@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,664 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------- Lichtenstein pop-art comic panel ----------
|
|
3
|
+
const C = { red: '#e2231a', blue: '#1f5fbf', yellow: '#ffd51f', black: '#141210', white: '#fbf6ea' };
|
|
4
|
+
|
|
5
|
+
// Ben-Day dot field inside the current clip (staggered grid)
|
|
6
|
+
const benday = (x, color, s, r, x0, y0, x1, y1, seed, rgain) => {
|
|
7
|
+
const rr = H.rng(seed);
|
|
8
|
+
x.fillStyle = color; x.beginPath();
|
|
9
|
+
for (let j = 0, y = y0; y < y1 + s; j++, y += s * 0.5) {
|
|
10
|
+
const off = (j & 1) ? s * 0.5 : 0;
|
|
11
|
+
for (let xx = x0 + off; xx < x1 + s; xx += s) {
|
|
12
|
+
const g = rgain ? rgain(xx, y) : 1;
|
|
13
|
+
const rad = r * g * (1 + (rr() - 0.5) * 0.08);
|
|
14
|
+
if (rad < 0.4) continue;
|
|
15
|
+
const px = xx + (rr() - 0.5) * 0.6, py = y + (rr() - 0.5) * 0.6;
|
|
16
|
+
x.moveTo(px + rad, py); x.arc(px, py, rad, 0, Math.PI * 2);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
x.fill();
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// ---------- robot head geometry (local coords) ----------
|
|
23
|
+
const headPath = (x) => {
|
|
24
|
+
x.beginPath(); x.moveTo(-238, -70);
|
|
25
|
+
x.bezierCurveTo(-240, -255, -125, -325, 0, -325);
|
|
26
|
+
x.bezierCurveTo(125, -325, 240, -255, 238, -70);
|
|
27
|
+
x.lineTo(228, 170); x.quadraticCurveTo(218, 262, 120, 292);
|
|
28
|
+
x.lineTo(-120, 292); x.quadraticCurveTo(-218, 262, -228, 170); x.closePath();
|
|
29
|
+
};
|
|
30
|
+
const facePath = (x) => {
|
|
31
|
+
x.beginPath(); x.moveTo(-180, -150);
|
|
32
|
+
x.quadraticCurveTo(-182, -205, -120, -208); x.lineTo(120, -208); x.quadraticCurveTo(182, -205, 180, -150);
|
|
33
|
+
x.lineTo(172, 150); x.quadraticCurveTo(165, 240, 80, 252); x.lineTo(-80, 252); x.quadraticCurveTo(-165, 240, -172, 150); x.closePath();
|
|
34
|
+
};
|
|
35
|
+
const eye = (x, cx, cy, sgn) => { x.beginPath(); x.ellipse(cx, cy, 64, 50, sgn * 0.08, 0, Math.PI * 2); };
|
|
36
|
+
const tearPath = (x, cx, cy, s) => {
|
|
37
|
+
x.beginPath(); x.moveTo(cx, cy - 46 * s);
|
|
38
|
+
x.bezierCurveTo(cx + 8 * s, cy - 20 * s, cx + 26 * s, cy, cx + 26 * s, cy + 16 * s);
|
|
39
|
+
x.arc(cx, cy + 16 * s, 26 * s, 0, Math.PI);
|
|
40
|
+
x.bezierCurveTo(cx - 26 * s, cy, cx - 8 * s, cy - 20 * s, cx, cy - 46 * s); x.closePath();
|
|
41
|
+
};
|
|
42
|
+
const mouthPath = (x) => {
|
|
43
|
+
x.beginPath(); x.moveTo(-78, 186); x.quadraticCurveTo(0, 150, 78, 186);
|
|
44
|
+
x.lineTo(70, 216); x.quadraticCurveTo(0, 186, -70, 216); x.closePath();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const bubblePath = (x) => {
|
|
48
|
+
x.beginPath();
|
|
49
|
+
x.ellipse(330, 318, 262, 150, -0.04, 0.78, Math.PI * 2 + 0.5);
|
|
50
|
+
x.lineTo(588, 600); x.closePath();
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// ================= EXPLAINER: a comic page read panel by panel =================
|
|
54
|
+
const PE = H.ease, pcl = H.clamp, pseg = (t, a, b) => pcl((t - a) / (b - a));
|
|
55
|
+
const PPW = 1200, PPH = 660, PG = 44;
|
|
56
|
+
const pX = (i) => i * (PPW + PG);
|
|
57
|
+
|
|
58
|
+
// robot head drawn on colour (c) + key (k) plates; mood: think | eat | sad | happy
|
|
59
|
+
const robot = (c, k, cx, cy, s, rot, mood, t, opt = {}) => {
|
|
60
|
+
for (const [x, isKey] of [[c, false], [k, true]]) {
|
|
61
|
+
x.save(); x.translate(cx, cy); x.rotate(rot); x.scale(s, s);
|
|
62
|
+
x.lineJoin = 'round'; x.lineCap = 'round';
|
|
63
|
+
if (!isKey) {
|
|
64
|
+
x.fillStyle = C.yellow; headPath(x); x.fill();
|
|
65
|
+
// orange Ben-Day shade band down the shell's right side (as in the hero still)
|
|
66
|
+
x.save(); headPath(x); x.clip();
|
|
67
|
+
x.beginPath(); x.moveTo(160, -320); x.bezierCurveTo(150, -150, 150, 60, 170, 320); x.lineTo(320, 320); x.lineTo(320, -320); x.closePath(); x.clip();
|
|
68
|
+
benday(x, C.red, 12, 3.3, 100, -340, 320, 340, 12);
|
|
69
|
+
x.restore();
|
|
70
|
+
x.save(); facePath(x); x.clip(); x.fillStyle = C.white; x.fillRect(-300, -300, 600, 600); benday(x, C.red, 16, 3.7, -260, -260, 260, 300, 8); x.restore();
|
|
71
|
+
x.fillStyle = C.white; x.beginPath(); x.moveTo(-226, -60); x.bezierCurveTo(-226, -200, -160, -270, -90, -300); x.bezierCurveTo(-150, -250, -196, -170, -200, -60); x.closePath(); x.fill();
|
|
72
|
+
for (const [ex, sg] of [[-88, -1], [92, 1]]) { x.fillStyle = C.white; eye(x, ex, -30, sg); x.fill(); x.save(); eye(x, ex, -30, sg); x.clip(); x.fillStyle = C.blue; x.beginPath(); x.arc(ex + (opt.look || [8, -8])[0], -30 + (opt.look || [8, -8])[1], 34, 0, 7); x.fill(); x.restore(); }
|
|
73
|
+
// blue-dot eye-shadow lids (Lichtenstein make-up, matches the hero)
|
|
74
|
+
for (const [ex, sg] of [[-88, -1], [92, 1]]) {
|
|
75
|
+
x.save(); x.beginPath(); x.ellipse(ex, -48, 74, 40, sg * 0.1, Math.PI, Math.PI * 2); x.closePath(); x.clip();
|
|
76
|
+
x.fillStyle = C.white; x.fillRect(ex - 90, -120, 180, 70);
|
|
77
|
+
benday(x, C.blue, 10, 3.4, ex - 100, -130, ex + 100, -40, 21 + ex);
|
|
78
|
+
x.restore();
|
|
79
|
+
}
|
|
80
|
+
x.fillStyle = C.red; x.beginPath(); x.arc(0, -392, 30, 0, 7); x.fill();
|
|
81
|
+
for (const sx of [-1, 1]) { x.beginPath(); x.arc(sx * 262, -10, 46, 0, 7); x.fill(); }
|
|
82
|
+
} else {
|
|
83
|
+
x.strokeStyle = C.black; x.fillStyle = C.black;
|
|
84
|
+
x.lineWidth = 12; x.beginPath(); x.moveTo(0, -325); x.lineTo(0, -362); x.stroke();
|
|
85
|
+
x.lineWidth = 9; x.beginPath(); x.arc(0, -392, 30, 0, 7); x.stroke();
|
|
86
|
+
x.fillStyle = C.white; x.beginPath(); x.ellipse(-10, -402, 9, 6, -0.6, 0, 7); x.fill(); x.fillStyle = C.black;
|
|
87
|
+
for (const sx of [-1, 1]) {
|
|
88
|
+
x.lineWidth = 9; x.beginPath(); x.arc(sx * 262, -10, 46, 0, 7); x.stroke();
|
|
89
|
+
x.lineWidth = 5; x.beginPath(); x.arc(sx * 262, -10, 22, 0, 7); x.stroke();
|
|
90
|
+
const sp = opt.spin || 0; x.lineWidth = 8; x.beginPath(); x.moveTo(sx * 262 + Math.cos(sp) * 30, -10 + Math.sin(sp) * 30); x.lineTo(sx * 262 - Math.cos(sp) * 30, -10 - Math.sin(sp) * 30); x.stroke();
|
|
91
|
+
}
|
|
92
|
+
x.save(); headPath(x); x.clip();
|
|
93
|
+
x.beginPath(); x.moveTo(252, -300); x.bezierCurveTo(222, -170, 214, 40, 226, 300); x.lineTo(320, 300); x.lineTo(320, -300); x.closePath(); x.fill();
|
|
94
|
+
x.beginPath(); x.moveTo(-150, -296); x.bezierCurveTo(-60, -318, 70, -318, 150, -290); x.bezierCurveTo(70, -300, -50, -298, -150, -282); x.closePath(); x.fill();
|
|
95
|
+
x.restore();
|
|
96
|
+
// fat brush outlines, doubled with a slight offset like the hero's inking
|
|
97
|
+
x.lineWidth = 13; headPath(x); x.stroke();
|
|
98
|
+
x.save(); x.translate(5, 4); x.lineWidth = 10; headPath(x); x.stroke(); x.restore();
|
|
99
|
+
x.lineWidth = 9; facePath(x); x.stroke();
|
|
100
|
+
x.save(); x.translate(-4, 3); x.lineWidth = 6; facePath(x); x.stroke(); x.restore();
|
|
101
|
+
for (const [rx, ry] of [[-208, -150], [-206, 100], [204, -150], [200, 100], [-60, -270], [60, -270]]) { x.beginPath(); x.arc(rx, ry, 7, 0, 7); x.fill(); }
|
|
102
|
+
for (const [ex, sg] of [[-88, -1], [92, 1]]) {
|
|
103
|
+
const lk = opt.look || [8, -8];
|
|
104
|
+
x.lineWidth = 8; eye(x, ex, -30, sg); x.stroke();
|
|
105
|
+
x.save(); eye(x, ex, -30, sg); x.clip(); x.beginPath(); x.arc(ex + lk[0], -30 + lk[1], 15, 0, 7); x.fill();
|
|
106
|
+
x.fillStyle = C.white; x.beginPath(); x.ellipse(ex + lk[0] - 14, -30 + lk[1] - 16, 10, 7, -0.5, 0, 7); x.fill(); x.fillStyle = C.black; x.restore();
|
|
107
|
+
}
|
|
108
|
+
// brows by mood
|
|
109
|
+
const browY = mood === 'happy' ? -128 : -118;
|
|
110
|
+
for (const sg of [-1, 1]) {
|
|
111
|
+
x.lineWidth = 14; x.beginPath();
|
|
112
|
+
if (mood === 'sad') { x.moveTo(sg * 165, -100); x.quadraticCurveTo(sg * 100, -150, sg * 36, -150); }
|
|
113
|
+
else if (mood === 'think' && sg > 0) { x.moveTo(36, -150); x.quadraticCurveTo(100, -175, 165, -140); }
|
|
114
|
+
else { x.moveTo(sg * 160, browY + 10); x.quadraticCurveTo(sg * 100, browY - 18, sg * 40, browY); }
|
|
115
|
+
x.stroke();
|
|
116
|
+
}
|
|
117
|
+
// mouth
|
|
118
|
+
x.lineWidth = 12;
|
|
119
|
+
if (mood === 'happy') { x.beginPath(); x.moveTo(-80, 160); x.quadraticCurveTo(0, 235, 80, 160); x.quadraticCurveTo(0, 200, -80, 160); x.fill(); x.stroke(); }
|
|
120
|
+
else if (mood === 'eat') { const o = 30 + 16 * Math.abs(Math.sin(t * 9)); x.beginPath(); x.ellipse(0, 180, 60, o, 0, 0, 7); x.fill(); }
|
|
121
|
+
else if (mood === 'sad') { mouthPath(x); x.fill(); }
|
|
122
|
+
else { x.beginPath(); x.moveTo(-60, 185); x.lineTo(50, 175); x.stroke(); }
|
|
123
|
+
}
|
|
124
|
+
x.restore();
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// comic devices
|
|
129
|
+
const box = (c, k, x0, y0, w, h, fill, txt, size, t0, t) => {
|
|
130
|
+
if (t < t0) return;
|
|
131
|
+
const u = PE.back(pseg(t, t0, t0 + 0.25)), dx = (1 - u) * -w;
|
|
132
|
+
c.fillStyle = fill; c.fillRect(x0 + dx, y0, w, h);
|
|
133
|
+
k.lineWidth = 6; k.strokeStyle = C.black; k.strokeRect(x0 + dx, y0, w, h);
|
|
134
|
+
k.fillStyle = C.black; k.font = `400 ${size}px "Bangers"`; k.letterSpacing = '1.5px'; k.textAlign = 'left'; k.textBaseline = 'middle';
|
|
135
|
+
k.fillText(txt, x0 + dx + 16, y0 + h / 2 + 2); k.letterSpacing = '0px';
|
|
136
|
+
};
|
|
137
|
+
const balloon = (c, k, cx, cy, rx, ry, tx, ty, sc) => {
|
|
138
|
+
if (sc <= 0) return;
|
|
139
|
+
const path = (x) => { x.beginPath(); x.ellipse(cx, cy, rx, ry, 0, 0, 7); };
|
|
140
|
+
const tail = (x) => { const a = Math.atan2(ty - cy, tx - cx); x.beginPath(); x.moveTo(cx + Math.cos(a - 0.22) * rx * 0.9, cy + Math.sin(a - 0.22) * ry * 0.9); x.lineTo(tx, ty); x.lineTo(cx + Math.cos(a + 0.22) * rx * 0.9, cy + Math.sin(a + 0.22) * ry * 0.9); x.closePath(); };
|
|
141
|
+
for (const x of [c, k]) { x.save(); x.translate(cx, cy); x.scale(sc, sc); x.translate(-cx, -cy); }
|
|
142
|
+
k.lineWidth = 8; k.strokeStyle = C.black; tail(k); k.stroke(); path(k); k.stroke();
|
|
143
|
+
c.fillStyle = C.white; path(c); c.fill(); tail(c); c.fill();
|
|
144
|
+
k.fillStyle = '#fff'; k.save(); k.globalCompositeOperation = 'destination-out'; path(k); k.fill(); k.restore();
|
|
145
|
+
k.fillStyle = C.black; k.lineWidth = 8;
|
|
146
|
+
// re-stroke ellipse (tail join hidden by fill)
|
|
147
|
+
path(k); k.stroke();
|
|
148
|
+
k.save(); k.globalCompositeOperation = 'destination-out'; k.beginPath(); k.ellipse(cx, cy, rx - 4, ry - 4, 0, 0, 7); k.fill(); k.restore();
|
|
149
|
+
for (const x of [c, k]) x.restore();
|
|
150
|
+
};
|
|
151
|
+
const burstPath = (x, cx, cy, r, n, seed) => { const rr = H.rng(seed); x.beginPath(); for (let i = 0; i < n * 2; i++) { const a = (i / (n * 2)) * Math.PI * 2, R = i % 2 ? r * rr.range(0.6, 0.72) : r * rr.range(0.92, 1.12); x.lineTo(cx + Math.cos(a) * R, cy + Math.sin(a) * R); } x.closePath(); };
|
|
152
|
+
const burst = (c, k, cx, cy, r, sc, fill, seed) => {
|
|
153
|
+
if (sc <= 0) return;
|
|
154
|
+
c.fillStyle = fill; burstPath(c, cx, cy, r * sc, 14, seed); c.fill();
|
|
155
|
+
k.lineWidth = 8; k.strokeStyle = C.black; k.lineJoin = 'miter'; burstPath(k, cx, cy, r * sc, 14, seed); k.stroke(); k.lineJoin = 'round';
|
|
156
|
+
};
|
|
157
|
+
const say = (k, txt, size, x, y, sc = 1, align = 'center', rot = 0) => {
|
|
158
|
+
if (sc <= 0) return;
|
|
159
|
+
k.save(); k.translate(x, y); k.rotate(rot); k.scale(sc, sc); k.fillStyle = C.black; k.font = `400 ${size}px "Bangers"`; k.letterSpacing = '2px'; k.textAlign = align; k.textBaseline = 'middle'; k.fillText(txt, 0, 0); k.letterSpacing = '0px'; k.restore();
|
|
160
|
+
};
|
|
161
|
+
// lettering knocked out of whatever is behind it: a white halo on both plates (breaks dots, arrow fills and key lines)
|
|
162
|
+
const sayK = (c, k, txt, size, x, y, sc = 1, align = 'center', rot = 0, halo = 12) => {
|
|
163
|
+
if (sc <= 0) return;
|
|
164
|
+
for (const x_ of [c, k]) {
|
|
165
|
+
x_.save(); x_.translate(x, y); x_.rotate(rot); x_.scale(sc, sc); x_.font = `400 ${size}px "Bangers"`; x_.letterSpacing = '2px'; x_.textAlign = align; x_.textBaseline = 'middle';
|
|
166
|
+
x_.lineJoin = 'round'; x_.lineWidth = halo; x_.strokeStyle = C.white; x_.strokeText(txt, 0, 0); x_.letterSpacing = '0px'; x_.restore();
|
|
167
|
+
}
|
|
168
|
+
say(k, txt, size, x, y, sc, align, rot);
|
|
169
|
+
};
|
|
170
|
+
const popIn = (t, t0) => (t < t0 ? 0 : 1 + 0.3 * (1 - PE.out(pseg(t, t0, t0 + 0.16))) * (t - t0 < 0.16 ? 1 : 0));
|
|
171
|
+
// fat comic arrow along a quadratic, drawn on with progress u (yellow fill, black outline)
|
|
172
|
+
const fatArrow = (c, k, p0, cp, p1, u, w, fill) => {
|
|
173
|
+
if (u <= 0) return;
|
|
174
|
+
const q = (s) => [(1 - s) * (1 - s) * p0[0] + 2 * (1 - s) * s * cp[0] + s * s * p1[0], (1 - s) * (1 - s) * p0[1] + 2 * (1 - s) * s * cp[1] + s * s * p1[1]];
|
|
175
|
+
const N = 24, L = [], R = [], end = u * 0.86;
|
|
176
|
+
for (let i = 0; i <= N; i++) { const s = (i / N) * end, a = q(s), b = q(Math.min(1, s + 0.01)); let dx = b[0] - a[0], dy = b[1] - a[1]; const l = Math.hypot(dx, dy) || 1; dx /= l; dy /= l; const ww = w * (0.6 + 0.4 * (i / N)) / 2; L.push([a[0] - dy * ww, a[1] + dx * ww]); R.push([a[0] + dy * ww, a[1] - dx * ww]); }
|
|
177
|
+
const e = q(end), tip = q(Math.min(1, end + 0.14)); let dx = tip[0] - e[0], dy = tip[1] - e[1]; const l = Math.hypot(dx, dy) || 1; dx /= l; dy /= l;
|
|
178
|
+
const hw = w * 1.1;
|
|
179
|
+
const path = (x) => { x.beginPath(); x.moveTo(L[0][0], L[0][1]); L.forEach((p) => x.lineTo(p[0], p[1])); x.lineTo(e[0] - dy * hw, e[1] + dx * hw); x.lineTo(tip[0], tip[1]); x.lineTo(e[0] + dy * hw, e[1] - dx * hw); for (let i = R.length - 1; i >= 0; i--) x.lineTo(R[i][0], R[i][1]); x.closePath(); };
|
|
180
|
+
c.fillStyle = fill; path(c); c.fill(); k.lineWidth = 6; k.strokeStyle = C.black; k.lineJoin = 'round'; path(k); k.stroke();
|
|
181
|
+
return q(0.5);
|
|
182
|
+
};
|
|
183
|
+
// labelled training photo (comic style)
|
|
184
|
+
const photo = (c, k, cx, cy, s, kind, label, rot) => {
|
|
185
|
+
for (const x of [c, k]) { x.save(); x.translate(cx, cy); x.rotate(rot); x.scale(s / 100, s / 100); }
|
|
186
|
+
c.fillStyle = C.white; c.fillRect(-50, -60, 100, 120); c.fillStyle = C.yellow; c.fillRect(-50, 30, 100, 30);
|
|
187
|
+
benday(c, C.blue, 9, 2.4, -46, -56, 46, 24, 4);
|
|
188
|
+
c.fillStyle = C.white; c.beginPath(); c.arc(0, -16, 30, 0, 7); c.fill();
|
|
189
|
+
k.lineWidth = 4; k.strokeStyle = C.black; k.fillStyle = C.black; k.strokeRect(-50, -60, 100, 120); k.beginPath(); k.moveTo(-50, 30); k.lineTo(50, 30); k.stroke();
|
|
190
|
+
k.lineWidth = 3.5; k.beginPath(); k.arc(0, -16, 28, 0, 7); k.stroke();
|
|
191
|
+
if (kind === 'cat') { k.beginPath(); k.moveTo(-24, -28); k.lineTo(-22, -54); k.lineTo(-6, -42); k.moveTo(24, -28); k.lineTo(22, -54); k.lineTo(6, -42); k.stroke(); k.beginPath(); for (const sg of [-1, 1]) { k.moveTo(sg * 8, -8); k.lineTo(sg * 38, -10); k.moveTo(sg * 8, -4); k.lineTo(sg * 36, 0); } k.stroke(); }
|
|
192
|
+
else { for (const sg of [-1, 1]) { k.beginPath(); k.ellipse(sg * 28, -14, 8, 18, sg * 0.4, 0, 7); k.fill(); } k.beginPath(); k.ellipse(0, -6, 6, 4, 0, 0, 7); k.fill(); }
|
|
193
|
+
k.beginPath(); k.arc(-9, -20, 3.2, 0, 7); k.arc(9, -20, 3.2, 0, 7); k.fill();
|
|
194
|
+
k.font = '400 26px "Bangers"'; k.textAlign = 'center'; k.textBaseline = 'middle'; k.fillText(label, 0, 46);
|
|
195
|
+
for (const x of [c, k]) x.restore();
|
|
196
|
+
};
|
|
197
|
+
const speedLines = (k, x0, y0, len, n, seed) => { const r = H.rng(seed); k.lineWidth = 3; k.strokeStyle = C.black; k.lineCap = 'round'; for (let i = 0; i < n; i++) { const yy = y0 + r.range(-50, 50), l = len * r.range(0.5, 1); k.beginPath(); k.moveTo(x0 - 70, yy); k.lineTo(x0 - 70 - l, yy); k.stroke(); } };
|
|
198
|
+
|
|
199
|
+
const panel = (c, k, i, t, bg) => {
|
|
200
|
+
const x0 = pX(i);
|
|
201
|
+
c.save(); c.translate(x0, 0); c.beginPath(); c.rect(0, 0, PPW, PPH); c.clip();
|
|
202
|
+
k.save(); k.translate(x0, 0); k.beginPath(); k.rect(0, 0, PPW, PPH); k.clip();
|
|
203
|
+
bg(c, k);
|
|
204
|
+
return () => { c.restore(); k.restore(); k.save(); k.translate(x0, 0); k.lineWidth = 9; k.strokeStyle = C.black; k.strokeRect(0, 0, PPW, PPH); k.restore(); };
|
|
205
|
+
};
|
|
206
|
+
const fmtN = (n) => Math.round(n).toLocaleString('en-US');
|
|
207
|
+
|
|
208
|
+
const PANELS = [
|
|
209
|
+
// 1 HOOK
|
|
210
|
+
(c, k, t) => {
|
|
211
|
+
const done = panel(c, k, 0, t, (c) => { c.fillStyle = C.white; c.fillRect(0, 0, PPW, PPH); benday(c, C.blue, 17, 5.2, -10, -10, PPW, PPH, 3, (x, y) => 0.75 + 0.3 * H.smooth(0, 1400, x + y * 0.4)); });
|
|
212
|
+
robot(c, k, 860, 470, 0.82, -0.08, 'think', t, { look: [-14, -16] });
|
|
213
|
+
box(c, k, 22, 20, 420, 60, C.yellow, 'MEANWHILE, IN THE LAB...', 30, 0.1, t);
|
|
214
|
+
balloon(c, k, 330, 300, 290, 165, 600, 430, PE.back(pseg(t, 0.35, 0.6)));
|
|
215
|
+
say(k, 'HOW DOES', 84, 330, 255, popIn(t, 0.55)); say(k, 'AI LEARN?', 92, 330, 345, popIn(t, 0.85));
|
|
216
|
+
const bob = Math.floor(t * 6) % 2 ? -6 : 0;
|
|
217
|
+
if (t > 1.1) { say(k, '?', 90, 1040, 120 + bob, 1, 'center', 0.2); say(k, '?', 64, 1110, 190 - bob, 1, 'center', 0.35); }
|
|
218
|
+
done();
|
|
219
|
+
},
|
|
220
|
+
// 2 DATA
|
|
221
|
+
(c, k, t) => {
|
|
222
|
+
const done = panel(c, k, 1, t, (c) => { c.fillStyle = C.yellow; c.fillRect(0, 0, PPW, PPH); benday(c, C.red, 18, 5.4, -10, -10, PPW, PPH, 5, (x, y) => H.smooth(700, 50, Math.hypot(x - 980, y - 330))); });
|
|
223
|
+
robot(c, k, 1000, 370, 0.6, -0.05, 'eat', t, { look: [-20, 0] });
|
|
224
|
+
// stream of labelled examples, faster and faster
|
|
225
|
+
for (let j = 0, t0 = 2.75; j < 26; j++, t0 += Math.max(0.1, 0.34 - j * 0.02)) {
|
|
226
|
+
const u = pseg(t, t0, t0 + 1.1); if (u <= 0 || u >= 1) continue;
|
|
227
|
+
const e = u * u * 0.4 + u * 0.6, x = -80 + e * 1020, y = 300 + Math.sin(j * 1.7) * 90 * (1 - e), kind = j % 3 === 1 ? 'dog' : 'cat';
|
|
228
|
+
speedLines(k, x, y, 120, 3, j);
|
|
229
|
+
photo(c, k, x, y, 130 * (1 - e * 0.55), kind, kind.toUpperCase(), Math.sin(j * 2.3) * 0.25);
|
|
230
|
+
}
|
|
231
|
+
box(c, k, 22, 20, 470, 60, C.white, 'STEP 1: STUDY EXAMPLES', 32, 2.55, t);
|
|
232
|
+
const bs = PE.back(pseg(t, 2.9, 3.15));
|
|
233
|
+
burst(c, k, 250, 540, 175, bs, C.white, 7);
|
|
234
|
+
if (bs > 0) { sayK(c, k, fmtN(1e6 * Math.pow(pseg(t, 3.0, 5.4), 2.3)), 68, 250, 524, bs, 'center', 0, 10); say(k, 'EXAMPLES!', 32, 250, 580, bs); }
|
|
235
|
+
if (t > 5.4) { k.save(); k.strokeStyle = C.black; k.lineWidth = 6; for (let a = 0; a < 8; a++) { const an = -Math.PI / 2 + (a - 3.5) * 0.28, r0 = 195, r1 = 235 + 20 * (Math.floor(t * 8) % 2); k.beginPath(); k.moveTo(250 + Math.cos(an) * r0, 540 + Math.sin(an) * r0); k.lineTo(250 + Math.cos(an) * r1, 540 + Math.sin(an) * r1); k.stroke(); } k.restore(); }
|
|
236
|
+
done();
|
|
237
|
+
},
|
|
238
|
+
// 3 GUESS -> CHECK -> ADJUST
|
|
239
|
+
(c, k, t) => {
|
|
240
|
+
const done = panel(c, k, 2, t, (c) => { c.fillStyle = C.white; c.fillRect(0, 0, PPW, PPH); benday(c, C.blue, 16, 3.6, -10, -10, PPW, PPH, 9); });
|
|
241
|
+
const right = t > 9.0;
|
|
242
|
+
const spin = PE.inOut(pseg(t, 8.2, 8.9)) * Math.PI * 4;
|
|
243
|
+
robot(c, k, 560, 430, 0.55, 0.03, right ? 'happy' : (t > 7.5 ? 'sad' : 'think'), t, { spin, look: [-22, -4] });
|
|
244
|
+
photo(c, k, 165, 400, 230 * PE.back(pseg(t, 6.15, 6.4)), 'cat', '?', -0.06);
|
|
245
|
+
sayK(c, k, 'NEW PICTURE', 30, 165, 590, popIn(t, 6.3), 'center', 0, 9);
|
|
246
|
+
// GUESS arrow (photo -> robot -> thought)
|
|
247
|
+
const g = fatArrow(c, k, [210, 240], [330, 120], [430, 200], PE.out(pseg(t, 6.45, 6.85)), 34, C.yellow);
|
|
248
|
+
if (t > 6.7) sayK(c, k, 'GUESS', 40, 325, 120, popIn(t, 6.7), 'center', -0.1);
|
|
249
|
+
// thought balloon
|
|
250
|
+
const ts = PE.back(pseg(t, 6.85, 7.1));
|
|
251
|
+
if (ts > 0) {
|
|
252
|
+
for (const [bx, by, br] of [[730, 250, 12], [760, 215, 18]]) { c.fillStyle = C.white; c.beginPath(); c.arc(bx, by, br, 0, 7); c.fill(); k.lineWidth = 5; k.beginPath(); k.arc(bx, by, br, 0, 7); k.stroke(); }
|
|
253
|
+
balloon(c, k, 900, 140, 150, 90, 900, 140, ts);
|
|
254
|
+
say(k, right ? 'CAT!' : 'DOG?', 78, 900, 145, right ? popIn(t, 9.0) : ts);
|
|
255
|
+
}
|
|
256
|
+
// CHECK: WRONG! burst with emphasis shake
|
|
257
|
+
if (t > 7.45 && !right) {
|
|
258
|
+
const bs = PE.back(pseg(t, 7.45, 7.65)), sh = t < 7.9 ? Math.sin(t * 90) * 6 : 0;
|
|
259
|
+
burst(c, k, 1050 + sh, 360, 120, bs, C.red, 13);
|
|
260
|
+
say(k, 'WRONG!', 44, 1050 + sh, 350, bs, 'center', -0.1);
|
|
261
|
+
say(k, "IT'S A CAT", 26, 1050, 395, bs, 'center', -0.1);
|
|
262
|
+
k.save(); k.strokeStyle = C.black; k.lineWidth = 14; k.lineCap = 'round'; const u = PE.out(pseg(t, 7.5, 7.7)); k.translate(1000, 120); k.scale(u, u); k.beginPath(); k.moveTo(-40, -40); k.lineTo(40, 40); k.moveTo(40, -40); k.lineTo(-40, 40); k.stroke(); k.restore();
|
|
263
|
+
}
|
|
264
|
+
if (t > 7.5) sayK(c, k, 'CHECK', 40, 1120, 245, popIn(t, 7.5), 'center', 0.08);
|
|
265
|
+
// ADJUST: arrow from the burst back round to the ear bolt
|
|
266
|
+
fatArrow(c, k, [1030, 480], [960, 650], [740, 540], PE.inOut(pseg(t, 7.95, 8.35)), 34, C.yellow);
|
|
267
|
+
if (t > 8.15) sayK(c, k, 'ADJUST', 40, 930, 600, popIn(t, 8.15), 'center', 0.05);
|
|
268
|
+
if (t > 8.25 && t < 9.1) sayK(c, k, 'WHIRR!', 46, 790, 300, 1 + 0.08 * (Math.floor(t * 12) % 2), 'center', 0.25);
|
|
269
|
+
if (right) {
|
|
270
|
+
const u = PE.back(pseg(t, 9.15, 9.4));
|
|
271
|
+
c.fillStyle = C.blue; c.beginPath(); c.arc(1080, 90, 46 * u, 0, 7); c.fill();
|
|
272
|
+
k.save(); k.translate(1080, 90); k.scale(u, u); k.lineWidth = 7; k.strokeStyle = C.black; k.beginPath(); k.arc(0, 0, 46, 0, 7); k.stroke(); k.restore();
|
|
273
|
+
c.save(); c.translate(1080, 90); c.scale(u, u); c.lineWidth = 13; c.lineCap = 'round'; c.lineJoin = 'round'; c.strokeStyle = C.white; c.beginPath(); c.moveTo(-20, 0); c.lineTo(-5, 16); c.lineTo(22, -16); c.stroke(); c.restore();
|
|
274
|
+
// loop it again: arrow re-draws as a cycling highlight
|
|
275
|
+
const lp = ((t - 9.4) / 0.6) % 1;
|
|
276
|
+
if (t > 9.4) { k.save(); k.fillStyle = C.black; const q = [(1 - lp) * (1 - lp) * 1030 + 2 * (1 - lp) * lp * 960 + lp * lp * 740, (1 - lp) * (1 - lp) * 480 + 2 * (1 - lp) * lp * 650 + lp * lp * 540]; k.beginPath(); k.arc(q[0], q[1], 9, 0, 7); k.fill(); k.restore(); }
|
|
277
|
+
}
|
|
278
|
+
done();
|
|
279
|
+
},
|
|
280
|
+
// 4 PROGRESS
|
|
281
|
+
(c, k, t) => {
|
|
282
|
+
const done = panel(c, k, 3, t, (c) => { c.fillStyle = C.white; c.fillRect(0, 0, PPW, PPH); benday(c, C.blue, 17, 5.2, -10, -10, PPW, PPH, 21, (x, y) => 0.6 + 0.3 * H.smooth(1200, 0, x)); });
|
|
283
|
+
box(c, k, 22, 20, 520, 60, C.yellow, 'AFTER MILLIONS OF ROUNDS...', 32, 10.75, t);
|
|
284
|
+
const hit = t > 12.4;
|
|
285
|
+
// sunburst rays sit behind the chart board
|
|
286
|
+
if (hit) { c.save(); c.fillStyle = C.yellow; c.beginPath(); for (let a = 0; a < 16; a++) { const a0 = (a / 16) * Math.PI * 2 + t * 0.4; c.moveTo(950, 340); c.arc(950, 340, 700, a0, a0 + 0.1); } c.fill(); c.restore(); }
|
|
287
|
+
// chart board
|
|
288
|
+
c.fillStyle = C.white; c.fillRect(70, 120, 620, 480); k.lineWidth = 7; k.strokeStyle = C.black; k.strokeRect(70, 120, 620, 480);
|
|
289
|
+
k.lineWidth = 5; k.beginPath(); k.moveTo(110, 150); k.lineTo(110, 540); k.lineTo(670, 540); k.stroke();
|
|
290
|
+
say(k, 'TRAINING ROUNDS', 28, 390, 572, 1);
|
|
291
|
+
const prog = PE.inOut(pseg(t, 11.0, 12.4));
|
|
292
|
+
for (let b = 0; b < 8; b++) {
|
|
293
|
+
const hv = (61 + 36 * (1 - Math.exp(-(b + 1) * 0.45)) / (1 - Math.exp(-8 * 0.45)) - 50) / 50 * 370;
|
|
294
|
+
const g = pcl(prog * 8 - b), h = hv * PE.back(g);
|
|
295
|
+
if (h <= 0) continue;
|
|
296
|
+
const bx = 130 + b * 66;
|
|
297
|
+
c.fillStyle = b % 2 ? C.red : C.yellow; c.fillRect(bx, 540 - h, 50, h);
|
|
298
|
+
c.save(); c.beginPath(); c.rect(bx + 32, 540 - h, 18, h); c.clip(); benday(c, C.black, 8, 2.2, bx, 540 - h, bx + 50, 540, b); c.restore();
|
|
299
|
+
k.lineWidth = 5; k.strokeRect(bx, 540 - h, 50, h);
|
|
300
|
+
}
|
|
301
|
+
const v = Math.round(61 + 36 * prog);
|
|
302
|
+
const bs = PE.back(pseg(t, 10.9, 11.15));
|
|
303
|
+
burst(c, k, 950, 340, 230, bs * (hit ? 1 + 0.12 * (1 - PE.out(pseg(t, 12.4, 12.65))) : 1), hit ? C.red : C.white, 31);
|
|
304
|
+
if (bs > 0) { say(k, 'ACCURACY', 36, 950, 250, bs); say(k, v + '%', 150, 950, 360, bs * (hit ? 1 + 0.25 * (1 - PE.out(pseg(t, 12.4, 12.6))) : 1)); }
|
|
305
|
+
if (hit) sayK(c, k, 'WOW!', 46, 1100, 520, popIn(t, 12.5), 'center', -0.15);
|
|
306
|
+
done();
|
|
307
|
+
},
|
|
308
|
+
// 5 TAKEAWAY
|
|
309
|
+
(c, k, t) => {
|
|
310
|
+
const done = panel(c, k, 4, t, (c) => { c.fillStyle = C.yellow; c.fillRect(0, 0, PPW, PPH); benday(c, C.red, 18, 5.4, -10, -10, PPW, PPH, 41, (x, y) => H.smooth(800, 100, Math.hypot(x - 900, y - 400))); });
|
|
311
|
+
robot(c, k, 920, 450, 0.7, 0.06, 'happy', t, { look: [-18, -10] });
|
|
312
|
+
balloon(c, k, 380, 290, 330, 190, 684, 446, PE.back(pseg(t, 13.15, 13.4)));
|
|
313
|
+
say(k, 'MORE EXAMPLES', 74, 380, 230, popIn(t, 13.3));
|
|
314
|
+
if (t > 13.55) { const u = PE.out(pseg(t, 13.55, 13.75)); k.save(); k.translate(380, 300); k.fillStyle = C.black; k.beginPath(); k.moveTo(-12, -40 * u); k.lineTo(12, -40 * u); k.lineTo(12, 0); k.lineTo(30, 0); k.lineTo(0, 30 * u); k.lineTo(-30, 0); k.lineTo(-12, 0); k.closePath(); k.fill(); k.restore(); }
|
|
315
|
+
say(k, 'BETTER GUESSES!', 74, 380, 375, popIn(t, 13.7));
|
|
316
|
+
box(c, k, 22, PPH - 82, 230, 60, C.white, 'THE END!', 34, 14.2, t);
|
|
317
|
+
done();
|
|
318
|
+
},
|
|
319
|
+
];
|
|
320
|
+
|
|
321
|
+
let ageCache = null;
|
|
322
|
+
const paperAge = (W, H_) => {
|
|
323
|
+
if (ageCache && ageCache.W === W && ageCache.H === H_) return ageCache;
|
|
324
|
+
const nz = H.makeNoise(41), LW = 160, LH = 90;
|
|
325
|
+
const lo = H.layer(LW, LH), lx = lo.getContext('2d'), li = lx.createImageData(LW, LH);
|
|
326
|
+
for (let y = 0; y < LH; y++) for (let x = 0; x < LW; x++) { const i = (y * LW + x) * 4; li.data[i] = 128 + 127 * nz.fbm2(x * 0.06, y * 0.06, 3); li.data[i + 3] = 255; }
|
|
327
|
+
lx.putImageData(li, 0, 0);
|
|
328
|
+
const big = H.layer(W, H_), bx = big.getContext('2d'); bx.drawImage(lo, 0, 0, W, H_);
|
|
329
|
+
const lf = bx.getImageData(0, 0, W, H_).data, r = H.rng(5), N = W * H_;
|
|
330
|
+
const R = new Float32Array(N), G = new Float32Array(N), B = new Float32Array(N);
|
|
331
|
+
for (let y = 0, p = 0; y < H_; y++) for (let x = 0; x < W; x++, p++) {
|
|
332
|
+
const n = (lf[p * 4] - 128) / 128, g = r() - 0.5, ex = Math.min(x, W - x, y, H_ - y);
|
|
333
|
+
const age = 1 - 0.06 * (1 - H.smooth(0, 160, ex)) - 0.025 * n;
|
|
334
|
+
R[p] = age * (1 + g * 0.05); G[p] = (age - 0.01) * (1 + g * 0.05); B[p] = (age - 0.045) * (1 + g * 0.06);
|
|
335
|
+
}
|
|
336
|
+
return (ageCache = { W, H: H_, r: R, g: G, b: B });
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const popExplainer = (ctx, W, H_, t) => {
|
|
340
|
+
// camera: reads the strip panel by panel, pulling back a touch on each move
|
|
341
|
+
const B = [2.4, 6.0, 10.6, 13.0];
|
|
342
|
+
let pos = 0, dip = 0;
|
|
343
|
+
B.forEach((b, i) => { const u = PE.inOut(pseg(t, b - 0.4, b + 0.4)); pos += u; dip = Math.max(dip, Math.sin(Math.PI * pseg(t, b - 0.4, b + 0.4))); });
|
|
344
|
+
const cx = pos * (PPW + PG) + PPW / 2, cy = PPH / 2, z = 1 - 0.2 * dip;
|
|
345
|
+
const col = H.layer(W, H_), c = col.getContext('2d'), key = H.layer(W, H_), k = key.getContext('2d');
|
|
346
|
+
c.fillStyle = C.white; c.fillRect(0, 0, W, H_);
|
|
347
|
+
for (const x of [c, k]) { x.save(); x.translate(W / 2, H_ / 2); x.scale(z, z); x.translate(-cx, -cy); x.lineJoin = 'round'; x.lineCap = 'round'; }
|
|
348
|
+
const half = (W / 2) / z - 1.5;
|
|
349
|
+
PANELS.forEach((fn, i) => { const px = pX(i); if (px + PPW + 4.5 < cx - half || px - 4.5 > cx + half) return; fn(c, k, t); });
|
|
350
|
+
for (const x of [c, k]) x.restore();
|
|
351
|
+
ctx.fillStyle = C.white; ctx.fillRect(0, 0, W, H_);
|
|
352
|
+
ctx.drawImage(col, 2.5, -1.5);
|
|
353
|
+
ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(key, 0, 0); ctx.globalCompositeOperation = 'source-over';
|
|
354
|
+
// paper: the hero's low-frequency yellowing, edge ageing and fibre grain (multipliers cached per size)
|
|
355
|
+
const A = paperAge(W, H_), img = ctx.getImageData(0, 0, W, H_), d = img.data;
|
|
356
|
+
for (let i = 0, p = 0; i < d.length; i += 4, p++) { d[i] *= A.r[p]; d[i + 1] *= A.g[p]; d[i + 2] *= A.b[p]; }
|
|
357
|
+
ctx.putImageData(img, 0, 0);
|
|
358
|
+
};
|
|
359
|
+
|
|
360
|
+
registerStyle({
|
|
361
|
+
id: 'halftone-pop',
|
|
362
|
+
name: 'Halftone Pop Panel',
|
|
363
|
+
category: 'Print',
|
|
364
|
+
description: 'A Lichtenstein-style blown-up comic panel: fat tapering black brush outlines, mechanical Ben-Day dot fields in red and blue, flat primary yellow, a tearful close-up and hand-lettered balloon.',
|
|
365
|
+
motion: 'Blue dots pop on in a wave, the robot head springs up into frame, red skin dots wash down the face, caption and balloon slam in word by word, then a single tear wells up and rolls down during a slow push-in.',
|
|
366
|
+
fonts: ['400 66px "Bangers"', '400 30px "Bangers"'],
|
|
367
|
+
duration: 5,
|
|
368
|
+
frameT: 4.8,
|
|
369
|
+
readsPixels: true,
|
|
370
|
+
explainerDuration: 15,
|
|
371
|
+
explainerNote: 'Told as a comic strip the camera reads panel by panel (pulling back to the gutters on each move), with fat yellow comic arrows, captions and balloons, Bangers counters bursting out of explosion balloons, and a WRONG! burst with shake as the emphasis.',
|
|
372
|
+
explainer(ctx, W, H_, t) { popExplainer(ctx, W, H_, t); },
|
|
373
|
+
// Sound: a comic book read aloud by its sound effects. Lettering lands with short punchy "thwacks", explosion
|
|
374
|
+
// balloons go BAM, the WRONG! burst crashes and rattles with its shake, the ear bolts ratchet through WHIRR!,
|
|
375
|
+
// fat arrows zip on, and each camera move across the strip is a soft, smooth slide over the page.
|
|
376
|
+
// Timings mirror PANELS / popExplainer above (B camera beats, box/balloon/popIn/pseg windows).
|
|
377
|
+
// Voiceover mode: room bed drops; photo zips, counter clicks, small labels and bubbles are minor.
|
|
378
|
+
explainerSound(k) {
|
|
379
|
+
const D = 15, B = [2.4, 6.0, 10.6, 13.0];
|
|
380
|
+
const pan = (px) => H.clamp((px / PPW) * 2 - 1, -1, 1) * 0.6; // panel-local x -> stereo
|
|
381
|
+
const when = (t0, t1, ease, u) => { for (let i = 0; i <= 400; i++) { const t = t0 + ((t1 - t0) * i) / 400; if (ease(i / 400) >= u - 1e-6) return t; } return t1; };
|
|
382
|
+
// comic lettering landing: a short dry thwack with a low body (no pitched chirp)
|
|
383
|
+
const punch = (t, g, p, minor) => {
|
|
384
|
+
const o = { minor: !!minor };
|
|
385
|
+
k.tone(t, { ...o, f: [[0, 210], [0.09, 70]], dur: 0.14, gain: 0.42 * g, pan: p });
|
|
386
|
+
k.noise(t, { ...o, dur: 0.06, filter: 'bandpass', f: 1100, q: 0.9, gain: 0.34 * g, a: 0.001, pan: p });
|
|
387
|
+
k.noise(t, { ...o, dur: 0.015, filter: 'highpass', f: 2600, gain: 0.14 * g, a: 0.001, pan: p });
|
|
388
|
+
};
|
|
389
|
+
// explosion balloon: BAM (thud + a short crashing burst of noise)
|
|
390
|
+
const bam = (t, g, p) => {
|
|
391
|
+
k.play('thud', t, { f: 120, gain: 0.9 * g, pan: p });
|
|
392
|
+
k.noise(t, { dur: 0.42, color: 'pink', filter: 'lowpass', f: [[0, 3200], [0.4, 380]], gain: 0.34 * g, a: 0.002, verb: 0.25, pan: p });
|
|
393
|
+
k.noise(t, { dur: 0.05, filter: 'bandpass', f: 1400, q: 0.8, gain: 0.3 * g, a: 0.001, pan: p });
|
|
394
|
+
};
|
|
395
|
+
// fat arrow / photo whipping on: a short low zip, length = draw time
|
|
396
|
+
const zip = (t, d, g, p, minor) => k.noise(t, { minor: !!minor, dur: d, color: 'pink', filter: 'bandpass', f: [[0, 420], [d * 0.7, 1500], [d, 900]], q: 1.1, gain: 0.22 * g, a: d * 0.55, pan: p });
|
|
397
|
+
// camera glides along the strip: a soft smooth slide, centred on the beat
|
|
398
|
+
const glide = (b) => k.noise(b - 0.4, { dur: 0.85, color: 'brown', filter: 'lowpass', f: [[0, 260], [0.4, 700], [0.85, 240]], q: 0.7, gain: 0.2, a: 0.38 });
|
|
399
|
+
// counter digit flip: tiny dry click, low
|
|
400
|
+
const clk = (t, g, p) => k.noise(t, { minor: true, dur: 0.02, filter: 'bandpass', f: 2300, q: 1.6, gain: 0.26 * g, a: 0.001, pan: p });
|
|
401
|
+
|
|
402
|
+
k.bed('room', 0, D, { gain: 0.18 });
|
|
403
|
+
|
|
404
|
+
// ---- panel 1: HOOK
|
|
405
|
+
k.play('slap', when(0.1, 0.35, PE.back, 1), { gain: 0.35, pan: pan(230), minor: true }); // MEANWHILE caption box
|
|
406
|
+
k.play('pop', when(0.35, 0.6, PE.back, 1), { f: 330, gain: 0.7, pan: pan(330), minor: true }); // balloon inflates
|
|
407
|
+
punch(0.55, 0.9, pan(330)); // HOW DOES
|
|
408
|
+
punch(0.85, 1.05, pan(330)); // AI LEARN?
|
|
409
|
+
k.play('pop', 1.1, { f: 300, gain: 0.45, pan: pan(1040), minor: true }); // the two ?s
|
|
410
|
+
k.play('pop', 1.24, { f: 260, gain: 0.4, pan: pan(1110), minor: true });
|
|
411
|
+
|
|
412
|
+
// ---- camera to panel 2
|
|
413
|
+
glide(B[0]);
|
|
414
|
+
k.play('slap', when(2.55, 2.8, PE.back, 1), { gain: 0.3, pan: -0.4, minor: true }); // STEP 1 caption
|
|
415
|
+
bam(when(2.9, 3.15, PE.back, 1) - 0.12, 0.5, pan(250)); // EXAMPLES! burst springs open
|
|
416
|
+
// photos fly in with speed lines (same spawn schedule as panel 2)
|
|
417
|
+
for (let j = 0, t0 = 2.75; t0 < 5.45; j++, t0 += Math.max(0.1, 0.34 - j * 0.02)) zip(t0, 0.16, 0.6, -0.6, true);
|
|
418
|
+
// counter n = 1e6 * pseg(3.0, 5.4)^2.3: clicks speeding up with the count, <= ~11/s
|
|
419
|
+
for (let t = 3.06; t < 5.32; ) { const s = pseg(t, 3.0, 5.4); clk(t, 0.75 + 0.35 * s, pan(250)); t += 0.18 - 0.09 * s; }
|
|
420
|
+
bam(5.4, 0.75, pan(250)); // 1,000,000 + action lines flash
|
|
421
|
+
punch(5.4, 0.55, pan(250));
|
|
422
|
+
|
|
423
|
+
// ---- camera to panel 3
|
|
424
|
+
glide(B[1]);
|
|
425
|
+
k.play('pop', when(6.15, 6.4, PE.back, 1) - 0.08, { f: 240, gain: 0.7, pan: pan(165), minor: true }); // NEW PICTURE photo
|
|
426
|
+
punch(6.3, 0.45, pan(165), true); // NEW PICTURE label
|
|
427
|
+
zip(6.45, 0.4, 0.9, pan(320)); // GUESS arrow
|
|
428
|
+
punch(6.7, 0.65, pan(325), true); // GUESS
|
|
429
|
+
k.play('pop', 6.85, { f: 380, gain: 0.35, pan: pan(730), minor: true }); // thought bubbles
|
|
430
|
+
k.play('pop', when(6.85, 7.1, PE.back, 1) - 0.05, { f: 300, gain: 0.6, pan: pan(900) }); // DOG? balloon
|
|
431
|
+
// CHECK: WRONG! burst crashes in, rattles while it shakes (7.45 -> 7.9)
|
|
432
|
+
bam(7.45, 1.15, pan(1050));
|
|
433
|
+
k.play('buzz', 7.58, { f: 92, dur: 0.3, gain: 0.8, pan: pan(1050) });
|
|
434
|
+
for (let t = 7.5; t < 7.88; t += 0.07) k.noise(t, { minor: true, dur: 0.04, filter: 'bandpass', f: 700, q: 1.2, gain: 0.16, a: 0.001, pan: pan(1050) });
|
|
435
|
+
k.play('stroke', 7.56, { material: 'brush', dur: 0.16, gain: 1.1, pan: pan(1000), minor: true }); // the inked X
|
|
436
|
+
punch(7.5, 0.55, pan(1120), true); // CHECK
|
|
437
|
+
// ADJUST arrow, label, then the ear bolts ratchet round twice (spin 8.2..8.9, 4*pi) under WHIRR!
|
|
438
|
+
zip(7.95, 0.4, 0.9, pan(900));
|
|
439
|
+
punch(8.15, 0.6, pan(930), true);
|
|
440
|
+
k.tone(8.22, { f: [[0, 85], [0.35, 150], [0.7, 85]], type: 'sawtooth', dur: 0.75, gain: 0.045, a: 0.1, hold: 0.4, filter: 'lowpass', f2: 650, pan: pan(560) });
|
|
441
|
+
for (let i = 1; i <= 4; i++) k.play('click', when(8.2, 8.9, PE.inOut, i / 4), { f: 1150, gain: 1.1, pan: pan(560) + (i % 2 ? -0.15 : 0.15), minor: true });
|
|
442
|
+
// CORRECT: CAT! lands, then the blue tick
|
|
443
|
+
punch(9.0, 1.0, pan(900));
|
|
444
|
+
k.play('success', when(9.15, 9.4, PE.back, 1) - 0.05, { f: 'C5', gain: 0.5, pan: pan(1080) });
|
|
445
|
+
// the loop keeps cycling: a soft tap each time the dot sets off
|
|
446
|
+
for (let t = 10.0; t < 10.3; t += 0.6) k.play('tock', t, { f: 420, gain: 0.25, pan: pan(1030), minor: true });
|
|
447
|
+
|
|
448
|
+
// ---- camera to panel 4
|
|
449
|
+
glide(B[2]);
|
|
450
|
+
k.play('slap', when(10.75, 11.0, PE.back, 1), { gain: 0.3, pan: -0.4, minor: true }); // AFTER MILLIONS caption
|
|
451
|
+
k.play('pop', when(10.9, 11.15, PE.back, 1) - 0.08, { f: 260, gain: 0.6, pan: pan(950), minor: true }); // ACCURACY burst opens
|
|
452
|
+
// bars spring up one by one (bar b starts when prog = b/8), each a little higher-pitched thunk
|
|
453
|
+
for (let b = 0; b < 8; b++) k.tone(when(11.0, 12.4, PE.inOut, b / 8 + 0.01), { minor: true, f: [[0, 150 + b * 18], [0.08, 80 + b * 10]], dur: 0.12, gain: 0.18, pan: pan(130 + b * 66) });
|
|
454
|
+
// 61% -> 97% counter, throttled to ~10/s
|
|
455
|
+
for (let t = 11.0, last = 61, lastT = -1; t < 12.42; t += 1 / 120) { const v = Math.round(61 + 36 * PE.inOut(pseg(t, 11.0, 12.4))); if (v !== last && t - lastT >= 0.1) { clk(t, 0.9, pan(950)); last = v; lastT = t; } }
|
|
456
|
+
// 97%: the biggest hit. KA-POW + sunburst
|
|
457
|
+
bam(12.4, 1.4, pan(950));
|
|
458
|
+
k.play('impact', 12.4, { gain: 1.0, pan: pan(950) });
|
|
459
|
+
k.tone(12.4, { f: [[0, 75], [0.5, 38]], dur: 0.7, gain: 0.5, pan: pan(950) }); // sub boom under the sunburst
|
|
460
|
+
punch(12.5, 0.75, pan(1100)); // WOW!
|
|
461
|
+
|
|
462
|
+
// ---- camera to panel 5: TAKEAWAY
|
|
463
|
+
glide(B[3]);
|
|
464
|
+
k.play('pop', when(13.15, 13.4, PE.back, 1) - 0.06, { f: 300, gain: 0.6, pan: pan(380), minor: true });
|
|
465
|
+
punch(13.3, 0.8, pan(380)); // MORE EXAMPLES
|
|
466
|
+
zip(13.55, 0.2, 0.6, pan(380), true); // down arrow
|
|
467
|
+
punch(13.7, 1.0, pan(380)); // BETTER GUESSES!
|
|
468
|
+
['C5', 'E5', 'G5', 'C6'].forEach((f, i) => k.play('ding', 13.8 + i * 0.07, { f, gain: 0.38 - i * 0.05, pan: (i - 1.5) * 0.2 }));
|
|
469
|
+
k.play('slap', when(14.2, 14.45, PE.back, 1), { gain: 0.4, pan: -0.5 }); // THE END!
|
|
470
|
+
},
|
|
471
|
+
draw(ctx, W, H_, t) {
|
|
472
|
+
const PX = 26, PY = 24, PW = W - 52, PH = H_ - 48; // panel
|
|
473
|
+
const E = H.ease;
|
|
474
|
+
const headIn = H.clamp((t - 0.5) / 0.5);
|
|
475
|
+
const hx = 880, hy = 462 + Math.sin(t * 1.3) * 3 + (1 - E.back(headIn)) * 720, hr = -0.11 + (1 - E.out(headIn)) * 0.25, hs = 1.04;
|
|
476
|
+
const bgT = H.clamp((t - 0.02) / 0.7), bgWave = (x, y) => H.clamp((bgT * 1900 - (x - PX) - (y - PY) * 0.5) / 160);
|
|
477
|
+
const faceT = H.clamp((t - 1.0) / 0.5), faceWave = (x, y) => H.clamp((faceT * 700 - (y + 330)) / 70);
|
|
478
|
+
const tearIn = H.clamp((t - 2.85) / 1.15), tearY = H.lerp(-40, 105, E.inOut(tearIn)), tearS = 1.3 * E.out(H.clamp((t - 2.85) / 0.3));
|
|
479
|
+
const capIn = E.back(H.clamp((t - 1.5) / 0.35)), capDX = (1 - capIn) * -500;
|
|
480
|
+
const balIn = H.clamp((t - 1.9) / 0.28), balS = balIn <= 0 ? 0 : E.back(balIn);
|
|
481
|
+
const pop = (t0) => (t < t0 ? 0 : 1 + 0.35 * (1 - E.out(H.clamp((t - t0) / 0.14))));
|
|
482
|
+
const bal = (x, fn) => { if (balS <= 0) return; x.save(); x.translate(560, 520); x.scale(balS, balS); x.translate(-560, -520); fn(); x.restore(); };
|
|
483
|
+
const cap = (x, fn) => { if (t < 1.5) return; x.save(); x.translate(capDX, 0); fn(); x.restore(); };
|
|
484
|
+
const word = (x, t0, txt, size, cx, cy) => { const sc = pop(t0); if (!sc) return; x.save(); x.translate(cx, cy); x.scale(sc, sc); x.font = `400 ${size}px "Bangers"`; x.fillText(txt, 0, 0); x.restore(); };
|
|
485
|
+
|
|
486
|
+
// ---------- COLOR PLATES ----------
|
|
487
|
+
const col = H.layer(W, H_), c = col.getContext('2d');
|
|
488
|
+
c.fillStyle = C.white; c.fillRect(0, 0, W, H_);
|
|
489
|
+
c.save(); c.beginPath(); c.rect(PX, PY, PW, PH); c.clip();
|
|
490
|
+
// background: blue Ben-Day field, denser toward the right edge
|
|
491
|
+
benday(c, C.blue, 17, 5.2, PX - 10, PY - 10, PX + PW, PY + PH, 3, (x, y) => (0.72 + 0.32 * H.smooth(200, 1200, x + y * 0.4)) * bgWave(x, y));
|
|
492
|
+
// head
|
|
493
|
+
if (headIn > 0) {
|
|
494
|
+
c.save(); c.translate(hx, hy); c.rotate(hr); c.scale(hs, hs);
|
|
495
|
+
c.fillStyle = C.yellow; headPath(c); c.fill();
|
|
496
|
+
c.save(); headPath(c); c.clip();
|
|
497
|
+
c.beginPath(); c.moveTo(160, -320); c.bezierCurveTo(150, -150, 150, 60, 170, 320); c.lineTo(320, 320); c.lineTo(320, -320); c.closePath(); c.clip();
|
|
498
|
+
benday(c, C.red, 12, 3.3, 100, -340, 320, 340, 12, faceWave);
|
|
499
|
+
c.restore();
|
|
500
|
+
c.fillStyle = C.white; c.beginPath(); c.moveTo(-226, -60); c.bezierCurveTo(-226, -200, -160, -270, -90, -300); c.bezierCurveTo(-150, -250, -196, -170, -200, -60); c.closePath(); c.fill();
|
|
501
|
+
c.save(); facePath(c); c.clip();
|
|
502
|
+
c.fillStyle = C.white; c.fillRect(-300, -300, 600, 600);
|
|
503
|
+
benday(c, C.red, 16, 3.7, -260, -260, 260, 300, 8, faceWave);
|
|
504
|
+
c.restore();
|
|
505
|
+
// eyes: whites + blue irises
|
|
506
|
+
for (const [ex, sg] of [[-88, -1], [92, 1]]) {
|
|
507
|
+
c.fillStyle = C.white; eye(c, ex, -30, sg); c.fill();
|
|
508
|
+
c.save(); eye(c, ex, -30, sg); c.clip();
|
|
509
|
+
c.fillStyle = C.blue; c.beginPath(); c.arc(ex + 8, -22, 34, 0, 7); c.fill();
|
|
510
|
+
c.restore();
|
|
511
|
+
}
|
|
512
|
+
// eye-shadow lids (blue dots like Roy's make-up)
|
|
513
|
+
for (const [ex, sg] of [[-88, -1], [92, 1]]) {
|
|
514
|
+
c.save(); c.beginPath(); c.ellipse(ex, -48, 74, 40, sg * 0.1, Math.PI, Math.PI * 2); c.closePath(); c.clip();
|
|
515
|
+
c.fillStyle = C.white; c.fillRect(ex - 90, -120, 180, 70);
|
|
516
|
+
benday(c, C.blue, 10, 3.4, ex - 100, -130, ex + 100, -40, 21 + ex);
|
|
517
|
+
c.restore();
|
|
518
|
+
}
|
|
519
|
+
// antenna bulb, ear bolts
|
|
520
|
+
c.fillStyle = C.red; c.beginPath(); c.arc(0, -392, 30, 0, 7); c.fill();
|
|
521
|
+
for (const sx of [-1, 1]) { c.beginPath(); c.arc(sx * 262, -10, 46, 0, 7); c.fill(); }
|
|
522
|
+
// tear
|
|
523
|
+
if (tearS > 0) {
|
|
524
|
+
c.fillStyle = '#5fa3ef'; tearPath(c, -128, tearY + 40, tearS); c.fill();
|
|
525
|
+
if (tearY > 14) { c.beginPath(); c.moveTo(-128, 4); c.lineTo(-128, tearY - 10); c.lineWidth = 12; c.strokeStyle = '#5fa3ef'; c.stroke(); }
|
|
526
|
+
}
|
|
527
|
+
c.restore();
|
|
528
|
+
}
|
|
529
|
+
// caption box + balloon
|
|
530
|
+
cap(c, () => { c.fillStyle = C.yellow; c.fillRect(PX + 18, PY + 16, 412, 64); });
|
|
531
|
+
bal(c, () => { c.fillStyle = C.white; bubblePath(c); c.fill(); });
|
|
532
|
+
c.restore();
|
|
533
|
+
|
|
534
|
+
// ---------- KEY (black) PLATE ----------
|
|
535
|
+
const key = H.layer(W, H_), k = key.getContext('2d');
|
|
536
|
+
k.lineJoin = 'round'; k.lineCap = 'round'; k.strokeStyle = C.black; k.fillStyle = C.black;
|
|
537
|
+
k.save(); k.beginPath(); k.rect(PX, PY, PW, PH); k.clip();
|
|
538
|
+
if (headIn > 0) {
|
|
539
|
+
k.save(); k.translate(hx, hy); k.rotate(hr); k.scale(hs, hs);
|
|
540
|
+
// antenna
|
|
541
|
+
k.lineWidth = 12; k.beginPath(); k.moveTo(0, -325); k.lineTo(0, -362); k.stroke();
|
|
542
|
+
k.lineWidth = 9; k.beginPath(); k.arc(0, -392, 30, 0, 7); k.stroke();
|
|
543
|
+
k.fillStyle = C.white; k.beginPath(); k.ellipse(-10, -402, 9, 6, -0.6, 0, 7); k.fill(); k.fillStyle = C.black;
|
|
544
|
+
// ear bolts
|
|
545
|
+
for (const sx of [-1, 1]) {
|
|
546
|
+
k.lineWidth = 9; k.beginPath(); k.arc(sx * 262, -10, 46, 0, 7); k.stroke();
|
|
547
|
+
k.lineWidth = 6; k.beginPath(); k.arc(sx * 262, -10, 22, 0, 7); k.stroke();
|
|
548
|
+
}
|
|
549
|
+
// chrome shading on yellow shell: hard black reflection shapes
|
|
550
|
+
k.save(); headPath(k); k.clip();
|
|
551
|
+
k.beginPath(); k.moveTo(250, -300); k.bezierCurveTo(205, -170, 196, 40, 214, 300); k.lineTo(320, 300); k.lineTo(320, -300); k.closePath(); k.fill();
|
|
552
|
+
k.beginPath(); k.moveTo(-150, -296); k.bezierCurveTo(-60, -318, 70, -318, 150, -290); k.bezierCurveTo(70, -300, -50, -298, -150, -282); k.closePath(); k.fill();
|
|
553
|
+
// shell highlight streaks (white) on the dome
|
|
554
|
+
k.restore();
|
|
555
|
+
// outlines: fat brush lines
|
|
556
|
+
k.lineWidth = 12; headPath(k); k.stroke();
|
|
557
|
+
k.save(); k.translate(5, 4); k.lineWidth = 10; headPath(k); k.stroke(); k.restore();
|
|
558
|
+
k.lineWidth = 8; facePath(k); k.stroke();
|
|
559
|
+
k.save(); k.translate(-4, 3); k.lineWidth = 6; facePath(k); k.stroke(); k.restore();
|
|
560
|
+
// rivets
|
|
561
|
+
for (const [rx, ry] of [[-208, -150], [-206, 100], [204, -150], [200, 100], [-60, -270], [60, -270]]) { k.beginPath(); k.arc(rx, ry, 7, 0, 7); k.fill(); }
|
|
562
|
+
// eyes
|
|
563
|
+
for (const [ex, sg] of [[-88, -1], [92, 1]]) {
|
|
564
|
+
k.lineWidth = 8; eye(k, ex, -30, sg); k.stroke();
|
|
565
|
+
k.save(); eye(k, ex, -30, sg); k.clip();
|
|
566
|
+
k.beginPath(); k.arc(ex + 8, -22, 15, 0, 7); k.fill();
|
|
567
|
+
k.lineWidth = 5; k.beginPath(); k.arc(ex + 8, -22, 34, 0, 7); k.stroke();
|
|
568
|
+
k.fillStyle = C.white;
|
|
569
|
+
k.beginPath(); k.ellipse(ex - 6, -40, 11, 8, -0.5, 0, 7); k.fill();
|
|
570
|
+
k.beginPath(); k.arc(ex + 20, -10, 5, 0, 7); k.fill();
|
|
571
|
+
k.fillStyle = C.black;
|
|
572
|
+
// heavy upper lid
|
|
573
|
+
k.beginPath(); k.ellipse(ex, -30, 66, 52, sg * 0.08, Math.PI * 1.02, Math.PI * 1.98); k.lineWidth = 14; k.stroke();
|
|
574
|
+
k.restore();
|
|
575
|
+
// lashes-like brush flicks at outer corner
|
|
576
|
+
k.lineWidth = 7;
|
|
577
|
+
k.beginPath(); k.moveTo(ex + sg * 58, -52); k.quadraticCurveTo(ex + sg * 80, -66, ex + sg * 92, -62); k.stroke();
|
|
578
|
+
}
|
|
579
|
+
// worried brows: tapered brush strokes
|
|
580
|
+
const brow = (x0, y0, x1, y1, bulge) => {
|
|
581
|
+
k.beginPath(); k.moveTo(x0, y0);
|
|
582
|
+
k.quadraticCurveTo((x0 + x1) / 2, (y0 + y1) / 2 - bulge, x1, y1);
|
|
583
|
+
k.quadraticCurveTo((x0 + x1) / 2, (y0 + y1) / 2 - bulge + 22, x0 + (x1 > x0 ? 6 : -6), y0 + 6); k.closePath(); k.fill();
|
|
584
|
+
};
|
|
585
|
+
brow(-168, -96, -32, -150, 18); brow(172, -96, 36, -150, 18);
|
|
586
|
+
// nose seam / cheek plate lines
|
|
587
|
+
k.lineWidth = 6; k.beginPath(); k.moveTo(0, 30); k.quadraticCurveTo(-14, 80, 4, 104); k.stroke();
|
|
588
|
+
// mouth grille (down-turned)
|
|
589
|
+
mouthPath(k); k.fill();
|
|
590
|
+
k.strokeStyle = C.white; k.lineWidth = 4;
|
|
591
|
+
k.beginPath(); k.moveTo(-50, 188); k.quadraticCurveTo(0, 168, 50, 188); k.stroke();
|
|
592
|
+
k.strokeStyle = C.black;
|
|
593
|
+
// tear outline + highlight
|
|
594
|
+
if (tearS > 0) {
|
|
595
|
+
k.lineWidth = 7; tearPath(k, -128, tearY + 40, tearS); k.stroke();
|
|
596
|
+
if (tearY > 14) { k.lineWidth = 5; k.beginPath(); k.moveTo(-134, 6); k.lineTo(-134, tearY - 14); k.moveTo(-122, 6); k.lineTo(-122, tearY - 14); k.stroke(); }
|
|
597
|
+
k.fillStyle = C.white; k.beginPath(); k.ellipse(-128 - 12 * tearS / 1.3, tearY + 40 + 18 * tearS / 1.3, 8 * tearS / 1.3, 14 * tearS / 1.3, 0.3, 0, 7); k.fill(); k.fillStyle = C.black;
|
|
598
|
+
}
|
|
599
|
+
k.restore();
|
|
600
|
+
}
|
|
601
|
+
// emanata (shock lines) around head
|
|
602
|
+
k.lineWidth = 8;
|
|
603
|
+
if (t > 1.45) {
|
|
604
|
+
const throb = 1 + 0.12 * (Math.floor(t * 6) % 2); // on-twos shock pulse
|
|
605
|
+
for (const [a, r0, r1] of [[-2.2, 380, 440], [-2.0, 390, 455], [-1.8, 384, 430], [-1.0, 395, 445], [-0.8, 380, 438]]) {
|
|
606
|
+
const r1b = r0 + (r1 - r0) * throb;
|
|
607
|
+
k.beginPath(); k.moveTo(hx + Math.cos(a) * r0, hy - 30 + Math.sin(a) * r0); k.lineTo(hx + Math.cos(a) * r1b, hy - 30 + Math.sin(a) * r1b); k.stroke();
|
|
608
|
+
}
|
|
609
|
+
}
|
|
610
|
+
// caption box
|
|
611
|
+
cap(k, () => {
|
|
612
|
+
k.lineWidth = 6; k.strokeRect(PX + 18, PY + 16, 412, 64);
|
|
613
|
+
k.font = '400 30px "Bangers"'; k.letterSpacing = '1.5px'; k.textBaseline = 'middle'; k.textAlign = 'left';
|
|
614
|
+
k.fillText('MEANWHILE, IN THE LAB...', PX + 36, PY + 50);
|
|
615
|
+
});
|
|
616
|
+
k.textBaseline = 'middle';
|
|
617
|
+
// balloon
|
|
618
|
+
bal(k, () => { k.lineWidth = 8; bubblePath(k); k.stroke(); });
|
|
619
|
+
k.textAlign = 'center'; k.letterSpacing = '2px';
|
|
620
|
+
word(k, 2.12, 'I THINK...', 58, 330, 250);
|
|
621
|
+
word(k, 2.42, 'I FEEL', 70, 330, 318);
|
|
622
|
+
word(k, 2.72, 'SOMETHING!', 66, 336, 386);
|
|
623
|
+
k.restore();
|
|
624
|
+
// panel border
|
|
625
|
+
{ const per = 2 * (PW + PH), u = E.inOut(H.clamp(t / 0.4));
|
|
626
|
+
k.lineWidth = 9; k.setLineDash([per * u, per]); k.strokeRect(PX, PY, PW, PH); k.setLineDash([]); }
|
|
627
|
+
|
|
628
|
+
// ---------- PRINT: paper, misregistered plates, dot gain, grain ----------
|
|
629
|
+
ctx.drawImage(col, 0, 0);
|
|
630
|
+
// offset colour plate ~2px vs key (press misregistration)
|
|
631
|
+
ctx.fillStyle = C.white; ctx.fillRect(0, 0, W, H_);
|
|
632
|
+
ctx.drawImage(col, 2.5, -1.5);
|
|
633
|
+
ctx.globalCompositeOperation = 'multiply';
|
|
634
|
+
ctx.drawImage(key, 0, 0);
|
|
635
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
636
|
+
const img = ctx.getImageData(0, 0, W, H_), d = img.data;
|
|
637
|
+
const nz = H.makeNoise(41);
|
|
638
|
+
const lo = H.layer(80, 45), lx = lo.getContext('2d'), li = lx.createImageData(80, 45);
|
|
639
|
+
for (let y = 0; y < 45; y++) for (let x = 0; x < 80; x++) { const i = (y * 80 + x) * 4; li.data[i] = 128 + 127 * nz.fbm2(x * 0.12, y * 0.12, 3); li.data[i + 3] = 255; }
|
|
640
|
+
lx.putImageData(li, 0, 0);
|
|
641
|
+
const big = H.layer(W, H_), bx = big.getContext('2d'); bx.drawImage(lo, 0, 0, W, H_);
|
|
642
|
+
const lf = bx.getImageData(0, 0, W, H_).data;
|
|
643
|
+
const r = H.rng(5);
|
|
644
|
+
for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) {
|
|
645
|
+
const i = (y * W + x) * 4;
|
|
646
|
+
const n = (lf[i] - 128) / 128, g = (r() - 0.5);
|
|
647
|
+
// paper yellowing toward edges + fibre grain
|
|
648
|
+
const ex = Math.min(x, W - x, y, H_ - y);
|
|
649
|
+
const age = 1 - 0.05 * (1 - H.smooth(0, 160, ex)) - 0.02 * n;
|
|
650
|
+
d[i] = d[i] * age * (1 + g * 0.05);
|
|
651
|
+
d[i + 1] = d[i + 1] * (age - 0.008) * (1 + g * 0.05);
|
|
652
|
+
d[i + 2] = d[i + 2] * (age - 0.04) * (1 + g * 0.06);
|
|
653
|
+
}
|
|
654
|
+
ctx.putImageData(img, 0, 0);
|
|
655
|
+
// slow camera push-in on the drama
|
|
656
|
+
const z = 1 + 0.02 * E.inOut(H.clamp((t - 1.9) / 3.1));
|
|
657
|
+
if (z > 1.0001) {
|
|
658
|
+
const cp = H.layer(W, H_); cp.getContext('2d').drawImage(ctx.canvas, 0, 0, W, H_);
|
|
659
|
+
const fx = 660, fy = 360;
|
|
660
|
+
ctx.drawImage(cp, fx - fx / z, fy - fy / z, W / z, H_ / z, 0, 0, W, H_);
|
|
661
|
+
}
|
|
662
|
+
},
|
|
663
|
+
});
|
|
664
|
+
}
|