@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
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 53,
|
|
3
|
+
"name": "Vaporwave",
|
|
4
|
+
"nameZh": "蒸汽波",
|
|
5
|
+
"family": "Retro UI & Neon",
|
|
6
|
+
"familyZh": "复古界面与霓虹",
|
|
7
|
+
"description": "Pastel-neon dreamscape: sliced sunset sun, glowing grid floor, a glitching marble bust on a checkerboard plinth, palm silhouettes, katakana, and a chewed-up VHS tape finish.",
|
|
8
|
+
"motionNotes": "Motion: Tape starts on a blue VCR screen and rolls into lock, the striped sun rises, palms slide in, the bust glitches into existence slice by slice, katakana stamps on with chroma split and a tracking band crawls down.",
|
|
9
|
+
"explainerNotes": "Explainer: Told on a worn VHS tape: scenes change with a tape cut (VCR blue screen, fast-forward, vertical roll), the header lives on as the VCR on-screen menu, the counter is tape-counter digits, labelled polaroids slide along the neon floor into the marble-bust \"model\", a pastel guess.exe pop-up does the guessing and the chart is drawn in neon across the sky.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "vaporwave",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|
|
@@ -0,0 +1,504 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Ebru / suminagashi marbling, simulated FORWARD with the mathematical marbling transforms
|
|
3
|
+
// (Jaffer & Lu): ink drop p' = c + (p-c)·sqrt(1 + r²/|p-c|²)
|
|
4
|
+
// tine line p' = p + α·λ/(d+λ)·M (d = distance to the stylus line)
|
|
5
|
+
// comb = a periodic set of tine lines with alternating direction
|
|
6
|
+
// Every floated colour is a polygon; each later operation moves all earlier vertices, with
|
|
7
|
+
// adaptive re-subdivision so stretched veins stay smooth. Pigment speckle is sampled in the
|
|
8
|
+
// pre-comb coordinates, so it is dragged along the rake like real floated paint.
|
|
9
|
+
const TAU = Math.PI * 2;
|
|
10
|
+
const PAL = {
|
|
11
|
+
indigo: '#1d2a5e', red: '#a6252b', ochre: '#d89c2c', teal: '#21807c', cream: '#efe3c4',
|
|
12
|
+
ink: '#1b1d27', slate: '#3f5470', rose: '#cf6f5e', sky: '#7ea6c4', sand: '#d9bf86', green: '#4f6b3a',
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// ---- transforms (forward) ----
|
|
16
|
+
const T = {
|
|
17
|
+
drop: (o) => (x, y) => { const dx = x - o.x, dy = y - o.y, d2 = dx * dx + dy * dy + 1e-9, f = Math.sqrt(1 + (o.r * o.r) / d2); return [o.x + dx * f, o.y + dy * f]; },
|
|
18
|
+
tine: (o) => (x, y) => { const d = Math.abs((x - o.x) * -o.my + (y - o.y) * o.mx), z = (o.a * o.l) / (d + o.l); return [x + z * o.mx, y + z * o.my]; },
|
|
19
|
+
// periodic comb: tines every S px along one axis, alternating push direction
|
|
20
|
+
comb: (o) => (x, y) => { const u = o.vertical ? x : y; const dv = combShift(u, o); return o.vertical ? [x, y + dv] : [x + dv, y]; },
|
|
21
|
+
wave: (o) => (x, y) => o.vertical ? [x, y + o.A * Math.sin((x / o.L) * TAU + o.ph)] : [x + o.A * Math.sin((y / o.L) * TAU + o.ph), y],
|
|
22
|
+
};
|
|
23
|
+
function combShift(u, o) {
|
|
24
|
+
const j0 = Math.round((u - o.off) / o.S);
|
|
25
|
+
let s = 0;
|
|
26
|
+
for (let j = j0 - 3; j <= j0 + 3; j++) { const d = Math.abs(u - (o.off + j * o.S)); s += ((j & 1) ? -1 : 1) * (o.a * o.l) / (d + o.l); }
|
|
27
|
+
return s;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function applyOp(polys, f, maxLen) {
|
|
31
|
+
for (const P of polys) {
|
|
32
|
+
if (P.dead) continue;
|
|
33
|
+
const src = P.pts, out = [];
|
|
34
|
+
const n = src.length / 2;
|
|
35
|
+
const tp = new Array(n);
|
|
36
|
+
for (let i = 0; i < n; i++) tp[i] = f(src[i * 2], src[i * 2 + 1]);
|
|
37
|
+
const push = (ax, ay, bx, by, A, B, depth) => {
|
|
38
|
+
// emits A then (recursively) the midpoints needed so that |A'B'| <= maxLen
|
|
39
|
+
const ddx = B[0] - A[0], ddy = B[1] - A[1];
|
|
40
|
+
if (depth < 5 && ddx * ddx + ddy * ddy > maxLen * maxLen) {
|
|
41
|
+
const mx = (ax + bx) / 2, my = (ay + by) / 2, M = f(mx, my);
|
|
42
|
+
push(ax, ay, mx, my, A, M, depth + 1);
|
|
43
|
+
push(mx, my, bx, by, M, B, depth + 1);
|
|
44
|
+
} else out.push(A[0], A[1]);
|
|
45
|
+
};
|
|
46
|
+
// NB: subdivision samples pre-images; we keep transformed coordinates as the new polygon
|
|
47
|
+
for (let i = 0; i < n; i++) {
|
|
48
|
+
const j = (i + 1) % n;
|
|
49
|
+
push(src[i * 2], src[i * 2 + 1], src[j * 2], src[j * 2 + 1], tp[i], tp[j], 0);
|
|
50
|
+
}
|
|
51
|
+
P.pts = out;
|
|
52
|
+
// cull polygons that have left the sheet entirely
|
|
53
|
+
let minx = 1e9, maxx = -1e9, miny = 1e9, maxy = -1e9;
|
|
54
|
+
for (let i = 0; i < out.length; i += 2) { const x = out[i], y = out[i + 1]; if (x < minx) minx = x; if (x > maxx) maxx = x; if (y < miny) miny = y; if (y > maxy) maxy = y; }
|
|
55
|
+
if (minx > 1500 || maxx < -220 || miny > 940 || maxy < -220) P.dead = true;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function circle(x, y, r) {
|
|
59
|
+
const n = Math.max(48, Math.min(360, Math.round((TAU * r) / 3)));
|
|
60
|
+
const pts = [];
|
|
61
|
+
for (let i = 0; i < n; i++) { const a = (i / n) * TAU; pts.push(x + Math.cos(a) * r, y + Math.sin(a) * r); }
|
|
62
|
+
return pts;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
// =====================================================================================
|
|
67
|
+
// EXPLAINER — the model is the marbling bath. Examples are drops of colour landing on it
|
|
68
|
+
// (ochre = cat, indigo = dog). A new picture is a red probe drop; the model's "guess" is the
|
|
69
|
+
// way its current flow drags the probe. The error is a counter-stroke of the stylus, and the
|
|
70
|
+
// adjustment is a comb pass that puts the pattern in order. Scenes change the marbler's way:
|
|
71
|
+
// the printed sheet is lifted off the bath, revealing the next bath underneath.
|
|
72
|
+
// =====================================================================================
|
|
73
|
+
const PX = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
74
|
+
const INKB = '#2a2018', REDI = '#a6252b';
|
|
75
|
+
let PM_TEX = null, PM_STILL = null, PM_SPK = null;
|
|
76
|
+
function pmTexture(W, H_) {
|
|
77
|
+
if (PM_TEX) return PM_TEX;
|
|
78
|
+
PM_TEX = H.layer(W, H_); const c = PM_TEX.getContext('2d'), img = c.createImageData(W, H_), d = img.data;
|
|
79
|
+
const nz = H.makeNoise(3), nz2 = H.makeNoise(8), cn = H.makeNoise(44), r = H.rng(9);
|
|
80
|
+
for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) {
|
|
81
|
+
const sp = nz.n2(x * 0.08, y * 0.08) * 0.5 + nz2.n2(x * 0.35, y * 0.35) * 0.5;
|
|
82
|
+
const laid = (x % 3 === 0 ? -0.025 : 0) + ((x + 9) % 64 < 1.5 ? -0.03 : 0);
|
|
83
|
+
const cock = cn.fbm2(x * 0.0044, y * 0.0044, 3) * 0.11 + cn.fbm2(x * 0.035 / 8 + 7, y * 0.035 / 8, 3) * 0.035; // broad cockle + the still's finer drying undulation
|
|
84
|
+
const k = H.clamp(0.93 + sp * 0.07 + laid + cock + (r() - 0.5) * 0.04, 0, 1);
|
|
85
|
+
const o = (y * W + x) * 4; d[o] = d[o + 1] = d[o + 2] = 255 * k; d[o + 3] = 255;
|
|
86
|
+
}
|
|
87
|
+
c.putImageData(img, 0, 0); return PM_TEX;
|
|
88
|
+
}
|
|
89
|
+
function pmStill(W, H_) {
|
|
90
|
+
if (PM_STILL) return PM_STILL;
|
|
91
|
+
PM_STILL = H.layer(W, H_); const st = window.STYLES.find((s) => s.id === 'paper-marbling'); H.render(st, PM_STILL, 7.9); return PM_STILL;
|
|
92
|
+
}
|
|
93
|
+
// pigment pooling at a floated drop's rim: the drop's own colour, darker (not a neutral ink outline)
|
|
94
|
+
const RIM = {}, HALO = {};
|
|
95
|
+
const rimOf = (col) => RIM[col] || (RIM[col] = H.rgba(H.hex(col).map((v) => v * 0.66), 0.72));
|
|
96
|
+
const haloOf = (col) => HALO[col] || (HALO[col] = H.rgba(H.mix(col, '#fff6e2', 0.35), 0.38));
|
|
97
|
+
// cockle (the hero's soft-light undulation from the wetted sheet drying), built once per size
|
|
98
|
+
let PM_COCK = null;
|
|
99
|
+
function pmCockle(W, H_) {
|
|
100
|
+
if (PM_COCK && PM_COCK.w === W) return PM_COCK.c;
|
|
101
|
+
const cn = H.makeNoise(44), sw = Math.round(W / 8), sh = Math.round(H_ / 8);
|
|
102
|
+
const lay = H.layer(sw, sh), lc = lay.getContext('2d'), li = lc.createImageData(sw, sh);
|
|
103
|
+
for (let y = 0; y < sh; y++) for (let x = 0; x < sw; x++) {
|
|
104
|
+
const v = cn.fbm2(x * 0.035, y * 0.035, 3), o = (y * sw + x) * 4;
|
|
105
|
+
li.data[o] = li.data[o + 1] = li.data[o + 2] = v > 0 ? 255 : 0; li.data[o + 3] = Math.min(255, Math.abs(v) * 110);
|
|
106
|
+
}
|
|
107
|
+
lc.putImageData(li, 0, 0); PM_COCK = { w: W, c: lay }; return lay;
|
|
108
|
+
}
|
|
109
|
+
// pigment granulation: fine dark/light speckle that only lives inside floated colour. Baked into one
|
|
110
|
+
// repeating tile per pigment, so filling a drop with its colour already carries the granulation.
|
|
111
|
+
let PM_GRAIN = null; const PM_PAT = {};
|
|
112
|
+
function pmGrain() {
|
|
113
|
+
if (PM_GRAIN) return PM_GRAIN;
|
|
114
|
+
const S = 512; PM_GRAIN = new Float32Array(S * S);
|
|
115
|
+
const nz = H.makeNoise(17), r = H.rng(23), w = (x, y, f) => nz.n2(x * f, y * f * 0.42) ; // tileable via 4-corner blend
|
|
116
|
+
for (let y = 0; y < S; y++) for (let x = 0; x < S; x++) {
|
|
117
|
+
const fx = x / S, fy = y / S, n = (xx, yy) => w(xx, yy, 0.5) * 0.6 + nz.n2(xx * 0.09 + 40, yy * 0.05) * 0.4;
|
|
118
|
+
const v = n(x, y) * (1 - fx) * (1 - fy) + n(x - S, y) * fx * (1 - fy) + n(x, y - S) * (1 - fx) * fy + n(x - S, y - S) * fx * fy;
|
|
119
|
+
PM_GRAIN[y * S + x] = H.clamp(0.9 + (v * 1.6 + (r() - 0.5) * 0.5) * 0.16, 0, 1.05);
|
|
120
|
+
}
|
|
121
|
+
return PM_GRAIN;
|
|
122
|
+
}
|
|
123
|
+
function pigment(ctx, col) {
|
|
124
|
+
if (PM_PAT[col]) return PM_PAT[col];
|
|
125
|
+
const S = 512, g = pmGrain(), c3 = H.hex(col), cv = H.layer(S, S), c = cv.getContext('2d'), img = c.createImageData(S, S), d = img.data;
|
|
126
|
+
for (let i = 0; i < S * S; i++) { const k = g[i]; d[i * 4] = Math.min(255, c3[0] * k); d[i * 4 + 1] = Math.min(255, c3[1] * k); d[i * 4 + 2] = Math.min(255, c3[2] * k); d[i * 4 + 3] = 255; }
|
|
127
|
+
c.putImageData(img, 0, 0); return (PM_PAT[col] = ctx.createPattern(cv, 'repeat'));
|
|
128
|
+
}
|
|
129
|
+
function renderPolys(ctx, W, H_, polys, bath) {
|
|
130
|
+
ctx.fillStyle = bath; ctx.fillRect(0, 0, W, H_);
|
|
131
|
+
// the bare size is never flat: a broad, faint tonal drift across the bath
|
|
132
|
+
const bg = ctx.createLinearGradient(0, 0, W, H_); bg.addColorStop(0, 'rgba(255,250,236,0.35)'); bg.addColorStop(0.55, 'rgba(255,250,236,0)'); bg.addColorStop(1, 'rgba(150,120,70,0.12)');
|
|
133
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
134
|
+
for (const P of polys) {
|
|
135
|
+
if (P.dead) continue; const p = P.pts;
|
|
136
|
+
ctx.beginPath(); ctx.moveTo(p[0], p[1]); for (let i = 2; i < p.length; i += 2) ctx.lineTo(p[i], p[i + 1]); ctx.closePath();
|
|
137
|
+
ctx.fillStyle = pigment(ctx, P.col); ctx.fill();
|
|
138
|
+
if (P.halo) { ctx.save(); ctx.clip(); ctx.strokeStyle = haloOf(P.col); ctx.lineWidth = 6; ctx.stroke(); ctx.restore(); } // gall halo just inside a fresh drop
|
|
139
|
+
ctx.strokeStyle = rimOf(P.col); ctx.lineWidth = 1; ctx.stroke();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
function finish(ctx, W, H_) {
|
|
143
|
+
// soften like the still (floated colour never has a vector edge): a half-pixel resample of itself,
|
|
144
|
+
// visually the hero's blur(0.6px) at a fraction of the cost; then laid paper, fibre and cockle (pmTexture)
|
|
145
|
+
ctx.save(); ctx.globalAlpha = 0.5; ctx.drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height, 0.5, 0.5, W, H_); ctx.restore();
|
|
146
|
+
ctx.save(); ctx.globalCompositeOperation = 'multiply'; ctx.drawImage(pmTexture(W, H_), 0, 0); ctx.restore();
|
|
147
|
+
H.vignette(ctx, W, H_, 0.2, '40,25,10');
|
|
148
|
+
}
|
|
149
|
+
// bookbinder's paper label: cream, double rule, serif type
|
|
150
|
+
function blabel(ctx, text, x, y, o = {}) {
|
|
151
|
+
const size = o.size || 22; ctx.save();
|
|
152
|
+
ctx.font = `${o.italic ? 'italic ' : ''}700 ${size}px "Libre Baskerville"`; try { ctx.letterSpacing = o.ls || '1px'; } catch (e) {}
|
|
153
|
+
const w = ctx.measureText(text).width + size * 1.4, h = size * 2;
|
|
154
|
+
const ox = o.align === 'left' ? x : o.align === 'right' ? x - w : x - w / 2;
|
|
155
|
+
ctx.fillStyle = 'rgba(30,20,10,0.25)'; ctx.fillRect(ox + 3, y - h / 2 + 4, w, h);
|
|
156
|
+
ctx.fillStyle = o.red ? '#f6e6d6' : '#f3ead2'; ctx.fillRect(ox, y - h / 2, w, h);
|
|
157
|
+
ctx.strokeStyle = o.red ? REDI : INKB; ctx.lineWidth = 1.6; ctx.strokeRect(ox + 4, y - h / 2 + 4, w - 8, h - 8); ctx.lineWidth = 0.6; ctx.strokeRect(ox + 7, y - h / 2 + 7, w - 14, h - 14);
|
|
158
|
+
ctx.fillStyle = o.red ? REDI : INKB; ctx.textBaseline = 'middle'; ctx.textAlign = 'center'; ctx.fillText(text, ox + w / 2, y + 1);
|
|
159
|
+
ctx.restore(); return w;
|
|
160
|
+
}
|
|
161
|
+
function dropLabel(ctx, t, t0, text, x, y, o) { if (t < t0) return; const k = H.ease.out(PX(t, t0, t0 + 0.25)); ctx.save(); ctx.globalAlpha = k; blabel(ctx, text, x, y + (1 - k) * -18, o); ctx.restore(); }
|
|
162
|
+
// inked brush arrow (stylus-drawn line with a tapered head)
|
|
163
|
+
function inkArrow(ctx, pts, p, col = INKB, lw = 4) {
|
|
164
|
+
if (p <= 0) return; let L = 0; for (let i = 1; i < pts.length; i++) L += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);
|
|
165
|
+
let left = L * p, end = pts[0], prev = pts[0]; ctx.save(); ctx.strokeStyle = col; ctx.fillStyle = col; ctx.lineWidth = lw; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]);
|
|
166
|
+
for (let i = 1; i < pts.length && left > 0; i++) { const d = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]), q = Math.min(1, left / d); prev = pts[i - 1]; end = [pts[i - 1][0] + (pts[i][0] - pts[i - 1][0]) * q, pts[i - 1][1] + (pts[i][1] - pts[i - 1][1]) * q]; ctx.lineTo(end[0], end[1]); left -= d; }
|
|
167
|
+
ctx.stroke(); const a = Math.atan2(end[1] - prev[1], end[0] - prev[0]), hd = lw * 4.5;
|
|
168
|
+
ctx.beginPath(); ctx.moveTo(end[0] + Math.cos(a) * lw, end[1] + Math.sin(a) * lw); ctx.lineTo(end[0] - Math.cos(a - 0.45) * hd, end[1] - Math.sin(a - 0.45) * hd); ctx.quadraticCurveTo(end[0] - Math.cos(a) * hd * 0.6, end[1] - Math.sin(a) * hd * 0.6, end[0] - Math.cos(a + 0.45) * hd, end[1] - Math.sin(a + 0.45) * hd); ctx.closePath(); ctx.fill();
|
|
169
|
+
ctx.restore();
|
|
170
|
+
}
|
|
171
|
+
const HEAD = 28; // running head: a story label, so >= 28px
|
|
172
|
+
const pmHeader = (ctx) => blabel(ctx, 'How does AI learn?', 24, 42, { size: HEAD, italic: true, align: 'left' });
|
|
173
|
+
const textW = (ctx, text, size, italic) => { ctx.save(); ctx.font = `${italic ? 'italic ' : ''}700 ${size}px "Libre Baskerville"`; try { ctx.letterSpacing = '1px'; } catch (e) {} const w = ctx.measureText(text).width + size * 1.4; ctx.restore(); return w; };
|
|
174
|
+
// shared battal ground (the untrained model): `n` drops, landing from time t0 over dur
|
|
175
|
+
function battal(polys, t, n, t0, dur, ML, halo = false) {
|
|
176
|
+
const r = H.rng(77), cols = [PAL.ochre, PAL.indigo, PAL.sand, PAL.ochre, PAL.indigo, PAL.cream, PAL.sky];
|
|
177
|
+
const landed = [];
|
|
178
|
+
for (let i = 0; i < n; i++) {
|
|
179
|
+
const x = r.range(-40, 1320), y = r.range(-40, 760), rad = r.range(44, 84), kind = r() < 0.5 ? 'cat' : 'dog';
|
|
180
|
+
const col = kind === 'cat' ? (r() < 0.75 ? PAL.ochre : PAL.sand) : (r() < 0.75 ? PAL.indigo : PAL.sky);
|
|
181
|
+
const ti = t0 + dur * Math.pow(i / n, 0.85); if (t < ti) break;
|
|
182
|
+
const g = H.ease.out(PX(t, ti, ti + 0.3));
|
|
183
|
+
applyOp(polys, T.drop({ x, y, r: Math.max(0.5, rad * g) }), ML); polys.push({ pts: circle(x, y, Math.max(0.5, rad * g)), col, halo: halo && t - ti < 0.7 }); // gall halo only while the drop is fresh
|
|
184
|
+
landed.push({ x, y, kind, ti });
|
|
185
|
+
}
|
|
186
|
+
return landed;
|
|
187
|
+
}
|
|
188
|
+
function probe(polys, x, y, ML, g = 1) {
|
|
189
|
+
[PAL.red, PAL.cream, PAL.red, PAL.cream, PAL.red].forEach((col, i) => { const rad = Math.max(0.5, (i === 0 ? 40 : 19) * g); applyOp(polys, T.drop({ x, y, r: rad }), ML); polys.push({ pts: circle(x, y, rad), col, probe: true }); });
|
|
190
|
+
}
|
|
191
|
+
function centroid(polys) { let sx = 0, sy = 0, n = 0; for (const P of polys) if (P.probe) for (let i = 0; i < P.pts.length; i += 2) { sx += P.pts[i]; sy += P.pts[i + 1]; n++; } return [sx / n, sy / n]; }
|
|
192
|
+
function flowX(polys, t) { // horizontal tine through the probe = the model's flow
|
|
193
|
+
return polys;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ---------- scenes ----------
|
|
197
|
+
function mHook(ctx, W, H_, t) {
|
|
198
|
+
const z = 1.08 - 0.06 * H.ease.out(PX(t, 0, 2.4));
|
|
199
|
+
ctx.save(); ctx.translate(W / 2, H_ / 2); ctx.scale(z, z); ctx.translate(-W / 2, -H_ / 2); ctx.drawImage(pmStill(W, H_), 0, 0); ctx.restore();
|
|
200
|
+
const k = H.ease.inOut(PX(t, 1.75, 2.3)), pop = H.ease.back(PX(t, 0.15, 0.5));
|
|
201
|
+
const hs = HEAD / 64, hcx = 24 + textW(ctx, 'How does AI learn?', HEAD, true) / 2;
|
|
202
|
+
if (k < 1) { ctx.save(); ctx.translate(640 + (hcx - 640) * k, 360 + (42 - 360) * k); ctx.scale(pop * (1 - (1 - hs) * k), pop * (1 - (1 - hs) * k)); blabel(ctx, 'How does AI learn?', 0, 0, { size: 64, italic: true }); ctx.restore(); }
|
|
203
|
+
else pmHeader(ctx);
|
|
204
|
+
}
|
|
205
|
+
function mData(ctx, W, H_, t) {
|
|
206
|
+
const polys = [], ML = 4;
|
|
207
|
+
const landed = battal(polys, t, 95, 2.5, 3.1, ML, true);
|
|
208
|
+
renderPolys(ctx, W, H_, polys, PAL.cream); finish(ctx, W, H_);
|
|
209
|
+
// the most recent drops carry their labels
|
|
210
|
+
const placed = [];
|
|
211
|
+
landed.slice(-3).forEach((d, i) => {
|
|
212
|
+
let lx = H.clamp(d.x, 80, 1200), ly = H.clamp(d.y - 80, 200, 470);
|
|
213
|
+
for (let pass = 0; pass < 3; pass++) for (const q of placed) if (Math.abs(q[0] - lx) < 60 + 50 && Math.abs(q[1] - ly) < 60) ly = q[1] - 58; // stack above a crowding label
|
|
214
|
+
placed.push([lx, ly]);
|
|
215
|
+
const a = H.clamp(1 - (t - d.ti - 0.5) / 0.4); if (a > 0) { ctx.save(); ctx.globalAlpha = a; blabel(ctx, d.kind, lx, ly, { size: 28 }); ctx.restore(); }
|
|
216
|
+
});
|
|
217
|
+
dropLabel(ctx, t, 2.9, 'the bath = the model', 960, 110, { size: 30 });
|
|
218
|
+
dropLabel(ctx, t, 3.2, 'ochre = cat · indigo = dog', 1000, 650, { size: 28, italic: true });
|
|
219
|
+
if (t > 2.65) {
|
|
220
|
+
const p = PX(t, 2.7, 5.0), n = p >= 1 ? 1e6 : Math.floor(Math.pow(10, 6 * H.ease.inOut(p))) - 1;
|
|
221
|
+
const w = blabel(ctx, Math.round(n).toLocaleString('en-US'), 40, 600, { size: 64, align: 'left' }); blabel(ctx, 'examples', 40 + w + 14, 610, { size: 30, align: 'left', italic: true });
|
|
222
|
+
}
|
|
223
|
+
pmHeader(ctx);
|
|
224
|
+
}
|
|
225
|
+
function mLoop(ctx, W, H_, t) {
|
|
226
|
+
const polys = [], ML = 4;
|
|
227
|
+
battal(polys, 99, 95, 0, 0.01, ML);
|
|
228
|
+
const adj = PX(t, 8.95, 9.45);
|
|
229
|
+
if (adj > 0) applyOp(polys, T.comb({ vertical: true, S: 90, off: 20, a: 60 * H.ease.inOut(adj), l: 22 }), ML); // adjust: the comb orders the bath
|
|
230
|
+
const pg = PX(t, 6.35, 6.6); if (pg > 0) probe(polys, 640, 330, ML, H.ease.out(pg));
|
|
231
|
+
const f1 = H.ease.inOut(PX(t, 6.8, 7.3)), back = H.ease.inOut(PX(t, 8.35, 8.85)), f2 = H.ease.inOut(PX(t, 9.45, 9.85));
|
|
232
|
+
if (f1 > 0) applyOp(polys, T.tine({ x: 0, y: 330, mx: 1, my: 0, a: 300 * f1, l: 40 }), ML); // guess: flow drags probe toward DOG
|
|
233
|
+
if (back > 0) applyOp(polys, T.tine({ x: 0, y: 330, mx: -1, my: 0, a: 300 * back, l: 40 }), ML); // error: counter-stroke drags it back
|
|
234
|
+
if (f2 > 0) applyOp(polys, T.tine({ x: 0, y: 330, mx: -1, my: 0, a: 330 * f2, l: 40 }), ML); // guess again: now flows to CAT
|
|
235
|
+
renderPolys(ctx, W, H_, polys, PAL.cream); finish(ctx, W, H_);
|
|
236
|
+
const right = t >= 9.85, [px, py] = pg > 0 ? centroid(polys) : [640, 330];
|
|
237
|
+
blabel(ctx, '◀ CAT', 30, 330, { size: 36, align: 'left', red: right });
|
|
238
|
+
blabel(ctx, 'DOG ▶', 1250, 330, { size: 36, align: 'right', red: t >= 7.3 && t < 8.85 });
|
|
239
|
+
dropLabel(ctx, t, 6.35, t >= 8.0 ? 'answer: cat' : 'new picture', 640, 450, { size: 28, italic: true, red: t >= 8.0 });
|
|
240
|
+
if (t >= 6.8 && t < 7.3) inkArrow(ctx, [[600, 250], [900, 250]], PX(t, 6.8, 7.2));
|
|
241
|
+
if (t >= 7.3 && !right) dropLabel(ctx, t, 7.3, 'guess: dog?', 1010, 215, { size: 34 });
|
|
242
|
+
if (t >= 7.85 && !right) { const k = H.ease.out(PX(t, 7.85, 8.05)); ctx.save(); ctx.strokeStyle = REDI; ctx.lineWidth = 6; ctx.lineCap = 'round'; ctx.beginPath(); ctx.ellipse(1150, 330, 140, 70, -0.05, -1.4, -1.4 + 6.4 * k); ctx.stroke(); ctx.restore(); dropLabel(ctx, t, 7.95, 'check: wrong ✗', 1060, 445, { size: 30, red: true }); }
|
|
243
|
+
if (t >= 8.35 && t < 9.45) { inkArrow(ctx, [[960, 395], [600, 395]], PX(t, 8.35, 8.8), REDI, 6); dropLabel(ctx, t, 8.4, 'error: pull back', 700, 520, { size: 28, red: true }); }
|
|
244
|
+
if (t >= 8.95 && t < 10.5) dropLabel(ctx, t, 8.95, 'adjust: comb the bath', 640, 120, { size: 32, red: true });
|
|
245
|
+
if (right) dropLabel(ctx, t, 9.85, 'cat ✓', 300, 220, { size: 44, red: true });
|
|
246
|
+
const words = ['guess', 'check', 'adjust'], act = t < 7.3 ? -1 : t < 7.85 ? 0 : t < 8.35 ? 1 : t < 9.45 ? 2 : Math.floor((t - 9.45) / 0.16) % 3;
|
|
247
|
+
let x = 120; words.forEach((w, i) => { x += blabel(ctx, w, x, 630, { size: 32, align: 'left', red: i === act }) + 12; if (i < 2) { inkArrow(ctx, [[x, 630], [x + 52, 630]], 1, INKB, 4.5); x += 68; } });
|
|
248
|
+
blabel(ctx, '↻ ×1,000,000', x + 6, 630, { size: 28, italic: true, align: 'left' });
|
|
249
|
+
pmHeader(ctx);
|
|
250
|
+
}
|
|
251
|
+
function mProg(ctx, W, H_, t) {
|
|
252
|
+
const polys = [], ML = 4;
|
|
253
|
+
battal(polys, 99, 95, 0, 0.01, ML);
|
|
254
|
+
const p = H.ease.inOut(PX(t, 10.85, 12.3)), rounds = Math.floor(p * 5);
|
|
255
|
+
// each training round is one more, finer comb pass: chaos combs into order
|
|
256
|
+
const combs = [{ vertical: true, S: 90, off: 20, a: 60, l: 22 }, { vertical: false, S: 120, off: 40, a: 28, l: 30 }, { vertical: true, S: 45, off: 10, a: 22, l: 12 }];
|
|
257
|
+
for (let k = 0; k < Math.min(3, rounds); k++) applyOp(polys, T.comb(combs[k]), ML);
|
|
258
|
+
const sub = H.clamp(p * 5 - rounds); if (rounds < 3) applyOp(polys, T.comb({ ...combs[rounds], a: combs[rounds].a * H.ease.inOut(sub) }), ML);
|
|
259
|
+
renderPolys(ctx, W, H_, polys, PAL.cream); finish(ctx, W, H_);
|
|
260
|
+
// card with accuracy + ink chart
|
|
261
|
+
ctx.save(); ctx.fillStyle = 'rgba(30,20,10,0.25)'; ctx.fillRect(84, 124, 520, 470); ctx.fillStyle = '#f3ead2'; ctx.fillRect(80, 120, 520, 470);
|
|
262
|
+
ctx.strokeStyle = INKB; ctx.lineWidth = 1.6; ctx.strokeRect(86, 126, 508, 458); ctx.lineWidth = 0.6; ctx.strokeRect(90, 130, 500, 450); ctx.restore();
|
|
263
|
+
ctx.save(); ctx.font = 'italic 700 36px "Libre Baskerville"'; ctx.fillStyle = INKB; ctx.fillText('Accuracy', 120, 188);
|
|
264
|
+
const v = Math.round(61 + 36 * p), punch = t >= 12.3 ? 1 + 0.12 * Math.exp(-(t - 12.3) * 9) : 1;
|
|
265
|
+
ctx.translate(120, 320); ctx.scale(punch, punch); ctx.font = '700 120px "Libre Baskerville"'; ctx.fillText(v + '%', 0, 0); ctx.restore();
|
|
266
|
+
if (t >= 12.3) { const k = H.ease.out(PX(t, 12.3, 12.55)); ctx.save(); ctx.strokeStyle = REDI; ctx.lineWidth = 6; ctx.lineCap = 'round'; ctx.beginPath(); ctx.ellipse(300, 278, 205, 85, -0.04, -2.6, -2.6 + 6.5 * k); ctx.stroke(); ctx.restore(); }
|
|
267
|
+
const x0 = 130, y0 = 545, cw = 420, ch = 160, n = 6, pts = [];
|
|
268
|
+
for (let i = 0; i < n; i++) { const acc = 61 + 36 * (1 - Math.exp(-i / 1.6)) / (1 - Math.exp(-(n - 1) / 1.6)); pts.push([x0 + (i / (n - 1)) * cw, y0 - ((acc - 55) / 45) * ch]); }
|
|
269
|
+
ctx.save(); ctx.strokeStyle = INKB; ctx.lineWidth = 1; ctx.beginPath(); ctx.moveTo(x0, y0 - ch - 10); ctx.lineTo(x0, y0); ctx.lineTo(x0 + cw + 10, y0); ctx.stroke(); ctx.restore();
|
|
270
|
+
inkArrow(ctx, pts, PX(t, 10.9, 12.3), REDI, 3.5);
|
|
271
|
+
ctx.save(); ctx.font = 'italic 400 28px "Libre Baskerville"'; ctx.fillStyle = INKB; ctx.fillText('training rounds →', x0, y0 + 32); ctx.restore();
|
|
272
|
+
dropLabel(ctx, t, 10.75, `comb pass ${Math.min(5, rounds + 1)}: chaos → order`, 940, 640, { size: 28, italic: true });
|
|
273
|
+
pmHeader(ctx);
|
|
274
|
+
}
|
|
275
|
+
function mTake(ctx, W, H_, t) {
|
|
276
|
+
ctx.drawImage(pmStill(W, H_), 0, 0);
|
|
277
|
+
const k = H.ease.back(PX(t, 13.25, 13.6));
|
|
278
|
+
ctx.save(); ctx.translate(640, 360); ctx.scale(k, k);
|
|
279
|
+
blabel(ctx, 'More examples', 0, -56, { size: 56, italic: true });
|
|
280
|
+
blabel(ctx, '→ better guesses', 0, 60, { size: 56, italic: true, red: true });
|
|
281
|
+
ctx.restore();
|
|
282
|
+
}
|
|
283
|
+
function explainPM(ctx, W, H_, t) {
|
|
284
|
+
const seq = [[0, mHook], [2.4, mData], [6.0, mLoop], [10.5, mProg], [13.0, mTake]];
|
|
285
|
+
let i = 0; for (let k = 0; k < seq.length; k++) if (t >= seq[k][0]) i = k;
|
|
286
|
+
const t0 = seq[i][0], D = 0.55;
|
|
287
|
+
if (i > 0 && t < t0 + D) {
|
|
288
|
+
// the printed sheet is lifted off the bath, peeling from the left edge
|
|
289
|
+
seq[i][1](ctx, W, H_, t);
|
|
290
|
+
const A = H.layer(W, H_); seq[i - 1][1](A.getContext('2d'), W, H_, t0 - 0.01);
|
|
291
|
+
const lx = -80 + (W + 200) * H.ease.inOut(PX(t, t0, t0 + D)), curl = 90;
|
|
292
|
+
ctx.save(); ctx.fillStyle = 'rgba(20,10,0,0.35)'; ctx.fillRect(lx, 0, 40, H_); ctx.restore(); // shadow of the lifted sheet
|
|
293
|
+
ctx.drawImage(A, lx + curl, 0, W, H_, lx + curl, 0, W, H_);
|
|
294
|
+
const g = ctx.createLinearGradient(lx, 0, lx + curl, 0); g.addColorStop(0, '#d9ccad'); g.addColorStop(0.55, '#f5ecd6'); g.addColorStop(1, '#bfae8a');
|
|
295
|
+
ctx.fillStyle = g; ctx.fillRect(lx, 0, curl, H_); // the paper's back, curling up
|
|
296
|
+
} else seq[i][1](ctx, W, H_, t);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
registerStyle({
|
|
300
|
+
id: 'paper-marbling',
|
|
301
|
+
name: 'Ebru Paper Marbling',
|
|
302
|
+
category: 'Generative & Type',
|
|
303
|
+
description: 'Turkish ebru on laid paper: a battal stone ground of floated pigments raked into gel-git chevrons and combed, then suminagashi ring-drops pulled by a stylus into tulips — built with the exact drop and tine transforms.',
|
|
304
|
+
motion: 'Colour drops rain onto the size and bloom, shoving earlier drops into veins; the rake drags the stone into gel-git flames; ring after ring is dropped at one point; then the stylus pulls the rings into a tulip, and a wet sheen glides over the finished bath.',
|
|
305
|
+
fonts: ['700 22px "Libre Baskerville"', 'italic 700 22px "Libre Baskerville"', 'italic 400 15px "Libre Baskerville"'],
|
|
306
|
+
duration: 8,
|
|
307
|
+
frameT: 7.9,
|
|
308
|
+
readsPixels: true,
|
|
309
|
+
explainerDuration: 14.5,
|
|
310
|
+
explainerNote: 'The bath is the model: examples land as colour drops, a new picture is a red probe drop that the flow drags toward its guess, the error is a stylus counter-stroke and the adjustment a comb pass that puts the pattern in order; scenes change by lifting the printed sheet off the bath, labels are bookbinder paper labels and arrows are inked stylus strokes.',
|
|
311
|
+
explainer(ctx, W, H_, t) { explainPM(ctx, W, H_, t); },
|
|
312
|
+
// Sound: the marbling bath. Colour drops land with small low water plops (pitch from drop size, pan from position,
|
|
313
|
+
// throttled), the stylus and its counter-stroke are soft watery drags across the size, comb passes are a fuller
|
|
314
|
+
// swish with the tines riffling through, scene changes are the wet sheet peeled off the bath, and the bookbinder
|
|
315
|
+
// labels set down with a soft paper tap. A quiet pad sits under it. Timings mirror explainPM / mHook..mTake above.
|
|
316
|
+
explainerSound(k) {
|
|
317
|
+
const D = 14.5, E = H.ease;
|
|
318
|
+
const pan = (px) => H.clamp((px / 1280) * 2 - 1, -1, 1) * 0.7;
|
|
319
|
+
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; };
|
|
320
|
+
// a colour drop meeting the size: short low bubble plop (stays under ~1 kHz) with a soft body
|
|
321
|
+
const plop = (t, f, g, p) => { k.tone(t, { f: [[0, f], [0.045, f * 2.1]], dur: 0.075, gain: 0.13 * g, a: 0.002, pan: p }); k.tone(t, { f: [[0, 140], [0.06, 90]], dur: 0.08, gain: 0.1 * g, pan: p }); k.noise(t, { dur: 0.03, filter: 'lowpass', f: 700, gain: 0.08 * g, a: 0.001, pan: p }); };
|
|
322
|
+
// label set down on the bench
|
|
323
|
+
const label = (t0, p, g = 1) => { const t = t0 + 0.06; k.withMinor(() => { k.noise(t, { dur: 0.05, filter: 'bandpass', f: 1000, q: 0.7, gain: 0.12 * g, a: 0.003, pan: p }); k.tone(t, { f: 150, dur: 0.05, gain: 0.07 * g, pan: p }); }); };
|
|
324
|
+
// stylus dragged through the bath from pan a to b over dur
|
|
325
|
+
const drag = (t, dur, a, b, g = 1) => {
|
|
326
|
+
const steps = 6; for (let i = 0; i < steps; i++) { const u = i / steps; k.noise(t + u * dur, { dur: dur / steps + 0.08, color: 'pink', filter: 'bandpass', f: 380 + 380 * Math.sin(Math.PI * (u + 0.08)), q: 1.6, gain: 0.13 * g * Math.sin(Math.PI * (u + 0.1)), a: 0.04, pan: a + (b - a) * u }); }
|
|
327
|
+
k.tone(t + dur * 0.3, { f: [[0, 220], [0.05, 420]], dur: 0.07, gain: 0.05 * g, pan: a + (b - a) * 0.3, minor: true });
|
|
328
|
+
k.tone(t + dur * 0.7, { f: [[0, 260], [0.05, 500]], dur: 0.07, gain: 0.04 * g, pan: a + (b - a) * 0.7, minor: true });
|
|
329
|
+
};
|
|
330
|
+
// comb pass: a swish with the tines riffling through the size
|
|
331
|
+
const comb = (t, dur, g = 1) => {
|
|
332
|
+
k.noise(t, { dur: dur + 0.1, color: 'pink', filter: 'lowpass', f: [[0, 500], [dur * 0.5, 1100], [dur + 0.1, 400]], gain: 0.13 * g, a: dur * 0.35 });
|
|
333
|
+
const r = H.rng(Math.round(t * 100)); k.withMinor(() => { for (let x = 0.03; x < dur; x += 0.045 + r() * 0.03) k.noise(t + x, { dur: 0.03, filter: 'bandpass', f: 600 + r() * 500, q: 2, gain: 0.07 * g * Math.sin(Math.PI * x / dur), a: 0.008, pan: (x / dur - 0.5) * 0.8 }); });
|
|
334
|
+
};
|
|
335
|
+
// wet sheet peeled off the bath, left edge first (D = 0.55, inOut)
|
|
336
|
+
const peel = (t0) => {
|
|
337
|
+
const r = H.rng(Math.round(t0 * 10));
|
|
338
|
+
k.noise(t0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.3, 1200], [0.6, 600]], q: 0.9, gain: 0.12, a: 0.25 });
|
|
339
|
+
k.withMinor(() => { for (let x = 0.04; x < 0.55; x += 0.025 + r() * 0.03) { const u = x / 0.55; k.noise(t0 + x, { dur: 0.025, filter: 'bandpass', f: 700 + r() * 800, q: 1.2, gain: 0.1 * Math.sin(Math.PI * u) + 0.02, a: 0.003, pan: -0.7 + 1.4 * E.inOut(u) }); } });
|
|
340
|
+
k.noise(t0 + 0.5, { dur: 0.12, color: 'brown', filter: 'lowpass', f: 300, gain: 0.12, a: 0.01 }); // last of it lets go of the size
|
|
341
|
+
};
|
|
342
|
+
const busy = []; // drop times: counter clicks step around them so neither masks the other
|
|
343
|
+
const counter = (t0, t1, ease, steps, rate, p) => { let last = -1, prev = 0;
|
|
344
|
+
for (let t = t0; t <= t1; t += 0.002) { const s = Math.floor(ease(H.clamp((t - t0) / (t1 - t0))) * steps); if (s !== prev && t - last >= 1 / rate && !busy.some((b) => t > b - 0.02 && t < b + 0.07)) { k.play('tock', t, { f: 700, gain: 0.38, pan: p, minor: true }); last = t; } prev = s; } };
|
|
345
|
+
|
|
346
|
+
k.bed('room', 0, D, { gain: 0.3 });
|
|
347
|
+
k.music('pad', 0, D, { bpm: 64, gain: 0.35, chords: [['D3', 'A3', 'C4', 'F4'], ['G2', 'D3', 'B3', 'F4'], ['C3', 'G3', 'C4', 'E4'], ['A2', 'E3', 'A3', 'C4']] });
|
|
348
|
+
|
|
349
|
+
// ---- hook: title label pops (back 0.15-0.5), slides to the corner (1.75-2.3)
|
|
350
|
+
label(when(0.15, 0.5, E.back, 1) - 0.06, 0, 1.6);
|
|
351
|
+
k.noise(1.75, { dur: 0.55, color: 'pink', filter: 'lowpass', f: [[0, 600], [0.55, 280]], gain: 0.028, a: 0.2, minor: true });
|
|
352
|
+
|
|
353
|
+
// ---- data: peel, then 95 drops at ti = 2.5 + 3.1 (i/95)^0.85 (same seeded draws as battal), labels, counter
|
|
354
|
+
peel(2.4);
|
|
355
|
+
{ const r = H.rng(77); let last = -1;
|
|
356
|
+
for (let i = 0; i < 95; i++) {
|
|
357
|
+
const x = r.range(-40, 1320); r.range(-40, 760); const rad = r.range(44, 84); r(); r();
|
|
358
|
+
const ti = 2.5 + 3.1 * Math.pow(i / 95, 0.85);
|
|
359
|
+
if (ti - last < (i < 4 ? 0.1 : 0.16)) continue; last = ti; busy.push(ti);
|
|
360
|
+
k.withMinor(() => plop(ti, 520 - rad * 3, i < 4 ? 1.2 : 0.8, pan(x)));
|
|
361
|
+
} }
|
|
362
|
+
label(2.9, 0.5); label(3.2, 0.55);
|
|
363
|
+
counter(2.72, 4.98, E.inOut, 30, 8, -0.6);
|
|
364
|
+
label(4.94, -0.6, 1.5); k.tone(5.0, { f: 'D3', dur: 0.7, gain: 0.1, a: 0.01, verb: 0.3, pan: -0.5 }); // 1,000,000 lands
|
|
365
|
+
|
|
366
|
+
// ---- loop: peel, probe drop, guess drag, wrong, counter-stroke, comb, guess again, cat ✓
|
|
367
|
+
peel(6.0);
|
|
368
|
+
plop(6.35, 300, 1.4, 0); plop(6.42, 380, 0.7, 0); plop(6.5, 420, 0.5, 0); // the red ring drops
|
|
369
|
+
label(6.35, 0);
|
|
370
|
+
drag(6.8, 0.5, -0.1, 0.6); // the flow pulls the probe toward DOG
|
|
371
|
+
label(7.3, 0.5);
|
|
372
|
+
k.play('stroke', 7.85, { material: 'brush', dur: 0.2, gain: 1.4, pan: pan(1150), minor: true }); // red ring round DOG
|
|
373
|
+
label(7.95, 0.5, 1.3);
|
|
374
|
+
k.play('tock', 7.97, { f: 300, gain: 0.7, pan: pan(1100) }); k.play('tock', 8.13, { f: 225, gain: 0.7, pan: pan(1100) }); // check: wrong
|
|
375
|
+
k.play('thud', 7.97, { f: 100, gain: 0.45, pan: pan(1100) });
|
|
376
|
+
drag(8.35, 0.5, 0.6, -0.1, 1.1); // counter-stroke pulls it back
|
|
377
|
+
label(8.4, 0.1);
|
|
378
|
+
label(8.95, 0);
|
|
379
|
+
comb(8.95, 0.5, 0.75); // adjust: the comb orders the bath
|
|
380
|
+
drag(9.45, 0.4, 0, -0.6); // now the flow pulls it to CAT
|
|
381
|
+
label(9.85, -0.4, 1.3);
|
|
382
|
+
['G4', 'D5'].forEach((n, i) => k.play('ding', 9.88 + i * 0.09, { f: n, gain: 0.5, pan: -0.4 })); // cat ✓
|
|
383
|
+
|
|
384
|
+
// ---- progress: peel, comb passes (rounds = floor(5 inOut(p))), red ink chart, counter, 97%
|
|
385
|
+
peel(10.5);
|
|
386
|
+
label(10.75, 0.4);
|
|
387
|
+
for (let r = 0; r < 3; r++) { const a = when(10.85, 12.3, E.inOut, r / 5 + 1e-4), b = when(10.85, 12.3, E.inOut, (r + 1) / 5); comb(a, b - a, 0.6 - r * 0.12); busy.push(a + (b - a) / 2); }
|
|
388
|
+
k.play('stroke', 10.9, { material: 'brush', dur: 1.4, gain: 0.35, pan: -0.4, minor: true });
|
|
389
|
+
counter(10.86, 12.28, E.inOut, 36, 8, -0.5);
|
|
390
|
+
k.play('impact', 12.3, { gain: 1.0, pan: -0.4 });
|
|
391
|
+
plop(12.3, 180, 2.2, -0.3);
|
|
392
|
+
['D3', 'A3', 'F#4', 'A4'].forEach((n, i) => k.tone(12.32 + i * 0.03, { f: n, type: 'triangle', dur: 1.3, gain: 0.07, a: 0.01, verb: 0.35, filter: 'lowpass', f2: 1800, pan: (i - 1.5) * 0.25 })); // the 97% bloom
|
|
393
|
+
k.play('stroke', 12.32, { material: 'brush', dur: 0.24, gain: 0.8, pan: -0.4, minor: true }); // red ring
|
|
394
|
+
|
|
395
|
+
// ---- takeaway: peel back to the finished sheet, labels pop, resolve
|
|
396
|
+
peel(13.0);
|
|
397
|
+
const tk = when(13.25, 13.6, E.back, 1);
|
|
398
|
+
label(tk - 0.06, 0, 1.6);
|
|
399
|
+
['G2', 'D3', 'B3', 'D4', 'G4'].forEach((n, i) => k.tone(tk + 0.05 + i * 0.05, { f: n, type: 'triangle', dur: 1.2, gain: 0.07, a: 0.01, verb: 0.35, filter: 'lowpass', f2: 1800, pan: (i - 2) * 0.2 }));
|
|
400
|
+
},
|
|
401
|
+
draw(ctx, W, H_, t) {
|
|
402
|
+
const r = H.rng(2024);
|
|
403
|
+
const polys = [];
|
|
404
|
+
const ML = 3.2;
|
|
405
|
+
const drop = (x, y, rad, col) => {
|
|
406
|
+
applyOp(polys, T.drop({ x, y, r: rad }), ML);
|
|
407
|
+
polys.push({ pts: circle(x, y, rad), col });
|
|
408
|
+
};
|
|
409
|
+
// a drop lands at time ti and blooms outward (radius eases to full) — later drops wait
|
|
410
|
+
const land = (ti, x, y, rad, col, dur = 0.3) => {
|
|
411
|
+
if (t < ti) return;
|
|
412
|
+
const g = H.ease.out(H.clamp((t - ti) / dur));
|
|
413
|
+
drop(x, y, Math.max(0.5, rad * g), col);
|
|
414
|
+
};
|
|
415
|
+
const P = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
416
|
+
const op = (k, f) => { if (k > 1e-4) applyOp(polys, f, ML); };
|
|
417
|
+
// 1) battal ground: rows of drops sprinkled from the brush
|
|
418
|
+
const ground = ['indigo', 'sand', 'sky', 'indigo', 'cream', 'teal', 'indigo', 'slate', 'sky', 'indigo', 'sand', 'ochre'];
|
|
419
|
+
for (let i = 0; i < 270; i++) {
|
|
420
|
+
land(0.05 + 2.1 * Math.pow(i / 270, 0.9), r.range(-80, W + 80), r.range(-80, H_ + 80), r.range(28, 64), PAL[ground[i % ground.length]]);
|
|
421
|
+
}
|
|
422
|
+
// fine 'eyes' sprinkled into the stone before raking (become streaks & veins)
|
|
423
|
+
for (let i = 0; i < 160; i++) land(2.15 + 0.4 * (i / 160), r.range(-40, W + 40), r.range(-40, H_ + 40), r.range(3, 11), r.chance(0.55) ? PAL.cream : r.chance(0.5) ? PAL.ochre : PAL.ink, 0.15);
|
|
424
|
+
// 2) gel-git: coarse alternating rake top<->bottom, then a wave, then the fine comb (tarak)
|
|
425
|
+
const ph = 0.36;
|
|
426
|
+
const rake = P(2.6, 3.7), wv = P(3.2, 4.0);
|
|
427
|
+
op(rake, T.comb({ vertical: true, S: 80, off: 20 + ph * 30, a: 70 * rake, l: 22 }));
|
|
428
|
+
op(wv, T.wave({ vertical: false, A: 18 * wv, L: 260, ph: 0.8 + ph }));
|
|
429
|
+
|
|
430
|
+
// 3) sprinkle of small dark drops (opens tiny eyes in the ground)
|
|
431
|
+
for (let i = 0; i < 40; i++) land(4.0 + 0.35 * (i / 40), r.range(0, W), r.range(0, H_), r.range(4, 9), r.chance(0.5) ? PAL.cream : PAL.ink, 0.12);
|
|
432
|
+
// 4) focal tulip: concentric suminagashi rings dropped at one point
|
|
433
|
+
const fx = 640, fy = 250;
|
|
434
|
+
const rings = ['ochre', 'red', 'cream', 'red', 'cream', 'ochre', 'cream', 'red', 'ink', 'cream', 'red', 'cream', 'teal', 'cream', 'red', 'ochre', 'cream', 'red', 'cream', 'ink', 'cream'];
|
|
435
|
+
rings.forEach((c, i) => land(4.35 + i * 0.045, fx, fy, i === 0 ? 34 : 24, PAL[c], 0.12));
|
|
436
|
+
// side buds
|
|
437
|
+
[[330, 420], [950, 420]].forEach(([bx, by], k) => {
|
|
438
|
+
['red', 'cream', 'ochre', 'cream', 'red', 'cream', 'ink', 'cream', 'red'].forEach((c, i) => land(4.6 + k * 0.08 + i * 0.05, bx, by, i === 0 ? 20 : 15, PAL[c], 0.12));
|
|
439
|
+
});
|
|
440
|
+
// stem + leaves: a column of small green drops and two leaf drops
|
|
441
|
+
for (let y = 470; y < 700; y += 24) land(5.2 + (y - 470) / 230 * 0.25, fx + Math.sin(y * 0.02) * 4, y, 9, PAL.green, 0.12);
|
|
442
|
+
land(5.45, fx - 62, 560, 26, PAL.green, 0.2); land(5.5, fx + 62, 600, 24, PAL.green, 0.2);
|
|
443
|
+
// 5) stylus: pull through each flower to form the tulip; stem and leaves pulled too
|
|
444
|
+
const p0 = P(5.6, 6.4), p1 = P(5.9, 6.5), p2 = P(6.05, 6.6), p3 = P(6.3, 6.8), swl = P(6.4, 7.0);
|
|
445
|
+
op(p0, T.tine({ x: fx, y: 0, mx: 0, my: 1, a: 260 * p0, l: 16 }));
|
|
446
|
+
op(p1, T.tine({ x: 330, y: 0, mx: 0, my: -1, a: 120 * p1, l: 10 }));
|
|
447
|
+
op(p2, T.tine({ x: 950, y: 0, mx: 0, my: -1, a: 120 * p2, l: 10 }));
|
|
448
|
+
op(p3, T.tine({ x: fx - 62, y: 560, mx: -0.6, my: -0.8, a: 70 * p3, l: 12 }));
|
|
449
|
+
op(p3, T.tine({ x: fx + 62, y: 600, mx: 0.6, my: -0.8, a: 70 * p3, l: 12 }));
|
|
450
|
+
// a slight lateral swirl as the sheet is laid (keeps it from looking rigid)
|
|
451
|
+
op(swl, T.wave({ vertical: true, A: 5 * swl, L: 420, ph: 1.7 }));
|
|
452
|
+
|
|
453
|
+
// ---------- render ----------
|
|
454
|
+
ctx.fillStyle = PAL.sand; ctx.fillRect(0, 0, W, H_);
|
|
455
|
+
for (const P of polys) {
|
|
456
|
+
if (P.dead) continue;
|
|
457
|
+
const p = P.pts;
|
|
458
|
+
ctx.beginPath(); ctx.moveTo(p[0], p[1]);
|
|
459
|
+
for (let i = 2; i < p.length; i += 2) ctx.lineTo(p[i], p[i + 1]);
|
|
460
|
+
ctx.closePath();
|
|
461
|
+
ctx.fillStyle = P.col; ctx.fill();
|
|
462
|
+
// pigment concentrates at the rim of each floated drop: its own colour, darker
|
|
463
|
+
ctx.strokeStyle = rimOf(P.col); ctx.lineWidth = 1; ctx.stroke();
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
ctx.save(); ctx.filter = 'blur(0.6px)'; ctx.drawImage(ctx.canvas, 0, 0, ctx.canvas.width, ctx.canvas.height, 0, 0, W, H_); ctx.restore();
|
|
467
|
+
// ---------- flow-dragged pigment speckle + paper ----------
|
|
468
|
+
// The rake is a vertical comb, so its inverse shift depends on x only: one combShift per column, and the
|
|
469
|
+
// speckle noise is looked up from a cached pre-comb field (same formula as sampling it per pixel).
|
|
470
|
+
const SPM = 260;
|
|
471
|
+
if (!PM_SPK || PM_SPK.W !== W) {
|
|
472
|
+
const nz = H.makeNoise(3), nz2 = H.makeNoise(8), rr = H.rng(9), RH = H_ + SPM * 2;
|
|
473
|
+
const sp = new Float32Array(W * RH), lr = new Float32Array(W * H_);
|
|
474
|
+
for (let y = 0; y < RH; y++) for (let x = 0; x < W; x++) { const py = y - SPM; sp[y * W + x] = (nz.n2(x * 0.08, py * 0.08) * 0.55 + nz2.n2(x * 0.35, py * 0.35) * 0.45) * 0.12; }
|
|
475
|
+
for (let y = 0; y < H_; y++) for (let x = 0; x < W; x++) lr[y * W + x] = (x % 3 === 0 ? -0.025 : 0) + ((x + 9) % 64 < 1.5 ? -0.03 : 0) + (rr() - 0.5) * 0.04; // laid lines, fibre tooth
|
|
476
|
+
PM_SPK = { W, sp, lr };
|
|
477
|
+
}
|
|
478
|
+
const img = ctx.getImageData(0, 0, W, H_), d = img.data;
|
|
479
|
+
const combA = { vertical: true, S: 80, off: 20 + ph * 30, a: 70 * rake, l: 22 };
|
|
480
|
+
const shiftX = new Int32Array(W);
|
|
481
|
+
for (let x = 0; x < W; x++) shiftX[x] = Math.max(-SPM, Math.min(SPM, Math.round(-combShift(x, combA))));
|
|
482
|
+
const { sp, lr } = PM_SPK;
|
|
483
|
+
for (let y = 0; y < H_; y++) {
|
|
484
|
+
for (let x = 0; x < W; x++) {
|
|
485
|
+
const k = 1 + sp[(y + shiftX[x] + SPM) * W + x] + lr[y * W + x];
|
|
486
|
+
const o = (y * W + x) * 4;
|
|
487
|
+
d[o] *= k; d[o + 1] *= k; d[o + 2] *= k;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
ctx.putImageData(img, 0, 0);
|
|
491
|
+
// cockle: broad soft light/dark undulation from the wetted sheet drying
|
|
492
|
+
ctx.save(); ctx.globalCompositeOperation = 'soft-light'; ctx.imageSmoothingQuality = 'high'; ctx.drawImage(pmCockle(W, H_), 0, 0, W, H_); ctx.restore();
|
|
493
|
+
// wet sheen gliding across the bath once the pattern is finished
|
|
494
|
+
const gs = H.clamp((t - 6.7) / 1.3);
|
|
495
|
+
if (gs > 0) {
|
|
496
|
+
const gx = -400 + 2100 * H.ease.inOut(gs);
|
|
497
|
+
const lg = ctx.createLinearGradient(gx - 260, 0, gx + 260, 200);
|
|
498
|
+
lg.addColorStop(0, 'rgba(255,250,235,0)'); lg.addColorStop(0.5, 'rgba(255,250,235,0.13)'); lg.addColorStop(1, 'rgba(255,250,235,0)');
|
|
499
|
+
ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_); ctx.restore();
|
|
500
|
+
}
|
|
501
|
+
H.vignette(ctx, W, H_, 0.2, '40,25,10');
|
|
502
|
+
},
|
|
503
|
+
});
|
|
504
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": 54,
|
|
3
|
+
"name": "Ebru Paper Marbling",
|
|
4
|
+
"nameZh": "土耳其水拓画",
|
|
5
|
+
"family": "Generative & Type",
|
|
6
|
+
"familyZh": "生成艺术与字体",
|
|
7
|
+
"description": "Turkish ebru on laid paper: a battal stone ground of floated pigments raked into gel-git chevrons and combed, then suminagashi ring-drops pulled by a stylus into tulips — built with the exact drop and tine transforms.",
|
|
8
|
+
"motionNotes": "Motion: Colour drops rain onto the size and bloom, shoving earlier drops into veins; the rake drags the stone into gel-git flames; ring after ring is dropped at one point; then the stylus pulls the rings into a tulip, and a wet sheen glides over the finished bath.",
|
|
9
|
+
"explainerNotes": "Explainer: The bath is the model: examples land as colour drops, a new picture is a red probe drop that the flow drags toward its guess, the error is a stylus counter-stroke and the adjustment a comb pass that puts the pattern in order; scenes change by lifting the printed sheet off the bath, labels are bookbinder paper labels and arrows are inked stylus strokes.",
|
|
10
|
+
"code": "code.js",
|
|
11
|
+
"registeredAs": "paper-marbling",
|
|
12
|
+
"source": "https://www.futurepedia.io/resources/ai-style-atlas/view"
|
|
13
|
+
}
|