@autoaiai/aicut 0.1.7 → 0.1.13
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/MANUAL.md +94 -84
- package/package.json +1 -1
- package/product.json +2 -1
- package/skills/aicut/SKILL.md +4 -4
- package/skills/aicut/references/commands.md +15 -15
- package/skills/aicut/references/film-format.md +1 -1
- package/skills/aicut/references/recipes.md +4 -4
- package/skills/aicut/references/troubleshooting.md +2 -2
- package/skills/aicut-motion/SKILL.md +75 -9
- package/skills/aicut-motion/THIRD_PARTY_NOTICES.md +14 -0
- package/skills/aicut-motion/assets/atlas/lib/harness.js +116 -0
- package/skills/aicut-motion/assets/atlas/lib/sound.js +274 -0
- package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/code.js +937 -0
- package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/001-flat-vector-science/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/code.js +909 -0
- package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/002-watercolor-wash/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/code.js +881 -0
- package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/003-shadow-puppet-theatre/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/code.js +897 -0
- package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/004-whiteboard-explainer/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/code.js +814 -0
- package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/005-psychedelic-poster/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/code.js +990 -0
- package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/006-tech-glow-explainer/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/code.js +994 -0
- package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/007-papercraft-models/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/code.js +792 -0
- package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/008-1940s-film-noir/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/code.js +840 -0
- package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/009-manim-style-math/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/code.js +1126 -0
- package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/010-illuminated-manuscript/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/code.js +854 -0
- package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/011-editorial-essay-collage/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/code.js +1124 -0
- package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/012-bioluminescent-deep-sea/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/code.js +728 -0
- package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/013-product-launch-reveal/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/code.js +664 -0
- package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/014-halftone-pop-panel/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/code.js +991 -0
- package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/015-isometric-pastel/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/code.js +801 -0
- package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/016-oscilloscope-vector-art/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/code.js +692 -0
- package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/017-risograph-zine/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/code.js +756 -0
- package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/018-claymation-set/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/code.js +922 -0
- package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/019-neon-sign-at-night/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/code.js +852 -0
- package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/020-kawaii-anime/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/code.js +724 -0
- package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/021-ukiyo-e-woodblock/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/code.js +793 -0
- package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/022-16-bit-pixel-odyssey/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/code.js +846 -0
- package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/023-chalkboard-lesson/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/code.js +1008 -0
- package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/024-cut-paper-diorama/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/code.js +680 -0
- package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/025-cathedral-glass/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/code.js +633 -0
- package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/026-editorial-chart/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/code.js +757 -0
- package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/027-synthwave-outrun/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/code.js +828 -0
- package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/028-origami-studio/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/code.js +697 -0
- package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/029-linocut-relief-print/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/code.js +687 -0
- package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/030-holographic-foil-card/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/code.js +727 -0
- package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/031-voxel-diorama/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/code.js +903 -0
- package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/032-sumi-e-ink-wash/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/code.js +662 -0
- package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/033-etch-a-sketch-toy/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/code.js +951 -0
- package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/034-cosmic-star-chart/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/code.js +759 -0
- package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/035-punk-zine-collage/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/code.js +738 -0
- package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/036-thermal-camera/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/code.js +793 -0
- package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/037-engineering-blueprint/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/code.js +673 -0
- package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/038-impasto-night/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/code.js +907 -0
- package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/039-win95-desktop/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/code.js +833 -0
- package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/040-low-poly-faceted/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/041-manga-page/code.js +845 -0
- package/skills/aicut-motion/assets/atlas/styles/041-manga-page/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/041-manga-page/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/code.js +496 -0
- package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/042-wood-type-letterpress/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/043-light-painting/code.js +635 -0
- package/skills/aicut-motion/assets/atlas/styles/043-light-painting/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/043-light-painting/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/code.js +799 -0
- package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/044-felt-patchwork/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/code.js +547 -0
- package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/045-phosphor-terminal/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/code.js +592 -0
- package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/046-glassmorphism-ui/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/code.js +768 -0
- package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/047-tarot-card/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/048-transit-map/code.js +666 -0
- package/skills/aicut-motion/assets/atlas/styles/048-transit-map/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/048-transit-map/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/code.js +559 -0
- package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/049-gouache-travel-poster/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/code.js +943 -0
- package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/050-napkin-sketch/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/code.js +606 -0
- package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/051-art-deco-foil/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/code.js +730 -0
- package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/052-radiograph-on-lightbox/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/code.js +782 -0
- package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/053-vaporwave/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/code.js +504 -0
- package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/054-ebru-paper-marbling/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/code.js +517 -0
- package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/055-isotype-pictograms/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/code.js +713 -0
- package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/056-soft-3d-clay/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/code.js +717 -0
- package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/057-cross-stitch-sampler/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/code.js +540 -0
- package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/058-constructivist-propaganda/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/code.js +524 -0
- package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/059-pocket-monochrome/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/code.js +934 -0
- package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/060-botanical-plate/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/code.js +690 -0
- package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/061-kinetic-type/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/code.js +612 -0
- package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/062-byzantine-mosaic/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/code.js +666 -0
- package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/063-y2k-liquid-chrome/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/code.js +651 -0
- package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/064-sticky-note-wall/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/code.js +656 -0
- package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/065-vintage-newsprint/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/code.js +672 -0
- package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/066-hidden-line-wireframe/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/code.js +548 -0
- package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/067-memphis-group/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/code.js +989 -0
- package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/068-ballpoint-notebook/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/code.js +440 -0
- package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/069-op-art-sphere/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/code.js +895 -0
- package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/070-topographic-survey/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/code.js +563 -0
- package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/071-pointillist-afternoon/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/code.js +627 -0
- package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/072-vector-arcade-1980/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/code.js +1096 -0
- package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/073-patent-drawing/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/code.js +595 -0
- package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/074-bauhaus-poster/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/code.js +665 -0
- package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/075-bitmap-paint-doodle/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/code.js +645 -0
- package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/076-nail-thread-string-art/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/code.js +436 -0
- package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/077-ridgeline/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/code.js +1237 -0
- package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/078-sketchbook-graphite/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/code.js +622 -0
- package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/079-teletext-page/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/code.js +524 -0
- package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/080-silkscreen-grid/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/code.js +689 -0
- package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/081-guilloch-security-print/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/code.js +574 -0
- package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/082-ascii-demoscene/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/code.js +469 -0
- package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/083-international-typographic-style/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/084-flow-field/code.js +531 -0
- package/skills/aicut-motion/assets/atlas/styles/084-flow-field/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/084-flow-field/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/code.js +675 -0
- package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/085-macpaint-1984/style.json +13 -0
- package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/code.js +609 -0
- package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/poster.jpg +0 -0
- package/skills/aicut-motion/assets/atlas/styles/086-multi-scale-truchet/style.json +13 -0
- package/skills/aicut-motion/references/design-systems/README.md +1 -1
- package/skills/aicut-motion/references/motion-delivery.md +55 -0
- package/skills/aicut-motion/references/pages.md +1 -1
- package/skills/aicut-motion/references/style-index.json +690 -0
- package/skills/aicut-motion/references/style-research.md +73 -0
- package/skills/aicut-motion/references/style-selection.md +71 -0
- package/skills/aicut-motion/scripts/atlas-viewer.html +195 -0
- package/skills/aicut-motion/scripts/atlas.mjs +109 -0
- package/studio/client.mjs +3 -3
- package/studio/server/fetch-github.mjs +14 -3
- package/studio/server/server.mjs +71 -6
- package/studio/server/skill-summaries.mjs +60 -0
- package/studio/server/skills.mjs +121 -7
- package/studio/server/styles-store.mjs +235 -0
- package/studio/server/template-posters.mjs +2 -2
- package/studio/server/templates-page.mjs +93 -93
- package/studio/server/templates.mjs +32 -7
- package/studio/ui/dist/assets/ExportCenter-DasulhAv.js +1 -0
- package/studio/ui/dist/assets/SettingsModal-BZw0Hz2_.js +2 -0
- package/studio/ui/dist/assets/apple-touch-icon-ClxQ7j-O.png +0 -0
- package/studio/ui/dist/assets/es-C8XPZARI.js +1 -0
- package/studio/ui/dist/assets/favicon-Blwx8Trs.png +0 -0
- package/studio/ui/dist/assets/index-BjJVmBzB.js +1623 -0
- package/studio/ui/dist/assets/index-C3BfKZFO.css +1 -0
- package/studio/ui/dist/assets/ja-BeNkdaEj.js +1 -0
- package/studio/ui/dist/assets/ko-DcTVhdzK.js +1 -0
- package/studio/ui/dist/assets/zh-CN-Cg51ctbL.js +1 -0
- package/studio/ui/dist/index.html +4 -3
- package/studio/ui/dist/assets/ExportCenter-F-2vF-lQ.js +0 -1
- package/studio/ui/dist/assets/SettingsModal-BgVYguNu.js +0 -2
- package/studio/ui/dist/assets/es-BRUzvuoM.js +0 -1
- package/studio/ui/dist/assets/index-Bhg-9dA3.css +0 -1
- package/studio/ui/dist/assets/index-CWur1Iw9.js +0 -1625
- package/studio/ui/dist/assets/ja-Cth-uDcW.js +0 -1
- package/studio/ui/dist/assets/ko-upvw591D.js +0 -1
- package/studio/ui/dist/assets/zh-CN-BCfJFazB.js +0 -1
|
@@ -0,0 +1,713 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------- soft "clay" 3D faked in 2D: extrusion + gradient face + inner shadows + rim + specular ----------
|
|
3
|
+
// rounded polygon with per-vertex radius (pts: [x,y,r])
|
|
4
|
+
function roundedPoly(pts) {
|
|
5
|
+
const p = new Path2D();
|
|
6
|
+
const n = pts.length;
|
|
7
|
+
const mid = (a, b) => [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2];
|
|
8
|
+
const start = mid(pts[n - 1], pts[0]);
|
|
9
|
+
p.moveTo(start[0], start[1]);
|
|
10
|
+
for (let i = 0; i < n; i++) {
|
|
11
|
+
const c = pts[i], nx = pts[(i + 1) % n];
|
|
12
|
+
const m = mid(c, nx);
|
|
13
|
+
p.arcTo(c[0], c[1], m[0], m[1], c[2] || 0);
|
|
14
|
+
}
|
|
15
|
+
p.closePath();
|
|
16
|
+
return p;
|
|
17
|
+
}
|
|
18
|
+
// inner shadow: the casting fill is pushed far off-canvas in device space so only its shadow lands (no hairline)
|
|
19
|
+
function innerShadow(ctx, path, rule, bb, color, blur, dx, dy) {
|
|
20
|
+
const OFF = 5000;
|
|
21
|
+
ctx.save();
|
|
22
|
+
ctx.clip(path, rule);
|
|
23
|
+
const p2 = new Path2D();
|
|
24
|
+
const m = 400;
|
|
25
|
+
p2.rect(bb[0] - m, bb[1] - m, bb[2] - bb[0] + 2 * m, bb[3] - bb[1] + 2 * m);
|
|
26
|
+
p2.addPath(path);
|
|
27
|
+
const M = ctx.getTransform();
|
|
28
|
+
ctx.setTransform(new DOMMatrix().translate(OFF, 0).multiply(M));
|
|
29
|
+
ctx.shadowColor = color; ctx.shadowBlur = blur; ctx.shadowOffsetX = dx - OFF; ctx.shadowOffsetY = dy;
|
|
30
|
+
ctx.fillStyle = '#000';
|
|
31
|
+
ctx.fill(p2, 'evenodd');
|
|
32
|
+
ctx.restore();
|
|
33
|
+
}
|
|
34
|
+
let EXT = null;
|
|
35
|
+
// o: {x,y,rot,s, path, rule, bb, face:[light,mid,dark], side:[light,dark], ext:[ex,ey], spec:[x,y,rx,ry]}
|
|
36
|
+
function puffy(ctx, o) {
|
|
37
|
+
const rule = o.rule || 'nonzero';
|
|
38
|
+
const [ex, ey] = o.ext;
|
|
39
|
+
const n = Math.ceil(Math.hypot(ex, ey) * 1.5);
|
|
40
|
+
const W = ctx.canvas.width, Hh = ctx.canvas.height;
|
|
41
|
+
const base = ctx.getTransform();
|
|
42
|
+
let lc = ctx;
|
|
43
|
+
if (!o.fast) {
|
|
44
|
+
if (!EXT || EXT.width !== W) EXT = H.layer(W, Hh);
|
|
45
|
+
lc = EXT.getContext('2d');
|
|
46
|
+
lc.setTransform(1, 0, 0, 1, 0, 0); lc.clearRect(0, 0, W, Hh);
|
|
47
|
+
} else ctx.save();
|
|
48
|
+
const bounce = H.mix(o.side[0], '#ffffff', 0.35);
|
|
49
|
+
for (let i = n; i >= 1; i--) {
|
|
50
|
+
const k = i / n;
|
|
51
|
+
lc.setTransform(base); lc.translate(o.x + ex * k, o.y + ey * k); lc.rotate(o.rot || 0); lc.scale(o.s, o.s);
|
|
52
|
+
const c = k > 0.86 ? H.mix(o.side[1], bounce, (k - 0.86) / 0.14) : k < 0.25 ? H.mix(o.face[2], o.side[0], k / 0.25) : H.mix(o.side[0], o.side[1], (k - 0.25) / 0.61);
|
|
53
|
+
lc.fillStyle = H.rgba(c);
|
|
54
|
+
lc.fill(o.path, rule);
|
|
55
|
+
}
|
|
56
|
+
if (!o.fast) { ctx.save(); ctx.setTransform(1, 0, 0, 1, 0, 0); ctx.filter = 'blur(1.4px)'; ctx.drawImage(EXT, 0, 0); ctx.restore(); }
|
|
57
|
+
else { ctx.restore(); ctx.setTransform(base); }
|
|
58
|
+
ctx.save(); ctx.translate(o.x, o.y); ctx.rotate(o.rot || 0); ctx.scale(o.s, o.s);
|
|
59
|
+
const bb = o.bb;
|
|
60
|
+
const g = ctx.createLinearGradient(bb[0], bb[1], bb[2], bb[3]);
|
|
61
|
+
g.addColorStop(0, H.rgba(o.face[0])); g.addColorStop(0.5, H.rgba(o.face[1])); g.addColorStop(1, H.rgba(o.face[2]));
|
|
62
|
+
ctx.fillStyle = g; ctx.fill(o.path, rule);
|
|
63
|
+
const S = o.s * Math.hypot(base.a, base.b);
|
|
64
|
+
innerShadow(ctx, o.path, rule, bb, H.rgba(o.side[1], 0.5), 40 * S, -16 * S, -20 * S); // core shadow toward bottom-right
|
|
65
|
+
innerShadow(ctx, o.path, rule, bb, H.rgba(o.side[0], 0.35), 14 * S, 0, 0); // rounded falloff at every edge
|
|
66
|
+
innerShadow(ctx, o.path, rule, bb, 'rgba(255,255,255,0.72)', 26 * S, 12 * S, 14 * S); // soft top-left highlight
|
|
67
|
+
if (o.spec) {
|
|
68
|
+
ctx.save(); ctx.clip(o.path, rule);
|
|
69
|
+
const [sx, sy, rx, ry] = o.spec;
|
|
70
|
+
const sg = ctx.createRadialGradient(sx, sy, 0, sx, sy, rx);
|
|
71
|
+
sg.addColorStop(0, 'rgba(255,255,255,0.95)'); sg.addColorStop(0.35, 'rgba(255,255,255,0.45)'); sg.addColorStop(1, 'rgba(255,255,255,0)');
|
|
72
|
+
ctx.translate(sx, sy); ctx.scale(1, ry / rx); ctx.translate(-sx, -sy);
|
|
73
|
+
ctx.fillStyle = sg; ctx.beginPath(); ctx.arc(sx, sy, rx, 0, 7); ctx.fill();
|
|
74
|
+
ctx.restore();
|
|
75
|
+
}
|
|
76
|
+
ctx.restore();
|
|
77
|
+
}
|
|
78
|
+
function sphere(ctx, x, y, r, light, mid, dark) {
|
|
79
|
+
const g = ctx.createRadialGradient(x - r * 0.35, y - r * 0.4, r * 0.05, x, y, r * 1.05);
|
|
80
|
+
g.addColorStop(0, H.rgba(light)); g.addColorStop(0.55, H.rgba(mid)); g.addColorStop(1, H.rgba(dark));
|
|
81
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, r, 0, 7); ctx.fill();
|
|
82
|
+
const p = new Path2D(); p.arc(x, y, r, 0, 7);
|
|
83
|
+
innerShadow(ctx, p, 'nonzero', [x - r, y - r, x + r, y + r], H.rgba(H.mix(light, '#ffffff', 0.5), 0.8), r * 0.12, -r * 0.05, -r * 0.06);
|
|
84
|
+
const s = ctx.createRadialGradient(x - r * 0.38, y - r * 0.45, 0, x - r * 0.38, y - r * 0.45, r * 0.42);
|
|
85
|
+
s.addColorStop(0, 'rgba(255,255,255,0.9)'); s.addColorStop(1, 'rgba(255,255,255,0)');
|
|
86
|
+
ctx.fillStyle = s; ctx.beginPath(); ctx.arc(x, y, r, 0, 7); ctx.fill();
|
|
87
|
+
}
|
|
88
|
+
function contactShadow(ctx, x, y, w, h, alpha, blur) {
|
|
89
|
+
ctx.save(); ctx.filter = `blur(${blur}px)`;
|
|
90
|
+
ctx.fillStyle = `rgba(110,80,170,${alpha})`;
|
|
91
|
+
ctx.beginPath(); ctx.ellipse(x, y, w, h, 0, 0, 7); ctx.fill();
|
|
92
|
+
ctx.filter = `blur(${blur * 0.4}px)`; ctx.fillStyle = `rgba(90,60,150,${alpha * 0.8})`;
|
|
93
|
+
ctx.beginPath(); ctx.ellipse(x, y, w * 0.55, h * 0.45, 0, 0, 7); ctx.fill();
|
|
94
|
+
ctx.restore();
|
|
95
|
+
}
|
|
96
|
+
// ---------- shapes (local coords) ----------
|
|
97
|
+
const bubblePath = () => roundedPoly([[-170, -115, 78], [170, -115, 78], [170, 95, 70], [-40, 95, 22], [-120, 160, 12], [-100, 95, 16], [-170, 95, 60]]);
|
|
98
|
+
const BUBBLE_BB = [-170, -115, 170, 160];
|
|
99
|
+
function gearPath(R, teeth, holeR) {
|
|
100
|
+
const pts = [];
|
|
101
|
+
const r0 = R * 0.78;
|
|
102
|
+
for (let i = 0; i < teeth; i++) {
|
|
103
|
+
const a = (i / teeth) * Math.PI * 2, w = (Math.PI * 2) / teeth;
|
|
104
|
+
pts.push([Math.cos(a - w * 0.5) * r0, Math.sin(a - w * 0.5) * r0, R * 0.12]);
|
|
105
|
+
pts.push([Math.cos(a - w * 0.2) * R, Math.sin(a - w * 0.2) * R, R * 0.1]);
|
|
106
|
+
pts.push([Math.cos(a + w * 0.2) * R, Math.sin(a + w * 0.2) * R, R * 0.1]);
|
|
107
|
+
}
|
|
108
|
+
const p = roundedPoly(pts);
|
|
109
|
+
p.moveTo(holeR, 0); p.arc(0, 0, holeR, 0, Math.PI * 2, true); p.closePath();
|
|
110
|
+
return p;
|
|
111
|
+
}
|
|
112
|
+
const cursorPath = () => roundedPoly([[0, 0, 10], [0, 150, 12], [38, 114, 8], [64, 168, 10], [92, 154, 10], [66, 102, 8], [114, 102, 12]]);
|
|
113
|
+
const CURSOR_BB = [0, 0, 114, 168];
|
|
114
|
+
function bulbGlassPath(r) {
|
|
115
|
+
const p = new Path2D();
|
|
116
|
+
// glass: big circle flowing into a neck
|
|
117
|
+
p.moveTo(-r * 0.5, r * 0.95);
|
|
118
|
+
p.bezierCurveTo(-r * 0.55, r * 0.6, -r, r * 0.45, -r, -0.05 * r);
|
|
119
|
+
p.arc(0, -0.05 * r, r, Math.PI, 0);
|
|
120
|
+
p.bezierCurveTo(r, r * 0.45, r * 0.55, r * 0.6, r * 0.5, r * 0.95);
|
|
121
|
+
p.closePath();
|
|
122
|
+
return p;
|
|
123
|
+
}
|
|
124
|
+
function ringPath(R, r) {
|
|
125
|
+
const p = new Path2D();
|
|
126
|
+
p.ellipse(0, 0, R, R * 0.42, 0, 0, Math.PI * 2);
|
|
127
|
+
p.moveTo(r, 0); p.ellipse(0, 0, r, r * 0.36, 0, 0, Math.PI * 2, true);
|
|
128
|
+
return p;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ================= EXPLAINER: a wide pastel set the camera dollies across (with parallax), clay props springing in =================
|
|
132
|
+
const HX = (h) => H.hex(h);
|
|
133
|
+
const PAL = {
|
|
134
|
+
lav: { face: [HX('#e6e0ff'), HX('#b9abff'), HX('#9a88f5')], side: [HX('#8f7cf0'), HX('#5d48c2')] },
|
|
135
|
+
peach: { face: [HX('#fff0e6'), HX('#ffcfb3'), HX('#ffad8a')], side: [HX('#f79d7a'), HX('#cf6b4f')] },
|
|
136
|
+
mint: { face: [HX('#e6fff6'), HX('#b3eedb'), HX('#86d7bf')], side: [HX('#7fcbb3'), HX('#4f9f8a')] },
|
|
137
|
+
white: { face: [HX('#ffffff'), HX('#f7f3ff'), HX('#e2d9fb')], side: [HX('#d4c8f5'), HX('#9a87de')] },
|
|
138
|
+
pink: { face: [HX('#ffe3ea'), HX('#ffb1c2'), HX('#ff8fa8')], side: [HX('#f57f9b'), HX('#cc4f72')] },
|
|
139
|
+
blue: { face: [HX('#e3f1ff'), HX('#b4d6ff'), HX('#8fbaf5')], side: [HX('#86b0ee'), HX('#5a7fc7')] },
|
|
140
|
+
coral: { face: [HX('#ffd9d2'), HX('#ff8f7e'), HX('#f2675a')], side: [HX('#e85d4f'), HX('#b23a33')] },
|
|
141
|
+
butter: { face: [HX('#fffbe0'), HX('#ffe58f'), HX('#f7c45e')], side: [HX('#f2b955'), HX('#d98f3a')] },
|
|
142
|
+
};
|
|
143
|
+
const pill = (w, h) => roundedPoly([[-w / 2, -h / 2, h / 2], [w / 2, -h / 2, h / 2], [w / 2, h / 2, h / 2], [-w / 2, h / 2, h / 2]]);
|
|
144
|
+
const rrect = (w, h, r) => roundedPoly([[-w / 2, -h / 2, r], [w / 2, -h / 2, r], [w / 2, h / 2, r], [-w / 2, h / 2, r]]);
|
|
145
|
+
function clay(ctx, path, bb, x, y, pal, o = {}) {
|
|
146
|
+
puffy(ctx, { x, y, rot: o.rot || 0, s: o.s || 1, path, bb, face: pal.face, side: pal.side, ext: o.ext || [6, 11], spec: o.spec, fast: o.fast, z: o.z });
|
|
147
|
+
}
|
|
148
|
+
function clayPet(ctx, kind, x, y, r) {
|
|
149
|
+
const fur = kind === 'cat' ? [HX('#efeaff'), HX('#c4b8ff'), HX('#9a88f5')] : [HX('#fff1e2'), HX('#ffcf9e'), HX('#e9a46a')];
|
|
150
|
+
ctx.save();
|
|
151
|
+
if (kind === 'cat') for (const sx of [-1, 1]) { const g = ctx.createLinearGradient(x, y - r * 1.2, x, y); g.addColorStop(0, H.rgba(fur[0])); g.addColorStop(1, H.rgba(fur[2])); ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(x + sx * 0.88 * r, y - 0.15 * r); ctx.quadraticCurveTo(x + sx * 0.8 * r, y - 1.25 * r, x + sx * 0.6 * r, y - 1.12 * r); ctx.quadraticCurveTo(x + sx * 0.3 * r, y - 0.95 * r, x + sx * 0.1 * r, y - 0.8 * r); ctx.closePath(); ctx.fill(); }
|
|
152
|
+
else for (const sx of [-1, 1]) { ctx.fillStyle = H.rgba(H.mix(fur[2], '#8a5a3b', 0.3)); ctx.beginPath(); ctx.ellipse(x + sx * 0.9 * r, y + 0.05 * r, 0.3 * r, 0.62 * r, sx * 0.35, 0, 7); ctx.fill(); }
|
|
153
|
+
sphere(ctx, x, y, r, fur[0], fur[1], fur[2]);
|
|
154
|
+
ctx.fillStyle = '#3b2f5c';
|
|
155
|
+
for (const sx of [-1, 1]) { ctx.beginPath(); ctx.ellipse(x + sx * 0.36 * r, y - 0.08 * r, r * 0.1, r * 0.14, 0, 0, 7); ctx.fill(); ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(x + sx * 0.36 * r - r * 0.03, y - 0.13 * r, r * 0.035, 0, 7); ctx.fill(); ctx.fillStyle = '#3b2f5c'; }
|
|
156
|
+
ctx.fillStyle = 'rgba(255,120,150,0.45)'; for (const sx of [-1, 1]) { ctx.beginPath(); ctx.ellipse(x + sx * 0.6 * r, y + 0.22 * r, r * 0.15, r * 0.09, 0, 0, 7); ctx.fill(); }
|
|
157
|
+
ctx.fillStyle = kind === 'cat' ? '#ff8fa8' : '#3b2f5c'; ctx.beginPath(); ctx.ellipse(x, y + 0.2 * r, r * (kind === 'cat' ? 0.09 : 0.16), r * (kind === 'cat' ? 0.07 : 0.11), 0, 0, 7); ctx.fill();
|
|
158
|
+
ctx.restore();
|
|
159
|
+
}
|
|
160
|
+
function clayLabel(ctx, x, y, text, pal, o = {}) {
|
|
161
|
+
const k = o.k ?? 1; if (k <= 0.01) return;
|
|
162
|
+
ctx.save(); ctx.translate(x, y); ctx.scale(k, k);
|
|
163
|
+
ctx.font = o.font || '700 28px "Fredoka"';
|
|
164
|
+
const fsz = parseInt((ctx.font.match(/(\d+)px/) || [0, 28])[1]);
|
|
165
|
+
const w = ctx.measureText(text).width + (o.padX || fsz * 1.5), h = o.h || Math.round(fsz * 1.8);
|
|
166
|
+
clay(ctx, pill(w, h), [-w / 2, -h / 2, w / 2, h / 2], 0, 0, pal, { ext: [3, 6], fast: true, z: o.z });
|
|
167
|
+
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
168
|
+
const col = o.col || '#4a3a8a';
|
|
169
|
+
if (col === '#fff') { ctx.fillStyle = 'rgba(70,45,170,0.32)'; ctx.fillText(text, fsz * 0.06, 2 + fsz * 0.09); } // pressed-in ink shadow, like the bubble text
|
|
170
|
+
else { ctx.fillStyle = 'rgba(255,255,255,0.55)'; ctx.fillText(text, 0, 3.5); } // dark text: soft lower highlight (debossed)
|
|
171
|
+
ctx.fillStyle = col; ctx.fillText(text, 0, 2);
|
|
172
|
+
ctx.restore();
|
|
173
|
+
}
|
|
174
|
+
function clayTube(ctx, a, c, b, prog, pal, w = 14) {
|
|
175
|
+
if (prog <= 0) return null;
|
|
176
|
+
const q = (u) => [(1 - u) * (1 - u) * a[0] + 2 * u * (1 - u) * c[0] + u * u * b[0], (1 - u) * (1 - u) * a[1] + 2 * u * (1 - u) * c[1] + u * u * b[1]];
|
|
177
|
+
const N = 36, pts = []; for (let i = 0; i <= N; i++) pts.push(q(prog * i / N * 0.93));
|
|
178
|
+
const path = (off) => { ctx.beginPath(); pts.forEach((p, i) => i ? ctx.lineTo(p[0] + off[0], p[1] + off[1]) : ctx.moveTo(p[0] + off[0], p[1] + off[1])); };
|
|
179
|
+
ctx.save(); ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
|
180
|
+
ctx.strokeStyle = 'rgba(90,60,150,0.18)'; ctx.lineWidth = w; path([3, 7]); ctx.stroke();
|
|
181
|
+
ctx.strokeStyle = H.rgba(pal.side[1]); ctx.lineWidth = w; path([0, 0]); ctx.stroke();
|
|
182
|
+
ctx.strokeStyle = H.rgba(pal.face[1]); ctx.lineWidth = w * 0.72; path([-w * 0.1, -w * 0.12]); ctx.stroke();
|
|
183
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.75)'; ctx.lineWidth = w * 0.2; path([-w * 0.2, -w * 0.25]); ctx.stroke();
|
|
184
|
+
const e = q(prog), e0 = q(Math.max(0, prog - 0.05)), ang = Math.atan2(e[1] - e0[1], e[0] - e0[0]);
|
|
185
|
+
ctx.restore();
|
|
186
|
+
const hp = roundedPoly([[w * 1.3, 0, w * 0.35], [-w * 0.9, -w * 1.15, w * 0.35], [-w * 0.9, w * 1.15, w * 0.35]]);
|
|
187
|
+
clay(ctx, hp, [-w, -w, w, w], e[0], e[1], pal, { rot: ang, ext: [3, 6], fast: true });
|
|
188
|
+
return q;
|
|
189
|
+
}
|
|
190
|
+
let FG_SPHERE = null;
|
|
191
|
+
function fgSphere() { // the hero's blurred foreground sphere, baked once
|
|
192
|
+
if (FG_SPHERE) return FG_SPHERE;
|
|
193
|
+
const a = H.layer(320, 320), ac = a.getContext('2d');
|
|
194
|
+
sphere(ac, 160, 160, 100, HX('#f3eeff'), HX('#d4c8ff'), HX('#a998ec'));
|
|
195
|
+
const b = H.layer(320, 320), bc = b.getContext('2d');
|
|
196
|
+
bc.filter = 'blur(9px)'; bc.drawImage(a, 0, 0);
|
|
197
|
+
return (FG_SPHERE = b);
|
|
198
|
+
}
|
|
199
|
+
function softExplainer(ctx, W, H_, t) {
|
|
200
|
+
const ph = (a, b) => H.clamp((t - a) / (b - a));
|
|
201
|
+
const E = (a, b) => H.ease.inOut(ph(a, b));
|
|
202
|
+
const EO = (a, b) => H.ease.out(ph(a, b));
|
|
203
|
+
const spring = (t0, d = 0.8) => { const p = ph(t0, t0 + d); if (p <= 0) return 0; return p >= 1 ? 1 : 1 - Math.exp(-6.5 * p) * Math.cos(10 * p); };
|
|
204
|
+
const INK = '#4a3a8a';
|
|
205
|
+
// ---------- camera: [t, centreX, centreY, zoom] over a wide set ----------
|
|
206
|
+
const KF = [[0, 640, 360, 1], [1.6, 640, 360, 1], [2.8, 980, 360, 1], [5.8, 1020, 360, 1], [6.8, 1560, 360, 1], [10.2, 1580, 360, 1], [11.1, 2560, 360, 1], [12.8, 2580, 360, 1], [13.8, 1790, 400, 0.44], [20, 1790, 400, 0.44]];
|
|
207
|
+
let k = 0; while (k < KF.length - 2 && t > KF[k + 1][0]) k++;
|
|
208
|
+
const A = KF[k], B = KF[k + 1], u = H.ease.inOut(H.clamp((t - A[0]) / (B[0] - A[0])));
|
|
209
|
+
const camX = H.lerp(A[1], B[1], u), camY = H.lerp(A[2], B[2], u), zoom = H.lerp(A[3], B[3], u);
|
|
210
|
+
const bob = (p, a) => Math.sin(t * 1.6 + p) * a;
|
|
211
|
+
const FLOOR = 610;
|
|
212
|
+
|
|
213
|
+
// ---------- seamless cyclorama (screen space) + parallax far props ----------
|
|
214
|
+
const bg = ctx.createLinearGradient(0, 0, 0, H_);
|
|
215
|
+
bg.addColorStop(0, '#f3ecff'); bg.addColorStop(0.5, '#e8dffb'); bg.addColorStop(0.62, '#dccff5'); bg.addColorStop(0.78, '#e6dcf9'); bg.addColorStop(1, '#efe7fe');
|
|
216
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
217
|
+
const glow = ctx.createRadialGradient(W * 0.5, H_ * 0.35, 40, W * 0.5, H_ * 0.35, 700);
|
|
218
|
+
glow.addColorStop(0, 'rgba(255,250,255,0.8)'); glow.addColorStop(1, 'rgba(255,250,255,0)');
|
|
219
|
+
ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H_);
|
|
220
|
+
const world = (c, par = 1) => { c.translate(W / 2, H_ / 2); c.scale(zoom, zoom); c.translate(-camX * par, -camY); };
|
|
221
|
+
{
|
|
222
|
+
const far = H.layer(W, H_), fc = far.getContext('2d');
|
|
223
|
+
fc.save(); world(fc, 0.6);
|
|
224
|
+
const fr = H.rng(3);
|
|
225
|
+
for (let i = 0; i < 16; i++) { const x = i * 260 + fr() * 120, y = 90 + fr() * 200, r = 12 + fr() * 22; const p = fr.pick([[HX('#fff6c7'), HX('#ffe08a'), HX('#f3b860')], [HX('#d8f5ec'), HX('#a6e3d2'), HX('#6cbfb0')], [HX('#f3eeff'), HX('#d4c8ff'), HX('#a998ec')], [HX('#ffe3ea'), HX('#ffb1c2'), HX('#ff8fa8')]]); sphere(fc, x, y + bob(i, 6), r, ...p); }
|
|
226
|
+
fc.restore();
|
|
227
|
+
ctx.save(); ctx.filter = 'blur(3px)'; ctx.drawImage(far, 0, 0); ctx.restore();
|
|
228
|
+
}
|
|
229
|
+
ctx.save(); world(ctx);
|
|
230
|
+
const Z = zoom;
|
|
231
|
+
// warm key-light glows behind each prop as it arrives (the hero's lit-bulb aura)
|
|
232
|
+
const keyGlow = (x, y, r, a) => {
|
|
233
|
+
a = H.clamp(a); if (a <= 0.01) return;
|
|
234
|
+
const g = ctx.createRadialGradient(x, y, r * 0.06, x, y, r);
|
|
235
|
+
g.addColorStop(0, `rgba(255,214,120,${0.26 * a})`); g.addColorStop(0.45, `rgba(255,222,160,${0.11 * a})`); g.addColorStop(1, 'rgba(255,230,180,0)');
|
|
236
|
+
ctx.fillStyle = g; ctx.fillRect(x - r, y - r, 2 * r, 2 * r);
|
|
237
|
+
};
|
|
238
|
+
keyGlow(1330, 330, 340, spring(1.0, 0.9));
|
|
239
|
+
if (t > 6.8) keyGlow(1830, 260, 320, spring(6.9, 0.7));
|
|
240
|
+
if (t > 10.5) keyGlow(2650, 330, 460, spring(10.5, 0.7));
|
|
241
|
+
|
|
242
|
+
// ================= SET 1/2: data → the model (chip) =================
|
|
243
|
+
const CHIP = [1350, 360 + bob(0.3, 6)];
|
|
244
|
+
// contact shadows first
|
|
245
|
+
contactShadow(ctx, CHIP[0] + 10, FLOOR, 150 * spring(1.0), 22, 0.3, 18);
|
|
246
|
+
// data cards travelling into the chip (beat 2) + test card (beat 3)
|
|
247
|
+
const cards = [];
|
|
248
|
+
if (t > 2.3 && t < 6.5) for (let j = 0; j < 9; j++) {
|
|
249
|
+
const p = (t - 2.3) * 0.36 - j * 0.15; if (p < 0 || p > 1) continue;
|
|
250
|
+
cards.push({ p, kind: (j * 5 + 1) % 3 === 0 ? 'dog' : (j % 2 ? 'cat' : 'dog'), j });
|
|
251
|
+
}
|
|
252
|
+
const cardPos = (p) => { const x = H.lerp(560, CHIP[0] - 40, p), y = 420 - Math.sin(p * Math.PI) * 170 + (p > 0.85 ? (p - 0.85) * 300 * 0 : 0); return [x, y, 1 - H.smooth(0.75, 1, p) * 0.75]; };
|
|
253
|
+
// big puffy tube path showing the flow
|
|
254
|
+
if (t > 2.6 && t < 6.6) clayTube(ctx, [520, 470], [900, 140], [CHIP[0] - 150, 330], E(2.6, 3.5) * (1 - ph(6.2, 6.6)), PAL.white, 18);
|
|
255
|
+
// pile of examples (grows) on the floor left
|
|
256
|
+
{
|
|
257
|
+
const n = Math.floor(1 + 7 * Math.pow(EO(2.6, 5.8), 1.1));
|
|
258
|
+
const pk0 = spring(2.6, 0.6);
|
|
259
|
+
if (pk0 > 0) contactShadow(ctx, 600 + Math.min(n, 3) * 38 + (n > 3 ? 9 : 0), FLOOR + 2, (40 + Math.min(n, 3) * 34) * Math.min(1, pk0), 11, 0.3, 9);
|
|
260
|
+
for (let i = 0; i < n; i++) {
|
|
261
|
+
const k2 = spring(2.6 + i * 0.4, 0.6); if (k2 <= 0) continue;
|
|
262
|
+
const row = Math.floor(i / 3), x = 590 + (i % 3) * 76 + row * 18, y = FLOOR - 26 - row * 50;
|
|
263
|
+
ctx.save(); ctx.translate(x, y); ctx.scale(k2, k2);
|
|
264
|
+
clay(ctx, rrect(70, 46, 14), [-35, -23, 35, 23], 0, 0, i % 2 ? PAL.peach : PAL.lav, { ext: [3, 7], fast: true });
|
|
265
|
+
ctx.restore();
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
for (const cd of cards) {
|
|
269
|
+
const [x, y, sc] = cardPos(cd.p);
|
|
270
|
+
ctx.save(); ctx.translate(x, y); ctx.scale(sc, sc); ctx.rotate(Math.sin(cd.j * 1.7 + t * 2) * 0.12);
|
|
271
|
+
clay(ctx, rrect(96, 96, 26), [-48, -48, 48, 48], 0, 0, PAL.white, { ext: [5, 9], fast: true });
|
|
272
|
+
clayPet(ctx, cd.kind, 0, 4, 28);
|
|
273
|
+
if (cd.p < 0.82) clayLabel(ctx, 30, -52, cd.kind, cd.kind === 'cat' ? PAL.lav : PAL.peach, { font: '700 28px "Fredoka"', col: '#fff' });
|
|
274
|
+
ctx.restore();
|
|
275
|
+
}
|
|
276
|
+
let testPos = null;
|
|
277
|
+
if (t > 5.9 && t < 7.2) {
|
|
278
|
+
const p = E(5.9, 7.0); const [x, y, sc] = cardPos(p * 0.98); testPos = [x, y];
|
|
279
|
+
ctx.save(); ctx.translate(x, y); ctx.scale(sc * 1.1, sc * 1.1);
|
|
280
|
+
clay(ctx, rrect(96, 96, 26), [-48, -48, 48, 48], 0, 0, PAL.pink, { ext: [5, 9], fast: true });
|
|
281
|
+
clayPet(ctx, 'cat', 0, 4, 28);
|
|
282
|
+
clayLabel(ctx, 34, -54, '?', PAL.coral, { font: '700 28px "Fredoka"', col: '#fff' });
|
|
283
|
+
ctx.restore();
|
|
284
|
+
}
|
|
285
|
+
// the chip (model) with three weight sliders on its face
|
|
286
|
+
const ck = spring(1.0, 0.9);
|
|
287
|
+
const adj = E(8.6, 9.3);
|
|
288
|
+
const SL0 = [0.2, 0.78, 0.38], SL1 = [0.66, 0.3, 0.82];
|
|
289
|
+
const jitter = t > 8.6 && t < 9.3 ? Math.sin(t * 40) * 0.03 : 0;
|
|
290
|
+
if (ck > 0) {
|
|
291
|
+
ctx.save(); ctx.translate(CHIP[0], CHIP[1] + 110); ctx.scale(ck, ck); ctx.translate(-CHIP[0], -(CHIP[1] + 110));
|
|
292
|
+
// pins
|
|
293
|
+
for (let i = 0; i < 4; i++) for (const [dx, dy, w, h] of [[-118, -66 + i * 44, 34, 16], [118, -66 + i * 44, 34, 16], [-66 + i * 44, -118, 16, 34], [-66 + i * 44, 118, 16, 34]]) clay(ctx, rrect(w, h, 7), [-w / 2, -h / 2, w / 2, h / 2], CHIP[0] + dx, CHIP[1] + dy, PAL.butter, { ext: [3, 6], fast: true });
|
|
294
|
+
const err = ph(8.0, 8.4) * (1 - ph(9.0, 9.4));
|
|
295
|
+
clay(ctx, rrect(220, 220, 50), [-110, -110, 110, 110], CHIP[0], CHIP[1], err > 0 ? { face: PAL.lav.face.map(c => H.mix(c, HX('#ff8f7e'), err * 0.6)), side: PAL.lav.side } : PAL.lav, { ext: [10, 18], spec: [-60, -62, 60, 30] });
|
|
296
|
+
ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic';
|
|
297
|
+
ctx.font = '700 34px "Fredoka"'; ctx.fillStyle = 'rgba(255,255,255,0.6)'; ctx.fillText('AI', CHIP[0] - 82, CHIP[1] - 52.5);
|
|
298
|
+
ctx.fillStyle = INK; ctx.fillText('AI', CHIP[0] - 82, CHIP[1] - 54);
|
|
299
|
+
ctx.font = '600 22px "Fredoka"'; ctx.fillStyle = 'rgba(255,255,255,0.5)'; ctx.fillText('weights', CHIP[0] - 30, CHIP[1] - 55);
|
|
300
|
+
ctx.fillStyle = 'rgba(74,58,138,0.85)'; ctx.fillText('weights', CHIP[0] - 30, CHIP[1] - 56);
|
|
301
|
+
for (let i = 0; i < 3; i++) {
|
|
302
|
+
const ty = CHIP[1] - 14 + i * 42;
|
|
303
|
+
ctx.fillStyle = 'rgba(70,50,160,0.28)'; ctx.beginPath(); ctx.roundRect(CHIP[0] - 76, ty - 7, 152, 14, 7); ctx.fill();
|
|
304
|
+
ctx.fillStyle = 'rgba(255,255,255,0.4)'; ctx.beginPath(); ctx.roundRect(CHIP[0] - 76, ty + 4, 152, 3, 2); ctx.fill();
|
|
305
|
+
const v = H.lerp(SL0[i], SL1[i], adj) + jitter * (i + 1);
|
|
306
|
+
sphere(ctx, CHIP[0] - 70 + v * 140, ty, 14, err > 0 && adj < 1 && t > 8.4 ? HX('#ffe3df') : HX('#ffffff'), err > 0 && t > 8.4 ? HX('#ffb3a6') : HX('#f2ecff'), HX('#b9a8f2'));
|
|
307
|
+
}
|
|
308
|
+
ctx.restore();
|
|
309
|
+
}
|
|
310
|
+
// counter badge
|
|
311
|
+
if (t > 2.7 && t < 6.9) {
|
|
312
|
+
const cnt = Math.floor(1e6 * Math.pow(EO(2.9, 5.7), 1.6));
|
|
313
|
+
const kb = spring(2.7) * (1 - E(6.5, 6.9));
|
|
314
|
+
ctx.save(); ctx.translate(980, 98); ctx.scale(kb, kb);
|
|
315
|
+
clay(ctx, pill(540, 116), [-270, -58, 270, 58], 0, 0, PAL.butter, { ext: [6, 12], spec: [-150, -28, 90, 18] });
|
|
316
|
+
ctx.font = '700 68px "Fredoka"'; ctx.fillStyle = '#8a5a1f'; ctx.textAlign = 'right'; ctx.textBaseline = 'middle'; ctx.fillText(cnt.toLocaleString('en-US'), 88, 4);
|
|
317
|
+
ctx.font = '700 34px "Fredoka"'; ctx.textAlign = 'left'; ctx.fillText('examples', 102, 10);
|
|
318
|
+
ctx.restore();
|
|
319
|
+
}
|
|
320
|
+
if (t > 4.0 && t < 6.6) clayLabel(ctx, CHIP[0], CHIP[1] - 175, 'the model', PAL.lav, { k: spring(4.0) * (1 - E(6.3, 6.6)), col: '#fff' });
|
|
321
|
+
|
|
322
|
+
// ================= SET 3: guess → check → adjust =================
|
|
323
|
+
const BUB = [1840, 250 + bob(1.1, 6)];
|
|
324
|
+
const wrong = t > 7.0 && t < 9.4, right = t >= 9.4;
|
|
325
|
+
if (t > 6.8) {
|
|
326
|
+
const bk = spring(6.9, 0.7);
|
|
327
|
+
const sk = t > 7.4 && t < 7.8 ? Math.sin(t * 70) * 6 : 0;
|
|
328
|
+
contactShadow(ctx, BUB[0] + 10, FLOOR, 120 * bk, 18, 0.22, 16);
|
|
329
|
+
ctx.save(); ctx.translate(BUB[0] + sk, BUB[1] + 100); ctx.scale(bk * 0.82, bk * 0.82); ctx.translate(-BUB[0], -(BUB[1] + 100));
|
|
330
|
+
const pal = right ? PAL.mint : wrong && t > 7.4 ? PAL.peach : PAL.white;
|
|
331
|
+
clay(ctx, bubblePath(), BUBBLE_BB, BUB[0], BUB[1], pal, { ext: [10, 18], spec: [-95, -78, 85, 40] });
|
|
332
|
+
clayPet(ctx, right ? 'cat' : 'dog', BUB[0] - 80, BUB[1] - 8, 46);
|
|
333
|
+
ctx.font = '700 64px "Fredoka"'; ctx.textAlign = 'left'; ctx.textBaseline = 'middle';
|
|
334
|
+
ctx.fillStyle = 'rgba(70,45,170,0.25)'; ctx.fillText(right ? 'cat' : 'dog?', BUB[0] - 16, BUB[1] + 4);
|
|
335
|
+
ctx.fillStyle = '#ffffff'; ctx.fillText(right ? 'cat' : 'dog?', BUB[0] - 19, BUB[1]);
|
|
336
|
+
ctx.restore();
|
|
337
|
+
// verdict badge (puffy ball with ✗ / ✓)
|
|
338
|
+
const verdict = () => {
|
|
339
|
+
const vk = right ? spring(9.45, 0.7) : spring(7.4, 0.6);
|
|
340
|
+
const bx = BUB[0] + 140, by = BUB[1] - 100;
|
|
341
|
+
ctx.save(); ctx.translate(bx, by); ctx.scale(vk, vk);
|
|
342
|
+
sphere(ctx, 0, 0, 38, right ? HX('#d9fff0') : HX('#ffd9d2'), right ? HX('#5ee0b0') : HX('#ff7a68'), right ? HX('#2fae84') : HX('#c8403a'));
|
|
343
|
+
ctx.strokeStyle = '#fff'; ctx.lineWidth = 9; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath();
|
|
344
|
+
if (right) { ctx.moveTo(-14, 0); ctx.lineTo(-4, 11); ctx.lineTo(16, -12); } else { ctx.moveTo(-12, -12); ctx.lineTo(12, 12); ctx.moveTo(12, -12); ctx.lineTo(-12, 12); }
|
|
345
|
+
ctx.stroke(); ctx.restore();
|
|
346
|
+
};
|
|
347
|
+
if (t > 7.4) verdict();
|
|
348
|
+
const fade = 1 - E(10.3, 10.7);
|
|
349
|
+
ctx.save(); ctx.globalAlpha = fade;
|
|
350
|
+
// 1 Guess tube: chip → bubble
|
|
351
|
+
clayTube(ctx, [CHIP[0] + 120, CHIP[1] - 80], [1600, 120], [BUB[0] - 170, BUB[1] - 40], E(6.85, 7.25), PAL.lav, 16);
|
|
352
|
+
clayLabel(ctx, 1580, 150, '1 Guess', PAL.white, { k: spring(7.0) });
|
|
353
|
+
if (t > 7.45) clayLabel(ctx, BUB[0] + 30, BUB[1] - 175, '2 Check', PAL.white, { k: spring(7.45) });
|
|
354
|
+
// emphasis: pulsing ring + "answer: cat"
|
|
355
|
+
if (wrong && t > 7.45) {
|
|
356
|
+
const pr = EO(7.45, 7.9);
|
|
357
|
+
// a puffy coral clay torus (cast shadow, darker side, lit face, two highlights) instead of a flat stroke
|
|
358
|
+
ctx.save(); ctx.lineCap = 'round'; ctx.globalAlpha = fade * (0.78 + 0.22 * Math.sin(t * 9));
|
|
359
|
+
const rr = 1.25 - 0.25 * pr, a0 = -Math.PI / 2, a1 = a0 + Math.PI * 2 * pr;
|
|
360
|
+
const ring = (dx, dy, col, lw) => { ctx.strokeStyle = col; ctx.lineWidth = lw; ctx.beginPath(); ctx.ellipse(BUB[0] + dx, BUB[1] + 10 + dy, 210 * rr, 150 * rr, -0.05, a0, a1); ctx.stroke(); };
|
|
361
|
+
ring(5, 14, 'rgba(110,70,160,0.13)', 18);
|
|
362
|
+
ring(0, 4, '#c8483e', 16);
|
|
363
|
+
ring(0, 0, '#ff7a68', 14);
|
|
364
|
+
ring(-1, -2, 'rgba(255,186,170,0.75)', 7);
|
|
365
|
+
ring(-2, -3.5, 'rgba(255,255,255,0.6)', 3);
|
|
366
|
+
ctx.restore();
|
|
367
|
+
verdict(); // keep the ✗ ball in front of the ring
|
|
368
|
+
clayLabel(ctx, BUB[0] + 10, BUB[1] + 205, 'answer: cat', PAL.lav, { k: spring(7.65), col: '#fff' });
|
|
369
|
+
}
|
|
370
|
+
// 3 Adjust tube: bubble → sliders (coral)
|
|
371
|
+
if (t > 8.0) {
|
|
372
|
+
const q = clayTube(ctx, [BUB[0] - 60, BUB[1] + 150], [1640, 640], [CHIP[0] + 100, CHIP[1] + 60], E(8.0, 8.6), PAL.coral, 16);
|
|
373
|
+
if (q && t < 8.75) { const p = q(E(8.0, 8.6) * 0.93); sphere(ctx, p[0], p[1], 15, HX('#ffd9d2'), HX('#ff7a68'), HX('#c8403a')); }
|
|
374
|
+
clayLabel(ctx, 1610, 600, '3 Adjust', PAL.coral, { k: spring(8.5), col: '#fff' });
|
|
375
|
+
if (t < 9.4) clayLabel(ctx, 1700, 500, 'error', PAL.peach, { k: spring(8.1) * (1 - E(9.1, 9.4)), font: '700 28px "Fredoka"', col: '#c8403a' });
|
|
376
|
+
}
|
|
377
|
+
if (right) clayLabel(ctx, BUB[0] + 40, BUB[1] - 175, 'Guess again', PAL.mint, { k: spring(9.5), col: '#fff' });
|
|
378
|
+
ctx.restore();
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// ================= SET 4: progress — clay bar chart =================
|
|
382
|
+
const ACCS = [61, 72, 80, 86, 90, 93, 95, 97];
|
|
383
|
+
const BX = 2330, BW = 56, GAP = 22;
|
|
384
|
+
if (t > 10.5) {
|
|
385
|
+
const pk = spring(10.5, 0.7);
|
|
386
|
+
contactShadow(ctx, BX + 320, FLOOR + 6, 380 * pk, 22, 0.25, 18);
|
|
387
|
+
ctx.save(); ctx.translate(BX + 310, FLOOR - 10); ctx.scale(pk, pk);
|
|
388
|
+
clay(ctx, pill(700, 40), [-350, -20, 350, 20], 0, 0, PAL.white, { ext: [5, 9], fast: true });
|
|
389
|
+
ctx.restore();
|
|
390
|
+
let topP = null;
|
|
391
|
+
ACCS.forEach((a, i) => {
|
|
392
|
+
const g = spring(10.95 + i * 0.2, 0.8); if (g <= 0) return;
|
|
393
|
+
const hh = (a - 50) * 7 * g, x = BX + i * (BW + GAP) + 40, y = FLOOR - 30 - hh / 2;
|
|
394
|
+
if (hh > 4) clay(ctx, rrect(BW, hh, Math.min(26, hh / 2)), [-BW / 2, -hh / 2, BW / 2, hh / 2], x, y, i === 7 ? PAL.coral : (i % 2 ? PAL.blue : PAL.mint), { ext: [5, 9], fast: true });
|
|
395
|
+
topP = [x, FLOOR - 30 - hh, i];
|
|
396
|
+
});
|
|
397
|
+
if (topP) {
|
|
398
|
+
const cur = Math.round(H.lerp(61, 97, H.ease.inOut(ph(11.0, 12.6))));
|
|
399
|
+
const pop = t > 12.6 ? 1 + 0.18 * Math.sin(Math.min(1, (t - 12.6) / 0.4) * Math.PI) : 1;
|
|
400
|
+
// counter-scale the readout about its base during the final pull-back so 97% stays >= 64px on screen
|
|
401
|
+
const cz = Math.max(1, 0.93 / Z), ax = topP[0] - 30, ay = topP[1] - 70 + 52;
|
|
402
|
+
ctx.save(); ctx.translate(ax, ay); ctx.scale(cz, cz); ctx.translate(-ax, -ay);
|
|
403
|
+
clayLabel(ctx, topP[0] - 30, topP[1] - 70, `${cur}%`, cur >= 97 ? PAL.coral : PAL.butter, { k: spring(10.95) * pop, font: '700 70px "Fredoka"', h: 104, padX: 60, col: cur >= 97 ? '#fff' : '#8a5a1f' });
|
|
404
|
+
clayLabel(ctx, topP[0] - 30, topP[1] - 160, 'Accuracy', PAL.lav, { k: spring(10.95) * pop, font: '700 32px "Fredoka"', col: '#fff' });
|
|
405
|
+
ctx.restore();
|
|
406
|
+
if (t < 13.3) clayLabel(ctx, BX + 40, FLOOR - 30 - 11 * 7 - 36, 'round 1: 61%', PAL.white, { k: spring(11.2) * (1 - E(13.0, 13.3)), font: '700 28px "Fredoka"' });
|
|
407
|
+
if (t < 13.3) { ctx.save(); ctx.globalAlpha = EO(11.2, 11.6) * (1 - E(13.0, 13.3)); ctx.font = '700 28px "Fredoka"'; ctx.fillStyle = 'rgba(80,58,160,0.85)'; ctx.textAlign = 'center'; ctx.fillText('training rounds →', BX + 310, FLOOR + 44); ctx.restore(); }
|
|
408
|
+
if (t > 12.6 && t < 13.6) { // confetti spheres burst
|
|
409
|
+
const cr = H.rng(5), pp = EO(12.6, 13.6);
|
|
410
|
+
for (let i = 0; i < 14; i++) { const a = cr() * Math.PI * 2, d = 60 + cr() * 120; const col = cr.pick([[HX('#ffe3ea'), HX('#ffb1c2'), HX('#ff8fa8')], [HX('#fff6c7'), HX('#ffe08a'), HX('#f3b860')], [HX('#d8f5ec'), HX('#a6e3d2'), HX('#6cbfb0')]]); ctx.globalAlpha = 1 - pp; sphere(ctx, topP[0] + Math.cos(a) * d * pp, topP[1] - 52 + Math.sin(a) * d * pp + pp * pp * 40, 9, ...col); }
|
|
411
|
+
ctx.globalAlpha = 1;
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
ctx.restore(); // end world
|
|
416
|
+
|
|
417
|
+
// ---------- screen-space atmosphere: soft lavender vignette + an out-of-focus foreground sphere (hero DOF) ----------
|
|
418
|
+
{
|
|
419
|
+
const vg = ctx.createRadialGradient(W * 0.5, H_ * 0.45, H_ * 0.45, W * 0.5, H_ * 0.45, W * 0.78);
|
|
420
|
+
vg.addColorStop(0, 'rgba(120,90,200,0)'); vg.addColorStop(1, 'rgba(120,90,200,0.11)');
|
|
421
|
+
ctx.fillStyle = vg; ctx.fillRect(0, 0, W, H_);
|
|
422
|
+
ctx.drawImage(fgSphere(), 1238 - (camX - 640) * 0.04 - 160, 678 + bob(4, 3) - 160);
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ---------- hook bubble → corner header pill (screen space) ----------
|
|
426
|
+
{
|
|
427
|
+
const inK = spring(0.1, 0.9), out = E(1.5, 2.2);
|
|
428
|
+
if (out < 1) {
|
|
429
|
+
const s2 = inK * (1 - out * 0.75);
|
|
430
|
+
const x = H.lerp(640, 170, out), y = H.lerp(330, 60, out);
|
|
431
|
+
if (out < 0.6 && inK > 0) { // the hook bubble floats over the set: warm glow + soft contact shadow under it
|
|
432
|
+
const fa = 1 - H.smooth(0, 0.6, out), g = ctx.createRadialGradient(640, 320, 30, 640, 320, 380);
|
|
433
|
+
g.addColorStop(0, `rgba(255,214,120,${0.22 * fa * Math.min(1, inK)})`); g.addColorStop(1, 'rgba(255,224,170,0)');
|
|
434
|
+
ctx.fillStyle = g; ctx.fillRect(260, 0, 760, 700);
|
|
435
|
+
contactShadow(ctx, 650, FLOOR, 170 * Math.min(1.1, inK) * (1 - out * 0.8), 22, 0.28 * fa, 18);
|
|
436
|
+
}
|
|
437
|
+
ctx.save(); ctx.globalAlpha = 1 - H.smooth(0.6, 1, out); ctx.translate(x, y + 100 * s2); ctx.scale(Math.max(0.001, s2) * 1.15, Math.max(0.001, s2) * 1.05); ctx.translate(0, -100);
|
|
438
|
+
clay(ctx, bubblePath(), BUBBLE_BB, 0, 0, PAL.lav, { ext: [12, 22], spec: [-95, -78, 85, 40] });
|
|
439
|
+
ctx.font = '700 40px "Fredoka"'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
440
|
+
ctx.fillStyle = 'rgba(70,45,170,0.35)'; ctx.fillText('How does', 3, -24 + 4); ctx.fillText('AI learn?', 3, 24 + 4);
|
|
441
|
+
ctx.fillStyle = '#ffffff'; ctx.fillText('How does', 0, -24); ctx.fillText('AI learn?', 0, 24);
|
|
442
|
+
ctx.restore();
|
|
443
|
+
}
|
|
444
|
+
if (t > 1.9) clayLabel(ctx, 170, 58, 'How does AI learn?', PAL.lav, { k: spring(1.9, 0.7), font: '700 28px "Fredoka"', h: 54, padX: 44, col: '#fff' });
|
|
445
|
+
}
|
|
446
|
+
// ---------- takeaway ----------
|
|
447
|
+
if (t > 13.0) {
|
|
448
|
+
const kk = spring(13.0, 0.8);
|
|
449
|
+
ctx.save(); ctx.translate(640, 628); ctx.scale(kk, kk);
|
|
450
|
+
clay(ctx, pill(860, 100), [-430, -50, 430, 50], 0, 0, PAL.white, { ext: [7, 13], spec: [-260, -22, 140, 18] });
|
|
451
|
+
ctx.font = '700 46px "Fredoka"'; ctx.fillStyle = INK; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText('More examples → better guesses', 0, 3);
|
|
452
|
+
ctx.restore();
|
|
453
|
+
}
|
|
454
|
+
ctx.font = '600 20px "Fredoka"'; ctx.fillStyle = `rgba(85,62,165,${0.78 * (1 - E(12.9, 13.2))})`; ctx.textAlign = 'left'; ctx.textBaseline = 'alphabetic'; ctx.fillText('AI ASSISTANTS · 101', 64, 692); // steps aside for the takeaway pill
|
|
455
|
+
H.grain(ctx, W, H_, 0.018, 9 + Math.floor(t * 12));
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// ---------- sound: squishy clay set (rubbery bounces as things spring on, soft puffs, gentle camera dollies) ----------
|
|
459
|
+
function softSound(k) {
|
|
460
|
+
const D = 14.5;
|
|
461
|
+
const ph = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
462
|
+
const inv = (u) => (u < 0.5 ? Math.cbrt(u / 4) : 1 - Math.cbrt((1 - u) * 2) / 2); // inverse of H.ease.inOut
|
|
463
|
+
const invO = (u) => 1 - Math.cbrt(1 - u); // inverse of H.ease.out
|
|
464
|
+
// spring(t0, d) from softExplainer first reaches full size at p = pi/20 (overshoot peak at pi/10)
|
|
465
|
+
const sp = (t0, d = 0.8) => t0 + d * (Math.PI / 20);
|
|
466
|
+
// panning: world x on the set -> screen, using the camera keyframes copied from softExplainer (KF)
|
|
467
|
+
const KF = [[0, 640, 360, 1], [1.6, 640, 360, 1], [2.8, 980, 360, 1], [5.8, 1020, 360, 1], [6.8, 1560, 360, 1], [10.2, 1580, 360, 1], [11.1, 2560, 360, 1], [12.8, 2580, 360, 1], [13.8, 1790, 400, 0.44], [20, 1790, 400, 0.44]];
|
|
468
|
+
const pan = (wx, t) => { let i = 0; while (i < KF.length - 2 && t > KF[i + 1][0]) i++; const A = KF[i], B = KF[i + 1], u = H.ease.inOut(H.clamp((t - A[0]) / (B[0] - A[0]))); const sx = 640 + (wx - H.lerp(A[1], B[1], u)) * H.lerp(A[3], B[3], u); return H.clamp(sx / 640 - 1, -1, 1) * 0.6; };
|
|
469
|
+
const busy = [];
|
|
470
|
+
// squish: a soft rubbery bounce, pitch bends up as it squashes then settles with one smaller wobble
|
|
471
|
+
const squish = (t, f, g, p = 0, minor) => {
|
|
472
|
+
busy.push(t);
|
|
473
|
+
k.tone(t, { f: [[0, f * 0.7], [0.05, f * 1.18], [0.17, f * 0.92]], dur: 0.2, gain: 0.26 * g, a: 0.006, pan: p, minor });
|
|
474
|
+
k.tone(t + 0.13, { f: [[0, f * 0.95], [0.05, f * 1.06], [0.12, f]], dur: 0.13, gain: 0.08 * g, a: 0.01, pan: p, minor }); // the overshoot wobble
|
|
475
|
+
k.noise(t, { dur: 0.05, color: 'pink', filter: 'lowpass', f: 900, gain: 0.18 * g, a: 0.004, pan: p, minor }); // soft contact puff
|
|
476
|
+
};
|
|
477
|
+
const puff = (t, g, p = 0) => busy.push(t) && k.noise(t, { dur: 0.09, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.09, 900]], q: 1, gain: 0.12 * g, a: 0.02, pan: p, minor: true });
|
|
478
|
+
const dolly = (t0, t1, g = 1) => { const d = t1 - t0; k.noise(t0, { dur: d + 0.15, color: 'pink', filter: 'bandpass', f: [[0, 220], [d * 0.5, 480], [d, 240]], q: 0.7, gain: 0.045 * g, a: d * 0.5, verb: 0.1 }); };
|
|
479
|
+
const counter = (a, b, val, f, g, p, minGap) => {
|
|
480
|
+
let last = -1, lastV = val(a); const top = Math.abs(val(b) - val(a)) / (b - a) * 0.22;
|
|
481
|
+
for (let t = a; t <= b + 1e-6; t += 1 / 120) {
|
|
482
|
+
const v = val(t); if (v === lastV) continue; lastV = v;
|
|
483
|
+
const speed = Math.abs(val(Math.min(b, t + 0.05)) - val(Math.max(a, t - 0.05)));
|
|
484
|
+
const gap = H.clamp(minGap / Math.max(0.2, Math.min(1, speed / top)), minGap, 0.4);
|
|
485
|
+
if (t - last >= gap && !busy.some((x) => t > x - 0.03 && t < x + 0.27)) { k.play('tick', t, { f, gain: g, pan: p, minor: true }); k.tone(t, { f: f / 10, dur: 0.03, gain: 0.05 * g, pan: p, minor: true }); last = t; }
|
|
486
|
+
}
|
|
487
|
+
};
|
|
488
|
+
|
|
489
|
+
k.bed('room', 0, D, { gain: 0.22 });
|
|
490
|
+
k.music('pad', 0.2, D, { bpm: 100, gain: 0.3, chords: [['Eb4', 'G4', 'Bb4', 'D5'], ['C4', 'Eb4', 'G4', 'Bb4'], ['Ab3', 'C4', 'Eb4', 'G4'], ['Bb3', 'D4', 'F4', 'Ab4'], ['Eb4', 'G4', 'Bb4', 'Eb5']] });
|
|
491
|
+
|
|
492
|
+
// ---- hook: the clay speech bubble bounces on (spring 0.1, 0.9), the chip springs up behind (1.0, 0.9)
|
|
493
|
+
squish(sp(0.1, 0.9), 300, 1.2, 0);
|
|
494
|
+
squish(sp(1.0, 0.9), 170, 1.1, pan(1350, 1.14)); // the AI chip
|
|
495
|
+
k.noise(1.5, { dur: 0.7, color: 'pink', filter: 'lowpass', f: [[0, 650], [0.7, 300]], gain: 0.05, a: 0.25, minor: true }); // bubble shrinks off to the corner
|
|
496
|
+
squish(sp(1.9, 0.7), 420, 0.4, -0.7, true); // header pill
|
|
497
|
+
dolly(1.6, 2.8);
|
|
498
|
+
|
|
499
|
+
// ---- beat 2: cards puff out of the tube, the pile grows, the counter badge
|
|
500
|
+
k.noise(2.6, { dur: 0.9, color: 'pink', filter: 'bandpass', f: [[0, 260], [0.9, 620]], q: 1.4, gain: 0.07, a: 0.4, pan: pan(700, 3), minor: true }); // puffy tube squeezes out (E 2.6-3.5)
|
|
501
|
+
for (let j = 0; j < 9; j++) { const t = 2.3 + j * 0.15 / 0.36; if (t < 6.4) puff(t, 0.9, pan(560, t)); } // a card pops out of the tube
|
|
502
|
+
for (let j = 0; j < 4; j++) { const t = 2.3 + (1 + j * 0.15) / 0.36; if (t < 6.5) k.tone(t, { f: [[0, 240], [0.1, 150]], dur: 0.14, gain: 0.1, pan: pan(1310, t), minor: true }); } // swallowed by the chip
|
|
503
|
+
// examples pile: item i springs at 2.6 + 0.4i once n = floor(1 + 7 * EO(2.6,5.8)^1.1) > i
|
|
504
|
+
for (let i = 0; i < 8; i++) { const tn = i === 0 ? 2.6 : 2.6 + 3.2 * invO(Math.pow(i / 7, 1 / 1.1)), t = sp(Math.max(tn, 2.6 + i * 0.4), 0.6); if (t < 6.4) squish(t, 260 + (i % 3) * 30, 0.32, pan(600 + (i % 3) * 76, t), true); }
|
|
505
|
+
squish(sp(2.7), 360, 0.8, pan(980, 2.8)); // butter counter badge
|
|
506
|
+
counter(2.95, 5.66, (t) => Math.floor(1e6 * Math.pow(H.ease.out(ph(t, 2.9, 5.7)), 1.6)), 2400, 0.75, pan(980, 4), 1 / 8);
|
|
507
|
+
squish(sp(4.0), 380, 0.75, pan(1350, 4.1)); // "the model"
|
|
508
|
+
squish(5.7, 330, 0.9, pan(980, 5.7)); k.play('ding', 5.7, { f: 'Bb5', gain: 0.35, pan: pan(980, 5.7) }); // 1,000,000 lands
|
|
509
|
+
|
|
510
|
+
// ---- beat 3
|
|
511
|
+
squish(5.95, 340, 0.8, pan(560, 5.95)); // pink test card with its "?"
|
|
512
|
+
dolly(5.8, 6.8);
|
|
513
|
+
k.noise(6.5, { dur: 0.4, color: 'pink', filter: 'lowpass', f: [[0, 700], [0.4, 300]], gain: 0.04, a: 0.1, minor: true }); // counter badge squashes away
|
|
514
|
+
k.tone(7.0, { f: [[0, 200], [0.12, 130]], dur: 0.16, gain: 0.12, pan: pan(1310, 7.0), minor: true }); // test card swallowed
|
|
515
|
+
k.noise(6.85, { dur: 0.4, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.4, 700]], q: 1.4, gain: 0.07, a: 0.2, pan: pan(1600, 7.05), minor: true }); // Guess tube squeezes over
|
|
516
|
+
squish(sp(6.9, 0.7), 280, 1.1, pan(1840, 7.0)); // answer bubble "dog?" bounces up
|
|
517
|
+
squish(sp(7.0), 420, 0.42, pan(1580, 7.1), true); // "1 Guess"
|
|
518
|
+
// CHECK: wrong. bubble shakes, the X ball bounces on
|
|
519
|
+
k.play('error', 7.42, { f: 'G3', gain: 0.8, pan: pan(1980, 7.45) });
|
|
520
|
+
squish(sp(7.4, 0.6), 220, 0.8, pan(1980, 7.45));
|
|
521
|
+
squish(sp(7.65), 360, 0.4, pan(1850, 7.75), true); // "answer: cat"
|
|
522
|
+
// ADJUST: coral tube squeezes back to the chip, the error ball rolls along it
|
|
523
|
+
k.noise(8.0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 700], [0.6, 300]], q: 1.4, gain: 0.08, a: 0.3, pan: pan(1640, 8.3) });
|
|
524
|
+
k.tone(8.0, { f: [[0, 300], [0.6, 180]], type: 'triangle', dur: 0.62, gain: 0.05, a: 0.25, pan: pan(1640, 8.3), minor: true });
|
|
525
|
+
squish(sp(8.1), 460, 0.38, pan(1700, 8.2), true); // "error"
|
|
526
|
+
squish(sp(8.5), 340, 0.8, pan(1610, 8.6)); // "3 Adjust"
|
|
527
|
+
// sliders glide to new weights (E 8.6-9.3 with a jitter): three rubber knobs sliding
|
|
528
|
+
[0, 1, 2].forEach((i) => k.noise(8.62 + i * 0.05, { dur: 0.62, color: 'pink', filter: 'bandpass', f: [[0, 380 + i * 60], [0.3, 640 + i * 60], [0.62, 420 + i * 60]], q: 3, gain: 0.07, a: 0.25, pan: pan(1350, 9), minor: true }));
|
|
529
|
+
squish(9.3, 300, 0.5, pan(1350, 9.3), true); // knobs settle
|
|
530
|
+
// CORRECT: bubble turns mint "cat", the check ball bounces on
|
|
531
|
+
k.play('success', sp(9.45, 0.7), { f: 'Eb5', gain: 0.42, pan: pan(1980, 9.55) });
|
|
532
|
+
squish(sp(9.45, 0.7), 300, 0.8, pan(1980, 9.55));
|
|
533
|
+
squish(sp(9.5), 420, 0.38, pan(1880, 9.6), true); // "Guess again"
|
|
534
|
+
|
|
535
|
+
// ---- beat 4: clay bars bounce up one per round
|
|
536
|
+
dolly(10.2, 11.1);
|
|
537
|
+
squish(sp(10.5, 0.7), 180, 0.7, pan(2640, 10.6), true); // chart base
|
|
538
|
+
squish(sp(10.95), 360, 0.7, pan(2330, 11.05)); // Accuracy badge
|
|
539
|
+
['Eb3', 'F3', 'G3', 'Bb3', 'C4', 'D4', 'Eb4', 'G4'].forEach((n, i) => squish(sp(10.95 + i * 0.2), k.hz(n), i === 7 ? 0.7 : 0.45, pan(2370 + i * 78, 11 + i * 0.2), i < 7));
|
|
540
|
+
counter(11.03, 12.56, (t) => Math.round(H.lerp(61, 97, H.ease.inOut(ph(t, 11.0, 12.6)))), 2200, 0.7, pan(2700, 12), 1 / 12);
|
|
541
|
+
// 97%: the big hit. badge swells coral, confetti spheres burst
|
|
542
|
+
k.play('impact', 12.6, { gain: 1.3, pan: pan(2950, 12.6) });
|
|
543
|
+
squish(12.6, 240, 1.4, pan(2950, 12.6));
|
|
544
|
+
['Eb4', 'G4', 'Bb4', 'Eb5'].forEach((n, i) => k.play('ding', 12.62 + i * 0.03, { f: n, gain: 0.4 - i * 0.05, pan: (i - 1.5) * 0.25 }));
|
|
545
|
+
for (let i = 0; i < 6; i++) k.tone(12.68 + i * 0.07, { f: [[0, 380 + i * 30], [0.06, 520 + i * 30]], dur: 0.08, gain: 0.07, pan: Math.sin(i * 2.1) * 0.6, minor: true }); // little bouncy confetti balls
|
|
546
|
+
dolly(12.8, 13.8, 0.9);
|
|
547
|
+
// takeaway pill springs on (13.0, 0.8) and the phrase resolves
|
|
548
|
+
squish(sp(13.0), 260, 1.0, 0);
|
|
549
|
+
['Bb4', 'Eb5'].forEach((n, i) => k.play('ding', 13.32 + i * 0.09, { f: n, gain: 0.3 - i * 0.05, pan: (i - 0.5) * 0.3 }));
|
|
550
|
+
k.tone(13.32, { f: 'Eb3', type: 'triangle', dur: 1.2, gain: 0.12, a: 0.02 });
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
registerStyle({
|
|
554
|
+
id: 'soft-3d',
|
|
555
|
+
name: 'Soft 3D Clay',
|
|
556
|
+
category: 'Dimensional',
|
|
557
|
+
description: 'Dribbble-style “puffy” 3D icons: pastel matte-clay objects with chunky rounded extrusions, soft core shadows, bright bounce-light rims, glossy specular blobs and blurry contact shadows on a seamless pastel sweep.',
|
|
558
|
+
motion: 'Clay icons drop in one by one on springy squash-and-stretch with their contact shadows growing beneath them, the bulb flickers on, then the cursor flies in and taps the bubble while everything bobs at different depths.',
|
|
559
|
+
duration: 5,
|
|
560
|
+
fonts: ['600 38px "Fredoka"', '700 38px "Fredoka"', '600 15px "Fredoka"', '700 64px "Fredoka"', '600 18px "Fredoka"'],
|
|
561
|
+
frameT: 4.6,
|
|
562
|
+
readsPixels: true,
|
|
563
|
+
explainerDuration: 14.5,
|
|
564
|
+
explainerNote: 'The camera dollies across one wide pastel set with parallax depth-of-field (cards → clay chip with weight sliders → answer bubble → clay bar chart → pull back), arrows are puffy clay tubes with blobby heads, labels are squishy pills that spring onto objects, and numbers count inside butter-yellow clay badges.',
|
|
565
|
+
explainer: softExplainer,
|
|
566
|
+
explainerSound: softSound,
|
|
567
|
+
draw(ctx, W, H_, t) {
|
|
568
|
+
// ---------- seamless cyclorama ----------
|
|
569
|
+
const bg = ctx.createLinearGradient(0, 0, 0, H_);
|
|
570
|
+
bg.addColorStop(0, '#f3ecff'); bg.addColorStop(0.55, '#e6dcfa'); bg.addColorStop(0.62, '#e4d9f8'); bg.addColorStop(1, '#f1eaff');
|
|
571
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
572
|
+
const glow = ctx.createRadialGradient(W * 0.48, H_ * 0.35, 40, W * 0.48, H_ * 0.35, 700);
|
|
573
|
+
glow.addColorStop(0, 'rgba(255,250,255,0.8)'); glow.addColorStop(1, 'rgba(255,250,255,0)');
|
|
574
|
+
ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H_);
|
|
575
|
+
|
|
576
|
+
const bob = (ph, amp) => Math.sin(t * 1.6 + ph) * amp;
|
|
577
|
+
const FLOOR = 610;
|
|
578
|
+
const ph = (a, b) => H.clamp((t - a) / (b - a));
|
|
579
|
+
// springy pop-in: drops in from above, overshoots, squashes and stretches, settles
|
|
580
|
+
const spring = (t0) => {
|
|
581
|
+
const p = ph(t0, t0 + 0.95);
|
|
582
|
+
if (p <= 0) return null;
|
|
583
|
+
const k = p >= 1 ? 1 : 1 - Math.exp(-6.5 * p) * Math.cos(10 * p);
|
|
584
|
+
const st = p >= 1 ? 0 : Math.pow(1 - p, 2) * Math.sin(p * 13) * 0.22;
|
|
585
|
+
return { k, sx: k * (1 - st), sy: k * (1 + st), dy: -(1 - H.ease.out(Math.min(1, p * 1.6))) * 70 };
|
|
586
|
+
};
|
|
587
|
+
const pop = (c, x, y, t0, fn) => {
|
|
588
|
+
const sp = spring(t0); if (!sp) return;
|
|
589
|
+
c.save(); c.translate(x, y + sp.dy); c.scale(Math.max(0.001, sp.sx), Math.max(0.001, sp.sy)); c.translate(-x, -y);
|
|
590
|
+
fn(); c.restore();
|
|
591
|
+
};
|
|
592
|
+
const cshadow = (x, y, w, h, a, bl, t0) => { const sp = spring(t0); if (!sp) return; const k = Math.min(1.1, sp.k) * (1 + sp.dy / 200); contactShadow(ctx, x, y, w * k, h * k, a * Math.min(1, sp.k), bl); };
|
|
593
|
+
|
|
594
|
+
// ---------- far layer (blurred for depth of field) ----------
|
|
595
|
+
const far = H.layer(W, H_), fc = far.getContext('2d');
|
|
596
|
+
const gy = 250 + bob(1.2, 9);
|
|
597
|
+
cshadow(985, FLOOR - 90, 95, 16, 0.22, 16, 0.8);
|
|
598
|
+
pop(fc, 975, gy, 0.8, () => puffy(fc, { x: 975, y: gy, rot: t * 0.35 + 0.2 - (1 - ph(0.8, 2.2)) * 1.2, s: 0.92, path: gearPath(118, 8, 40), rule: 'evenodd', bb: [-118, -118, 118, 118],
|
|
599
|
+
face: [H.hex('#e3f1ff'), H.hex('#b4d6ff'), H.hex('#8fbaf5')], side: [H.hex('#86b0ee'), H.hex('#5a7fc7')], ext: [10, 20], spec: [-50, -60, 50, 30] }));
|
|
600
|
+
pop(fc, 1150, 120, 1.0, () => sphere(fc, 1150, 120 + bob(2.5, 6), 22, H.hex('#fff6c7'), H.hex('#ffe08a'), H.hex('#f3b860')));
|
|
601
|
+
pop(fc, 180, 110, 1.2, () => sphere(fc, 180, 110 + bob(0.4, 5), 16, H.hex('#d8f5ec'), H.hex('#a6e3d2'), H.hex('#6cbfb0')));
|
|
602
|
+
ctx.save(); ctx.filter = 'blur(2.2px)'; ctx.drawImage(far, 0, 0); ctx.restore();
|
|
603
|
+
|
|
604
|
+
// ---------- mid layer: lightbulb (left) ----------
|
|
605
|
+
{
|
|
606
|
+
const bx = 300, by = 262 + bob(0.6, 11), r = 92, s = 1, rot = -0.14;
|
|
607
|
+
cshadow(318, FLOOR - 40, 80, 14, 0.22, 14, 0.5);
|
|
608
|
+
// glow aura: the bulb "switches on" with a flicker once it has landed
|
|
609
|
+
const on = t > 1.7 ? (t < 1.78 || (t > 1.86 && t < 1.92) ? 0.3 : 1) * H.ease.out(ph(1.7, 2.1)) : 0;
|
|
610
|
+
const a = ctx.createRadialGradient(bx, by - 10, 20, bx, by - 10, 210);
|
|
611
|
+
a.addColorStop(0, `rgba(255,236,150,${0.55 * on})`); a.addColorStop(1, 'rgba(255,236,150,0)');
|
|
612
|
+
ctx.fillStyle = a; ctx.fillRect(bx - 220, by - 230, 440, 440);
|
|
613
|
+
pop(ctx, bx, by + 120, 0.5, () => {
|
|
614
|
+
for (let i = 2; i >= 0; i--) {
|
|
615
|
+
const ox = Math.sin(rot) * -(r * 1.05 + i * 26), oy = Math.cos(rot) * (r * 1.05 + i * 26);
|
|
616
|
+
const w = 54 - i * 4;
|
|
617
|
+
puffy(ctx, { x: bx + ox, y: by + oy, rot, s, path: roundedPoly([[-w, -14, 14], [w, -14, 14], [w, 14, 14], [-w, 14, 14]]), bb: [-w, -14, w, 14],
|
|
618
|
+
face: [H.hex('#e3fbf3'), H.hex('#b3eadb'), H.hex('#86d3c0')], side: [H.hex('#7fcbb8'), H.hex('#4f9f93')], ext: [5, 9] });
|
|
619
|
+
}
|
|
620
|
+
{
|
|
621
|
+
const ox = Math.sin(rot) * -(r * 1.05 + 3 * 26 - 6), oy = Math.cos(rot) * (r * 1.05 + 3 * 26 - 6);
|
|
622
|
+
puffy(ctx, { x: bx + ox, y: by + oy, rot, s, path: roundedPoly([[-26, -12, 12], [26, -12, 12], [12, 12, 8], [-12, 12, 8]]), bb: [-26, -12, 26, 12],
|
|
623
|
+
face: [H.hex('#a39cc4'), H.hex('#7d74a8'), H.hex('#5f5689')], side: [H.hex('#5a5185'), H.hex('#3f3866')], ext: [4, 7] });
|
|
624
|
+
}
|
|
625
|
+
const off = 1 - on;
|
|
626
|
+
puffy(ctx, { x: bx, y: by, rot, s, path: bulbGlassPath(r), bb: [-r, -r, r, r],
|
|
627
|
+
face: [H.mix('#fffbe0', '#f4efe6', off * 0.6), H.mix('#ffe58f', '#e8dcc6', off * 0.6), H.mix('#f7c45e', '#cdbb9e', off * 0.6)], side: [H.mix('#f2b955', '#c4ae8c', off * 0.6), H.mix('#d98f3a', '#9c8566', off * 0.6)], ext: [8, 14], spec: [-38, -52, 40, 26] });
|
|
628
|
+
ctx.save(); ctx.translate(bx, by); ctx.rotate(rot);
|
|
629
|
+
ctx.strokeStyle = `rgba(232,150,60,${0.25 + 0.3 * on})`; ctx.lineWidth = 7; ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
|
630
|
+
ctx.beginPath(); ctx.moveTo(-18, 70); ctx.lineTo(-18, 18); ctx.lineTo(-9, 4); ctx.lineTo(0, 18); ctx.lineTo(9, 4); ctx.lineTo(18, 18); ctx.lineTo(18, 70); ctx.stroke();
|
|
631
|
+
ctx.strokeStyle = `rgba(255,255,240,${0.9 * on})`; ctx.lineWidth = 2.5;
|
|
632
|
+
ctx.beginPath(); ctx.moveTo(-19, 68); ctx.lineTo(-19, 17); ctx.lineTo(-10, 3); ctx.lineTo(-1, 17); ctx.lineTo(8, 3); ctx.lineTo(17, 17); ctx.stroke();
|
|
633
|
+
ctx.restore();
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// ---------- hero: chat bubble ----------
|
|
638
|
+
const cbx = 640, cby = 305 + bob(0, 8);
|
|
639
|
+
const tapAt = 2.6, tapT = t - tapAt;
|
|
640
|
+
const tapPress = tapT > 0 ? Math.max(0, Math.sin(tapT * 2.4 * Math.PI / 2.4 * 1.0)) : 0; // 0..1 press cycles
|
|
641
|
+
const squish = tapT > 0 ? Math.pow(Math.max(0, Math.sin(tapT * Math.PI * 0.8)), 6) * 0.03 : 0;
|
|
642
|
+
cshadow(650, FLOOR, 170, 22, 0.3, 18, 0.15);
|
|
643
|
+
pop(ctx, cbx, cby + 120, 0.15, () => {
|
|
644
|
+
ctx.save(); ctx.translate(cbx, cby + 100); ctx.scale(1 + squish, 1 - squish); ctx.translate(-cbx, -(cby + 100));
|
|
645
|
+
puffy(ctx, { x: cbx, y: cby, rot: -0.04, s: 1, path: bubblePath(), bb: BUBBLE_BB,
|
|
646
|
+
face: [H.hex('#d9d0ff'), H.hex('#a796ff'), H.hex('#8c78f5')], side: [H.hex('#806ce8'), H.hex('#5340b8')], ext: [12, 22], spec: [-95, -78, 85, 40] });
|
|
647
|
+
// text on bubble: each line bounces in
|
|
648
|
+
ctx.save(); ctx.translate(cbx, cby); ctx.rotate(-0.04);
|
|
649
|
+
ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
650
|
+
ctx.font = '700 38px "Fredoka"';
|
|
651
|
+
[['Ask me', -26, 1.0], ['anything', 22, 1.25]].forEach(([str, y, t0]) => {
|
|
652
|
+
const sp = spring(t0); if (!sp) return;
|
|
653
|
+
ctx.save(); ctx.translate(0, y); ctx.scale(sp.sx, sp.sy);
|
|
654
|
+
ctx.fillStyle = 'rgba(70,45,170,0.35)'; ctx.fillText(str, 3, 4);
|
|
655
|
+
ctx.fillStyle = '#ffffff'; ctx.fillText(str, 0, 0);
|
|
656
|
+
ctx.restore();
|
|
657
|
+
});
|
|
658
|
+
ctx.restore();
|
|
659
|
+
ctx.restore();
|
|
660
|
+
});
|
|
661
|
+
// typing-dots badge
|
|
662
|
+
{
|
|
663
|
+
const px = cbx + 150, py = cby - 118;
|
|
664
|
+
pop(ctx, px, py, 1.6, () => {
|
|
665
|
+
puffy(ctx, { x: px, y: py, rot: 0.1, s: 1, path: roundedPoly([[-42, -22, 22], [42, -22, 22], [42, 22, 22], [-42, 22, 22]]), bb: [-42, -22, 42, 22],
|
|
666
|
+
face: [H.hex('#ffffff'), H.hex('#f4efff'), H.hex('#ddd2fb')], side: [H.hex('#cdbff5'), H.hex('#9a87de')], ext: [5, 9] });
|
|
667
|
+
for (let i = 0; i < 3; i++) {
|
|
668
|
+
const jump = Math.max(0, Math.sin(t * 6 - i * 0.9)) * 5;
|
|
669
|
+
sphere(ctx, px - 22 + i * 22, py + i * 2.2 - 2 - jump, 7.5, H.hex('#c8bcff'), H.hex('#9481f0'), H.hex('#6a55cf'));
|
|
670
|
+
}
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// ---------- ring (donut) bottom-left ----------
|
|
675
|
+
{
|
|
676
|
+
const rx = 150, ry = 525 + bob(2.2, 4);
|
|
677
|
+
cshadow(rx + 8, FLOOR - 10, 92, 14, 0.25, 12, 1.0);
|
|
678
|
+
pop(ctx, rx, ry + 30, 1.0, () => puffy(ctx, { x: rx, y: ry, rot: 0.08, s: 1, path: ringPath(92, 46), rule: 'evenodd', bb: [-92, -40, 92, 40],
|
|
679
|
+
face: [H.hex('#fff3d6'), H.hex('#ffd59a'), H.hex('#f6b36d')], side: [H.hex('#efa760'), H.hex('#c97a3e')], ext: [4, 16], spec: [-50, -22, 34, 12] }));
|
|
680
|
+
}
|
|
681
|
+
// sphere resting on floor
|
|
682
|
+
cshadow(1095, FLOOR - 4, 46, 9, 0.35, 8, 1.15);
|
|
683
|
+
pop(ctx, 1090, 600, 1.15, () => sphere(ctx, 1090, 560, 42, H.hex('#d8f5ec'), H.hex('#a6e3d2'), H.hex('#5fb3a3')));
|
|
684
|
+
|
|
685
|
+
// ---------- near layer: cursor (flies in, then taps the bubble) ----------
|
|
686
|
+
{
|
|
687
|
+
const tap = tapPress * 10;
|
|
688
|
+
const fly = H.ease.out(ph(1.35, 2.2));
|
|
689
|
+
const cx = 790 - tap * 0.6 + (1 - fly) * 260, cy = 395 - tap + bob(3.1, 4) + (1 - fly) * 160;
|
|
690
|
+
const rot = -0.32 + (1 - fly) * 0.5;
|
|
691
|
+
if (fly > 0) {
|
|
692
|
+
contactShadow(ctx, 870 + (1 - fly) * 200, FLOOR + 20, 70, 14, 0.25 * fly, 14);
|
|
693
|
+
puffy(ctx, { x: cx, y: cy, rot, s: 1.08, path: cursorPath(), bb: CURSOR_BB,
|
|
694
|
+
face: [H.hex('#ffe3ea'), H.hex('#ffb1c2'), H.hex('#ff8fa8')], side: [H.hex('#f57f9b'), H.hex('#cc4f72')], ext: [10, 16], spec: [26, 40, 30, 50] });
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
// ---------- out-of-focus foreground element (drifts in from the corner) ----------
|
|
699
|
+
{
|
|
700
|
+
const fi = H.ease.out(ph(0.3, 1.6));
|
|
701
|
+
const fg = H.layer(W, H_), gc = fg.getContext('2d');
|
|
702
|
+
sphere(gc, 1238 + (1 - fi) * 160, 678 + (1 - fi) * 90 + bob(4, 3), 100, H.hex('#f3eeff'), H.hex('#d4c8ff'), H.hex('#a998ec'));
|
|
703
|
+
ctx.save(); ctx.filter = 'blur(9px)'; ctx.drawImage(fg, 0, 0); ctx.restore();
|
|
704
|
+
}
|
|
705
|
+
// ---------- caption chip ----------
|
|
706
|
+
ctx.font = '600 20px "Fredoka"'; ctx.fillStyle = `rgba(85,62,165,${0.8 * ph(1.8, 2.3)})`; ctx.textAlign = 'left';
|
|
707
|
+
try { ctx.letterSpacing = '1px'; } catch (e) {}
|
|
708
|
+
ctx.fillText('AI ASSISTANTS · 101', 64, 676);
|
|
709
|
+
try { ctx.letterSpacing = '0px'; } catch (e) {}
|
|
710
|
+
H.grain(ctx, W, H_, 0.018, 9 + Math.floor(t * 12));
|
|
711
|
+
},
|
|
712
|
+
});
|
|
713
|
+
}
|