@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,724 @@
|
|
|
1
|
+
{
|
|
2
|
+
const fieldNoise = (w, h, seed, cell, oct, sx = 1, sy = 1) => {
|
|
3
|
+
const nz = H.makeNoise(seed), gw = Math.ceil(w / cell) + 2, gh = Math.ceil(h / cell) + 2, g = new Float32Array(gw * gh);
|
|
4
|
+
for (let j = 0; j < gh; j++) for (let i = 0; i < gw; i++) g[j * gw + i] = nz.fbm2(i * 0.35 * sx, j * 0.35 * sy, oct);
|
|
5
|
+
const out = new Float32Array(w * h);
|
|
6
|
+
for (let y = 0; y < h; y++) {
|
|
7
|
+
const fy = y / cell, j = fy | 0, ty = fy - j;
|
|
8
|
+
for (let x = 0; x < w; x++) {
|
|
9
|
+
const fx = x / cell, i = fx | 0, tx = fx - i, k = j * gw + i;
|
|
10
|
+
const a = g[k] + (g[k + 1] - g[k]) * tx, b = g[k + gw] + (g[k + gw + 1] - g[k + gw]) * tx;
|
|
11
|
+
out[y * w + x] = a + (b - a) * ty;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
return out;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const C = {
|
|
18
|
+
paper: '#ecdfc2', key: '#1b2236', prussD: '#16304f', pruss: '#24507e', mid: '#4d7ca6', light: '#93b4cc', pale: '#c9d6d6',
|
|
19
|
+
foam: '#f1e8d2', sky: '#e3d2ab', fuji: '#6d84a0', hull: '#7d3f2c', ochre: '#d2a75c', grey: '#9aa8b0', red: '#c4432d', green: '#7a9a6a',
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Catmull-Rom sampling of a point list -> dense polyline
|
|
23
|
+
const sample = (pts, per = 14) => {
|
|
24
|
+
const out = [];
|
|
25
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
26
|
+
const p0 = pts[Math.max(0, i - 1)], p1 = pts[i], p2 = pts[i + 1], p3 = pts[Math.min(pts.length - 1, i + 2)];
|
|
27
|
+
for (let k = 0; k < per; k++) {
|
|
28
|
+
const t = k / per, t2 = t * t, t3 = t2 * t;
|
|
29
|
+
const f = (a, b, c, d) => 0.5 * (2 * b + (-a + c) * t + (2 * a - 5 * b + 4 * c - d) * t2 + (-a + 3 * b - 3 * c + d) * t3);
|
|
30
|
+
out.push([f(p0[0], p1[0], p2[0], p3[0]), f(p0[1], p1[1], p2[1], p3[1])]);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
out.push(pts[pts.length - 1]);
|
|
34
|
+
return out;
|
|
35
|
+
};
|
|
36
|
+
const normals = (poly) => poly.map((p, i) => {
|
|
37
|
+
const a = poly[Math.max(0, i - 1)], b = poly[Math.min(poly.length - 1, i + 1)];
|
|
38
|
+
let tx = b[0] - a[0], ty = b[1] - a[1]; const l = Math.hypot(tx, ty) || 1; tx /= l; ty /= l;
|
|
39
|
+
return { tx, ty, nx: ty, ny: -tx }; // n = outward (left of travel)
|
|
40
|
+
});
|
|
41
|
+
const pathOf = (poly, close) => { const p = new Path2D(); poly.forEach(([x, y], i) => (i ? p.lineTo(x, y) : p.moveTo(x, y))); if (close) p.closePath(); return p; };
|
|
42
|
+
|
|
43
|
+
// keyblock line: slightly uneven carved stroke
|
|
44
|
+
const keyline = (c, path, w, col = C.key) => {
|
|
45
|
+
c.save(); c.strokeStyle = col; c.lineJoin = 'round'; c.lineCap = 'round';
|
|
46
|
+
c.lineWidth = w; c.stroke(path);
|
|
47
|
+
c.globalAlpha = 0.5; c.lineWidth = w * 0.6; c.translate(0.7, 0.5); c.stroke(path);
|
|
48
|
+
c.restore();
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
// one foam claw: a hooked finger plus smaller fingers
|
|
52
|
+
const claw = (c, x, y, nx, ny, tx, ty, L, r) => {
|
|
53
|
+
const fingers = r.int(3, 4);
|
|
54
|
+
for (let f = 0; f < fingers; f++) {
|
|
55
|
+
const s = L * (1 - f * 0.2) * r.range(0.85, 1.1);
|
|
56
|
+
const off = (f - (fingers - 1) / 2) * L * 0.24;
|
|
57
|
+
const bx = x + tx * off, by = y + ty * off;
|
|
58
|
+
const w = s * 0.22;
|
|
59
|
+
// direction: outward then hooking forward and down (tangent)
|
|
60
|
+
const cx1 = bx + nx * s * 0.9 + tx * s * 0.2, cy1 = by + ny * s * 0.9 + ty * s * 0.2;
|
|
61
|
+
const ex = bx + nx * s * 0.75 + tx * s * 0.75, ey = by + ny * s * 0.75 + ty * s * 0.75;
|
|
62
|
+
const cx2 = bx + nx * s * 0.45 + tx * s * 0.55, cy2 = by + ny * s * 0.45 + ty * s * 0.55;
|
|
63
|
+
const p = new Path2D();
|
|
64
|
+
p.moveTo(bx - tx * w, by - ty * w);
|
|
65
|
+
p.quadraticCurveTo(cx1, cy1, ex, ey);
|
|
66
|
+
p.quadraticCurveTo(cx2, cy2, bx + tx * w, by + ty * w);
|
|
67
|
+
p.closePath();
|
|
68
|
+
c.fillStyle = C.foam; c.fill(p);
|
|
69
|
+
c.lineWidth = 1.6; c.strokeStyle = C.key; c.stroke(p);
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
// broken, tapering vein lines + alternating lighter bands, following a contour (sign=-1 inward of outward normal)
|
|
75
|
+
const veins = (c, poly, nn, o) => {
|
|
76
|
+
const r = H.rng(o.seed), L = poly.length;
|
|
77
|
+
for (let k = 0; k < o.count; k++) {
|
|
78
|
+
const d = o.d0 + k * o.step + r.range(-3, 3);
|
|
79
|
+
const tk = (o.toK || 0) * k, i0 = Math.floor(L * r.range(o.from[0], o.from[1])), i1 = Math.min(L - 1, Math.floor(L * (r.range(o.to[0], o.to[1]) - tk)));
|
|
80
|
+
if (i1 - i0 < 4) continue;
|
|
81
|
+
const pt = (i) => { const w = d + Math.sin(i * 0.07 + k) * 2.5; return [poly[i][0] + o.sign * nn[i].nx * w, poly[i][1] + o.sign * nn[i].ny * w]; };
|
|
82
|
+
if (o.band && k % 2 === 0) {
|
|
83
|
+
c.strokeStyle = o.band; c.lineWidth = o.step * 0.62; c.lineCap = 'round';
|
|
84
|
+
c.beginPath(); for (let i = i0; i <= i1; i++) { const [x, y] = pt(i); i === i0 ? c.moveTo(x, y) : c.lineTo(x, y); } c.stroke();
|
|
85
|
+
}
|
|
86
|
+
c.strokeStyle = o.line; c.lineCap = 'round';
|
|
87
|
+
const seg = 6;
|
|
88
|
+
for (let a = i0; a < i1; a += seg) {
|
|
89
|
+
const u = (a - i0) / (i1 - i0);
|
|
90
|
+
c.lineWidth = H.lerp(o.w0, o.w1, u) * (u < 0.08 ? u / 0.08 : 1) + 0.3;
|
|
91
|
+
c.beginPath(); for (let i = a; i <= Math.min(i1, a + seg); i++) { const [x, y] = pt(i); i === a ? c.moveTo(x, y) : c.lineTo(x, y); } c.stroke();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
// keyblock-only context: key-coloured fills/strokes print; everything else carves the key away (occlusion)
|
|
98
|
+
const keyOnly = (kc) => {
|
|
99
|
+
const P = CanvasRenderingContext2D.prototype;
|
|
100
|
+
const isKey = (st) => typeof st === 'string' && (st.startsWith('#1b2236') || st.startsWith('rgba(27, 34, 54'));
|
|
101
|
+
const wrap = (name, prop) => { kc[name] = function (...a) { if (isKey(this[prop])) return P[name].apply(this, a); const g = this.globalCompositeOperation; this.globalCompositeOperation = 'destination-out'; P[name].apply(this, a); this.globalCompositeOperation = g; }; };
|
|
102
|
+
wrap('fill', 'fillStyle'); wrap('fillRect', 'fillStyle'); wrap('fillText', 'fillStyle'); wrap('stroke', 'strokeStyle');
|
|
103
|
+
};
|
|
104
|
+
// finish-pass noise fields are deterministic -> compute once
|
|
105
|
+
let FIELDS = null;
|
|
106
|
+
const fields = (W, HH) => FIELDS || (FIELDS = { warp: fieldNoise(W, HH, 3, 30, 3), fine: fieldNoise(W, HH, 4, 2, 2, 0.15, 2.2), pap: fieldNoise(W, HH, 6, 3, 2, 2.5, 0.6), blot: fieldNoise(W, HH, 7, 40, 4) });
|
|
107
|
+
|
|
108
|
+
// isometric data cube
|
|
109
|
+
const cube = (c, x, y, s, rot) => {
|
|
110
|
+
c.save(); c.translate(x, y); c.rotate(rot);
|
|
111
|
+
const h = s * 0.58;
|
|
112
|
+
const top = new Path2D(); top.moveTo(0, -s); top.lineTo(s * 0.87, -s + h); top.lineTo(0, -s + 2 * h); top.lineTo(-s * 0.87, -s + h); top.closePath();
|
|
113
|
+
const left = new Path2D(); left.moveTo(-s * 0.87, -s + h); left.lineTo(0, -s + 2 * h); left.lineTo(0, s * 0.9); left.lineTo(-s * 0.87, s * 0.9 - h); left.closePath();
|
|
114
|
+
const right = new Path2D(); right.moveTo(s * 0.87, -s + h); right.lineTo(0, -s + 2 * h); right.lineTo(0, s * 0.9); right.lineTo(s * 0.87, s * 0.9 - h); right.closePath();
|
|
115
|
+
c.fillStyle = C.foam; c.fill(top); c.fillStyle = C.pale; c.fill(left); c.fillStyle = C.light; c.fill(right);
|
|
116
|
+
c.lineWidth = Math.max(1, s * 0.12); c.strokeStyle = C.key; c.lineJoin = 'round';
|
|
117
|
+
c.stroke(top); c.stroke(left); c.stroke(right);
|
|
118
|
+
c.restore();
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// ===================== EXPLAINER: "How does AI learn?" =====================
|
|
122
|
+
// An emaki handscroll: the camera unrolls the scroll right-to-left, scenes are divided by golden kasumi
|
|
123
|
+
// clouds, flow is shown by currents with claw-foam heads, labels are paper cartouches and tanzaku slips,
|
|
124
|
+
// judgements are vermilion seal stamps.
|
|
125
|
+
const fillKey = (c, path, col, w = 1.8) => { c.save(); c.translate(1.5, 1); c.fillStyle = col; c.fill(path); c.restore(); keyline(c, path, w); };
|
|
126
|
+
const cartouche = (c, txt, x, y, size, p = 1, bg = '#e8d49a', align = 'center', fg = C.key) => {
|
|
127
|
+
if (p <= 0) return; c.save(); c.font = `700 ${size}px "Noto Serif JP"`; c.textBaseline = 'middle';
|
|
128
|
+
const w = c.measureText(txt).width + size * 1.1, h = size * 1.7, x0 = align === 'center' ? x - w / 2 : x;
|
|
129
|
+
c.globalAlpha = Math.min(1, p * 1.5);
|
|
130
|
+
const r = new Path2D(); r.rect(x0, y - h / 2, w, h);
|
|
131
|
+
if (bg[0] === '#' && bg.length === 7) { const v = H.hex(bg), lt = v.map((q) => Math.round(q + (255 - q) * 0.35)), dk = v.map((q) => Math.round(q * 0.93)); c.fillStyle = vbok(c, y - h / 2, y + h / 2, [[0, `rgb(${lt})`], [0.45, bg], [1, `rgb(${dk})`]]); } else c.fillStyle = bg;
|
|
132
|
+
c.fill(r); c.strokeStyle = C.key; c.lineWidth = 2; c.stroke(r); c.lineWidth = 1; c.strokeRect(x0 + 4, y - h / 2 + 4, w - 8, h - 8);
|
|
133
|
+
c.fillStyle = fg; c.textAlign = 'center'; c.fillText(txt, x0 + w / 2, y + 1); c.restore();
|
|
134
|
+
};
|
|
135
|
+
const seal = (c, x, y, s, glyph, p, rot = 0.05) => { // vermilion seal stamped down
|
|
136
|
+
if (p <= 0) return; const k = 1 + (1 - p) * 0.8; c.save(); c.translate(x, y); c.rotate(rot); c.scale(k, k); c.globalAlpha = Math.min(1, p * 2);
|
|
137
|
+
c.fillStyle = C.red; c.fillRect(-s / 2, -s / 2, s, s); c.strokeStyle = '#efe2c6'; c.lineWidth = s * 0.06; c.strokeRect(-s * 0.4, -s * 0.4, s * 0.8, s * 0.8);
|
|
138
|
+
c.fillStyle = '#efe2c6'; c.strokeStyle = '#efe2c6'; c.lineCap = 'square'; c.lineWidth = s * 0.13;
|
|
139
|
+
if (glyph === 'x') { c.beginPath(); c.moveTo(-s * 0.22, -s * 0.22); c.lineTo(s * 0.22, s * 0.22); c.moveTo(s * 0.22, -s * 0.22); c.lineTo(-s * 0.22, s * 0.22); c.stroke(); }
|
|
140
|
+
else if (glyph === 'v') { c.beginPath(); c.moveTo(-s * 0.24, 0); c.lineTo(-s * 0.06, s * 0.18); c.lineTo(s * 0.24, -s * 0.2); c.stroke(); }
|
|
141
|
+
else { c.font = `900 ${s * 0.62}px "Noto Serif JP"`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(glyph, 0, s * 0.03); }
|
|
142
|
+
c.restore();
|
|
143
|
+
};
|
|
144
|
+
const tanzaku = (c, txt, x, y, p = 1) => { // paper label slip hanging from a cord
|
|
145
|
+
if (p <= 0) return; c.save(); c.globalAlpha = Math.min(1, p * 2); c.strokeStyle = C.key; c.lineWidth = 1.2; c.beginPath(); c.moveTo(x, y - 16); c.lineTo(x, y - 4); c.stroke();
|
|
146
|
+
c.font = '700 28px "Noto Serif JP"'; const w = c.measureText(txt).width + 20; const r = new Path2D(); r.rect(x - w / 2, y - 4, w, 40);
|
|
147
|
+
const g = c.createLinearGradient(0, y - 4, 0, y + 36); g.addColorStop(0, '#f4ecd8'); g.addColorStop(1, '#e9dcbc'); c.fillStyle = g; c.fill(r); keyline(c, r, 1.4);
|
|
148
|
+
c.fillStyle = C.key; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(txt, x, y + 16); c.restore();
|
|
149
|
+
};
|
|
150
|
+
const animal = (c, kind, x, y, s) => { // flat woodblock silhouette: grey cat / ochre shiba
|
|
151
|
+
c.save(); c.translate(x, y); c.scale(s / 100, s / 100);
|
|
152
|
+
const p = new Path2D();
|
|
153
|
+
if (kind === 'cat') {
|
|
154
|
+
p.ellipse(0, 18, 30, 34, 0, 0, 7); p.moveTo(22, -14); p.arc(0, -22, 24, 0, 7);
|
|
155
|
+
const e = new Path2D(); e.moveTo(-22, -32); e.lineTo(-18, -58); e.lineTo(-4, -42); e.closePath(); e.moveTo(22, -32); e.lineTo(18, -58); e.lineTo(4, -42); e.closePath();
|
|
156
|
+
fillKey(c, e, C.grey, 2); fillKey(c, p, C.grey, 2);
|
|
157
|
+
c.strokeStyle = C.key; c.lineWidth = 6; c.lineCap = 'round'; c.beginPath(); c.moveTo(26, 40); c.quadraticCurveTo(58, 40, 50, 6); c.stroke(); c.strokeStyle = C.grey; c.lineWidth = 3; c.stroke();
|
|
158
|
+
c.fillStyle = C.key; c.beginPath(); c.ellipse(-9, -24, 2.5, 4, 0, 0, 7); c.ellipse(9, -24, 2.5, 4, 0, 0, 7); c.fill();
|
|
159
|
+
} else {
|
|
160
|
+
p.ellipse(0, 18, 32, 32, 0, 0, 7); p.moveTo(26, -18); p.arc(0, -20, 26, 0, 7); p.moveTo(10, -6); p.ellipse(0, -8, 14, 10, 0, 0, 7);
|
|
161
|
+
const e = new Path2D(); e.moveTo(-24, -26); e.lineTo(-22, -50); e.lineTo(-6, -40); e.closePath(); e.moveTo(24, -26); e.lineTo(22, -50); e.lineTo(6, -40); e.closePath();
|
|
162
|
+
fillKey(c, e, C.ochre, 2); fillKey(c, p, C.ochre, 2);
|
|
163
|
+
const m = new Path2D(); m.ellipse(0, -6, 12, 8, 0, 0, 7); c.fillStyle = '#f1e8d2'; c.fill(m);
|
|
164
|
+
c.fillStyle = C.key; c.beginPath(); c.arc(-10, -24, 3, 0, 7); c.arc(10, -24, 3, 0, 7); c.arc(0, -10, 3.5, 0, 7); c.fill();
|
|
165
|
+
c.strokeStyle = C.key; c.lineWidth = 6; c.lineCap = 'round'; c.beginPath(); c.arc(32, 0, 12, Math.PI * 0.6, Math.PI * 2.1); c.stroke(); c.strokeStyle = C.ochre; c.lineWidth = 3; c.stroke();
|
|
166
|
+
}
|
|
167
|
+
c.restore();
|
|
168
|
+
};
|
|
169
|
+
const print = (c, kind, x, y, s) => { const r = new Path2D(); r.rect(x - s / 2, y - s / 2, s, s * 1.15); fillKey(c, r, '#f1e8d2', 2); c.strokeStyle = C.key; c.lineWidth = 1; c.strokeRect(x - s / 2 + 5, y - s / 2 + 5, s - 10, s * 1.15 - 10); animal(c, kind, x, y + s * 0.12, s * 0.8); };
|
|
170
|
+
const gear = (c, x, y, r, a, col) => { const p = new Path2D(); for (let i = 0; i < 24; i++) { const rr = i % 2 ? r : r * 1.2, aa = a + i / 24 * Math.PI * 2; p.lineTo(x + Math.cos(aa) * rr, y + Math.sin(aa) * rr); } p.closePath(); p.moveTo(x + r * 0.35, y); p.arc(x, y, r * 0.35, 0, 7, true); fillKey(c, p, col, 1.6); };
|
|
171
|
+
const karakuri = (c, x, y, look, mood, gears, sc = 1.3) => { c.save(); c.translate(x, y); c.scale(sc, sc); karakuri0(c, 0, 0, look, mood, gears); c.restore(); };
|
|
172
|
+
const karakuri0 = (c, x, y, look, mood, gears) => { // Edo automaton doll on a dais, with its gear cabinet (the weights)
|
|
173
|
+
const dais = new Path2D(); dais.rect(x - 130, y + 70, 260, 36); fillKey(c, dais, vbok(c, y + 70, y + 106, [[0, '#94503a'], [1, '#5e2c1e']]));
|
|
174
|
+
const cab = new Path2D(); cab.rect(x - 230, y - 40, 110, 140); fillKey(c, cab, vbok(c, y - 40, y + 100, [[0, '#5a4132'], [0.6, '#3b2a22'], [1, '#2a1d17']]));
|
|
175
|
+
gears.forEach((a, i) => gear(c, x - 175 + (i % 2) * 22, y - 8 + i * 42, 17 - (i % 2) * 3, a, C.ochre));
|
|
176
|
+
const body = new Path2D(); body.moveTo(x - 80, y + 70); body.lineTo(x - 50, y - 30); body.lineTo(x + 50, y - 30); body.lineTo(x + 80, y + 70); body.closePath(); fillKey(c, body, vbok(c, y - 30, y + 70, [[0, '#d65438'], [0.5, C.red], [1, '#a8331f']]));
|
|
177
|
+
c.save(); c.clip(body); c.fillStyle = '#efe2c6'; for (let i = 0; i < 6; i++) for (let j = 0; j < 4; j++) { c.beginPath(); c.arc(x - 70 + i * 28 + (j % 2) * 14, y - 20 + j * 26, 4, 0, 7); c.fill(); } c.restore();
|
|
178
|
+
const obi = new Path2D(); obi.rect(x - 58, y + 6, 116, 18); fillKey(c, obi, C.ochre, 1.4);
|
|
179
|
+
const tray = new Path2D(); tray.rect(x + 20, y + 20, 90, 12); fillKey(c, tray, C.hull, 1.4);
|
|
180
|
+
const head = new Path2D(); head.arc(x, y - 72, 40, 0, 7); fillKey(c, head, vbok(c, y - 112, y - 32, [[0, '#f7f0de'], [1, '#eaddc0']]), 2);
|
|
181
|
+
c.save(); c.globalAlpha = 0.35; c.fillStyle = '#e8a08a'; c.beginPath(); c.ellipse(x - 24, y - 58, 8, 5, 0, 0, 7); c.ellipse(x + 24, y - 58, 8, 5, 0, 0, 7); c.fill(); c.restore();
|
|
182
|
+
const hair = new Path2D(); hair.arc(x, y - 82, 41, Math.PI * 1.02, Math.PI * 1.98); hair.closePath(); hair.moveTo(x + 16, y - 128); hair.arc(x, y - 128, 16, 0, 7); c.fillStyle = C.key; c.fill(hair);
|
|
183
|
+
const dx = (look - 0.5) * 10; c.strokeStyle = C.key; c.lineWidth = 2.5; c.lineCap = 'round';
|
|
184
|
+
c.beginPath(); c.moveTo(x - 20 + dx, y - 70); c.lineTo(x - 8 + dx, y - 70); c.moveTo(x + 8 + dx, y - 70); c.lineTo(x + 20 + dx, y - 70); c.stroke();
|
|
185
|
+
c.fillStyle = C.red; c.beginPath(); if (mood < 0) c.arc(x + dx * 0.5, y - 46, 5, Math.PI, 0); else c.arc(x + dx * 0.5, y - 50, 5, 0, Math.PI); c.fill();
|
|
186
|
+
};
|
|
187
|
+
// vertical bokashi: a colour block wiped darker at one end, as the printer grades the block with a damp cloth
|
|
188
|
+
const vbok = (c, y0, y1, stops) => { const g = c.createLinearGradient(0, y0, 0, y1); stops.forEach(([u, col]) => g.addColorStop(u, col)); return g; };
|
|
189
|
+
// a golden mist mass dividing two scenes: scalloped cloud bank printed as a gold bokashi (deep core wiping out
|
|
190
|
+
// pale toward the scallops), dusted with mica, outlined by a fine keyline. Static per position -> cached.
|
|
191
|
+
const KAS = {};
|
|
192
|
+
const kasumi = (c, x, top, bot) => {
|
|
193
|
+
const key = Math.round(x) + ':' + top + ':' + bot;
|
|
194
|
+
let L = KAS[key];
|
|
195
|
+
if (!L) {
|
|
196
|
+
const r = H.rng(Math.round(x)), bumps = [];
|
|
197
|
+
for (let y = top; y <= bot; y += 60) bumps.push([x - 90 + r.range(-20, 20), y + 30]);
|
|
198
|
+
for (let y = bot; y >= top; y -= 60) bumps.push([x + 90 + r.range(-20, 20), y]);
|
|
199
|
+
const ox = x - 160, oy = top - 50, lw = 320, lh = bot - top + 130;
|
|
200
|
+
L = H.layer(lw, lh); L.ox = ox; L.oy = oy; const k = L.getContext('2d'); k.translate(-ox, -oy);
|
|
201
|
+
const shape = new Path2D(); shape.rect(x - 90, top, 180, bot + 30 - top); bumps.forEach(([bx, by]) => { shape.moveTo(bx + 34, by); shape.arc(bx, by, 34, 0, Math.PI * 2); });
|
|
202
|
+
// outline of the union: stroke every lobe, then fill every lobe over the inner halves
|
|
203
|
+
k.strokeStyle = 'rgba(27,34,54,0.72)'; k.lineWidth = 2.6; k.lineJoin = 'round';
|
|
204
|
+
bumps.forEach(([bx, by]) => { k.beginPath(); k.arc(bx, by, 34, 0, Math.PI * 2); k.stroke(); }); k.strokeRect(x - 90, top, 180, bot + 30 - top);
|
|
205
|
+
const g = k.createLinearGradient(x - 126, 0, x + 126, 0);
|
|
206
|
+
g.addColorStop(0, '#ece0bc'); g.addColorStop(0.2, '#e2cd92'); g.addColorStop(0.5, '#d4b25a'); g.addColorStop(0.8, '#e2cd92'); g.addColorStop(1, '#ece0bc');
|
|
207
|
+
k.save(); k.translate(1.2, 0.8); k.fillStyle = g; k.fill(shape); k.restore();
|
|
208
|
+
k.save(); k.clip(shape);
|
|
209
|
+
// a second, horizontal wipe: the gold deepens toward the top of the bank
|
|
210
|
+
const gv = k.createLinearGradient(0, top - 34, 0, bot + 64); gv.addColorStop(0, 'rgba(176,132,48,0.28)'); gv.addColorStop(0.45, 'rgba(176,132,48,0)'); gv.addColorStop(1, 'rgba(255,248,220,0.18)');
|
|
211
|
+
k.fillStyle = gv; k.fillRect(x - 160, top - 50, 320, lh);
|
|
212
|
+
// inner echo of each scallop (the carved line printed in a darker gold, as on Hiroshige's kasumi)
|
|
213
|
+
k.strokeStyle = 'rgba(160,118,40,0.2)'; k.lineWidth = 1;
|
|
214
|
+
bumps.forEach(([bx, by], i) => { const sgn = i < bumps.length / 2 ? -1 : 1; k.beginPath(); k.arc(bx - sgn * 7, by, 24, sgn < 0 ? Math.PI * 0.62 : -Math.PI * 0.38, sgn < 0 ? Math.PI * 1.38 : Math.PI * 0.38); k.stroke(); });
|
|
215
|
+
// mica dust
|
|
216
|
+
const mr = H.rng(Math.round(x) + 991);
|
|
217
|
+
for (let i = 0; i < 1100; i++) { const px = mr.range(x - 130, x + 130), py = mr.range(top - 34, bot + 64), sz = mr.range(0.6, 1.6); k.fillStyle = mr() < 0.8 ? `rgba(255,248,222,${mr.range(0.35, 0.7)})` : `rgba(150,110,40,${mr.range(0.15, 0.3)})`; k.fillRect(px, py, sz, sz); }
|
|
218
|
+
k.restore();
|
|
219
|
+
KAS[key] = L;
|
|
220
|
+
}
|
|
221
|
+
c.drawImage(L, L.ox, L.oy);
|
|
222
|
+
};
|
|
223
|
+
const current = (c, pts, p, foam = true) => { // a stream line with claw-foam arrowhead
|
|
224
|
+
if (p <= 0) return; const poly = sample(pts, 16), n = Math.max(2, Math.floor(poly.length * p)), part = poly.slice(0, n), nn = normals(part);
|
|
225
|
+
for (const off of [-8, 0, 8]) { c.strokeStyle = off ? C.mid : C.pruss; c.lineWidth = off ? 2 : 5; c.lineCap = 'round'; c.beginPath(); part.forEach(([x, y], i) => { const X = x + nn[i].nx * off, Y = y + nn[i].ny * off; i ? c.lineTo(X, Y) : c.moveTo(X, Y); }); c.stroke(); }
|
|
226
|
+
if (foam) { const e = part[part.length - 1], q = nn[nn.length - 1], r = H.rng(7); claw(c, e[0], e[1], q.tx * 0.6 + q.nx * 0.4, q.ty * 0.6 + q.ny * 0.4, -q.nx, -q.ny, 26, r); }
|
|
227
|
+
};
|
|
228
|
+
const seaBand = (c, x0, x1, y, t) => { // water with wave lines along the foot of the scroll
|
|
229
|
+
const g = c.createLinearGradient(0, y, 0, 720); g.addColorStop(0, C.light); g.addColorStop(0.18, C.mid); g.addColorStop(0.6, C.pruss); g.addColorStop(1, C.prussD); c.fillStyle = g; c.fillRect(x0, y, x1 - x0, 720 - y);
|
|
230
|
+
c.strokeStyle = 'rgba(241,232,210,0.75)'; c.lineWidth = 1.6; for (let i = 0; i < 6; i++) { const yy = y + 14 + i * 18; c.beginPath(); for (let x = x0; x <= x1; x += 8) { const v = yy + Math.sin(x * 0.03 + i * 1.7 + t * 1.2) * 3; x === x0 ? c.moveTo(x, v) : c.lineTo(x, v); } c.stroke(); }
|
|
231
|
+
c.strokeStyle = C.key; c.lineWidth = 2; c.beginPath(); for (let x = x0; x <= x1; x += 8) { const v = y + Math.sin(x * 0.02 + t) * 4; x === x0 ? c.moveTo(x, v) : c.lineTo(x, v); } c.stroke();
|
|
232
|
+
// little foam curls riding the waterline, anchored to the scroll
|
|
233
|
+
for (let k = Math.ceil(x0 / 150); k * 150 <= x1; k++) { const fx = k * 150 + 40 * Math.sin(k * 2.3), fy = y + Math.sin(fx * 0.02 + t) * 4, r = H.rng(1000 + k * 7); claw(c, fx, fy, 0.25, -1, 1, 0.25, 15 + r.range(0, 5), r); }
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
// Fuji's snowcap (panel 0 shape); s scales it to narrower summits
|
|
237
|
+
const fujiCap = (x, sx, sy, s) => { const cap = new Path2D(), P = (a, b) => [x + sx + (a - 905) * s, sy + (b - 400)];
|
|
238
|
+
cap.moveTo(...P(905, 400)); cap.lineTo(...P(960, 398)); cap.lineTo(...P(990, 445)); [[970, 430], [955, 450], [935, 428], [918, 448], [895, 432], [880, 444]].forEach(([a, b]) => cap.lineTo(...P(a, b))); cap.closePath(); return cap; };
|
|
239
|
+
// kozo fibres floating in the sheet (static -> cached)
|
|
240
|
+
let FIB = null;
|
|
241
|
+
const fibres = (W, HH) => { if (FIB) return FIB; FIB = H.layer(W, HH); const c = FIB.getContext('2d'), rf = H.rng(55);
|
|
242
|
+
for (let i = 0; i < 300; i++) { const x = rf.range(0, W), y = rf.range(0, HH), l = rf.range(10, 40), a = rf.range(0, Math.PI * 2);
|
|
243
|
+
c.strokeStyle = rf() < 0.5 ? 'rgba(255,250,235,0.24)' : 'rgba(120,95,60,0.13)'; c.lineWidth = rf.range(0.5, 1.1);
|
|
244
|
+
c.beginPath(); c.moveTo(x, y); c.quadraticCurveTo(x + Math.cos(a) * l * 0.5 + rf.range(-6, 6), y + Math.sin(a) * l * 0.5 + rf.range(-6, 6), x + Math.cos(a) * l, y + Math.sin(a) * l); c.stroke(); }
|
|
245
|
+
return FIB; };
|
|
246
|
+
const explainUkiyo = (c0, W, HH, t) => {
|
|
247
|
+
const E = H.ease, cl = H.clamp, S = (a, b) => cl((t - a) / (b - a), 0, 1);
|
|
248
|
+
const keys = [[0, 0], [2.0, 0], [2.9, 1], [5.7, 1], [6.5, 2], [10.1, 2], [10.8, 3], [12.5, 3], [13.2, 4]];
|
|
249
|
+
let pan = 0; for (let i = 0; i < keys.length - 1; i++) { const [ta, a] = keys[i], [tb, b] = keys[i + 1]; if (t >= ta) pan = t >= tb ? b : H.lerp(a, b, E.inOut((t - ta) / (tb - ta))); }
|
|
250
|
+
const camX = -pan * W; // the scroll unrolls right-to-left: later scenes lie to the left
|
|
251
|
+
const c = c0;
|
|
252
|
+
c.fillStyle = C.paper; c.fillRect(0, 0, W, HH);
|
|
253
|
+
c.save(); c.translate(-camX, 0);
|
|
254
|
+
const vis = (k) => Math.abs(pan - k) < 1.05, PX = (k) => -k * W; // panel k spans [PX(k), PX(k)+W]
|
|
255
|
+
// continuous sky bokashi and top mist band along the whole scroll
|
|
256
|
+
// the ochre sky plate printed over the whole scroll, paling to a light band at the horizon above the sea
|
|
257
|
+
c.fillStyle = vbok(c, 0, 600, [[0, C.sky], [0.2, C.sky], [0.82, '#e7d8b4'], [1, '#efe5cc']]); c.fillRect(-5 * W, 0, 7 * W, 600);
|
|
258
|
+
const sk = c.createLinearGradient(0, 0, 0, 200); sk.addColorStop(0, 'rgba(46,62,84,0.85)'); sk.addColorStop(1, 'rgba(90,100,110,0)'); c.fillStyle = sk; c.fillRect(-5 * W, 0, 7 * W, 200);
|
|
259
|
+
seaBand(c, camX - 50, camX + W + 50, 600, t);
|
|
260
|
+
|
|
261
|
+
// ---------- panel 0: hook ----------
|
|
262
|
+
if (vis(0)) {
|
|
263
|
+
const x = PX(0);
|
|
264
|
+
const sun = new Path2D(); const sy = H.lerp(460, 250, E.out(S(0.1, 1.4))); sun.arc(x + 930, sy, 90, 0, 7); c.save(); c.globalAlpha = 0.95; c.fillStyle = C.red; c.fill(sun); c.restore();
|
|
265
|
+
const fuji = new Path2D(); fuji.moveTo(x + 700, 600); fuji.quadraticCurveTo(x + 840, 520, x + 905, 400); fuji.lineTo(x + 960, 398); fuji.quadraticCurveTo(x + 1020, 520, x + 1170, 600); fuji.closePath(); fillKey(c, fuji, vbok(c, 398, 600, [[0, C.prussD], [0.55, C.fuji], [1, C.light]]));
|
|
266
|
+
const cap = fujiCap(x, 905, 400, 1); fillKey(c, cap, C.foam, 1.4);
|
|
267
|
+
current(c, [[x + 1250, 560], [x + 1000, 540], [x + 700, 560], [x + 420, 530]], E.inOut(S(0.4, 1.6)));
|
|
268
|
+
}
|
|
269
|
+
// ---------- panel 1: data — a river of labelled prints floats to the automaton ----------
|
|
270
|
+
if (vis(1)) {
|
|
271
|
+
const x = PX(1), kx = x + 430, ky = 410, K1 = 1.3;
|
|
272
|
+
karakuri(c, kx, ky, 0.9, 1, [t * 0.5, -t * 0.7, t * 0.4]);
|
|
273
|
+
tanzaku(c, 'model', kx + 150, ky + 150, S(2.6, 2.9));
|
|
274
|
+
// river current (arrow) into the doll's tray
|
|
275
|
+
current(c, [[x + 1250, 520], [x + 950, 490], [x + 720, 520], [x + 580, 470]], E.inOut(S(2.6, 3.3)));
|
|
276
|
+
// boats, each carrying a print with a tanzaku label
|
|
277
|
+
for (let i = 0; i < 9; i++) {
|
|
278
|
+
const u = (t - 2.7 - i * 0.6) * 0.42; if (u < 0 || u > 1) continue;
|
|
279
|
+
const kind = H.rng(i * 17 + 2)() < 0.5 ? 'cat' : 'dog', bx = H.lerp(x + 1330, x + 560, u), by = 500 + Math.sin(u * 9 + i) * 8, sh = 1 - E.in(cl((u - 0.82) / 0.18, 0, 1));
|
|
280
|
+
if (sh < 0.05) continue;
|
|
281
|
+
c.save(); c.translate(bx, by); c.scale(sh * 1.35, sh * 1.35); c.rotate(Math.sin(t * 2 + i) * 0.05);
|
|
282
|
+
const hull = new Path2D(); hull.moveTo(-60, 0); hull.lineTo(60, 0); hull.lineTo(44, 20); hull.lineTo(-48, 20); hull.closePath(); fillKey(c, hull, vbok(c, 0, 20, [[0, '#94503a'], [1, '#5e2c1e']]), 1.6);
|
|
283
|
+
print(c, kind, 0, -52, 70); tanzaku(c, kind, 0, 0, 1);
|
|
284
|
+
c.restore();
|
|
285
|
+
}
|
|
286
|
+
// counter cartouche (numbers re-cut as they climb)
|
|
287
|
+
const cp = S(3.0, 5.4), n = cp < 1 ? Math.pow(cp, 3) * 1e6 : 1e6;
|
|
288
|
+
if (t > 2.9) { cartouche(c, Math.round(n).toLocaleString('en-US'), x + 830, 230, 66, S(2.9, 3.2)); cartouche(c, 'examples · training data', x + 830, 335, 26, S(3.1, 3.4), '#f1e8d2'); }
|
|
289
|
+
}
|
|
290
|
+
// ---------- panel 2: guess, check, adjust — the whirlpool loop ----------
|
|
291
|
+
if (vis(2)) {
|
|
292
|
+
const x = PX(2), kx = x + 430, ky = 410, adj = E.inOut(S(8.3, 9.1)), right = t >= 9.3;
|
|
293
|
+
karakuri(c, kx, ky, 0.95, t < 7.4 ? 0 : right ? 1 : -1, [adj * 4, -adj * 5, adj * 3]);
|
|
294
|
+
tanzaku(c, 'weights', kx - 228, ky + 150, S(6.5, 6.8));
|
|
295
|
+
// new print on a stand
|
|
296
|
+
const st = new Path2D(); st.rect(x + 695, 470, 10, 130); fillKey(c, st, C.hull, 1.4);
|
|
297
|
+
print(c, 'cat', x + 700, 390, 120 * E.out(S(6.4, 6.8)));
|
|
298
|
+
tanzaku(c, 'new picture', x + 700, 512, S(6.7, 7.0));
|
|
299
|
+
// the guess, judged by a seal
|
|
300
|
+
if (!right) {
|
|
301
|
+
cartouche(c, 'dog?', x + 560, 150, 50, S(6.9, 7.1));
|
|
302
|
+
seal(c, x + 675, 148, 70, 'x', S(7.45, 7.6));
|
|
303
|
+
const rp = S(7.55, 7.95); if (rp > 0) { c.strokeStyle = C.red; c.lineWidth = 5; c.lineCap = 'round'; c.beginPath(); c.ellipse(x + 605, 150, 145, 70, -0.05, -1.6, -1.6 + Math.PI * 1.9 * rp); c.stroke(); }
|
|
304
|
+
if (t > 7.8) cartouche(c, 'wrong — it is a cat', x + 600, 250, 26, S(7.8, 8.0), '#f1e8d2', 'center', C.red);
|
|
305
|
+
// ADJUST: the error runs back along a red thread to the gear cabinet
|
|
306
|
+
const ep = S(7.9, 8.5); if (ep > 0) { const pts = sample([[x + 675, 185], [x + 560, 300], [x + 330, 250], [kx - 228, ky - 20]], 16), n = Math.floor(pts.length * ep); c.strokeStyle = C.red; c.lineWidth = 3.5; c.beginPath(); pts.slice(0, Math.max(2, n)).forEach(([a, b], i) => (i ? c.lineTo(a, b) : c.moveTo(a, b))); c.stroke(); if (ep >= 1) cartouche(c, 'error', x + 470, 300, 24, 1, C.red, 'center', '#efe2c6'); }
|
|
307
|
+
} else {
|
|
308
|
+
cartouche(c, 'cat', x + 560, 150, 50, S(9.35, 9.55));
|
|
309
|
+
seal(c, x + 672, 148, 70, 'v', S(9.65, 9.8));
|
|
310
|
+
}
|
|
311
|
+
// whirlpool diagram (the loop)
|
|
312
|
+
const wx = x + 990, wy = 370, rot = t * 0.9;
|
|
313
|
+
const wp = E.out(S(6.6, 7.2));
|
|
314
|
+
if (wp > 0) {
|
|
315
|
+
const disk = new Path2D(); disk.arc(wx, wy, 170 * wp, 0, 7); const dg = c.createRadialGradient(wx, wy, 0, wx, wy, 170 * wp); dg.addColorStop(0, C.prussD); dg.addColorStop(0.45, C.pruss); dg.addColorStop(1, C.mid); fillKey(c, disk, dg, 2);
|
|
316
|
+
c.save(); c.clip(disk); c.strokeStyle = 'rgba(241,232,210,0.85)'; c.lineWidth = 3; c.beginPath(); for (let a = 0; a < 26; a += 0.05) { const r = 162 - a * 6; c.lineTo(wx + Math.cos(a + rot) * r, wy + Math.sin(a + rot) * r); } c.stroke();
|
|
317
|
+
c.strokeStyle = C.pruss; c.lineWidth = 5; c.beginPath(); for (let a = 0; a < 26; a += 0.05) { const r = 155 - a * 6; c.lineTo(wx + Math.cos(a + rot) * r, wy + Math.sin(a + rot) * r); } c.stroke(); c.restore();
|
|
318
|
+
const rc = H.rng(3); for (let i = 0; i < 18; i++) { const a = i / 18 * Math.PI * 2 + rot * 0.5; claw(c, wx + Math.cos(a) * 170 * wp, wy + Math.sin(a) * 170 * wp, Math.cos(a), Math.sin(a), -Math.sin(a), Math.cos(a), 22 * wp, rc); }
|
|
319
|
+
}
|
|
320
|
+
const steps = [['Guess', -Math.PI / 2], ['Check', Math.PI / 6], ['Adjust', Math.PI * 5 / 6]], act = t < 7.45 ? 0 : t < 8.3 ? 1 : t < 9.3 ? 2 : Math.floor((t - 9.3) * 3) % 3;
|
|
321
|
+
steps.forEach(([s, a], i) => cartouche(c, s, wx + Math.cos(a) * 100, wy + Math.sin(a) * 100, 27, S(6.9 + i * 0.15, 7.15 + i * 0.15), i === act && t > 6.9 ? '#e8b07a' : '#e8d49a'));
|
|
322
|
+
// currents chase round the rim from step to step
|
|
323
|
+
const arcPts = (a0, a1) => { const P = []; for (let k = 0; k <= 6; k++) { const a = H.lerp(a0, a1, k / 6); P.push([wx + Math.cos(a) * 212, wy + Math.sin(a) * 212]); } return P; };
|
|
324
|
+
current(c, arcPts(-Math.PI / 2 + 0.45, Math.PI / 6 - 0.45), E.out(S(7.2, 7.5)));
|
|
325
|
+
current(c, arcPts(Math.PI / 6 + 0.45, Math.PI * 5 / 6 - 0.45), E.out(S(7.9, 8.2)));
|
|
326
|
+
current(c, arcPts(Math.PI * 5 / 6 + 0.45, Math.PI * 1.5 - 0.45), E.out(S(9.0, 9.3)));
|
|
327
|
+
// a cube of data rides the whirlpool round the loop
|
|
328
|
+
if (t > 7.0) { const a = -Math.PI / 2 + (t - 7.0) * 2.1; cube(c, wx + Math.cos(a) * 145, wy + Math.sin(a) * 145, 13, 0); }
|
|
329
|
+
}
|
|
330
|
+
// ---------- panel 3: progress — accuracy climbs like a mountain path ----------
|
|
331
|
+
if (vis(3)) {
|
|
332
|
+
const x = PX(3), gx = x + 150, gw = 680, base = 580, prog = E.inOut(S(10.8, 12.0));
|
|
333
|
+
const pts = []; for (let i = 0; i <= 40; i++) { const u = i / 40, acc = 61 + 36 * (1 - Math.pow(1 - u, 2.2)) + Math.sin(i * 1.7) * 1.5 * (1 - u); pts.push([gx + u * gw, base - (acc - 40) / 60 * 420]); }
|
|
334
|
+
const ridge = new Path2D(); ridge.moveTo(gx, base); pts.forEach((p) => ridge.lineTo(...p)); ridge.lineTo(gx + gw, base); ridge.closePath();
|
|
335
|
+
const sunY = H.lerp(520, 150, prog); const sun = new Path2D(); sun.arc(gx + gw * prog, sunY - 40, 42, 0, 7); c.fillStyle = C.red; c.fill(sun);
|
|
336
|
+
c.save(); c.beginPath(); c.rect(gx - 5, 0, gw * prog + 6, HH); c.clip();
|
|
337
|
+
const g = c.createLinearGradient(0, 150, 0, base); g.addColorStop(0, C.green); g.addColorStop(1, C.pruss); c.save(); c.translate(1.5, 1); c.fillStyle = g; c.fill(ridge); c.restore();
|
|
338
|
+
c.strokeStyle = 'rgba(241,232,210,0.7)'; c.lineWidth = 1.5; for (let k = 1; k < 6; k++) { c.beginPath(); pts.forEach(([a, b], i) => (i ? c.lineTo(a, b + k * 26) : c.moveTo(a, b + k * 26))); c.stroke(); }
|
|
339
|
+
keyline(c, ridge, 2.4); c.restore();
|
|
340
|
+
c.strokeStyle = C.key; c.lineWidth = 2; c.beginPath(); c.moveTo(gx, base + 4); c.lineTo(gx + gw, base + 4); c.stroke();
|
|
341
|
+
tanzaku(c, 'training rounds →', gx + gw / 2, base + 20, S(10.8, 11.1));
|
|
342
|
+
const acc = Math.round(H.lerp(61, 97, prog));
|
|
343
|
+
cartouche(c, 'accuracy', x + 990, 148, 30, S(10.7, 11.0));
|
|
344
|
+
cartouche(c, acc + '%', x + 990, 270, 72, S(10.7, 11.0));
|
|
345
|
+
cartouche(c, '61% → 97%', x + 990, 398, 30, S(11.0, 11.3), '#f1e8d2');
|
|
346
|
+
seal(c, x + 1110, 470, 70, '優', S(12.05, 12.2), -0.08); // emphasis: "excellent" seal stamped on 97%
|
|
347
|
+
const rp = S(12.1, 12.45); if (rp > 0) { c.strokeStyle = C.red; c.lineWidth = 5; c.beginPath(); c.ellipse(x + 990, 270, 158, 86, -0.04, -1.5, -1.5 + Math.PI * 1.9 * rp); c.stroke(); }
|
|
348
|
+
}
|
|
349
|
+
// ---------- panel 4: takeaway ----------
|
|
350
|
+
if (vis(4)) {
|
|
351
|
+
const x = PX(4);
|
|
352
|
+
const fuji = new Path2D(); fuji.moveTo(x + 380, 600); fuji.quadraticCurveTo(x + 560, 500, x + 620, 380); fuji.lineTo(x + 660, 378); fuji.quadraticCurveTo(x + 720, 500, x + 900, 600); fuji.closePath(); fillKey(c, fuji, vbok(c, 378, 600, [[0, C.prussD], [0.55, C.fuji], [1, C.light]]));
|
|
353
|
+
fillKey(c, fujiCap(x, 620, 380, 40 / 55), C.foam, 1.4);
|
|
354
|
+
cartouche(c, 'More examples', x + 640, 190, 58, S(12.9, 13.2));
|
|
355
|
+
current(c, [[x + 640, 245], [x + 615, 270], [x + 638, 294]], E.out(S(13.1, 13.4)), true);
|
|
356
|
+
cartouche(c, 'better guesses', x + 640, 365, 58, S(13.2, 13.5));
|
|
357
|
+
seal(c, x + 900, 440, 70, '学', S(13.5, 13.65));
|
|
358
|
+
}
|
|
359
|
+
// ---------- kasumi clouds divide the scenes ----------
|
|
360
|
+
for (let k = 0; k <= 4; k++) { const bx = PX(k) + (k === 0 ? W : 0); if (bx < camX - 200 || bx > camX + W + 200) continue; kasumi(c, bx, 120, 560); }
|
|
361
|
+
// mist bands top and bottom edge of the scroll
|
|
362
|
+
c.fillStyle = vbok(c, 0, 30, [[0, '#cfac55'], [0.55, 'rgba(214,184,104,0.85)'], [1, 'rgba(220,194,122,0)']]); c.fillRect(camX - 10, 0, W + 20, 30);
|
|
363
|
+
c.fillStyle = vbok(c, HH - 28, HH, [[0, 'rgba(220,194,122,0)'], [0.45, 'rgba(214,184,104,0.85)'], [1, '#cfac55']]); c.fillRect(camX - 10, HH - 28, W + 20, 28);
|
|
364
|
+
c.restore();
|
|
365
|
+
|
|
366
|
+
// ---------- pinned title cartouche (the hook shrinks into it) ----------
|
|
367
|
+
const hp = E.inOut(S(1.6, 2.3));
|
|
368
|
+
const tx = H.lerp(560, 1030, hp), ty = H.lerp(300, 58, hp), ts = H.lerp(1, 0.5, hp);
|
|
369
|
+
c.save(); c.translate(tx, ty); c.scale(ts, ts);
|
|
370
|
+
if (t > 0.15) { cartouche(c, 'How does AI learn?', 0, 0, 56, S(0.15, 0.5)); cartouche(c, 'AIはどう学ぶのか', 0, 92, 26 * H.lerp(1, 1.35, hp), S(0.5, 0.8), '#f1e8d2'); }
|
|
371
|
+
c.restore();
|
|
372
|
+
if (hp > 0.9) seal(c, 1250, 54, 30, '学', S(2.3, 2.45));
|
|
373
|
+
|
|
374
|
+
// ---------- woodblock finish ----------
|
|
375
|
+
const img = c.getImageData(0, 0, W, HH), d = img.data, { warp, fine, pap, blot } = fields(W, HH), rg = H.rng(2);
|
|
376
|
+
for (let y = 0, p = 0; y < HH; y++) for (let x = 0; x < W; x++, p++) {
|
|
377
|
+
const i = p * 4, lum = (d[i] * 0.3 + d[i + 1] * 0.55 + d[i + 2] * 0.15) / 255, inked = H.clamp((0.85 - lum) * 1.6, 0, 1), plate = H.clamp((0.94 - lum) * 1.3, 0, 1);
|
|
378
|
+
const grain = Math.sin(y * 0.55 + warp[p] * 26 + x * 0.004) * 0.5 + fine[p] * 0.8;
|
|
379
|
+
// wood grain and uneven take-up show in every printed block, pale plates included
|
|
380
|
+
let s = 1 + grain * (0.07 + 0.07 * inked) * plate; s *= 1 + pap[p] * 0.035 + (rg() - 0.5) * 0.04; s *= 1 - Math.max(0, blot[p]) * 0.12 * plate;
|
|
381
|
+
d[i] = d[i] * s + 6 * (1 - inked); d[i + 1] = d[i + 1] * s; d[i + 2] = d[i + 2] * s * 0.97 - 4 * (1 - inked);
|
|
382
|
+
}
|
|
383
|
+
c.putImageData(img, 0, 0);
|
|
384
|
+
c.drawImage(fibres(W, HH), 0, 0);
|
|
385
|
+
H.vignette(c, W, HH, 0.25, '90,60,30');
|
|
386
|
+
};
|
|
387
|
+
|
|
388
|
+
registerStyle({
|
|
389
|
+
id: 'ukiyo-e',
|
|
390
|
+
name: 'Ukiyo-e Woodblock',
|
|
391
|
+
category: 'Art & Design Movements',
|
|
392
|
+
description: 'An Edo-period woodblock print in the manner of Hokusai: Prussian-blue bokashi gradients, a towering wave with claw-like foam whose spray breaks into data cubes, a cargo ship stacked with server racks in the trough, flat colour blocks with carved keyblock outlines, wood grain and washi fibre, a vertical-text cartouche and vermilion seal.',
|
|
393
|
+
motion: 'Printed like a real woodblock: the black keyblock lands first, then each colour block is pressed in turn (sky ochre, pale blue, mid blue, vermilion, Prussian) and the bokashi sky is wiped on last, while the wave swells, its claws lean and data-cube spray tumbles onto the rolling ship.',
|
|
394
|
+
fonts: ['700 26px "Noto Serif JP"', '400 15px "Noto Serif JP"', '900 22px "Noto Serif JP"'],
|
|
395
|
+
frameT: 5.2,
|
|
396
|
+
duration: 5.5,
|
|
397
|
+
readsPixels: true,
|
|
398
|
+
explainerDuration: 14.5,
|
|
399
|
+
explainerNote: 'An emaki handscroll unrolled right-to-left: golden kasumi clouds divide the scenes, a river of labelled prints floats to an Edo karakuri automaton, flow is drawn as currents with claw-foam heads, the learning loop is a Naruto whirlpool, numbers sit in paper cartouches and every judgement is a vermilion seal stamp.',
|
|
400
|
+
explainer(ctx, W, HH, t) { explainUkiyo(ctx, W, HH, t); },
|
|
401
|
+
// Sound: an emaki handscroll and a woodblock workshop. The scroll unrolls between scenes (paper over a wooden
|
|
402
|
+
// roller), water laps under the whole scroll, currents swish as they flow, counters are a carving chisel, the
|
|
403
|
+
// karakuri's wooden gears ratchet, every judgement is a vermilion seal pressed down, and a koto answers each
|
|
404
|
+
// guess (unresolved for "dog?", bent down for wrong, resolving for right). A taiko lands the 97%.
|
|
405
|
+
// Times copied from explainUkiyo().
|
|
406
|
+
explainerSound(k) {
|
|
407
|
+
const D = 14.5, E = H.ease, cl = H.clamp;
|
|
408
|
+
const S = (t, a, b) => cl((t - a) / (b - a), 0, 1);
|
|
409
|
+
const pan = (px) => cl((px / 1280) * 2 - 1, -1, 1) * 0.7;
|
|
410
|
+
const when = (a, b, ease, u) => { for (let i = 0; i <= 400; i++) if (ease(i / 400) >= u - 1e-6) return a + ((b - a) * i) / 400; return b; };
|
|
411
|
+
const card = (a, b) => a + (b - a) / 1.5; // cartouches fade in fully at 2/3 of their S(a,b)
|
|
412
|
+
// koto pluck: bright attack that darkens, a tiny pitch settle
|
|
413
|
+
const koto = (t, n, g, p, minor, bend) => { const f = k.hz(n);
|
|
414
|
+
k.tone(t, { f: bend ? [[0, f], [0.08, f], [0.5, f * 0.89]] : [[0, f * 1.012], [0.05, f]], type: 'sawtooth', dur: 1.3, gain: 0.05 * g, a: 0.002, filter: 'lowpass', f2: [[0, 2600], [0.25, 900], [1.3, 500]], q: 1.5, pan: p, verb: 0.3, minor });
|
|
415
|
+
k.tone(t, { f, type: 'triangle', dur: 1.0, gain: 0.06 * g, a: 0.002, pan: p, minor }); };
|
|
416
|
+
// seal pressed into the paper: a firm soft thump, the paper giving a little
|
|
417
|
+
const sealP = (t, g, p, minor) => { k.play('stamp', t, { gain: 0.6 * g, pan: p, minor }); k.noise(t + 0.01, { dur: 0.12, color: 'pink', filter: 'bandpass', f: 1100, q: 0.7, gain: 0.08 * g, a: 0.01, pan: p, minor }); };
|
|
418
|
+
// carving chisel knocking out a numeral
|
|
419
|
+
const chisel = (t, g, p) => k.withMinor(() => { k.noise(t, { dur: 0.03, filter: 'bandpass', f: 1600, q: 2, gain: 0.3 * g, a: 0.001, pan: p }); k.tone(t, { f: 240, dur: 0.05, gain: 0.08 * g, pan: p }); });
|
|
420
|
+
// hyoshigi: two dry wooden clappers struck once
|
|
421
|
+
const clack = (t, g, p) => { k.noise(t, { dur: 0.06, filter: 'bandpass', f: 1250, q: 6, gain: 0.5 * g, a: 0.001, pan: p }); k.noise(t, { dur: 0.015, filter: 'highpass', f: 2500, gain: 0.15 * g, pan: p }); };
|
|
422
|
+
// a current of water flowing along
|
|
423
|
+
const swish = (t, dur, g, p) => k.noise(t, { dur: dur + 0.15, color: 'pink', filter: 'bandpass', f: [[0, 400], [dur * 0.6, 1100], [dur + 0.15, 500]], q: 1.1, gain: 0.08 * g, a: dur * 0.5, pan: p, minor: true });
|
|
424
|
+
// the scroll unrolls: paper sliding off the wooden roller
|
|
425
|
+
const unroll = (t, dur) => {
|
|
426
|
+
k.noise(t, { dur, color: 'brown', filter: 'bandpass', f: [[0, 160], [dur * 0.5, 300], [dur, 160]], q: 1, gain: 0.18, a: dur * 0.4 });
|
|
427
|
+
k.play('paper', t + 0.2, { dur: dur - 0.3, gain: 0.75 });
|
|
428
|
+
};
|
|
429
|
+
const IN = ['D4', 'Eb4', 'G4', 'A4', 'Bb4', 'D5'];
|
|
430
|
+
|
|
431
|
+
k.bed('room', 0, D, { gain: 0.3 });
|
|
432
|
+
k.noise(0, { dur: D, color: 'pink', filter: 'lowpass', f: 450, gain: 0.035, a: 1, hold: D - 2, minor: true }); // water under the scroll
|
|
433
|
+
|
|
434
|
+
// ---- panel 0: the sun rises (0.1-1.4), a current flows (0.4-1.6), title cartouches fade in, seal (2.45)
|
|
435
|
+
swish(0.4, 1.2, 1, 0.3);
|
|
436
|
+
koto(card(0.15, 0.5), 'D4', 1, -0.2); koto(card(0.15, 0.5) + 0.12, 'A4', 0.8, -0.1);
|
|
437
|
+
koto(card(0.5, 0.8), 'G4', 0.8, 0.1, true); koto(card(0.5, 0.8) + 0.16, 'D5', 0.6, 0.2, true);
|
|
438
|
+
k.noise(1.6, { dur: 0.7, color: 'pink', filter: 'bandpass', f: [[0, 400], [0.7, 900]], q: 0.7, gain: 0.06, a: 0.3, pan: 0.5, minor: true }); // title slides to the corner
|
|
439
|
+
sealP(2.45, 0.5, 0.9, true);
|
|
440
|
+
|
|
441
|
+
// ---- the scroll unrolls between scenes
|
|
442
|
+
[[2.0, 2.9], [5.7, 6.5], [10.1, 10.8], [12.5, 13.2]].forEach(([a, b]) => unroll(a, b - a));
|
|
443
|
+
|
|
444
|
+
// ---- panel 1: DATA — a river of prints floats to the automaton
|
|
445
|
+
swish(2.6, 0.7, 1, 0.2); // river current
|
|
446
|
+
const boats = []; for (let i = 0; 2.8 + i * 0.6 < 5.6; i++) boats.push(2.8 + i * 0.6);
|
|
447
|
+
boats.forEach((tb) => k.noise(tb, { dur: 0.22, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.22, 300]], q: 1.4, gain: 0.12, a: 0.03, pan: 0.6, minor: true })); // each boat slides in on a lap of water
|
|
448
|
+
k.play('paper', card(2.9, 3.2), { dur: 0.12, gain: 0.25, pan: pan(830), minor: true }); // cartouche
|
|
449
|
+
// counter n = S(3.0,5.4)^3 * 1e6: the chisel re-cuts the numerals, faster as they climb
|
|
450
|
+
for (let t = 3.12; t < 5.32; ) { const s = S(t, 3.0, 5.4); if (!boats.some((b) => t > b - 0.04 && t < b + 0.06)) chisel(t, 0.7 + 0.4 * s, pan(830)); t += 0.16 - 0.075 * s; }
|
|
451
|
+
clack(5.4, 1, pan(830)); k.play('thud', 5.4, { f: 120, gain: 0.3, pan: pan(830) }); // 1,000,000
|
|
452
|
+
|
|
453
|
+
// ---- panel 2: GUESS / CHECK / ADJUST — the whirlpool
|
|
454
|
+
k.noise(6.6, { dur: 0.8, color: 'pink', filter: 'bandpass', f: [[0, 300], [0.35, 900], [0.8, 500]], q: 2, gain: 0.12, a: 0.3, pan: 0.4 }); // whirlpool swirls open
|
|
455
|
+
// the guess: "dog?" (6.9-7.1) — an unresolved koto pair
|
|
456
|
+
koto(card(6.9, 7.1), 'G4', 1, pan(560)); koto(card(6.9, 7.1) + 0.14, 'Eb4', 0.9, pan(560));
|
|
457
|
+
swish(7.2, 0.3, 0.8, 0.5);
|
|
458
|
+
// CHECK: the X seal comes down (7.45 -> 7.6), red ring brushed round (7.55-7.95) — wrong
|
|
459
|
+
sealP(7.6, 1.2, pan(675));
|
|
460
|
+
k.play('thud', 7.6, { f: 75, gain: 0.5, pan: pan(675) });
|
|
461
|
+
koto(7.64, 'Bb3', 1.1, pan(600), false, true); koto(7.64, 'Eb4', 0.8, pan(600), false, true);
|
|
462
|
+
k.play('stroke', 7.62, { material: 'brush', dur: 0.36, gain: 0.7, pan: pan(605), minor: true });
|
|
463
|
+
// ADJUST: the red thread runs back to the gear cabinet (7.9-8.5), "error" cartouche lands, gears ratchet (inOut 8.3-9.1)
|
|
464
|
+
k.play('stroke', 7.92, { material: 'brush', dur: 0.55, gain: 0.8, pan: pan(450), minor: true });
|
|
465
|
+
swish(7.9, 0.3, 0.7, 0.5);
|
|
466
|
+
clack(8.5, 0.5, pan(470));
|
|
467
|
+
for (let i = 1, lt = 8.5; i <= 9; i++) { const t = when(8.3, 9.1, E.inOut, i / 9); if (t - lt < 0.09) continue; lt = t; k.play('tock', t, { f: 380 + (i % 2) * 60, gain: 0.6, pan: pan(200), minor: true }); }
|
|
468
|
+
swish(9.0, 0.3, 0.8, 0.5);
|
|
469
|
+
// the correction: "cat" (9.35-9.55), the tick seal comes down (9.8) — the koto resolves
|
|
470
|
+
koto(card(9.35, 9.55), 'D4', 0.9, pan(560));
|
|
471
|
+
sealP(9.8, 0.9, pan(672));
|
|
472
|
+
['G4', 'A4', 'D5'].forEach((n, i) => koto(9.82 + i * 0.1, n, 0.9 - i * 0.1, pan(672) - 0.1 * i));
|
|
473
|
+
|
|
474
|
+
// ---- panel 3: PROGRESS — the ridge is printed in as accuracy climbs (inOut 10.8-12.0)
|
|
475
|
+
// baren rubbing the ridge print as it appears
|
|
476
|
+
for (let x = 0; x < 1.15; x += 0.19) k.noise(10.82 + x, { dur: 0.2, color: 'pink', filter: 'bandpass', f: [[0, 500], [0.1, 800], [0.2, 500]], q: 1, gain: 0.06, a: 0.08, pan: pan(200 + x * 500), minor: true });
|
|
477
|
+
for (let t = 10.85, last = 61, lt = -1; t < 11.97; t += 1 / 120) {
|
|
478
|
+
const v = Math.round(H.lerp(61, 97, E.inOut(S(t, 10.8, 12.0))));
|
|
479
|
+
if (v !== last && t - lt >= 1 / 11) { chisel(t, 0.9, pan(990)); last = v; lt = t; }
|
|
480
|
+
}
|
|
481
|
+
// 97%: the 優 seal comes down (12.05 -> 12.2) and the ring is brushed round (12.1-12.45) — the biggest hit, a taiko
|
|
482
|
+
k.play('impact', 12.2, { gain: 1.3, pan: pan(1110) });
|
|
483
|
+
k.play('thud', 12.2, { f: 62, gain: 0.9 });
|
|
484
|
+
sealP(12.2, 1.2, pan(1110));
|
|
485
|
+
['D3', 'A3', 'D4', 'G4'].forEach((n, i) => koto(12.22 + i * 0.03, n, 1.0, (i - 1.5) * 0.2));
|
|
486
|
+
|
|
487
|
+
// ---- panel 4: TAKEAWAY: More examples (13.1), current, better guesses (13.4), the 学 seal (13.65)
|
|
488
|
+
koto(card(12.9, 13.2), 'A4', 0.7, -0.1, true);
|
|
489
|
+
swish(13.1, 0.3, 0.7, 0);
|
|
490
|
+
koto(card(13.2, 13.5), 'G4', 0.7, 0.1, true);
|
|
491
|
+
sealP(13.65, 0.9, pan(900));
|
|
492
|
+
['D3', 'A3', 'D4', 'Eb4', 'G4'].forEach((n, i) => koto(13.68 + i * 0.07, n, 0.85 - i * 0.08, (i - 2) * 0.2)); // resolves
|
|
493
|
+
},
|
|
494
|
+
draw(c0, W, HH, t) {
|
|
495
|
+
const swell = Math.sin(t * 0.9) * 6;
|
|
496
|
+
const scene = (c) => {
|
|
497
|
+
// ---------- paper ----------
|
|
498
|
+
c.fillStyle = C.paper; c.fillRect(0, 0, W, HH);
|
|
499
|
+
|
|
500
|
+
// ---------- sky: bokashi from the top ----------
|
|
501
|
+
c.fillStyle = C.sky; c.fillRect(0, 0, W, 560);
|
|
502
|
+
const sk = c.createLinearGradient(0, 0, 0, 230);
|
|
503
|
+
sk.addColorStop(0, 'rgba(46,62,84,0.92)'); sk.addColorStop(0.45, 'rgba(70,86,104,0.45)'); sk.addColorStop(1, 'rgba(90,100,110,0)');
|
|
504
|
+
c.fillStyle = sk; c.fillRect(0, 0, W, 240); // bokashi (separated out and printed last in the build)
|
|
505
|
+
// flat cloud bands
|
|
506
|
+
c.fillStyle = 'rgba(236,224,196,0.85)';
|
|
507
|
+
const cloud = (x, y, w) => { const p = new Path2D(); p.moveTo(x, y); p.bezierCurveTo(x + w * 0.2, y - 18, x + w * 0.45, y - 22, x + w * 0.6, y - 8); p.bezierCurveTo(x + w * 0.75, y - 20, x + w * 0.95, y - 10, x + w, y); p.closePath(); c.fill(p); keyline(c, p, 1.2, 'rgba(27,34,54,0.6)'); };
|
|
508
|
+
cloud(700, 300, 260); cloud(980, 250, 200);
|
|
509
|
+
|
|
510
|
+
// ---------- Mt Fuji (far) ----------
|
|
511
|
+
const fx = 940, fb = 520;
|
|
512
|
+
const fuji = new Path2D();
|
|
513
|
+
fuji.moveTo(fx - 170, fb); fuji.quadraticCurveTo(fx - 70, fb - 40, fx - 22, fb - 104); fuji.lineTo(fx + 22, fb - 106); fuji.quadraticCurveTo(fx + 70, fb - 40, fx + 170, fb); fuji.closePath();
|
|
514
|
+
c.save(); c.translate(1.5, 1); c.fillStyle = C.fuji; c.fill(fuji); c.restore();
|
|
515
|
+
const cap = new Path2D(); cap.moveTo(fx - 22, fb - 104); cap.lineTo(fx + 22, fb - 106); cap.lineTo(fx + 48, fb - 70);
|
|
516
|
+
[[34, -78], [22, -64], [10, -76], [-4, -60], [-16, -74], [-30, -62], [-44, -72]].forEach(([dx, dy]) => cap.lineTo(fx + dx, fb + dy));
|
|
517
|
+
cap.closePath(); c.fillStyle = C.foam; c.fill(cap); keyline(c, cap, 1.2);
|
|
518
|
+
keyline(c, fuji, 1.6);
|
|
519
|
+
|
|
520
|
+
// ---------- distant sea ----------
|
|
521
|
+
const sea = c.createLinearGradient(0, 500, 0, 720);
|
|
522
|
+
sea.addColorStop(0, C.mid); sea.addColorStop(1, C.pruss);
|
|
523
|
+
c.fillStyle = sea; c.fillRect(0, 510, W, 210);
|
|
524
|
+
c.strokeStyle = 'rgba(241,232,210,0.7)'; c.lineWidth = 1.5;
|
|
525
|
+
for (let i = 0; i < 9; i++) { const y = 528 + i * 18; c.beginPath(); for (let x = 600; x < W; x += 6) { const yy = y + Math.sin(x * 0.03 + i + t) * 3; x === 600 ? c.moveTo(x, yy) : c.lineTo(x, yy); } c.stroke(); }
|
|
526
|
+
|
|
527
|
+
// ---------- secondary wave (right foreground, a small Fuji-shaped crest) ----------
|
|
528
|
+
{
|
|
529
|
+
const pts = sample([[760, 720], [820, 640], [920, 590 + swell * 0.5], [1010, 576], [1080, 600], [1100, 630], [1080, 640]]);
|
|
530
|
+
const back = [...pts, [1150, 650], [1300, 640], [1300, 720]];
|
|
531
|
+
const body = pathOf(back, true);
|
|
532
|
+
const g = c.createLinearGradient(0, 580, 0, 720); g.addColorStop(0, C.mid); g.addColorStop(1, C.prussD);
|
|
533
|
+
c.save(); c.translate(1.5, 1); c.fillStyle = g; c.fill(body); c.restore();
|
|
534
|
+
const nn = normals(pts);
|
|
535
|
+
c.save(); c.clip(body);
|
|
536
|
+
veins(c, pts, nn, { count: 6, d0: 14, step: 14, sign: -1, seed: 21, band: 'rgba(147,180,204,0.4)', line: 'rgba(241,232,210,0.9)', from: [0.02, 0.25], to: [0.8, 0.98], w0: 1, w1: 2.4 });
|
|
537
|
+
c.restore();
|
|
538
|
+
keyline(c, body, 2.2);
|
|
539
|
+
const r = H.rng(5);
|
|
540
|
+
for (let i = 20; i < pts.length - 2; i += 5) { const n = nn[i]; claw(c, pts[i][0], pts[i][1], n.nx, n.ny, n.tx, n.ty, 20 + r.range(0, 6), r); }
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
// ---------- the cargo ship of servers (in the trough, under the lip) ----------
|
|
544
|
+
c.save(); c.translate(560, 520 + swell * 0.6); c.rotate(-0.17 + Math.sin(t * 0.9 + 1) * 0.02);
|
|
545
|
+
// container stacks = server racks
|
|
546
|
+
const rk = H.rng(9);
|
|
547
|
+
for (let s = 0; s < 6; s++) {
|
|
548
|
+
const x0 = -170 + s * 52, hgt = rk.int(2, 4);
|
|
549
|
+
for (let k = 0; k < hgt; k++) {
|
|
550
|
+
const p = new Path2D(); p.rect(x0, -28 - k * 26, 46, 24);
|
|
551
|
+
c.fillStyle = [C.grey, C.ochre, C.pale, C.grey][(s + k) % 4]; c.fill(p);
|
|
552
|
+
c.fillStyle = C.key; for (let u = 0; u < 3; u++) c.fillRect(x0 + 5, -28 - k * 26 + 5 + u * 6, 26, 1.6);
|
|
553
|
+
c.fillStyle = (s + k) % 3 ? C.red : C.green; c.fillRect(x0 + 36, -28 - k * 26 + 6, 4, 4);
|
|
554
|
+
keyline(c, p, 1.5);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
// bridge
|
|
558
|
+
const br = new Path2D(); br.rect(150, -78, 44, 52); c.fillStyle = C.paper; c.fill(br); keyline(c, br, 1.5);
|
|
559
|
+
c.fillStyle = C.key; for (let u = 0; u < 3; u++) c.fillRect(156 + u * 13, -70, 8, 8);
|
|
560
|
+
const st = new Path2D(); st.rect(164, -104, 16, 26); c.fillStyle = C.red; c.fill(st); keyline(c, st, 1.4);
|
|
561
|
+
// hull
|
|
562
|
+
const hull = new Path2D(); hull.moveTo(-230, -30); hull.lineTo(230, -30); hull.lineTo(205, 22); hull.lineTo(-190, 22); hull.closePath();
|
|
563
|
+
c.save(); c.translate(1.5, 1); c.fillStyle = C.hull; c.fill(hull); c.restore();
|
|
564
|
+
c.fillStyle = C.ochre; c.fillRect(-222, -22, 446, 6);
|
|
565
|
+
keyline(c, hull, 2);
|
|
566
|
+
c.restore();
|
|
567
|
+
|
|
568
|
+
// ---------- main great wave ----------
|
|
569
|
+
const lean = Math.sin(t * 0.9) * 8;
|
|
570
|
+
const backPts = [[-40, 600], [10, 440], [80, 310], [175, 205], [290, 132], [405, 98 + lean * 0.3], [510, 108], [598, 146 + lean * 0.5], [650, 205 + lean], [655, 262 + lean], [625, 300 + lean], [585, 306 + lean]];
|
|
571
|
+
const back = sample(backPts, 16);
|
|
572
|
+
const facePts = sample([[585, 306 + lean], [548, 284 + lean * 0.8], [505, 268], [470, 285], [452, 340], [462, 420], [500, 500], [560, 560], [650, 600], [760, 622], [800, 720]], 12);
|
|
573
|
+
const body = pathOf([...back, ...facePts.slice(1), [-40, 720]], true);
|
|
574
|
+
const bn = normals(back);
|
|
575
|
+
// body fill with depth gradient
|
|
576
|
+
const g = c.createLinearGradient(0, 90, 300, 640);
|
|
577
|
+
g.addColorStop(0, C.pruss); g.addColorStop(0.5, C.prussD); g.addColorStop(1, '#10243c');
|
|
578
|
+
c.save(); c.translate(2, 1.5); c.fillStyle = g; c.fill(body); c.restore();
|
|
579
|
+
// lighter face inside the curl (bokashi from the face edge inward)
|
|
580
|
+
c.save(); c.clip(body);
|
|
581
|
+
const fg = c.createRadialGradient(560, 330, 20, 560, 330, 230);
|
|
582
|
+
fg.addColorStop(0, C.light); fg.addColorStop(0.55, 'rgba(77,124,166,0.85)'); fg.addColorStop(1, 'rgba(36,80,126,0)');
|
|
583
|
+
c.fillStyle = fg; c.fillRect(300, 100, 500, 560);
|
|
584
|
+
// striations following the back of the wave: lighter bands + broken white veins
|
|
585
|
+
veins(c, back, bn, { count: 13, d0: 30, step: 19, sign: -1, seed: 12, band: 'rgba(77,124,166,0.55)', line: 'rgba(241,232,210,0.92)', from: [0.08, 0.4], to: [0.92, 0.99], toK: 0.04, w0: 1.2, w1: 3.0 });
|
|
586
|
+
// face veins (parallel to the hollow)
|
|
587
|
+
const fnn = normals(facePts);
|
|
588
|
+
veins(c, facePts, fnn, { count: 4, d0: 12, step: 15, sign: 1, seed: 13, band: null, line: 'rgba(241,232,210,0.7)', from: [0.1, 0.18], to: [0.5, 0.7], w0: 2.2, w1: 0.8 });
|
|
589
|
+
c.restore();
|
|
590
|
+
// foam crest band
|
|
591
|
+
const crestStart = Math.floor(back.length * 0.32);
|
|
592
|
+
const band = [];
|
|
593
|
+
for (let i = crestStart; i < back.length; i++) band.push(back[i]);
|
|
594
|
+
for (let i = back.length - 1; i >= crestStart; i--) { const w = 26 * Math.min(1, (i - crestStart) / 18) + Math.abs(Math.sin(i * 0.45)) * 7; band.push([back[i][0] - bn[i].nx * w, back[i][1] - bn[i].ny * w]); }
|
|
595
|
+
const bandP = pathOf(band, true);
|
|
596
|
+
c.fillStyle = C.foam; c.fill(bandP);
|
|
597
|
+
// foam band inner edge carved as a keyline with little scalloped bumps
|
|
598
|
+
{ const edge = new Path2D(); let first = true;
|
|
599
|
+
for (let i = crestStart; i < back.length; i++) { const w = 26 * Math.min(1, (i - crestStart) / 18) + Math.abs(Math.sin(i * 0.45)) * 7; const x = back[i][0] - bn[i].nx * w, y = back[i][1] - bn[i].ny * w; first ? edge.moveTo(x, y) : edge.lineTo(x, y); first = false; }
|
|
600
|
+
c.save(); c.clip(body); c.strokeStyle = C.key; c.lineWidth = 1.6; c.stroke(edge); c.restore(); }
|
|
601
|
+
keyline(c, body, 2.6);
|
|
602
|
+
// claws along the crest and lip
|
|
603
|
+
const rc = H.rng(31);
|
|
604
|
+
for (let i = crestStart; i < back.length - 1; i += 7) {
|
|
605
|
+
const n = bn[i], u = (i - crestStart) / (back.length - crestStart);
|
|
606
|
+
const L = H.lerp(26, 52, Math.min(1, u * 1.6)) * rc.range(0.85, 1.1);
|
|
607
|
+
claw(c, back[i][0], back[i][1], n.nx, n.ny, n.tx, n.ty, L, rc);
|
|
608
|
+
}
|
|
609
|
+
// lip-tip claws hanging down over the ship
|
|
610
|
+
for (let k = 0; k < 6; k++) {
|
|
611
|
+
const i = back.length - 1, x = back[i][0] - k * 22 + 8, y = back[i][1] - 4 + Math.sin(k) * 4;
|
|
612
|
+
claw(c, x, y, -0.15, 1, -1, 0.2, 30 + rc.range(-4, 10), rc);
|
|
613
|
+
}
|
|
614
|
+
|
|
615
|
+
// ---------- data-cube spray ----------
|
|
616
|
+
const rs = H.rng(77);
|
|
617
|
+
for (let i = 0; i < 46; i++) {
|
|
618
|
+
const u = rs();
|
|
619
|
+
const sx = H.lerp(300, 820, u) + rs.gauss() * 30;
|
|
620
|
+
const sy0 = H.lerp(70, 190, Math.abs(u - 0.6)) + rs.gauss() * 40;
|
|
621
|
+
const fall = ((t * 0.25 + rs()) % 1) * 60;
|
|
622
|
+
const s = rs.range(5, 14) * (1 - Math.abs(u - 0.55) * 0.6);
|
|
623
|
+
const y = sy0 + fall + (u > 0.6 ? (u - 0.6) * 380 : 0);
|
|
624
|
+
if (y > 470) continue;
|
|
625
|
+
cube(c, sx, y, s, rs.range(-0.4, 0.4) + t * rs.range(-0.5, 0.5));
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// ---------- foreground water in front of the hull ----------
|
|
629
|
+
{
|
|
630
|
+
const pts = sample([[420, 720], [480, 668], [580, 638 + swell * 0.4], [672, 612], [732, 618], [762, 644], [748, 662]], 12);
|
|
631
|
+
const p = pathOf([...pts, [760, 720]], true);
|
|
632
|
+
const gg = c.createLinearGradient(0, 570, 0, 720); gg.addColorStop(0, C.pruss); gg.addColorStop(1, C.prussD);
|
|
633
|
+
c.save(); c.translate(1.5, 1); c.fillStyle = gg; c.fill(p); c.restore();
|
|
634
|
+
const nn = normals(pts);
|
|
635
|
+
c.save(); c.clip(p);
|
|
636
|
+
veins(c, pts, nn, { count: 5, d0: 12, step: 13, sign: -1, seed: 22, band: 'rgba(147,180,204,0.4)', line: 'rgba(241,232,210,0.88)', from: [0.0, 0.2], to: [0.75, 0.95], w0: 1, w1: 2.2 });
|
|
637
|
+
c.restore();
|
|
638
|
+
keyline(c, p, 2.2);
|
|
639
|
+
const r = H.rng(8);
|
|
640
|
+
for (let i = 10; i < pts.length - 1; i += 6) { const n = nn[i]; claw(c, pts[i][0], pts[i][1], n.nx, n.ny, n.tx, n.ty, 16 + r.range(0, 6), r); }
|
|
641
|
+
}
|
|
642
|
+
|
|
643
|
+
// ---------- cartouche + signature + seal ----------
|
|
644
|
+
const cx = 1150, cy = 40, cw = 52, ch = 228;
|
|
645
|
+
const cart = new Path2D(); cart.rect(cx, cy, cw, ch);
|
|
646
|
+
c.fillStyle = '#e8d49a'; c.fill(cart);
|
|
647
|
+
c.strokeStyle = C.key; c.lineWidth = 2; c.stroke(cart); c.lineWidth = 1; c.strokeRect(cx + 4, cy + 4, cw - 8, ch - 8);
|
|
648
|
+
c.fillStyle = C.key; c.font = '700 26px "Noto Serif JP"'; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
649
|
+
[...'データの大波'].forEach((chr, i) => { if (chr === 'ー') { c.save(); c.translate(cx + cw / 2, cy + 30 + i * 33); c.rotate(Math.PI / 2); c.fillText(chr, 0, 0); c.restore(); } else c.fillText(chr, cx + cw / 2, cy + 30 + i * 33); });
|
|
650
|
+
// second, smaller cartouche with series title
|
|
651
|
+
const c2 = new Path2D(); c2.rect(cx - 40, cy, 30, 120); c.fillStyle = '#d9c38a'; c.fill(c2); c.strokeStyle = C.key; c.lineWidth = 1.5; c.stroke(c2);
|
|
652
|
+
c.fillStyle = C.key; c.font = '400 15px "Noto Serif JP"';
|
|
653
|
+
[...'電脳百景'].forEach((chr, i) => c.fillText(chr, cx - 25, cy + 20 + i * 26));
|
|
654
|
+
// signature + seal (lower left of cartouche area)
|
|
655
|
+
c.font = '400 15px "Noto Serif JP"';
|
|
656
|
+
[...'雲斎筆'].forEach((chr, i) => c.fillText(chr, cx - 25, cy + 160 + i * 20));
|
|
657
|
+
c.save(); c.translate(cx - 25, cy + 236); c.rotate(0.02);
|
|
658
|
+
c.fillStyle = C.red; c.fillRect(-15, -15, 30, 30);
|
|
659
|
+
c.fillStyle = '#efe2c6'; c.font = '900 22px "Noto Serif JP"'; c.fillText('電', 0, 1);
|
|
660
|
+
c.restore();
|
|
661
|
+
|
|
662
|
+
};
|
|
663
|
+
|
|
664
|
+
// ---------- printing build: keyblock first, then one colour block per impression, bokashi last ----------
|
|
665
|
+
const COL = H.layer(W, HH), cc = COL.getContext('2d', { willReadFrequently: true });
|
|
666
|
+
scene(cc);
|
|
667
|
+
const KEY = H.layer(W, HH), kc = KEY.getContext('2d', { willReadFrequently: true });
|
|
668
|
+
keyOnly(kc); scene(kc);
|
|
669
|
+
const press = (t0) => H.clamp((t - t0) / 0.16, 0, 1); // an impression lands in ~5 frames
|
|
670
|
+
// plates: 0 key, 1 warm (sky/ochre), 2 pale blue, 3 mid blue, 4 vermilion/brown, 5 Prussian
|
|
671
|
+
const prog = [press(0.35), press(1.0), press(1.5), press(2.0), press(2.45), press(2.9)];
|
|
672
|
+
const cd = cc.getImageData(0, 0, W, HH).data, kd = kc.getImageData(0, 0, W, HH).data;
|
|
673
|
+
const out = c0.createImageData(W, HH), od = out.data;
|
|
674
|
+
const PA = H.hex(C.paper), SKY = H.hex(C.sky), SL = [46, 62, 84], BL = Math.hypot(SKY[0] - SL[0], SKY[1] - SL[1], SKY[2] - SL[2]), BD = [(SKY[0] - SL[0]) / BL, (SKY[1] - SL[1]) / BL, (SKY[2] - SL[2]) / BL];
|
|
675
|
+
// shifting registration: each fresh impression lands a couple of px off, then the printer's kento corrects it
|
|
676
|
+
for (let y = 0, p = 0; y < HH; y++) for (let x = 0; x < W; x++, p++) {
|
|
677
|
+
const i = p * 4, r = cd[i], g = cd[i + 1], b = cd[i + 2];
|
|
678
|
+
let pl;
|
|
679
|
+
if (kd[i + 3] > 110) pl = 0;
|
|
680
|
+
else if (Math.abs(r - PA[0]) + Math.abs(g - PA[1]) + Math.abs(b - PA[2]) < 24 || (r > 228 && g > 220 && b > 196)) pl = -1;
|
|
681
|
+
else if (g < r * 0.6 && r > b) pl = 4;
|
|
682
|
+
else if (r >= b) pl = 1;
|
|
683
|
+
else { const lum = (r * 0.3 + g * 0.55 + b * 0.15) / 255; pl = lum > 0.55 ? 2 : lum > 0.36 ? 3 : 5; }
|
|
684
|
+
let R, G, B;
|
|
685
|
+
// bokashi pixels lie on the sky->slate mixing line: print the sky plate, then wipe the gradation on top-down
|
|
686
|
+
const qr = SKY[0] - r, qg = SKY[1] - g, qb = SKY[2] - b, proj = qr * BD[0] + qg * BD[1] + qb * BD[2], tl = proj / BL;
|
|
687
|
+
const perp = Math.hypot(qr - BD[0] * proj, qg - BD[1] * proj, qb - BD[2] * proj);
|
|
688
|
+
if (pl !== 0 && y < 245 && tl > 0.03 && tl < 0.94 && perp < 16) {
|
|
689
|
+
const kb = H.smooth(0, 1, (t - 3.25 - y * 0.0018) / 0.45), ks = prog[1];
|
|
690
|
+
R = PA[0] + (SKY[0] - PA[0]) * ks + (r - SKY[0]) * kb; G = PA[1] + (SKY[1] - PA[1]) * ks + (g - SKY[1]) * kb; B = PA[2] + (SKY[2] - PA[2]) * ks + (b - SKY[2]) * kb;
|
|
691
|
+
} else {
|
|
692
|
+
const k = pl < 0 ? prog[0] : prog[pl];
|
|
693
|
+
R = PA[0] + (r - PA[0]) * k; G = PA[1] + (g - PA[1]) * k; B = PA[2] + (b - PA[2]) * k;
|
|
694
|
+
}
|
|
695
|
+
od[i] = R; od[i + 1] = G; od[i + 2] = B; od[i + 3] = 255;
|
|
696
|
+
}
|
|
697
|
+
c0.putImageData(out, 0, 0);
|
|
698
|
+
const c = c0;
|
|
699
|
+
// ---------- woodblock finish: wood grain in inked areas, fibres, ageing ----------
|
|
700
|
+
const img = c.getImageData(0, 0, W, HH), d = img.data;
|
|
701
|
+
const { warp, fine, pap, blot } = fields(W, HH);
|
|
702
|
+
const rg = H.rng(2);
|
|
703
|
+
for (let y = 0, p = 0; y < HH; y++) for (let x = 0; x < W; x++, p++) {
|
|
704
|
+
const i = p * 4, lum = (d[i] * 0.3 + d[i + 1] * 0.55 + d[i + 2] * 0.15) / 255;
|
|
705
|
+
const inked = H.clamp((0.85 - lum) * 1.6, 0, 1), plate = H.clamp((0.94 - lum) * 1.3, 0, 1);
|
|
706
|
+
const grain = Math.sin(y * 0.55 + warp[p] * 26 + x * 0.004) * 0.5 + fine[p] * 0.8;
|
|
707
|
+
let s = 1 + grain * (0.06 + 0.08 * inked) * plate; // wood grain shows in every printed block, the pale sky plate too
|
|
708
|
+
s *= 1 + pap[p] * 0.035 + (rg() - 0.5) * 0.04; // washi fibre + tooth
|
|
709
|
+
s *= 1 - Math.max(0, blot[p]) * 0.12 * plate; // uneven pigment take-up
|
|
710
|
+
// age: warm the paper, slightly fade blues
|
|
711
|
+
d[i] = d[i] * s * 1.0 + 6 * (1 - inked); d[i + 1] = d[i + 1] * s; d[i + 2] = d[i + 2] * s * 0.97 - 4 * (1 - inked);
|
|
712
|
+
}
|
|
713
|
+
c.putImageData(img, 0, 0);
|
|
714
|
+
// kozo fibres
|
|
715
|
+
const rf = H.rng(55);
|
|
716
|
+
for (let i = 0; i < 260; i++) {
|
|
717
|
+
const x = rf.range(0, W), y = rf.range(0, HH), l = rf.range(10, 40), a = rf.range(0, Math.PI * 2);
|
|
718
|
+
c.strokeStyle = rf() < 0.5 ? 'rgba(255,250,235,0.22)' : 'rgba(120,95,60,0.12)'; c.lineWidth = rf.range(0.5, 1.1);
|
|
719
|
+
c.beginPath(); c.moveTo(x, y); c.quadraticCurveTo(x + Math.cos(a) * l * 0.5 + rf.range(-6, 6), y + Math.sin(a) * l * 0.5 + rf.range(-6, 6), x + Math.cos(a) * l, y + Math.sin(a) * l); c.stroke();
|
|
720
|
+
}
|
|
721
|
+
H.vignette(c, W, HH, 0.25, '90,60,30');
|
|
722
|
+
},
|
|
723
|
+
});
|
|
724
|
+
}
|