@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,635 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------- long-exposure light painting ----------
|
|
3
|
+
const hsl = (h, s, l) => {
|
|
4
|
+
h = ((h % 360) + 360) % 360 / 360;
|
|
5
|
+
const q = l < 0.5 ? l * (1 + s) : l + s - l * s, p = 2 * l - q;
|
|
6
|
+
const f = (t) => { t = (t + 1) % 1; return t < 1 / 6 ? p + (q - p) * 6 * t : t < 0.5 ? q : t < 2 / 3 ? p + (q - p) * (2 / 3 - t) * 6 : p; };
|
|
7
|
+
return [f(h + 1 / 3) * 255, f(h) * 255, f(h - 1 / 3) * 255];
|
|
8
|
+
};
|
|
9
|
+
function blurSmall(src, px, s) {
|
|
10
|
+
const w = Math.ceil(src.width * s), h = Math.ceil(src.height * s), c = H.layer(w, h), x = c.getContext('2d');
|
|
11
|
+
x.filter = `blur(${Math.max(0.5, px * s)}px)`; x.drawImage(src, 0, 0, w, h); return c;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
// light-tube ribbon: an RGB tube swept round an ellipse and twisted as it goes (shared by the still and the explainer)
|
|
15
|
+
function lightRibbon(rb, nz, { cx0, cy0, rx, ry, skew = 30, tyk = 0.42, len = 112, twists = 3.4, hue0 = 175, hueSpan = 150, RN = 900, stri = 10, gain = 1, prog }) {
|
|
16
|
+
let pv = null, tip = null;
|
|
17
|
+
for (let i = 0; i < RN; i++) {
|
|
18
|
+
const u = i / (RN - 1);
|
|
19
|
+
if (u > prog) break;
|
|
20
|
+
const a = Math.PI * (0.62 + u * 1.72);
|
|
21
|
+
const cx = cx0 + Math.cos(a) * rx + nz.n2(u * 6, 1) * 6, cy = cy0 + Math.sin(a) * ry - Math.cos(a) * skew + nz.n2(u * 6, 4) * 6;
|
|
22
|
+
const tw = Math.cos(u * Math.PI * twists + 0.4);
|
|
23
|
+
const ln = len * tw;
|
|
24
|
+
const tx = -Math.sin(a), ty = Math.cos(a) * tyk;
|
|
25
|
+
const nl = Math.hypot(tx, ty), nx = -ty / nl, ny = tx / nl;
|
|
26
|
+
const cur = [cx - nx * ln / 2, cy - ny * ln / 2, cx + nx * ln / 2, cy + ny * ln / 2];
|
|
27
|
+
if (pv) {
|
|
28
|
+
const hue = hue0 + u * hueSpan;
|
|
29
|
+
const dens = H.clamp(0.13 / (Math.abs(tw) + 0.15), 0.12, 0.75) * gain;
|
|
30
|
+
const gr = rb.createLinearGradient(cur[0], cur[1], cur[2], cur[3]);
|
|
31
|
+
gr.addColorStop(0, H.rgba(hsl(hue, 1, 0.6), dens)); gr.addColorStop(0.5, H.rgba(hsl(hue + 45, 1, 0.55), dens * 0.55)); gr.addColorStop(1, H.rgba(hsl(hue + 95, 1, 0.6), dens));
|
|
32
|
+
rb.fillStyle = gr; rb.beginPath(); rb.moveTo(pv[0], pv[1]); rb.lineTo(pv[2], pv[3]); rb.lineTo(cur[2], cur[3]); rb.lineTo(cur[0], cur[1]); rb.closePath(); rb.fill();
|
|
33
|
+
// individual LEDs leave fine parallel striations
|
|
34
|
+
for (let k = 1; k < stri; k++) {
|
|
35
|
+
const f = k / stri, hk = hue + f * 95;
|
|
36
|
+
rb.strokeStyle = H.rgba(hsl(hk, 1, 0.7), Math.min(0.5, dens * 0.55)); rb.lineWidth = 0.9;
|
|
37
|
+
rb.beginPath(); rb.moveTo(pv[0] + (pv[2] - pv[0]) * f, pv[1] + (pv[3] - pv[1]) * f); rb.lineTo(cur[0] + (cur[2] - cur[0]) * f, cur[1] + (cur[3] - cur[1]) * f); rb.stroke();
|
|
38
|
+
}
|
|
39
|
+
// hot tube ends trace the edges
|
|
40
|
+
rb.strokeStyle = H.rgba(hsl(hue, 1, 0.8), 0.5 * gain); rb.lineWidth = 1.8; rb.beginPath(); rb.moveTo(pv[0], pv[1]); rb.lineTo(cur[0], cur[1]); rb.stroke();
|
|
41
|
+
rb.strokeStyle = H.rgba(hsl(hue + 95, 1, 0.8), 0.5 * gain); rb.beginPath(); rb.moveTo(pv[2], pv[3]); rb.lineTo(cur[2], cur[3]); rb.stroke();
|
|
42
|
+
}
|
|
43
|
+
pv = cur;
|
|
44
|
+
if (prog > 0 && prog < 1) tip = [(cur[0] + cur[2]) / 2, (cur[1] + cur[3]) / 2, hsl(hue0 + u * hueSpan, 1, 0.7), cur];
|
|
45
|
+
}
|
|
46
|
+
return tip;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ghost of the painter (moved during the exposure): silhouette G, trail-lit colour Gc and a rim-lit edge Rim. Static, cached.
|
|
50
|
+
let ghostCache = null;
|
|
51
|
+
function getGhost(W, Hh) {
|
|
52
|
+
if (ghostCache && ghostCache.W === W && ghostCache.H === Hh) return ghostCache;
|
|
53
|
+
const G = H.layer(W, Hh), gx = G.getContext('2d');
|
|
54
|
+
const ghost = (ox, oy, a) => {
|
|
55
|
+
gx.save(); gx.translate(ox, oy); gx.globalAlpha = a;
|
|
56
|
+
gx.fillStyle = 'rgb(255,255,255)'; gx.strokeStyle = 'rgb(255,255,255)'; gx.lineCap = 'round'; gx.lineJoin = 'round';
|
|
57
|
+
gx.beginPath(); gx.ellipse(1012, 330, 19, 24, 0.1, 0, 7); gx.fill(); // head
|
|
58
|
+
gx.beginPath(); gx.moveTo(1004, 352); gx.lineTo(1020, 352); gx.lineTo(1022, 362); gx.lineTo(1002, 362); gx.fill(); // neck
|
|
59
|
+
gx.beginPath(); gx.moveTo(976, 372); gx.quadraticCurveTo(1012, 356, 1048, 374); // shoulders
|
|
60
|
+
gx.quadraticCurveTo(1054, 420, 1046, 480); gx.lineTo(1040, 500); gx.lineTo(984, 500); gx.lineTo(976, 470);
|
|
61
|
+
gx.quadraticCurveTo(970, 420, 976, 372); gx.fill();
|
|
62
|
+
gx.lineWidth = 22; gx.beginPath(); gx.moveTo(996, 500); gx.lineTo(990, 580); gx.lineTo(984, 650); gx.stroke(); // legs
|
|
63
|
+
gx.beginPath(); gx.moveTo(1028, 500); gx.lineTo(1036, 580); gx.lineTo(1046, 650); gx.stroke();
|
|
64
|
+
gx.lineWidth = 15; gx.beginPath(); gx.moveTo(982, 380); gx.quadraticCurveTo(944, 330, 912, 272); gx.stroke(); // raised arm
|
|
65
|
+
gx.beginPath(); gx.moveTo(1044, 384); gx.quadraticCurveTo(1062, 440, 1052, 492); gx.stroke(); // other arm
|
|
66
|
+
gx.restore();
|
|
67
|
+
};
|
|
68
|
+
ghost(0, 0, 0.6); ghost(-8, 2, 0.25);
|
|
69
|
+
// lit by the coloured trails from the left/front, cool rim + warm wand light from above
|
|
70
|
+
const Gc = H.layer(W, Hh), gcx = Gc.getContext('2d');
|
|
71
|
+
gcx.drawImage(G, 0, 0); gcx.globalCompositeOperation = 'source-in';
|
|
72
|
+
const gl = gcx.createLinearGradient(960, 260, 1060, 640);
|
|
73
|
+
gl.addColorStop(0, 'rgb(150,110,120)'); gl.addColorStop(0.4, 'rgb(90,70,140)'); gl.addColorStop(1, 'rgb(30,30,60)');
|
|
74
|
+
gcx.fillStyle = gl; gcx.fillRect(0, 0, W, Hh);
|
|
75
|
+
// rim light: the edge facing the light trails (left / top) catches cyan-magenta spill, so the figure reads as a body
|
|
76
|
+
const Rim = H.layer(W, Hh), rx = Rim.getContext('2d');
|
|
77
|
+
rx.drawImage(G, 0, 0); rx.globalCompositeOperation = 'destination-out'; rx.drawImage(G, 6, 3);
|
|
78
|
+
rx.globalCompositeOperation = 'source-in';
|
|
79
|
+
const rg = rx.createLinearGradient(900, 260, 1060, 660);
|
|
80
|
+
rg.addColorStop(0, 'rgb(255,200,170)'); rg.addColorStop(0.35, 'rgb(150,120,255)'); rg.addColorStop(0.7, 'rgb(70,160,220)'); rg.addColorStop(1, 'rgb(20,40,70)');
|
|
81
|
+
rx.fillStyle = rg; rx.fillRect(0, 0, W, Hh);
|
|
82
|
+
// pre-blur once and keep only the figure's bounding box, so each frame is three small, filter-free draws
|
|
83
|
+
const BX = 860, BY = 236, BW = 240, BH = 450;
|
|
84
|
+
const crop = (src, b) => { const c = H.layer(BW, BH), x = c.getContext('2d'); x.filter = `blur(${b}px)`; x.drawImage(src, -BX, -BY); return c; };
|
|
85
|
+
return (ghostCache = { W, H: Hh, BX, BY, G: crop(G, 2), Gc: crop(Gc, 2.5), Rim: crop(Rim, 1.2) });
|
|
86
|
+
}
|
|
87
|
+
const drawGhost = (x, gh, dx, a) => {
|
|
88
|
+
if (a <= 0) return;
|
|
89
|
+
x.save();
|
|
90
|
+
x.globalCompositeOperation = 'multiply'; x.globalAlpha = 0.45 * a; x.drawImage(gh.G, gh.BX + dx, gh.BY);
|
|
91
|
+
x.globalCompositeOperation = 'lighter'; x.globalAlpha = 0.32 * a; x.drawImage(gh.Gc, gh.BX + dx, gh.BY);
|
|
92
|
+
x.globalAlpha = 0.5 * a; x.drawImage(gh.Rim, gh.BX + dx, gh.BY);
|
|
93
|
+
x.restore();
|
|
94
|
+
};
|
|
95
|
+
// ground spill mask: fades away from the horizon and breaks up in patches of wet grass (cached)
|
|
96
|
+
let spillMask = null;
|
|
97
|
+
function getSpillMask(SW, SH) {
|
|
98
|
+
if (spillMask && spillMask.width === SW && spillMask.height === SH) return spillMask;
|
|
99
|
+
const m = H.layer(SW, SH), x = m.getContext('2d'), id = x.createImageData(SW, SH), nz = H.makeNoise(4242);
|
|
100
|
+
for (let y = 0; y < SH; y++) for (let q = 0; q < SW; q++) {
|
|
101
|
+
const v = Math.pow(1 - y / SH, 1.25) * H.clamp(0.5 + 0.75 * nz.fbm2(q * 0.045, y * 0.3, 3) + 0.1, 0, 1);
|
|
102
|
+
const i = (y * SW + q) * 4; id.data[i] = id.data[i + 1] = id.data[i + 2] = 255; id.data[i + 3] = v * 255;
|
|
103
|
+
}
|
|
104
|
+
x.putImageData(id, 0, 0);
|
|
105
|
+
return (spillMask = m);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// static caches: eroded lettering cores (per word) and finished ribbons (per beat)
|
|
109
|
+
const lpCores = new Map(), lpRibbons = {};
|
|
110
|
+
|
|
111
|
+
// ================= EXPLAINER: a live long exposure; whip-pans smear everything into streaks between beats =================
|
|
112
|
+
let lpBg = null;
|
|
113
|
+
function getLpBg(W, Hh) {
|
|
114
|
+
if (lpBg) return lpBg;
|
|
115
|
+
const BW = W * 3, HZ = 560, c = H.layer(BW, Hh), x = c.getContext('2d'), R = H.rng(3131), nz = H.makeNoise(3132);
|
|
116
|
+
const sky = x.createLinearGradient(0, 0, 0, HZ); sky.addColorStop(0, '#02030a'); sky.addColorStop(0.6, '#060a1c'); sky.addColorStop(1, '#151a2e');
|
|
117
|
+
x.fillStyle = sky; x.fillRect(0, 0, BW, HZ + 2);
|
|
118
|
+
for (let k = 0; k < 1500; k++) {
|
|
119
|
+
const sx = R() * BW, sy = Math.pow(R(), 1.3) * (HZ - 30), m = Math.pow(R(), 5);
|
|
120
|
+
x.fillStyle = H.rgba(R.pick([[255, 255, 255], [200, 215, 255], [255, 230, 200]]), (0.25 + m * 0.75) * (1 - H.smooth(HZ - 160, HZ, sy) * 0.8));
|
|
121
|
+
x.beginPath(); x.arc(sx, sy, 0.6 + m * 1.3, 0, 7); x.fill();
|
|
122
|
+
}
|
|
123
|
+
x.fillStyle = '#04050a'; x.beginPath(); x.moveTo(0, HZ);
|
|
124
|
+
for (let px = 0; px <= BW; px += 4) x.lineTo(px, HZ - 14 - 16 * Math.sin(px * 0.004 + 1.2) - Math.max(0, nz.fbm2(px * 0.03, 2.2, 3)) * 46 - Math.max(0, nz.n2(px * 0.08, 5)) * 10);
|
|
125
|
+
x.lineTo(BW, Hh); x.lineTo(0, Hh); x.fill();
|
|
126
|
+
for (let k = 0; k < 7000; k++) { const gx = R() * BW, gy2 = HZ + Math.pow(R(), 0.7) * (Hh - HZ); x.strokeStyle = `rgba(30,36,48,${R.range(0.2, 0.6)})`; x.beginPath(); x.moveTo(gx, gy2); x.lineTo(gx + R.range(-3, 3), gy2 - R.range(3, 8)); x.stroke(); }
|
|
127
|
+
x.globalCompositeOperation = 'lighter';
|
|
128
|
+
for (let k = 0; k < 26; k++) { const bx = R() * BW, by = HZ - 10 - R() * 30, r = 8 + R() * 16, cc = R.pick([[255, 170, 90], [255, 200, 120], [170, 210, 255]]);
|
|
129
|
+
const g = x.createRadialGradient(bx, by, 0, bx, by, r); g.addColorStop(0, H.rgba(cc, 0.16)); g.addColorStop(0.85, H.rgba(cc, 0.28)); g.addColorStop(1, H.rgba(cc, 0)); x.fillStyle = g; x.beginPath(); x.arc(bx, by, r, 0, 7); x.fill(); }
|
|
130
|
+
return (lpBg = c);
|
|
131
|
+
}
|
|
132
|
+
function lpCat(x, cx, cy, s) {
|
|
133
|
+
x.beginPath(); x.moveTo(cx - s * 0.95, cy - s * 0.1); x.lineTo(cx - s * 0.8, cy - s * 1.05); x.lineTo(cx - s * 0.35, cy - s * 0.72);
|
|
134
|
+
x.quadraticCurveTo(cx, cy - s * 0.82, cx + s * 0.35, cy - s * 0.72); x.lineTo(cx + s * 0.8, cy - s * 1.05); x.lineTo(cx + s * 0.95, cy - s * 0.1);
|
|
135
|
+
x.bezierCurveTo(cx + s * 1.0, cy + s * 0.75, cx - s * 1.0, cy + s * 0.75, cx - s * 0.95, cy - s * 0.1);
|
|
136
|
+
x.moveTo(cx - s * 0.45, cy - s * 0.14); x.lineTo(cx - s * 0.2, cy - s * 0.1); x.moveTo(cx + s * 0.45, cy - s * 0.14); x.lineTo(cx + s * 0.2, cy - s * 0.1);
|
|
137
|
+
x.moveTo(cx - s * 0.38, cy + s * 0.18); x.lineTo(cx - s * 1.05, cy + s * 0.06); x.moveTo(cx + s * 0.38, cy + s * 0.18); x.lineTo(cx + s * 1.05, cy + s * 0.06);
|
|
138
|
+
x.moveTo(cx - s * 0.08, cy + s * 0.12); x.lineTo(cx + s * 0.08, cy + s * 0.12); x.lineTo(cx, cy + s * 0.22);
|
|
139
|
+
}
|
|
140
|
+
function lpDog(x, cx, cy, s) {
|
|
141
|
+
x.beginPath(); x.moveTo(cx - s * 0.55, cy - s * 0.7); x.quadraticCurveTo(cx, cy - s * 0.95, cx + s * 0.55, cy - s * 0.7);
|
|
142
|
+
x.bezierCurveTo(cx + s * 0.78, cy - s * 0.1, cx + s * 0.55, cy + s * 0.78, cx, cy + s * 0.8); x.bezierCurveTo(cx - s * 0.55, cy + s * 0.78, cx - s * 0.78, cy - s * 0.1, cx - s * 0.55, cy - s * 0.7);
|
|
143
|
+
x.moveTo(cx - s * 0.55, cy - s * 0.7); x.bezierCurveTo(cx - s * 1.2, cy - s * 0.6, cx - s * 1.15, cy + s * 0.3, cx - s * 0.8, cy + s * 0.25);
|
|
144
|
+
x.moveTo(cx + s * 0.55, cy - s * 0.7); x.bezierCurveTo(cx + s * 1.2, cy - s * 0.6, cx + s * 1.15, cy + s * 0.3, cx + s * 0.8, cy + s * 0.25);
|
|
145
|
+
x.moveTo(cx - s * 0.2, cy - s * 0.2); x.arc(cx - s * 0.26, cy - s * 0.2, s * 0.06, 0, 7); x.moveTo(cx + s * 0.32, cy - s * 0.2); x.arc(cx + s * 0.26, cy - s * 0.2, s * 0.06, 0, 7);
|
|
146
|
+
x.moveTo(cx - s * 0.16, cy + s * 0.18); x.quadraticCurveTo(cx, cy + s * 0.06, cx + s * 0.16, cy + s * 0.18); x.quadraticCurveTo(cx, cy + s * 0.32, cx - s * 0.16, cy + s * 0.18);
|
|
147
|
+
}
|
|
148
|
+
const LED = { '0': '01110100011001110101110011000101110', '1': '00100011000010000100001000010001110', '2': '01110100010000100010001000100011111', '3': '11110000010000101110000010000111110',
|
|
149
|
+
'4': '00010001100101010010111110001000010', '5': '11111100001111000001000011000101110', '6': '00110010001000011110100011000101110', '7': '11111000010001000100010000100001000',
|
|
150
|
+
'8': '01110100011000101110100011000101110', '9': '01110100011000101111000010001001100', '%': '11001110100001000100010000101110011', ',': '00000000000000000000001100010001000' };
|
|
151
|
+
|
|
152
|
+
function lightExplainer(ctx, W, Hh, t) {
|
|
153
|
+
const sm = H.smooth, E = H.ease, Rn = H.rng(31 + Math.floor(t * 30));
|
|
154
|
+
// beats live in separate exposures; whip-pans between them: [start, end-of-pan]
|
|
155
|
+
const pans = [[2.05, 2.5], [5.9, 6.35], [10.3, 10.75], [12.85, 13.3]];
|
|
156
|
+
let beat = 0; pans.forEach(([a, b]) => { if (t >= (a + b) / 2) beat++; });
|
|
157
|
+
const panP = pans.map(([a, b]) => sm(a, b, t));
|
|
158
|
+
let camX = 0; panP.forEach((p) => camX += p * 560);
|
|
159
|
+
const smear = pans.reduce((m, [a, b]) => Math.max(m, t > a && t < b ? Math.sin(Math.PI * (t - a) / (b - a)) : 0), 0);
|
|
160
|
+
const Ccol = H.layer(W, Hh), cc = Ccol.getContext('2d'), Ccore = H.layer(W, Hh), co = Ccore.getContext('2d');
|
|
161
|
+
[cc, co].forEach((x) => { x.globalCompositeOperation = 'lighter'; x.lineCap = 'round'; x.lineJoin = 'round'; });
|
|
162
|
+
let tip = null;
|
|
163
|
+
const C = { cyan: [60, 220, 255], pink: [255, 70, 190], amber: [255, 180, 60], violet: [170, 90, 255], red: [255, 50, 40], green: [80, 255, 120], white: [235, 240, 255] };
|
|
164
|
+
const pen = (id, pts, c0, c1, w, prog, gaps = 0) => {
|
|
165
|
+
if (prog <= 0) return; const jn = H.makeNoise(id); let prev = null;
|
|
166
|
+
for (let i = 0; i < pts.length; i++) {
|
|
167
|
+
const u = i / (pts.length - 1); if (u > prog) break;
|
|
168
|
+
const x = pts[i][0] + jn.n2(u * 9, 0.5) * 2.2, y = pts[i][1] + jn.n2(u * 9, 7.5) * 2.2;
|
|
169
|
+
if (prev && !(gaps && jn.n2(u * 6, 11) > 0.45)) {
|
|
170
|
+
const sp = 0.65 + 0.35 * jn.n2(u * 4, 3), c = H.mix(c0, c1, u);
|
|
171
|
+
cc.strokeStyle = H.rgba(c, 0.6 * sp); cc.lineWidth = w * (0.8 + sp * 0.4); cc.beginPath(); cc.moveTo(...prev); cc.lineTo(x, y); cc.stroke();
|
|
172
|
+
co.strokeStyle = H.rgba(H.mix(c, [255, 255, 255], 0.75), 0.55 * sp); co.lineWidth = w * 0.38; co.beginPath(); co.moveTo(...prev); co.lineTo(x, y); co.stroke();
|
|
173
|
+
}
|
|
174
|
+
prev = [x, y];
|
|
175
|
+
}
|
|
176
|
+
if (prog < 1 && prev) tip = [prev[0], prev[1], H.mix(c0, c1, prog)];
|
|
177
|
+
};
|
|
178
|
+
const line = (a, b, n = 40) => Array.from({ length: n }, (_, i) => [a[0] + (b[0] - a[0]) * i / (n - 1), a[1] + (b[1] - a[1]) * i / (n - 1)]);
|
|
179
|
+
const quad = (a, q, b, n = 50) => Array.from({ length: n }, (_, i) => { const u = i / (n - 1); return [(1 - u) * (1 - u) * a[0] + 2 * (1 - u) * u * q[0] + u * u * b[0], (1 - u) * (1 - u) * a[1] + 2 * (1 - u) * u * q[1] + u * u * b[1]]; });
|
|
180
|
+
const loop = (cx, cy, r, n = 90) => Array.from({ length: n }, (_, i) => { const a = i / (n - 1) * Math.PI * 4.3 - 1.2, rr = r * (1 + 0.07 * Math.sin(a * 0.5)) + i * 0.02; return [cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]; });
|
|
181
|
+
const arrowPen = (id, pts, c0, c1, w, prog) => {
|
|
182
|
+
pen(id, pts, c0, c1, w, prog);
|
|
183
|
+
if (prog >= 1) { const e = pts[pts.length - 1], q = pts[pts.length - 4], a = Math.atan2(e[1] - q[1], e[0] - q[0]);
|
|
184
|
+
pen(id + 500, [[e[0] + Math.cos(a + 2.5) * 24, e[1] + Math.sin(a + 2.5) * 24], ...line(e, e, 2), [e[0] + Math.cos(a - 2.5) * 24, e[1] + Math.sin(a - 2.5) * 24]], c1, c1, w, 1); }
|
|
185
|
+
};
|
|
186
|
+
const pathPen = (pathFn, col, w, prog, cx, cy, r) => { // a doodle traced with the wand, revealed in a sweep
|
|
187
|
+
if (prog <= 0) return;
|
|
188
|
+
for (const [x, k, ww] of [[cc, 0.6, w], [co, 0.55, w * 0.38]]) { x.save(); x.beginPath(); x.rect(cx - r * 1.3, cy - r * 1.3, r * 2.6 * Math.min(1, prog), r * 2.7); x.clip();
|
|
189
|
+
x.strokeStyle = H.rgba(x === co ? H.mix(col, [255, 255, 255], 0.75) : col, k); x.lineWidth = ww; pathFn(x); x.stroke(); x.restore(); }
|
|
190
|
+
if (prog < 1) tip = [cx - r * 1.3 + r * 2.6 * prog, cy + Math.sin(prog * 30) * r * 0.6, col];
|
|
191
|
+
};
|
|
192
|
+
// handwriting traced with the wand: the glyph laid down a few times with hand shake (soft coloured falloff),
|
|
193
|
+
// and a thin, eroded white-hot core that flickers along its length
|
|
194
|
+
const write = (s, x, y, size, col, prog, align = 'center') => {
|
|
195
|
+
if (prog <= 0) return; cc.font = co.font = `700 ${size}px "Caveat"`; const w = cc.measureText(s).width, x0 = align === 'center' ? x - w / 2 : x;
|
|
196
|
+
const jn = H.makeNoise(s.length * 31 + size), cw = (w + 12) * Math.min(1, prog);
|
|
197
|
+
cc.save(); cc.beginPath(); cc.rect(x0 - 6, y - size, cw, size * 1.5); cc.clip(); cc.textAlign = 'left';
|
|
198
|
+
cc.fillStyle = H.rgba(col, 0.66); cc.fillText(s, x0, y);
|
|
199
|
+
for (let k = 0; k < 2; k++) { cc.fillStyle = H.rgba(col, 0.32); cc.fillText(s, x0 + jn.n2(k * 3.1, 0.5) * 1.8 + (k ? 2 : -1), y + jn.n2(k * 3.1, 7.5) * 1.8); }
|
|
200
|
+
cc.restore();
|
|
201
|
+
// eroded core, sized to the word (static per word, so cached)
|
|
202
|
+
const bw = Math.ceil(w + 24), bh = Math.ceil(size * 1.6), er = Math.max(1.5, size * 0.03), ck = s + '|' + size + '|' + col.join(',');
|
|
203
|
+
let E2 = lpCores.get(ck);
|
|
204
|
+
if (!E2) {
|
|
205
|
+
const T = H.layer(bw, bh), tx = T.getContext('2d'); tx.font = cc.font; tx.textAlign = 'left'; tx.fillStyle = '#fff'; tx.fillText(s, 12, size * 1.1);
|
|
206
|
+
E2 = H.layer(bw, bh); const ex = E2.getContext('2d'); ex.drawImage(T, 0, 0); ex.globalCompositeOperation = 'destination-in';
|
|
207
|
+
for (let k = 0; k < 8; k++) ex.drawImage(T, Math.cos(k * Math.PI / 4) * er, Math.sin(k * Math.PI / 4) * er);
|
|
208
|
+
ex.globalCompositeOperation = 'source-in'; ex.fillStyle = H.rgba(H.mix(col, [255, 255, 255], 0.85), 0.95); ex.fillRect(0, 0, bw, bh);
|
|
209
|
+
// flicker: the core dims in a few places where the wand turned away
|
|
210
|
+
ex.globalCompositeOperation = 'destination-out';
|
|
211
|
+
for (let q = 0; q < bw; q += 6) { const g = jn.n2(q * 0.02, 11); if (g > 0.35) { ex.fillStyle = `rgba(0,0,0,${Math.min(0.75, (g - 0.35) * 2.5)})`; ex.fillRect(q, 0, 6, bh); } }
|
|
212
|
+
if (lpCores.size > 64) lpCores.clear();
|
|
213
|
+
lpCores.set(ck, E2);
|
|
214
|
+
}
|
|
215
|
+
co.save(); co.beginPath(); co.rect(x0 - 6, y - size, cw, size * 1.5); co.clip();
|
|
216
|
+
co.globalAlpha = 0.35; co.fillStyle = H.rgba(H.mix(col, [255, 255, 255], 0.6), 1); co.fillText(s, x0, y);
|
|
217
|
+
co.globalAlpha = 1; co.drawImage(E2, 0, 0, bw, bh, x0 - 12, y - size * 1.1, bw, bh);
|
|
218
|
+
co.restore();
|
|
219
|
+
if (prog < 1) tip = [x0 + w * prog, y - size * 0.3 + Math.sin(prog * 50) * size * 0.2, col];
|
|
220
|
+
};
|
|
221
|
+
const led = (s, x, y, dot, col, k = 1) => { // LED pixel-stick read-out
|
|
222
|
+
let px = x; for (const ch of s) { const gph = LED[ch]; if (!gph) { px += dot * 3; continue; }
|
|
223
|
+
for (let r = 0; r < 7; r++) for (let q = 0; q < 5; q++) if (gph[r * 5 + q] === '1') {
|
|
224
|
+
const dx = px + q * dot, dy = y + r * dot; cc.fillStyle = H.rgba(col, 0.85 * k); cc.beginPath(); cc.arc(dx, dy, dot * 0.36, 0, 7); cc.fill();
|
|
225
|
+
co.fillStyle = H.rgba([255, 255, 255], 0.7 * k); co.beginPath(); co.arc(dx, dy, dot * 0.16, 0, 7); co.fill(); }
|
|
226
|
+
px += dot * 6; }
|
|
227
|
+
return px - x;
|
|
228
|
+
};
|
|
229
|
+
const net = (id, ox, oy, sc, prog, cols, conP = prog) => {
|
|
230
|
+
const L = [[-1, 3], [0, 4], [1, 2]], P = L.map(([lx, n]) => Array.from({ length: n }, (_, k) => [ox + lx * 150 * sc, oy + (k - (n - 1) / 2) * 92 * sc]));
|
|
231
|
+
let ci = 0;
|
|
232
|
+
for (let li = 0; li < 2; li++) P[li].forEach((a) => P[li + 1].forEach((b) => { const dx = b[0] - a[0], dy = b[1] - a[1], L2 = Math.hypot(dx, dy);
|
|
233
|
+
pen(id + ci, line([a[0] + dx / L2 * 24 * sc, a[1] + dy / L2 * 24 * sc], [b[0] - dx / L2 * 24 * sc, b[1] - dy / L2 * 24 * sc], 30), cols(ci, li), cols(ci, li + 1), 2.6, sm(ci * 0.03, ci * 0.03 + 0.5, conP), 1); ci++; }));
|
|
234
|
+
P.forEach((ln, li) => ln.forEach(([x, y], k) => pen(id + 100 + li * 10 + k, loop(x, y, 18 * sc), [C.cyan, C.violet, C.amber][li], [C.cyan, C.violet, C.amber][li], 4, sm(li * 0.2 + k * 0.05, li * 0.2 + k * 0.05 + 0.4, prog))));
|
|
235
|
+
return P;
|
|
236
|
+
};
|
|
237
|
+
// the RGB tube goes straight into the colour layer; once a beat's ribbon is complete it is a static, cached image
|
|
238
|
+
const ribbon = (key, opts) => {
|
|
239
|
+
if (opts.prog >= 1) {
|
|
240
|
+
let c = lpRibbons[key];
|
|
241
|
+
if (!c || c.width !== W) { c = lpRibbons[key] = H.layer(W, Hh); const x = c.getContext('2d'); x.globalCompositeOperation = 'lighter'; lightRibbon(x, H.makeNoise(opts.seed), opts); }
|
|
242
|
+
cc.drawImage(c, 0, 0);
|
|
243
|
+
} else {
|
|
244
|
+
const rt = lightRibbon(cc, H.makeNoise(opts.seed), opts);
|
|
245
|
+
// the tube itself, white-hot, at the leading edge of the sweep
|
|
246
|
+
if (rt && rt[3]) { co.strokeStyle = 'rgba(255,255,255,0.85)'; co.lineWidth = 3; co.beginPath(); co.moveTo(rt[3][0], rt[3][1]); co.lineTo(rt[3][2], rt[3][3]); co.stroke(); }
|
|
247
|
+
}
|
|
248
|
+
};
|
|
249
|
+
let ghostA = 0, ghostDX = 0;
|
|
250
|
+
// ---------------- beats ----------------
|
|
251
|
+
if (beat === 0) {
|
|
252
|
+
write('How does', 640, 270, 96, C.cyan, sm(0.15, 0.75, t));
|
|
253
|
+
write('AI learn?', 640, 410, 150, C.pink, sm(0.8, 1.6, t));
|
|
254
|
+
pen(9, quad([380, 450], [640, 490], [920, 440]), C.amber, C.amber, 5, sm(1.6, 1.9, t));
|
|
255
|
+
} else if (beat === 1) {
|
|
256
|
+
// the RGB tube swept round the network while it is traced (same window as the net)
|
|
257
|
+
ribbon(1, { seed: 5101, cx0: 930, cy0: 290, rx: 330, ry: 215, skew: 24, tyk: 0.65, len: 86, twists: 3.0, RN: 170, stri: 4, gain: 0.65, prog: E.inOut(sm(2.45, 3.2, t)) });
|
|
258
|
+
ghostA = 0.65 * sm(2.45, 4.2, t); ghostDX = 175;
|
|
259
|
+
const P = net(1000, 930, 290, 1.2, sm(2.45, 3.2, t), (ci, li) => [C.cyan, C.violet, C.amber][li]);
|
|
260
|
+
for (let k = 0; k < 6; k++) {
|
|
261
|
+
const cx = 120 + (k % 2) * 210, cy = 130 + Math.floor(k / 2) * 185, kind = k % 3 === 1 ? 'dog' : 'cat', t0 = 2.55 + k * 0.32;
|
|
262
|
+
pathPen((x) => (kind === 'cat' ? lpCat : lpDog)(x, cx, cy, 54), kind === 'cat' ? C.amber : C.cyan, 5, sm(t0, t0 + 0.28, t), cx, cy, 54);
|
|
263
|
+
write(kind, cx, cy + 94, 48, C.white, sm(t0 + 0.2, t0 + 0.4, t));
|
|
264
|
+
const tgt = P[0][k % 3];
|
|
265
|
+
pen(200 + k, quad([cx + 60, cy], [(cx + tgt[0]) / 2, Math.min(cy, tgt[1]) - 90], [tgt[0] - 30, tgt[1]]), kind === 'cat' ? C.amber : C.cyan, C.white, 7, sm(t0 + 0.4, t0 + 0.85, t));
|
|
266
|
+
}
|
|
267
|
+
const p = sm(2.9, 5.4, t), v = p >= 1 ? 1000000 : Math.round(Math.pow(10, 6 * p)), s = v.toLocaleString('en-US');
|
|
268
|
+
const wdt = led(s, 930 - (s.length * 6 * 10) / 2, 515, 10, C.amber, sm(2.85, 3.0, t));
|
|
269
|
+
write('examples', 930, 640, 54, C.white, sm(3.0, 3.3, t));
|
|
270
|
+
} else if (beat === 2) {
|
|
271
|
+
pathPen((x) => lpCat(x, 200, 330, 70), C.amber, 5, sm(6.35, 6.75, t), 200, 330, 70);
|
|
272
|
+
const adj = sm(9.0, 9.45, t);
|
|
273
|
+
const P = net(2000, 620, 330, 1.05, sm(6.4, 7.0, t), (ci, li) => [C.cyan, C.violet, C.amber][li]);
|
|
274
|
+
if (adj > 0) net(3000, 620, 330, 1.05, 1, (ci) => (ci % 3 === 0 ? C.green : C.white), adj); // weights re-painted
|
|
275
|
+
arrowPen(40, quad([300, 300], [360, 250], [440, 300]), C.white, C.white, 4, sm(6.95, 7.2, t));
|
|
276
|
+
write('Guess', 370, 236, 54, C.white, sm(7.0, 7.3, t));
|
|
277
|
+
arrowPen(41, quad([800, 330], [840, 310], [880, 330]), C.white, C.white, 4, sm(7.3, 7.45, t));
|
|
278
|
+
write('dog?', 1010, 360, 100, C.pink, sm(7.5, 7.85, t));
|
|
279
|
+
pen(42, line([1130, 300], [1200, 370], 12), C.red, C.red, 7, sm(7.95, 8.05, t)); pen(43, line([1200, 300], [1130, 370], 12), C.red, C.red, 7, sm(8.05, 8.15, t));
|
|
280
|
+
pen(44, Array.from({ length: 70 }, (_, i) => { const a = -0.9 + i / 69 * Math.PI * 2.15; return [1005 + Math.cos(a) * 115, 330 + Math.sin(a) * 62]; }), C.red, C.red, 5, sm(8.1, 8.4, t));
|
|
281
|
+
write('Check', 1010, 240, 54, C.red, sm(8.15, 8.45, t));
|
|
282
|
+
arrowPen(45, quad([1000, 420], [880, 610], [690, 470]), C.red, C.red, 5, sm(8.45, 8.9, t));
|
|
283
|
+
write('Adjust', 870, 610, 54, C.red, sm(8.75, 9.05, t));
|
|
284
|
+
pen(46, line([940, 345], [1080, 330], 14), C.red, C.red, 6, sm(9.45, 9.55, t));
|
|
285
|
+
write('cat', 1010, 480, 100, C.green, sm(9.55, 9.85, t));
|
|
286
|
+
pen(47, [[1100, 450], [1120, 475], [1170, 410]].flatMap((p, i, a) => i ? line(a[i - 1], p, 10) : []), C.green, C.green, 7, sm(9.85, 10.0, t));
|
|
287
|
+
} else if (beat === 3) {
|
|
288
|
+
pen(50, [...line([140, 150], [140, 520], 20), ...line([140, 520], [760, 520], 30)], C.white, C.white, 3.5, sm(10.75, 11.1, t));
|
|
289
|
+
write('training rounds', 450, 580, 46, C.white, sm(10.95, 11.3, t));
|
|
290
|
+
const lp = sm(11.1, 12.3, t), pts = Array.from({ length: 60 }, (_, i) => { const r = i / 59 * 12; return [160 + r * 50, 520 - (0.97 - 0.36 * Math.exp(-r / 3.2) - 0.5) / 0.5 * 360]; });
|
|
291
|
+
for (let k = 0; k < 7; k++) pen(60 + k, pts.map(([x, y]) => [x, y - 9 + k * 3]), hsl(180 + k * 25, 1, 0.6), hsl(300 + k * 15, 1, 0.6), 2, lp); // RGB light-tube trace
|
|
292
|
+
write('Accuracy', 1010, 230, 64, C.cyan, sm(10.85, 11.15, t));
|
|
293
|
+
const pct = Math.round(61 + 36 * E.out(lp)), s = pct + '%';
|
|
294
|
+
led(s, 1010 - s.length * 6 * 11 / 2, 290, 11, pct >= 97 ? C.green : C.amber, sm(10.9, 11.05, t));
|
|
295
|
+
pen(70, Array.from({ length: 70 }, (_, i) => { const a = -1 + i / 69 * Math.PI * 2.2; return [1010 + Math.cos(a) * 170, 325 + Math.sin(a) * 92]; }), C.amber, C.pink, 5, sm(12.35, 12.7, t));
|
|
296
|
+
const burst = sm(12.3, 12.75, t);
|
|
297
|
+
if (burst > 0 && burst < 1) for (let k = 0; k < 24; k++) { const a = k / 24 * Math.PI * 2 + 0.3, r0 = 180 + burst * 40, r1 = r0 + 30 + (k % 3) * 18; pen(80 + k, line([1010 + Math.cos(a) * r0, 325 + Math.sin(a) * r0 * 0.6], [1010 + Math.cos(a) * r1, 325 + Math.sin(a) * r1 * 0.6], 4), C.amber, [255, 255, 255], 2.5, 1); }
|
|
298
|
+
} else {
|
|
299
|
+
ribbon(4, { seed: 5104, cx0: 640, cy0: 362, rx: 560, ry: 212, skew: 26, tyk: 0.38, len: 100, twists: 3.4, RN: 180, stri: 4, gain: 0.75, prog: E.inOut(sm(13.3, 14.4, t)) });
|
|
300
|
+
ghostA = 0.6 * sm(13.3, 14.6, t); ghostDX = 150;
|
|
301
|
+
write('More examples', 640, 250, 104, C.cyan, sm(13.3, 13.75, t));
|
|
302
|
+
arrowPen(90, line([640, 290], [640, 370], 20), C.amber, C.amber, 6, sm(13.75, 13.9, t));
|
|
303
|
+
write('better guesses', 640, 500, 132, C.pink, sm(13.9, 14.4, t));
|
|
304
|
+
}
|
|
305
|
+
// ---------- compose: landscape + accumulated light, camera whip-pan smears it all ----------
|
|
306
|
+
const bg = getLpBg(W, Hh);
|
|
307
|
+
const scene = H.layer(W, Hh), sx = scene.getContext('2d');
|
|
308
|
+
sx.drawImage(bg, -camX * 0.6 % (bg.width - W), 0);
|
|
309
|
+
if (ghostA > 0) drawGhost(sx, getGhost(W, Hh), ghostDX, ghostA);
|
|
310
|
+
sx.globalCompositeOperation = 'lighter';
|
|
311
|
+
const glow34 = blurSmall(Ccol, 34, 0.125);
|
|
312
|
+
// colour spill on the wet grass: the light mirrored about the horizon, squashed, fading and broken up in patches
|
|
313
|
+
{ const HZ = 560, SW = Math.ceil(W / 4), SH = Math.ceil((Hh - HZ) / 4), sp = H.layer(SW, SH), spx = sp.getContext('2d');
|
|
314
|
+
spx.setTransform(0.25, 0, 0, -0.0875, 0, 0.0875 * HZ); spx.drawImage(glow34, 0, 0, W, Hh);
|
|
315
|
+
spx.setTransform(1, 0, 0, 1, 0, 0); spx.globalCompositeOperation = 'destination-in'; spx.drawImage(getSpillMask(SW, SH), 0, 0);
|
|
316
|
+
sx.globalAlpha = 1; sx.drawImage(sp, 0, HZ, W, Hh - HZ); }
|
|
317
|
+
sx.globalAlpha = 0.5; sx.drawImage(glow34, 0, 0, W, Hh);
|
|
318
|
+
sx.globalAlpha = 0.8; sx.drawImage(blurSmall(Ccol, 9, 0.25), 0, 0, W, Hh);
|
|
319
|
+
sx.globalAlpha = 1; sx.drawImage(Ccol, 0, 0); sx.drawImage(Ccore, 0, 0);
|
|
320
|
+
sx.globalAlpha = 0.6; sx.drawImage(blurSmall(Ccore, 3, 0.5), 0, 0, W, Hh);
|
|
321
|
+
if (tip && smear < 0.05) { const tg = sx.createRadialGradient(tip[0], tip[1], 0, tip[0], tip[1], 34); tg.addColorStop(0, 'rgba(255,255,255,0.95)'); tg.addColorStop(0.15, H.rgba(tip[2], 0.6)); tg.addColorStop(1, H.rgba(tip[2], 0)); sx.globalAlpha = 1; sx.fillStyle = tg; sx.beginPath(); sx.arc(tip[0], tip[1], 34, 0, 7); sx.fill(); }
|
|
322
|
+
ctx.fillStyle = '#000'; ctx.fillRect(0, 0, W, Hh);
|
|
323
|
+
if (smear > 0.02) {
|
|
324
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
325
|
+
for (let k = 0; k < 28; k++) { ctx.globalAlpha = 0.14 * (1 - k / 28); ctx.drawImage(scene, -k * smear * 26 - smear * 80, 0); }
|
|
326
|
+
ctx.globalAlpha = 1; ctx.globalCompositeOperation = 'source-over';
|
|
327
|
+
} else ctx.drawImage(scene, 0, 0);
|
|
328
|
+
H.vignette(ctx, W, Hh, 0.55, '0,0,4');
|
|
329
|
+
const img = ctx.getImageData(0, 0, W, Hh), d = img.data;
|
|
330
|
+
for (let i = 0; i < d.length; i += 4) { const l = (d[i] + d[i + 1] + d[i + 2]) / 765, k = (1 - l) * 9 + 2, n = (Rn() - 0.5) * k; d[i] += n + (Rn() - 0.5) * k * 0.8; d[i + 1] += n; d[i + 2] += n + (Rn() - 0.5) * k; }
|
|
331
|
+
ctx.putImageData(img, 0, 0);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Sound: out in a dark field at night with a camera on a tripod. Each beat is one exposure: the shutter opens (soft
|
|
335
|
+
// click), the LED wand swishes through the air for exactly as long as it draws (short hand gestures, low and airy),
|
|
336
|
+
// orbs fly into the network and land with a soft glow tone, the pixel-stick ticks as numbers change, and the 97%
|
|
337
|
+
// ring goes off with a steel-wool spark crackle. Between beats the shutter closes and the tripod head is swung.
|
|
338
|
+
// The final exposure closes over a warm chord. A quiet night breeze and a soft pad sit underneath.
|
|
339
|
+
// All times are copied from lightExplainer() above (same pans table, smooth windows and doodle/orb schedule).
|
|
340
|
+
// Voiceover mode (k.vo): beds and pad drop out; small gestures, labels, orb flights and counter ticks are minor.
|
|
341
|
+
function lightSound(k) {
|
|
342
|
+
const sm = H.smooth, E = H.ease, D = 15;
|
|
343
|
+
const when = (f, v, a, b) => { for (let t = a; t <= b; t += 0.001) if (f(t) >= v) return t; return b; };
|
|
344
|
+
// the wand moving through air while it draws: a run of short, low, breathy hand gestures covering a..b
|
|
345
|
+
const wand = (a, b, g = 1, pan = 0, minor = true) => { const r = H.rng(Math.floor(a * 997)); let x = a;
|
|
346
|
+
while (x < b - 0.04) { const d = Math.min(b - x, 0.14 + r() * 0.1), up = r() > 0.5, f0 = 380 + r() * 120, f1 = f0 * (1.6 + r() * 0.5);
|
|
347
|
+
k.noise(x, { dur: d + 0.05, color: 'pink', filter: 'bandpass', f: up ? [[0, f0], [d * 0.6, f1], [d + 0.05, f0]] : [[0, f1], [d * 0.6, f0 * 1.1], [d + 0.05, f0]], q: 1.3, gain: 0.08 * g * (0.75 + r() * 0.4), a: d * 0.45, pan: pan + (r() - 0.5) * 0.3, minor });
|
|
348
|
+
x += d * (0.85 + r() * 0.2); } };
|
|
349
|
+
// camera shutter: open = light click; close = click with the mirror's soft knock
|
|
350
|
+
const shutterOpen = (t, g = 1) => { k.noise(t, { dur: 0.015, filter: 'bandpass', f: 1800, q: 1.5, gain: 0.25 * g, a: 0.001 }); k.tone(t, { f: [[0, 300], [0.03, 160]], dur: 0.04, gain: 0.12 * g }); };
|
|
351
|
+
const shutterClose = (t, g = 1) => { k.noise(t, { dur: 0.018, filter: 'bandpass', f: 1500, q: 1.3, gain: 0.3 * g, a: 0.001 }); k.tone(t, { f: [[0, 200], [0.05, 90]], dur: 0.08, gain: 0.22 * g }); k.noise(t + 0.045, { dur: 0.02, filter: 'bandpass', f: 1100, q: 2, gain: 0.15 * g, a: 0.001 }); };
|
|
352
|
+
// tripod head swung hard (smoothstep pan a..b): low friction drag + air, fastest in the middle
|
|
353
|
+
const swing = (a, b) => { const d = b - a;
|
|
354
|
+
k.noise(a + 0.03, { dur: d, color: 'brown', filter: 'bandpass', f: [[0, 160], [d / 2, 320], [d, 140]], q: 1.4, gain: 0.32, a: d * 0.45 });
|
|
355
|
+
k.noise(a + 0.03, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 400], [d / 2, 1100], [d, 450]], q: 0.9, gain: 0.09, a: d * 0.5 }); };
|
|
356
|
+
const transition = (a, b) => { shutterClose(a, 0.9); swing(a, b); shutterOpen(b, 0.8); };
|
|
357
|
+
// an orb flying along its arc and landing in the network (soft glow tone on arrival)
|
|
358
|
+
const orb = (a, b, pan0, pan1) => { const d = b - a;
|
|
359
|
+
k.noise(a, { dur: d, color: 'pink', filter: 'bandpass', f: [[0, 600], [d, 1300]], q: 2, gain: 0.045, a: d * 0.7, pan: pan0, minor: true });
|
|
360
|
+
k.tone(b, { f: 'A3', dur: 0.5, gain: 0.07, a: 0.01, verb: 0.3, pan: pan1, minor: true }); k.tone(b, { f: 'E4', dur: 0.35, gain: 0.03, a: 0.01, verb: 0.3, pan: pan1, minor: true }); };
|
|
361
|
+
// pixel-stick readout ticks, throttled
|
|
362
|
+
const pixel = (val, a, b, gap, pan, avoid = []) => { let last = -1, prev = val(a);
|
|
363
|
+
for (let t = a; t <= b; t += 0.002) { const v = val(t);
|
|
364
|
+
if (v !== prev && t - last >= gap && !avoid.some((x) => t > x - 0.03 && t < x + 0.1)) { k.play('click', t, { f: 1500, gain: 1.5, pan, minor: true }); last = t; }
|
|
365
|
+
prev = v; } };
|
|
366
|
+
|
|
367
|
+
// ---- beds: night breeze, a quiet field; a soft slow pad (64 bpm, one chord per 3.75 s)
|
|
368
|
+
k.bed('room', 0, D, { gain: 0.3 });
|
|
369
|
+
k.noise(0, { dur: D, color: 'pink', filter: 'lowpass', f: [[0, 380], [5, 520], [10, 340], [15, 480]], gain: 0.05, a: 1, hold: D - 2, bus: 'amb', minor: true });
|
|
370
|
+
k.music('pad', 0, D, { bpm: 64, gain: 0.22, chords: [['A3', 'C4', 'E4', 'G4'], ['F3', 'A3', 'C4', 'E4'], ['D3', 'F3', 'A3', 'C4'], ['C3', 'E3', 'G3', 'B3']] });
|
|
371
|
+
|
|
372
|
+
// ---- beat 0: exposure opens; "How does" 0.15–0.75, "AI learn?" 0.8–1.6, underline 1.6–1.9
|
|
373
|
+
shutterOpen(0.05, 1);
|
|
374
|
+
wand(0.15, 0.75, 1.0, -0.2, false);
|
|
375
|
+
wand(0.8, 1.6, 1.2, 0.1, false);
|
|
376
|
+
wand(1.6, 1.9, 0.9, 0.2);
|
|
377
|
+
transition(2.05, 2.5);
|
|
378
|
+
|
|
379
|
+
// ---- beat 1: network traced 2.45–3.2; six doodles at t0 = 2.55 + k*0.32 (doodle t0..t0+0.28, label +0.2..+0.4),
|
|
380
|
+
// each sends an orb along an arc (t0+0.4 .. t0+0.85) into the input layer
|
|
381
|
+
wand(2.5, 3.2, 0.35, 0.45);
|
|
382
|
+
const land = [];
|
|
383
|
+
for (let c = 0; c < 6; c++) { const t0 = 2.55 + c * 0.32, pan = c % 2 ? -0.55 : -0.8;
|
|
384
|
+
wand(t0, t0 + 0.38, 0.4, pan); orb(t0 + 0.4, t0 + 0.85, pan, 0.3); land.push(t0 + 0.85); }
|
|
385
|
+
// LED pixel-stick counter 10^(6p), p = sm(2.9, 5.4), lit 2.85; "examples" 3.0–3.3
|
|
386
|
+
pixel((t) => { const p = sm(2.9, 5.4, t); return p >= 1 ? 1e6 : Math.round(Math.pow(10, 6 * p)); }, 2.92, 5.35, 0.09, 0.3, land);
|
|
387
|
+
k.play('tick', 5.4, { f: 2400, gain: 1.0, pan: 0.3 }); k.tone(5.4, { f: 'E4', dur: 0.4, gain: 0.08, verb: 0.25, pan: 0.3 });
|
|
388
|
+
transition(5.9, 6.35);
|
|
389
|
+
|
|
390
|
+
// ---- beat 2: cat doodle 6.35–6.75, network 6.4–7.0, Guess arrow 6.95–7.2 + label 7.0–7.3, arrow 7.3–7.45
|
|
391
|
+
wand(6.38, 6.75, 0.8, -0.6);
|
|
392
|
+
wand(6.75, 7.0, 0.5, 0);
|
|
393
|
+
wand(7.0, 7.3, 0.6, -0.3); wand(7.3, 7.45, 0.5, 0.3);
|
|
394
|
+
wand(7.5, 7.85, 1.0, 0.45, false); // "dog?" (the guess)
|
|
395
|
+
// WRONG: two hard red slashes 7.95–8.05, 8.05–8.15, then a ring 8.1–8.4 and "Check" 8.15–8.45
|
|
396
|
+
[7.95, 8.05].forEach((ts, i) => { k.noise(ts, { dur: 0.12, color: 'pink', filter: 'bandpass', f: [[0, 1400], [0.1, 350]], q: 1.1, gain: 0.34, a: 0.01, pan: 0.6 });
|
|
397
|
+
k.tone(ts + 0.02, { f: [[0, i ? 150 : 175], [0.12, 70]], dur: 0.2, gain: 0.28, pan: 0.6 }); });
|
|
398
|
+
k.tone(8.0, { f: 'D3', type: 'triangle', dur: 0.45, gain: 0.1, a: 0.01, verb: 0.2, pan: 0.5 }); k.tone(8.0, { f: 'Ab3', type: 'triangle', dur: 0.45, gain: 0.07, a: 0.01, verb: 0.2, pan: 0.5 }); // sour, low
|
|
399
|
+
wand(8.15, 8.45, 0.6, 0.45);
|
|
400
|
+
wand(8.45, 8.9, 0.8, 0.2, false); // red arrow back
|
|
401
|
+
wand(8.92, 9.05, 0.5, 0.1); // "Adjust" tail
|
|
402
|
+
// weights re-painted 9.0–9.45: a soft rising glow swell under the net
|
|
403
|
+
k.tone(9.0, { f: [[0, 220], [0.45, 330]], type: 'triangle', dur: 0.55, gain: 0.06, a: 0.25, verb: 0.3 });
|
|
404
|
+
k.noise(9.0, { dur: 0.5, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.45, 1400]], q: 1.5, gain: 0.08, a: 0.3, minor: true });
|
|
405
|
+
// RIGHT: strike-through 9.45–9.55, "cat" 9.55–9.85, tick 9.85–10.0
|
|
406
|
+
wand(9.45, 9.55, 0.6, 0.45);
|
|
407
|
+
wand(9.55, 9.85, 1.0, 0.45, false);
|
|
408
|
+
wand(9.85, 10.0, 0.9, 0.6, false);
|
|
409
|
+
k.play('success', 10.0, { f: 'A4', gain: 0.6, pan: 0.45 });
|
|
410
|
+
transition(10.3, 10.75);
|
|
411
|
+
|
|
412
|
+
// ---- beat 3: axes 10.75–11.1, "Accuracy" 10.85–11.15, labels; RGB tube sweeps the curve 11.1–12.3
|
|
413
|
+
wand(10.78, 11.1, 0.6, -0.5);
|
|
414
|
+
wand(11.1, 11.3, 0.4, 0.4);
|
|
415
|
+
// the light tube is swept along the curve: one long, smooth airy pass, brighter as it climbs
|
|
416
|
+
k.noise(11.1, { dur: 1.2, color: 'pink', filter: 'bandpass', f: [[0, 350], [1.2, 900]], q: 1.2, gain: 0.07, a: 0.3, hold: 0.7, pan: -0.4, minor: true });
|
|
417
|
+
k.tone(11.1, { f: [[0, 196], [1.2, 294]], type: 'triangle', dur: 1.25, gain: 0.03, a: 0.3, hold: 0.7, pan: -0.4, minor: true });
|
|
418
|
+
const pct = (t) => Math.round(61 + 36 * E.out(sm(11.1, 12.3, t)));
|
|
419
|
+
const t97 = when(pct, 97, 11.1, 12.3);
|
|
420
|
+
pixel(pct, 11.11, t97 - 0.04, 0.085, 0.45);
|
|
421
|
+
k.play('tick', t97, { f: 2400, gain: 1.0, pan: 0.45 }); k.tone(t97, { f: 'E4', dur: 0.4, gain: 0.08, verb: 0.25, pan: 0.45 });
|
|
422
|
+
// THE HIT 12.3: steel wool spins up — a soft ignition thump, then a spark crackle as the burst flies (12.3–12.75)
|
|
423
|
+
k.play('thud', 12.3, { f: 90, gain: 1.1, pan: 0.4 });
|
|
424
|
+
k.noise(12.3, { dur: 0.5, color: 'pink', filter: 'lowpass', f: [[0, 2600], [0.45, 400]], gain: 0.28, a: 0.005, verb: 0.35, pan: 0.4 });
|
|
425
|
+
{ const r = H.rng(1230); for (let x = 0; x < 0.5; x += 0.012 + r() * 0.02) k.noise(12.3 + x, { dur: 0.006 + r() * 0.006, filter: 'bandpass', f: 1200 + r() * 1300, q: 1.2, gain: (0.35 + r() * 0.35) * (1 - x * 1.4), a: 0.001, pan: 0.4 + (r() - 0.5) * 0.9 }); }
|
|
426
|
+
wand(12.35, 12.7, 0.7, 0.4); // ring traced round 97%
|
|
427
|
+
['C4', 'E4', 'G4', 'C5'].forEach((n, i) => k.tone(12.32, { f: n, type: 'triangle', dur: 1.0, gain: 0.05, a: 0.02, hold: 0.15, filter: 'lowpass', f2: 1800, verb: 0.35, pan: (i - 1.5) * 0.3 }));
|
|
428
|
+
transition(12.85, 13.3);
|
|
429
|
+
|
|
430
|
+
// ---- beat 4: "More examples" 13.3–13.75, arrow 13.75–13.9, "better guesses" 13.9–14.4
|
|
431
|
+
wand(13.3, 13.75, 1.0, -0.1, false);
|
|
432
|
+
wand(13.75, 13.9, 0.6, 0);
|
|
433
|
+
wand(13.9, 14.4, 1.1, 0.1, false);
|
|
434
|
+
// resolve: the last exposure closes over a warm chord
|
|
435
|
+
shutterClose(14.45, 1.0);
|
|
436
|
+
['C3', 'G3', 'E4', 'B4'].forEach((n, i) => k.tone(14.42, { f: n, type: i ? 'sine' : 'triangle', dur: 0.58, gain: 0.08 - i * 0.012, a: 0.05, hold: 0.2, verb: 0.4, pan: (i - 1.5) * 0.25 }));
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
registerStyle({
|
|
440
|
+
id: 'light-painting',
|
|
441
|
+
name: 'Light Painting',
|
|
442
|
+
category: 'Light & Imaging',
|
|
443
|
+
description: 'A 30-second exposure in a dark field: a neural network drawn in the air with an LED wand and a twisting rainbow light-tube ribbon, hand-jittered trails with hot cores and coloured falloff, a ghost of the painter, pinpoint stars, horizon bokeh and colour spill on the grass.',
|
|
444
|
+
motion: 'Shot as a live-composite exposure: the wand traces node after node and fans out each layer of connections, the RGB tube sweeps the twisting ribbon around it, the word is written last, while the ghost painter slowly accumulates and star trails lengthen.',
|
|
445
|
+
fonts: ['700 92px "Caveat"'],
|
|
446
|
+
frameT: 5.6,
|
|
447
|
+
duration: 6,
|
|
448
|
+
readsPixels: true,
|
|
449
|
+
explainerDuration: 15,
|
|
450
|
+
explainerNote: 'Each beat is a fresh live long exposure: the wand traces doodles, arrows and handwriting with its glowing tip, orbs leave arcing light trails into the network, numbers come from an LED pixel-stick, the emphasis is a traced ring with a spark burst, and between beats the camera whip-pans so every trail smears into streaks.',
|
|
451
|
+
explainer(ctx, W, Hh, t) { lightExplainer(ctx, W, Hh, t); },
|
|
452
|
+
explainerSound(k) { lightSound(k); },
|
|
453
|
+
draw(ctx, W, Hh, t) {
|
|
454
|
+
const R = H.rng(3030), nz = H.makeNoise(303);
|
|
455
|
+
const HZ = 528; // horizon
|
|
456
|
+
const sch = (a, d) => H.clamp((t - a) / d); // stroke progress
|
|
457
|
+
const ghostA = 0.3 + 0.7 * H.smooth(0.2, 4.6, t);
|
|
458
|
+
const expo = 0.3 + 0.7 * H.smooth(0, 6, t); // star trails lengthen as the exposure accumulates
|
|
459
|
+
let tip = null;
|
|
460
|
+
|
|
461
|
+
// ---------- night landscape ----------
|
|
462
|
+
const sky = ctx.createLinearGradient(0, 0, 0, HZ);
|
|
463
|
+
sky.addColorStop(0, '#02030a'); sky.addColorStop(0.6, '#060a1c'); sky.addColorStop(1, '#14182c');
|
|
464
|
+
ctx.fillStyle = sky; ctx.fillRect(0, 0, W, HZ + 2);
|
|
465
|
+
// light-pollution glow on the horizon
|
|
466
|
+
const lp = ctx.createRadialGradient(980, HZ, 10, 980, HZ, 520);
|
|
467
|
+
lp.addColorStop(0, 'rgba(120,80,60,0.35)'); lp.addColorStop(1, 'rgba(60,40,40,0)');
|
|
468
|
+
ctx.fillStyle = lp; ctx.fillRect(0, 0, W, HZ + 2);
|
|
469
|
+
// stars: pinpoints, a few short trails from earth rotation
|
|
470
|
+
for (let k = 0; k < 520; k++) {
|
|
471
|
+
const x = R() * W, y = Math.pow(R(), 1.3) * (HZ - 30), m = Math.pow(R(), 5);
|
|
472
|
+
const col = R.pick([[255, 255, 255], [200, 215, 255], [255, 230, 200], [180, 200, 255]]);
|
|
473
|
+
const fade = 1 - H.smooth(HZ - 160, HZ, y) * 0.8;
|
|
474
|
+
ctx.strokeStyle = H.rgba(col, (0.25 + m * 0.75) * fade); ctx.lineWidth = 0.8 + m * 1.6; ctx.lineCap = 'round';
|
|
475
|
+
const len = (1 + m * 3) * expo;
|
|
476
|
+
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + len, y - len * 0.35); ctx.stroke();
|
|
477
|
+
if (m > 0.6) { ctx.fillStyle = H.rgba(col, 0.1 * fade); ctx.beginPath(); ctx.arc(x, y, 2.5 + m * 1.5, 0, 7); ctx.fill(); }
|
|
478
|
+
}
|
|
479
|
+
// treeline / hills silhouette
|
|
480
|
+
ctx.fillStyle = '#04050a';
|
|
481
|
+
ctx.beginPath(); ctx.moveTo(0, HZ);
|
|
482
|
+
for (let x = 0; x <= W; x += 4) {
|
|
483
|
+
const hill = 18 * Math.sin(x * 0.004 + 1.2) + 10 * Math.sin(x * 0.011);
|
|
484
|
+
const trees = Math.max(0, nz.fbm2(x * 0.03, 2.2, 3)) * 46 * H.smooth(80, 300, x) * (1 - H.smooth(760, 940, x)) + Math.max(0, nz.n2(x * 0.08, 5)) * 10;
|
|
485
|
+
ctx.lineTo(x, HZ - 14 - hill - trees);
|
|
486
|
+
}
|
|
487
|
+
ctx.lineTo(W, Hh); ctx.lineTo(0, Hh); ctx.fill();
|
|
488
|
+
// grass field texture
|
|
489
|
+
for (let k = 0; k < 2600; k++) {
|
|
490
|
+
const x = R() * W, y = HZ + Math.pow(R(), 0.7) * (Hh - HZ);
|
|
491
|
+
ctx.strokeStyle = `rgba(30,36,48,${R.range(0.2, 0.6)})`; ctx.lineWidth = 1;
|
|
492
|
+
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(x + R.range(-3, 3), y - R.range(3, 6 + (y - HZ) * 0.05)); ctx.stroke();
|
|
493
|
+
}
|
|
494
|
+
// bokeh: distant out-of-focus lights along the horizon
|
|
495
|
+
const bok = [[860, HZ - 30, 18, [255, 170, 90]], [915, HZ - 22, 13, [255, 200, 120]], [1040, HZ - 34, 22, [255, 150, 70]], [1100, HZ - 18, 11, [170, 210, 255]],
|
|
496
|
+
[1170, HZ - 28, 16, [255, 190, 110]], [120, HZ - 26, 14, [255, 160, 80]], [60, HZ - 16, 9, [255, 210, 150]], [990, HZ - 12, 9, [255, 220, 170]]];
|
|
497
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter';
|
|
498
|
+
bok.forEach(([x, y, r, c]) => {
|
|
499
|
+
const g = ctx.createRadialGradient(x, y, 0, x, y, r);
|
|
500
|
+
g.addColorStop(0, H.rgba(c, 0.18)); g.addColorStop(0.8, H.rgba(c, 0.26)); g.addColorStop(0.95, H.rgba(c, 0.42)); g.addColorStop(1, H.rgba(c, 0));
|
|
501
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, r, 0, 7); ctx.fill();
|
|
502
|
+
});
|
|
503
|
+
ctx.restore();
|
|
504
|
+
|
|
505
|
+
// ---------- ghost painter (moved during exposure: translucent) ----------
|
|
506
|
+
drawGhost(ctx, getGhost(W, Hh), 0, ghostA);
|
|
507
|
+
|
|
508
|
+
// ---------- light trails ----------
|
|
509
|
+
const Ccol = H.layer(W, Hh), cc = Ccol.getContext('2d'); // coloured trail
|
|
510
|
+
const Ccore = H.layer(W, Hh), co = Ccore.getContext('2d'); // hot core
|
|
511
|
+
[cc, co].forEach((x) => { x.globalCompositeOperation = 'lighter'; x.lineCap = 'round'; x.lineJoin = 'round'; });
|
|
512
|
+
let seed = 1;
|
|
513
|
+
// a hand-held LED pen stroke along pts with jitter, speed-dependent brightness and colour from c0->c1
|
|
514
|
+
const pen = (pts, c0, c1, width = 4.2, gaps = 0, pr = 1) => {
|
|
515
|
+
const jn = H.makeNoise(seed++);
|
|
516
|
+
const N = pts.length;
|
|
517
|
+
let prev = null;
|
|
518
|
+
for (let i = 0; i < N; i++) {
|
|
519
|
+
const u = i / (N - 1);
|
|
520
|
+
if (u > pr) break;
|
|
521
|
+
const [x0, y0] = pts[i];
|
|
522
|
+
const x = x0 + jn.n2(u * 9, 0.5) * 2.4, y = y0 + jn.n2(u * 9, 7.5) * 2.4;
|
|
523
|
+
if (prev) {
|
|
524
|
+
const sp = 0.65 + 0.35 * jn.n2(u * 4, 3); // dwell variation
|
|
525
|
+
const off = gaps && jn.n2(u * 6, 11) > 0.42; // pen flicker gap
|
|
526
|
+
if (!off) {
|
|
527
|
+
const c = H.mix(c0, c1, u);
|
|
528
|
+
cc.strokeStyle = H.rgba(c, 0.55 * sp); cc.lineWidth = width * (0.8 + sp * 0.4);
|
|
529
|
+
cc.beginPath(); cc.moveTo(prev[0], prev[1]); cc.lineTo(x, y); cc.stroke();
|
|
530
|
+
co.strokeStyle = H.rgba(H.mix(c, [255, 255, 255], 0.75), 0.5 * sp); co.lineWidth = width * 0.38;
|
|
531
|
+
co.beginPath(); co.moveTo(prev[0], prev[1]); co.lineTo(x, y); co.stroke();
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
prev = [x, y];
|
|
535
|
+
}
|
|
536
|
+
if (pr > 0 && pr < 1 && prev) tip = [prev[0], prev[1], H.mix(c0, c1, pr)];
|
|
537
|
+
};
|
|
538
|
+
const line = (a, b, n = 60) => Array.from({ length: n }, (_, i) => [a[0] + (b[0] - a[0]) * i / (n - 1), a[1] + (b[1] - a[1]) * i / (n - 1)]);
|
|
539
|
+
const loop = (cx, cy, r, turns = 2.3, n = 120) => Array.from({ length: n }, (_, i) => {
|
|
540
|
+
const a = i / (n - 1) * Math.PI * 2 * turns - 1.2, rr = r * (1 + 0.08 * Math.sin(a * 0.5)) + i * 0.02;
|
|
541
|
+
return [cx + Math.cos(a) * rr, cy + Math.sin(a) * rr * 0.96];
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// neural net layout
|
|
545
|
+
const layers = [3, 4, 4, 2];
|
|
546
|
+
const lx = [330, 500, 670, 840];
|
|
547
|
+
const nodes = layers.map((n, li) => Array.from({ length: n }, (_, k) => [lx[li] + Math.sin(k * 2 + li) * 6, 300 + (k - (n - 1) / 2) * 96 + Math.cos(k + li) * 6]));
|
|
548
|
+
const lcol = [[60, 220, 255], [170, 90, 255], [255, 70, 190], [255, 180, 60]];
|
|
549
|
+
for (let li = 0; li < layers.length - 1; li++) {
|
|
550
|
+
let ci = 0;
|
|
551
|
+
nodes[li].forEach((a) => nodes[li + 1].forEach((b) => {
|
|
552
|
+
const cp = sch(0.62 + li * 0.6 + (ci++) * 0.022, 0.26);
|
|
553
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], L = Math.hypot(dx, dy);
|
|
554
|
+
const a2 = [a[0] + dx / L * 26, a[1] + dy / L * 26], b2 = [b[0] - dx / L * 26, b[1] - dy / L * 26];
|
|
555
|
+
pen(line(a2, b2, 50), lcol[li], lcol[li + 1], 2.6, 1, cp);
|
|
556
|
+
}));
|
|
557
|
+
}
|
|
558
|
+
nodes.forEach((ln, li) => ln.forEach(([x, y], k) => pen(loop(x, y, 20), lcol[li], H.mix(lcol[li], [255, 255, 255], 0.3), 4.5, 0, sch(0.3 + li * 0.6 + k * 0.07, 0.24))));
|
|
559
|
+
|
|
560
|
+
// light-tube ribbon: a 60cm RGB tube swept and twisted around the net
|
|
561
|
+
const Rb = H.layer(W, Hh), rb = Rb.getContext('2d');
|
|
562
|
+
rb.globalCompositeOperation = 'lighter';
|
|
563
|
+
const rprog = H.ease.inOut(sch(2.5, 1.1));
|
|
564
|
+
{ const rt = lightRibbon(rb, nz, { cx0: 590, cy0: 290, rx: 430, ry: 182, prog: rprog }); if (rt) tip = rt; }
|
|
565
|
+
|
|
566
|
+
// light-written word (pen script)
|
|
567
|
+
co.save(); cc.save();
|
|
568
|
+
const word = 'learning';
|
|
569
|
+
const writeWord = (x, col, a, wobble) => {
|
|
570
|
+
x.font = '700 92px "Caveat"'; x.textAlign = 'center';
|
|
571
|
+
x.fillStyle = col; x.globalAlpha = a;
|
|
572
|
+
x.save(); x.beginPath(); x.rect(395, 440, 10 + wp * 420, 160); x.clip();
|
|
573
|
+
x.translate(600 + wobble, 548); x.rotate(-0.05); x.fillText(word, 0, 0); x.restore();
|
|
574
|
+
};
|
|
575
|
+
const wp = sch(3.65, 0.85);
|
|
576
|
+
if (wp > 0 && wp < 1) tip = [395 + 10 + wp * 420, 530 - Math.sin(wp * 40) * 18, [255, 140, 60]];
|
|
577
|
+
if (wp > 0) {
|
|
578
|
+
writeWord(cc, 'rgb(255,120,40)', 0.9, 0); writeWord(cc, 'rgb(255,80,30)', 0.4, 2);
|
|
579
|
+
co.globalCompositeOperation = 'source-over';
|
|
580
|
+
const tmp = H.layer(W, Hh), tx = tmp.getContext('2d'); writeWord(tx, '#fff', 1, 0);
|
|
581
|
+
// thin core: erode the glyphs
|
|
582
|
+
const ero = H.layer(W, Hh), ex = ero.getContext('2d'); ex.drawImage(tmp, 0, 0); ex.globalCompositeOperation = 'destination-in';
|
|
583
|
+
for (let k = 0; k < 8; k++) ex.drawImage(tmp, Math.round(Math.cos(k * Math.PI / 4) * 2), Math.round(Math.sin(k * Math.PI / 4) * 2));
|
|
584
|
+
ex.globalCompositeOperation = 'source-in'; ex.fillStyle = 'rgba(255,236,200,0.95)'; ex.fillRect(0, 0, W, Hh);
|
|
585
|
+
co.globalCompositeOperation = 'lighter'; co.drawImage(ero, 0, 0);
|
|
586
|
+
}
|
|
587
|
+
co.restore(); cc.restore();
|
|
588
|
+
|
|
589
|
+
// ---------- composite light (additive, with optical bloom) ----------
|
|
590
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter';
|
|
591
|
+
// spill onto the grass beneath (soft, squashed)
|
|
592
|
+
ctx.globalAlpha = 1.0; ctx.drawImage(blurSmall(Ccol, 40, 0.125), 0, HZ + 6, W, 190);
|
|
593
|
+
ctx.drawImage(blurSmall(Rb, 40, 0.125), 0, HZ + 6, W, 190);
|
|
594
|
+
ctx.globalAlpha = 0.5; ctx.drawImage(blurSmall(Ccol, 34, 0.125), 0, 0, W, Hh);
|
|
595
|
+
ctx.globalAlpha = 0.8; ctx.drawImage(blurSmall(Ccol, 9, 0.25), 0, 0, W, Hh);
|
|
596
|
+
ctx.globalAlpha = 0.7; ctx.drawImage(blurSmall(Rb, 22, 0.25), 0, 0, W, Hh);
|
|
597
|
+
ctx.globalAlpha = 1; ctx.drawImage(Rb, 0, 0); ctx.drawImage(Ccol, 0, 0);
|
|
598
|
+
ctx.drawImage(Ccore, 0, 0);
|
|
599
|
+
ctx.globalAlpha = 0.6; ctx.drawImage(blurSmall(Ccore, 3, 0.5), 0, 0, W, Hh);
|
|
600
|
+
ctx.restore();
|
|
601
|
+
|
|
602
|
+
// the wand itself, live at the current stroke tip
|
|
603
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter';
|
|
604
|
+
if (tip) {
|
|
605
|
+
const tg = ctx.createRadialGradient(tip[0], tip[1], 0, tip[0], tip[1], 34);
|
|
606
|
+
tg.addColorStop(0, 'rgba(255,255,255,0.95)'); tg.addColorStop(0.15, H.rgba(tip[2], 0.6)); tg.addColorStop(1, H.rgba(tip[2], 0));
|
|
607
|
+
ctx.fillStyle = tg; ctx.beginPath(); ctx.arc(tip[0], tip[1], 34, 0, 7); ctx.fill();
|
|
608
|
+
if (tip[3]) { ctx.strokeStyle = 'rgba(255,255,255,0.9)'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(tip[3][0], tip[3][1]); ctx.lineTo(tip[3][2], tip[3][3]); ctx.stroke(); }
|
|
609
|
+
}
|
|
610
|
+
ctx.restore();
|
|
611
|
+
// wand light on the painter's raised hand
|
|
612
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter';
|
|
613
|
+
ctx.globalAlpha = tip ? 0.3 : 1;
|
|
614
|
+
const wg = ctx.createRadialGradient(910, 270, 0, 910, 270, 22);
|
|
615
|
+
wg.addColorStop(0, 'rgba(255,220,180,0.55)'); wg.addColorStop(1, 'rgba(255,160,80,0)');
|
|
616
|
+
ctx.fillStyle = wg; ctx.beginPath(); ctx.arc(910, 270, 22, 0, 7); ctx.fill();
|
|
617
|
+
ctx.restore();
|
|
618
|
+
|
|
619
|
+
// ---------- sensor finish: high-ISO noise, vignette ----------
|
|
620
|
+
// slow push-in
|
|
621
|
+
const push = 1 + 0.03 * H.ease.inOut(H.clamp(t / 6));
|
|
622
|
+
const cpy = H.layer(W, Hh); cpy.getContext('2d').drawImage(ctx.canvas, 0, 0);
|
|
623
|
+
ctx.save(); ctx.translate(600, 330); ctx.scale(push, push); ctx.translate(-600, -330); ctx.drawImage(cpy, 0, 0); ctx.restore();
|
|
624
|
+
H.vignette(ctx, W, Hh, 0.55, '0,0,4');
|
|
625
|
+
const Rn = H.rng(31 + Math.floor(t * 30));
|
|
626
|
+
const img = ctx.getImageData(0, 0, W, Hh), d = img.data;
|
|
627
|
+
for (let i = 0; i < d.length; i += 4) {
|
|
628
|
+
const l = (d[i] + d[i + 1] + d[i + 2]) / 765, k = (1 - l) * 9 + 2;
|
|
629
|
+
const n = (Rn() - 0.5) * k;
|
|
630
|
+
d[i] += n + (Rn() - 0.5) * k * 0.8; d[i + 1] += n; d[i + 2] += n + (Rn() - 0.5) * k;
|
|
631
|
+
}
|
|
632
|
+
ctx.putImageData(img, 0, 0);
|
|
633
|
+
},
|
|
634
|
+
});
|
|
635
|
+
}
|