@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,531 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Generative streamline art: particles traced through a divergence-free (curl) field.
|
|
3
|
+
const PAL = ['#123A4A', '#1C6F78', '#5FB3A6', '#CDE3CF', '#F3E6C8', '#EDBB73', '#DE7A52', '#A8384E', '#5A1E44'];
|
|
4
|
+
const PALRGB = PAL.map((c) => H.hex(c));
|
|
5
|
+
function pal(u) {
|
|
6
|
+
u = H.clamp(u) * (PAL.length - 1);
|
|
7
|
+
const i = Math.min(PAL.length - 2, Math.floor(u)), f = u - i;
|
|
8
|
+
return H.mix(PALRGB[i], PALRGB[i + 1], f);
|
|
9
|
+
}
|
|
10
|
+
const FX = {}; // cached offscreen layers (keyed by size)
|
|
11
|
+
// The core: a lensing void, not a sticker. Warm halo, dark contact ring, disc lit from the upper-left
|
|
12
|
+
// with an inner rim-light, then the ivory hairline rim (drawn by the caller).
|
|
13
|
+
function coreBody(ctx, cx, cy, Rc, a) {
|
|
14
|
+
ctx.save(); ctx.globalAlpha = a; ctx.globalCompositeOperation = 'lighter';
|
|
15
|
+
const halo = ctx.createRadialGradient(cx, cy, Rc * 0.9, cx, cy, Rc * 1.5);
|
|
16
|
+
halo.addColorStop(0, 'rgba(255,230,190,0.07)'); halo.addColorStop(1, 'rgba(255,230,190,0)');
|
|
17
|
+
ctx.fillStyle = halo; ctx.beginPath(); ctx.arc(cx, cy, Rc * 1.5, 0, 7); ctx.fill();
|
|
18
|
+
ctx.restore();
|
|
19
|
+
ctx.save();
|
|
20
|
+
// contact ring: the streams darken where they graze the void
|
|
21
|
+
const cr = ctx.createRadialGradient(cx, cy, Rc * 0.96, cx, cy, Rc * 0.97 + 7);
|
|
22
|
+
cr.addColorStop(0, 'rgba(4,4,8,0.75)'); cr.addColorStop(0.45, 'rgba(4,4,8,0.4)'); cr.addColorStop(1, 'rgba(4,4,8,0)');
|
|
23
|
+
ctx.fillStyle = cr; ctx.beginPath(); ctx.arc(cx, cy, Rc * 0.97 + 7, 0, 7); ctx.fill();
|
|
24
|
+
const core = ctx.createRadialGradient(cx - Rc * 0.35, cy - Rc * 0.4, 4, cx, cy, Rc);
|
|
25
|
+
core.addColorStop(0, '#1A1C28'); core.addColorStop(0.6, '#0D0E15'); core.addColorStop(1, '#06070A');
|
|
26
|
+
ctx.fillStyle = core; ctx.beginPath(); ctx.arc(cx, cy, Rc * 0.97, 0, 7); ctx.fill();
|
|
27
|
+
ctx.clip();
|
|
28
|
+
// inner rim light, strongest on the upper-left edge
|
|
29
|
+
const rim = ctx.createRadialGradient(cx + Rc * 0.16, cy + Rc * 0.2, Rc * 0.62, cx + Rc * 0.05, cy + Rc * 0.06, Rc * 1.04);
|
|
30
|
+
rim.addColorStop(0, 'rgba(243,230,200,0)'); rim.addColorStop(0.75, 'rgba(243,230,200,0.035)'); rim.addColorStop(1, 'rgba(243,230,200,0.13)');
|
|
31
|
+
ctx.globalAlpha = a; ctx.fillStyle = rim; ctx.fillRect(cx - Rc, cy - Rc, Rc * 2, Rc * 2);
|
|
32
|
+
// faint cool counter-light on the lower-right edge
|
|
33
|
+
const rim2 = ctx.createRadialGradient(cx - Rc * 0.2, cy - Rc * 0.22, Rc * 0.78, cx - Rc * 0.05, cy - Rc * 0.05, Rc * 1.02);
|
|
34
|
+
rim2.addColorStop(0, 'rgba(95,179,166,0)'); rim2.addColorStop(1, 'rgba(95,179,166,0.06)');
|
|
35
|
+
ctx.fillStyle = rim2; ctx.fillRect(cx - Rc, cy - Rc, Rc * 2, Rc * 2);
|
|
36
|
+
ctx.restore();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
registerStyle({
|
|
40
|
+
id: 'flow-field',
|
|
41
|
+
name: 'Flow Field',
|
|
42
|
+
category: 'Data & Information',
|
|
43
|
+
description: 'Thousands of hairline streamlines traced through a curl-noise field that parts around an unseen core; additive colour from a refined gradient builds luminous braids on dark paper.',
|
|
44
|
+
motion: 'The dark core appears first, then particles are released in a wave from left to right and trace their streamlines outward until the braids glow; in the hold the field slowly breathes while bright sparks ride the currents.',
|
|
45
|
+
duration: 7,
|
|
46
|
+
fonts: ['400 11px "Space Mono"', '700 11px "Space Mono"', '700 34px "Space Mono"', '700 13px "Space Mono"', '400 13px "Space Mono"', '700 38px "Space Mono"', '700 30px "Space Mono"', '700 74px "Space Mono"', '700 88px "Space Mono"', '700 60px "Space Mono"', '400 24px "Space Mono"'],
|
|
47
|
+
frameT: 6.8,
|
|
48
|
+
readsPixels: true,
|
|
49
|
+
draw(ctx, W, H_, t) {
|
|
50
|
+
// ---- dark paper ----
|
|
51
|
+
const bg = ctx.createRadialGradient(W * 0.6, H_ * 0.48, 40, W * 0.55, H_ * 0.5, W * 0.75);
|
|
52
|
+
bg.addColorStop(0, '#16182A'); bg.addColorStop(1, '#08090E');
|
|
53
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
54
|
+
|
|
55
|
+
// ---- stream function on a grid: flow past a cylinder + curl noise ----
|
|
56
|
+
const nz = H.makeNoise(23), nz2 = H.makeNoise(57);
|
|
57
|
+
const G = 4, gw = Math.ceil(W / G) + 3, gh = Math.ceil(H_ / G) + 3;
|
|
58
|
+
const cx = W * 0.4, cy = H_ * 0.47, Rc = 112;
|
|
59
|
+
const VORT = [[cx + 240, cy + 70, 95], [cx + 420, cy - 70, -105], [cx + 610, cy + 66, 85], [cx + 780, cy - 52, -70]];
|
|
60
|
+
const psi = new Float32Array(gw * gh);
|
|
61
|
+
const z = t * 0.06;
|
|
62
|
+
for (let j = 0; j < gh; j++) for (let i = 0; i < gw; i++) {
|
|
63
|
+
const x = (i - 1) * G, y = (j - 1) * G;
|
|
64
|
+
const dx = x - cx, dy = y - cy, r2 = dx * dx + dy * dy, r = Math.sqrt(r2);
|
|
65
|
+
const pot = (dy) * (1 - (Rc * Rc) / Math.max(r2, 1)); // potential flow around a cylinder
|
|
66
|
+
const away = H.smooth(Rc * 1.05, Rc * 2.1, r);
|
|
67
|
+
const n = nz.fbm3(x * 0.0026, y * 0.0026, z, 3) * 190 + nz2.n3(x * 0.006, y * 0.006, z * 2) * 26;
|
|
68
|
+
let vort = 0;
|
|
69
|
+
for (const [vxp, vyp, g] of VORT) {
|
|
70
|
+
const ex = x - vxp, ey = y - vyp, d2 = ex * ex + ey * ey;
|
|
71
|
+
vort += g * Math.log(d2 + 900) * 0.5 * Math.exp(-d2 / (2 * 150 * 150));
|
|
72
|
+
}
|
|
73
|
+
psi[j * gw + i] = pot * 1.0 + (n + vort) * away + (y - H_ / 2) * 0.1;
|
|
74
|
+
}
|
|
75
|
+
// velocity = (dpsi/dy, -dpsi/dx)
|
|
76
|
+
const vx = new Float32Array(gw * gh), vy = new Float32Array(gw * gh);
|
|
77
|
+
for (let j = 1; j < gh - 1; j++) for (let i = 1; i < gw - 1; i++) {
|
|
78
|
+
const k = j * gw + i;
|
|
79
|
+
vx[k] = (psi[k + gw] - psi[k - gw]) / (2 * G);
|
|
80
|
+
vy[k] = -(psi[k + 1] - psi[k - 1]) / (2 * G);
|
|
81
|
+
}
|
|
82
|
+
const sample = (x, y, out) => {
|
|
83
|
+
const fx = x / G + 1, fy = y / G + 1;
|
|
84
|
+
const i = Math.floor(fx), j = Math.floor(fy);
|
|
85
|
+
if (i < 1 || j < 1 || i >= gw - 2 || j >= gh - 2) return false;
|
|
86
|
+
const u = fx - i, v = fy - j, k = j * gw + i;
|
|
87
|
+
const a = (1 - u) * (1 - v), b = u * (1 - v), c = (1 - u) * v, d = u * v;
|
|
88
|
+
out[0] = vx[k] * a + vx[k + 1] * b + vx[k + gw] * c + vx[k + gw + 1] * d;
|
|
89
|
+
out[1] = vy[k] * a + vy[k + 1] * b + vy[k + gw] * c + vy[k + gw + 1] * d;
|
|
90
|
+
out[2] = psi[k] * a + psi[k + 1] * b + psi[k + gw] * c + psi[k + gw + 1] * d;
|
|
91
|
+
return true;
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// ---- trace particles; bucket polylines by colour ----
|
|
95
|
+
const NB = 28, buckets = Array.from({ length: NB }, () => []), allLines = [];
|
|
96
|
+
const rng = H.rng(91);
|
|
97
|
+
const N = 7000, STEPS = 170, STEP = 2.2;
|
|
98
|
+
const v = [0, 0, 0];
|
|
99
|
+
const inCore = (x, y) => (x - cx) * (x - cx) + (y - cy) * (y - cy) < Rc * Rc * 0.98;
|
|
100
|
+
for (let p = 0; p < N; p++) {
|
|
101
|
+
const sx = rng.range(-20, W + 20), sy = rng.range(-20, H_ + 20);
|
|
102
|
+
if (inCore(sx, sy)) continue;
|
|
103
|
+
// uneven seeding: braids of dense, luminous streams separated by quiet gaps
|
|
104
|
+
const dens = 0.5 + 0.5 * nz.n3(sx * 0.004, sy * 0.009, 7.3);
|
|
105
|
+
if (rng() > 0.18 + dens * dens * 1.4) continue;
|
|
106
|
+
if (!sample(sx, sy, v)) continue;
|
|
107
|
+
const birth = 0.35 + H.clamp(sx / W) * 1.5 + rng() * 0.8;
|
|
108
|
+
// colour by streamline identity (psi), slightly jittered
|
|
109
|
+
const u = H.clamp((v[2] + 320) / 640 + nz2.n2(sx * 0.0035, sy * 0.0035) * 0.22 + rng.gauss() * 0.02);
|
|
110
|
+
const line = []; let seedPt = 0;
|
|
111
|
+
for (const dir of [-1, 1]) {
|
|
112
|
+
let x = sx, y = sy;
|
|
113
|
+
const seg = [];
|
|
114
|
+
for (let s = 0; s < STEPS; s++) {
|
|
115
|
+
if (!sample(x, y, v)) break;
|
|
116
|
+
let mx = v[0], my = v[1];
|
|
117
|
+
const m = Math.hypot(mx, my) || 1;
|
|
118
|
+
// midpoint (RK2) step, normalised so strokes are even-length
|
|
119
|
+
const hx = x + dir * mx / m * STEP * 0.5, hy = y + dir * my / m * STEP * 0.5;
|
|
120
|
+
if (!sample(hx, hy, v)) break;
|
|
121
|
+
const m2 = Math.hypot(v[0], v[1]) || 1;
|
|
122
|
+
x += dir * v[0] / m2 * STEP; y += dir * v[1] / m2 * STEP;
|
|
123
|
+
if (inCore(x, y)) break;
|
|
124
|
+
seg.push(x, y);
|
|
125
|
+
}
|
|
126
|
+
if (dir === -1) { for (let q = seg.length - 2; q >= 0; q -= 2) line.push(seg[q], seg[q + 1]); line.push(sx, sy); seedPt = line.length / 2 - 1; }
|
|
127
|
+
else for (let q = 0; q < seg.length; q++) line.push(seg[q]);
|
|
128
|
+
}
|
|
129
|
+
if (line.length > 20) {
|
|
130
|
+
const grow = H.ease.out(H.clamp((t - birth) / 1.6));
|
|
131
|
+
if (grow > 0) {
|
|
132
|
+
const nPts = line.length / 2, si = Math.max(0, Math.min(nPts - 1, seedPt));
|
|
133
|
+
const reach = Math.ceil(grow * STEPS);
|
|
134
|
+
const a = Math.max(0, si - reach), b = Math.min(nPts - 1, si + reach);
|
|
135
|
+
const piece = line.slice(a * 2, b * 2 + 2);
|
|
136
|
+
if (piece.length >= 4) buckets[Math.min(NB - 1, Math.floor(u * NB))].push(piece);
|
|
137
|
+
}
|
|
138
|
+
if (allLines.length < 170 && rng() < 0.05) allLines.push({ L: line, u, ph: rng(), sp: rng.range(60, 120) });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ---- draw: additive hairlines ----
|
|
143
|
+
const lay = H.layer(W, H_), lc = lay.getContext('2d');
|
|
144
|
+
lc.globalCompositeOperation = 'lighter';
|
|
145
|
+
lc.lineCap = 'round'; lc.lineJoin = 'round';
|
|
146
|
+
for (let b = 0; b < NB; b++) {
|
|
147
|
+
const col = pal((b + 0.5) / NB);
|
|
148
|
+
lc.strokeStyle = H.rgba(col, 0.1);
|
|
149
|
+
lc.lineWidth = 0.7;
|
|
150
|
+
lc.beginPath();
|
|
151
|
+
for (const L of buckets[b]) { lc.moveTo(L[0], L[1]); for (let q = 2; q < L.length; q += 2) lc.lineTo(L[q], L[q + 1]); }
|
|
152
|
+
lc.stroke();
|
|
153
|
+
}
|
|
154
|
+
ctx.save();
|
|
155
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
156
|
+
// bloom: a soft, downsampled glow where streams pile up
|
|
157
|
+
const sm = H.layer(W / 4, H_ / 4), sc = sm.getContext('2d');
|
|
158
|
+
sc.filter = 'blur(3px)'; sc.drawImage(lay, 0, 0, W / 4, H_ / 4);
|
|
159
|
+
ctx.globalAlpha = 0.55; ctx.drawImage(sm, 0, 0, W, H_);
|
|
160
|
+
ctx.globalAlpha = 1; ctx.drawImage(lay, 0, 0);
|
|
161
|
+
ctx.restore();
|
|
162
|
+
|
|
163
|
+
// ---- sparks: bright heads riding the currents ----
|
|
164
|
+
const sparkA = H.clamp((t - 2.6) / 1.2);
|
|
165
|
+
if (sparkA > 0) {
|
|
166
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.lineCap = 'round';
|
|
167
|
+
for (const S of allLines) {
|
|
168
|
+
const nPts = S.L.length / 2, len = nPts * STEP;
|
|
169
|
+
const head = ((t * S.sp + S.ph * len) % (len + 200)) / STEP; // pause off-path between passes
|
|
170
|
+
if (head >= nPts) continue;
|
|
171
|
+
const tail = Math.max(0, Math.floor(head) - 22), hI = Math.floor(head);
|
|
172
|
+
if (hI - tail < 2) continue;
|
|
173
|
+
const col = pal(S.u), g = ctx.createLinearGradient(S.L[tail * 2], S.L[tail * 2 + 1], S.L[hI * 2], S.L[hI * 2 + 1]);
|
|
174
|
+
g.addColorStop(0, H.rgba(col, 0)); g.addColorStop(1, H.rgba(H.mix(col, [255, 250, 235], 0.5), 0.6 * sparkA));
|
|
175
|
+
ctx.strokeStyle = g; ctx.lineWidth = 1.2;
|
|
176
|
+
ctx.beginPath(); ctx.moveTo(S.L[tail * 2], S.L[tail * 2 + 1]);
|
|
177
|
+
for (let q = tail + 1; q <= hI; q++) ctx.lineTo(S.L[q * 2], S.L[q * 2 + 1]);
|
|
178
|
+
ctx.stroke();
|
|
179
|
+
}
|
|
180
|
+
ctx.restore();
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// ---- the core: a faint halo that defines the void ----
|
|
184
|
+
const coreA = H.ease.out(H.clamp(t / 0.9));
|
|
185
|
+
coreBody(ctx, cx, cy, Rc, coreA);
|
|
186
|
+
ctx.strokeStyle = `rgba(243,230,200,${0.4 * coreA})`; ctx.lineWidth = 0.8;
|
|
187
|
+
ctx.beginPath(); ctx.arc(cx, cy, Rc * 0.985, -Math.PI / 2, -Math.PI / 2 + Math.PI * 2 * coreA); ctx.stroke();
|
|
188
|
+
|
|
189
|
+
// ---- caption ----
|
|
190
|
+
// typed on, monospaced, after the field has filled
|
|
191
|
+
const typed = (str, t0, cps) => str.slice(0, Math.max(0, Math.floor((t - t0) * cps)));
|
|
192
|
+
ctx.textBaseline = 'alphabetic'; ctx.textAlign = 'left';
|
|
193
|
+
ctx.fillStyle = 'rgba(236,228,210,0.85)'; ctx.font = '700 11px "Space Mono"';
|
|
194
|
+
let x0 = 48; for (const ch of typed('LATENT FLOW', 3.0, 22)) { ctx.fillText(ch, x0, H_ - 54); x0 += ctx.measureText(ch).width + 3.2; }
|
|
195
|
+
ctx.fillStyle = 'rgba(236,228,210,0.45)'; ctx.font = '400 11px "Space Mono"';
|
|
196
|
+
ctx.fillText(typed('streamlines of a learned vector field', 3.5, 40), 48, H_ - 36);
|
|
197
|
+
ctx.textAlign = 'right';
|
|
198
|
+
ctx.globalAlpha = H.clamp((t - 4.2) / 0.4); ctx.fillText('No. 04', W - 48, H_ - 36); ctx.globalAlpha = 1;
|
|
199
|
+
|
|
200
|
+
H.grain(ctx, W, H_, 0.035, 12 + Math.floor(t * 12));
|
|
201
|
+
},
|
|
202
|
+
// Sound: air, not objects. A soft pad underneath, the field itself as breath-like filtered noise that swirls
|
|
203
|
+
// while the core drinks the data and goes from gusty to smooth as turbulence combs into order; the scripted
|
|
204
|
+
// particles are airy swells panned along their paths; labels arrive as low soft sine notes. Times mirror explainer().
|
|
205
|
+
explainerSound(k) {
|
|
206
|
+
const D = 15, cxs = 1280 * 0.42;
|
|
207
|
+
const io = H.ease.inOut;
|
|
208
|
+
const pan = (px) => H.clamp((px / 1280) * 2 - 1, -1, 1) * 0.7;
|
|
209
|
+
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; };
|
|
210
|
+
const seg = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
211
|
+
// a label surfacing: a low soft sine note with a little air
|
|
212
|
+
const note = (t, f, g = 1, p = 0, minor = true) => { k.tone(t, { f, dur: 0.9, gain: 0.09 * g, a: 0.03, verb: 0.35, pan: p, minor }); k.noise(t, { dur: 0.25, color: 'pink', filter: 'bandpass', f: 1200, q: 0.8, gain: 0.025 * g, a: 0.08, pan: p, minor }); };
|
|
213
|
+
// airy swell carried along a path: segments panned from p0 to p1, brightest in the middle (ease inOut speed)
|
|
214
|
+
const carry = (t0, t1, p0, p1, g = 1, f0 = 500, f1 = 1500) => {
|
|
215
|
+
const n = 6, d = (t1 - t0) / n;
|
|
216
|
+
for (let i = 0; i < n; i++) { const u = (i + 0.5) / n, sp = 1 - Math.abs(u - 0.5) * 1.6; k.noise(t0 + i * d, { dur: d * 1.7, color: 'pink', filter: 'bandpass', f: f0 + (f1 - f0) * sp, q: 1.1, gain: 0.07 * g * (0.4 + sp), a: d * 0.7, pan: p0 + (p1 - p0) * u, verb: 0.2 }); }
|
|
217
|
+
};
|
|
218
|
+
// soft grain tick for counters (filtered, dry, mid-low)
|
|
219
|
+
const grain = (t, g, p) => k.noise(t, { dur: 0.02, color: 'pink', filter: 'bandpass', f: 1400, q: 1.2, gain: 0.16 * g, a: 0.003, pan: p, minor: true });
|
|
220
|
+
const counter = (t0, t1, steps, p) => { let last = -1, lastT = -1; for (let t = t0; t <= t1 + 1e-6; t += 1 / 120) { const v = Math.floor(io(seg(t, t0, t1)) * steps); if (v !== last && t - lastT >= 1 / 12) { grain(t, 0.7 + 0.4 * v / steps, p); last = v; lastT = t; } } };
|
|
221
|
+
|
|
222
|
+
k.music('pad', 0, D, { bpm: 64, gain: 0.5, chords: [['D3', 'A3', 'E4', 'F#4'], ['B2', 'F#3', 'D4', 'A4'], ['G2', 'D3', 'B3', 'F#4'], ['D3', 'A3', 'F#4', 'A4']] });
|
|
223
|
+
|
|
224
|
+
// ---- the field: gusty air the whole way, rough early, combed smooth by 12.9 (turb 180 -> 26)
|
|
225
|
+
k.withMinor(() => {
|
|
226
|
+
for (let t = 0, i = 0; t < D; t += 0.5, i++) {
|
|
227
|
+
const turb = H.lerp(180, 26, io(seg(t, 10.8, 12.9))) / 180, grow = H.ease.out(seg(t, 0, 2.2));
|
|
228
|
+
const wob = Math.sin(i * 2.3) * 0.5 + Math.sin(i * 0.9) * 0.5;
|
|
229
|
+
k.noise(t, { dur: 0.95, color: 'pink', filter: 'bandpass', f: 700 + 500 * turb * (0.5 + 0.5 * wob), q: 0.5 + 1.6 * (1 - turb), gain: 0.05 * (0.35 + 0.65 * grow) * (1 + 0.6 * turb * wob), a: 0.45, pan: wob * 0.35 * turb });
|
|
230
|
+
}
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ---- hook: streamlines grow in, title surfaces, then docks top-left (1.6 -> 2.4)
|
|
234
|
+
k.noise(0, { dur: 2.2, color: 'pink', filter: 'bandpass', f: [[0, 300], [1.4, 1100], [2.2, 700]], q: 0.7, gain: 0.12, a: 1.2, verb: 0.3 });
|
|
235
|
+
note(0.12, 'A3', 1.3, 0, false);
|
|
236
|
+
k.noise(1.6, { dur: 0.8, color: 'brown', filter: 'bandpass', f: [[0, 260], [0.8, 420]], q: 0.6, gain: 0.08, a: 0.3, pan: -0.4, minor: true });
|
|
237
|
+
|
|
238
|
+
// ---- data: the core becomes a vortex drinking labelled particles (2.4 -> 6.6)
|
|
239
|
+
for (let t = 2.4, i = 0; t < 6.4; t += 0.4, i++) // swirl: air circling the core
|
|
240
|
+
k.noise(t, { dur: 0.7, color: 'pink', filter: 'bandpass', f: 500 + 120 * Math.sin(i * 1.3), q: 1.4, gain: 0.06 * (1 - seg(t, 5.6, 6.6) * 0.8), a: 0.3, pan: pan(cxs) + Math.sin(i * 1.57) * 0.45, minor: true });
|
|
241
|
+
note(2.85, 'D4', 1.1, pan(cxs + 140)); // "the model"
|
|
242
|
+
note(2.95, 'A3', 0.9, pan(360)); // "cat"
|
|
243
|
+
note(3.25, 'F#3', 0.9, pan(360)); // "dog"
|
|
244
|
+
counter(2.6, 5.9, 30, -0.6);
|
|
245
|
+
note(5.9, 'D3', 1.0, -0.6, false); // 1,000,000 settles
|
|
246
|
+
k.noise(6.0, { dur: 0.9, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.45, 650], [0.9, 280]], q: 0.7, gain: 0.08, a: 0.4 }); // camera eases in
|
|
247
|
+
|
|
248
|
+
// ---- loop: one particle passes over the core, out the wrong exit
|
|
249
|
+
carry(6.8, 8.0, pan(0), pan(cxs + 300), 1.1);
|
|
250
|
+
note(6.95, 'E4', 0.9, pan(200)); // "new photo of a cat"
|
|
251
|
+
note(7.95, 'F3', 1.2, pan(900), false); // guess: "dog?"
|
|
252
|
+
k.tone(8.0, { f: 'D4', dur: 0.5, gain: 0.12, a: 0.01, verb: 0.3, pan: pan(900) }); // the wrong guess: a soft falling pair
|
|
253
|
+
k.tone(8.18, { f: 'Bb3', dur: 0.9, gain: 0.13, a: 0.01, verb: 0.35, pan: pan(900) });
|
|
254
|
+
k.noise(8.0, { dur: 0.5, color: 'pink', filter: 'bandpass', f: [[0, 400], [0.5, 900]], q: 1.2, gain: 0.08, a: 0.06, pan: pan(900) }); // coral ring opens
|
|
255
|
+
note(8.32, 'C4', 0.8, -0.7); // check
|
|
256
|
+
carry(8.4, 9.1, pan(cxs + 300), pan(cxs), 1.1, 900, 380); // error counter-current runs back
|
|
257
|
+
// adjust: the circulation flips (9.0 -> 9.9), a broad slow breath that turns over
|
|
258
|
+
k.noise(9.0, { dur: 1.0, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.5, 800], [1.0, 350]], q: 0.8, gain: 0.13, a: 0.45, pan: pan(cxs), verb: 0.25 });
|
|
259
|
+
note(9.05, 'G3', 1.0, pan(cxs));
|
|
260
|
+
carry(9.7, 10.6, pan(0), pan(cxs + 300), 1.0, 450, 1200); // particle 2 slips under the core
|
|
261
|
+
['D4', 'A4', 'F#4'].forEach((f, i) => k.tone(10.3 + i * 0.07, { f, dur: 1.4, gain: 0.11 - i * 0.015, a: 0.01, verb: 0.4, pan: pan(900) })); // "cat" ✓
|
|
262
|
+
k.noise(10.5, { dur: 0.9, color: 'pink', filter: 'bandpass', f: [[0, 280], [0.45, 600], [0.9, 260]], q: 0.7, gain: 0.08, a: 0.4 }); // camera eases out
|
|
263
|
+
|
|
264
|
+
// ---- progress: the field sorts itself; accuracy counts up
|
|
265
|
+
counter(11.0, 12.9, 36, 0.8);
|
|
266
|
+
// 97%: the core rim flashes, the underline draws: the big hit, a deep soft bloom
|
|
267
|
+
k.play('impact', 12.9, { gain: 0.45, pan: 0.2 });
|
|
268
|
+
k.tone(12.9, { f: 'D2', dur: 2.2, gain: 0.22, a: 0.02, verb: 0.4 });
|
|
269
|
+
['D4', 'A4'].forEach((f, i) => k.play('ding', 13.0 + i * 0.07, { f, gain: 0.45, pan: 0.5 - i * 0.3 }));
|
|
270
|
+
k.noise(12.9, { dur: 0.4, color: 'pink', filter: 'bandpass', f: [[0, 600], [0.4, 1100]], q: 1, gain: 0.06, a: 0.1, pan: 0.85, minor: true }); // underline
|
|
271
|
+
// takeaway surfaces: the resolve
|
|
272
|
+
['D3', 'A3', 'F#4', 'A4'].forEach((f, i) => k.tone(13.4 + i * 0.09, { f, dur: 1.5, gain: 0.1 - i * 0.012, a: 0.02, verb: 0.45, pan: (i - 1.5) * 0.2 }));
|
|
273
|
+
},
|
|
274
|
+
explainerDuration: 15,
|
|
275
|
+
explainerNote: 'The model is the dark core of a flow field: labelled particles spiral into it as data, the camera pushes in as a new particle is carried out the wrong exit ("dog?"), a coral counter-current carries the error back, the circulation re-combs the whole field so the next one exits "cat", and training is shown as the turbulent field combing into smooth order, all captioned in hairline Space Mono.',
|
|
276
|
+
explainer(ctx, W, H_, t) {
|
|
277
|
+
const S = (a, b) => H.clamp((t - a) / (b - a));
|
|
278
|
+
const cx = W * 0.42, cy = H_ * 0.48, Rc = 92;
|
|
279
|
+
const TEAL = [95, 179, 166], CORAL = [222, 122, 82], IVORY = [243, 230, 200];
|
|
280
|
+
// ---- field parameters over the story ----
|
|
281
|
+
const swirl = 120 * (1 - S(5.6, 6.6)); // data: the core is a vortex drinking examples
|
|
282
|
+
const circ = t < 6.6 ? 0 : H.lerp(-55, 55, H.ease.inOut(S(9.0, 9.9))); // adjust: circulation flips which side wins
|
|
283
|
+
const turb = H.lerp(180, 26, H.ease.inOut(S(10.8, 12.9))); // progress: turbulence combs into order
|
|
284
|
+
// ---- camera ----
|
|
285
|
+
const CK = [[0, 1, cx, cy], [6.0, 1, cx, cy], [6.8, 1.18, cx + 110, cy], [10.5, 1.18, cx + 110, cy], [11.3, 1, cx, cy], [15, 1, cx, cy]];
|
|
286
|
+
let cs = 1, ccx = cx, ccy = cy;
|
|
287
|
+
for (let i = 0; i < CK.length - 1; i++) if (t >= CK[i][0] && t <= CK[i + 1][0]) { const u = H.ease.inOut((t - CK[i][0]) / (CK[i + 1][0] - CK[i][0])); cs = H.lerp(CK[i][1], CK[i + 1][1], u); ccx = H.lerp(CK[i][2], CK[i + 1][2], u); ccy = H.lerp(CK[i][3], CK[i + 1][3], u); break; }
|
|
288
|
+
const bg = ctx.createRadialGradient(W * 0.5, H_ * 0.48, 40, W * 0.5, H_ * 0.5, W * 0.75);
|
|
289
|
+
bg.addColorStop(0, '#16182A'); bg.addColorStop(1, '#08090E');
|
|
290
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
291
|
+
ctx.save(); ctx.translate(W / 2 + (cx - W / 2), H_ / 2 + (cy - H_ / 2)); ctx.scale(cs, cs); ctx.translate(-ccx, -ccy);
|
|
292
|
+
// world -> screen helper for HUD-attached labels
|
|
293
|
+
const toS = (x, y) => [(x - ccx) * cs + cx, (y - ccy) * cs + cy];
|
|
294
|
+
|
|
295
|
+
// ---- stream function on a grid ----
|
|
296
|
+
const nz = H.makeNoise(23), nz2 = H.makeNoise(57);
|
|
297
|
+
const G = 5, gx0 = -40, gy0 = -40, gw = Math.ceil((W + 80) / G) + 2, gh = Math.ceil((H_ + 80) / G) + 2;
|
|
298
|
+
const psi = new Float32Array(gw * gh);
|
|
299
|
+
const z = t * 0.06;
|
|
300
|
+
for (let j = 0; j < gh; j++) for (let i = 0; i < gw; i++) {
|
|
301
|
+
const x = gx0 + i * G, y = gy0 + j * G, dx = x - cx, dy = y - cy, r2 = dx * dx + dy * dy, r = Math.sqrt(r2);
|
|
302
|
+
const pot = dy * (1 - (Rc * Rc) / Math.max(r2, 1));
|
|
303
|
+
const away = H.smooth(Rc * 1.05, Rc * 2.2, r);
|
|
304
|
+
const n = nz.fbm3(x * 0.0026, y * 0.0026, z, 3) * turb;
|
|
305
|
+
psi[j * gw + i] = pot + n * away + (swirl + circ) * Math.log(Math.max(r, 1) / Rc);
|
|
306
|
+
}
|
|
307
|
+
const sample = (x, y, out) => {
|
|
308
|
+
const fx = (x - gx0) / G, fy = (y - gy0) / G, i = Math.floor(fx), j = Math.floor(fy);
|
|
309
|
+
if (i < 1 || j < 1 || i >= gw - 2 || j >= gh - 2) return false;
|
|
310
|
+
const k = j * gw + i, u = fx - i, v = fy - j;
|
|
311
|
+
const p = (kk) => psi[kk];
|
|
312
|
+
const dpy = ((p(k + gw) - p(k - gw)) * (1 - u) + (p(k + gw + 1) - p(k - gw + 1)) * u) / (2 * G);
|
|
313
|
+
const dpx = ((p(k + 1) - p(k - 1)) * (1 - v) + (p(k + gw + 1) - p(k + gw - 1)) * v) / (2 * G);
|
|
314
|
+
out[0] = dpy; out[1] = -dpx; out[2] = p(k) * (1 - u) * (1 - v) + p(k + 1) * u * (1 - v) + p(k + gw) * (1 - u) * v + p(k + gw + 1) * u * v;
|
|
315
|
+
return true;
|
|
316
|
+
};
|
|
317
|
+
// ---- background streamlines (grow in during the hook) ----
|
|
318
|
+
const DY = new Float32Array(gw * gh), DX = new Float32Array(gw * gh);
|
|
319
|
+
for (let j = 1; j < gh - 1; j++) for (let i = 1, k = j * gw + 1; i < gw - 1; i++, k++) { DY[k] = (psi[k + gw] - psi[k - gw]) / (2 * G); DX[k] = (psi[k + 1] - psi[k - 1]) / (2 * G); }
|
|
320
|
+
const NB = 24, buckets = Array.from({ length: NB }, () => []), sparkLines = [];
|
|
321
|
+
const rng = H.rng(91), v = [0, 0, 0];
|
|
322
|
+
const grow = H.ease.out(S(0, 2.2));
|
|
323
|
+
const STEPS = Math.floor(10 + 120 * grow), STEP = 2.4;
|
|
324
|
+
const inCore = (x, y) => (x - cx) ** 2 + (y - cy) ** 2 < Rc * Rc * 0.97;
|
|
325
|
+
for (let pI = 0; pI < 4600; pI++) {
|
|
326
|
+
const sx = rng.range(-30, W + 30), sy = rng.range(-30, H_ + 30);
|
|
327
|
+
// uneven seeding as in the still: dense luminous braids separated by quiet gaps
|
|
328
|
+
const dens = 0.5 + 0.5 * nz.n3(sx * 0.004, sy * 0.009, 7.3);
|
|
329
|
+
if (rng() > 0.1 + dens * dens * 1.45) continue;
|
|
330
|
+
if (inCore(sx, sy) || !sample(sx, sy, v)) continue;
|
|
331
|
+
const u = H.clamp((v[2] + 320) / 640 + nz2.n2(sx * 0.0035, sy * 0.0035) * 0.2);
|
|
332
|
+
const line = [];
|
|
333
|
+
for (const dir of [-1, 1]) {
|
|
334
|
+
let x = sx, y = sy; const seg = [];
|
|
335
|
+
for (let s = 0; s < STEPS; s++) {
|
|
336
|
+
// same velocity as sample(), read from the precomputed difference grids
|
|
337
|
+
const fx = (x - gx0) / G, fy = (y - gy0) / G, i = Math.floor(fx), j = Math.floor(fy);
|
|
338
|
+
if (i < 1 || j < 1 || i >= gw - 2 || j >= gh - 2) break;
|
|
339
|
+
const k = j * gw + i, uu = fx - i, vv = fy - j;
|
|
340
|
+
const vxx = DY[k] * (1 - uu) + DY[k + 1] * uu, vyy = -(DX[k] * (1 - vv) + DX[k + gw] * vv);
|
|
341
|
+
const m = Math.sqrt(vxx * vxx + vyy * vyy) || 1;
|
|
342
|
+
x += dir * vxx / m * STEP; y += dir * vyy / m * STEP; if ((x - cx) * (x - cx) + (y - cy) * (y - cy) < Rc * Rc * 0.97) break; seg.push(x, y);
|
|
343
|
+
}
|
|
344
|
+
if (dir === -1) { for (let q = seg.length - 2; q >= 0; q -= 2) line.push(seg[q], seg[q + 1]); line.push(sx, sy); } else line.push(...seg);
|
|
345
|
+
}
|
|
346
|
+
if (line.length > 8) {
|
|
347
|
+
buckets[Math.min(NB - 1, Math.floor(u * NB))].push(line);
|
|
348
|
+
if (sparkLines.length < 90 && line.length > 60 && (pI * 7919) % 23 === 0) sparkLines.push({ L: line, u, ph: (pI * 0.6180339) % 1, sp: 60 + ((pI * 37) % 61) });
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
// hairlines into an offscreen layer (camera applied there), then the still's bloom + sharp composite
|
|
352
|
+
const order = H.ease.inOut(S(10.8, 12.9));
|
|
353
|
+
if (!FX.lay || FX.lay.width !== W || FX.lay.height !== H_) { FX.lay = H.layer(W, H_); FX.sm = H.layer(W / 4, H_ / 4); }
|
|
354
|
+
const lc = FX.lay.getContext('2d');
|
|
355
|
+
lc.setTransform(1, 0, 0, 1, 0, 0); lc.globalCompositeOperation = 'source-over'; lc.clearRect(0, 0, W, H_);
|
|
356
|
+
lc.translate(cx, cy); lc.scale(cs, cs); lc.translate(-ccx, -ccy);
|
|
357
|
+
lc.globalCompositeOperation = 'lighter'; lc.lineCap = 'butt'; lc.lineJoin = 'bevel'; lc.lineWidth = 0.7 / cs;
|
|
358
|
+
for (let b = 0; b < NB; b++) {
|
|
359
|
+
lc.strokeStyle = H.rgba(pal((b + 0.5) / NB), 0.12 + 0.04 * order);
|
|
360
|
+
lc.beginPath(); for (const L of buckets[b]) { lc.moveTo(L[0], L[1]); for (let q = 4; q < L.length; q += 4) lc.lineTo(L[q], L[q + 1]); if (L.length % 4 === 0) lc.lineTo(L[L.length - 2], L[L.length - 1]); } lc.stroke();
|
|
361
|
+
}
|
|
362
|
+
ctx.save(); ctx.setTransform(ctx.getTransform().multiply(new DOMMatrix().translate(ccx, ccy).scale(1 / cs, 1 / cs).translate(-cx, -cy)));
|
|
363
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
364
|
+
const sc = FX.sm.getContext('2d'); sc.clearRect(0, 0, W / 4, H_ / 4); sc.filter = 'blur(3px)'; sc.drawImage(FX.lay, 0, 0, W / 4, H_ / 4); sc.filter = 'none';
|
|
365
|
+
ctx.globalAlpha = 0.6; ctx.drawImage(FX.sm, 0, 0, W, H_);
|
|
366
|
+
ctx.globalAlpha = 1; ctx.drawImage(FX.lay, 0, 0);
|
|
367
|
+
ctx.restore();
|
|
368
|
+
// sparks: bright heads riding the currents (the still's shimmer, at about half density)
|
|
369
|
+
const sparkA = S(1.4, 2.6) * 0.85;
|
|
370
|
+
if (sparkA > 0) {
|
|
371
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.lineCap = 'round'; ctx.lineWidth = 1.2 / cs;
|
|
372
|
+
for (const Sp of sparkLines) {
|
|
373
|
+
const nPts = Sp.L.length / 2, len = nPts * STEP;
|
|
374
|
+
const head = ((t * Sp.sp + Sp.ph * len) % (len + 200)) / STEP;
|
|
375
|
+
if (head >= nPts) continue;
|
|
376
|
+
const tail = Math.max(0, Math.floor(head) - 22), hI = Math.floor(head);
|
|
377
|
+
if (hI - tail < 2) continue;
|
|
378
|
+
const col = pal(Sp.u), g = ctx.createLinearGradient(Sp.L[tail * 2], Sp.L[tail * 2 + 1], Sp.L[hI * 2], Sp.L[hI * 2 + 1]);
|
|
379
|
+
g.addColorStop(0, H.rgba(col, 0)); g.addColorStop(1, H.rgba(H.mix(col, [255, 250, 235], 0.5), 0.6 * sparkA));
|
|
380
|
+
ctx.strokeStyle = g;
|
|
381
|
+
ctx.beginPath(); ctx.moveTo(Sp.L[tail * 2], Sp.L[tail * 2 + 1]);
|
|
382
|
+
for (let q = tail + 1; q <= hI; q++) ctx.lineTo(Sp.L[q * 2], Sp.L[q * 2 + 1]);
|
|
383
|
+
ctx.stroke();
|
|
384
|
+
}
|
|
385
|
+
ctx.restore();
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// ---- the core (the model) ----
|
|
389
|
+
const flash = S(12.9, 13.1) * (1 - S(13.3, 14.0));
|
|
390
|
+
coreBody(ctx, cx, cy, Rc, 1);
|
|
391
|
+
ctx.strokeStyle = `rgba(243,230,200,${0.4 + 0.5 * flash})`; ctx.lineWidth = (0.8 + 2 * flash) / cs; ctx.beginPath(); ctx.arc(cx, cy, Rc * 0.985, 0, 7); ctx.stroke();
|
|
392
|
+
|
|
393
|
+
// ---- particle helpers ----
|
|
394
|
+
const dot = (x, y, col, r, a = 1) => { const g = ctx.createRadialGradient(x, y, 0, x, y, r * 4); g.addColorStop(0, H.rgba(col, 0.9 * a)); g.addColorStop(1, H.rgba(col, 0)); ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, r * 4, 0, 7); ctx.fill(); ctx.restore(); ctx.fillStyle = H.rgba(IVORY, a); ctx.beginPath(); ctx.arc(x, y, r * 0.6, 0, 7); ctx.fill(); };
|
|
395
|
+
const trail = (P, col, a = 1) => { if (P.length < 2) return; ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.lineCap = 'round'; for (let i = 1; i < P.length; i++) { ctx.strokeStyle = H.rgba(col, a * 0.8 * i / P.length); ctx.lineWidth = 1.6 / cs; ctx.beginPath(); ctx.moveTo(...P[i - 1]); ctx.lineTo(...P[i]); ctx.stroke(); } ctx.restore(); };
|
|
396
|
+
// spiral into the core (data beat)
|
|
397
|
+
const spiral = (r0, th0, u) => { const r = H.lerp(r0, Rc * 0.95, Math.pow(u, 0.8)), th = th0 + u * u * 2.6; return [cx + Math.cos(th) * r, cy + Math.sin(th) * r]; };
|
|
398
|
+
// scripted pass over (up=+1 → dog exit) or under (up=-1 → cat exit) the core
|
|
399
|
+
const pass = (up, u, y0 = cy) => {
|
|
400
|
+
const s = up > 0 ? -1 : 1;
|
|
401
|
+
const P = [[-30, y0], [cx - Rc - 160, y0 + s * 6], [cx - Rc * 0.9, cy + s * (Rc * 0.75)], [cx, cy + s * (Rc + 26)], [cx + Rc * 0.95, cy + s * (Rc * 0.8)], [cx + Rc + 150, cy + s * 120], [cx + Rc + 330, cy + s * 195]];
|
|
402
|
+
const f = H.clamp(u) * (P.length - 1), i = Math.min(P.length - 2, Math.floor(f)), w = f - i;
|
|
403
|
+
const p0 = P[Math.max(0, i - 1)], p1 = P[i], p2 = P[i + 1], p3 = P[Math.min(P.length - 1, i + 2)];
|
|
404
|
+
const cr = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * w + (2 * a - 5 * b + 4 * c - d) * w * w + (-a + 3 * b - 3 * c + d) * w * w * w);
|
|
405
|
+
return [cr(p0[0], p1[0], p2[0], p3[0]), cr(p0[1], p1[1], p2[1], p3[1])];
|
|
406
|
+
};
|
|
407
|
+
const hist = (fn, u, n = 12, du = 0.012) => { const P = []; for (let k = n; k >= 0; k--) P.push(fn(u - k * du)); return P; };
|
|
408
|
+
|
|
409
|
+
// ================= DATA: labelled particles spiral into the core =================
|
|
410
|
+
const dA = S(2.4, 2.8) * (1 - S(6.2, 6.6));
|
|
411
|
+
let tagCat = null, tagDog = null;
|
|
412
|
+
if (dA > 0) {
|
|
413
|
+
const pr = H.rng(404);
|
|
414
|
+
for (let k = 0; k < 70; k++) {
|
|
415
|
+
const isCat = pr.chance(0.5), r0 = pr.range(560, 820), th0 = Math.PI + pr.range(-0.5, 0.5), t0 = 2.4 + pr.range(0, 3.4), dur = pr.range(1.6, 2.4);
|
|
416
|
+
const u = (t - t0) / dur; if (u <= 0 || u >= 1) continue;
|
|
417
|
+
const col = isCat ? TEAL : CORAL, P = hist((uu) => spiral(r0, th0, H.clamp(uu)), u, 10, 0.02);
|
|
418
|
+
trail(P, col, dA); const [x, y] = P[P.length - 1]; dot(x, y, col, 2.4, dA * (1 - S(0.85, 1) * 0));
|
|
419
|
+
|
|
420
|
+
if (!tagCat && isCat && u > 0.15 && u < 0.6 && y > 190 && x > 120) tagCat = [x, y];
|
|
421
|
+
if (!tagDog && !isCat && u > 0.15 && u < 0.6 && y > 190 && x > 120) tagDog = [x, y];
|
|
422
|
+
}
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// ================= LOOP: one new particle, wrong exit, error back, re-comb, right exit =================
|
|
426
|
+
const loopA = S(6.6, 6.9) * (1 - S(10.6, 11.0));
|
|
427
|
+
let p1 = null, p2 = null;
|
|
428
|
+
if (loopA > 0) {
|
|
429
|
+
// splitter: two exits
|
|
430
|
+
// particle 1 (above → dog)
|
|
431
|
+
const u1 = H.ease.inOut(S(6.8, 8.0));
|
|
432
|
+
if (t >= 6.8 && t < 9.9) { const P = hist((uu) => pass(1, uu), u1, 14, 0.01); trail(P, IVORY, loopA); p1 = P[P.length - 1]; dot(...p1, IVORY, 3.2, loopA); }
|
|
433
|
+
// error counter-current: coral streak running back from the dog exit to the core
|
|
434
|
+
const e = S(8.4, 9.1);
|
|
435
|
+
if (e > 0 && t < 9.9) {
|
|
436
|
+
const P = []; for (let k = 0; k <= 30; k++) { const uu = 1 - e * (k / 30) * 0.62; P.push(pass(1, uu)); }
|
|
437
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.strokeStyle = H.rgba(CORAL, 0.85 * (1 - S(9.5, 9.9))); ctx.lineWidth = 3 / cs; ctx.lineCap = 'round';
|
|
438
|
+
ctx.setLineDash([10 / cs, 6 / cs]); ctx.lineDashOffset = t * 60 / cs;
|
|
439
|
+
ctx.beginPath(); P.forEach(([x, y], i) => (i ? ctx.lineTo(x, y) : ctx.moveTo(x, y))); ctx.stroke(); ctx.restore();
|
|
440
|
+
}
|
|
441
|
+
// particle 2 (below → cat)
|
|
442
|
+
const u2 = H.ease.inOut(S(9.7, 10.6));
|
|
443
|
+
if (t >= 9.7) { const P = hist((uu) => pass(-1, uu), u2, 14, 0.01); trail(P, TEAL, loopA); p2 = P[P.length - 1]; dot(...p2, TEAL, 3.2, loopA); }
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// ================= PROGRESS: many particles now sort themselves correctly =================
|
|
447
|
+
if (t > 10.9) {
|
|
448
|
+
const pr = H.rng(808), acc = 0.61 + 0.36 * H.ease.inOut(S(11.0, 12.9));
|
|
449
|
+
for (let k = 0; k < 46; k++) {
|
|
450
|
+
const isCat = pr.chance(0.5), t0 = 10.9 + pr.range(0, 3.8), dur = pr.range(1.4, 2.0), y0 = cy + pr.range(-60, 60), right = pr() < acc;
|
|
451
|
+
const u = (t - t0) / dur; if (u <= 0 || u >= 1) continue;
|
|
452
|
+
const up = isCat ? (right ? -1 : 1) : (right ? 1 : -1);
|
|
453
|
+
const P = hist((uu) => pass(up, uu, y0), u, 10, 0.012), col = isCat ? TEAL : CORAL;
|
|
454
|
+
trail(P, col, 0.9); dot(...P[P.length - 1], col, 2.2, 0.9);
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
ctx.restore(); // camera
|
|
458
|
+
|
|
459
|
+
// ================= labels (hairline Space Mono, attached to what they label) =================
|
|
460
|
+
const mono = (str, x, y, size, weight, a, align = 'left', col = 'rgba(236,228,210,0.88)') => { if (a <= 0) return; ctx.globalAlpha = a; ctx.font = `${weight} ${size}px "Space Mono"`; ctx.fillStyle = col; ctx.textAlign = align; ctx.textBaseline = 'alphabetic'; ctx.fillText(str, x, y); ctx.globalAlpha = 1; };
|
|
461
|
+
const lead = (x0, y0, x1, y1, a, col = 'rgba(236,228,210,0.6)') => { if (a <= 0) return; ctx.globalAlpha = a; ctx.strokeStyle = col; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke(); ctx.beginPath(); ctx.arc(x1, y1, 2, 0, 7); ctx.stroke(); ctx.globalAlpha = 1; };
|
|
462
|
+
// hook title → docks top-left
|
|
463
|
+
{
|
|
464
|
+
const d = H.ease.inOut(S(1.6, 2.4));
|
|
465
|
+
const x = H.lerp(W / 2, 44, d), y = H.lerp(H_ * 0.48 + 14, 56, d), sz = H.lerp(60, 30, d);
|
|
466
|
+
ctx.globalAlpha = S(0.1, 0.5); ctx.font = `700 ${sz}px "Space Mono"`; ctx.fillStyle = 'rgba(236,228,210,0.95)'; ctx.textAlign = d > 0.5 ? 'left' : 'center'; ctx.textBaseline = 'alphabetic';
|
|
467
|
+
if (d <= 0.5) { ctx.fillStyle = 'rgba(8,9,14,0.65)'; ctx.fillRect(W / 2 - 430, y - 70, 860, 100); ctx.fillStyle = 'rgba(236,228,210,0.95)'; }
|
|
468
|
+
let w = 0; const str = 'HOW DOES AI LEARN?', tr = H.lerp(5, 3, d); for (const ch of str) w += ctx.measureText(ch).width + tr;
|
|
469
|
+
let xx = d > 0.5 ? x : x - w / 2; ctx.textAlign = 'left'; for (const ch of str) { ctx.fillText(ch, xx, y); xx += ctx.measureText(ch).width + tr; }
|
|
470
|
+
ctx.globalAlpha = 1;
|
|
471
|
+
}
|
|
472
|
+
// data labels
|
|
473
|
+
const coreS = toS(cx + Rc * 0.7, cy - Rc * 0.7);
|
|
474
|
+
const mA = S(2.8, 3.2) * (1 - S(10.6, 11)) * (1 - S(8.4, 8.6) + S(9.8, 10.0));
|
|
475
|
+
lead(coreS[0] + 60, coreS[1] - 60, coreS[0], coreS[1], mA);
|
|
476
|
+
mono('the model', coreS[0] + 64, coreS[1] - 66, 30, 700, mA);
|
|
477
|
+
if (dA > 0) {
|
|
478
|
+
if (tagCat) { const [x, y] = toS(...tagCat); lead(x - 34, y - 30, x, y, dA * S(2.9, 3.2)); mono('cat', x - 38, y - 34, 28, 700, dA * S(2.9, 3.2), 'right', 'rgb(95,179,166)'); }
|
|
479
|
+
if (tagDog) { const [x, y] = toS(...tagDog); lead(x + 34, y + 30, x, y, dA * S(3.2, 3.5)); mono('dog', x + 38, y + 52, 28, 700, dA * S(3.2, 3.5), 'left', 'rgb(222,122,82)'); }
|
|
480
|
+
const n = Math.round(1e6 * H.ease.inOut(S(2.6, 5.9)));
|
|
481
|
+
mono(n.toLocaleString('en-US'), 44, H_ - 84, 74, 700, dA);
|
|
482
|
+
mono('labelled examples flowing in', 48, H_ - 44, 28, 400, dA, 'left', 'rgba(236,228,210,0.7)');
|
|
483
|
+
}
|
|
484
|
+
// loop labels
|
|
485
|
+
if (loopA > 0) {
|
|
486
|
+
const steps = ['guess', 'check', 'adjust'], act = t < 8.3 ? 0 : t < 9.0 ? 1 : t < 9.8 ? 2 : 0;
|
|
487
|
+
let x = 48; steps.forEach((s, i) => { ctx.font = '700 30px "Space Mono"'; const w = ctx.measureText(s).width; mono(s, x, 110, 30, 700, loopA, 'left', i === act ? 'rgba(236,228,210,0.98)' : 'rgba(236,228,210,0.35)'); if (i === act) { ctx.globalAlpha = loopA; ctx.fillStyle = 'rgba(236,228,210,0.9)'; ctx.fillRect(x, 120, w, 3); ctx.globalAlpha = 1; } x += w + 18; if (i < 2) { mono('→', x, 110, 30, 400, loopA, 'left', 'rgba(236,228,210,0.4)'); x += 48; } });
|
|
488
|
+
const [ux, uy] = toS(...pass(1, 1)), [lx, ly] = toS(...pass(-1, 1));
|
|
489
|
+
// exits: hairline gates with their labels
|
|
490
|
+
[[ux, uy, 'dog', 'rgb(222,122,82)'], [lx, ly, 'cat', 'rgb(95,179,166)']].forEach(([x, y, s, c]) => {
|
|
491
|
+
ctx.globalAlpha = loopA; ctx.strokeStyle = c; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.arc(x, y, 9, 0, 7); ctx.stroke(); ctx.globalAlpha = 1;
|
|
492
|
+
mono(s, x + 20, y + 11, 32, 700, loopA, 'left', c);
|
|
493
|
+
});
|
|
494
|
+
if (p1 && t < 8.3) { const [x, y] = toS(...p1); mono('new photo of a cat', Math.max(x + 24, 40), y - 26, 26, 700, S(6.9, 7.2) * (1 - S(7.6, 7.8))); }
|
|
495
|
+
if (t >= 7.9 && t < 9.9) {
|
|
496
|
+
const a = S(7.9, 8.1) * (1 - S(9.6, 9.9));
|
|
497
|
+
mono('guess: “dog?” ✗', ux - 60, uy - 34, 30, 700, a, 'left', 'rgba(236,228,210,0.98)');
|
|
498
|
+
const ring = S(8.0, 8.5); ctx.globalAlpha = a * (1 - ring * 0.6); ctx.strokeStyle = 'rgba(222,122,82,0.9)'; ctx.lineWidth = 1.4; ctx.beginPath(); ctx.arc(ux, uy, 12 + 46 * ring, 0, 7); ctx.stroke(); ctx.globalAlpha = 1;
|
|
499
|
+
mono('check: it was a cat', lx - 60, ly + 52, 26, 700, S(8.3, 8.6) * (1 - S(9.6, 9.9)), 'left');
|
|
500
|
+
const [mx, my] = toS(cx + Rc * 1.2, cy - Rc * 1.55);
|
|
501
|
+
mono('error flows back', mx, my, 28, 700, S(8.6, 8.9) * (1 - S(9.5, 9.8)), 'left', 'rgb(222,122,82)');
|
|
502
|
+
}
|
|
503
|
+
const [ax, ay] = toS(cx, cy + Rc + 40);
|
|
504
|
+
mono('adjust: the flow re-combs', ax, ay + 30, 28, 700, S(9.0, 9.3) * (1 - S(9.9, 10.2)), 'center');
|
|
505
|
+
if (t >= 10.3) mono('guess: “cat” ✓', lx - 60, ly + 52, 30, 700, S(10.3, 10.5) * loopA, 'left', 'rgb(95,179,166)');
|
|
506
|
+
}
|
|
507
|
+
// progress
|
|
508
|
+
if (t > 10.9) {
|
|
509
|
+
const a = S(11.0, 11.3), p = H.ease.inOut(S(11.0, 12.9));
|
|
510
|
+
mono('accuracy', W - 48, H_ - 150, 28, 400, a, 'right', 'rgba(236,228,210,0.7)');
|
|
511
|
+
mono(Math.round(61 + 36 * p) + '%', W - 48, H_ - 70, 88, 700, a, 'right');
|
|
512
|
+
mono('turbulence combs into order', 48, H_ - 84, 28, 700, S(11.2, 11.6) * (1 - S(13.1, 13.4)));
|
|
513
|
+
// sparkline
|
|
514
|
+
ctx.globalAlpha = a; ctx.strokeStyle = 'rgba(236,228,210,0.8)'; ctx.lineWidth = 1; ctx.beginPath();
|
|
515
|
+
for (let k = 0; k <= 50 * p; k++) { const u = k / 50, vv = 1 - Math.exp(-u * 4); const x = W - 440 + u * 150, y = H_ - 96 - vv * 44; k ? ctx.lineTo(x, y) : ctx.moveTo(x, y); }
|
|
516
|
+
ctx.stroke();
|
|
517
|
+
// baseline, ticks and a glowing end-dot so it reads as a chart
|
|
518
|
+
ctx.strokeStyle = 'rgba(236,228,210,0.28)'; ctx.beginPath(); ctx.moveTo(W - 444, H_ - 95.5); ctx.lineTo(W - 286, H_ - 95.5);
|
|
519
|
+
for (let k = 0; k <= 5; k++) { const x = Math.round(W - 440 + k * 30) + 0.5; ctx.moveTo(x, H_ - 95.5); ctx.lineTo(x, H_ - 91); } ctx.stroke();
|
|
520
|
+
{ const vv = 1 - Math.exp(-p * 4), ex = W - 440 + p * 150, ey = H_ - 96 - vv * 44;
|
|
521
|
+
const g = ctx.createRadialGradient(ex, ey, 0, ex, ey, 10); g.addColorStop(0, 'rgba(255,240,215,0.7)'); g.addColorStop(1, 'rgba(255,240,215,0)');
|
|
522
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = g; ctx.beginPath(); ctx.arc(ex, ey, 10, 0, 7); ctx.fill(); ctx.restore();
|
|
523
|
+
ctx.fillStyle = 'rgba(246,238,222,0.95)'; ctx.beginPath(); ctx.arc(ex, ey, 2.4, 0, 7); ctx.fill(); }
|
|
524
|
+
ctx.globalAlpha = 1;
|
|
525
|
+
const em = S(12.9, 13.3); if (em > 0) { ctx.fillStyle = 'rgba(236,228,210,0.95)'; ctx.fillRect(W - 48 - 170 * em, H_ - 56, 170 * em, 3); }
|
|
526
|
+
}
|
|
527
|
+
mono('MORE EXAMPLES → BETTER GUESSES', W / 2 - 40, H_ - 26, 44, 700, S(13.3, 13.8), 'center', 'rgba(236,228,210,0.98)');
|
|
528
|
+
H.grain(ctx, W, H_, 0.035, 12 + Math.floor(t * 12));
|
|
529
|
+
},
|
|
530
|
+
});
|
|
531
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 84,
|
|
3
|
+
"name": "Flow Field",
|
|
4
|
+
"nameZh": "流场",
|
|
5
|
+
"family": "Data & Information",
|
|
6
|
+
"familyZh": "数据与信息",
|
|
7
|
+
"description": "Thousands of hairline streamlines traced through a curl-noise field that parts around an unseen core; additive colour from a refined gradient builds luminous braids on dark paper.",
|
|
8
|
+
"motionNotes": "Motion: The dark core appears first, then particles are released in a wave from left to right and trace their streamlines outward until the braids glow; in the hold the field slowly breathes while bright sparks ride the currents.",
|
|
9
|
+
"explainerNotes": "Explainer: The model is the dark core of a flow field: labelled particles spiral into it as data, the camera pushes in as a new particle is carried out the wrong exit (\"dog?\"), a coral counter-current carries the error back, the circulation re-combs the whole field so the next one exits \"cat\", and training is shown as the turbulent field combing into smooth order, all captioned in hairline Space Mono.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "flow-field",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|