@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,1008 @@
|
|
|
1
|
+
{
|
|
2
|
+
const TAU = Math.PI * 2;
|
|
3
|
+
|
|
4
|
+
const poly = (c, pts) => {
|
|
5
|
+
c.moveTo(pts[0][0], pts[0][1]);
|
|
6
|
+
for (let i = 1; i < pts.length; i++) c.lineTo(pts[i][0], pts[i][1]);
|
|
7
|
+
c.closePath();
|
|
8
|
+
};
|
|
9
|
+
// scissor-cut circle: slightly faceted, slightly wobbly
|
|
10
|
+
const circPts = (r, cx, cy, R, seg = 15, jit = 0.6) => {
|
|
11
|
+
const n = Math.max(18, Math.round((TAU * R) / seg)), a0 = r() * TAU, out = [];
|
|
12
|
+
for (let i = 0; i < n; i++) {
|
|
13
|
+
const a = a0 + ((i + r.range(-0.2, 0.2)) / n) * TAU, rr = R + r.gauss() * jit;
|
|
14
|
+
out.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]);
|
|
15
|
+
}
|
|
16
|
+
return out;
|
|
17
|
+
};
|
|
18
|
+
// scissor-cut edge following y=f(x): short straight-ish snips
|
|
19
|
+
const ridge = (r, x0, x1, f, sMin = 12, sMax = 30, jit = 0.6) => {
|
|
20
|
+
const out = [];
|
|
21
|
+
let x = x0;
|
|
22
|
+
while (x < x1) { out.push([x, f(x) + r.gauss() * jit]); x += r.range(sMin, sMax); }
|
|
23
|
+
out.push([x1, f(x1)]);
|
|
24
|
+
return out;
|
|
25
|
+
};
|
|
26
|
+
// straight cut between points with tiny wobbles
|
|
27
|
+
const cutLines = (r, pts, seg = 22, jit = 0.7) => {
|
|
28
|
+
const out = [];
|
|
29
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
30
|
+
const [ax, ay] = pts[i], [bx, by] = pts[i + 1];
|
|
31
|
+
const n = Math.max(1, Math.round(Math.hypot(bx - ax, by - ay) / seg));
|
|
32
|
+
for (let k = 0; k < n; k++) {
|
|
33
|
+
const u = k / n;
|
|
34
|
+
out.push([ax + (bx - ax) * u + (k ? r.gauss() * jit : 0), ay + (by - ay) * u + (k ? r.gauss() * jit : 0)]);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
out.push(pts[pts.length - 1]);
|
|
38
|
+
return out;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
// tileable paper fibre texture (alpha-only light/dark flecks)
|
|
42
|
+
const makeTex = (seed) => {
|
|
43
|
+
const S = 512, cv = H.layer(S, S), x = cv.getContext('2d'), r = H.rng(seed), nz = H.makeNoise(seed + 1);
|
|
44
|
+
// low-res tileable mottle
|
|
45
|
+
const M = 64, mc = H.layer(M, M), mx = mc.getContext('2d'), mi = mx.createImageData(M, M), f = 5 / M;
|
|
46
|
+
for (let j = 0; j < M; j++) for (let i = 0; i < M; i++) {
|
|
47
|
+
const n = (a, b) => nz.fbm2(a * f, b * f, 3);
|
|
48
|
+
const v = (n(i, j) * (M - i) * (M - j) + n(i - M, j) * i * (M - j) + n(i, j - M) * (M - i) * j + n(i - M, j - M) * i * j) / (M * M);
|
|
49
|
+
const k = (j * M + i) * 4, a = Math.min(255, Math.abs(v) * 120);
|
|
50
|
+
mi.data[k] = mi.data[k + 1] = mi.data[k + 2] = v > 0 ? 255 : 0; mi.data[k + 3] = a;
|
|
51
|
+
}
|
|
52
|
+
mx.putImageData(mi, 0, 0);
|
|
53
|
+
x.imageSmoothingEnabled = true;
|
|
54
|
+
for (let oy = -1; oy <= 1; oy++) for (let ox = -1; ox <= 1; ox++) x.drawImage(mc, ox * S, oy * S, S, S);
|
|
55
|
+
// fine grain
|
|
56
|
+
const g = x.getImageData(0, 0, S, S), d = g.data;
|
|
57
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
58
|
+
const n = r() - 0.5;
|
|
59
|
+
if (r() < 0.5) continue;
|
|
60
|
+
const a = Math.abs(n) * 60;
|
|
61
|
+
// blend fleck over mottle
|
|
62
|
+
const c = n > 0 ? 255 : 0, aa = a / 255, oa = d[i + 3] / 255, na = aa + oa * (1 - aa);
|
|
63
|
+
if (na > 0) {
|
|
64
|
+
d[i] = (c * aa + d[i] * oa * (1 - aa)) / na; d[i + 1] = d[i]; d[i + 2] = d[i];
|
|
65
|
+
d[i + 3] = na * 255;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
x.putImageData(g, 0, 0);
|
|
69
|
+
// fibres
|
|
70
|
+
x.lineCap = 'round';
|
|
71
|
+
for (let n = 0; n < 1100; n++) {
|
|
72
|
+
const px = r() * S, py = r() * S, len = r.range(4, 22), a = r() * TAU, bend = r.range(-5, 5);
|
|
73
|
+
const light = r() < 0.55;
|
|
74
|
+
x.strokeStyle = light ? `rgba(255,255,255,${r.range(0.08, 0.22)})` : `rgba(0,0,0,${r.range(0.05, 0.14)})`;
|
|
75
|
+
x.lineWidth = r.range(0.4, 1.1);
|
|
76
|
+
for (let oy = -1; oy <= 1; oy++) for (let ox = -1; ox <= 1; ox++) {
|
|
77
|
+
const sx = px + ox * S, sy = py + oy * S;
|
|
78
|
+
if (sx < -30 || sx > S + 30 || sy < -30 || sy > S + 30) continue;
|
|
79
|
+
x.beginPath(); x.moveTo(sx, sy);
|
|
80
|
+
x.quadraticCurveTo(sx + Math.cos(a) * len * 0.5 - Math.sin(a) * bend, sy + Math.sin(a) * len * 0.5 + Math.cos(a) * bend, sx + Math.cos(a) * len, sy + Math.sin(a) * len);
|
|
81
|
+
x.stroke();
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return cv;
|
|
85
|
+
};
|
|
86
|
+
|
|
87
|
+
// =====================================================================
|
|
88
|
+
// EXPLAINER: "How does AI learn?" told as one long paper diorama that
|
|
89
|
+
// the camera pans along (parallax), with paper sprites cached once.
|
|
90
|
+
// =====================================================================
|
|
91
|
+
const PK = {};
|
|
92
|
+
const pkTex = () => PK.tex || (PK.tex = makeTex(77));
|
|
93
|
+
const paperize = (w, h, paint, o = {}) => {
|
|
94
|
+
const L = H.layer(Math.ceil(w), Math.ceil(h)), c = L.getContext('2d');
|
|
95
|
+
paint(c);
|
|
96
|
+
const r = H.rng(o.seed || 7);
|
|
97
|
+
const pat = c.createPattern(pkTex(), 'repeat');
|
|
98
|
+
pat.setTransform(new DOMMatrix().translateSelf(r() * 512, r() * 512).rotateSelf(r.range(-30, 30)));
|
|
99
|
+
c.save(); c.globalCompositeOperation = 'source-atop'; c.globalAlpha = o.tex ?? 1; c.fillStyle = pat; c.fillRect(0, 0, w, h); c.restore();
|
|
100
|
+
const edge = (col, dx, dy) => {
|
|
101
|
+
const T = H.layer(L.width, L.height), tc = T.getContext('2d');
|
|
102
|
+
tc.drawImage(L, 0, 0); tc.globalCompositeOperation = 'source-in'; tc.fillStyle = col; tc.fillRect(0, 0, w, h);
|
|
103
|
+
tc.globalCompositeOperation = 'destination-out'; tc.drawImage(L, dx, dy);
|
|
104
|
+
c.save(); c.globalCompositeOperation = 'source-atop'; c.drawImage(T, 0, 0); c.restore();
|
|
105
|
+
};
|
|
106
|
+
if (o.rim !== false) { edge(`rgba(255,247,232,${o.hi ?? 0.38})`, 1.3, 1.6); edge(`rgba(0,0,0,${o.lo ?? 0.28})`, -1.1, -1.3); }
|
|
107
|
+
return L;
|
|
108
|
+
};
|
|
109
|
+
const shadowDraw = (ctx, img, x, y, d = 1, sa = 0.5) => {
|
|
110
|
+
ctx.save();
|
|
111
|
+
if (d > 0) {
|
|
112
|
+
ctx.shadowColor = `rgba(14,10,30,${sa})`; ctx.shadowBlur = 6 + d * 7; ctx.shadowOffsetX = 1.5 + d * 2.4; ctx.shadowOffsetY = 2 + d * 3.6;
|
|
113
|
+
ctx.drawImage(img, x, y);
|
|
114
|
+
ctx.shadowColor = 'rgba(10,6,20,0.38)'; ctx.shadowBlur = 2.5; ctx.shadowOffsetX = 0.8; ctx.shadowOffsetY = 1.4;
|
|
115
|
+
}
|
|
116
|
+
ctx.drawImage(img, x, y); ctx.restore();
|
|
117
|
+
};
|
|
118
|
+
const put = (ctx, spr, cx, cy, o = {}) => {
|
|
119
|
+
ctx.save(); ctx.translate(cx, cy);
|
|
120
|
+
if (o.rot) ctx.rotate(o.rot);
|
|
121
|
+
if (o.sx != null || o.s != null) ctx.scale(o.sx ?? o.s ?? 1, o.sy ?? o.s ?? 1);
|
|
122
|
+
if (o.a != null) ctx.globalAlpha = o.a;
|
|
123
|
+
shadowDraw(ctx, spr, -spr.width / 2, -spr.height / 2, o.d ?? 1, o.sa ?? 0.5);
|
|
124
|
+
ctx.restore();
|
|
125
|
+
};
|
|
126
|
+
const compose = (w, h, layers) => {
|
|
127
|
+
const C = H.layer(w, h), c = C.getContext('2d');
|
|
128
|
+
layers.forEach(([paint, d, o], i) => shadowDraw(c, paperize(w, h, paint, { seed: 30 + i * 7, ...(o || {}) }), 0, 0, d, 0.4));
|
|
129
|
+
return C;
|
|
130
|
+
};
|
|
131
|
+
const cache = (k, f) => PK[k] || (PK[k] = f());
|
|
132
|
+
const AF = (px) => `400 ${px}px "Archivo Black"`;
|
|
133
|
+
const measure = (str, font) => { const c = H.layer(4, 4).getContext('2d'); c.font = font; return c.measureText(str).width; };
|
|
134
|
+
// cut letters (or a word) on a cut-around backing
|
|
135
|
+
const wordSpr = (str, px, col, back = '#f3ecdc', bw = 18) => cache('w' + str + px + col + back, () => {
|
|
136
|
+
const font = AF(px), tw = measure(str, font), w = tw + bw * 2 + 16, h = px * 1.35 + bw * 2;
|
|
137
|
+
const by = h / 2 + px * 0.36;
|
|
138
|
+
const layers = [];
|
|
139
|
+
if (back) layers.push([(c) => { c.font = font; c.lineJoin = 'round'; c.lineWidth = bw * 1.6; c.strokeStyle = back; c.fillStyle = back; c.strokeText(str, bw + 8, by); c.fillText(str, bw + 8, by); }, 0]);
|
|
140
|
+
layers.push([(c) => { c.font = font; c.fillStyle = col; c.fillText(str, bw + 8, by); }, back ? 0.45 : 0]);
|
|
141
|
+
return compose(w, h, layers);
|
|
142
|
+
});
|
|
143
|
+
const blobPath = (c, parts) => { c.beginPath(); parts.forEach((p) => poly(c, p)); };
|
|
144
|
+
// little cut-paper picture cards with a tag that names them
|
|
145
|
+
const cardSpr = (kind) => cache('card' + kind, () => {
|
|
146
|
+
const r = H.rng(kind === 'cat' ? 11 : 12);
|
|
147
|
+
const cat = kind === 'cat';
|
|
148
|
+
return compose(140, 176, [
|
|
149
|
+
[(c) => { c.fillStyle = '#f3ecdc'; c.beginPath(); poly(c, cutLines(r, [[8, 8], [132, 8], [132, 168], [8, 168], [8, 8]], 20, 0.5)); c.fill(); }, 0],
|
|
150
|
+
[(c) => { c.fillStyle = cat ? '#86bccd' : '#e8c56b'; c.fillRect(20, 20, 100, 94); }, 0.3],
|
|
151
|
+
[(c) => {
|
|
152
|
+
c.fillStyle = cat ? '#e8874c' : '#6e4428';
|
|
153
|
+
c.beginPath();
|
|
154
|
+
if (cat) { poly(c, [[44, 58], [50, 26], [66, 48]]); poly(c, [[96, 58], [90, 26], [74, 48]]); }
|
|
155
|
+
else { c.ellipse(42, 66, 11, 24, 0.3, 0, TAU); c.ellipse(98, 66, 11, 24, -0.3, 0, TAU); }
|
|
156
|
+
c.fill();
|
|
157
|
+
}, 0.3],
|
|
158
|
+
[(c) => { c.fillStyle = cat ? '#f09a5c' : '#b07a4c'; c.beginPath(); poly(c, circPts(r, 70, 70, 29, 10, 0.5)); c.fill(); }, 0.45],
|
|
159
|
+
[(c) => {
|
|
160
|
+
c.fillStyle = cat ? '#fbe3c8' : '#f3dfc2'; c.beginPath(); c.ellipse(70, 84, cat ? 13 : 16, cat ? 9 : 12, 0, 0, TAU); c.fill();
|
|
161
|
+
c.fillStyle = '#2b2033'; c.beginPath(); c.arc(59, 64, 4, 0, TAU); c.arc(81, 64, 4, 0, TAU); c.fill();
|
|
162
|
+
c.fillStyle = cat ? '#e0607a' : '#2b2033'; c.beginPath(); c.ellipse(70, 79, cat ? 4 : 6, cat ? 3 : 4.5, 0, 0, TAU); c.fill();
|
|
163
|
+
}, 0.25],
|
|
164
|
+
[(c) => { c.fillStyle = '#fbf6ea'; c.beginPath(); poly(c, [[30, 128], [100, 128], [114, 145], [100, 162], [30, 162]]); c.fill(); c.globalCompositeOperation = 'destination-out'; c.beginPath(); c.arc(103, 145, 4, 0, TAU); c.fill(); }, 0.55],
|
|
165
|
+
[(c) => { c.font = AF(24); c.fillStyle = cat ? '#d9473b' : '#2f7f86'; c.fillText(kind, 38, 154); }, 0.12],
|
|
166
|
+
]);
|
|
167
|
+
});
|
|
168
|
+
const brainSpr = () => cache('brain', () => {
|
|
169
|
+
const r = H.rng(21), parts = [], inner = [];
|
|
170
|
+
[[-90, 10, 58], [-50, -50, 62], [15, -68, 64], [80, -40, 58], [104, 22, 50], [50, 50, 60], [-30, 52, 62], [-10, 0, 70]].forEach(([x, y, rr]) => parts.push(circPts(r, 160 + x, 128 + y, rr, 12, 0.6)));
|
|
171
|
+
[[-60, -10, 26], [-20, -40, 24], [30, -30, 26], [70, 0, 22], [20, 25, 24], [-30, 30, 22]].forEach(([x, y, rr]) => inner.push(circPts(r, 160 + x, 128 + y, rr, 10, 0.5)));
|
|
172
|
+
return compose(330, 260, [
|
|
173
|
+
[(c) => { c.fillStyle = '#e8839a'; blobPath(c, parts); c.fill(); }, 0],
|
|
174
|
+
[(c) => { c.strokeStyle = '#f3a7b6'; c.lineWidth = 13; c.lineCap = 'round'; c.lineJoin = 'round';
|
|
175
|
+
[[[70, 120], [95, 80], [130, 95], [120, 60]], [[70, 170], [110, 150], [100, 120]], [[195, 60], [230, 80], [210, 110], [245, 130]], [[190, 180], [225, 150], [255, 175]], [[115, 205], [140, 180]], [[205, 210], [240, 200]]].forEach((ln) => { c.beginPath(); ln.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.stroke(); });
|
|
176
|
+
}, 0.35],
|
|
177
|
+
[(c) => { c.fillStyle = '#c95f7a'; c.fillRect(156, 52, 8, 150); }, 0.2],
|
|
178
|
+
]);
|
|
179
|
+
});
|
|
180
|
+
const panelSpr = () => cache('panel', () => compose(300, 96, [
|
|
181
|
+
[(c) => { c.fillStyle = '#2f7f86'; c.beginPath(); poly(c, cutLines(H.rng(4), [[6, 6], [294, 6], [294, 90], [6, 90], [6, 6]], 24, 0.5)); c.fill(); }, 0],
|
|
182
|
+
[(c) => { c.fillStyle = '#f3ecdc'; [70, 150, 230].forEach((x) => { c.beginPath(); poly(c, circPts(H.rng(x), x, 48, 28, 10, 0.4)); c.fill(); }); }, 0.45],
|
|
183
|
+
]));
|
|
184
|
+
const pointerSpr = () => cache('ptr', () => paperize(16, 56, (c) => { c.fillStyle = '#d9473b'; c.beginPath(); poly(c, [[8, 2], [14, 30], [10, 54], [6, 54], [2, 30]]); c.fill(); }, { seed: 5 }));
|
|
185
|
+
const bubbleSpr = (txt) => cache('bub' + txt, () => {
|
|
186
|
+
const r = H.rng(txt.length + 3);
|
|
187
|
+
return compose(230, 150, [
|
|
188
|
+
[(c) => { c.fillStyle = '#fbf6ea'; c.beginPath(); c.ellipse(118, 64, 100, 54, 0, 0, TAU); c.moveTo(40, 92); c.lineTo(18, 140); c.lineTo(78, 112); c.fill(); }, 0],
|
|
189
|
+
[(c) => { c.font = AF(46); c.fillStyle = txt === 'cat' ? '#2f7f86' : '#2b2d5a'; const w = measure(txt, AF(46)); c.fillText(txt, 118 - w / 2, 82); }, 0.35],
|
|
190
|
+
]);
|
|
191
|
+
});
|
|
192
|
+
const crossSpr = () => cache('x', () => paperize(110, 110, (c) => { c.fillStyle = '#d9473b'; c.save(); c.translate(55, 55); c.rotate(0.785); c.fillRect(-48, -11, 96, 22); c.fillRect(-11, -48, 22, 96); c.restore(); }, { seed: 9 }));
|
|
193
|
+
const checkSpr = () => cache('ok', () => paperize(120, 110, (c) => { c.fillStyle = '#5fae6a'; c.beginPath(); poly(c, [[8, 58], [26, 40], [46, 62], [96, 10], [114, 28], [46, 98]]); c.fill(); }, { seed: 10 }));
|
|
194
|
+
const ringSpr = () => cache('ring', () => paperize(300, 230, (c) => { c.fillStyle = '#f2b545'; c.beginPath(); c.ellipse(150, 115, 145, 110, 0, 0, TAU); c.ellipse(150, 115, 125, 92, 0, 0, TAU); c.fill('evenodd'); }, { seed: 12 }));
|
|
195
|
+
const burstSpr = () => cache('burst', () => paperize(340, 340, (c) => { c.fillStyle = '#f2b545'; c.beginPath(); for (let i = 0; i < 32; i++) { const a = (i / 32) * TAU, rr = i % 2 ? 100 : 168; const x = 170 + Math.cos(a) * rr, y = 170 + Math.sin(a) * rr; i ? c.lineTo(x, y) : c.moveTo(x, y); } c.closePath(); c.fill(); }, { seed: 13 }));
|
|
196
|
+
const digitSpr = (ch, px, col) => wordSpr(ch, px, col, '#f3ecdc', 10);
|
|
197
|
+
// cut-paper arrow (strip + head) revealed along a quadratic path
|
|
198
|
+
const arrow = (ctx, ax, ay, cx, cy, bx, by, p, col, seed) => {
|
|
199
|
+
if (p <= 0) return;
|
|
200
|
+
const pts = [];
|
|
201
|
+
for (let i = 0; i <= 40 * p; i++) { const u = i / 40; pts.push([(1 - u) * (1 - u) * ax + 2 * u * (1 - u) * cx + u * u * bx, (1 - u) * (1 - u) * ay + 2 * u * (1 - u) * cy + u * u * by]); }
|
|
202
|
+
if (pts.length < 2) return;
|
|
203
|
+
let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
|
|
204
|
+
pts.forEach(([x, y]) => { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); });
|
|
205
|
+
x0 -= 34; y0 -= 34; x1 += 34; y1 += 34;
|
|
206
|
+
const [ex, ey] = pts[pts.length - 1], [px, py] = pts[pts.length - 2], a = Math.atan2(ey - py, ex - px);
|
|
207
|
+
const L = paperize(x1 - x0, y1 - y0, (c) => {
|
|
208
|
+
c.translate(-x0, -y0); c.strokeStyle = col; c.lineWidth = 13; c.lineCap = 'butt'; c.lineJoin = 'round';
|
|
209
|
+
c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x - Math.cos(a) * 12 * (i === pts.length - 1), y - Math.sin(a) * 12 * (i === pts.length - 1)) : c.moveTo(x, y))); c.stroke();
|
|
210
|
+
c.fillStyle = col; c.beginPath(); c.moveTo(ex + Math.cos(a) * 14, ey + Math.sin(a) * 14);
|
|
211
|
+
c.lineTo(ex + Math.cos(a + 2.5) * 22, ey + Math.sin(a + 2.5) * 22); c.lineTo(ex + Math.cos(a - 2.5) * 22, ey + Math.sin(a - 2.5) * 22); c.fill();
|
|
212
|
+
}, { seed });
|
|
213
|
+
shadowDraw(ctx, L, x0, y0, 0.6);
|
|
214
|
+
};
|
|
215
|
+
const fmt = (n) => Math.round(n).toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
216
|
+
const numberRow = (ctx, str, x, y, px, col, o = {}) => {
|
|
217
|
+
let cx = x;
|
|
218
|
+
for (const ch of str) {
|
|
219
|
+
const s = digitSpr(ch, px, col), w = s.width - 22;
|
|
220
|
+
const jr = H.rng(ch.charCodeAt(0) * 13 + Math.round(cx));
|
|
221
|
+
put(ctx, s, cx + w / 2, y, { rot: (jr() - 0.5) * 0.08, d: o.d ?? 0.7 });
|
|
222
|
+
cx += w - 2;
|
|
223
|
+
}
|
|
224
|
+
return cx;
|
|
225
|
+
};
|
|
226
|
+
// drop-in animation helper (paper falls on, overshoots, settles; shadow is lifted while in the air)
|
|
227
|
+
const dropIn = (t, st, du = 0.4, dist = 60) => {
|
|
228
|
+
const u = H.clamp((t - st) / du);
|
|
229
|
+
if (u <= 0) return null;
|
|
230
|
+
const e = H.ease.back(u), lift = 1 - H.ease.out(u);
|
|
231
|
+
return { dy: -(1 - e) * dist, s: 1 + lift * 0.08, d: lift * 3, a: H.clamp(u * 4) };
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
const EXPLAIN = (ctx, W, H_, t) => {
|
|
235
|
+
// ---- camera: one long diorama, panned station to station ----
|
|
236
|
+
const seg = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
237
|
+
const cam = 1280 * seg(1.8, 2.8) + 380 * seg(5.6, 6.2) + 1280 * seg(10.1, 10.8);
|
|
238
|
+
// backboard + stars (screen-fixed, far away)
|
|
239
|
+
ctx.drawImage(cache('sky', () => {
|
|
240
|
+
const C = H.layer(W, H_), c = C.getContext('2d');
|
|
241
|
+
c.drawImage(paperize(W, H_, (x) => { x.fillStyle = '#2b2d5a'; x.fillRect(0, 0, W, H_); }, { rim: false, seed: 2 }), 0, 0);
|
|
242
|
+
const r = H.rng(31);
|
|
243
|
+
const st = paperize(W, H_, (x) => { x.fillStyle = '#f6e7bf'; for (let i = 0; i < 60; i++) { const sx = r.range(10, W - 10), sy = r.range(10, 430), s = r.range(3, 7), pts = []; for (let j = 0; j < 10; j++) { const rr = j % 2 ? s * 0.42 : s, a = (j / 10) * TAU; pts.push([sx + Math.cos(a) * rr, sy + Math.sin(a) * rr]); } x.beginPath(); poly(x, pts); x.fill(); } }, { hi: 0.3 });
|
|
244
|
+
shadowDraw(c, st, 0, 0, 0.25);
|
|
245
|
+
return C;
|
|
246
|
+
}), 0, 0);
|
|
247
|
+
// far mountains (parallax 0.3)
|
|
248
|
+
const far = cache('far', () => {
|
|
249
|
+
const r = H.rng(71), f = (x) => 470 + 70 * Math.sin(x * 0.006 + 1) + 40 * Math.sin(x * 0.017);
|
|
250
|
+
// sprite starts 40px higher than before so the tallest peak (y 360) is no longer sliced flat by the sprite top
|
|
251
|
+
return paperize(2400, 400, (c) => { c.fillStyle = '#4c5590'; c.beginPath(); poly(c, [...ridge(r, 0, 2400, (x) => f(x) - 340, 14, 30, 0.6), [2400, 400], [0, 400]]); c.fill(); });
|
|
252
|
+
});
|
|
253
|
+
shadowDraw(ctx, far, -cam * 0.3, 340, 1.0);
|
|
254
|
+
// mid ridge (parallax 0.6): a second scissor-cut hill sheet with little paper pines glued on,
|
|
255
|
+
// so the backdrop reads as a stacked shadow-box like the hero rather than one ridge + a slab
|
|
256
|
+
const mid = cache('mid', () => {
|
|
257
|
+
const r = H.rng(91), f = (x) => 62 + 26 * Math.sin(x * 0.0047 + 0.6) + 14 * Math.sin(x * 0.0135 + 2.1) + 6 * Math.sin(x * 0.037);
|
|
258
|
+
const C = H.layer(3200, 300), c = C.getContext('2d');
|
|
259
|
+
shadowDraw(c, paperize(3200, 300, (x) => { x.fillStyle = '#3d5f7a'; x.beginPath(); poly(x, [...ridge(r, 0, 3200, f, 12, 28, 0.6), [3200, 300], [0, 300]]); x.fill(); }, { seed: 92 }), 0, 0, 0);
|
|
260
|
+
// pines: three stacked zigzag tiers on a trunk, each pine its own cut sheet
|
|
261
|
+
const pr = H.rng(93);
|
|
262
|
+
for (let px = 60; px < 3200; px += pr.range(130, 320)) {
|
|
263
|
+
const h = pr.range(46, 84), base = f(px) + h * 0.18, w = h * 0.5, col = pr() < 0.5 ? '#2a5560' : '#335f6a';
|
|
264
|
+
const pts = [];
|
|
265
|
+
for (let k = 0; k < 3; k++) {
|
|
266
|
+
const ty = base - h * 0.16 - k * h * 0.25, tw = w * (1 - k * 0.24), th = h * 0.4;
|
|
267
|
+
const tier = [[px, ty - th]];
|
|
268
|
+
for (let j = 0; j <= 4; j++) { const u = j / 4; tier.push([px + tw / 2 - u * tw, ty + (j % 2 ? -4 : 0) + pr.gauss() * 0.5]); }
|
|
269
|
+
pts.push(tier);
|
|
270
|
+
}
|
|
271
|
+
const pw = Math.ceil(w + 24), ph = Math.ceil(h * 1.25 + 10), ox = px - pw / 2, oy = base - h * 1.15;
|
|
272
|
+
const sp = paperize(pw, ph, (x) => { x.translate(-ox, -oy); x.fillStyle = '#3a2c2a'; x.fillRect(px - 3, base - h * 0.22, 6, h * 0.3); x.fillStyle = col; x.beginPath(); pts.forEach((p) => poly(x, p)); x.fill(); }, { seed: 94 + Math.round(px) });
|
|
273
|
+
shadowDraw(c, sp, ox, oy, 0.5, 0.4);
|
|
274
|
+
}
|
|
275
|
+
return C;
|
|
276
|
+
});
|
|
277
|
+
shadowDraw(ctx, mid, -cam * 0.6, 452, 1.15);
|
|
278
|
+
// ground (world, parallax 1): torn top edge showing the white fibrous core of the card, like the hero's hills
|
|
279
|
+
const ground = cache('ground', () => {
|
|
280
|
+
const nz = H.makeNoise(83), r = H.rng(81), f = (x) => 22 + 12 * Math.sin(x * 0.005 + 1.3) + 7 * Math.sin(x * 0.019);
|
|
281
|
+
const torn = [], core = [];
|
|
282
|
+
for (let x = 0; x <= 4500; x += 2.5) {
|
|
283
|
+
const y = f(x) + nz.fbm2(x * 0.03, 0.5, 4) * 5 + r.gauss() * 0.6;
|
|
284
|
+
torn.push([x, y + 3 + Math.abs(nz.n2(x * 0.045, 3.1)) * 7 + Math.abs(nz.n2(x * 0.3, 7.7)) * 2]);
|
|
285
|
+
core.push([x, y]);
|
|
286
|
+
}
|
|
287
|
+
return paperize(4500, 200, (c) => {
|
|
288
|
+
c.fillStyle = '#f3ecdc'; c.beginPath(); poly(c, [...core, [4500, 200], [0, 200]]); c.fill();
|
|
289
|
+
c.strokeStyle = 'rgba(248,244,236,0.95)'; c.lineCap = 'round';
|
|
290
|
+
for (let i = 0; i < core.length; i++) {
|
|
291
|
+
if (!r.chance(0.55)) continue;
|
|
292
|
+
const [x, y] = core[i], a = -Math.PI / 2 + r.range(-0.9, 0.9), l = r.range(1.5, 5.5);
|
|
293
|
+
c.lineWidth = r.range(0.4, 0.9); c.beginPath(); c.moveTo(x, y + 1); c.lineTo(x + Math.cos(a) * l, y + Math.sin(a) * l); c.stroke();
|
|
294
|
+
}
|
|
295
|
+
c.fillStyle = '#2f6b66'; c.beginPath(); poly(c, [...torn, [4500, 200], [0, 200]]); c.fill();
|
|
296
|
+
// grass snips cut up out of the green sheet
|
|
297
|
+
const gr = H.rng(84);
|
|
298
|
+
c.beginPath();
|
|
299
|
+
for (let x = 30; x < 4480; x += gr.range(60, 190)) {
|
|
300
|
+
const n = gr.int(2, 4), by = f(x) + 40 + gr.range(0, 60);
|
|
301
|
+
for (let k = 0; k < n; k++) { const bx = x + k * 7; c.moveTo(bx, by); c.lineTo(bx + 2 + gr.range(-3, 4), by - gr.range(10, 22)); c.lineTo(bx + 5, by); }
|
|
302
|
+
}
|
|
303
|
+
c.fillStyle = '#285f5a'; c.fill();
|
|
304
|
+
}, { seed: 85 });
|
|
305
|
+
});
|
|
306
|
+
shadowDraw(ctx, ground, -cam - 100, 560, 1.3);
|
|
307
|
+
// nearest strip (parallax 1.25): dark foreground card with grass snips along the bottom edge
|
|
308
|
+
const fore = cache('fore', () => {
|
|
309
|
+
const r = H.rng(141), f = (x) => 30 + 10 * Math.sin(x * 0.0045 + 0.4) + 5 * Math.sin(x * 0.013), pts = [];
|
|
310
|
+
let x = 0;
|
|
311
|
+
while (x < 5200) {
|
|
312
|
+
if (r.chance(0.2)) {
|
|
313
|
+
const n = r.int(2, 4);
|
|
314
|
+
for (let k = 0; k < n; k++) { const bx = x + k * 7, by = f(bx); pts.push([bx, by], [bx + 2 + r.range(-3, 4), by - r.range(12, 26)], [bx + 5, by]); }
|
|
315
|
+
x += n * 7 + 4;
|
|
316
|
+
} else { pts.push([x, f(x) + r.gauss() * 0.6]); x += r.range(14, 30); }
|
|
317
|
+
}
|
|
318
|
+
return paperize(5200, 90, (c) => { c.fillStyle = '#163a3a'; c.beginPath(); poly(c, [...pts, [5200, 90], [0, 90]]); c.fill(); }, { seed: 142 });
|
|
319
|
+
});
|
|
320
|
+
shadowDraw(ctx, fore, -cam * 1.25 - 60, 660, 1.6);
|
|
321
|
+
|
|
322
|
+
ctx.save(); ctx.translate(-cam, 0); // ---------------- world space ----------------
|
|
323
|
+
|
|
324
|
+
// ---- BEAT 1: the question, cut letters dropped on the backboard ----
|
|
325
|
+
{
|
|
326
|
+
const lines = [['How does', 92, 250], ['AI learn?', 128, 400]];
|
|
327
|
+
let i = 0;
|
|
328
|
+
lines.forEach(([str, px, y]) => {
|
|
329
|
+
const tot = measure(str, AF(px)) + str.length * 4;
|
|
330
|
+
let x = 640 - tot / 2;
|
|
331
|
+
for (const ch of str) {
|
|
332
|
+
const w = measure(ch, AF(px));
|
|
333
|
+
if (ch !== ' ') {
|
|
334
|
+
const A = dropIn(t, 0.15 + i * 0.06, 0.38, 70);
|
|
335
|
+
const cols = ['#e8604a', '#f2b545', '#58b0a6'];
|
|
336
|
+
if (A && cam < 1300) put(ctx, wordSpr(ch, px, cols[i % 3], '#f3ecdc', 14), x + w / 2, y + A.dy, { s: A.s, d: 0.8 + A.d, rot: (H.rng(i + 3)() - 0.5) * 0.12 });
|
|
337
|
+
i++;
|
|
338
|
+
}
|
|
339
|
+
x += w + 4;
|
|
340
|
+
}
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ---- BEAT 2: labelled examples ride a paper conveyor into the model ----
|
|
345
|
+
const BX = 2290, BY = 330;
|
|
346
|
+
if (cam > 400) {
|
|
347
|
+
const belt = cache('belt', () => compose(1060, 120, [
|
|
348
|
+
[(c) => { // cut-card supports: tapered teal legs with a foot tab
|
|
349
|
+
const r = H.rng(88);
|
|
350
|
+
[37, 527, 1007].forEach((x) => { c.fillStyle = '#24585b'; c.beginPath(); poly(c, cutLines(r, [[x - 14, 50], [x + 14, 50], [x + 17, 112], [x + 26, 112], [x + 26, 120], [x - 26, 120], [x - 26, 112], [x - 17, 112], [x - 14, 50]], 16, 0.4)); c.fill();
|
|
351
|
+
c.fillStyle = 'rgba(8,24,30,0.35)'; c.beginPath(); poly(c, [[x + 3, 50], [x + 14, 50], [x + 17, 112], [x + 5, 112]]); c.fill(); });
|
|
352
|
+
}, 0.5],
|
|
353
|
+
[(c) => { c.fillStyle = '#1f4f53'; c.beginPath(); poly(c, cutLines(H.rng(8), [[0, 20], [1060, 20], [1060, 56], [0, 56], [0, 20]], 26, 0.5)); c.fill(); }, 0.4],
|
|
354
|
+
[(c) => { c.fillStyle = '#f2b545'; for (let x = 40; x < 1060; x += 90) { c.beginPath(); c.arc(x, 38, 11, 0, TAU); c.fill(); } }, 0.3],
|
|
355
|
+
]));
|
|
356
|
+
shadowDraw(ctx, belt, 1110, 470, 0.9);
|
|
357
|
+
// flow arrow over the belt
|
|
358
|
+
arrow(ctx, 1380, 300, 1700, 262, 2080, 300, seg(2.7, 3.3), '#f2b545', 41);
|
|
359
|
+
// cards
|
|
360
|
+
const r = H.rng(55);
|
|
361
|
+
for (let k = 0; k < 9; k++) {
|
|
362
|
+
const s = 2.15 + k * 0.3, kind = r() < 0.5 ? 'cat' : 'dog', wob = r() * 6;
|
|
363
|
+
const x = 1150 + (t - s) * 650;
|
|
364
|
+
if (t < s || x > BX - 60) continue;
|
|
365
|
+
put(ctx, cardSpr(kind), x, 410 + Math.sin(t * 9 + wob) * 2, { rot: Math.sin(t * 5 + wob) * 0.03, d: 0.9 });
|
|
366
|
+
}
|
|
367
|
+
// counter: paper digits
|
|
368
|
+
const A = dropIn(t, 2.55, 0.4, 50);
|
|
369
|
+
if (A) {
|
|
370
|
+
const n = 1e6 * Math.pow(H.clamp((t - 2.8) / 2.3), 2.2);
|
|
371
|
+
const end = numberRow(ctx, fmt(n), 1440, 165 + A.dy, 64, '#f2b545');
|
|
372
|
+
put(ctx, wordSpr('examples', 40, '#58b0a6'), end + 120, 172 + A.dy, { d: 0.7 });
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
// model: brain + dial panel (stays through beat 3)
|
|
376
|
+
let gulp = 0;
|
|
377
|
+
{ const r = H.rng(55); for (let k = 0; k < 9; k++) { const s = 2.15 + k * 0.3; r(); r(); const tin = s + (BX - 60 - 1150) / 650; gulp += Math.exp(-Math.pow((t - tin) * 9, 2)); } }
|
|
378
|
+
const PB = dropIn(t, 2.6, 0.45, 80);
|
|
379
|
+
if (PB) {
|
|
380
|
+
put(ctx, panelSpr(), BX, 560 + PB.dy, { d: 1 + PB.d });
|
|
381
|
+
const adj = seg(8.9, 9.5);
|
|
382
|
+
[0, 1, 2].forEach((i) => {
|
|
383
|
+
const base = [-0.9, 0.5, -0.3][i], delta = [1.3, -0.9, 1.0][i];
|
|
384
|
+
const ang = base + delta * adj + (adj > 0 && adj < 1 ? Math.sin(t * 40 + i) * 0.08 : 0);
|
|
385
|
+
put(ctx, pointerSpr(), BX - 80 + i * 80 + Math.sin(ang) * 14, 560 + PB.dy - Math.cos(ang) * 14, { rot: ang, d: 0.4 });
|
|
386
|
+
});
|
|
387
|
+
put(ctx, brainSpr(), BX, BY + PB.dy, { s: (1 + gulp * 0.05) * PB.s, d: 1.4 + PB.d });
|
|
388
|
+
const T1 = dropIn(t, 3.3, 0.35, 40);
|
|
389
|
+
if (T1) put(ctx, wordSpr('model', 30, '#2b2d5a', '#fbf6ea', 10), BX - 110, BY + 118 + T1.dy, { rot: -0.1, d: 0.8 + T1.d });
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ---- BEAT 3: guess -> check -> adjust loop ----
|
|
393
|
+
if (t > 6.0) {
|
|
394
|
+
const NC = dropIn(t, 6.3, 0.4, 90);
|
|
395
|
+
if (NC) put(ctx, cardSpr('cat'), 1880, 300 + NC.dy, { s: 1.15 * NC.s, d: 1.2 + NC.d, rot: -0.05 });
|
|
396
|
+
arrow(ctx, 1975, 300, 2040, 270, 2120, 300, seg(6.7, 7.0), '#f2b545', 51);
|
|
397
|
+
arrow(ctx, 2445, 290, 2510, 250, 2570, 262, seg(7.05, 7.35), '#f2b545', 52);
|
|
398
|
+
const G = dropIn(t, 7.2, 0.3, 40);
|
|
399
|
+
if (G) put(ctx, wordSpr('Guess', 30, '#2b2d5a', '#fbf6ea', 10), 2505, 205 + G.dy, { rot: -0.08, d: 0.8 + G.d });
|
|
400
|
+
// bubble flips from "dog?" to "cat"
|
|
401
|
+
const B = dropIn(t, 7.4, 0.35, 50);
|
|
402
|
+
if (B) {
|
|
403
|
+
const flip = H.clamp((t - 9.55) / 0.3), sx = Math.abs(Math.cos(flip * Math.PI)) + 0.001;
|
|
404
|
+
const wrong = flip < 0.5;
|
|
405
|
+
const shake = t > 7.9 && t < 8.3 ? Math.sin(t * 60) * 6 * (8.3 - t) * 2.5 : 0;
|
|
406
|
+
const ring = dropIn(t, 7.9, 0.3, 0);
|
|
407
|
+
if (ring && wrong) put(ctx, ringSpr(), 2690, 255, { s: 0.7 + 0.3 * H.ease.back(H.clamp((t - 7.9) / 0.3)), d: 0.6, rot: 0.05 });
|
|
408
|
+
put(ctx, bubbleSpr(wrong ? 'dog?' : 'cat'), 2690 + shake, 260 + B.dy, { sx, sy: 1, d: 1 + B.d });
|
|
409
|
+
const X = dropIn(t, 7.9, 0.25, 40);
|
|
410
|
+
if (X && wrong) put(ctx, crossSpr(), 2790, 215 + X.dy, { s: X.s, d: 0.8 + X.d, rot: 0.1 });
|
|
411
|
+
const C = dropIn(t, 8.05, 0.3, 40);
|
|
412
|
+
if (C && t < 9.55) put(ctx, wordSpr('Check', 30, '#d9473b', '#fbf6ea', 10), 2640, 345 + C.dy, { rot: 0.06, d: 0.8 + C.d });
|
|
413
|
+
const V = dropIn(t, 9.9, 0.3, 50);
|
|
414
|
+
if (V) put(ctx, checkSpr(), 2795, 215 + V.dy, { s: V.s, d: 0.8 + V.d, rot: -0.06 });
|
|
415
|
+
}
|
|
416
|
+
// error signal travels back to the dials
|
|
417
|
+
if (t < 9.6) arrow(ctx, 2660, 335, 2580, 560, 2420, 560, seg(8.3, 8.8), '#d9473b', 53);
|
|
418
|
+
const AJ = dropIn(t, 8.7, 0.3, 40);
|
|
419
|
+
if (AJ && t < 9.6) put(ctx, wordSpr('Adjust', 30, '#d9473b', '#fbf6ea', 10), 2560, 610 + AJ.dy, { rot: -0.05, d: 0.8 + AJ.d });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// ---- BEAT 4: progress chart, bars pop up from behind the ground ----
|
|
423
|
+
if (cam > 2000) {
|
|
424
|
+
const OXc = 3250, base = 590, accs = [61, 72, 80, 86, 90, 93, 95, 97];
|
|
425
|
+
const axis = cache('axis', () => compose(640, 400, [[(c) => { c.fillStyle = '#f3ecdc'; c.fillRect(20, 0, 12, 390); c.fillRect(20, 378, 620, 12); }, 0]]));
|
|
426
|
+
shadowDraw(ctx, axis, OXc - 26, base - 388, 0.6);
|
|
427
|
+
let shown = 61, last = -1;
|
|
428
|
+
ctx.save(); ctx.beginPath(); ctx.rect(OXc - 40, 0, 700, base - 6); ctx.clip();
|
|
429
|
+
accs.forEach((a, i) => {
|
|
430
|
+
const st = 10.85 + i * 0.22, u = H.clamp((t - st) / 0.32);
|
|
431
|
+
if (u <= 0) return;
|
|
432
|
+
const hgt = a * 3.4, bar = cache('bar' + i, () => paperize(56, hgt, (c) => { c.fillStyle = i === 7 ? '#e8604a' : i % 2 ? '#58b0a6' : '#f2b545'; c.beginPath(); poly(c, cutLines(H.rng(i), [[2, 2], [54, 2], [54, hgt], [2, hgt], [2, 2]], 22, 0.5)); c.fill(); }, { seed: 60 + i }));
|
|
433
|
+
shadowDraw(ctx, bar, OXc + 30 + i * 74, base - hgt * H.ease.back(u), 0.9);
|
|
434
|
+
last = i;
|
|
435
|
+
});
|
|
436
|
+
ctx.restore();
|
|
437
|
+
if (last >= 0) { const st = 10.85 + last * 0.22; shown = H.lerp(last ? accs[last - 1] : 55, accs[last], H.clamp((t - st) / 0.32)); }
|
|
438
|
+
const Lb = dropIn(t, 10.6, 0.35, 50);
|
|
439
|
+
if (Lb) {
|
|
440
|
+
if (t > 12.6) { const u = H.clamp((t - 12.6) / 0.35); put(ctx, burstSpr(), 3995, 168, { s: 0.2 + 0.8 * H.ease.back(u), rot: t * 0.3, d: 0.7 }); }
|
|
441
|
+
put(ctx, wordSpr('Accuracy', 44, '#58b0a6'), 3480, 170 + Lb.dy, { d: 0.8 + Lb.d, rot: -0.03 });
|
|
442
|
+
const pop = t > 12.6 ? 1 + 0.18 * Math.sin(H.clamp((t - 12.6) / 0.35) * Math.PI) : 1;
|
|
443
|
+
ctx.save(); ctx.translate(3995, 170); ctx.scale(pop, pop); ctx.translate(-3995, -170);
|
|
444
|
+
numberRow(ctx, Math.round(shown) + '%', 3900, 165 + Lb.dy, 72, '#e8604a');
|
|
445
|
+
ctx.restore();
|
|
446
|
+
}
|
|
447
|
+
const RL = dropIn(t, 11.1, 0.3, 30);
|
|
448
|
+
if (RL) put(ctx, wordSpr('training rounds', 26, '#2b2d5a', '#fbf6ea', 9), OXc + 330, base + 40 + RL.dy, { d: 0.6 + RL.d });
|
|
449
|
+
}
|
|
450
|
+
ctx.restore(); // ---------------- end world ----------------
|
|
451
|
+
|
|
452
|
+
// header: the question shrinks to a pinned paper tag
|
|
453
|
+
const HD = dropIn(t, 2.0, 0.4, 60);
|
|
454
|
+
if (HD) put(ctx, wordSpr('How does AI learn?', 30, '#f3ecdc', '#e8604a', 10), 225, 48 + HD.dy, { rot: -0.03, d: 1 + HD.d });
|
|
455
|
+
|
|
456
|
+
// ---- BEAT 5: takeaway banner ----
|
|
457
|
+
const TB = dropIn(t, 13.0, 0.45, 80);
|
|
458
|
+
if (TB) {
|
|
459
|
+
const ban = cache('banner', () => {
|
|
460
|
+
const a = wordSpr('More examples', 38, '#2b2d5a', null), b = wordSpr('better guesses', 38, '#e8604a', null);
|
|
461
|
+
const w = a.width + b.width + 110, C = H.layer(w + 40, 110), c = C.getContext('2d');
|
|
462
|
+
shadowDraw(c, paperize(w + 40, 110, (x) => { x.fillStyle = '#fbf6ea'; x.beginPath(); poly(x, cutLines(H.rng(3), [[6, 12], [w + 34, 6], [w + 30, 98], [10, 104], [6, 12]], 30, 0.6)); x.fill(); }, { seed: 4 }), 0, 0, 0);
|
|
463
|
+
shadowDraw(c, a, 20, 55 - a.height / 2, 0.4, 0.3);
|
|
464
|
+
arrow(c, a.width + 22, 56, a.width + 50, 56, a.width + 84, 56, 1, '#58b0a6', 77);
|
|
465
|
+
shadowDraw(c, b, a.width + 100, 55 - b.height / 2, 0.4, 0.3);
|
|
466
|
+
return C;
|
|
467
|
+
});
|
|
468
|
+
put(ctx, ban, 640, 660 + TB.dy, { s: TB.s, d: 1.4 + TB.d, rot: -0.01 });
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// ---- photo finish ----
|
|
472
|
+
ctx.save(); ctx.globalCompositeOperation = 'soft-light';
|
|
473
|
+
const lg = ctx.createRadialGradient(260, 60, 40, 420, 200, 1100);
|
|
474
|
+
lg.addColorStop(0, 'rgba(255,236,200,1)'); lg.addColorStop(0.5, 'rgba(255,230,200,0.25)'); lg.addColorStop(1, 'rgba(0,0,20,0.6)');
|
|
475
|
+
ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_);
|
|
476
|
+
// second, fainter warm pool on the stage where the action is (a lamp over the shadow-box)
|
|
477
|
+
const lp = ctx.createRadialGradient(720, 330, 20, 720, 330, 560);
|
|
478
|
+
lp.addColorStop(0, 'rgba(255,214,160,0.42)'); lp.addColorStop(1, 'rgba(255,214,160,0)');
|
|
479
|
+
ctx.fillStyle = lp; ctx.fillRect(0, 0, W, H_); ctx.restore();
|
|
480
|
+
H.vignette(ctx, W, H_, 0.42, '10,6,24');
|
|
481
|
+
H.grain(ctx, W, H_, 0.03, 9 + Math.floor(t * 12));
|
|
482
|
+
};
|
|
483
|
+
|
|
484
|
+
registerStyle({
|
|
485
|
+
id: 'cut-paper',
|
|
486
|
+
name: 'Cut-Paper Diorama',
|
|
487
|
+
category: 'Paper & Craft',
|
|
488
|
+
description: 'A layered cardstock shadow-box: every shape is a separate scissor-cut sheet with fibre texture, a lit paper edge and a soft drop shadow onto the sheet behind it; torn edges show the white core of the paper.',
|
|
489
|
+
motion: 'Layers slide in on parallax like a pop-up book, the rocket rises on its wire while paper smoke puffs and the flame tongues flutter.',
|
|
490
|
+
fonts: ['400 112px "Archivo Black"', '400 50px "Archivo Black"', '400 30px "Archivo Black"'],
|
|
491
|
+
frameT: 4.8,
|
|
492
|
+
duration: 5,
|
|
493
|
+
readsPixels: true,
|
|
494
|
+
explainerDuration: 15,
|
|
495
|
+
explainerNote: 'One long paper diorama the camera pans along on parallax; arrows are cut-paper strips that grow, labels are paper tags dropped onto what they name, numbers are swapped cut-out digit tiles, and emphasis is a paper ring or sunburst popping up behind.',
|
|
496
|
+
explainer(ctx, W, H_, t) { EXPLAIN(ctx, W, H_, t); },
|
|
497
|
+
// Sound: a cardstock shadow-box. Every dropped piece lands as a light card tap (body + paper snap) at the moment
|
|
498
|
+
// its back-eased drop first reaches rest (st + 0.37*du); camera pans are big cardstock layers sliding past each
|
|
499
|
+
// other; arrows are paper strips pushed out with a dry slide; digit tiles flick; the sunburst is a pop-up page.
|
|
500
|
+
// Times mirror EXPLAIN()/dropIn above (title 0.15+i*0.06, cam pans 1.8-2.8 / 5.6-6.2 / 10.1-10.8, bars 10.85+i*0.22).
|
|
501
|
+
explainerSound(k) {
|
|
502
|
+
const LAND = 1 - 1.70158 / 2.70158; // H.ease.back first reaches 1 here: the piece touches down
|
|
503
|
+
const land = (st, du) => st + LAND * du;
|
|
504
|
+
const seg = (t, a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
505
|
+
const cam = (t) => 1280 * seg(t, 1.8, 2.8) + 380 * seg(t, 5.6, 6.2) + 1280 * seg(t, 10.1, 10.8);
|
|
506
|
+
const pan = (wx, t) => H.clamp(((wx - cam(t)) / 1280) * 2 - 1, -1, 1) * 0.6;
|
|
507
|
+
// card tap: a cut piece of cardstock landing on the sheet behind it
|
|
508
|
+
const card = (t, g = 1, p = 0, f = 1, minor = true) => {
|
|
509
|
+
k.noise(t, { dur: 0.045, filter: 'bandpass', f: 1100 * f, q: 0.9, gain: 0.26 * g, a: 0.001, pan: p, minor });
|
|
510
|
+
k.noise(t, { dur: 0.09, color: 'brown', filter: 'lowpass', f: 650, gain: 0.3 * g, a: 0.002, pan: p, minor });
|
|
511
|
+
k.tone(t, { f: [[0, 160 * f], [0.07, 85]], dur: 0.09, gain: 0.16 * g, pan: p, minor });
|
|
512
|
+
};
|
|
513
|
+
// a big cardstock layer sliding against the one behind it (camera pans): smooth, low, no crinkle
|
|
514
|
+
const layer = (t, d, g = 1) => {
|
|
515
|
+
k.noise(t, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 260], [d * 0.5, 620], [d, 300]], q: 0.6, gain: 0.16 * g, a: d * 0.45, pan: 0.15 });
|
|
516
|
+
k.noise(t + 0.05, { dur: d, color: 'brown', filter: 'lowpass', f: 380, gain: 0.14 * g, a: d * 0.5, pan: -0.15 });
|
|
517
|
+
};
|
|
518
|
+
// paper strip pushed out (arrows): a dry, soft slide with a little tooth, then the head taps down
|
|
519
|
+
const strip = (t0, t1, g = 1, p0 = 0, p1 = 0) => {
|
|
520
|
+
const d = t1 - t0;
|
|
521
|
+
k.noise(t0, { dur: d, filter: 'bandpass', f: [[0, 900], [d, 1500]], q: 0.8, gain: 0.09 * g, a: d * 0.3, hold: d * 0.4, pan: (p0 + p1) / 2, minor: true });
|
|
522
|
+
card(t1, 0.55 * g, p1, 1.2);
|
|
523
|
+
};
|
|
524
|
+
// digit tile flick (counters)
|
|
525
|
+
const flick = (t, g = 1, p = 0) => {
|
|
526
|
+
k.noise(t, { dur: 0.022, filter: 'bandpass', f: 1800, q: 1.4, gain: 0.22 * g, a: 0.001, pan: p, minor: true });
|
|
527
|
+
k.tone(t, { f: 220, dur: 0.03, gain: 0.06 * g, pan: p, minor: true });
|
|
528
|
+
};
|
|
529
|
+
|
|
530
|
+
k.bed('room', 0, 15, { gain: 0.45 });
|
|
531
|
+
|
|
532
|
+
// ---- BEAT 1: 15 cut letters drop onto the backboard
|
|
533
|
+
for (let i = 0; i < 15; i++) {
|
|
534
|
+
const line = i < 7 ? 0 : 1, j = line ? i - 7 : i, n = line ? 8 : 7;
|
|
535
|
+
card(land(0.15 + i * 0.06, 0.38), i === 14 ? 0.85 : 0.42 + (i % 3) * 0.07, ((j + 0.5) / n - 0.5) * 0.8, line ? 0.9 : 1.05);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
// ---- pan 1: the diorama slides to the conveyor; header tag pinned
|
|
539
|
+
layer(1.8, 1.0, 1);
|
|
540
|
+
card(land(2.0, 0.4), 0.7, -0.6, 1.2);
|
|
541
|
+
card(land(2.55, 0.4), 0.8, pan(1600, 2.7)); // counter row drops
|
|
542
|
+
card(land(2.6, 0.45), 1.1, pan(2290, 2.77), 0.8, false); // dial panel + brain (the model) lands
|
|
543
|
+
strip(2.7, 3.3, 1, pan(1380, 3), pan(2080, 3.3));
|
|
544
|
+
card(land(3.3, 0.35), 0.6, pan(2180, 3.4), 1.2); // 'model' tag
|
|
545
|
+
// cards riding the belt (soft drag) and the brain gulping each one (tin = s + (BX-60-1150)/650)
|
|
546
|
+
k.noise(2.3, { dur: 3.95, color: 'brown', filter: 'lowpass', f: 520, gain: 0.05, a: 0.4, hold: 3.2, pan: 0, minor: true });
|
|
547
|
+
for (let c = 0; c < 9; c++) {
|
|
548
|
+
const tin = 2.15 + c * 0.3 + (2290 - 60 - 1150) / 650;
|
|
549
|
+
k.noise(tin, { dur: 0.08, color: 'brown', filter: 'lowpass', f: 420, gain: 0.32, a: 0.01, pan: pan(2230, tin), minor: true });
|
|
550
|
+
k.tone(tin, { f: [[0, 120], [0.08, 70]], dur: 0.1, gain: 0.1, pan: pan(2230, tin), minor: true });
|
|
551
|
+
}
|
|
552
|
+
// counter 0 -> 1,000,000 over 2.8..5.1: tile flicks speeding up with the count, a firm tile at the end
|
|
553
|
+
for (let t = 2.86; t < 5.06;) { const s = H.clamp((t - 2.8) / 2.3); flick(t, 0.75 + 0.3 * s, pan(1600, t)); t += 0.125 - 0.04 * s; }
|
|
554
|
+
card(5.1, 1.1, pan(1700, 5.1), 1.3, false);
|
|
555
|
+
|
|
556
|
+
// ---- pan 2 (short) and BEAT 3: guess -> check -> adjust
|
|
557
|
+
layer(5.6, 0.6, 0.6);
|
|
558
|
+
card(land(6.3, 0.4), 1.0, pan(1880, 6.45), 0.9, false); // new cat photo drops in
|
|
559
|
+
strip(6.7, 7.0, 1, pan(1975, 6.8), pan(2120, 7));
|
|
560
|
+
strip(7.05, 7.35, 1, pan(2445, 7.1), pan(2570, 7.35));
|
|
561
|
+
card(land(7.2, 0.3), 0.6, pan(2505, 7.3), 1.2); // 'Guess' tag
|
|
562
|
+
card(land(7.4, 0.35), 0.9, pan(2690, 7.5)); // 'dog?' bubble
|
|
563
|
+
// the wrong guess: red paper X slapped on, ring pops behind, the bubble rattles
|
|
564
|
+
const tX = land(7.9, 0.25);
|
|
565
|
+
k.play('slap', tX, { gain: 0.6, pan: pan(2790, tX) });
|
|
566
|
+
card(tX, 1.3, pan(2790, tX), 0.8, false);
|
|
567
|
+
k.tone(tX + 0.01, { f: 'D3', type: 'triangle', dur: 0.16, gain: 0.2, filter: 'lowpass', f2: 700 }); // low wooden "nope"
|
|
568
|
+
k.tone(tX + 0.17, { f: 'A2', type: 'triangle', dur: 0.3, gain: 0.22, filter: 'lowpass', f2: 600 });
|
|
569
|
+
k.play('paper', 7.93, { dur: 0.33, gain: 0.35, pan: pan(2690, 8), minor: true }); // bubble card shaking
|
|
570
|
+
card(land(8.05, 0.3), 0.6, pan(2640, 8.16), 1.2); // 'Check' tag
|
|
571
|
+
strip(8.3, 8.8, 1.1, pan(2660, 8.3), pan(2420, 8.8)); // red error strip back to the dials
|
|
572
|
+
card(land(8.7, 0.3), 0.6, pan(2560, 8.8), 1.2); // 'Adjust' tag
|
|
573
|
+
// dial pointers pivot on their brads (seg 8.9..9.5)
|
|
574
|
+
for (let i = 1; i <= 6; i++) {
|
|
575
|
+
let t = 8.9; while (seg(t, 8.9, 9.5) < i / 6 - 1e-6 && t < 9.5) t += 0.005;
|
|
576
|
+
k.noise(t, { dur: 0.03, filter: 'bandpass', f: 1300 + i * 40, q: 2, gain: 0.22, a: 0.001, pan: pan(2420, t), minor: true });
|
|
577
|
+
k.tone(t, { f: 300, dur: 0.035, gain: 0.05, pan: pan(2420, t), minor: true });
|
|
578
|
+
}
|
|
579
|
+
// bubble flips over (9.55..9.85): two soft flaps of stiff card, then the green tick lands
|
|
580
|
+
k.noise(9.56, { dur: 0.13, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.13, 1100]], q: 0.7, gain: 0.25, a: 0.05, pan: pan(2690, 9.6) });
|
|
581
|
+
k.noise(9.7, { dur: 0.14, color: 'pink', filter: 'bandpass', f: [[0, 1000], [0.14, 450]], q: 0.7, gain: 0.22, a: 0.04, pan: pan(2690, 9.7) });
|
|
582
|
+
const tV = land(9.9, 0.3);
|
|
583
|
+
card(tV, 1.1, pan(2795, tV), 1, false);
|
|
584
|
+
k.play('success', tV, { f: 'G5', gain: 0.6, pan: pan(2795, tV) });
|
|
585
|
+
|
|
586
|
+
// ---- pan 3 and BEAT 4: bars pop up from behind the ground
|
|
587
|
+
layer(10.1, 0.7, 1);
|
|
588
|
+
card(land(10.6, 0.35), 0.75, pan(3480, 10.73)); // 'Accuracy' header
|
|
589
|
+
card(land(11.1, 0.3), 0.55, pan(3580, 11.2), 1.2); // 'training rounds'
|
|
590
|
+
const accs = [61, 72, 80, 86, 90, 93, 95, 97];
|
|
591
|
+
accs.forEach((a, i) => {
|
|
592
|
+
const st = 10.85 + i * 0.22, p = pan(3280 + i * 74, st);
|
|
593
|
+
k.noise(st, { dur: 0.1, color: 'pink', filter: 'bandpass', f: [[0, 400], [0.1, 900]], q: 0.7, gain: 0.14, a: 0.05, pan: p, minor: true }); // slides up past the ground layer
|
|
594
|
+
card(land(st, 0.32), 0.6 + i * 0.04, p, 0.9 + i * 0.05);
|
|
595
|
+
});
|
|
596
|
+
{ // % digits swap: tick on each change, at most ~12/s
|
|
597
|
+
let last = -1, lastT = -1;
|
|
598
|
+
for (let t = 10.86; t < 12.72; t += 1 / 120) {
|
|
599
|
+
const i = Math.min(7, Math.floor((t - 10.85) / 0.22)), st = 10.85 + i * 0.22;
|
|
600
|
+
const v = Math.round(H.lerp(i ? accs[i - 1] : 55, accs[i], H.clamp((t - st) / 0.32)));
|
|
601
|
+
if (v !== last && t - lastT >= 1 / 12) { flick(t, 0.7, pan(3995, t)); last = v; lastT = t; }
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
// 97%: the sunburst springs up behind the number like a pop-up page (biggest hit)
|
|
605
|
+
k.play('impact', 12.6, { gain: 1.3, pan: 0.5 });
|
|
606
|
+
k.noise(12.6, { dur: 0.12, color: 'pink', filter: 'bandpass', f: [[0, 600], [0.12, 1600]], q: 0.6, gain: 0.3, a: 0.02, pan: 0.5 });
|
|
607
|
+
k.play('ding', 12.6, { f: 'E5', gain: 0.65, pan: 0.4 });
|
|
608
|
+
|
|
609
|
+
// ---- BEAT 5: takeaway banner drops and resolves
|
|
610
|
+
card(land(13.0, 0.45), 1.0, 0, 0.8, false);
|
|
611
|
+
['C4', 'G4', 'E5'].forEach((f, i) => k.play('ding', land(13.0, 0.45) + 0.16 + i * 0.09, { f, gain: 0.48 - i * 0.04, pan: (i - 1) * 0.25 }));
|
|
612
|
+
},
|
|
613
|
+
draw(ctx, W, H_, t) {
|
|
614
|
+
const R = H.rng(1207);
|
|
615
|
+
const tex = makeTex(77);
|
|
616
|
+
const pat = ctx.createPattern(tex, 'repeat');
|
|
617
|
+
|
|
618
|
+
// one paper sheet = shape + fibre texture + lit/dark cut edge + 2-part drop shadow
|
|
619
|
+
// entrance animation: 'up' slides up like a pop-up layer, 'drop' falls on with a lifted shadow, 'pop' springs out
|
|
620
|
+
const anim = (o) => {
|
|
621
|
+
if (!o.at) return { p: 1, dx: 0, dy: 0, s: 1, lift: 0 };
|
|
622
|
+
const [st, du, mode, dist] = o.at, u = H.clamp((t - st) / du);
|
|
623
|
+
if (u <= 0) return null;
|
|
624
|
+
if (mode === 'up') { const e = H.ease.out(u); return { p: u, dx: 0, dy: (1 - e) * (dist || 260), s: 1, lift: (1 - e) * 1.2 }; }
|
|
625
|
+
if (mode === 'drop') { const e = H.ease.back(u); return { p: u, dx: (1 - e) * -10, dy: (1 - e) * -(dist || 60), s: 1 + (1 - H.ease.out(u)) * 0.06, lift: (1 - H.ease.out(u)) * 3 }; }
|
|
626
|
+
const e = H.ease.back(u); return { p: u, dx: 0, dy: 0, s: Math.max(0.01, e), lift: (1 - H.ease.out(u)) * 2 };
|
|
627
|
+
};
|
|
628
|
+
const sheet = (paint, o = {}) => {
|
|
629
|
+
const tx = R.range(0, 512), ty = R.range(0, 512), tr = R.range(-30, 30);
|
|
630
|
+
const A = anim(o);
|
|
631
|
+
if (!A) return;
|
|
632
|
+
const pad = 6;
|
|
633
|
+
let [x0, y0, x1, y1] = o.bb || [0, 0, W, H_];
|
|
634
|
+
x0 = Math.floor(Math.max(-20, x0 - pad)); y0 = Math.floor(Math.max(-20, y0 - pad));
|
|
635
|
+
x1 = Math.ceil(Math.min(W + 20, x1 + pad)); y1 = Math.ceil(Math.min(H_ + 20, y1 + pad));
|
|
636
|
+
const w = x1 - x0, h = y1 - y0;
|
|
637
|
+
const L = H.layer(w, h), c = L.getContext('2d');
|
|
638
|
+
c.translate(-x0, -y0); paint(c); c.setTransform(1, 0, 0, 1, 0, 0);
|
|
639
|
+
// fibre texture
|
|
640
|
+
c.save(); c.globalCompositeOperation = 'source-atop'; c.globalAlpha = o.tex ?? 1;
|
|
641
|
+
pat.setTransform(new DOMMatrix().translateSelf(tx, ty).rotateSelf(tr));
|
|
642
|
+
c.fillStyle = pat; c.fillRect(0, 0, w, h); c.restore();
|
|
643
|
+
// edge: light catches top-left cut edge, bottom-right edge falls dark
|
|
644
|
+
const edge = (col, dx, dy) => {
|
|
645
|
+
const T = H.layer(w, h), tc = T.getContext('2d');
|
|
646
|
+
tc.drawImage(L, 0, 0); tc.globalCompositeOperation = 'source-in'; tc.fillStyle = col; tc.fillRect(0, 0, w, h);
|
|
647
|
+
tc.globalCompositeOperation = 'destination-out'; tc.drawImage(L, dx, dy);
|
|
648
|
+
c.save(); c.globalCompositeOperation = 'source-atop'; c.drawImage(T, 0, 0); c.restore();
|
|
649
|
+
};
|
|
650
|
+
if (o.rim !== false) {
|
|
651
|
+
edge(`rgba(255,247,232,${o.hi ?? 0.38})`, 1.3, 1.6);
|
|
652
|
+
edge(`rgba(0,0,0,${o.lo ?? 0.28})`, -1.1, -1.3);
|
|
653
|
+
}
|
|
654
|
+
ctx.save();
|
|
655
|
+
if (A.p < 1) {
|
|
656
|
+
const cx = (x0 + x1) / 2, cy = (y0 + y1) / 2;
|
|
657
|
+
ctx.translate(cx + A.dx, cy + A.dy); ctx.scale(A.s, A.s); ctx.translate(-cx, -cy);
|
|
658
|
+
}
|
|
659
|
+
const d = (o.depth ?? 1) + (o.depth ? A.lift : 0);
|
|
660
|
+
if (d > 0) {
|
|
661
|
+
ctx.shadowColor = `rgba(14,10,30,${o.sa ?? 0.5})`; ctx.shadowBlur = 6 + d * 7;
|
|
662
|
+
ctx.shadowOffsetX = 1.5 + d * 2.4; ctx.shadowOffsetY = 2 + d * 3.6;
|
|
663
|
+
ctx.drawImage(L, x0, y0);
|
|
664
|
+
ctx.shadowColor = 'rgba(10,6,20,0.38)'; ctx.shadowBlur = 2.5; ctx.shadowOffsetX = 0.8; ctx.shadowOffsetY = 1.4;
|
|
665
|
+
}
|
|
666
|
+
ctx.drawImage(L, x0, y0);
|
|
667
|
+
ctx.restore();
|
|
668
|
+
};
|
|
669
|
+
|
|
670
|
+
// slow camera push once the diorama is assembled
|
|
671
|
+
const push = 1 + 0.035 * H.ease.inOut(H.clamp((t - 2.2) / 2.8));
|
|
672
|
+
ctx.translate(W * 0.52, H_ * 0.5); ctx.scale(push, push); ctx.translate(-W * 0.52, -H_ * 0.5);
|
|
673
|
+
const sway = Math.sin(t * 1.7) * 0.03;
|
|
674
|
+
const drift = t * 6;
|
|
675
|
+
|
|
676
|
+
// ---------- 1 backboard (sky) ----------
|
|
677
|
+
sheet((c) => { c.fillStyle = '#2b2d5a'; c.fillRect(-20, -20, W + 40, H_ + 40); }, { depth: 0, rim: false });
|
|
678
|
+
|
|
679
|
+
// stars: punched little shapes
|
|
680
|
+
{
|
|
681
|
+
const r = H.rng(31);
|
|
682
|
+
sheet((c) => {
|
|
683
|
+
c.fillStyle = '#f6e7bf';
|
|
684
|
+
for (let i = 0; i < 46; i++) {
|
|
685
|
+
const x = r.range(20, W - 20), y = r.range(20, 420);
|
|
686
|
+
if (Math.hypot(x - 840, y - 300) < 250) continue;
|
|
687
|
+
const s = r.range(3, 7.5), pts = [], k = r.chance(0.6) ? 5 : 4, a0 = r() * TAU;
|
|
688
|
+
for (let j = 0; j < k * 2; j++) { const rr = j % 2 ? s * 0.42 : s; const a = a0 + (j / (k * 2)) * TAU; pts.push([x + Math.cos(a) * rr, y + Math.sin(a) * rr]); }
|
|
689
|
+
c.beginPath(); poly(c, pts); c.fill();
|
|
690
|
+
}
|
|
691
|
+
}, { depth: 0.25, bb: [0, 0, W, 440], hi: 0.3, at: [0.1, 0.4, 'drop', 20] });
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
// ---------- 2 sun rings ----------
|
|
695
|
+
const sun = [[232, '#3c3f78'], [192, '#c75a5c'], [154, '#e9845b'], [116, '#f3ad62'], [78, '#f8d891']];
|
|
696
|
+
sun.forEach(([rad, col], i) => {
|
|
697
|
+
const r = H.rng(50 + i);
|
|
698
|
+
const pts = circPts(r, 840, 300, rad, 14, 0.7);
|
|
699
|
+
sheet((c) => { c.fillStyle = col; c.beginPath(); poly(c, pts); c.fill(); },
|
|
700
|
+
{ depth: 0.55, bb: [840 - rad, 300 - rad, 840 + rad, 300 + rad], at: [0.2 + i * 0.13, 0.5, 'pop'] });
|
|
701
|
+
});
|
|
702
|
+
|
|
703
|
+
// ---------- paper clouds in sky ----------
|
|
704
|
+
const cloud = (seed, cx, cy, s, col, depth, st) => {
|
|
705
|
+
const r = H.rng(seed), parts = [];
|
|
706
|
+
const bumps = [[-1.0, 0, 0.42], [-0.45, -0.25, 0.6], [0.2, -0.35, 0.72], [0.82, -0.1, 0.5], [1.2, 0.05, 0.34]];
|
|
707
|
+
bumps.forEach(([bx, by, br]) => parts.push(circPts(r, cx + bx * s, cy + by * s, br * s, 12, 0.5)));
|
|
708
|
+
sheet((c) => {
|
|
709
|
+
c.fillStyle = col; c.beginPath();
|
|
710
|
+
parts.forEach((p) => poly(c, p));
|
|
711
|
+
poly(c, cutLines(r, [[cx - 1.15 * s, cy], [cx + 1.38 * s, cy], [cx + 1.38 * s, cy + 0.3 * s], [cx - 1.15 * s, cy + 0.3 * s]], 26, 0.5));
|
|
712
|
+
c.fill();
|
|
713
|
+
}, { depth, bb: [cx - 1.6 * s, cy - 1.2 * s, cx + 1.7 * s, cy + 0.5 * s], at: [st, 0.5, 'drop', 50] });
|
|
714
|
+
};
|
|
715
|
+
cloud(61, 1130 - drift * 0.6, 120, 62, '#e9e1d4', 0.8, 1.0);
|
|
716
|
+
cloud(62, 640 + drift, 412, 46, '#dcd3c8', 0.8, 1.15);
|
|
717
|
+
|
|
718
|
+
// ---------- 3 far mountains + snowcaps ----------
|
|
719
|
+
const peaks = [[-30, 520], [70, 420], [150, 470], [260, 360], [380, 455], [470, 410], [560, 480], [690, 430], [790, 490], [930, 380], [1040, 460], [1150, 395], [1310, 470]];
|
|
720
|
+
{
|
|
721
|
+
const r = H.rng(71);
|
|
722
|
+
const top = cutLines(r, peaks, 20, 0.8);
|
|
723
|
+
sheet((c) => { c.fillStyle = '#4c5590'; c.beginPath(); poly(c, [...top, [1310, 740], [-30, 740]]); c.fill(); },
|
|
724
|
+
{ depth: 1.0, bb: [-30, 350, W + 30, H_], at: [0.35, 0.6, 'up', 380] });
|
|
725
|
+
// snowcaps (glued-on pieces with zigzag bottom)
|
|
726
|
+
const caps = [[260, 360], [930, 380], [1150, 395]];
|
|
727
|
+
sheet((c) => {
|
|
728
|
+
c.fillStyle = '#f1ece2';
|
|
729
|
+
caps.forEach(([px, py], ci) => {
|
|
730
|
+
const i = peaks.findIndex((p) => p[0] === px);
|
|
731
|
+
const [lx, ly] = peaks[i - 1], [rx, ry] = peaks[i + 1];
|
|
732
|
+
const k = 0.3;
|
|
733
|
+
const L1 = [px + (lx - px) * k, py + (ly - py) * k], R1 = [px + (rx - px) * k, py + (ry - py) * k];
|
|
734
|
+
const pts = [[px, py - 1], R1];
|
|
735
|
+
const n = 4;
|
|
736
|
+
for (let j = 1; j < n; j++) {
|
|
737
|
+
const u = j / n, x = R1[0] + (L1[0] - R1[0]) * u, y = R1[1] + (L1[1] - R1[1]) * u + (j % 2 ? 9 : -2) + r.range(-2, 2);
|
|
738
|
+
pts.push([x, y]);
|
|
739
|
+
}
|
|
740
|
+
pts.push(L1);
|
|
741
|
+
c.beginPath(); poly(c, pts); c.fill();
|
|
742
|
+
});
|
|
743
|
+
}, { depth: 0.35, bb: [200, 340, 1250, 440], hi: 0.7, at: [0.9, 0.4, 'drop', 40] });
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
// ---------- 4 mid hills ----------
|
|
747
|
+
{
|
|
748
|
+
const r = H.rng(81);
|
|
749
|
+
const f = (x) => 492 + 34 * Math.sin(x * 0.0072 + 1.3) + 18 * Math.sin(x * 0.019 + 0.4) + 10 * Math.sin(x * 0.041);
|
|
750
|
+
const top = ridge(r, -30, W + 30, f, 12, 26, 0.6);
|
|
751
|
+
sheet((c) => { c.fillStyle = '#373f73'; c.beginPath(); poly(c, [...top, [W + 30, 740], [-30, 740]]); c.fill(); },
|
|
752
|
+
{ depth: 1.1, bb: [-30, 430, W + 30, H_], at: [0.5, 0.6, 'up', 320] });
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// ---------- 5 server city: lit sheet behind, skyline with cut-out windows ----------
|
|
756
|
+
{
|
|
757
|
+
const r = H.rng(91), blds = [];
|
|
758
|
+
let x = -10;
|
|
759
|
+
while (x < W + 10) {
|
|
760
|
+
const w = r.range(42, 86), h = r.range(55, 150) * (Math.abs(x - 840) < 160 ? 0.65 : 1);
|
|
761
|
+
blds.push({ x, w, h, ant: r.chance(0.3), seed: r.int(1, 1e6) });
|
|
762
|
+
x += w + r.range(-6, 10);
|
|
763
|
+
}
|
|
764
|
+
const base = 575;
|
|
765
|
+
sheet((c) => {
|
|
766
|
+
c.fillStyle = '#f6c862';
|
|
767
|
+
blds.forEach((b) => c.fillRect(b.x + 4, base - b.h + 6, b.w - 8, b.h));
|
|
768
|
+
}, { depth: 0.2, bb: [-20, 380, W + 20, 600], rim: false, at: [0.65, 0.6, 'up', 260] });
|
|
769
|
+
sheet((c) => {
|
|
770
|
+
c.fillStyle = '#232a55';
|
|
771
|
+
c.beginPath();
|
|
772
|
+
blds.forEach((b) => {
|
|
773
|
+
const rr = H.rng(b.seed);
|
|
774
|
+
const pts = cutLines(rr, [[b.x, base + 20], [b.x, base - b.h], [b.x + b.w, base - b.h], [b.x + b.w, base + 20]], 24, 0.5);
|
|
775
|
+
poly(c, pts);
|
|
776
|
+
if (b.ant) poly(c, [[b.x + b.w * 0.5 - 2, base - b.h + 1], [b.x + b.w * 0.5 - 1, base - b.h - 26], [b.x + b.w * 0.5 + 1, base - b.h - 26], [b.x + b.w * 0.5 + 2, base - b.h + 1]]);
|
|
777
|
+
});
|
|
778
|
+
c.fill();
|
|
779
|
+
// cut-out windows / rack slots
|
|
780
|
+
c.globalCompositeOperation = 'destination-out';
|
|
781
|
+
blds.forEach((b) => {
|
|
782
|
+
const rr = H.rng(b.seed + 3), slots = rr.chance(0.45);
|
|
783
|
+
const cols = Math.max(2, Math.floor((b.w - 12) / 13));
|
|
784
|
+
const gx = (b.w - cols * 13 + 5) / 2;
|
|
785
|
+
for (let yy = base - b.h + 12; yy < base - 14; yy += slots ? 11 : 16) {
|
|
786
|
+
if (slots) { if (rr.chance(0.7)) c.fillRect(b.x + 8, yy, b.w - 16 - rr.range(0, 14), 3.5); continue; }
|
|
787
|
+
for (let i = 0; i < cols; i++) if (rr.chance(0.55)) c.fillRect(b.x + gx + i * 13 + rr.gauss() * 0.4, yy + rr.gauss() * 0.4, 7, 9);
|
|
788
|
+
}
|
|
789
|
+
});
|
|
790
|
+
}, { depth: 0.9, bb: [-20, 380, W + 20, 600], at: [0.65, 0.6, 'up', 260] });
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
// ---------- 6 rocket ----------
|
|
794
|
+
const launch = H.clamp((t - 1.5) / 1.7);
|
|
795
|
+
const rx = 840 + Math.sin(t * 1.3) * 6 * launch, ry = H.lerp(1000, 345, H.ease.out(launch)) - Math.max(0, t - 3.2) * 10; // body centre
|
|
796
|
+
const rocketT = (c) => { c.translate(rx, ry); c.rotate(sway + 0.06); c.scale(1.28, 1.28); };
|
|
797
|
+
const body = (c) => {
|
|
798
|
+
c.moveTo(0, -95);
|
|
799
|
+
c.bezierCurveTo(28, -78, 37, -35, 37, 5); c.bezierCurveTo(37, 40, 34, 70, 30, 88);
|
|
800
|
+
c.lineTo(-30, 88);
|
|
801
|
+
c.bezierCurveTo(-34, 70, -37, 40, -37, 5); c.bezierCurveTo(-37, -35, -28, -78, 0, -95);
|
|
802
|
+
c.closePath();
|
|
803
|
+
};
|
|
804
|
+
const rbb = [rx - 190, ry - 150, rx + 190, ry + 330];
|
|
805
|
+
// smoke column behind flame
|
|
806
|
+
{
|
|
807
|
+
const r = H.rng(101), parts = [];
|
|
808
|
+
const topY = ry + 190, botY = 590;
|
|
809
|
+
for (let i = 0; i < 16; i++) {
|
|
810
|
+
const u = i / 15, y = H.lerp(topY, botY, u), spread = 14 + u * u * 120;
|
|
811
|
+
parts.push(circPts(r, rx + 12 + r.range(-spread, spread) * 0.6, y + r.range(-6, 6), 18 + u * 34 + r.range(-4, 4), 12, 0.5));
|
|
812
|
+
}
|
|
813
|
+
sheet((c) => { c.fillStyle = '#d9d2c9'; c.beginPath(); parts.forEach((p) => poly(c, p)); c.fill(); },
|
|
814
|
+
{ depth: 0.9, bb: [rx - 200, topY - 40, rx + 220, 650] });
|
|
815
|
+
}
|
|
816
|
+
// flames: three stacked cut pieces, tongues flicker with t
|
|
817
|
+
const flame = (w, L, col, seed, depth) => {
|
|
818
|
+
const r = H.rng(seed), fl = (k) => 1 + 0.12 * Math.sin(t * 19 + k * 2.1 + seed);
|
|
819
|
+
const y0 = 84;
|
|
820
|
+
const p = [[w / 2, y0], [w * 0.47, y0 + L * 0.3], [w * 0.3, y0 + L * 0.72 * fl(1)], [w * 0.14, y0 + L * 0.5], [0, y0 + L * fl(2)],
|
|
821
|
+
[-w * 0.15, y0 + L * 0.55], [-w * 0.31, y0 + L * 0.78 * fl(3)], [-w * 0.47, y0 + L * 0.3], [-w / 2, y0]];
|
|
822
|
+
sheet((c) => {
|
|
823
|
+
rocketT(c); c.fillStyle = col; c.beginPath();
|
|
824
|
+
c.moveTo(p[0][0], p[0][1]);
|
|
825
|
+
for (let i = 1; i < p.length; i++) {
|
|
826
|
+
const [ax, ay] = p[i - 1], [bx, by] = p[i];
|
|
827
|
+
const mx = (ax + bx) / 2 + r.gauss() * 1.2, my = (ay + by) / 2 + r.gauss() * 1.2;
|
|
828
|
+
c.quadraticCurveTo(ax + (mx - ax) * 0.4 + (by - ay) * 0.08, ay + (my - ay) * 0.4, mx, my);
|
|
829
|
+
c.lineTo(bx, by);
|
|
830
|
+
}
|
|
831
|
+
c.closePath(); c.fill();
|
|
832
|
+
}, { depth, bb: rbb });
|
|
833
|
+
};
|
|
834
|
+
flame(64, 150, '#e4513a', 111, 0.6);
|
|
835
|
+
flame(46, 112, '#f39a3c', 112, 0.45);
|
|
836
|
+
flame(26, 72, '#f9db78', 113, 0.35);
|
|
837
|
+
// back fins
|
|
838
|
+
sheet((c) => {
|
|
839
|
+
rocketT(c); c.fillStyle = '#b5372f'; c.beginPath();
|
|
840
|
+
c.moveTo(-28, 22); c.bezierCurveTo(-58, 40, -68, 76, -64, 112); c.lineTo(-24, 84); c.closePath();
|
|
841
|
+
c.moveTo(28, 22); c.bezierCurveTo(58, 40, 68, 76, 64, 112); c.lineTo(24, 84); c.closePath();
|
|
842
|
+
c.fill();
|
|
843
|
+
}, { depth: 0.5, bb: rbb });
|
|
844
|
+
// body
|
|
845
|
+
sheet((c) => { rocketT(c); c.fillStyle = '#f1e8d6'; c.beginPath(); body(c); c.fill(); }, { depth: 0.7, bb: rbb });
|
|
846
|
+
// nose cone + band + centre fin (glued on top)
|
|
847
|
+
sheet((c) => {
|
|
848
|
+
rocketT(c);
|
|
849
|
+
c.save(); c.beginPath(); body(c); c.clip();
|
|
850
|
+
c.fillStyle = '#d9473b';
|
|
851
|
+
c.beginPath(); c.moveTo(-50, -110); c.lineTo(50, -110); c.lineTo(50, -50); c.quadraticCurveTo(0, -38, -50, -50); c.closePath(); c.fill();
|
|
852
|
+
c.fillStyle = '#2f7f86';
|
|
853
|
+
c.beginPath(); c.moveTo(-50, 44); c.quadraticCurveTo(0, 54, 50, 44); c.lineTo(50, 60); c.quadraticCurveTo(0, 70, -50, 60); c.closePath(); c.fill();
|
|
854
|
+
c.restore();
|
|
855
|
+
c.fillStyle = '#c63f35';
|
|
856
|
+
c.beginPath(); c.moveTo(-6, 58); c.lineTo(6, 58); c.lineTo(8, 116); c.lineTo(-8, 116); c.closePath(); c.fill();
|
|
857
|
+
// window frame
|
|
858
|
+
c.fillStyle = '#9c968c'; c.beginPath(); c.arc(0, -8, 23, 0, TAU); c.fill();
|
|
859
|
+
}, { depth: 0.35, bb: rbb });
|
|
860
|
+
sheet((c) => {
|
|
861
|
+
rocketT(c);
|
|
862
|
+
c.fillStyle = '#26566a'; c.beginPath(); c.arc(0, -8, 15.5, 0, TAU); c.fill();
|
|
863
|
+
}, { depth: 0.3, bb: rbb, hi: 0.4 });
|
|
864
|
+
sheet((c) => {
|
|
865
|
+
rocketT(c);
|
|
866
|
+
c.fillStyle = '#e9f1ea'; c.beginPath(); c.arc(-4, -12, 7.5, Math.PI * 0.95, Math.PI * 1.75); c.arc(-1, -9, 6.5, Math.PI * 1.6, Math.PI * 1.05, true); c.closePath(); c.fill();
|
|
867
|
+
}, { depth: 0.2, bb: rbb, hi: 0.2 });
|
|
868
|
+
|
|
869
|
+
// ---------- 7 launch smoke puffs at ground ----------
|
|
870
|
+
{
|
|
871
|
+
const r = H.rng(121), parts = [];
|
|
872
|
+
for (let i = 0; i < 13; i++) {
|
|
873
|
+
const u = (i / 12) * 2 - 1, x = rx + 10 + u * 230 + r.range(-12, 12), y = 588 - (1 - Math.abs(u)) * 22 + r.range(-6, 6);
|
|
874
|
+
parts.push(circPts(r, x, y, 26 + (1 - Math.abs(u)) * 22 + r.range(-5, 5), 12, 0.5));
|
|
875
|
+
}
|
|
876
|
+
sheet((c) => { c.fillStyle = '#f4efe6'; c.beginPath(); parts.forEach((p) => poly(c, p)); c.fill(); },
|
|
877
|
+
{ depth: 1.0, bb: [rx - 300, 500, rx + 320, 650], at: [1.65, 0.5, 'pop'] });
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// ---------- 8 torn hills: white fibrous core ----------
|
|
881
|
+
{
|
|
882
|
+
const nz = H.makeNoise(131), r = H.rng(132);
|
|
883
|
+
const f = (x) => 606 + 22 * Math.sin(x * 0.0058 + 2.2) + 11 * Math.sin(x * 0.017 + 1);
|
|
884
|
+
const torn = [], core = [];
|
|
885
|
+
for (let x = -30; x <= W + 30; x += 2.5) {
|
|
886
|
+
const n = nz.fbm2(x * 0.03, 0.5, 4) * 5 + r.gauss() * 0.6;
|
|
887
|
+
const y = f(x) + n;
|
|
888
|
+
torn.push([x, y + 3.5 + Math.abs(nz.n2(x * 0.045, 3.1)) * 8 + Math.abs(nz.n2(x * 0.3, 7.7)) * 2]);
|
|
889
|
+
core.push([x, y]);
|
|
890
|
+
}
|
|
891
|
+
sheet((c) => {
|
|
892
|
+
c.fillStyle = '#f5f0e5'; c.beginPath(); poly(c, [...core, [W + 30, 740], [-30, 740]]); c.fill();
|
|
893
|
+
// protruding fibres along the torn core
|
|
894
|
+
c.strokeStyle = 'rgba(248,244,236,0.95)'; c.lineCap = 'round';
|
|
895
|
+
for (let i = 0; i < core.length; i += 1) {
|
|
896
|
+
if (!r.chance(0.55)) continue;
|
|
897
|
+
const [x, y] = core[i], a = -Math.PI / 2 + r.range(-0.9, 0.9), l = r.range(1.5, 5.5);
|
|
898
|
+
c.lineWidth = r.range(0.4, 0.9);
|
|
899
|
+
c.beginPath(); c.moveTo(x, y + 1); c.lineTo(x + Math.cos(a) * l, y + Math.sin(a) * l); c.stroke();
|
|
900
|
+
}
|
|
901
|
+
c.fillStyle = '#2f6b66'; c.beginPath(); poly(c, [...torn, [W + 30, 740], [-30, 740]]); c.fill();
|
|
902
|
+
}, { depth: 1.3, bb: [-30, 560, W + 30, H_], at: [0.8, 0.6, 'up', 200] });
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
// ---------- 8b paper pines on the hills ----------
|
|
906
|
+
{
|
|
907
|
+
const r = H.rng(161);
|
|
908
|
+
const pine = (x, base, h, col, seed, depth, st) => {
|
|
909
|
+
const rr = H.rng(seed), w = h * 0.52, tiers = 3, pieces = [];
|
|
910
|
+
for (let k = 0; k < tiers; k++) {
|
|
911
|
+
const ty = base - h * 0.18 - k * h * 0.26, tw = w * (1 - k * 0.24), th = h * 0.42;
|
|
912
|
+
const pts = [[x, ty - th]];
|
|
913
|
+
const n = 4;
|
|
914
|
+
for (let j = 0; j <= n; j++) { const u = j / n; pts.push([x + tw / 2 - u * tw, ty + (j % 2 ? -5 : 0) + rr.gauss() * 0.6]); }
|
|
915
|
+
pieces.push(cutLines(rr, [...pts, [x, ty - th]], 18, 0.5));
|
|
916
|
+
}
|
|
917
|
+
sheet((c) => {
|
|
918
|
+
c.fillStyle = '#4a3326'; c.fillRect(x - 4, base - h * 0.25, 8, h * 0.3);
|
|
919
|
+
c.fillStyle = col; c.beginPath(); pieces.forEach((p) => poly(c, p)); c.fill();
|
|
920
|
+
}, { depth, bb: [x - w, base - h * 1.1, x + w, base + 10], at: [st, 0.45, 'pop'] });
|
|
921
|
+
};
|
|
922
|
+
pine(70, 640, 150, '#24594f', 162, 1.1, 1.15);
|
|
923
|
+
pine(150, 628, 108, '#2e6c5b', 163, 0.9, 1.25);
|
|
924
|
+
pine(1215, 640, 136, '#24594f', 164, 1.1, 1.2);
|
|
925
|
+
pine(1140, 636, 92, '#2e6c5b', 165, 0.9, 1.3);
|
|
926
|
+
}
|
|
927
|
+
|
|
928
|
+
// ---------- 9 foreground with grass snips ----------
|
|
929
|
+
{
|
|
930
|
+
const r = H.rng(141);
|
|
931
|
+
const f = (x) => 664 + 20 * Math.sin(x * 0.0045 + 0.4) + 8 * Math.sin(x * 0.013);
|
|
932
|
+
const pts = [];
|
|
933
|
+
let x = -30;
|
|
934
|
+
while (x < W + 30) {
|
|
935
|
+
if (r.chance(0.22)) {
|
|
936
|
+
const n = r.int(2, 4);
|
|
937
|
+
for (let k = 0; k < n; k++) {
|
|
938
|
+
const bx = x + k * 7, by = f(bx);
|
|
939
|
+
pts.push([bx, by], [bx + 2 + r.range(-3, 4), by - r.range(16, 36)], [bx + 5, by]);
|
|
940
|
+
}
|
|
941
|
+
x += n * 7 + 4;
|
|
942
|
+
} else { pts.push([x, f(x) + r.gauss() * 0.6]); x += r.range(14, 30); }
|
|
943
|
+
}
|
|
944
|
+
sheet((c) => { c.fillStyle = '#163a3a'; c.beginPath(); poly(c, [...pts, [W + 30, 740], [-30, 740]]); c.fill(); },
|
|
945
|
+
{ depth: 1.6, bb: [-30, 610, W + 30, H_], at: [0.95, 0.6, 'up', 160] });
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
// ---------- 9b nearest layer: scalloped paper bushes in the corners ----------
|
|
949
|
+
{
|
|
950
|
+
const bush = (seed, cx, cy, s, col, depth, st) => {
|
|
951
|
+
const r = H.rng(seed), parts = [];
|
|
952
|
+
for (let i = 0; i < 7; i++) {
|
|
953
|
+
const a = Math.PI + (i / 6) * Math.PI;
|
|
954
|
+
parts.push(circPts(r, cx + Math.cos(a) * s * 1.1, cy + Math.sin(a) * s * 0.75, s * r.range(0.42, 0.58), 12, 0.5));
|
|
955
|
+
}
|
|
956
|
+
parts.push(circPts(r, cx, cy, s * 0.9, 14, 0.5));
|
|
957
|
+
sheet((c) => { c.fillStyle = col; c.beginPath(); parts.forEach((p) => poly(c, p)); c.fill(); },
|
|
958
|
+
{ depth, bb: [cx - s * 1.8, cy - s * 1.4, cx + s * 1.8, cy + s * 1.2], at: [st, 0.5, 'up', 180] });
|
|
959
|
+
};
|
|
960
|
+
bush(171, 1210, 740, 120, '#11302f', 2.2, 1.1);
|
|
961
|
+
bush(172, 1040, 760, 70, '#1d4a45', 1.8, 1.2);
|
|
962
|
+
bush(173, 30, 735, 105, '#11302f', 2.2, 1.05);
|
|
963
|
+
}
|
|
964
|
+
|
|
965
|
+
// ---------- 10 title: cut letters on a cut-around backing sheet ----------
|
|
966
|
+
{
|
|
967
|
+
const r = H.rng(151);
|
|
968
|
+
const lines = [{ s: 'READY FOR', f: '400 50px "Archivo Black"', y: 132, x: 86 }, { s: 'LAUNCH', f: '400 112px "Archivo Black"', y: 246, x: 78 }];
|
|
969
|
+
const glyphs = [];
|
|
970
|
+
ctx.save();
|
|
971
|
+
lines.forEach((ln) => {
|
|
972
|
+
ctx.font = ln.f; let x = ln.x;
|
|
973
|
+
const sp = ln.s === 'LAUNCH' ? 4 : 6;
|
|
974
|
+
for (const ch of ln.s) {
|
|
975
|
+
const w = ctx.measureText(ch).width;
|
|
976
|
+
if (ch !== ' ') glyphs.push({ ch, f: ln.f, x, y: ln.y + r.range(-3, 3), rot: r.range(-0.06, 0.06), w });
|
|
977
|
+
x += w + sp;
|
|
978
|
+
}
|
|
979
|
+
});
|
|
980
|
+
ctx.restore();
|
|
981
|
+
const cols = ['#e8604a', '#f2b545', '#58b0a6', '#f2b545', '#e8604a', '#58b0a6', '#e8604a', '#f2b545'];
|
|
982
|
+
const tilt = (c) => { c.translate(330, 190); c.rotate(-0.045); c.translate(-330, -190); };
|
|
983
|
+
const drawG = (c, g, fn) => { c.save(); c.translate(g.x + g.w / 2, g.y); c.rotate(g.rot); c.font = g.f; fn(c, -g.w / 2); c.restore(); };
|
|
984
|
+
sheet((c) => {
|
|
985
|
+
tilt(c); c.fillStyle = '#f3ecdc'; c.strokeStyle = '#f3ecdc'; c.lineJoin = 'round'; c.lineWidth = 26;
|
|
986
|
+
glyphs.forEach((g) => drawG(c, g, (cc, x) => { cc.strokeText(g.ch, x, 0); cc.fillText(g.ch, x, 0); }));
|
|
987
|
+
// fill gaps so backing reads as one cut piece
|
|
988
|
+
c.lineWidth = 34; c.beginPath(); c.moveTo(100, 115); c.lineTo(395, 115); c.moveTo(95, 210); c.lineTo(560, 210); c.stroke();
|
|
989
|
+
}, { depth: 1.5, bb: [40, 50, 640, 290], at: [1.7, 0.45, 'drop', 70] });
|
|
990
|
+
glyphs.forEach((g, i) => {
|
|
991
|
+
const big = g.f.includes('112');
|
|
992
|
+
sheet((c) => { tilt(c); drawG(c, g, (cc, x) => { cc.fillStyle = cols[i % cols.length]; cc.fillText(g.ch, x, 0); }); },
|
|
993
|
+
{ depth: 0.6, bb: [g.x - 24, g.y - (big ? 110 : 55), g.x + g.w + 24, g.y + 30], at: [1.95 + i * 0.06, 0.38, 'drop', big ? 60 : 40] });
|
|
994
|
+
});
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// ---------- lighting / photo finish ----------
|
|
998
|
+
ctx.save();
|
|
999
|
+
ctx.globalCompositeOperation = 'soft-light';
|
|
1000
|
+
const lg = ctx.createRadialGradient(260, 60, 40, 420, 200, 1100);
|
|
1001
|
+
lg.addColorStop(0, 'rgba(255,236,200,0.9)'); lg.addColorStop(0.5, 'rgba(255,230,200,0.25)'); lg.addColorStop(1, 'rgba(0,0,20,0.6)');
|
|
1002
|
+
ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_);
|
|
1003
|
+
ctx.restore();
|
|
1004
|
+
H.vignette(ctx, W, H_, 0.42, '10,6,24');
|
|
1005
|
+
H.grain(ctx, W, H_, 0.035, 9 + Math.floor(t * 12));
|
|
1006
|
+
},
|
|
1007
|
+
});
|
|
1008
|
+
}
|