@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,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 70,
|
|
3
|
+
"name": "Topographic Survey",
|
|
4
|
+
"nameZh": "地形测绘",
|
|
5
|
+
"family": "Data & Information",
|
|
6
|
+
"familyZh": "数据与信息",
|
|
7
|
+
"description": "A vintage survey quadrangle of a \"loss landscape\": brown contour lines with heavier labelled index contours, soft hillshade and woodland tint, a water-lined lake, a red dashed switchback trail and a full printed collar with scale bar and north arrow.",
|
|
8
|
+
"motionNotes": "Motion: Printed plate by plate: the black frame and collar, then the grey hillshade, the green woodland sliding into register, the blue lake, then brown contours rising from the shore like a tide; streams trace downhill, names land, and the red trail walks itself down the switchbacks while the camera eases in.",
|
|
9
|
+
"explainerNotes": "Explainer: The loss landscape is literally the map: survey points (examples) raise the terrain, a red hiker guesses, checks the slope and steps down a dashed switchback trail while the camera pans along it, and accuracy is lettered in a map cartouche like an elevation figure.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "topographic",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|
|
@@ -0,0 +1,563 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------------------------------------------------------------
|
|
3
|
+
// Pointillism — pure-hue dabs that mix in the eye, on a woven canvas
|
|
4
|
+
// ---------------------------------------------------------------
|
|
5
|
+
const PIG = [
|
|
6
|
+
[250, 246, 230], // lead white
|
|
7
|
+
[248, 216, 64], // lemon yellow
|
|
8
|
+
[240, 150, 46], // orange
|
|
9
|
+
[214, 66, 44], // vermilion
|
|
10
|
+
[232, 138, 156], // rose
|
|
11
|
+
[126, 82, 168], // violet
|
|
12
|
+
[44, 62, 168], // ultramarine
|
|
13
|
+
[74, 146, 214], // cerulean
|
|
14
|
+
[36, 148, 96], // emerald
|
|
15
|
+
[158, 196, 62], // yellow-green
|
|
16
|
+
[28, 84, 66], // viridian deep
|
|
17
|
+
[204, 150, 72], // ochre
|
|
18
|
+
[36, 34, 92], // deep ultramarine (no black on the palette)
|
|
19
|
+
];
|
|
20
|
+
const css = (c) => `rgb(${c[0]},${c[1]},${c[2]})`;
|
|
21
|
+
// Seurat's vibration: each pigment's complement (index into PIG), laid in beside it now and then
|
|
22
|
+
// white lemon orange verm. rose violet ultra cerul. emer. y-grn viri. ochre deep
|
|
23
|
+
const COMP = [0, 5, 7, 8, 9, 1, 2, 2, 4, 5, 3, 6, 11];
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
// =================================================================
|
|
27
|
+
// EXPLAINER — "How does AI learn?" in divisionist dots. Every frame re-dabs a fixed field of dots against
|
|
28
|
+
// a colour study of the current scene; scene changes are the painter re-dabbing the canvas in a diagonal
|
|
29
|
+
// sweep (dots pop to their new hue behind the front). Lettering, arrows and numbers are fine dabs.
|
|
30
|
+
// =================================================================
|
|
31
|
+
let PXC = null;
|
|
32
|
+
const clP = (v) => (v < 0 ? 0 : v > 1 ? 1 : v);
|
|
33
|
+
const ezP = (u) => H.ease.inOut(clP(u));
|
|
34
|
+
const wP = (t, a, b) => clP((t - a) / (b - a));
|
|
35
|
+
const lerpP = (a, b, u) => a + (b - a) * u;
|
|
36
|
+
const PAL_INK = [6, 6, 6, 6, 12, 12, 5, 6, 3], PAL_RED = [3, 3, 3, 2, 4, 3], PAL_GREEN = [8, 8, 10, 10, 6, 9], PAL_GOLD = [1, 1, 2, 0, 11];
|
|
37
|
+
function pxCache(W, H_) {
|
|
38
|
+
if (PXC) return PXC;
|
|
39
|
+
const C = { glyph: new Map() };
|
|
40
|
+
const tex = H.layer(W, H_), tc = tex.getContext('2d');
|
|
41
|
+
tc.fillStyle = 'rgb(232,222,198)'; tc.fillRect(0, 0, W, H_);
|
|
42
|
+
tc.globalAlpha = 0.16; tc.fillStyle = 'rgb(150,130,100)'; for (let y = 0; y < H_; y += 3) tc.fillRect(0, y, W, 1);
|
|
43
|
+
tc.globalAlpha = 0.1; for (let x = 0; x < W; x += 3) tc.fillRect(x, 0, 1, H_);
|
|
44
|
+
C.tex = tex;
|
|
45
|
+
// brush touches: larger oval dabs laid along a slow flow field (the painter's hand), not random round dots
|
|
46
|
+
const rp = H.rng(2025), nzf = H.makeNoise(2026), sp = 5.6, pos = [];
|
|
47
|
+
for (let y = 6; y < H_ - 4; y += sp * 0.87) for (let x = 6; x < W - 4; x += sp) {
|
|
48
|
+
const px = x + rp.range(-1.6, 1.6) + ((y / sp) & 1) * sp * 0.5, py = y + rp.range(-1.4, 1.4);
|
|
49
|
+
pos.push(px, py, rp.range(2.6, 3.4), -0.6 + 0.5 * nzf.n2(px * 0.004, py * 0.004) + rp.range(-0.18, 0.18), rp());
|
|
50
|
+
}
|
|
51
|
+
C.pos = new Float32Array(pos); C.n = pos.length / 5;
|
|
52
|
+
C.study = H.layer(W, H_); C.study2 = H.layer(W, H_);
|
|
53
|
+
// the painted border of dots (hero step 5), baked once: darker complementary dabs travelling round the frame
|
|
54
|
+
C.border = H.layer(W, H_); { const bc = C.border.getContext('2d'), br = H.rng(66), bl = PIG.map(() => []);
|
|
55
|
+
for (let k = 0; k < 2600; k++) {
|
|
56
|
+
const side = br.int(0, 3), along = br(), depth = br.range(2, 16);
|
|
57
|
+
const x = side === 0 || side === 1 ? along * W : side === 2 ? depth : W - depth, y = side === 0 ? depth : side === 1 ? H_ - depth : along * H_;
|
|
58
|
+
bl[br.pick([6, 6, 12, 5, 2, 3, 6, 12])].push([x, y, br.range(2, 2.8), br() * 3]);
|
|
59
|
+
}
|
|
60
|
+
bc.shadowColor = 'rgba(52,38,74,0.42)'; bc.shadowOffsetX = 1.2; bc.shadowOffsetY = 1.6; batchFill(bc, bl); }
|
|
61
|
+
PXC = C;
|
|
62
|
+
return C;
|
|
63
|
+
}
|
|
64
|
+
// fine-dab glyphs, cached per (char,size)
|
|
65
|
+
function glyph(ch, size) {
|
|
66
|
+
const C = PXC, key = ch + '|' + size;
|
|
67
|
+
let g = C.glyph.get(key);
|
|
68
|
+
if (g) return g;
|
|
69
|
+
const font = `800 italic ${size}px "Playfair Display"`, m = H.layer(4, 4).getContext('2d'); m.font = font;
|
|
70
|
+
const adv = m.measureText(ch).width, w = Math.ceil(adv + size * 0.5), h = Math.ceil(size * 1.4);
|
|
71
|
+
const cv = H.layer(w, h), c = cv.getContext('2d'); c.font = font; c.fillStyle = '#000'; c.fillText(ch, size * 0.15, size * 1.05);
|
|
72
|
+
const d = c.getImageData(0, 0, w, h).data, r = H.rng(ch.charCodeAt(0) * 131 + size), st = Math.max(1.35, size / 30), dots = [];
|
|
73
|
+
for (let y = 0; y < h; y += st) for (let x = 0; x < w; x += st) {
|
|
74
|
+
const xx = x + r.range(-0.5, 0.5), yy = y + r.range(-0.5, 0.5), k = ((yy | 0) * w + (xx | 0)) * 4 + 3;
|
|
75
|
+
if (xx >= 0 && yy >= 0 && xx < w && yy < h && d[k] > 110) dots.push([xx - size * 0.15, yy - size * 1.05, st * r.range(0.72, 0.92), r(), r()]);
|
|
76
|
+
}
|
|
77
|
+
g = { adv, dots };
|
|
78
|
+
C.glyph.set(key, g);
|
|
79
|
+
return g;
|
|
80
|
+
}
|
|
81
|
+
function batchFill(ctx, lists, ratio = 0.8) {
|
|
82
|
+
lists.forEach((list, p) => {
|
|
83
|
+
if (!list.length) return;
|
|
84
|
+
ctx.fillStyle = `rgb(${PIG[p][0]},${PIG[p][1]},${PIG[p][2]})`;
|
|
85
|
+
// fill in chunks: one huge multi-subpath path rasterises far slower than many modest ones
|
|
86
|
+
for (let s = 0; s < list.length; s += 600) {
|
|
87
|
+
ctx.beginPath();
|
|
88
|
+
for (let i = s, e = Math.min(list.length, s + 600); i < e; i++) { const [x, y, rr, a] = list[i]; ctx.moveTo(x + rr * Math.cos(a), y + rr * Math.sin(a)); ctx.ellipse(x, y, rr, rr * ratio, a, 0, 6.2832); }
|
|
89
|
+
ctx.fill();
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const textW = (str, size) => { let w = 0; for (const ch of str) w += glyph(ch, size).adv; return w; };
|
|
94
|
+
// lettering dabbed in fine dots; reveal sweeps left to right, each dot popping on in turn
|
|
95
|
+
function dotText(ctx, str, x, y, size, { align = 'left', reveal = 1, pal = PAL_INK, scale = 1 } = {}) {
|
|
96
|
+
if (reveal <= 0) return;
|
|
97
|
+
const lists = PIG.map(() => []), tw = textW(str, size);
|
|
98
|
+
let cx = align === 'center' ? -tw / 2 : align === 'right' ? -tw : 0;
|
|
99
|
+
const n = [...str].length;
|
|
100
|
+
[...str].forEach((ch, i) => {
|
|
101
|
+
const g = glyph(ch, size), lr = clP(reveal * (n + 2) - i);
|
|
102
|
+
if (lr > 0) for (const [dx, dy, rr, q, q2] of g.dots) if (q < lr) lists[pal[(q2 * pal.length) | 0]].push([x + (cx + dx) * scale, y + dy * scale, rr * scale, q * 3]);
|
|
103
|
+
cx += g.adv;
|
|
104
|
+
});
|
|
105
|
+
batchFill(ctx, lists);
|
|
106
|
+
}
|
|
107
|
+
// dotted arrow / line, dabbed along the path as it is drawn on
|
|
108
|
+
function dotPath(ctx, pts, prog, width, pal, seed, head = true) {
|
|
109
|
+
if (prog <= 0) return;
|
|
110
|
+
const lists = PIG.map(() => []), r = H.rng(seed);
|
|
111
|
+
let L = 0; const seg = []; for (let i = 1; i < pts.length; i++) { const l = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); seg.push(l); L += l; }
|
|
112
|
+
const upto = L * clP(prog);
|
|
113
|
+
let acc = 0, ang = 0, ex = pts[0][0], ey = pts[0][1];
|
|
114
|
+
for (let i = 1; i < pts.length && acc < upto; i++) {
|
|
115
|
+
const a = pts[i - 1], b = pts[i], l = seg[i - 1]; ang = Math.atan2(b[1] - a[1], b[0] - a[0]);
|
|
116
|
+
for (let s = 0; s < l && acc + s < upto; s += 2.4) {
|
|
117
|
+
const u = s / l, x = a[0] + (b[0] - a[0]) * u, y = a[1] + (b[1] - a[1]) * u; ex = x; ey = y;
|
|
118
|
+
for (let k = 0; k < Math.max(1, Math.round(width / 2.4)); k++) { const o = (k + 0.5 - width / 4.8) * 2.4 + r.range(-0.7, 0.7); lists[pal[(r() * pal.length) | 0]].push([x - Math.sin(ang) * o, y + Math.cos(ang) * o, r.range(1.1, 1.5), r() * 3]); }
|
|
119
|
+
}
|
|
120
|
+
acc += l;
|
|
121
|
+
}
|
|
122
|
+
if (head && prog >= 1) for (const sgn of [-1, 1]) for (let s = 0; s < width * 3.2; s += 2.2) { const a = ang + Math.PI + sgn * 0.55; for (let k = 0; k < 2; k++) lists[pal[(r() * pal.length) | 0]].push([ex + Math.cos(a) * s + r.range(-0.8, 0.8), ey + Math.sin(a) * s + r.range(-0.8, 0.8), r.range(1.1, 1.5), r() * 3]); }
|
|
123
|
+
batchFill(ctx, lists);
|
|
124
|
+
}
|
|
125
|
+
const bezP = (p0, p1, p2, p3, n = 30) => { const o = []; for (let i = 0; i <= n; i++) { const u = i / n, a = 1 - u; o.push([a * a * a * p0[0] + 3 * a * a * u * p1[0] + 3 * a * u * u * p2[0] + u * u * u * p3[0], a * a * a * p0[1] + 3 * a * a * u * p1[1] + 3 * a * u * u * p2[1] + u * u * u * p3[1]]); } return o; };
|
|
126
|
+
const ringP = (cx, cy, rx, ry, a0 = -0.6, turns = 1.08, n = 50) => { const o = []; for (let i = 0; i <= n; i++) { const a = a0 + i / n * Math.PI * 2 * turns; o.push([cx + Math.cos(a) * rx, cy + Math.sin(a) * ry]); } return o; };
|
|
127
|
+
const fmtP = (n) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
128
|
+
|
|
129
|
+
// ----- the colour studies (what the dots are trying to mix to) -----
|
|
130
|
+
function studyScene(u, sc, t, W, H_) {
|
|
131
|
+
const lg = (y0, y1, stops) => { const g = u.createLinearGradient(0, y0, 0, y1); stops.forEach(([o, c]) => g.addColorStop(o, c)); return g; };
|
|
132
|
+
u.fillStyle = lg(0, 300, [[0, 'rgb(86,142,214)'], [0.6, 'rgb(158,190,226)'], [1, 'rgb(238,214,184)']]); u.fillRect(0, 0, W, 300);
|
|
133
|
+
u.fillStyle = 'rgb(130,152,148)'; u.beginPath(); u.moveTo(0, 310); for (let x = 0; x <= W; x += 20) u.lineTo(x, 286 - 16 * Math.abs(Math.sin(x * 0.021)) - 8 * Math.sin(x * 0.007)); u.lineTo(W, 310); u.fill();
|
|
134
|
+
u.fillStyle = lg(300, 390, [[0, 'rgb(150,186,214)'], [1, 'rgb(70,124,184)']]); u.fillRect(0, 300, W, 90);
|
|
135
|
+
// warm haze where sky meets the far bank, sun glints on the water (light from the left)
|
|
136
|
+
u.fillStyle = lg(230, 330, [[0, 'rgba(250,214,170,0)'], [0.55, 'rgba(250,214,170,0.2)'], [1, 'rgba(250,214,170,0)']]); u.fillRect(0, 230, W, 100);
|
|
137
|
+
u.fillStyle = 'rgba(252,238,196,0.7)'; for (let k = 0; k < 7; k++) u.fillRect(120 + Math.sin(k * 2.3) * 50, 318 + k * 10, 170 - k * 14, 3);
|
|
138
|
+
u.fillStyle = lg(380, 720, [[0, 'rgb(164,200,82)'], [0.55, 'rgb(124,174,64)'], [0.8, 'rgb(70,132,80)'], [1, 'rgb(40,92,96)']]); u.fillRect(0, 386, W, 340);
|
|
139
|
+
{ const g = u.createLinearGradient(0, 0, W, 0); g.addColorStop(0, 'rgba(250,226,140,0.28)'); g.addColorStop(0.5, 'rgba(250,226,140,0)'); g.addColorStop(1, 'rgba(60,70,150,0.16)'); u.fillStyle = g; u.fillRect(0, 386, W, 340); }
|
|
140
|
+
// violet cast shadow, thrown down-right (light from the upper left), soft-edged
|
|
141
|
+
const shadow = (fn) => { u.save(); u.filter = 'blur(10px)'; u.fillStyle = 'rgba(96,70,150,0.55)'; fn(); u.restore(); };
|
|
142
|
+
// labels sit on feathered pale-peach grounds so the dots thin out at the edges (no sticker boxes)
|
|
143
|
+
const ribbon = (x, y, w, h) => { u.save(); u.shadowColor = 'rgb(250,230,212)'; u.shadowBlur = 14; u.fillStyle = 'rgb(250,232,214)'; u.beginPath(); u.roundRect(x + 4, y + 4, Math.max(0, w - 8), h - 8, 14); u.fill(); u.restore(); };
|
|
144
|
+
const orb = (x, y, r, flash, spin) => {
|
|
145
|
+
shadow(() => { u.beginPath(); u.ellipse(x + r * 0.55, y + r + 92, r * 0.95, 20, 0, 0, 7); u.fill(); });
|
|
146
|
+
u.fillStyle = 'rgb(70,56,110)'; u.fillRect(x - 18, y + r - 6, 36, 90); u.beginPath(); u.ellipse(x, y + r + 86, 60, 14, 0, 0, 7); u.fill();
|
|
147
|
+
const g = u.createRadialGradient(x - r * 0.2, y - r * 0.2, 4, x, y, r * (1 + flash * 0.25)); g.addColorStop(0, flash > 0.2 ? 'rgb(255,250,226)' : 'rgb(252,232,140)'); g.addColorStop(0.55, 'rgb(242,168,66)'); g.addColorStop(1, 'rgb(150,70,120)');
|
|
148
|
+
u.fillStyle = g; u.beginPath(); u.arc(x, y, r * (1 + flash * 0.12), 0, 7); u.fill();
|
|
149
|
+
u.strokeStyle = 'rgb(40,36,96)'; u.lineWidth = 7; u.lineCap = 'round';
|
|
150
|
+
for (let k = 0; k < 3; k++) { const a = -2.4 + k * 1.0 + spin * (k === 1 ? -0.9 : 0.8); u.beginPath(); u.moveTo(x - 46 + k * 46, y + 30); u.lineTo(x - 46 + k * 46 + Math.cos(a) * 30, y + 30 + Math.sin(a) * 30); u.stroke(); u.beginPath(); u.arc(x - 46 + k * 46, y + 30, 18, 0, 7); u.lineWidth = 3; u.stroke(); u.lineWidth = 7; }
|
|
151
|
+
};
|
|
152
|
+
const card = (x, y, s, dog) => {
|
|
153
|
+
u.fillStyle = 'rgba(96,70,150,0.5)'; u.fillRect(x - s / 2 + s * 0.09, y - s / 2 + s * 0.11, s, s);
|
|
154
|
+
u.fillStyle = 'rgb(248,242,224)'; u.fillRect(x - s / 2, y - s / 2, s, s);
|
|
155
|
+
if (dog) { u.fillStyle = 'rgb(150,96,60)'; u.beginPath(); u.ellipse(x, y + s * 0.04, s * 0.28, s * 0.27, 0, 0, 7); u.fill(); u.fillStyle = 'rgb(90,56,40)'; for (const k of [-1, 1]) { u.beginPath(); u.ellipse(x + k * s * 0.3, y + s * 0.06, s * 0.09, s * 0.2, k * 0.3, 0, 7); u.fill(); } }
|
|
156
|
+
else { u.fillStyle = 'rgb(236,130,50)'; u.beginPath(); [[-0.32, 0], [-0.3, -0.36], [-0.1, -0.2], [0.1, -0.2], [0.3, -0.36], [0.32, 0], [0.22, 0.24], [0, 0.32], [-0.22, 0.24]].forEach(([a, b], i) => (i ? u.lineTo(x + a * s, y + b * s) : u.moveTo(x + a * s, y + b * s))); u.fill(); }
|
|
157
|
+
u.fillStyle = 'rgb(30,30,80)'; u.beginPath(); u.arc(x - s * 0.1, y, s * 0.04, 0, 7); u.arc(x + s * 0.1, y, s * 0.04, 0, 7); u.fill();
|
|
158
|
+
};
|
|
159
|
+
if (t > 1.9) ribbon(18, 14, 500 * clP((t - 1.9) / 0.3), 76);
|
|
160
|
+
if (sc === 1) {
|
|
161
|
+
// sandy path to the model
|
|
162
|
+
u.strokeStyle = 'rgb(226,200,150)'; u.lineWidth = 34; u.lineCap = 'round'; u.beginPath(); u.moveTo(-30, 540); u.bezierCurveTo(300, 580, 600, 470, 960, 400); u.stroke();
|
|
163
|
+
if (t > 1.0) orb(1080, 300, 100 * H.ease.back(wP(t, 1.0, 1.6)), 0, 0);
|
|
164
|
+
if (t > 2.4) ribbon(1000, 548, 162, 52);
|
|
165
|
+
for (const c of P_cards(t)) card(c.x, c.y, c.s, c.dog);
|
|
166
|
+
if (t > 2.9) ribbon(46, 600, 720, 104);
|
|
167
|
+
}
|
|
168
|
+
if (sc === 2) {
|
|
169
|
+
const flash = Math.sin(Math.PI * wP(t, 8.6, 9.6)), spin = ezP((t - 8.7) / 0.6);
|
|
170
|
+
orb(290, 370, 135, flash, spin); ribbon(205, 618, 172, 54);
|
|
171
|
+
card(1060, 250, 220, false); if (t > 6.8) ribbon(960, 376, 200, 52);
|
|
172
|
+
u.fillStyle = 'rgb(248,242,224)'; u.beginPath(); u.ellipse(660, 196, 196, 104, 0, 0, 7); u.fill(); u.beginPath(); u.moveTo(540, 260); u.lineTo(500, 330); u.lineTo(600, 280); u.fill();
|
|
173
|
+
if (t > 6.9) ribbon(330, 112, 140, 54); if (t > 7.6) ribbon(830, 72, 140, 54); if (t > 8.45) ribbon(722, 560, 166, 56);
|
|
174
|
+
if (t > 7.95 && t < 9.4) ribbon(950, 430, 220, 54);
|
|
175
|
+
}
|
|
176
|
+
if (sc === 3) {
|
|
177
|
+
const pr = wP(t, 11.2, 12.7), N = 24, pts = P_chart();
|
|
178
|
+
const lx = lerpP(160, 980, pr), area = () => { u.beginPath(); u.moveTo(160, 560); for (let i = 0; i <= N * pr; i++) u.lineTo(pts[i][0], pts[i][1]); u.lineTo(lx, 560); u.closePath(); };
|
|
179
|
+
if (pr > 0) shadow(() => { u.translate(16, 14); area(); u.fill(); });
|
|
180
|
+
u.fillStyle = lg(170, 560, [[0, 'rgb(254,228,96)'], [0.6, 'rgb(246,172,64)'], [0.88, 'rgb(232,130,70)'], [1, 'rgb(140,100,178)']]); area(); u.fill();
|
|
181
|
+
ribbon(170, 98, 560, 76); ribbon(400, 584, 340, 50);
|
|
182
|
+
if (t > 12.7) { const k = Math.sin(Math.PI * wP(t, 12.7, 13.4)); const g = u.createRadialGradient(980, pts[N][1], 2, 980, pts[N][1], 120); g.addColorStop(0, `rgba(255,246,190,${k})`); g.addColorStop(1, 'rgba(255,246,190,0)'); u.fillStyle = g; u.fillRect(840, 40, 280, 300); }
|
|
183
|
+
if (t > 13.15) ribbon(110, 628, 1060, 84);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function P_cards(t) {
|
|
187
|
+
const out = [];
|
|
188
|
+
for (let i = 0; i < 24; i++) {
|
|
189
|
+
const s0 = 2.5 + i * 0.15, v = (t - s0) / 1.5;
|
|
190
|
+
if (v <= 0 || v >= 1) continue;
|
|
191
|
+
const a = 1 - v, x = a * a * a * -30 + 3 * a * a * v * 300 + 3 * a * v * v * 600 + v * v * v * 980, y = a * a * a * 540 + 3 * a * a * v * 580 + 3 * a * v * v * 470 + v * v * v * 330;
|
|
192
|
+
out.push({ x, y: y - 64, s: v > 0.8 ? 92 * (1 - (v - 0.8) / 0.2 * 0.9) : 92, dog: (i % 2 === 1) !== (i % 5 === 0) });
|
|
193
|
+
}
|
|
194
|
+
return out;
|
|
195
|
+
}
|
|
196
|
+
function P_chart() { const N = 24, pts = []; for (let i = 0; i <= N; i++) { const acc = 61 + 36 * (1 - Math.exp(-i / 5.5)) / (1 - Math.exp(-N / 5.5)) + (i > 0 && i < N ? Math.sin(i * 2.7) * 1.6 : 0); pts.push([160 + i / N * 820, 560 - (acc - 50) / 50 * 380]); } return pts; }
|
|
197
|
+
|
|
198
|
+
// ----- the fine-dab layer for each scene (text, arrows, numbers, emphasis) -----
|
|
199
|
+
function overlayScene(ctx, sc, t) {
|
|
200
|
+
if (sc === 1) {
|
|
201
|
+
if (t > 2.4) dotText(ctx, 'the model', 1081, 584, 32, { align: 'center', reveal: wP(t, 2.4, 2.9) });
|
|
202
|
+
dotPath(ctx, bezP([140, 450], [400, 500], [640, 400], [950, 320]), wP(t, 2.3, 2.9), 5, PAL_INK, 11);
|
|
203
|
+
for (const c of P_cards(t)) if (c.s > 60) dotText(ctx, c.dog ? 'dog' : 'cat', c.x, c.y + c.s / 2 + 30, 28, { align: 'center', pal: c.dog ? PAL_INK : PAL_RED });
|
|
204
|
+
if (t > 2.9) {
|
|
205
|
+
const n = t > 5.6 ? 1e6 : Math.round(1e6 * H.ease.in(wP(t, 3.0, 5.6)) / 1000) * 1000;
|
|
206
|
+
dotText(ctx, fmtP(n), 70, 676, 66, { reveal: wP(t, 2.9, 3.3) });
|
|
207
|
+
dotText(ctx, 'examples', 84 + textW(fmtP(n), 66), 672, 44, { reveal: wP(t, 3.0, 3.4) });
|
|
208
|
+
if (t > 5.6) dotPath(ctx, [[70, 692], [400, 696], [720, 690]], wP(t, 5.6, 5.95), 5, PAL_RED, 12, false);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
if (sc === 2) {
|
|
212
|
+
const second = t > 9.4;
|
|
213
|
+
dotText(ctx, 'the model', 291, 656, 34, { align: 'center' });
|
|
214
|
+
dotText(ctx, 'new picture', 1060, 413, 32, { align: 'center', reveal: wP(t, 6.8, 7.2) });
|
|
215
|
+
dotPath(ctx, bezP([395, 270], [420, 210], [440, 200], [470, 196]), wP(t, 6.9, 7.2), 6, PAL_INK, 21);
|
|
216
|
+
dotText(ctx, 'Guess', 400, 150, 38, { align: 'center', reveal: wP(t, 6.9, 7.3) });
|
|
217
|
+
if (!second) dotText(ctx, 'dog?', 660, 226, 88, { align: 'center', reveal: wP(t, 7.1, 7.5), pal: PAL_INK });
|
|
218
|
+
dotPath(ctx, bezP([945, 230], [910, 190], [890, 180], [860, 178]), wP(t, 7.5, 7.8), 6, PAL_INK, 22);
|
|
219
|
+
dotText(ctx, 'Check', 900, 110, 38, { align: 'center', reveal: wP(t, 7.6, 8.0) });
|
|
220
|
+
if (t > 7.9 && !second) {
|
|
221
|
+
dotPath(ctx, [[570, 136], [750, 250]], wP(t, 7.9, 8.05), 12, PAL_RED, 23, false);
|
|
222
|
+
dotPath(ctx, [[750, 136], [570, 250]], wP(t, 8.05, 8.2), 12, PAL_RED, 24, false);
|
|
223
|
+
dotPath(ctx, ringP(660, 196, 214, 120), wP(t, 8.0, 8.45), 7, PAL_RED, 25, false);
|
|
224
|
+
dotText(ctx, "it's a cat!", 1060, 469, 34, { align: 'center', reveal: wP(t, 8.0, 8.4), pal: PAL_RED });
|
|
225
|
+
}
|
|
226
|
+
dotPath(ctx, bezP([700, 318], [780, 600], [520, 600], [440, 440]), wP(t, 8.25, 8.8), 7, PAL_RED, 26);
|
|
227
|
+
dotText(ctx, 'Adjust', 805, 600, 38, { align: 'center', reveal: wP(t, 8.5, 8.9), pal: PAL_RED });
|
|
228
|
+
if (second) {
|
|
229
|
+
dotText(ctx, 'cat', 640, 226, 88, { align: 'center', reveal: wP(t, 9.4, 9.7), pal: PAL_INK });
|
|
230
|
+
dotPath(ctx, [[730, 196], [752, 226], [806, 150]], wP(t, 9.7, 9.95), 11, PAL_GREEN, 27, false);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
if (sc === 3) {
|
|
234
|
+
const pts = P_chart(), pr = wP(t, 11.2, 12.7);
|
|
235
|
+
dotPath(ctx, [[160, 170], [160, 560], [1010, 560]], wP(t, 10.9, 11.3), 4, PAL_INK, 31, false);
|
|
236
|
+
dotText(ctx, 'training rounds', 570, 620, 34, { align: 'center', reveal: wP(t, 11.0, 11.4) });
|
|
237
|
+
dotPath(ctx, pts, pr, 6, PAL_INK, 32, false);
|
|
238
|
+
const acc = Math.round(t < 11.2 ? 61 : t > 12.7 ? 97 : lerpP(61, 97, H.ease.out(pr)));
|
|
239
|
+
dotText(ctx, 'Accuracy', 186, 154, 48, { reveal: wP(t, 10.9, 11.3) });
|
|
240
|
+
dotText(ctx, acc + '%', 470, 160, 68, { reveal: wP(t, 11.1, 11.3), pal: acc >= 97 ? PAL_RED : PAL_INK });
|
|
241
|
+
dotText(ctx, '61%', 92, pts[0][1] + 10, 32, { reveal: wP(t, 11.2, 11.5) });
|
|
242
|
+
if (t > 12.7) { dotPath(ctx, ringP(536, 138, 112, 58), wP(t, 12.7, 13.1), 6, PAL_RED, 33, false); dotPath(ctx, ringP(pts[24][0], pts[24][1], 26, 26, 0, 1.05, 30), wP(t, 12.9, 13.2), 5, PAL_RED, 34, false); }
|
|
243
|
+
if (t > 13.15) dotText(ctx, 'More examples → better guesses', 640, 688, 54, { align: 'center', reveal: ezP((t - 13.15) / 0.8) });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function explainPointillism(ctx, W, H_, t) {
|
|
248
|
+
const C = pxCache(W, H_);
|
|
249
|
+
ctx.drawImage(C.tex, 0, 0);
|
|
250
|
+
// which scene(s): the canvas is re-dabbed in a diagonal sweep between scenes
|
|
251
|
+
const wipes = [[5.95, 6.75], [10.35, 11.15]];
|
|
252
|
+
let sc = t < 6.35 ? 1 : t < 10.75 ? 2 : 3, sweep = null;
|
|
253
|
+
wipes.forEach(([a, b], i) => { if (t > a && t < b) sweep = { from: i + 1, to: i + 2, front: lerpP(-120, 1620, ezP((t - a) / (b - a))) }; });
|
|
254
|
+
const study = (cv, s, tt) => { const u = cv.getContext('2d'); studyScene(u, s, tt, W, H_); return u.getImageData(0, 0, W, H_).data; };
|
|
255
|
+
const TA = study(C.study, sweep ? sweep.from : sc, sweep ? Math.min(t, sweep.from === 1 ? 6.3 : 10.7) : t);
|
|
256
|
+
const TB = sweep ? study(C.study2, sweep.to, Math.max(t, sweep.to === 2 ? 6.4 : 10.8)) : null;
|
|
257
|
+
// greedy optical mixing per cell
|
|
258
|
+
const CELL = 8, cw = Math.ceil(W / CELL), ch = Math.ceil(H_ / CELL), acc = new Float32Array(cw * ch * 3), cnt = new Uint16Array(cw * ch);
|
|
259
|
+
const lists = PIG.map(() => []), sh = H.rng(7000 + Math.floor(t * 6)), P = C.pos;
|
|
260
|
+
for (let i = 0; i < C.n; i++) {
|
|
261
|
+
const x = P[i * 5], y = P[i * 5 + 1], q = P[i * 5 + 4];
|
|
262
|
+
let rr = P[i * 5 + 2], T = TA;
|
|
263
|
+
if (sweep) { const d = sweep.front - (x + y * 0.35); if (d > 0) { T = TB; if (d < 90) rr *= H.ease.back(clP(d / 90)); } else if (d > -50) rr *= 1 - 0.5 * clP((d + 50) / 50); }
|
|
264
|
+
const o = ((y | 0) * W + (x | 0)) * 4, ci = ((y / CELL) | 0) * cw + ((x / CELL) | 0), n = cnt[ci], k = ci * 3;
|
|
265
|
+
const w0 = T[o] * (n + 1) - acc[k], w1 = T[o + 1] * (n + 1) - acc[k + 1], w2 = T[o + 2] * (n + 1) - acc[k + 2];
|
|
266
|
+
const dark = T[o] + T[o + 1] + T[o + 2] < 330; // in shadow, reach for ultramarine/violet, not a darker same-hue
|
|
267
|
+
let best = 0, bd = 1e12, second = 0, sd = 1e12;
|
|
268
|
+
for (let p = 0; p < PIG.length; p++) { const Q = PIG[p]; let dd = (Q[0] - w0) ** 2 + (Q[1] - w1) ** 2 * 1.2 + (Q[2] - w2) ** 2 + ((q * 977 + p * 0.37) % 1) * 2600; if (dark && (p === 5 || p === 6 || p === 12)) dd *= 0.78; if (dd < bd) { second = best; sd = bd; best = p; bd = dd; } else if (dd < sd) { second = p; sd = dd; } }
|
|
269
|
+
const qq = (q * 7919) % 1;
|
|
270
|
+
let pick = qq < 0.16 ? second : qq > 0.93 && COMP[best] !== best ? COMP[best] : best;
|
|
271
|
+
acc[k] += PIG[pick][0]; acc[k + 1] += PIG[pick][1]; acc[k + 2] += PIG[pick][2]; cnt[ci]++;
|
|
272
|
+
if (sh() < 0.05) pick = pick === best ? second : best;
|
|
273
|
+
lists[pick].push([x, y, rr, P[i * 5 + 3]]);
|
|
274
|
+
}
|
|
275
|
+
// paint on canvas: every dab throws a hard little shadow onto the weave down-right (light from the upper left)
|
|
276
|
+
ctx.save(); ctx.shadowColor = 'rgba(52,38,74,0.42)'; ctx.shadowOffsetX = 1.2; ctx.shadowOffsetY = 1.6;
|
|
277
|
+
batchFill(ctx, lists, 0.6);
|
|
278
|
+
ctx.restore();
|
|
279
|
+
ctx.drawImage(C.border, 0, 0);
|
|
280
|
+
// fine dabs: the explanation itself (split along the sweep front during a change)
|
|
281
|
+
const clipSide = (ahead) => { ctx.beginPath(); const f = sweep.front; if (ahead) { ctx.moveTo(f, 0); ctx.lineTo(W + 400, 0); ctx.lineTo(W + 400, H_); ctx.lineTo(f - H_ * 0.35, H_); } else { ctx.moveTo(-400, 0); ctx.lineTo(f, 0); ctx.lineTo(f - H_ * 0.35, H_); ctx.lineTo(-400, H_); } ctx.closePath(); ctx.clip(); };
|
|
282
|
+
if (sweep) {
|
|
283
|
+
ctx.save(); clipSide(true); overlayScene(ctx, sweep.from, sweep.from === 1 ? 6.3 : 10.7); ctx.restore();
|
|
284
|
+
ctx.save(); clipSide(false); overlayScene(ctx, sweep.to, Math.max(t, sweep.to === 2 ? 6.4 : 10.8)); ctx.restore();
|
|
285
|
+
} else overlayScene(ctx, sc, t);
|
|
286
|
+
// hook -> header
|
|
287
|
+
{
|
|
288
|
+
const k = ezP((t - 1.5) / 0.6);
|
|
289
|
+
if (k < 1) { const s = lerpP(1, 44 / 84, k), tw = textW('How does AI learn?', 84); dotText(ctx, 'How does AI learn?', lerpP(640 - tw / 2, 36, k), lerpP(190, 66, k), 84, { reveal: wP(t, 0.1, 1.2), scale: s }); }
|
|
290
|
+
else dotText(ctx, 'How does AI learn?', 36, 66, 44);
|
|
291
|
+
}
|
|
292
|
+
ctx.strokeStyle = 'rgba(40,40,90,0.35)'; ctx.lineWidth = 1; ctx.strokeRect(19.5, 19.5, W - 39, H_ - 39);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
registerStyle({
|
|
296
|
+
id: 'pointillism',
|
|
297
|
+
name: 'Pointillist Afternoon',
|
|
298
|
+
category: 'Painterly',
|
|
299
|
+
description: 'Neo-Impressionist divisionism: every form is built from small dabs of unmixed pigment — complementary hues side by side that blend optically — with the canvas weave peeking between the dots and a dotted painted border.',
|
|
300
|
+
motion: 'Dots shimmer as individual dabs re-pick their hue each frame while the overall colour holds, like light vibrating on the water; the sailboat drifts and the parasol sways.',
|
|
301
|
+
fonts: ['800 italic 50px "Playfair Display"'],
|
|
302
|
+
duration: 5.5,
|
|
303
|
+
frameT: 5.4,
|
|
304
|
+
readsPixels: true,
|
|
305
|
+
explainerDuration: 15,
|
|
306
|
+
explainerNote: 'Every frame re-dabs one fixed field of pure-pigment dots against the current scene, so moving things shimmer as dots re-pick their hue; scene changes are the canvas being re-dabbed in a diagonal sweep, and lettering, arrows, counters and the emphasis rings are all fine dabs.',
|
|
307
|
+
explainer(ctx, W, H_, t) { explainPointillism(ctx, W, H_, t); },
|
|
308
|
+
explainerSound(k) {
|
|
309
|
+
// all times copied from explainPointillism()/overlayScene()/studyScene() above (their wP() windows)
|
|
310
|
+
const D = 15, px = (x) => H.clamp(x / 1280 * 2 - 1, -1, 1) * 0.7;
|
|
311
|
+
const when = (f, v, a, b) => { for (let t = a; t <= b; t += 0.001) if (f(t) >= v) return t; return b; };
|
|
312
|
+
// one dab: a round brush tip tapped onto canvas. a soft muted tap with a little body, nothing bright.
|
|
313
|
+
const dab = (t, g = 1, pan = 0, f = 1000, minor = true) => {
|
|
314
|
+
k.noise(t, { dur: 0.018, filter: 'bandpass', f, q: 1.4, gain: 0.28 * g, a: 0.002, pan, minor });
|
|
315
|
+
k.noise(t, { dur: 0.025, color: 'brown', filter: 'lowpass', f: 450, gain: 0.2 * g, a: 0.002, pan, minor });
|
|
316
|
+
};
|
|
317
|
+
// dabbing a line of dots on: a patter of taps at `rate` per second, panned along the path
|
|
318
|
+
const patter = (a, b, rate, g = 1, p0 = 0, p1 = p0, minor = true) => {
|
|
319
|
+
const d = b - a, r = H.rng(Math.round(a * 1009) + 5);
|
|
320
|
+
for (let x = 0; x < d; x += (0.6 + r() * 0.8) / rate) dab(a + x, g * (0.55 + r() * 0.5), p0 + (p1 - p0) * x / d, 750 + r() * 650, minor);
|
|
321
|
+
};
|
|
322
|
+
// the whole canvas re-dabbed in a diagonal sweep: a dense wave of taps travelling left to right, over a soft swell
|
|
323
|
+
const redab = (a, b) => { const d = b - a;
|
|
324
|
+
patter(a, b, 34, 0.85, -0.8, 0.8, false);
|
|
325
|
+
k.noise(a, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 350], [d * 0.5, 900], [d, 500]], q: 0.7, gain: 0.09, a: d * 0.45, verb: 0.2 }); };
|
|
326
|
+
const counter = (val, a, b, pan) => { let last = val(a), lastT = -1;
|
|
327
|
+
for (let t = a; t <= b; t += 1 / 240) { const v = val(t); if (v !== last && t - lastT >= 1 / 12) { dab(t, 1.1, pan, 900); lastT = t; } last = v; } };
|
|
328
|
+
|
|
329
|
+
k.bed('room', 0, D, { gain: 0.4 });
|
|
330
|
+
// a sunny afternoon pad (Gmaj7, Em7, Cmaj7, D6; 3.75 s bars)
|
|
331
|
+
k.music('pad', 0, D, { bpm: 64, gain: 0.3, chords: [['G2', 'D3', 'F#3', 'B3'], ['E2', 'B2', 'D3', 'G3'], ['C3', 'E3', 'G3', 'B3'], ['D3', 'F#3', 'A3', 'B3']] });
|
|
332
|
+
|
|
333
|
+
// ---- 1 HOOK: title dabbed in 0.1-1.2, shrinks to the header 1.5-2.1, ribbon 1.9-2.2
|
|
334
|
+
patter(0.1, 1.2, 16, 1.0, -0.5, 0.5, false);
|
|
335
|
+
k.play('tock', 1.0, { f: 260, gain: 0.35, pan: px(1080) }); // the model's orb blooms in (ease.back 1.0-1.6)
|
|
336
|
+
k.noise(1.0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.4, 800], [0.6, 500]], q: 0.8, gain: 0.08, a: 0.25, pan: px(1080), verb: 0.25 });
|
|
337
|
+
k.noise(1.5, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.6, 650]], q: 0.7, gain: 0.06, a: 0.2, minor: true }); // title slides to the corner
|
|
338
|
+
patter(1.9, 2.2, 12, 0.6, -0.9, -0.3);
|
|
339
|
+
|
|
340
|
+
// ---- 2 DATA: dotted arrow 2.3-2.9, 'the model' 2.4-2.9, counter 2.9-3.4
|
|
341
|
+
patter(2.3, 2.9, 14, 0.9, px(140), px(950), false);
|
|
342
|
+
patter(2.45, 2.9, 8, 0.5, px(1080));
|
|
343
|
+
patter(2.9, 3.35, 12, 0.8, -0.7, -0.3);
|
|
344
|
+
// counter: n = 1e6 * ease.in(wP(3.0, 5.6)), rounded to thousands; one firm dab per change, at most 12/s
|
|
345
|
+
counter((t) => Math.round(1e6 * H.ease.in(wP(t, 3.0, 5.6)) / 1000), 3.4, 5.58, -0.6);
|
|
346
|
+
k.play('tock', 5.6, { f: 420, gain: 0.55, pan: -0.6 }); // 1,000,000 lands
|
|
347
|
+
patter(5.6, 5.95, 16, 1.0, -0.8, 0.0, false); // red dotted underline
|
|
348
|
+
|
|
349
|
+
// ---- re-dab sweep 5.95-6.75
|
|
350
|
+
redab(5.95, 6.75);
|
|
351
|
+
|
|
352
|
+
// ---- 3 GUESS -> CHECK -> ADJUST
|
|
353
|
+
patter(6.85, 7.2, 14, 0.9, px(395), px(470), false); // guess arrow (+ 'Guess', 'new picture')
|
|
354
|
+
patter(7.2, 7.5, 12, 0.9, px(600), px(720)); // 'dog?'
|
|
355
|
+
patter(7.5, 7.85, 14, 0.9, px(945), px(860), false); // check arrow (+ 'Check')
|
|
356
|
+
// the wrong guess: two heavy red dotted slashes hammered on, a low "nope" under them
|
|
357
|
+
patter(7.9, 8.05, 30, 1.6, px(570), px(750), false); patter(8.05, 8.2, 30, 1.6, px(750), px(570), false);
|
|
358
|
+
k.play('thud', 7.9, { f: 115, gain: 0.45, pan: px(660) });
|
|
359
|
+
k.tone(7.92, { f: 'A3', type: 'triangle', dur: 0.2, gain: 0.17, a: 0.01, hold: 0.07, verb: 0.2, pan: px(660) });
|
|
360
|
+
k.tone(8.1, { f: 'F#3', type: 'triangle', dur: 0.38, gain: 0.17, a: 0.01, hold: 0.12, verb: 0.2, pan: px(660) });
|
|
361
|
+
patter(8.2, 8.45, 12, 0.7, px(800), px(560)); // the red ring closes
|
|
362
|
+
patter(8.45, 8.8, 14, 1.0, px(600), px(440), false); // adjust arrow back to the model
|
|
363
|
+
// the orb flashes and its dials turn (8.6-9.6 / ezP over 8.7-9.3): a soft warm swell and three detents
|
|
364
|
+
k.noise(8.6, { dur: 1.0, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.5, 800], [1.0, 380]], q: 0.8, gain: 0.08, a: 0.45, pan: px(290), verb: 0.3, minor: true });
|
|
365
|
+
const sp = (t) => ezP((t - 8.7) / 0.6);
|
|
366
|
+
for (let s = 1; s <= 3; s++) k.play('tock', when((t) => sp(t) * 4, s, 8.7, 9.3), { f: 600 + s * 30, gain: 0.5, pan: px(290), minor: true });
|
|
367
|
+
patter(9.4, 9.7, 12, 1.0, px(600), px(700)); // 'cat'
|
|
368
|
+
patter(9.7, 9.95, 22, 1.1, px(730), px(806), false); // green check
|
|
369
|
+
k.play('success', 9.95, { f: 'G5', gain: 0.45, pan: px(770) });
|
|
370
|
+
|
|
371
|
+
// ---- re-dab sweep 10.35-11.15
|
|
372
|
+
redab(10.35, 11.15);
|
|
373
|
+
|
|
374
|
+
// ---- 4 PROGRESS: axes and labels 10.9-11.4, dotted line 11.2-12.7
|
|
375
|
+
patter(11.15, 12.7, 10, 0.55, px(160), px(980));
|
|
376
|
+
const acc = (t) => Math.round(61 + 36 * H.ease.out(wP(t, 11.2, 12.7)));
|
|
377
|
+
const t97 = when(acc, 97, 11.2, 12.7);
|
|
378
|
+
counter(acc, 11.25, t97 - 0.05, px(470));
|
|
379
|
+
// 97% lands, re-dabbed in red: the biggest hit
|
|
380
|
+
k.play('impact', t97, { gain: 0.85, pan: px(470) });
|
|
381
|
+
patter(t97, t97 + 0.18, 40, 1.6, px(420), px(560), false);
|
|
382
|
+
k.play('ding', t97 + 0.02, { f: 'D5', gain: 0.5 });
|
|
383
|
+
['G4', 'B4'].forEach((f, i) => k.tone(t97 + 0.05 + i * 0.03, { f, dur: 1.3, gain: 0.07, verb: 0.3, pan: (i - 0.5) * 0.5 }));
|
|
384
|
+
patter(12.7, 13.1, 18, 1.1, px(430), px(650), false); // red ring around 97%, while the summit glows
|
|
385
|
+
k.noise(12.7, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 400], [0.35, 1000], [0.7, 500]], q: 0.8, gain: 0.07, a: 0.3, pan: px(980), verb: 0.3 });
|
|
386
|
+
patter(12.95, 13.2, 14, 0.7, px(980)); // small ring on the summit
|
|
387
|
+
|
|
388
|
+
// ---- 5 TAKEAWAY (13.15-13.95), then the resolve
|
|
389
|
+
patter(13.15, 13.95, 18, 1.1, -0.7, 0.7, false);
|
|
390
|
+
k.play('ding', 14.0, { f: 'G4', gain: 0.45 });
|
|
391
|
+
['B4', 'D5', 'G5'].forEach((f, i) => k.tone(14.06 + i * 0.06, { f, dur: 1.2, gain: 0.065 - i * 0.008, verb: 0.3, pan: (i - 1) * 0.3 }));
|
|
392
|
+
},
|
|
393
|
+
draw(ctx, W, H_, t) {
|
|
394
|
+
// ---------- 1. the scene as a soft colour study (sampled, never shown) ----------
|
|
395
|
+
const U = H.layer(W, H_), u = U.getContext('2d');
|
|
396
|
+
const lg = (y0, y1, stops) => { const g = u.createLinearGradient(0, y0, 0, y1); stops.forEach(([o, c]) => g.addColorStop(o, c)); return g; };
|
|
397
|
+
u.fillStyle = lg(0, 290, [[0, 'rgb(104,150,214)'], [0.55, 'rgb(170,196,226)'], [1, 'rgb(240,214,186)']]); u.fillRect(0, 0, W, 300);
|
|
398
|
+
const sg = u.createRadialGradient(980, 200, 10, 980, 200, 380); sg.addColorStop(0, 'rgba(255,236,190,0.9)'); sg.addColorStop(1, 'rgba(255,236,190,0)'); u.fillStyle = sg; u.fillRect(0, 0, W, 300);
|
|
399
|
+
// far bank: hazy trees + a low data-centre with mast
|
|
400
|
+
u.fillStyle = 'rgb(126,150,146)'; u.beginPath(); u.moveTo(0, 300);
|
|
401
|
+
for (let x = 0; x <= W; x += 20) u.lineTo(x, 270 - 18 * Math.abs(Math.sin(x * 0.021)) - 10 * Math.sin(x * 0.007)); u.lineTo(W, 300); u.fill();
|
|
402
|
+
u.fillStyle = 'rgb(150,128,186)'; u.fillRect(450, 226, 200, 62); u.fillRect(510, 204, 70, 24);
|
|
403
|
+
u.fillStyle = 'rgb(120,104,170)'; u.fillRect(650, 248, 100, 40); u.fillStyle = 'rgb(214,170,150)'; u.fillRect(450, 222, 200, 6); u.fillRect(650, 244, 100, 5);
|
|
404
|
+
u.fillStyle = 'rgb(96,84,150)'; u.fillRect(593, 130, 6, 100); u.fillRect(582, 154, 28, 4); u.fillRect(586, 176, 20, 3);
|
|
405
|
+
u.fillStyle = 'rgb(235,100,70)'; u.fillRect(590, 124, 12, 10);
|
|
406
|
+
u.fillStyle = 'rgb(246,190,110)'; for (let x = 462; x < 640; x += 16) { u.fillRect(x, 240, 9, 7); u.fillRect(x, 260, 9, 7); }
|
|
407
|
+
// river
|
|
408
|
+
u.fillStyle = lg(288, 430, [[0, 'rgb(150,186,214)'], [1, 'rgb(64,118,180)']]); u.fillRect(0, 288, W, 150);
|
|
409
|
+
u.fillStyle = 'rgba(250,236,200,0.75)'; for (let k = 0; k < 9; k++) u.fillRect(880 + Math.sin(k * 2.1) * 60, 300 + k * 12, 150 - k * 8, 3);
|
|
410
|
+
// sailboat (drifts with t)
|
|
411
|
+
const bx = 760 + t * 12;
|
|
412
|
+
u.fillStyle = 'rgb(250,246,232)'; u.beginPath(); u.moveTo(bx, 250); u.lineTo(bx + 4, 340); u.lineTo(bx + 58, 334); u.closePath(); u.fill();
|
|
413
|
+
u.fillStyle = 'rgb(236,232,214)'; u.beginPath(); u.moveTo(bx - 4, 270); u.lineTo(bx - 4, 338); u.lineTo(bx - 40, 336); u.closePath(); u.fill();
|
|
414
|
+
u.fillStyle = 'rgb(150,60,50)'; u.beginPath(); u.moveTo(bx - 50, 340); u.lineTo(bx + 66, 338); u.lineTo(bx + 52, 352); u.lineTo(bx - 40, 352); u.fill();
|
|
415
|
+
u.fillStyle = 'rgba(80,70,120,0.5)'; u.fillRect(bx - 44, 354, 100, 10);
|
|
416
|
+
// near bank: sunlit grass
|
|
417
|
+
u.fillStyle = lg(420, 720, [[0, 'rgb(150,190,76)'], [1, 'rgb(118,170,60)']]);
|
|
418
|
+
u.beginPath(); u.moveTo(0, 440); u.bezierCurveTo(300, 418, 700, 432, 1280, 412); u.lineTo(1280, 720); u.lineTo(0, 720); u.fill();
|
|
419
|
+
// tree shadow pooled across the grass (cool)
|
|
420
|
+
u.fillStyle = 'rgb(42,96,86)'; u.beginPath(); u.moveTo(0, 470); u.bezierCurveTo(260, 470, 520, 520, 740, 560); u.bezierCurveTo(800, 600, 760, 720, 620, 720); u.lineTo(0, 720); u.fill();
|
|
421
|
+
u.fillStyle = 'rgb(62,120,86)'; u.beginPath(); u.ellipse(420, 600, 160, 30, 0, 0, 7); u.fill();
|
|
422
|
+
// tree: slender trunk + clumped canopy, lit warm on the right, cool in shadow
|
|
423
|
+
u.fillStyle = 'rgb(86,66,60)'; u.beginPath(); u.moveTo(158, 600); u.bezierCurveTo(172, 460, 150, 300, 186, 110); u.lineTo(202, 110); u.bezierCurveTo(176, 300, 198, 460, 190, 604); u.fill();
|
|
424
|
+
u.fillStyle = 'rgb(206,150,90)'; u.fillRect(187, 300, 5, 280);
|
|
425
|
+
u.strokeStyle = 'rgb(70,60,60)'; u.lineWidth = 7; u.beginPath(); u.moveTo(186, 200); u.quadraticCurveTo(260, 150, 330, 140); u.stroke(); u.beginPath(); u.moveTo(182, 240); u.quadraticCurveTo(110, 200, 60, 196); u.stroke();
|
|
426
|
+
const cr = H.rng(44);
|
|
427
|
+
for (let k = 0; k < 26; k++) {
|
|
428
|
+
const x = cr.range(10, 420), y = cr.range(-10, 200 - Math.abs(x - 200) * 0.25), rx = cr.range(40, 80), ry = cr.range(22, 40);
|
|
429
|
+
const lit = H.clamp((x - 120) / 300) * (1 - y / 260);
|
|
430
|
+
const c = H.mix([24, 60, 56], [126, 172, 64], lit * cr.range(0.6, 1));
|
|
431
|
+
u.fillStyle = `rgb(${c[0] | 0},${c[1] | 0},${c[2] | 0})`; u.beginPath(); u.ellipse(x, y, rx, ry, cr.range(-0.3, 0.3), 0, 7); u.fill();
|
|
432
|
+
}
|
|
433
|
+
// seated figure in profile with a laptop (sits in the sun at the shadow's edge)
|
|
434
|
+
const fx = 780, fy = 600;
|
|
435
|
+
u.save(); u.translate(fx, fy); u.scale(1.35, 1.35); u.translate(-fx, -fy);
|
|
436
|
+
// halo of complementary warmth around the cool screen glow
|
|
437
|
+
{ const g = u.createRadialGradient(fx + 78, fy - 64, 4, fx + 78, fy - 64, 60); g.addColorStop(0, 'rgba(250,190,110,0.85)'); g.addColorStop(1, 'rgba(250,190,110,0)'); u.fillStyle = g; u.fillRect(fx, fy - 140, 160, 150); }
|
|
438
|
+
u.fillStyle = 'rgb(46,44,88)';
|
|
439
|
+
u.beginPath(); u.moveTo(fx - 30, fy); u.lineTo(fx - 24, fy - 86); u.quadraticCurveTo(fx - 4, fy - 102, fx + 12, fy - 84); u.lineTo(fx + 20, fy - 40); u.lineTo(fx + 70, fy - 34); u.lineTo(fx + 76, fy); u.closePath(); u.fill();
|
|
440
|
+
u.fillStyle = 'rgb(228,176,146)'; u.beginPath(); u.arc(fx - 6, fy - 112, 15, 0, 7); u.fill();
|
|
441
|
+
u.fillStyle = 'rgb(60,44,40)'; u.beginPath(); u.ellipse(fx - 10, fy - 120, 17, 9, -0.2, 0, 7); u.fill();
|
|
442
|
+
u.fillStyle = 'rgb(220,96,70)'; u.fillRect(fx - 30, fy - 94, 40, 6);
|
|
443
|
+
u.fillStyle = 'rgb(170,170,186)'; u.beginPath(); u.moveTo(fx + 14, fy - 46); u.lineTo(fx + 68, fy - 44); u.lineTo(fx + 68, fy - 38); u.lineTo(fx + 14, fy - 40); u.fill();
|
|
444
|
+
u.fillStyle = 'rgb(214,240,252)'; u.beginPath(); u.moveTo(fx + 62, fy - 44); u.lineTo(fx + 84, fy - 92); u.lineTo(fx + 94, fy - 88); u.lineTo(fx + 72, fy - 42); u.fill();
|
|
445
|
+
u.fillStyle = 'rgb(28,70,70)'; u.beginPath(); u.ellipse(fx + 20, fy + 6, 80, 10, 0, 0, 7); u.fill();
|
|
446
|
+
u.restore();
|
|
447
|
+
// standing figure with parasol (right)
|
|
448
|
+
const sx = 1080, sy = 640, sway = Math.sin(t * 1.2) * 0.05;
|
|
449
|
+
u.fillStyle = 'rgb(58,48,96)'; u.beginPath(); u.moveTo(sx - 18, sy - 150); u.lineTo(sx + 14, sy - 150); u.quadraticCurveTo(sx + 26, sy - 60, sx + 60, sy); u.lineTo(sx - 30, sy); u.quadraticCurveTo(sx - 26, sy - 80, sx - 18, sy - 150); u.fill();
|
|
450
|
+
u.fillStyle = 'rgb(226,170,140)'; u.beginPath(); u.arc(sx - 2, sy - 166, 13, 0, 7); u.fill();
|
|
451
|
+
u.fillStyle = 'rgb(40,34,70)'; u.beginPath(); u.ellipse(sx - 2, sy - 178, 20, 6, 0, 0, 7); u.fill();
|
|
452
|
+
u.save(); u.translate(sx + 6, sy - 140); u.rotate(-0.25 + sway);
|
|
453
|
+
u.strokeStyle = 'rgb(60,40,40)'; u.lineWidth = 3; u.beginPath(); u.moveTo(0, 0); u.lineTo(0, -70); u.stroke();
|
|
454
|
+
u.fillStyle = 'rgb(222,84,64)'; u.beginPath(); u.moveTo(-66, -60); u.quadraticCurveTo(0, -116, 66, -60); u.quadraticCurveTo(0, -70, -66, -60); u.fill();
|
|
455
|
+
u.restore();
|
|
456
|
+
u.fillStyle = 'rgb(30,72,70)'; u.beginPath(); u.ellipse(sx + 50, sy + 4, 90, 9, 0, 0, 7); u.fill();
|
|
457
|
+
// title in the sky
|
|
458
|
+
u.fillStyle = 'rgb(38,40,112)'; u.font = '800 italic 50px "Playfair Display"'; u.textBaseline = 'alphabetic';
|
|
459
|
+
// (lettering is NOT in the colour study: it is dabbed on top later with a finer brush)
|
|
460
|
+
const TM = H.layer(W, H_), tm = TM.getContext('2d');
|
|
461
|
+
tm.fillStyle = '#000'; tm.font = '800 italic 50px "Playfair Display"'; tm.fillText('A Sunday with the Machine', 540, 96);
|
|
462
|
+
const textMask = tm.getImageData(0, 0, W, H_).data;
|
|
463
|
+
u.filter = 'blur(2px)'; u.drawImage(U, 0, 0); u.filter = 'none';
|
|
464
|
+
const T = u.getImageData(0, 0, W, H_).data;
|
|
465
|
+
|
|
466
|
+
// ---------- 2. canvas ----------
|
|
467
|
+
ctx.fillStyle = 'rgb(232,222,198)'; ctx.fillRect(0, 0, W, H_);
|
|
468
|
+
ctx.save(); ctx.globalAlpha = 0.16; ctx.fillStyle = 'rgb(150,130,100)';
|
|
469
|
+
for (let y = 0; y < H_; y += 3) ctx.fillRect(0, y, W, 1);
|
|
470
|
+
ctx.globalAlpha = 0.1; for (let x = 0; x < W; x += 3) ctx.fillRect(x, 0, 1, H_);
|
|
471
|
+
ctx.restore();
|
|
472
|
+
|
|
473
|
+
// ---------- 3. dabs: greedy optical mixing per cell ----------
|
|
474
|
+
const D = H.layer(W, H_), dx = D.getContext('2d');
|
|
475
|
+
const CELL = 8, cw = Math.ceil(W / CELL), ch = Math.ceil(H_ / CELL);
|
|
476
|
+
const acc = new Float32Array(cw * ch * 3), cnt = new Uint16Array(cw * ch);
|
|
477
|
+
const r = H.rng(2025); // the colour decisions are stable...
|
|
478
|
+
const sh = H.rng(7000 + Math.floor(t * 6)); // ...but a few dabs flick to their runner-up hue: the shimmer
|
|
479
|
+
const live = H.clamp((t - 3.3) / 0.6);
|
|
480
|
+
const rp = H.rng(2025); // positions stay put
|
|
481
|
+
const dots = [];
|
|
482
|
+
// directional brush touches: oval dabs laid along a slow flow field (the hand's rhythm), slightly larger than before
|
|
483
|
+
const sp = 4.3, nzf = H.makeNoise(2026);
|
|
484
|
+
for (let y = 22; y < H_ - 20; y += sp * 0.87) for (let x = 22; x < W - 20; x += sp) { const px = x + rp.range(-1.6, 1.6) + ((y / sp) & 1) * sp * 0.5, py = y + rp.range(-1.4, 1.4), rr = rp.range(2.05, 2.7); rp();
|
|
485
|
+
dots.push([px, py, rr, -0.6 + 0.5 * nzf.n2(px * 0.004, py * 0.004) + nzf.n2(px * 0.3, py * 0.3) * 0.18]); }
|
|
486
|
+
// each dab gets its moment: the painter works down from the sky, dabbing all over each band
|
|
487
|
+
dots.forEach((d) => { d.push(0.12 + 2.7 * (0.45 * rp() + 0.55 * (d[1] / H_))); });
|
|
488
|
+
dots.sort((a, b) => a[4] - b[4]);
|
|
489
|
+
function choose(tc, ci, temp) {
|
|
490
|
+
const n = cnt[ci], k = ci * 3;
|
|
491
|
+
const want = [0, 1, 2].map((c) => tc[c] * (n + 1) - acc[k + c]);
|
|
492
|
+
let best = 0, bd = 1e12, second = 0, sd = 1e12;
|
|
493
|
+
const dark = tc[0] + tc[1] + tc[2] < 330; // shadows reach for ultramarine/violet rather than a darker same hue
|
|
494
|
+
for (let p = 0; p < PIG.length; p++) {
|
|
495
|
+
const P = PIG[p];
|
|
496
|
+
let d = (P[0] - want[0]) ** 2 + (P[1] - want[1]) ** 2 * 1.2 + (P[2] - want[2]) ** 2 + r() * temp;
|
|
497
|
+
if (dark && (p === 5 || p === 6 || p === 12)) d *= 0.78;
|
|
498
|
+
if (d < bd) { second = best; sd = bd; best = p; bd = d; } else if (d < sd) { second = p; sd = d; }
|
|
499
|
+
}
|
|
500
|
+
const rq = r(), pick = rq < 0.16 ? second : rq > 0.93 && COMP[best] !== best ? COMP[best] : best; // ~7% complements: the vibration
|
|
501
|
+
acc[k] += PIG[pick][0]; acc[k + 1] += PIG[pick][1]; acc[k + 2] += PIG[pick][2]; cnt[ci]++;
|
|
502
|
+
return live > 0 && sh() < 0.07 * live ? (pick === best ? second : best) : pick;
|
|
503
|
+
}
|
|
504
|
+
const batches = PIG.map(() => []);
|
|
505
|
+
const flush = () => {
|
|
506
|
+
batches.forEach((list, p) => {
|
|
507
|
+
if (!list.length) return;
|
|
508
|
+
dx.fillStyle = css(PIG[p]); dx.beginPath();
|
|
509
|
+
for (const [x, y, rr, a] of list) { dx.moveTo(x + rr * Math.cos(a), y + rr * Math.sin(a)); dx.ellipse(x, y, rr, rr * (rr > 1.7 ? 0.62 : 0.78), a, 0, 6.2832); }
|
|
510
|
+
dx.fill(); list.length = 0;
|
|
511
|
+
});
|
|
512
|
+
};
|
|
513
|
+
dots.forEach((d0, i) => {
|
|
514
|
+
if (d0[4] > t) return;
|
|
515
|
+
const pop = H.ease.back(H.clamp((t - d0[4]) / 0.09));
|
|
516
|
+
const d = pop >= 1 ? d0 : [d0[0], d0[1], d0[2] * pop, d0[3]];
|
|
517
|
+
const [x, y] = d, xi = Math.min(W - 1, x | 0), yi = Math.min(H_ - 1, y | 0), o = (yi * W + xi) * 4;
|
|
518
|
+
const tc = [T[o], T[o + 1], T[o + 2]];
|
|
519
|
+
const ci = Math.min(ch - 1, (y / CELL) | 0) * cw + Math.min(cw - 1, (x / CELL) | 0);
|
|
520
|
+
batches[choose(tc, ci, 2600)].push(d);
|
|
521
|
+
if (i % 9000 === 8999) flush();
|
|
522
|
+
});
|
|
523
|
+
flush();
|
|
524
|
+
// finer dabs for the lettering and the figures' faces: keeps edges crisp
|
|
525
|
+
const fine = [];
|
|
526
|
+
for (let y = 40; y < 115; y += 1.7) for (let x = 500; x < 1220; x += 1.8) {
|
|
527
|
+
const xi = (x + rp.range(-0.8, 0.8)) | 0, yi = (y + rp.range(-0.8, 0.8)) | 0;
|
|
528
|
+
const fr = rp.range(1.2, 1.55), fa = rp() * 3, fb = 2.85 + (x - 500) / 720 * 0.55 + rp() * 0.08;
|
|
529
|
+
if (textMask[(yi * W + xi) * 4 + 3] > 120 && t >= fb) fine.push([xi + 0.5, yi + 0.5, fr * H.ease.out(H.clamp((t - fb) / 0.08)), fa]);
|
|
530
|
+
}
|
|
531
|
+
fine.forEach((d) => { const q = H.rng((d[0] * 31 + d[1] * 7919) | 0); const pick = q() < 0.55 ? 6 : q() < 0.7 ? 12 : q() < 0.6 ? 5 : 3; batches[pick].push(d); });
|
|
532
|
+
flush();
|
|
533
|
+
|
|
534
|
+
// ---------- 4. dab relief: each dab catches light on its upper-left rim ----------
|
|
535
|
+
ctx.drawImage(D, 0, 0);
|
|
536
|
+
const img = ctx.getImageData(0, 0, W, H_), d = img.data, a = dx.getImageData(0, 0, W, H_).data;
|
|
537
|
+
for (let y = 1; y < H_ - 1; y++) for (let x = 1; x < W - 1; x++) {
|
|
538
|
+
const i = (y * W + x) * 4;
|
|
539
|
+
const g = (a[i + 3 - 4 - W * 4] - a[i + 3 + 4 + W * 4]);
|
|
540
|
+
if (g === 0) continue;
|
|
541
|
+
const f = 1 + (g < 0 ? 0.2 : -0.17) * Math.abs(g) / 255; // lit upper-left rim, shadowed lower-right rim
|
|
542
|
+
d[i] *= f; d[i + 1] *= f; d[i + 2] *= f;
|
|
543
|
+
}
|
|
544
|
+
ctx.putImageData(img, 0, 0);
|
|
545
|
+
|
|
546
|
+
// ---------- 5. painted border of dots (complementary, darker) ----------
|
|
547
|
+
const br = H.rng(66);
|
|
548
|
+
const border = PIG.map(() => []);
|
|
549
|
+
for (let k = 0; k < 2600; k++) {
|
|
550
|
+
const side = br.int(0, 3), along = br();
|
|
551
|
+
let x, y; const depth = br.range(2, 16);
|
|
552
|
+
if (side === 0) { x = along * W; y = depth; } else if (side === 1) { x = along * W; y = H_ - depth; } else if (side === 2) { x = depth; y = along * H_; } else { x = W - depth; y = along * H_; }
|
|
553
|
+
const p = br.pick([6, 6, 12, 5, 2, 3, 6, 12]), rr = br.range(2, 2.8), an = br() * 3;
|
|
554
|
+
// the border is dabbed on last, travelling clockwise round the frame
|
|
555
|
+
const pos = side === 0 ? along * 0.25 : side === 3 ? 0.25 + along * 0.25 : side === 1 ? 0.5 + (1 - along) * 0.25 : 0.75 + (1 - along) * 0.25;
|
|
556
|
+
const bb = 3.2 + pos * 0.8;
|
|
557
|
+
if (t >= bb) border[p].push([x, y, rr * H.ease.out(H.clamp((t - bb) / 0.08)), an]);
|
|
558
|
+
}
|
|
559
|
+
border.forEach((list, p) => { if (!list.length) return; ctx.fillStyle = css(PIG[p]); ctx.beginPath(); for (const [x, y, rr, an] of list) { ctx.moveTo(x + rr, y); ctx.ellipse(x, y, rr, rr * 0.8, an, 0, 6.2832); } ctx.fill(); });
|
|
560
|
+
ctx.strokeStyle = `rgba(40,40,90,${0.35 * H.clamp((t - 3.9) / 0.3)})`; ctx.lineWidth = 1; ctx.strokeRect(19.5, 19.5, W - 39, H_ - 39);
|
|
561
|
+
}
|
|
562
|
+
});
|
|
563
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 71,
|
|
3
|
+
"name": "Pointillist Afternoon",
|
|
4
|
+
"nameZh": "点彩午后",
|
|
5
|
+
"family": "Painterly",
|
|
6
|
+
"familyZh": "绘画质感",
|
|
7
|
+
"description": "Neo-Impressionist divisionism: every form is built from small dabs of unmixed pigment — complementary hues side by side that blend optically — with the canvas weave peeking between the dots and a dotted painted border.",
|
|
8
|
+
"motionNotes": "Motion: Dots shimmer as individual dabs re-pick their hue each frame while the overall colour holds, like light vibrating on the water; the sailboat drifts and the parasol sways.",
|
|
9
|
+
"explainerNotes": "Explainer: Every frame re-dabs one fixed field of pure-pigment dots against the current scene, so moving things shimmer as dots re-pick their hue; scene changes are the canvas being re-dabbed in a diagonal sweep, and lettering, arrows, counters and the emphasis rings are all fine dabs.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "pointillism",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|