@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": 48,
|
|
3
|
+
"name": "Transit Map",
|
|
4
|
+
"nameZh": "公共交通线路图",
|
|
5
|
+
"family": "Data & Information",
|
|
6
|
+
"familyZh": "数据与信息",
|
|
7
|
+
"description": "A Beck-style metro diagram of an AI pipeline: fat coloured lines locked to 0/45/90 degrees with rounded bends, line-colour station ticks, black-ringed interchanges, a river and a boxed legend, all in clean sans lettering.",
|
|
8
|
+
"motionNotes": "Motion: Title and key settle, then each line draws out from its terminus in turn with stations ticking on and interchanges popping as the line passes; once the network is complete, small trains glide along every route.",
|
|
9
|
+
"explainerNotes": "Explainer: The lesson is a journey on the network: the camera rides along a Data Line into the Model interchange, round a Training Loop line (Guess, Check, Adjust stations) and out along an Accuracy Line, while an amber dot-matrix departure board counts examples and accuracy and finally flips to the takeaway.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "transit-map",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|
|
@@ -0,0 +1,559 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------------------------------------------------------------
|
|
3
|
+
// Gouache travel poster — flat opaque matte shapes, dry-brush edges
|
|
4
|
+
// ---------------------------------------------------------------
|
|
5
|
+
const PAL = {
|
|
6
|
+
navy: [30, 52, 66], teal: [47, 127, 131], orange: [224, 97, 43],
|
|
7
|
+
mustard: [242, 180, 64], cream: [243, 231, 204], rose: [234, 165, 138],
|
|
8
|
+
};
|
|
9
|
+
const tint = (c, k) => H.mix(c, PAL.cream, k);
|
|
10
|
+
|
|
11
|
+
// anisotropic value noise field (0..1): long cells along the brush direction, short across it
|
|
12
|
+
function field(W, H_, cx, cy, seed) {
|
|
13
|
+
const r = H.rng(seed);
|
|
14
|
+
const gw = Math.ceil(W / cx) + 2, gh = Math.ceil(H_ / cy) + 2, g = new Float32Array(gw * gh);
|
|
15
|
+
for (let i = 0; i < g.length; i++) g[i] = r();
|
|
16
|
+
const out = new Float32Array(W * H_);
|
|
17
|
+
for (let y = 0; y < H_; y++) {
|
|
18
|
+
const fy = y / cy, iy = fy | 0, ty = fy - iy, sy = ty * ty * (3 - 2 * ty);
|
|
19
|
+
for (let x = 0; x < W; x++) {
|
|
20
|
+
const fx = x / cx, ix = fx | 0, tx = fx - ix, sx = tx * tx * (3 - 2 * tx);
|
|
21
|
+
const a = g[iy * gw + ix], b = g[iy * gw + ix + 1], c = g[(iy + 1) * gw + ix], d = g[(iy + 1) * gw + ix + 1];
|
|
22
|
+
out[y * W + x] = a + (b - a) * sx + (c - a) * sy + (a - b - c + d) * sx * sy;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return out;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
// =================================================================
|
|
30
|
+
// EXPLAINER — "How does AI learn?" as a run of hand-painted posters. Scene changes are a fresh coat of
|
|
31
|
+
// cream gouache swept across the board, then the next poster is painted over it, flat colour by flat colour.
|
|
32
|
+
// =================================================================
|
|
33
|
+
let GXC = null;
|
|
34
|
+
function gpCache(W, H_) {
|
|
35
|
+
if (GXC && GXC.W === W && GXC.H === H_) return GXC;
|
|
36
|
+
GXC = { W, H: H_, hS: field(W, H_, 46, 2.2, 11), hS2: field(W, H_, 120, 6, 12), vS: field(W, H_, 2.4, 40, 13), grain: null, SP: {}, BG: {} };
|
|
37
|
+
// static matte board grain (added to every frame)
|
|
38
|
+
const g = new Float32Array(W * H_), pr = H.rng(4), nz = H.makeNoise(5);
|
|
39
|
+
for (let i = 0; i < W * H_; i++) { const x = i % W, y = (i / W) | 0; g[i] = (pr() - 0.5) * 9 + (GXC.hS2[i] - 0.5) * 4 + nz.n2(x * 0.5, y * 0.5) * 3; }
|
|
40
|
+
GXC.grain = g;
|
|
41
|
+
// texture is split from edges: a mostly isotropic field raggeds the edges, broad brush bands (not 2px streaks)
|
|
42
|
+
// carry the body tone, and a slow lumpy field gives the chalky bloom of dried matte gouache
|
|
43
|
+
GXC.eF = field(W, H_, 7, 5, 14);
|
|
44
|
+
GXC.bH = field(W, H_, 90, 9, 15); GXC.bV = field(W, H_, 9, 90, 16);
|
|
45
|
+
const ch = field(W, H_, 26, 22, 17), cm = new Float32Array(W * H_);
|
|
46
|
+
for (let i = 0; i < cm.length; i++) { const u = H.clamp((ch[i] - 0.6) / 0.25); cm[i] = 1 + 0.03 * u * u * (3 - 2 * u); }
|
|
47
|
+
GXC.chalk = cm;
|
|
48
|
+
GXC.S = H.layer(W, H_); GXC.B = H.layer(W, H_);
|
|
49
|
+
GXC.S.getContext('2d', { willReadFrequently: true }); GXC.B.getContext('2d', { willReadFrequently: true }); // CPU-backed: cheap readback
|
|
50
|
+
return GXC;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// paint(): one opaque colour, one pass of the brush. Mask -> blurred mask -> ragged coverage, then a matte body
|
|
54
|
+
// with broad stroke bands, a slightly darker dried ridge just inside the edge and chalky bloom.
|
|
55
|
+
// dir: 'h' / 'v' brushing, 'd' diagonal (sheared) brushing for slopes. key: cache a finished (p = 1) static pass.
|
|
56
|
+
function makePainter(buf, W, H_, Cx, defEdge, defRag) {
|
|
57
|
+
const { hS, hS2, vS, eF, bH, bV, chalk, SP } = Cx, sx = Cx.S.getContext('2d'), bx = Cx.B.getContext('2d');
|
|
58
|
+
return function paint(col, bb, drawFn, { edge = defEdge, rag = defRag, dir = 'h', tex = 0.07, p = 1, key = null } = {}) {
|
|
59
|
+
if (p <= 0) return;
|
|
60
|
+
if (key && p >= 1 && SP[key]) {
|
|
61
|
+
const { idx, cv, cov } = SP[key];
|
|
62
|
+
for (let j = 0, n = idx.length; j < n; j++) { const o = idx[j] * 4, c = cov[j], q = j * 3; buf[o] += (cv[q] - buf[o]) * c; buf[o + 1] += (cv[q + 1] - buf[o + 1]) * c; buf[o + 2] += (cv[q + 2] - buf[o + 2]) * c; }
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const rec = key && p >= 1 ? { idx: [], cv: [], cov: [] } : null;
|
|
66
|
+
let [x0, y0, x1, y1] = bb;
|
|
67
|
+
x0 = Math.max(0, Math.floor(x0 - edge * 2)); y0 = Math.max(0, Math.floor(y0 - edge * 2));
|
|
68
|
+
x1 = Math.min(W, Math.ceil(x1 + edge * 2)); y1 = Math.min(H_, Math.ceil(y1 + edge * 2));
|
|
69
|
+
const w = x1 - x0, h = y1 - y0; if (w <= 0 || h <= 0) return;
|
|
70
|
+
sx.clearRect(x0, y0, w, h); sx.save(); sx.fillStyle = '#fff'; sx.strokeStyle = '#fff'; drawFn(sx); sx.restore();
|
|
71
|
+
bx.clearRect(x0, y0, w, h); bx.filter = `blur(${edge}px)`; bx.drawImage(Cx.S, x0, y0, w, h, x0, y0, w, h); bx.filter = 'none';
|
|
72
|
+
const A = bx.getImageData(x0, y0, w, h).data, F = dir === 'v' ? vS : hS, BD = dir === 'v' ? bV : bH, diag = dir === 'd';
|
|
73
|
+
for (let y = 0; y < h; y++) for (let x = 0; x < w; x++) {
|
|
74
|
+
const k = (y * w + x) * 4, b = A[k + 3] / 255;
|
|
75
|
+
if (b < 0.02) continue;
|
|
76
|
+
const gi = (y + y0) * W + (x + x0);
|
|
77
|
+
const si = diag ? ((y + y0 + (((x + x0) * 0.42) | 0)) % H_) * W + (x + x0) : gi; // sheared lookup = diagonal strokes
|
|
78
|
+
const n = F[si];
|
|
79
|
+
const thr = Math.min(0.88, 0.5 + ((eF[gi] - 0.5) * 0.65 + (n - 0.5) * 0.35) * rag * 0.7);
|
|
80
|
+
let cov = (b - thr) / 0.08 + 0.5; cov = cov < 0 ? 0 : cov > 1 ? 1 : cov;
|
|
81
|
+
if (cov <= 0) continue;
|
|
82
|
+
if (p < 1) {
|
|
83
|
+
// the brush is mid-sweep: left->right for horizontal brushing, bottom->top for vertical; ragged dry front
|
|
84
|
+
const sw = dir === 'v' ? 1 - y / h : x / w, front = p * 1.15 - sw + (n - 0.5) * 0.1;
|
|
85
|
+
if (front <= 0) continue;
|
|
86
|
+
cov *= Math.min(1, front / 0.035);
|
|
87
|
+
}
|
|
88
|
+
let v = 1 + (hS2[gi] - 0.5) * tex * 0.65 + (BD[si] - 0.5) * tex * 0.5 + (n - 0.5) * tex * 0.25;
|
|
89
|
+
const rim = b - thr; if (rim < 0.3) v *= 1 - 0.06 * (1 - rim / 0.3); // paint piles up a touch at the edge
|
|
90
|
+
v *= chalk[gi];
|
|
91
|
+
const o = gi * 4, r0 = col[0] * v, g0 = col[1] * v, b0 = col[2] * v;
|
|
92
|
+
buf[o] += (r0 - buf[o]) * cov; buf[o + 1] += (g0 - buf[o + 1]) * cov; buf[o + 2] += (b0 - buf[o + 2]) * cov;
|
|
93
|
+
if (rec) { rec.idx.push(gi); rec.cv.push(r0, g0, b0); rec.cov.push(cov); }
|
|
94
|
+
}
|
|
95
|
+
if (rec) SP[key] = { idx: Int32Array.from(rec.idx), cv: Float32Array.from(rec.cv), cov: Float32Array.from(rec.cov) };
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function explainGouache(ctx, W, H_, t) {
|
|
100
|
+
const { navy, teal, orange, mustard, cream, rose } = PAL;
|
|
101
|
+
const Cx = gpCache(W, H_), { grain } = Cx;
|
|
102
|
+
const buf = new Uint8ClampedArray(W * H_ * 4);
|
|
103
|
+
for (let i = 0; i < buf.length; i += 4) { buf[i] = cream[0]; buf[i + 1] = cream[1]; buf[i + 2] = cream[2]; buf[i + 3] = 255; }
|
|
104
|
+
const paint = makePainter(buf, W, H_, Cx, 2.5, 0.8);
|
|
105
|
+
const G = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
106
|
+
const poly = (c, pts) => { c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); c.fill(); };
|
|
107
|
+
const txt = (c, s, x, y, size, align = 'left', font = 'Bebas Neue', sp = 3) => { c.font = `400 ${size}px "${font}"`; c.textAlign = align; c.textBaseline = 'alphabetic'; if (c.letterSpacing !== undefined) c.letterSpacing = sp + 'px'; c.fillText(s, x, y); };
|
|
108
|
+
const fmt = (n) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
109
|
+
// flat poster animals
|
|
110
|
+
const head = (c, x, y, s, dog) => {
|
|
111
|
+
if (dog) { c.beginPath(); c.ellipse(x, y, s * 0.36, s * 0.34, 0, 0, 7); c.fill(); }
|
|
112
|
+
else poly(c, [[x - s * 0.4, y - s * 0.05], [x - s * 0.38, y - s * 0.46], [x - s * 0.14, y - s * 0.26], [x + s * 0.14, y - s * 0.26], [x + s * 0.38, y - s * 0.46], [x + s * 0.4, y - s * 0.05], [x + s * 0.3, y + s * 0.26], [x, y + s * 0.36], [x - s * 0.3, y + s * 0.26]]);
|
|
113
|
+
};
|
|
114
|
+
const ears = (c, x, y, s) => { for (const k of [-1, 1]) { c.beginPath(); c.ellipse(x + k * s * 0.36, y + s * 0.02, s * 0.11, s * 0.24, k * 0.3, 0, 7); c.fill(); } };
|
|
115
|
+
const eyes = (c, x, y, s) => { c.beginPath(); c.arc(x - s * 0.13, y - s * 0.02, s * 0.05, 0, 7); c.arc(x + s * 0.13, y - s * 0.02, s * 0.05, 0, 7); c.fill(); poly(c, [[x - s * 0.06, y + s * 0.12], [x + s * 0.06, y + s * 0.12], [x, y + s * 0.19]]); };
|
|
116
|
+
// a whole labelled card, painted colour by colour (each colour is one brush pass)
|
|
117
|
+
// key: cache each finished pass (only for a card set that no longer moves)
|
|
118
|
+
function cards(list, bb, p = 1, key = null) {
|
|
119
|
+
const K = (i) => (key ? key + i : null);
|
|
120
|
+
paint(cream, bb, (c) => list.forEach(({ x, y, s }) => c.fillRect(x - s / 2, y - s / 2, s, s)), { edge: 1.4, rag: 0.5, p, key: K(1) });
|
|
121
|
+
paint(orange, bb, (c) => list.forEach(({ x, y, s, dog }) => { if (!dog) head(c, x, y + s * 0.04, s * 0.8, false); }), { edge: 1, rag: 0.4, p, key: K(2) });
|
|
122
|
+
paint(H.mix(rose, navy, 0.25), bb, (c) => list.forEach(({ x, y, s, dog }) => { if (dog) head(c, x, y, s * 0.8, true); }), { edge: 1, rag: 0.4, p, key: K(3) });
|
|
123
|
+
paint(H.mix(orange, navy, 0.55), bb, (c) => list.forEach(({ x, y, s, dog }) => { if (dog) ears(c, x, y, s * 0.8); }), { edge: 1, rag: 0.4, p, key: K(4) });
|
|
124
|
+
paint(navy, bb, (c) => list.forEach(({ x, y, s, dog, tag }) => { eyes(c, x, y + s * 0.02, s * 0.8); if (tag !== false && s > 80) c.fillRect(x - s * 0.36, y + s * 0.5, s * 0.72, s * 0.3); }), { edge: 0.8, rag: 0.3, p, key: K(5) });
|
|
125
|
+
paint(cream, bb, (c) => list.forEach(({ x, y, s, dog, tag }) => { if (tag !== false && s > 80) txt(c, dog ? 'DOG' : 'CAT', x, y + s * 0.74, Math.max(24, Math.round(s * 0.27)), 'center', 'Bebas Neue', 2); }), { edge: 0.6, rag: 0.2, p, key: K(6) });
|
|
126
|
+
}
|
|
127
|
+
// the learning machine (retro factory), with three gauges = its weights
|
|
128
|
+
function machine(x, y, s, p0, gauges, key = null, gStatic = false) {
|
|
129
|
+
const K = (i) => (key ? key + i : null);
|
|
130
|
+
const P = (a) => G(p0 + a, p0 + a + 0.35);
|
|
131
|
+
const bb = [x - 20, y - 260 * s, x + 330 * s, y + 10];
|
|
132
|
+
paint(teal, bb, (c) => { c.fillRect(x, y - 200 * s, 300 * s, 200 * s); poly(c, [[x - 10, y - 200 * s], [x + 150 * s, y - 250 * s], [x + 310 * s, y - 200 * s]]); }, { dir: 'v', p: P(0), key: K('a') });
|
|
133
|
+
paint(H.mix(teal, navy, 0.5), bb, (c) => { c.fillRect(x + 220 * s, y - 200 * s, 80 * s, 200 * s); c.fillRect(x + 236 * s, y - 300 * s, 34 * s, 80 * s); }, { dir: 'v', p: P(0.15), key: K('b') });
|
|
134
|
+
paint(cream, bb, (c) => { for (let k = 0; k < 3; k++) { c.beginPath(); c.arc(x + (50 + k * 64) * s, y - 120 * s, 26 * s, 0, 7); c.fill(); } c.fillRect(x + 6 * s, y - 72 * s, 208 * s, 36 * s); }, { edge: 1.2, rag: 0.4, p: P(0.3), key: K('c') });
|
|
135
|
+
paint(navy, bb, (c) => {
|
|
136
|
+
for (let k = 0; k < 3; k++) { const a = gauges[k], gx = x + (50 + k * 64) * s, gy = y - 120 * s; c.save(); c.translate(gx, gy); c.rotate(a); c.fillRect(-2.5 * s, -22 * s, 5 * s, 24 * s); c.restore(); c.beginPath(); c.arc(gx, gy, 4 * s, 0, 7); c.fill(); }
|
|
137
|
+
txt(c, 'LEARNING MACHINE', x + 110 * s, y - 45 * s, Math.max(24, Math.round(22 * s)), 'center', 'Oswald', 1);
|
|
138
|
+
}, { edge: 0.8, rag: 0.3, p: P(0.45), key: gStatic ? K('d' + gauges.map((a) => a.toFixed(3)).join()) : null });
|
|
139
|
+
paint(orange, bb, (c) => { c.fillRect(x + 236 * s, y - 306 * s, 34 * s, 14 * s); }, { edge: 0.8, rag: 0.3, p: P(0.5), key: K('e') });
|
|
140
|
+
}
|
|
141
|
+
const navyBand = () => paint(navy, [0, 560, W, H_], (c) => c.fillRect(0, 572, W, H_ - 572), { p: G(0.2, 0.7), key: 'band' });
|
|
142
|
+
const bandText = (s, a, size = 96, key = null) => paint(cream, [30, 570, W - 30, 715], (c) => txt(c, s, 52, 680, size), { edge: 1.2, rag: 0.6, p: G(a, a + 0.45), key });
|
|
143
|
+
|
|
144
|
+
const scene = t < 6.35 ? 1 : t < 10.85 ? 2 : 3;
|
|
145
|
+
// ======== scene backgrounds (sky + ground), re-painted for each new poster ========
|
|
146
|
+
const bgStart = scene === 1 ? 0 : scene === 2 ? 6.35 : 10.85;
|
|
147
|
+
const skyCol = scene === 1 ? tint(teal, 0.55) : scene === 2 ? tint(rose, 0.35) : tint(mustard, 0.55);
|
|
148
|
+
// once every background pass of the current poster is finished, the painted board is the same on every frame:
|
|
149
|
+
// it is kept as a snapshot instead of being repainted
|
|
150
|
+
const bgP = [G(bgStart, bgStart + 0.45), G(bgStart + 0.1, bgStart + 0.5), G(bgStart + 0.15, bgStart + 0.55), G(bgStart + 0.2, bgStart + 0.6), G(0.2, 0.7)];
|
|
151
|
+
const bgKey = 's' + scene, bgDone = bgP.every((v) => v >= 1);
|
|
152
|
+
if (bgDone && Cx.BG[bgKey]) buf.set(Cx.BG[bgKey]);
|
|
153
|
+
else {
|
|
154
|
+
paint(skyCol, [0, 0, W, scene === 3 ? 572 : 470], (c) => c.fillRect(0, 0, W, scene === 3 ? 566 : 470), { edge: 4, p: bgP[0] });
|
|
155
|
+
// tonal depth like the still's sky bands and receding ranges (two extra values per poster)
|
|
156
|
+
const ridge = (c, y, amp, ph) => { c.beginPath(); c.moveTo(0, 480); for (let x = 0; x <= W; x += 40) c.lineTo(x, y + Math.sin(x * 0.006 + ph) * amp + Math.sin(x * 0.017 + ph * 2) * amp * 0.35); c.lineTo(W, 480); c.closePath(); c.fill(); };
|
|
157
|
+
if (scene === 1) {
|
|
158
|
+
paint(tint(teal, 0.68), [0, 250, W, 470], (c) => c.fillRect(0, 262, W, 208), { edge: 4, rag: 1, p: bgP[1] });
|
|
159
|
+
paint(tint(teal, 0.42), [0, 330, W, 480], (c) => ridge(c, 392, 26, 0.4), { edge: 3, rag: 1, dir: 'd', p: bgP[2] });
|
|
160
|
+
} else if (scene === 2) {
|
|
161
|
+
paint(tint(rose, 0.58), [0, 250, W, 470], (c) => c.fillRect(0, 268, W, 202), { edge: 4, rag: 1, p: bgP[1] });
|
|
162
|
+
paint(tint(teal, 0.48), [0, 360, W, 480], (c) => ridge(c, 424, 18, 2.1), { edge: 3, rag: 1, dir: 'd', p: bgP[2] });
|
|
163
|
+
} else {
|
|
164
|
+
paint(tint(mustard, 0.36), [0, 0, W, 170], (c) => c.fillRect(0, 0, W, 150), { edge: 4, rag: 1, p: bgP[1] });
|
|
165
|
+
paint(tint(mustard, 0.74), [0, 380, W, 572], (c) => c.fillRect(0, 398, W, 168), { edge: 4, rag: 1, p: bgP[2] });
|
|
166
|
+
}
|
|
167
|
+
if (scene !== 3) paint(scene === 1 ? H.mix(teal, navy, 0.25) : tint(teal, 0.25), [0, 420, W, 572], (c) => { c.beginPath(); c.moveTo(0, 470); c.bezierCurveTo(400, 440, 900, 480, 1280, 450); c.lineTo(1280, 572); c.lineTo(0, 572); c.fill(); }, { edge: 3, p: bgP[3] });
|
|
168
|
+
navyBand();
|
|
169
|
+
if (bgDone) Cx.BG[bgKey] = buf.slice();
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (scene === 1) {
|
|
173
|
+
// ---- the machine and its conveyor of labelled examples ----
|
|
174
|
+
machine(900, 470, 1.05, 0.9, [-0.9, 0.2, 1.0], 'm1', true);
|
|
175
|
+
paint(mustard, [0, 440, 900, 500], (c) => { c.fillRect(0, 448, 900, 22); }, { p: G(1.6, 2.1), key: 'belt' });
|
|
176
|
+
paint(navy, [0, 440, 900, 520], (c) => { const off = (t * 60) % 40; for (let x = -40 + off; x < 900; x += 40) c.fillRect(x, 452, 16, 14); for (let x = 30; x < 900; x += 110) { c.beginPath(); c.arc(x, 482, 12, 0, 7); c.fill(); } }, { edge: 1, rag: 0.4, p: G(1.9, 2.3) });
|
|
177
|
+
// hopper on the machine's side
|
|
178
|
+
paint(orange, [790, 330, 910, 470], (c) => poly(c, [[800, 360], [898, 360], [884, 450], [856, 450]]), { dir: 'v', p: G(2.0, 2.3), key: 'hopper' });
|
|
179
|
+
const list = [];
|
|
180
|
+
for (let i = 0; i < 20; i++) { const s0 = 2.4 + i * 0.18, u = (t - s0) / 1.4; if (u <= 0 || u >= 1) continue; const x = -60 + u * 940, lift = x > 800 ? (x - 800) * 0.9 : 0; list.push({ x: Math.min(x, 860), y: 390 - lift, s: x > 800 ? 96 * (1 - (x - 800) / 140) : 96, dog: (i % 2 === 1) !== (i % 5 === 0) }); }
|
|
181
|
+
if (list.length) cards(list, [-120, 260, 960, 470]);
|
|
182
|
+
// counter in the band
|
|
183
|
+
if (t > 2.6) { const n = t > 5.6 ? 1e6 : Math.round(1e6 * H.ease.in(H.clamp((t - 2.7) / 2.9)) / 1000) * 1000; bandText(fmt(n) + ' EXAMPLES', 2.6, 96, n >= 1e6 ? 'bt1' : null); }
|
|
184
|
+
// a flat poster arrow pointing into the hopper with its label
|
|
185
|
+
paint(cream, [380, 200, 860, 330], (c) => { c.fillRect(420, 250, 330, 34); poly(c, [[750, 228], [810, 267], [750, 306]]); }, { p: G(2.6, 3.0), key: 'arr1' });
|
|
186
|
+
paint(navy, [380, 200, 860, 330], (c) => txt(c, 'LABELLED EXAMPLES', 432, 279, 30, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(2.9, 3.2), key: 'arr1t' });
|
|
187
|
+
if (t > 5.6) paint(orange, [30, 570, 900, 700], (c) => c.fillRect(52, 690, 700, 6), { p: G(5.6, 5.9), key: 'ul1' });
|
|
188
|
+
}
|
|
189
|
+
if (scene === 2) {
|
|
190
|
+
const ga = [-0.9, 0.2, 1.0].map((a, k) => a + G(8.6, 9.2) * [0.9, -0.8, -1.3][k]);
|
|
191
|
+
machine(120, 500, 1.15, 6.6, ga, 'm2', t < 8.6 || t > 9.2);
|
|
192
|
+
// the new picture drops in at the top of the board
|
|
193
|
+
cards([{ x: 270, y: 120 + (1 - G(6.9, 7.2)) * -140, s: 110, dog: false, tag: false }], [190, 20, 360, 200], 1, t >= 7.2 ? 'np' : null);
|
|
194
|
+
paint(navy, [340, 80, 700, 170], (c) => txt(c, 'NEW PICTURE', 350, 132, 34, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(7.0, 7.3), key: 'npt' });
|
|
195
|
+
paint(mustard, [250, 170, 300, 215], (c) => poly(c, [[262, 178], [278, 178], [278, 196], [288, 196], [270, 214], [252, 196], [262, 196]]), { dir: 'v', p: G(7.15, 7.35), key: 'npa' });
|
|
196
|
+
// GUESS: output ticket
|
|
197
|
+
const second = t > 9.4;
|
|
198
|
+
paint(mustard, [470, 300, 820, 380], (c) => { c.fillRect(480, 318, 240, 40); poly(c, [[720, 300], [790, 338], [720, 376]]); }, { p: G(7.1, 7.4), key: 'gt' });
|
|
199
|
+
paint(navy, [470, 300, 820, 380], (c) => txt(c, 'GUESS', 500, 350, 34, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(7.2, 7.5), key: 'gtt' });
|
|
200
|
+
paint(cream, [800, 240, 1180, 440], (c) => c.fillRect(820, 260, 320, 160), { edge: 2, p: G(7.3, 7.6), key: 'card2' });
|
|
201
|
+
paint(second ? teal : H.mix(rose, navy, 0.62), [800, 240, 1180, 440], (c) => txt(c, second ? 'CAT' : 'DOG?', 980, 380, 120, 'center'), { edge: 1, rag: 0.5, p: second ? G(9.4, 9.7) : G(7.4, 7.7), key: second ? 'ansC' : 'ansD' });
|
|
202
|
+
// CHECK: wrong! an orange starburst stamp and a big cross
|
|
203
|
+
if (t > 7.9 && !second) {
|
|
204
|
+
const k = G(7.9, 8.05);
|
|
205
|
+
paint(orange, [760, 180, 1220, 500], (c) => { c.beginPath(); for (let i = 0; i < 28; i++) { const a = i / 28 * Math.PI * 2, r = (i % 2 ? 150 : 210) * (0.6 + 0.4 * k); c.lineTo(980 + Math.cos(a) * r * 1.15, 340 + Math.sin(a) * r * 0.8); } c.closePath(); c.fill(); }, { edge: 1.5, rag: 0.5, p: k, key: k >= 1 ? 'burst2' : null });
|
|
206
|
+
paint(cream, [800, 240, 1180, 440], (c) => c.fillRect(820, 260, 320, 160), { edge: 2, key: 'card2' });
|
|
207
|
+
paint(H.mix(rose, navy, 0.62), [800, 240, 1180, 440], (c) => txt(c, 'DOG?', 980, 380, 120, 'center'), { edge: 1, rag: 0.5, key: 'ansD' });
|
|
208
|
+
paint(navy, [800, 240, 1180, 440], (c) => { c.save(); c.translate(980, 340); for (const a of [0.7, -0.7]) { c.save(); c.rotate(a); c.fillRect(-120, -11, 240, 22); c.restore(); } c.restore(); }, { edge: 1, rag: 0.4, p: G(8.0, 8.2), key: 'x2' });
|
|
209
|
+
paint(cream, [800, 150, 1170, 240], (c) => c.fillRect(812, 172, 336, 56), { edge: 1.5, p: G(8.05, 8.25), key: 'cks' });
|
|
210
|
+
paint(navy, [800, 150, 1170, 240], (c) => txt(c, "CHECK: IT'S A CAT", 980, 216, 46, 'center', 'Bebas Neue', 3), { edge: 0.8, rag: 0.3, p: G(8.15, 8.45), key: 'ckt' });
|
|
211
|
+
}
|
|
212
|
+
// ADJUST: a flat arrow loops back under the ticket into the machine
|
|
213
|
+
paint(rose, [380, 420, 1180, 560], (c) => { c.beginPath(); c.moveTo(1000, 430); c.lineTo(1000, 520); c.lineTo(480, 520); c.lineTo(480, 470); c.lineTo(450, 470); c.lineTo(500, 425); c.lineTo(550, 470); c.lineTo(520, 470); c.lineTo(520, 490); c.lineTo(960, 490); c.lineTo(960, 430); c.closePath(); c.fill(); }, { dir: 'h', p: G(8.3, 8.7), key: 'adj' });
|
|
214
|
+
paint(navy, [560, 480, 960, 540], (c) => txt(c, 'ADJUST', 640, 514, 30, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(8.6, 8.8), key: 'adjt' });
|
|
215
|
+
if (second) paint(teal, [1120, 240, 1260, 420], (c) => { c.lineWidth = 22; c.lineCap = 'round'; c.lineJoin = 'round'; c.beginPath(); c.moveTo(1150, 330); c.lineTo(1180, 370); c.lineTo(1240, 270); c.stroke(); }, { edge: 1, rag: 0.4, p: G(9.7, 9.95), key: 'tick' });
|
|
216
|
+
bandText('GUESS · CHECK · ADJUST', 6.8, 96, 'bt2');
|
|
217
|
+
}
|
|
218
|
+
if (scene === 3) {
|
|
219
|
+
// a flat bar chart climbing over training rounds
|
|
220
|
+
const N = 10, x0 = 110, base = 510, bw = 64, gap = 22; // chart sits 10px higher so the axis label clears the band
|
|
221
|
+
paint(navy, [100, 120, 1200, 530], (c) => { c.fillRect(110, 514, 1020, 8); }, { p: G(11.0, 11.3), key: 'axis' });
|
|
222
|
+
const pr = H.clamp((t - 11.2) / 1.6);
|
|
223
|
+
for (let i = 0; i < N; i++) {
|
|
224
|
+
const acc = 61 + 36 * (1 - Math.exp(-i / 2.6)) / (1 - Math.exp(-(N - 1) / 2.6)), h = (acc - 45) * 6;
|
|
225
|
+
const a = 11.2 + i * 0.15;
|
|
226
|
+
paint(i === N - 1 ? orange : H.mix(teal, cream, 0.15 * (N - i) / N), [x0 + i * (bw + gap) - 6, base - h - 6, x0 + i * (bw + gap) + bw + 6, base + 6], (c) => c.fillRect(x0 + i * (bw + gap), base - h, bw, h), { dir: 'v', p: G(a, a + 0.3), key: 'bar' + i });
|
|
227
|
+
}
|
|
228
|
+
paint(navy, [100, 524, 1200, 572], (c) => txt(c, 'TRAINING ROUNDS →', 140, 553, 30, 'left', 'Oswald', 2), { edge: 0.7, rag: 0.3, p: G(11.2, 11.5), key: 'tr' });
|
|
229
|
+
const acc = Math.round(t < 11.2 ? 61 : t > 12.8 ? 97 : 61 + 36 * H.ease.out(pr));
|
|
230
|
+
if (t > 12.8) { const k = G(12.8, 12.95); paint(orange, [960, 40, 1260, 300], (c) => { c.beginPath(); for (let i = 0; i < 24; i++) { const a = i / 24 * Math.PI * 2, r = (i % 2 ? 80 : 118) * (0.5 + 0.5 * k); c.lineTo(1115 + Math.cos(a) * r, 170 + Math.sin(a) * r); } c.closePath(); c.fill(); }, { edge: 1.5, rag: 0.5, p: k, key: k >= 1 ? 'burst3' : null }); }
|
|
231
|
+
paint(navy, [900, 80, 1260, 260], (c) => txt(c, acc + '%', 1115, 212, 110, 'center'), { edge: 1, rag: 0.4, p: G(11.1, 11.3), key: acc === 97 ? 'acc97' : null });
|
|
232
|
+
paint(navy, [100, 300, 200, 420], (c) => txt(c, '61%', 142, 390, 34, 'center'), { edge: 0.7, rag: 0.3, p: G(11.3, 11.5), key: 'l61' });
|
|
233
|
+
if (t < 13.15) bandText('ACCURACY ' + acc + '%', 11.0);
|
|
234
|
+
else bandText('MORE EXAMPLES → BETTER GUESSES', 13.15, 84, 'bt3');
|
|
235
|
+
}
|
|
236
|
+
// ======== the medium's scene change: a fresh coat of cream swept across the board ========
|
|
237
|
+
for (const [a, b] of [[5.9, 6.35], [10.4, 10.85]]) {
|
|
238
|
+
if (t > a && t < b + 0.01) paint(cream, [0, 0, W, 572], (c) => c.fillRect(0, 0, W, 566), { edge: 6, rag: 1.4, p: G(a, b) });
|
|
239
|
+
}
|
|
240
|
+
// ======== hook -> header tab ========
|
|
241
|
+
{
|
|
242
|
+
const mc = H.layer(4, 4).getContext('2d'); mc.font = '400 140px "Bebas Neue"'; if (mc.letterSpacing !== undefined) mc.letterSpacing = '6px';
|
|
243
|
+
const tw = mc.measureText('HOW DOES AI LEARN?').width;
|
|
244
|
+
const k = G(1.5, 2.2), s = 1 - 0.68 * k, x = (640 - tw / 2) + (40 - (640 - tw / 2)) * k, y = 330 + (64 - 330) * k;
|
|
245
|
+
if (k > 0.3) paint(navy, [20, 20, 520, 110], (c) => c.fillRect(22, 22, 470, 62), { p: G(1.9, 2.3), key: 'tab' });
|
|
246
|
+
paint(k > 0.6 ? cream : navy, [0, 0, W, 470], (c) => { c.translate(x, y); c.scale(s, s); txt(c, 'HOW DOES AI LEARN?', 0, 0, 140, 'left', 'Bebas Neue', 6); }, { edge: 1.2, rag: 0.6, p: G(0.3, 0.9), key: k >= 1 ? 'title' : null });
|
|
247
|
+
}
|
|
248
|
+
// poster border + grain
|
|
249
|
+
paint(cream, [0, 0, W, H_], (c) => { c.beginPath(); c.rect(0, 0, W, H_); c.moveTo(20, 20); c.lineTo(20, H_ - 20); c.lineTo(W - 20, H_ - 20); c.lineTo(W - 20, 20); c.closePath(); c.fill('evenodd'); c.fillRect(20, 560, W - 40, 9); }, { edge: 2, rag: 0.9, key: 'border' });
|
|
250
|
+
for (let i = 0, p = 0; i < W * H_; i++, p += 4) { const g = grain[i]; buf[p] += g; buf[p + 1] += g; buf[p + 2] += g * 0.92; }
|
|
251
|
+
ctx.putImageData(new ImageData(buf, W, H_), 0, 0);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
registerStyle({
|
|
255
|
+
id: 'gouache-poster',
|
|
256
|
+
name: 'Gouache Travel Poster',
|
|
257
|
+
category: 'Painterly',
|
|
258
|
+
description: 'Mid-century travel poster hand-painted in gouache: six flat, opaque, matte colours stacked in receding layers, dry-brush streaks where the paint thins at each edge, and a bold hand-lettered block.',
|
|
259
|
+
motion: 'Layers slide in parallax like stacked paper cut-outs, the sun rises behind the ridges, steam puffs bob from the cooling towers and server windows blink on in rows.',
|
|
260
|
+
fonts: ['400 132px "Bebas Neue"', '700 24px "Oswald"', '400 18px "Oswald"'],
|
|
261
|
+
duration: 6,
|
|
262
|
+
frameT: 5.9,
|
|
263
|
+
readsPixels: true,
|
|
264
|
+
explainerDuration: 15,
|
|
265
|
+
explainerNote: 'A run of hand-painted posters: each scene change is a fresh coat of cream swept across the board before the next poster is painted flat colour by flat colour; arrows are flat poster arrow shapes, numbers are repainted in the lettering band, and an orange starburst stamps the key moments.',
|
|
266
|
+
explainer(ctx, W, H_, t) { explainGouache(ctx, W, H_, t); },
|
|
267
|
+
explainerSound(k) {
|
|
268
|
+
// all times copied from explainGouache() above (its G(a, b) paint windows)
|
|
269
|
+
const D = 15, px = (x) => H.clamp(x / 1280 * 2 - 1, -1, 1) * 0.7;
|
|
270
|
+
const when = (f, v, a, b) => { for (let t = a; t <= b; t += 0.001) if (f(t) >= v) return t; return b; };
|
|
271
|
+
// a flat brush loaded with opaque gouache pulled across hardboard: a creamy drag with a bristle grain,
|
|
272
|
+
// panned along the stroke (p0 -> p1), with a soft knock as the ferrule touches the board
|
|
273
|
+
const flat = (a, b, g = 1, p0 = 0, p1 = p0, minor = true) => {
|
|
274
|
+
const d = b - a, r = H.rng(Math.round(a * 997) + 3);
|
|
275
|
+
k.noise(a, { dur: 0.04, color: 'brown', filter: 'lowpass', f: 450, gain: 0.2 * g, a: 0.004, pan: p0, minor });
|
|
276
|
+
for (let x = 0; x < d; x += 0.035 + r() * 0.02) {
|
|
277
|
+
const u = x / d, gd = 0.06 + r() * 0.03;
|
|
278
|
+
k.noise(a + x, { dur: gd, color: 'pink', filter: 'bandpass', f: 650 + r() * 350, q: 0.7, gain: 0.13 * g * (0.65 + r() * 0.5) * (1 - 0.35 * u), a: gd * 0.35, pan: p0 + (p1 - p0) * u, minor });
|
|
279
|
+
}
|
|
280
|
+
k.play('stroke', a, { material: 'brush', dur: d, gain: 0.9 * g, pan: (p0 + p1) / 2, minor });
|
|
281
|
+
};
|
|
282
|
+
// the scene change: a wide house-painter's brush sweeps a fresh coat of cream across the whole board, left to right
|
|
283
|
+
const coat = (a, b) => { flat(a, b, 2.0, -0.7, 0.7, false); k.noise(a, { dur: b - a + 0.1, color: 'brown', filter: 'lowpass', f: 300, gain: 0.14, a: 0.05, hold: (b - a) * 0.5 }); };
|
|
284
|
+
// repainted number in the band: a quick dab of the lettering brush
|
|
285
|
+
const dab = (t, g = 1, pan = 0) => { k.withMinor(() => { k.noise(t, { dur: 0.04, color: 'pink', filter: 'bandpass', f: 950, q: 1.0, gain: 0.3 * g, a: 0.004, pan }); k.tone(t, { f: 170, dur: 0.05, gain: 0.08 * g, pan }); }); };
|
|
286
|
+
const counter = (val, a, b, gap, pan, avoid = []) => { let last = val(a), lastT = -1;
|
|
287
|
+
for (let t = a; t <= b; t += 1 / 240) { const v = val(t); if (v !== last && t - lastT >= gap && !avoid.some((x) => t > x - 0.03 && t < x + 0.07)) { dab(t, 1, pan); lastT = t; } last = v; } };
|
|
288
|
+
|
|
289
|
+
k.bed('room', 0, D, { gain: 0.45 });
|
|
290
|
+
// a light mid-century pad (C6, Am7, Fmaj7, G6; 3.75 s bars) under the paint
|
|
291
|
+
k.music('pad', 0, D, { bpm: 64, gain: 0.3, chords: [['C3', 'E3', 'G3', 'A3'], ['A2', 'E3', 'G3', 'C4'], ['F2', 'C3', 'E3', 'A3'], ['G2', 'D3', 'E3', 'B3']] });
|
|
292
|
+
|
|
293
|
+
// ---- 1 HOOK: sky, ground and band painted 0-0.7, title lettered 0.3-0.9, then it shrinks to the tab 1.5-2.2
|
|
294
|
+
flat(0.0, 0.3, 0.7, -0.6, 0.2); // sky (0-0.45), quieter, the title takes over at 0.3
|
|
295
|
+
flat(0.3, 0.9, 1.2, -0.5, 0.5, false);
|
|
296
|
+
k.noise(1.5, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 320], [0.7, 650]], q: 0.7, gain: 0.07, a: 0.25, minor: true }); // soft slide
|
|
297
|
+
flat(1.9, 2.3, 0.7, -0.9, -0.3); // navy header tab
|
|
298
|
+
|
|
299
|
+
// ---- 2 DATA: the machine painted colour by colour from 0.9, conveyor 1.6-2.3, hopper 2.0-2.3
|
|
300
|
+
flat(0.9, 1.25, 0.9, px(900), px(1200), false); // teal body
|
|
301
|
+
flat(1.2, 1.55, 0.7, px(950), px(1100)); // gauges
|
|
302
|
+
flat(1.6, 2.1, 0.8, px(0), px(900)); // mustard conveyor
|
|
303
|
+
// the belt runs under the cards (2.4 -> ~5.9): a soft low rumble
|
|
304
|
+
k.noise(2.3, { dur: 3.6, color: 'brown', filter: 'bandpass', f: 220, q: 1.2, gain: 0.12, a: 0.4, hold: 2.9, pan: -0.2, minor: true });
|
|
305
|
+
flat(2.6, 3.0, 0.9, px(420), px(810), false); // cream poster arrow
|
|
306
|
+
flat(2.6, 3.05, 0.6, -0.8, 0.2); // counter lettered in the band
|
|
307
|
+
// counter: n = 1e6 * ease.in((t - 2.7) / 2.9), rounded to thousands
|
|
308
|
+
counter((t) => Math.round(1e6 * H.ease.in(H.clamp((t - 2.7) / 2.9)) / 1000), 3.05, 5.58, 1 / 12, -0.5);
|
|
309
|
+
k.play('tock', 5.6, { f: 300, gain: 0.7, pan: -0.5 }); // 1,000,000 lands
|
|
310
|
+
flat(5.6, 5.9, 1.0, -0.8, 0.3, false); // orange underline
|
|
311
|
+
|
|
312
|
+
// ---- fresh coat 5.9-6.35
|
|
313
|
+
coat(5.9, 6.35);
|
|
314
|
+
|
|
315
|
+
// ---- 3 GUESS -> CHECK -> ADJUST
|
|
316
|
+
flat(6.6, 6.95, 0.8, px(120), px(420)); // machine repainted at the left
|
|
317
|
+
flat(6.9, 7.25, 0.6, px(150), px(300));
|
|
318
|
+
k.play('slap', 7.2, { gain: 0.3, pan: px(270), minor: true }); // new picture drops in
|
|
319
|
+
flat(7.1, 7.4, 1.0, px(480), px(790), false); // GUESS ticket arrow
|
|
320
|
+
flat(7.4, 7.7, 0.9, px(860), px(1100)); // DOG?
|
|
321
|
+
// the wrong guess: an orange starburst splats on, the navy cross is brushed across it
|
|
322
|
+
k.play('slap', 7.9, { gain: 0.55, pan: px(980) });
|
|
323
|
+
k.play('thud', 7.9, { f: 120, gain: 0.4, pan: px(980) });
|
|
324
|
+
k.tone(7.92, { f: 'A3', type: 'triangle', dur: 0.2, gain: 0.18, a: 0.01, hold: 0.07, pan: px(980) });
|
|
325
|
+
k.tone(8.1, { f: 'F3', type: 'triangle', dur: 0.36, gain: 0.18, a: 0.01, hold: 0.12, pan: px(980) });
|
|
326
|
+
flat(8.0, 8.1, 1.4, px(860), px(1100), false); flat(8.1, 8.2, 1.4, px(1100), px(860), false);
|
|
327
|
+
flat(8.3, 8.7, 1.1, px(1000), px(480), false); // rose ADJUST arrow loops back
|
|
328
|
+
// gauges turn G(8.6, 9.2): a few mechanical detents
|
|
329
|
+
const G2 = (t) => H.ease.inOut(H.clamp((t - 8.6) / 0.6));
|
|
330
|
+
for (let s = 1; s <= 3; s++) k.play('tock', when((t) => G2(t) * 4, s, 8.6, 9.2), { f: 520 + s * 30, gain: 0.45, pan: px(250), minor: true });
|
|
331
|
+
flat(9.4, 9.7, 1.0, px(860), px(1100)); // CAT
|
|
332
|
+
flat(9.7, 9.95, 1.1, px(1150), px(1240), false); // teal tick
|
|
333
|
+
k.play('success', 9.95, { f: 'G5', gain: 0.5, pan: px(1180) });
|
|
334
|
+
|
|
335
|
+
// ---- fresh coat 10.4-10.85
|
|
336
|
+
coat(10.4, 10.85);
|
|
337
|
+
|
|
338
|
+
// ---- 4 PROGRESS: mustard sky 10.85, axis 11.0-11.3, ten bars painted upward from 11.2 + i*0.15
|
|
339
|
+
flat(10.85, 11.3, 0.7, -0.6, 0.6);
|
|
340
|
+
for (let i = 0; i < 10; i++) {
|
|
341
|
+
const a = 11.2 + i * 0.15, p = px(110 + i * 86 + 32);
|
|
342
|
+
k.withMinor(() => k.noise(a, { dur: 0.25, color: 'pink', filter: 'bandpass', f: [[0, 420 + i * 25], [0.25, 800 + i * 40]], q: 0.8, gain: 0.16, a: 0.03, pan: p }));
|
|
343
|
+
k.play('tock', a, { f: 260 + i * 12, gain: 0.36, pan: p, minor: true });
|
|
344
|
+
}
|
|
345
|
+
// % counter: 61 + 36 * ease.out((t - 11.2) / 1.6); soft dabs, sparser than the bars
|
|
346
|
+
const acc = (t) => Math.round(61 + 36 * H.ease.out(H.clamp((t - 11.2) / 1.6)));
|
|
347
|
+
const t97 = when(acc, 97, 11.2, 12.8);
|
|
348
|
+
counter(acc, 11.25, t97 - 0.05, 1 / 7, px(1115), Array.from({ length: 10 }, (_, i) => 11.2 + i * 0.15));
|
|
349
|
+
k.play('tock', t97, { f: 380, gain: 0.55, pan: px(1115) }); // 97% painted
|
|
350
|
+
// the orange starburst splats behind 97% (12.8-12.95): the biggest hit
|
|
351
|
+
k.play('impact', 12.8, { gain: 0.9, pan: px(1115) });
|
|
352
|
+
k.play('slap', 12.8, { gain: 0.9, pan: px(1115) });
|
|
353
|
+
k.play('ding', 12.83, { f: 'E5', gain: 0.5, pan: 0.3 });
|
|
354
|
+
['C5', 'G5'].forEach((f, i) => k.tone(12.83 + 0.03 * (i + 1), { f, dur: 1.2, gain: 0.07, verb: 0.3, pan: 0.15 + i * 0.3 }));
|
|
355
|
+
|
|
356
|
+
// ---- 5 TAKEAWAY: band repainted 13.15-13.6, then a resolving chord
|
|
357
|
+
flat(13.15, 13.6, 1.2, -0.7, 0.7, false);
|
|
358
|
+
k.play('ding', 13.65, { f: 'C5', gain: 0.45 });
|
|
359
|
+
['E5', 'G5', 'C6'].forEach((f, i) => k.tone(13.7 + i * 0.05, { f, dur: 1.2, gain: 0.06, verb: 0.3, pan: (i - 1) * 0.3 }));
|
|
360
|
+
},
|
|
361
|
+
draw(ctx, W, H_, t) {
|
|
362
|
+
const { navy, teal, orange, mustard, cream, rose } = PAL;
|
|
363
|
+
const Cx = gpCache(W, H_); // brushing fields + board grain, built once (same seeds as before)
|
|
364
|
+
// the board starts with the artist's pencil layout, which the opaque paint then buries
|
|
365
|
+
const PB = H.layer(W, H_), pb = PB.getContext('2d');
|
|
366
|
+
pb.fillStyle = `rgb(${cream})`; pb.fillRect(0, 0, W, H_);
|
|
367
|
+
pb.strokeStyle = 'rgba(90,80,70,0.38)'; pb.lineWidth = 1; pb.lineCap = 'round';
|
|
368
|
+
const pl = (pts) => { pb.beginPath(); pts.forEach(([x, y], i) => (i ? pb.lineTo(x, y) : pb.moveTo(x, y))); pb.stroke(); };
|
|
369
|
+
pl([[0, 300], [90, 262], [170, 296], [300, 220], [380, 270], [470, 236], [560, 288], [660, 250], [720, 276], [980, 214], [1080, 268], [1160, 236], [1280, 280]]);
|
|
370
|
+
pl([[0, 352], [120, 318], [230, 350], [340, 300], [450, 344], [560, 330], [700, 352], [780, 318], [900, 346], [1040, 306], [1150, 340], [1280, 322]]);
|
|
371
|
+
pl([[0, 392], [1280, 392]]); pl([[440, 452], [1280, 452]]); pl([[0, 160], [1280, 160]]); pl([[0, 250], [1280, 250]]);
|
|
372
|
+
pb.beginPath(); pb.arc(860, 234, 112, 0, 7); pb.stroke();
|
|
373
|
+
pb.strokeRect(470, 360, 760, 92); pb.strokeRect(36, 586, 840, 104); pb.strokeRect(930, 600, 300, 90);
|
|
374
|
+
pl([[160, 560], [150, 420], [176, 250]]); pb.beginPath(); pb.ellipse(200, 160, 180, 50, 0, Math.PI, 0); pb.stroke();
|
|
375
|
+
pl([[0, 470], [300, 470], [600, 540], [900, 520], [1280, 504]]);
|
|
376
|
+
const buf = pb.getImageData(0, 0, W, H_).data;
|
|
377
|
+
const paint = makePainter(buf, W, H_, Cx, 3, 0.9);
|
|
378
|
+
const polyPath = (c, pts) => { c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); c.fill(); };
|
|
379
|
+
const rise = H.ease.inOut(H.clamp((t - 0.8) / 4.2));
|
|
380
|
+
const G = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
381
|
+
|
|
382
|
+
// ---------- sky: three flat bands ----------
|
|
383
|
+
paint(rose, [0, 0, W, 170], (c) => c.fillRect(0, 0, W, 160), { edge: 4, rag: 0.8, p: G(0, 0.55) });
|
|
384
|
+
paint(tint(rose, 0.45), [0, 140, W, 260], (c) => c.fillRect(0, 158, W, 92), { edge: 4, rag: 0.8, p: G(0.25, 0.75) });
|
|
385
|
+
paint(tint(mustard, 0.5), [0, 230, W, 420], (c) => c.fillRect(0, 248, W, 170), { edge: 4, rag: 0.8, p: G(0.45, 0.95) });
|
|
386
|
+
|
|
387
|
+
// ---------- sun ----------
|
|
388
|
+
const sunY = 300 - rise * 66, sunX = 860;
|
|
389
|
+
paint(orange, [sunX - 130, sunY - 130, sunX + 130, sunY + 130], (c) => { c.beginPath(); c.arc(sunX, sunY, 112, 0, 7); c.fill(); }, { edge: 2.5, rag: 0.8, p: G(0.9, 1.2) });
|
|
390
|
+
// retro sunset bars cut back in with the sky colour
|
|
391
|
+
paint(tint(mustard, 0.5), [sunX - 130, sunY - 10, sunX + 130, sunY + 130], (c) => {
|
|
392
|
+
for (let k = 0; k < 4; k++) c.fillRect(sunX - 120, sunY + 4 + k * 17, 240, 3 + k * 2.2);
|
|
393
|
+
}, { edge: 1.2, rag: 0.5, p: G(1.15, 1.3) });
|
|
394
|
+
|
|
395
|
+
// ---------- birds ----------
|
|
396
|
+
paint(navy, [480, 120, 640, 200], (c) => {
|
|
397
|
+
c.lineWidth = 4; c.lineCap = 'round';
|
|
398
|
+
[[520, 160, 1], [570, 140, 0.8], [600, 172, 0.65]].forEach(([x, y, s]) => {
|
|
399
|
+
const yy = y + Math.sin(t * 2 + x) * 3; x += (t - 5.9) * 26;
|
|
400
|
+
c.beginPath(); c.moveTo(x - 16 * s, yy - 4 * s); c.quadraticCurveTo(x - 6 * s, yy - 10 * s, x, yy); c.quadraticCurveTo(x + 6 * s, yy - 10 * s, x + 16 * s, yy - 4 * s); c.stroke();
|
|
401
|
+
});
|
|
402
|
+
}, { edge: 1, rag: 0.5, p: G(1.0, 1.1) });
|
|
403
|
+
|
|
404
|
+
// ---------- far range + snow caps ----------
|
|
405
|
+
const farPts = [[0, 300]];
|
|
406
|
+
const farPeaks = [[0, 300], [90, 262], [170, 296], [300, 220], [380, 270], [470, 236], [560, 288], [660, 250], [720, 276], [980, 214], [1080, 268], [1160, 236], [1280, 280]];
|
|
407
|
+
farPeaks.forEach((p) => farPts.push(p)); farPts.push([1280, 420], [0, 420]);
|
|
408
|
+
paint(tint(teal, 0.5), [0, 200, W, 430], (c) => polyPath(c, farPts), { edge: 3, rag: 1, dir: 'd', p: G(1.0, 1.5) });
|
|
409
|
+
paint(cream, [0, 200, W, 300], (c) => {
|
|
410
|
+
[[300, 220], [470, 236], [980, 214], [1160, 236]].forEach(([x, y]) => {
|
|
411
|
+
polyPath(c, [[x, y], [x + 34, y + 20], [x + 20, y + 16], [x + 8, y + 26], [x - 6, y + 17], [x - 20, y + 25], [x - 32, y + 19]]);
|
|
412
|
+
});
|
|
413
|
+
}, { edge: 1.5, rag: 0.6, p: G(1.45, 1.6) });
|
|
414
|
+
// shadow planes on the far range
|
|
415
|
+
paint(tint(teal, 0.3), [0, 200, W, 430], (c) => {
|
|
416
|
+
for (let k = 1; k < farPeaks.length - 1; k++) {
|
|
417
|
+
const [x, y] = farPeaks[k], [nx, ny] = farPeaks[k + 1];
|
|
418
|
+
if (y < farPeaks[k - 1][1] && y < ny) polyPath(c, [[x, y + 6], [nx, ny], [nx - 10, 420], [x + 14, 420]]);
|
|
419
|
+
}
|
|
420
|
+
}, { edge: 2, rag: 1, p: G(1.55, 1.75) });
|
|
421
|
+
|
|
422
|
+
// ---------- mid range ----------
|
|
423
|
+
const mid = [[0, 352], [120, 318], [230, 350], [340, 300], [450, 344], [560, 330], [700, 352], [780, 318], [900, 346], [1040, 306], [1150, 340], [1280, 322], [1280, 440], [0, 440]];
|
|
424
|
+
paint(teal, [0, 290, W, 450], (c) => polyPath(c, mid), { edge: 3, rag: 1, dir: 'd', p: G(1.6, 2.0) });
|
|
425
|
+
paint(H.mix(teal, navy, 0.35), [0, 290, W, 450], (c) => {
|
|
426
|
+
for (let k = 1; k < mid.length - 3; k++) {
|
|
427
|
+
const [x, y] = mid[k], [nx, ny] = mid[k + 1];
|
|
428
|
+
if (y < mid[k - 1][1] && y < ny) polyPath(c, [[x, y], [nx, ny], [nx + 10, 440], [x + 40, 440]]);
|
|
429
|
+
}
|
|
430
|
+
}, { edge: 2, rag: 1, p: G(1.95, 2.15) });
|
|
431
|
+
|
|
432
|
+
// ---------- lake with broken sun reflection ----------
|
|
433
|
+
paint(tint(teal, 0.25), [0, 380, W, 480], (c) => c.fillRect(0, 392, W, 80), { edge: 3, rag: 1.1, p: G(2.0, 2.35) });
|
|
434
|
+
paint(mustard, [600, 380, 1120, 480], (c) => {
|
|
435
|
+
const rr = H.rng(77);
|
|
436
|
+
for (let k = 0; k < 9; k++) {
|
|
437
|
+
const y = 398 + k * 8, half = (130 - k * 11) * (0.7 + rr() * 0.4), cx = sunX + rr.gauss() * 10 + Math.sin(t * 1.5 + k) * 6;
|
|
438
|
+
c.fillRect(cx - half, y, half * 2 * (0.3 + rr() * 0.3), 3.5); c.fillRect(cx + half * 0.2, y, half * 0.8, 3.5);
|
|
439
|
+
}
|
|
440
|
+
}, { edge: 1, rag: 0.6, p: G(2.3, 2.5) });
|
|
441
|
+
|
|
442
|
+
// ---------- data-centre city on the far shore ----------
|
|
443
|
+
const city = [];
|
|
444
|
+
const cr = H.rng(901);
|
|
445
|
+
let bx0 = 470;
|
|
446
|
+
while (bx0 < 1230) {
|
|
447
|
+
const w = cr.range(46, 96), h = cr.range(40, 120) * (1 - Math.abs(bx0 - 860) / 900), d = cr.range(14, 24);
|
|
448
|
+
city.push({ x: bx0, w, h, d, base: 452 + cr.range(-4, 6) });
|
|
449
|
+
bx0 += w + cr.range(6, 30);
|
|
450
|
+
}
|
|
451
|
+
// shadowed sides (rose) first, lit fronts (cream), roofs (orange)
|
|
452
|
+
paint(H.mix(rose, orange, 0.25), [460, 280, 1260, 470], (c) => city.forEach((b) => polyPath(c, [[b.x + b.w, b.base - b.h], [b.x + b.w + b.d, b.base - b.h - 8], [b.x + b.w + b.d, b.base - 6], [b.x + b.w, b.base]])), { edge: 1.6, rag: 0.8, dir: 'v', p: G(2.4, 2.7) });
|
|
453
|
+
paint(cream, [460, 280, 1260, 470], (c) => city.forEach((b) => c.fillRect(b.x, b.base - b.h, b.w, b.h)), { edge: 1.6, rag: 0.8, dir: 'v', p: G(2.55, 2.85) });
|
|
454
|
+
paint(orange, [460, 280, 1260, 470], (c) => city.forEach((b) => polyPath(c, [[b.x, b.base - b.h], [b.x + b.w, b.base - b.h], [b.x + b.w + b.d, b.base - b.h - 8], [b.x + b.d, b.base - b.h - 8]])), { edge: 1.2, rag: 0.6, p: G(2.8, 2.95) });
|
|
455
|
+
// server windows: rows of slits; some glowing
|
|
456
|
+
paint(navy, [460, 280, 1260, 470], (c) => city.forEach((b) => {
|
|
457
|
+
for (let y = b.base - b.h + 12; y < b.base - 10; y += 12) for (let x = b.x + 8; x < b.x + b.w - 10; x += 10) c.fillRect(x, y, 6, 4);
|
|
458
|
+
}), { edge: 0.8, rag: 0.5, p: G(2.95, 3.1) });
|
|
459
|
+
paint(mustard, [460, 280, 1260, 470], (c) => {
|
|
460
|
+
const wr = H.rng(902);
|
|
461
|
+
city.forEach((b) => {
|
|
462
|
+
for (let y = b.base - b.h + 12; y < b.base - 10; y += 12) for (let x = b.x + 8; x < b.x + b.w - 10; x += 10) {
|
|
463
|
+
const on = wr() < (0.22 + 0.25 * (0.5 + 0.5 * Math.sin(Math.floor(t * 4) * 1.7 + x * 0.3 + y))) * H.clamp((t - 3.1 - (b.base - y) / 300) / 0.1);
|
|
464
|
+
if (on) c.fillRect(x, y, 6, 4);
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
}, { edge: 0.7, rag: 0.4, p: G(3.1, 3.2) });
|
|
468
|
+
// cooling towers + steam
|
|
469
|
+
const towers = [[648, 452, 36, 70], [724, 452, 30, 58]];
|
|
470
|
+
paint(cream, [580, 330, 780, 460], (c) => towers.forEach(([x, y, r, h]) => {
|
|
471
|
+
c.beginPath(); c.moveTo(x - r, y); c.quadraticCurveTo(x - r * 0.45, y - h * 0.55, x - r * 0.62, y - h); c.lineTo(x + r * 0.62, y - h); c.quadraticCurveTo(x + r * 0.45, y - h * 0.55, x + r, y); c.closePath(); c.fill();
|
|
472
|
+
}), { edge: 1.5, rag: 0.7, dir: 'v', p: G(2.6, 2.85) });
|
|
473
|
+
paint(H.mix(rose, orange, 0.25), [580, 330, 780, 460], (c) => towers.forEach(([x, y, r, h]) => {
|
|
474
|
+
c.beginPath(); c.moveTo(x + r * 0.15, y); c.quadraticCurveTo(x + r * 0.2, y - h * 0.55, x + r * 0.2, y - h); c.lineTo(x + r * 0.62, y - h); c.quadraticCurveTo(x + r * 0.45, y - h * 0.55, x + r, y); c.closePath(); c.fill();
|
|
475
|
+
}), { edge: 1.5, rag: 0.7, dir: 'v', p: G(2.8, 2.95) });
|
|
476
|
+
paint(tint(rose, 0.75), [540, 160, 860, 400], (c) => towers.forEach(([x, y, r, h], k) => {
|
|
477
|
+
for (let j = 0; j < 4; j++) {
|
|
478
|
+
const ph = (t * 0.5 + j / 4) % 1, yy = y - h - 10 - ph * 120, rr = r * (0.35 + ph * 0.6), xx = x - 6 + ph * 70;
|
|
479
|
+
c.beginPath(); c.arc(xx, yy, rr, 0, 7); c.arc(xx + rr * 0.8, yy + rr * 0.2, rr * 0.7, 0, 7); c.fill();
|
|
480
|
+
}
|
|
481
|
+
}), { edge: 2, rag: 0.6, p: G(3.0, 3.25) });
|
|
482
|
+
// radio mast with beacon
|
|
483
|
+
paint(navy, [1000, 230, 1060, 460], (c) => {
|
|
484
|
+
c.lineWidth = 3; c.beginPath(); c.moveTo(1018, 452); c.lineTo(1030, 260); c.lineTo(1042, 452); c.stroke();
|
|
485
|
+
for (let y = 280; y < 452; y += 22) { const u = (y - 260) / 192; c.beginPath(); c.moveTo(1030 - u * 12, y); c.lineTo(1030 + u * 12, y + 22); c.stroke(); }
|
|
486
|
+
c.fillRect(1029, 244, 2, 18);
|
|
487
|
+
}, { edge: 0.8, rag: 0.4, p: G(3.0, 3.2) });
|
|
488
|
+
paint(orange, [1020, 230, 1040, 260], (c) => { c.beginPath(); c.arc(1030, 244, 4 + Math.sin(t * 4) * 1.2, 0, 7); c.fill(); }, { edge: 0.6, rag: 0.3, p: G(3.2, 3.25) });
|
|
489
|
+
// shore line
|
|
490
|
+
paint(H.mix(teal, navy, 0.5), [440, 440, W, 470], (c) => c.fillRect(440, 451, W - 440, 9), { edge: 1.5, rag: 0.8, p: G(2.35, 2.5) });
|
|
491
|
+
|
|
492
|
+
// ---------- near hills (navy) with the road into the city ----------
|
|
493
|
+
paint(H.mix(navy, teal, 0.35), [0, 400, W, 560], (c) => { c.beginPath(); c.moveTo(0, 430); c.bezierCurveTo(260, 400, 520, 470, 760, 482); c.bezierCurveTo(940, 492, 1120, 466, 1280, 474); c.lineTo(1280, 560); c.lineTo(0, 560); c.closePath(); c.fill(); }, { edge: 3, rag: 1, p: G(2.1, 2.4) });
|
|
494
|
+
paint(navy, [0, 450, W, 560], (c) => { c.beginPath(); c.moveTo(0, 470); c.bezierCurveTo(300, 470, 600, 540, 900, 520); c.bezierCurveTo(1060, 510, 1180, 500, 1280, 504); c.lineTo(1280, 560); c.lineTo(0, 560); c.closePath(); c.fill(); }, { edge: 3, rag: 1, p: G(2.3, 2.6) });
|
|
495
|
+
// road narrowing into the city (painted as one tapered ribbon)
|
|
496
|
+
paint(tint(mustard, 0.2), [300, 440, 1000, 560], (c) => {
|
|
497
|
+
const P = (u) => { const a = 1 - u; return [a * a * a * 360 + 3 * a * a * u * 540 + 3 * a * u * u * 560 + u * u * u * 840, a * a * a * 562 + 3 * a * a * u * 516 + 3 * a * u * u * 490 + u * u * u * 470]; };
|
|
498
|
+
const L = [], R = [];
|
|
499
|
+
for (let i = 0; i <= 30; i++) { const u = i / 30, [x, y] = P(u), w = 26 * Math.pow(1 - u, 1.6) + 1.5; L.push([x - w * 0.2, y - w * 0.5]); R.push([x + w * 0.2, y + w * 0.5]); }
|
|
500
|
+
polyPath(c, L.concat(R.reverse()));
|
|
501
|
+
}, { edge: 1.5, rag: 0.6, p: G(2.6, 2.8) });
|
|
502
|
+
// two travellers taking in the view
|
|
503
|
+
const fig = (c, x, y, s) => { c.beginPath(); c.arc(x, y - 44 * s, 7 * s, 0, 7); c.fill(); polyPath(c, [[x - 9 * s, y - 34 * s], [x + 9 * s, y - 34 * s], [x + 13 * s, y - 4 * s], [x - 13 * s, y - 4 * s]]); c.fillRect(x - 7 * s, y - 6 * s, 5 * s, 8 * s); c.fillRect(x + 2 * s, y - 6 * s, 5 * s, 8 * s); };
|
|
504
|
+
paint(orange, [240, 420, 330, 520], (c) => fig(c, 262, 506, 1.1), { edge: 1, rag: 0.4, dir: 'v', p: G(3.2, 3.35) });
|
|
505
|
+
paint(cream, [270, 420, 340, 520], (c) => { fig(c, 296, 508, 1.0); }, { edge: 1, rag: 0.4, dir: 'v', p: G(3.25, 3.4) });
|
|
506
|
+
paint(mustard, [240, 420, 340, 520], (c) => { c.fillRect(272, 482, 14, 12); c.lineWidth = 3; c.beginPath(); c.moveTo(304, 474); c.lineTo(330, 452); c.stroke(); }, { edge: 0.8, rag: 0.4, p: G(3.35, 3.45) });
|
|
507
|
+
|
|
508
|
+
// ---------- foreground umbrella pine ----------
|
|
509
|
+
paint(navy, [0, 200, 420, 560], (c) => {
|
|
510
|
+
c.beginPath(); c.moveTo(150, 560); c.bezierCurveTo(160, 440, 120, 360, 170, 250); c.lineTo(186, 254); c.bezierCurveTo(146, 360, 190, 450, 184, 560); c.closePath(); c.fill();
|
|
511
|
+
c.beginPath(); c.moveTo(166, 300); c.quadraticCurveTo(220, 250, 280, 214); c.lineTo(284, 224); c.quadraticCurveTo(226, 262, 176, 312); c.closePath(); c.fill();
|
|
512
|
+
c.beginPath(); c.moveTo(168, 270); c.quadraticCurveTo(120, 220, 70, 196); c.lineTo(66, 206); c.quadraticCurveTo(116, 232, 162, 282); c.closePath(); c.fill();
|
|
513
|
+
}, { edge: 1.8, rag: 0.6, dir: 'v', p: G(2.5, 2.8) });
|
|
514
|
+
paint(navy, [0, 40, 420, 240], (c) => {
|
|
515
|
+
[[180, 170, 150, 46], [300, 196, 110, 34], [70, 190, 90, 30], [240, 120, 110, 36]].forEach(([x, y, a, b]) => {
|
|
516
|
+
c.beginPath(); c.ellipse(x, y, a, b, 0, Math.PI, 0); c.bezierCurveTo(x + a, y + b * 0.4, x - a, y + b * 0.4, x - a, y); c.fill();
|
|
517
|
+
});
|
|
518
|
+
}, { edge: 2.5, rag: 0.8, dir: 'h', p: G(2.75, 3.0) });
|
|
519
|
+
paint(teal, [0, 60, 420, 220], (c) => {
|
|
520
|
+
[[180, 148, 120, 22], [300, 180, 84, 16], [74, 176, 64, 14], [240, 100, 84, 18]].forEach(([x, y, a, b]) => { c.beginPath(); c.ellipse(x - 6, y, a, b, 0, Math.PI, 0); c.closePath(); c.fill(); });
|
|
521
|
+
}, { edge: 2, rag: 1, p: G(2.95, 3.1) });
|
|
522
|
+
|
|
523
|
+
// ---------- lettering band ----------
|
|
524
|
+
paint(navy, [0, 560, W, H_], (c) => c.fillRect(0, 572, W, H_ - 572), { edge: 2.5, rag: 0.9, p: G(3.3, 3.6) });
|
|
525
|
+
paint(cream, [40, 570, 900, 715], (c) => {
|
|
526
|
+
c.font = '400 132px "Bebas Neue"'; c.textBaseline = 'alphabetic';
|
|
527
|
+
if (c.letterSpacing !== undefined) c.letterSpacing = '4px';
|
|
528
|
+
c.fillText('VISIT THE CLOUD', 52, 692);
|
|
529
|
+
}, { edge: 1.4, rag: 0.7, p: G(3.6, 4.1) });
|
|
530
|
+
paint(mustard, [880, 590, W, 700], (c) => {
|
|
531
|
+
c.font = '700 24px "Oswald"'; c.textAlign = 'right';
|
|
532
|
+
if (c.letterSpacing !== undefined) c.letterSpacing = '5px';
|
|
533
|
+
c.fillText('DATA VALLEY LINE', 1230, 630);
|
|
534
|
+
c.fillRect(940, 646, 290, 4);
|
|
535
|
+
c.font = '400 18px "Oswald"'; if (c.letterSpacing !== undefined) c.letterSpacing = '4px';
|
|
536
|
+
c.fillText('EXPRESS SERVICE NIGHTLY', 1230, 680);
|
|
537
|
+
}, { edge: 1, rag: 0.5, p: G(4.0, 4.3) });
|
|
538
|
+
paint(orange, [880, 590, W, 700], (c) => { c.beginPath(); c.arc(926, 648, 9, 0, 7); c.fill(); }, { edge: 0.8, rag: 0.4, p: G(4.25, 4.35) });
|
|
539
|
+
|
|
540
|
+
// ---------- poster border (cream, hand-trimmed) ----------
|
|
541
|
+
paint(cream, [0, 0, W, H_], (c) => {
|
|
542
|
+
c.beginPath(); c.rect(0, 0, W, H_);
|
|
543
|
+
c.moveTo(20, 20); c.lineTo(20, H_ - 20); c.lineTo(W - 20, H_ - 20); c.lineTo(W - 20, 20); c.closePath();
|
|
544
|
+
c.fill('evenodd');
|
|
545
|
+
c.fillRect(20, 560, W - 40, 9);
|
|
546
|
+
}, { edge: 2, rag: 0.9, key: 'border' });
|
|
547
|
+
|
|
548
|
+
// ---------- board texture, matte grain ----------
|
|
549
|
+
const grain = Cx.grain;
|
|
550
|
+
for (let i = 0, p = 0; i < W * H_; i++, p += 4) { const g = grain[i]; buf[p] += g; buf[p + 1] += g; buf[p + 2] += g * 0.92; }
|
|
551
|
+
// slow push-in on the finished poster (the border stays put: it is the poster's edge)
|
|
552
|
+
const OUT = H.layer(W, H_); OUT.getContext('2d').putImageData(new ImageData(buf, W, H_), 0, 0);
|
|
553
|
+
const z = 1 + 0.035 * H.ease.inOut(H.clamp(t / 6));
|
|
554
|
+
ctx.drawImage(OUT, 0, 0);
|
|
555
|
+
ctx.save(); ctx.beginPath(); ctx.rect(22, 22, W - 44, 536); ctx.clip();
|
|
556
|
+
ctx.translate(640, 300); ctx.scale(z, z); ctx.translate(-640, -300); ctx.drawImage(OUT, 0, 0); ctx.restore();
|
|
557
|
+
}
|
|
558
|
+
});
|
|
559
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 49,
|
|
3
|
+
"name": "Gouache Travel Poster",
|
|
4
|
+
"nameZh": "水粉旅行海报",
|
|
5
|
+
"family": "Painterly",
|
|
6
|
+
"familyZh": "绘画质感",
|
|
7
|
+
"description": "Mid-century travel poster hand-painted in gouache: six flat, opaque, matte colours stacked in receding layers, dry-brush streaks where the paint thins at each edge, and a bold hand-lettered block.",
|
|
8
|
+
"motionNotes": "Motion: Layers slide in parallax like stacked paper cut-outs, the sun rises behind the ridges, steam puffs bob from the cooling towers and server windows blink on in rows.",
|
|
9
|
+
"explainerNotes": "Explainer: A run of hand-painted posters: each scene change is a fresh coat of cream swept across the board before the next poster is painted flat colour by flat colour; arrows are flat poster arrow shapes, numbers are repainted in the lettering band, and an orange starburst stamps the key moments.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "gouache-poster",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|