@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,645 @@
|
|
|
1
|
+
{
|
|
2
|
+
// String art: brass-nail lightbulb on a stained walnut board. Threads are straight chords between
|
|
3
|
+
// nails; curves emerge only as envelopes (cardioid filament, chord-ring halo, cross-hatched base).
|
|
4
|
+
const TAU = Math.PI * 2;
|
|
5
|
+
|
|
6
|
+
function woodBoard(ctx, W, H_, t) {
|
|
7
|
+
const img = ctx.createImageData(W, H_), d = img.data;
|
|
8
|
+
const nz = H.makeNoise(23), nz2 = H.makeNoise(91), nz3 = H.makeNoise(57), r = H.rng(4);
|
|
9
|
+
const planks = [0, 236, 486, 720];
|
|
10
|
+
const dark = [50, 27, 14], mid = [116, 70, 39], light = [186, 128, 78];
|
|
11
|
+
const yh = (n) => { const v = Math.sin(n * 12.9898 + 78.233) * 43758.5453; return v - Math.floor(v); };
|
|
12
|
+
for (let p = 0; p < 3; p++) {
|
|
13
|
+
const y0 = planks[p], y1 = planks[p + 1], off = p * 137.3, tone = [0.95, 1.05, 0.9][p];
|
|
14
|
+
for (let y = y0; y < y1; y++) {
|
|
15
|
+
for (let x = 0; x < W; x++) {
|
|
16
|
+
// flat-sawn figure: parabolic "cathedral" arches around a per-plank pith, warped by noise
|
|
17
|
+
const px = [380, 900, 560][p], c = [0.0011, -0.0008, 0.0013][p];
|
|
18
|
+
const wv = nz.n2(x * 0.0014 + off, y * 0.008) * 30 + nz.n2(x * 0.006 + off, y * 0.02) * 2.5 + nz2.n2(x * 0.0007 + off, y * 0.003) * 40;
|
|
19
|
+
let gg = (y - y0 - (y1 - y0) * 0.5) + (x - px) * (x - px) * c + wv;
|
|
20
|
+
// growth rate wanders, so the rings bunch up and spread out instead of marching evenly
|
|
21
|
+
const rate = 0.8 + 0.45 * (nz3.n2(x * 0.0011 + off, y * 0.006) * 0.5 + 0.5);
|
|
22
|
+
gg = Math.abs(gg) * 0.055 * rate + 0.6 * Math.sin(gg * 0.013 + p) + 0.25 * nz2.n2(x * 0.004, y * 0.05);
|
|
23
|
+
const yr = Math.floor(gg), fr = gg - yr, h1 = yh(yr + p * 31), h2 = yh(yr * 1.7 + p * 13 + 5);
|
|
24
|
+
// asymmetric ring: earlywood darkens gradually into a latewood band that ends sharply; width and
|
|
25
|
+
// darkness vary year to year
|
|
26
|
+
const lw = 0.5 + 0.32 * h1;
|
|
27
|
+
let ring = H.smooth(lw - 0.22, 0.95, fr) * (1 - H.smooth(0.965, 1, fr)) * (0.45 + 0.65 * h2);
|
|
28
|
+
const fine = nz2.n2(x * 0.015, y * 0.7 + off) * 0.5 + 0.5; // fine streaks along the grain
|
|
29
|
+
const pore = H.smooth(0.42, 0.72, nz3.n2(x * 0.045 + off, y * 1.25 + off)); // short open-pore flecks
|
|
30
|
+
const fig = nz.n2(x * 0.0012 + 3, y * 0.006 + off) * 0.5 + 0.5;
|
|
31
|
+
const streak = nz3.n2(x * 0.0022 + off, y * 0.03 + 7); // long colour streaks along the plank
|
|
32
|
+
ring *= 0.6 + 0.6 * fig;
|
|
33
|
+
let k = 0.47 + fig * 0.46 + streak * 0.09 - ring * 0.27 - fine * 0.1 - pore * (0.1 + ring * 0.1);
|
|
34
|
+
k = H.clamp(k, 0, 1);
|
|
35
|
+
const c0 = k < 0.5 ? dark : mid, c1 = k < 0.5 ? mid : light, kk = k < 0.5 ? k * 2 : (k - 0.5) * 2;
|
|
36
|
+
const o = (y * W + x) * 4;
|
|
37
|
+
// edge darkening toward plank seams (bevel)
|
|
38
|
+
const e = Math.min(y - y0, y1 - 1 - y);
|
|
39
|
+
const bev = e < 3 ? 0.45 + e * 0.15 : e < 7 ? 0.9 + (e - 3) * 0.025 : 1;
|
|
40
|
+
const sh = tone * bev * (1 + (r() - 0.5) * 0.04);
|
|
41
|
+
d[o] = (c0[0] + (c1[0] - c0[0]) * kk) * sh;
|
|
42
|
+
d[o + 1] = (c0[1] + (c1[1] - c0[1]) * kk) * sh;
|
|
43
|
+
d[o + 2] = (c0[2] + (c1[2] - c0[2]) * kk) * sh;
|
|
44
|
+
d[o + 3] = 255;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
ctx.putImageData(img, 0, 0);
|
|
49
|
+
// satin finish: soft broad sheen from upper left
|
|
50
|
+
const g = ctx.createRadialGradient(330, 120, 40, 330, 120, 900);
|
|
51
|
+
g.addColorStop(0, 'rgba(255,220,170,0.14)'); g.addColorStop(1, 'rgba(255,220,170,0)');
|
|
52
|
+
ctx.fillStyle = g; ctx.fillRect(0, 0, W, H_);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function thread(ctx, segs, col, alpha, width) {
|
|
56
|
+
if (!segs.length) return;
|
|
57
|
+
ctx.save();
|
|
58
|
+
ctx.lineCap = 'round';
|
|
59
|
+
const path = (dx, dy) => { ctx.beginPath(); for (const [a, b] of segs) { ctx.moveTo(a[0] + dx, a[1] + dy); ctx.lineTo(b[0] + dx, b[1] + dy); } };
|
|
60
|
+
// shadow cast onto the board (thread floats ~2mm above the wood, light from top-left): soft penumbra + tight core
|
|
61
|
+
path(2.8, 3.6); ctx.strokeStyle = 'rgba(15,6,2,0.1)'; ctx.lineWidth = width * 3.4; ctx.stroke();
|
|
62
|
+
path(1.6, 2.2); ctx.strokeStyle = 'rgba(15,6,2,0.3)'; ctx.lineWidth = width * 1.5; ctx.stroke();
|
|
63
|
+
// body, with a darker under-edge so pale thread separates from the wood
|
|
64
|
+
path(0.35, 0.45); ctx.strokeStyle = H.rgba(H.mix(col, '#2a1408', 0.55), alpha * 0.5); ctx.lineWidth = width * 1.05; ctx.stroke();
|
|
65
|
+
path(0, 0); ctx.strokeStyle = H.rgba(col, alpha); ctx.lineWidth = width; ctx.stroke();
|
|
66
|
+
// sheen (mercerised cotton): bright thin core on the lit side
|
|
67
|
+
path(-0.3, -0.3); ctx.strokeStyle = H.rgba(H.mix(col, '#ffffff', 0.65), Math.min(1, alpha * 0.65)); ctx.lineWidth = width * 0.38; ctx.stroke();
|
|
68
|
+
ctx.restore();
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function nail(ctx, x, y, r, seed) {
|
|
72
|
+
const rr = H.rng(seed);
|
|
73
|
+
const dx = rr.range(-0.6, 0.6), dy = rr.range(-0.6, 0.6);
|
|
74
|
+
// contact shadow + long soft shadow (light from top-left)
|
|
75
|
+
let g = ctx.createRadialGradient(x + 3.5, y + 4.5, 0, x + 3.5, y + 4.5, r * 2.4);
|
|
76
|
+
g.addColorStop(0, 'rgba(10,4,0,0.55)'); g.addColorStop(1, 'rgba(10,4,0,0)');
|
|
77
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x + 3.5, y + 4.5, r * 2.4, 0, TAU); ctx.fill();
|
|
78
|
+
// head
|
|
79
|
+
g = ctx.createRadialGradient(x - r * 0.4 + dx, y - r * 0.45 + dy, 0.2, x, y, r);
|
|
80
|
+
g.addColorStop(0, '#fff6dc'); g.addColorStop(0.25, '#d8c08a'); g.addColorStop(0.7, '#8c6d3a'); g.addColorStop(1, '#3d2c14');
|
|
81
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, r, 0, TAU); ctx.fill();
|
|
82
|
+
ctx.strokeStyle = 'rgba(30,18,6,0.7)'; ctx.lineWidth = 0.6; ctx.stroke();
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let WOOD = null; // the board never changes: build it once (deterministic), reuse every frame
|
|
86
|
+
// wind(list, p): chords wound so far; the chord currently being wound is drawn partially
|
|
87
|
+
function wind(list, p) {
|
|
88
|
+
if (p <= 0) return [];
|
|
89
|
+
const f = H.clamp(p) * list.length, n = Math.floor(f), out = list.slice(0, n);
|
|
90
|
+
if (n < list.length) { const [a, b] = list[n], k = f - n; out.push([a, [a[0] + (b[0] - a[0]) * k, a[1] + (b[1] - a[1]) * k]]); }
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
const ph = (t, t0, t1) => H.clamp((t - t0) / (t1 - t0));
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
// =====================================================================================
|
|
97
|
+
// EXPLAINER — the neural network IS a string-art board: nails are neurons, threads are
|
|
98
|
+
// connections, and a connection's weight is how many times the thread is wound. Learning
|
|
99
|
+
// = unwinding wrong paths and adding wraps to right ones. The camera travels along one
|
|
100
|
+
// long board; labels are kraft tags nailed on; counters are nail-and-thread 7-segment digits.
|
|
101
|
+
// =====================================================================================
|
|
102
|
+
const AX = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
103
|
+
const BROWN = '#2a1307';
|
|
104
|
+
function woodWorld(ctx, x0, x1) {
|
|
105
|
+
if (!WOOD) { WOOD = H.layer(1280, 720); woodBoard(WOOD.getContext('2d'), 1280, 720, 0); }
|
|
106
|
+
for (let i = Math.floor(x0 / 1280); i <= Math.floor(x1 / 1280); i++) {
|
|
107
|
+
ctx.drawImage(WOOD, i * 1280, 0);
|
|
108
|
+
ctx.fillStyle = 'rgba(20,8,2,0.55)'; ctx.fillRect(i * 1280 - 1.5, 0, 3, 720); // butt joint
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// kraft paper tag nailed at the top, hand-lettered
|
|
112
|
+
function ktag(ctx, text, x, y, o = {}) {
|
|
113
|
+
const size = o.size || 24;
|
|
114
|
+
ctx.save(); ctx.font = `700 ${size}px "Caveat"`;
|
|
115
|
+
const w = Math.max(o.minW || 0, ctx.measureText(text).width + size * 1.0), h = size * 1.5 + 10;
|
|
116
|
+
ctx.translate(x, y); ctx.rotate(o.rot || 0);
|
|
117
|
+
const ox = o.align === 'left' ? 0 : -w / 2;
|
|
118
|
+
ctx.fillStyle = 'rgba(10,4,0,0.35)'; ctx.fillRect(ox + 4, 6, w, h);
|
|
119
|
+
const g = ctx.createLinearGradient(0, 0, 0, h); g.addColorStop(0, o.red ? '#d0503a' : '#d4b27a'); g.addColorStop(1, o.red ? '#b23a28' : '#c09858');
|
|
120
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.moveTo(ox + 8, 0); ctx.lineTo(ox + w - 8, 0); ctx.lineTo(ox + w, 8); ctx.lineTo(ox + w, h); ctx.lineTo(ox, h); ctx.lineTo(ox, 8); ctx.closePath(); ctx.fill();
|
|
121
|
+
ctx.fillStyle = o.red ? '#fff3e0' : BROWN; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(text, ox + w / 2, h / 2 + 5);
|
|
122
|
+
ctx.restore();
|
|
123
|
+
nail(ctx, x + (o.align === 'left' ? 14 : 0), y + 7, 2.8, 900 + (x | 0));
|
|
124
|
+
return w;
|
|
125
|
+
}
|
|
126
|
+
function droppedTag(ctx, t, t0, text, x, y, o) { if (t < t0) return; const k = H.ease.back(AX(t, t0, t0 + 0.28)); ktag(ctx, text, x, y - (1 - k) * 60, { ...o, rot: (o && o.rot || 0) + (1 - k) * 0.25 }); }
|
|
127
|
+
// inked doodle on a tag
|
|
128
|
+
function petTag(ctx, kind, x, y, s, label) {
|
|
129
|
+
ctx.save();
|
|
130
|
+
ctx.fillStyle = 'rgba(10,4,0,0.35)'; ctx.fillRect(x + 4, y + 5, s, s * 1.2);
|
|
131
|
+
ctx.fillStyle = '#ead9b4'; ctx.fillRect(x, y, s, s * 1.2);
|
|
132
|
+
ctx.strokeStyle = BROWN; ctx.lineWidth = Math.max(1.5, s * 0.03); ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
|
133
|
+
const cx = x + s / 2, cy = y + s * 0.5, r = s * 0.25;
|
|
134
|
+
ctx.beginPath(); ctx.arc(cx, cy, r, 0, Math.PI * 2);
|
|
135
|
+
if (kind === 'cat') { ctx.moveTo(cx - r * 0.8, cy - r * 0.5); ctx.lineTo(cx - r * 0.75, cy - r * 1.35); ctx.lineTo(cx - r * 0.2, cy - r * 0.95); ctx.moveTo(cx + r * 0.8, cy - r * 0.5); ctx.lineTo(cx + r * 0.75, cy - r * 1.35); ctx.lineTo(cx + r * 0.2, cy - r * 0.95);
|
|
136
|
+
ctx.moveTo(cx - r * 1.5, cy + r * 0.2); ctx.lineTo(cx - r * 0.5, cy + r * 0.3); ctx.moveTo(cx + r * 1.5, cy + r * 0.2); ctx.lineTo(cx + r * 0.5, cy + r * 0.3); }
|
|
137
|
+
else { ctx.moveTo(cx - r * 0.9, cy - r * 0.6); ctx.quadraticCurveTo(cx - r * 1.6, cy, cx - r * 1.1, cy + r * 0.9); ctx.moveTo(cx + r * 0.9, cy - r * 0.6); ctx.quadraticCurveTo(cx + r * 1.6, cy, cx + r * 1.1, cy + r * 0.9); }
|
|
138
|
+
ctx.stroke();
|
|
139
|
+
ctx.fillStyle = BROWN; ctx.beginPath(); ctx.arc(cx - r * 0.35, cy - r * 0.1, s * 0.025, 0, 7); ctx.arc(cx + r * 0.35, cy - r * 0.1, s * 0.025, 0, 7); ctx.fill();
|
|
140
|
+
if (label) { ctx.font = `700 ${Math.max(28, s * 0.24)}px "Caveat"`; ctx.textAlign = 'center'; ctx.fillText(label, cx, y + s * 1.12); }
|
|
141
|
+
ctx.restore();
|
|
142
|
+
nail(ctx, x + s / 2, y + 6, 2.6, 500 + (x | 0) + (y | 0));
|
|
143
|
+
}
|
|
144
|
+
// nail-and-thread 7-segment digits
|
|
145
|
+
const SEG = { '0': 'abcdef', '1': 'bc', '2': 'abged', '3': 'abgcd', '4': 'fgbc', '5': 'afgcd', '6': 'afgedc', '7': 'abc', '8': 'abcdefg', '9': 'abcdfg' };
|
|
146
|
+
function sevenSeg(ctx, str, x, y, w, h, col, seed = 1) {
|
|
147
|
+
const segs = [], nails = []; let xx = x;
|
|
148
|
+
for (const ch of str) {
|
|
149
|
+
if (ch === ',' || ch === '.') { nails.push([xx + 4, y + h]); xx += w * 0.45; continue; }
|
|
150
|
+
if (ch === '%') { const a = [xx + w * 0.15, y + h * 0.1], b = [xx + w * 0.85, y + h * 0.9], c1 = [xx + w * 0.85, y + h * 0.1], c2 = [xx + w * 0.15, y + h * 0.9];
|
|
151
|
+
for (const o of [-1.5, 0, 1.5]) segs.push([[c2[0] + o, c2[1]], [c1[0] + o, c1[1]]]); nails.push(c1, c2); [[xx + w * 0.22, y + h * 0.2], [xx + w * 0.78, y + h * 0.8]].forEach(([cx, cy]) => { const rr = w * 0.22; for (let k = 0; k < 8; k++) { const a0 = (k / 8) * 6.283, a1 = ((k + 1) / 8) * 6.283; for (const o of [0, 1.6]) segs.push([[cx + Math.cos(a0) * (rr - o), cy + Math.sin(a0) * (rr - o)], [cx + Math.cos(a1) * (rr - o), cy + Math.sin(a1) * (rr - o)]]); nails.push([cx + Math.cos(a0) * rr, cy + Math.sin(a0) * rr]); } });
|
|
152
|
+
xx += w * 1.3; continue; }
|
|
153
|
+
const P = [[xx, y], [xx + w, y], [xx, y + h / 2], [xx + w, y + h / 2], [xx, y + h], [xx + w, y + h]];
|
|
154
|
+
const S = { a: [0, 1], b: [1, 3], c: [3, 5], d: [4, 5], e: [2, 4], f: [0, 2], g: [2, 3] };
|
|
155
|
+
for (const sg of SEG[ch] || '') { const [i, j] = S[sg]; for (let k = -2; k <= 2; k++) segs.push([[P[i][0] + k * 1.6, P[i][1] + k * 1.2], [P[j][0] + k * 1.6, P[j][1] + k * 1.2]]); }
|
|
156
|
+
nails.push(...P); xx += w * 1.45;
|
|
157
|
+
}
|
|
158
|
+
thread(ctx, segs, col, 0.95, 1.8);
|
|
159
|
+
nails.forEach((p, i) => nail(ctx, p[0], p[1], 3, seed + i));
|
|
160
|
+
return xx - x;
|
|
161
|
+
}
|
|
162
|
+
// thread arrow: shaft + two barbs, three nails
|
|
163
|
+
function tArrow(ctx, x0, y0, x1, y1, p, col = '#efe3c8', seed = 7) {
|
|
164
|
+
if (p <= 0) return; const x = x0 + (x1 - x0) * p, y = y0 + (y1 - y0) * p, a = Math.atan2(y1 - y0, x1 - x0), L = 18;
|
|
165
|
+
const b1 = [x - Math.cos(a - 0.5) * L, y - Math.sin(a - 0.5) * L], b2 = [x - Math.cos(a + 0.5) * L, y - Math.sin(a + 0.5) * L];
|
|
166
|
+
const segs = [[[x0, y0], [x, y]], [[x0 + 1, y0 + 1], [x + 1, y + 1]]]; if (p >= 1) segs.push([[x, y], b1], [[x, y], b2]);
|
|
167
|
+
thread(ctx, segs, col, 0.85, 1.3); nail(ctx, x0, y0, 3, seed); if (p >= 1) { nail(ctx, x, y, 3, seed + 1); nail(ctx, b1[0], b1[1], 2.6, seed + 2); nail(ctx, b2[0], b2[1], 2.6, seed + 3); }
|
|
168
|
+
}
|
|
169
|
+
// pyrography burn of a line of text, revealed left->right with a glowing ember
|
|
170
|
+
let BURNTEX = null; // uneven pen pressure: a static mottling texture, built once
|
|
171
|
+
function burnTex() {
|
|
172
|
+
if (BURNTEX) return BURNTEX;
|
|
173
|
+
BURNTEX = H.layer(256, 256); const c = BURNTEX.getContext('2d'), bn = H.makeNoise(5);
|
|
174
|
+
for (let y = 0; y < 256; y += 4) for (let x = 0; x < 256; x += 4) { const v = bn.n2(x * 0.03, y * 0.03) * 0.7 + bn.n2(x * 0.11, y * 0.11) * 0.3; if (v > 0.05) { c.fillStyle = `rgba(120,62,24,${Math.min(0.55, v * 1.1)})`; c.fillRect(x, y, 4, 4); } }
|
|
175
|
+
return BURNTEX;
|
|
176
|
+
}
|
|
177
|
+
const BURNC = {}; // finished burns never change: keep their pre-blurred layers
|
|
178
|
+
const blurred = (src, px, col) => { const P = px * 2, c = H.layer(src.width + P * 2, src.height + P * 2), x = c.getContext('2d'); x.filter = `blur(${px}px)`; x.drawImage(col ? tint(src, col) : src, P, P); return c; };
|
|
179
|
+
const tint = (src, col) => { const c = H.layer(src.width, src.height), x = c.getContext('2d'); x.drawImage(src, 0, 0); x.globalCompositeOperation = 'source-in'; x.fillStyle = col; x.fillRect(0, 0, c.width, c.height); return c; };
|
|
180
|
+
function burnText(ctx, text, x, y, font, prog, t) {
|
|
181
|
+
if (prog <= 0) return; ctx.save(); ctx.font = font; const w = ctx.measureText(text).width, asc = parseFloat(font.match(/(\d+)px/)[1]); ctx.restore();
|
|
182
|
+
const PAD = 40, ox = x - w / 2 - PAD, oy = y - asc * 1.1, key = text + '|' + font;
|
|
183
|
+
let B = prog >= 1 ? BURNC[key] : null;
|
|
184
|
+
if (!B) {
|
|
185
|
+
const L = H.layer(Math.ceil(w + PAD * 2), Math.ceil(asc * 1.6)), lc = L.getContext('2d');
|
|
186
|
+
lc.font = font; lc.textBaseline = 'alphabetic'; lc.fillStyle = '#000'; lc.fillText(text, PAD, asc * 1.1);
|
|
187
|
+
// reveal mask: once finished it covers the italic overhang too
|
|
188
|
+
lc.globalCompositeOperation = 'destination-in'; lc.fillRect(0, 0, prog >= 1 ? L.width : PAD + w * prog, L.height);
|
|
189
|
+
const toast = blurred(L, 12, 'rgb(214,150,80)'), halo = blurred(L, 5, 'rgb(40,16,4)');
|
|
190
|
+
// charred core with uneven pressure
|
|
191
|
+
lc.globalCompositeOperation = 'source-in'; lc.fillStyle = '#1c0a03'; lc.fillRect(0, 0, L.width, L.height);
|
|
192
|
+
lc.globalCompositeOperation = 'source-atop'; lc.globalAlpha = 0.7; lc.fillStyle = lc.createPattern(burnTex(), 'repeat'); lc.fillRect(0, 0, L.width, L.height); lc.globalAlpha = 1;
|
|
193
|
+
B = { toast, halo, core: L };
|
|
194
|
+
if (prog >= 1) BURNC[key] = B;
|
|
195
|
+
}
|
|
196
|
+
// toasted rim: the wood around the burn is singed paler and golden, so the letters sit in a lit patch
|
|
197
|
+
ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.globalAlpha = 0.5; ctx.drawImage(B.toast, ox - 24, oy - 24); ctx.restore();
|
|
198
|
+
// scorched halo right at the stroke edges, then the core
|
|
199
|
+
ctx.save(); ctx.globalAlpha = 0.5; ctx.drawImage(B.halo, ox - 10, oy - 10); ctx.globalAlpha = 0.95; ctx.drawImage(B.core, ox, oy); ctx.restore();
|
|
200
|
+
if (prog < 1) { const bx = ox + PAD + w * prog, by = y - asc * 0.35 + Math.sin(t * 40) * asc * 0.25, g = ctx.createRadialGradient(bx, by, 0, bx, by, 34);
|
|
201
|
+
g.addColorStop(0, 'rgba(255,225,150,1)'); g.addColorStop(0.18, 'rgba(255,170,70,0.75)'); g.addColorStop(0.45, 'rgba(255,100,25,0.32)'); g.addColorStop(1, 'rgba(255,80,20,0)');
|
|
202
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = g; ctx.fillRect(bx - 36, by - 36, 72, 72); ctx.restore();
|
|
203
|
+
const sm = ctx.createRadialGradient(bx + 6, by - 44, 0, bx + 6, by - 44, 40);
|
|
204
|
+
sm.addColorStop(0, 'rgba(220,210,200,0.14)'); sm.addColorStop(1, 'rgba(220,210,200,0)');
|
|
205
|
+
ctx.fillStyle = sm; ctx.fillRect(bx - 40, by - 100, 96, 100); }
|
|
206
|
+
}
|
|
207
|
+
// ---------- network on region B (world x 1280..2560) ----------
|
|
208
|
+
const NB = 1280;
|
|
209
|
+
const IN = [[NB + 470, 250], [NB + 470, 360], [NB + 470, 470]];
|
|
210
|
+
const HID = [0, 1, 2, 3, 4].map((k) => [NB + 740, 170 + k * 95]);
|
|
211
|
+
const OUT = { cat: [NB + 1010, 270], dog: [NB + 1010, 460] };
|
|
212
|
+
const HC = 1, HD = 3; // hidden neurons on the cat / dog path
|
|
213
|
+
function weights(t) {
|
|
214
|
+
const r = H.rng(808), w1 = [], w2 = [];
|
|
215
|
+
const adj = H.ease.inOut(AX(t, 8.95, 9.45));
|
|
216
|
+
for (let i = 0; i < 3; i++) for (let j = 0; j < 5; j++) { let w = r.range(0.1, 0.6); if (i === 1 && j === HD) w = 0.95; if (i === 1 && j === HC) w = 0.35 + 0.6 * adj; if (i === 1 && j === HD) w = 0.95 - 0.7 * adj; w1.push(w); }
|
|
217
|
+
for (let j = 0; j < 5; j++) for (const o of ['cat', 'dog']) { let w = r.range(0.1, 0.5); if (j === HD && o === 'dog') w = 0.95 - 0.75 * adj; if (j === HC && o === 'cat') w = 0.3 + 0.65 * adj; w2.push(w); }
|
|
218
|
+
return { w1, w2 };
|
|
219
|
+
}
|
|
220
|
+
function wrapSegs(a, b, w) { const n = 1 + Math.round(w * 5), dx = b[0] - a[0], dy = b[1] - a[1], L = Math.hypot(dx, dy), nx = -dy / L, ny = dx / L, out = [];
|
|
221
|
+
for (let k = 0; k < n; k++) { const o = (k - (n - 1) / 2) * 1.5; out.push([[a[0] + nx * o, a[1] + ny * o], [b[0] + nx * o, b[1] + ny * o]]); } return out; }
|
|
222
|
+
function drawNetwork(ctx, t, build) {
|
|
223
|
+
const { w1, w2 } = weights(t);
|
|
224
|
+
let segs = [];
|
|
225
|
+
let q = 0;
|
|
226
|
+
for (let i = 0; i < 3; i++) for (let j = 0; j < 5; j++) { segs = segs.concat(wrapSegs(IN[i], HID[j], w1[q++])); }
|
|
227
|
+
const s2 = []; q = 0;
|
|
228
|
+
for (let j = 0; j < 5; j++) for (const o of ['cat', 'dog']) s2.push(...wrapSegs(HID[j], OUT[o], w2[q++]));
|
|
229
|
+
thread(ctx, wind(segs, build), '#f4ead2', 0.78, 1.3);
|
|
230
|
+
thread(ctx, wind(s2, H.clamp(build * 1.3 - 0.3)), '#f4ead2', 0.78, 1.3);
|
|
231
|
+
IN.concat(HID).concat([OUT.cat, OUT.dog]).forEach((p, i) => nail(ctx, p[0], p[1], 4.2, 60 + i));
|
|
232
|
+
}
|
|
233
|
+
function pathSegs(a, b, c) { return [[a, b], [b, c]]; }
|
|
234
|
+
function sceneB(ctx, t) {
|
|
235
|
+
// examples zip along threads into the input nails, a counter in nail-and-thread digits
|
|
236
|
+
const lines = [[NB + 40, 250], [NB + 40, 360], [NB + 40, 470]];
|
|
237
|
+
lines.forEach((p, i) => { if (t > 2.4) { thread(ctx, [[p, IN[i]]], '#c9ccd2', 0.7, 1.1); nail(ctx, p[0], p[1], 3, 40 + i); } });
|
|
238
|
+
drawNetwork(ctx, t, AX(t, 2.6, 4.2));
|
|
239
|
+
if (t > 2.9) droppedTag(ctx, t, 2.9, 'the model', NB + 740, 52, { size: 28 });
|
|
240
|
+
if (t < 6.4) {
|
|
241
|
+
const r = H.rng(12), K = 32;
|
|
242
|
+
for (let k = 0; k < K; k++) {
|
|
243
|
+
const kind = r() < 0.5 ? 'cat' : 'dog', ts = 2.6 + 3.2 * Math.pow(k / K, 0.8); if (t < ts) continue;
|
|
244
|
+
const x = NB + 30 + (t - ts) * (430 + k * 14); if (x > NB + 420) continue;
|
|
245
|
+
petTag(ctx, kind, x, lines[k % 3][1] - 4, 64, kind);
|
|
246
|
+
}
|
|
247
|
+
if (t > 2.7) {
|
|
248
|
+
const p = AX(t, 2.75, 5.0), n = p >= 1 ? 1e6 : Math.floor(Math.pow(10, 6 * H.ease.inOut(Math.floor(p * 30) / 30))) - 1;
|
|
249
|
+
const w = sevenSeg(ctx, Math.round(n).toLocaleString('en-US'), NB + 60, 575, 30, 64, '#ffc93a', 200);
|
|
250
|
+
droppedTag(ctx, t, 3.0, 'examples', NB + 60 + w + 70, 600, { size: 28 });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
ktag(ctx, 'cat', OUT.cat[0] + 70, OUT.cat[1] - 24, { size: 34, red: t >= 9.85 && t < 10.6 });
|
|
254
|
+
ktag(ctx, 'dog', OUT.dog[0] + 70, OUT.dog[1] - 24, { size: 34 });
|
|
255
|
+
}
|
|
256
|
+
function sceneLoop(ctx, t) {
|
|
257
|
+
if (t < 6.0) return;
|
|
258
|
+
// the new picture hangs on the input
|
|
259
|
+
petTag(ctx, 'cat', NB + 290, 280, 120, null);
|
|
260
|
+
droppedTag(ctx, t, 6.2, t >= 8.0 ? 'answer: cat' : 'new picture', NB + 350, 440, { size: 28, red: t >= 8.0 });
|
|
261
|
+
tArrow(ctx, NB + 420, 360, NB + 455, 360, 1, '#efe3c8', 70);
|
|
262
|
+
const pass1 = AX(t, 6.9, 7.3), right = t >= 9.8;
|
|
263
|
+
if (!right && pass1 > 0) thread(ctx, wind(pathSegs(IN[1], HID[HD], OUT.dog), pass1), '#ffc93a', 0.95, 3.4);
|
|
264
|
+
if (t >= 7.3 && !right) droppedTag(ctx, t, 7.3, 'guess: dog?', OUT.dog[0] + 70, OUT.dog[1] + 78, { size: 28 });
|
|
265
|
+
if (t >= 7.85 && !right) { // check: red thread wound round a ring of nails on the wrong answer
|
|
266
|
+
const cx = OUT.dog[0] + 70, cy = OUT.dog[1] + 12, ring = [];
|
|
267
|
+
for (let k = 0; k < 14; k++) { for (const sk of [1, 2]) { const a0 = (k / 14) * 6.283, a1 = ((k + sk) / 14) * 6.283; ring.push([[cx + Math.cos(a0) * 82, cy + Math.sin(a0) * 55], [cx + Math.cos(a1) * 82, cy + Math.sin(a1) * 55]]); } }
|
|
268
|
+
thread(ctx, wind(ring, AX(t, 7.85, 8.2)), '#d8322a', 0.9, 1.6);
|
|
269
|
+
for (let k = 0; k < 14; k++) { const a = (k / 14) * 6.283; if (t > 7.85 + k * 0.02) nail(ctx, cx + Math.cos(a) * 82, cy + Math.sin(a) * 55, 2.8, 300 + k); }
|
|
270
|
+
droppedTag(ctx, t, 8.05, 'check: wrong ✗', cx, cy - 120, { size: 28, red: true });
|
|
271
|
+
}
|
|
272
|
+
if (t >= 8.35 && !right) thread(ctx, wind([[OUT.dog, HID[HD]], [HID[HD], IN[1]]], AX(t, 8.35, 8.85)), '#d8322a', 0.92, 2.8);
|
|
273
|
+
if (t >= 8.4 && t < 9.6) droppedTag(ctx, t, 8.4, 'error goes back', NB + 880, 520, { size: 28, red: true });
|
|
274
|
+
if (t >= 8.95 && t < 10.5) droppedTag(ctx, t, 8.95, 'adjust: rewind threads', NB + 740, 52, { size: 28, red: true });
|
|
275
|
+
if (t >= 9.4) thread(ctx, wind(pathSegs(IN[1], HID[HC], OUT.cat), AX(t, 9.4, 9.8)), '#ffc93a', 0.95, 3.4);
|
|
276
|
+
if (right) { droppedTag(ctx, t, 9.85, 'cat ✓', OUT.cat[0] + 70, OUT.cat[1] - 100, { size: 30, red: true }); }
|
|
277
|
+
// loop strip of tags joined by thread arrows
|
|
278
|
+
if (t > 6.3) {
|
|
279
|
+
const words = ['guess', 'check', 'adjust'], act = t < 7.3 ? -1 : t < 7.85 ? 0 : t < 8.35 ? 1 : t < 9.8 ? 2 : Math.floor((t - 9.8) / 0.16) % 3;
|
|
280
|
+
let x = NB + 120;
|
|
281
|
+
words.forEach((w, i) => { const tw = ktag(ctx, w, x, 590, { size: 32, align: 'left', red: i === act, minW: 110 }); x += tw + 16; if (i < 2) { tArrow(ctx, x, 618, x + 54, 618, 1, '#efe3c8', 80 + i * 4); x += 74; } });
|
|
282
|
+
ktag(ctx, '↻ x 1,000,000', x + 70, 590, { size: 28 });
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// ---------- region C: progress ----------
|
|
286
|
+
const NC = 2560;
|
|
287
|
+
function sceneC(ctx, t) {
|
|
288
|
+
droppedTag(ctx, t, 10.6, 'accuracy', NC + 290, 70, { size: 38 });
|
|
289
|
+
const p = H.ease.inOut(AX(t, 10.95, 12.3)), v = Math.round(61 + 36 * Math.floor(p * 18) / 18);
|
|
290
|
+
sevenSeg(ctx, v + '%', NC + 70, 190, 90, 200, '#ffc93a', 400);
|
|
291
|
+
if (t >= 12.3) { const cx = NC + 290, cy = 290, ring = []; for (let k = 0; k < 18; k++) { for (const sk of [1, 2]) { const a0 = (k / 18) * 6.283, a1 = ((k + sk) / 18) * 6.283; ring.push([[cx + Math.cos(a0) * 280, cy + Math.sin(a0) * 165], [cx + Math.cos(a1) * 280, cy + Math.sin(a1) * 165]]); } }
|
|
292
|
+
thread(ctx, wind(ring, AX(t, 12.3, 12.7)), '#d8322a', 0.85, 1.5); for (let k = 0; k < 18; k++) { const a = (k / 18) * 6.283; nail(ctx, cx + Math.cos(a) * 280, cy + Math.sin(a) * 165, 2.8, 700 + k); } }
|
|
293
|
+
// line chart strung nail to nail
|
|
294
|
+
const x0 = NC + 640, y0 = 600, cw = 560, ch = 420, n = 10, pts = [];
|
|
295
|
+
for (let i = 0; i < n; i++) { const acc = 61 + 36 * (1 - Math.exp(-i / 2.6)) / (1 - Math.exp(-(n - 1) / 2.6)); pts.push([x0 + 30 + i * (cw - 60) / (n - 1), y0 - ((acc - 50) / 50) * ch]); }
|
|
296
|
+
thread(ctx, [[[x0, y0], [x0 + cw, y0]], [[x0, y0], [x0, y0 - ch - 20]]], '#c9ccd2', 0.8, 1.2);
|
|
297
|
+
[[x0, y0], [x0 + cw, y0], [x0, y0 - ch - 20]].forEach((q, i) => nail(ctx, q[0], q[1], 3.2, 760 + i));
|
|
298
|
+
const segs = []; for (let i = 0; i < n - 1; i++) for (const o of [-1.5, 0, 1.5]) segs.push([[pts[i][0], pts[i][1] + o], [pts[i + 1][0], pts[i + 1][1] + o]]); // three wraps
|
|
299
|
+
const k = AX(t, 10.95, 12.3); thread(ctx, wind(segs, k), '#ffc93a', 0.9, 1.5);
|
|
300
|
+
pts.forEach((q, i) => { if (k * (n - 1) >= i - 0.01) nail(ctx, q[0], q[1], 3.6, 780 + i); });
|
|
301
|
+
droppedTag(ctx, t, 10.7, 'training rounds →', x0 + cw - 100, y0 + 24, { size: 28 });
|
|
302
|
+
}
|
|
303
|
+
// ---------- region D: takeaway ----------
|
|
304
|
+
const ND = 3840;
|
|
305
|
+
function sceneD(ctx, t) {
|
|
306
|
+
burnText(ctx, 'More examples', ND + 640, 300, '700 110px "Caveat"', AX(t, 13.2, 13.65), t);
|
|
307
|
+
burnText(ctx, '→ better guesses', ND + 660, 440, '700 110px "Caveat"', AX(t, 13.6, 14.05), t);
|
|
308
|
+
// a small cardioid winds on beneath, the same envelope trick as the still
|
|
309
|
+
const N = 72, cx = ND + 640, cy = 590, R = 70, circ = []; for (let i = 0; i < N; i++) { const a = -Math.PI / 2 + (i / N) * 6.283; circ.push([cx + Math.cos(a) * R * 1.12, cy + Math.sin(a) * R * 1.12]); }
|
|
310
|
+
const card = []; for (let i = 0; i < N; i++) card.push([circ[i], circ[(i * 2) % N]]);
|
|
311
|
+
thread(ctx, wind(card, AX(t, 13.0, 14.2)), '#ffc93a', 0.8, 1.05);
|
|
312
|
+
if (t > 12.85) circ.forEach((p, i) => { if (i % 2 === 0) nail(ctx, p[0], p[1], 2.4, 1200 + i); });
|
|
313
|
+
}
|
|
314
|
+
function sceneA(ctx, t) {
|
|
315
|
+
burnText(ctx, 'How does', 600, 300, '700 130px "Caveat"', AX(t, 0.1, 0.8), t);
|
|
316
|
+
burnText(ctx, 'AI learn?', 680, 450, '700 130px "Caveat"', AX(t, 0.75, 1.45), t);
|
|
317
|
+
const N = 40, ring = [], pts = []; for (let i = 0; i < N; i++) { const a = (i / N) * 6.283; pts.push([640 + Math.cos(a) * 470, 360 + Math.sin(a) * 300]); }
|
|
318
|
+
for (let i = 0; i < N; i++) ring.push([pts[i], pts[(i + 13) % N]]);
|
|
319
|
+
thread(ctx, wind(ring, AX(t, 0.3, 2.2)), '#efe3c8', 0.55, 1.1);
|
|
320
|
+
pts.forEach((p, i) => { if (t > 0.05 + i * 0.012) nail(ctx, p[0], p[1], 3, 1000 + i); });
|
|
321
|
+
}
|
|
322
|
+
function explainSA(ctx, W, H_, t) {
|
|
323
|
+
// camera path: (world centre x, centre y, zoom)
|
|
324
|
+
const keys = [[0, 640, 360, 1], [2.15, 640, 360, 1], [2.75, 1920, 360, 1], [6.0, 1920, 360, 1], [6.5, 2010, 340, 1.08], [10.35, 2010, 340, 1.08], [10.95, 3200, 360, 1], [12.85, 3200, 360, 1], [13.35, 4480, 360, 1], [99, 4480, 360, 1]];
|
|
325
|
+
let k = 0; while (k < keys.length - 2 && t >= keys[k + 1][0]) k++;
|
|
326
|
+
const a = keys[k], b = keys[k + 1], u = H.ease.inOut(AX(t, a[0], b[0]));
|
|
327
|
+
const cx = a[1] + (b[1] - a[1]) * u, cy = a[2] + (b[2] - a[2]) * u, z = (a[3] + (b[3] - a[3]) * u) * (1 + 0.012 * Math.sin(t * 0.7));
|
|
328
|
+
ctx.save(); ctx.translate(W / 2, H_ / 2); ctx.scale(z, z); ctx.translate(-cx, -cy);
|
|
329
|
+
const vx0 = cx - W / 2 / z, vx1 = cx + W / 2 / z;
|
|
330
|
+
woodWorld(ctx, vx0, vx1);
|
|
331
|
+
if (vx0 < 1280) sceneA(ctx, t);
|
|
332
|
+
if (vx1 > 1280 && vx0 < 2560) { sceneB(ctx, t); sceneLoop(ctx, t); }
|
|
333
|
+
if (vx1 > 2560 && vx0 < 3840 && t > 10) sceneC(ctx, t);
|
|
334
|
+
if (vx1 > 3840) sceneD(ctx, t);
|
|
335
|
+
ctx.restore();
|
|
336
|
+
if (t > 1.9) { const kk = H.ease.back(AX(t, 1.9, 2.2)); ktag(ctx, 'How does AI learn?', 30, 26 - (1 - kk) * 60, { size: 28, align: 'left', rot: -0.03 }); }
|
|
337
|
+
H.vignette(ctx, W, H_, 0.42, '20,8,2');
|
|
338
|
+
H.grain(ctx, W, H_, 0.035, 12 + Math.floor(t * 12));
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
registerStyle({
|
|
342
|
+
id: 'string-art',
|
|
343
|
+
name: 'Nail & Thread String Art',
|
|
344
|
+
category: 'Generative & Type',
|
|
345
|
+
description: 'Taut cotton threads strung between brass nails on a stained walnut board; every curve is an envelope of straight chords, with thread translucency, sheen and cast shadows, plus pyrography lettering.',
|
|
346
|
+
motion: 'Nails are tapped in around the outline, then thread is wound chord by chord so the glowing envelopes emerge, the filament and base are laced, the rays snap taut, and the title is burned in with a travelling ember while the camera slowly pushes in.',
|
|
347
|
+
fonts: ['700 92px "Caveat"', '400 26px "Caveat"', '700 26px "Caveat"'],
|
|
348
|
+
duration: 7,
|
|
349
|
+
frameT: 6.9,
|
|
350
|
+
readsPixels: true,
|
|
351
|
+
explainerDuration: 15,
|
|
352
|
+
explainerNote: 'The network is literally strung on the board — nails are neurons, a weight is how many wraps of thread a connection has — and learning is re-winding; the camera travels along one long walnut board between scenes, labels are kraft tags nailed on, arrows are threads with nail barbs, counters are nail-and-thread 7-segment digits, and emphasis is a red thread wound round a ring of nails.',
|
|
353
|
+
explainer(ctx, W, H_, t) { explainSA(ctx, W, H_, t); },
|
|
354
|
+
// Sound: a workbench. Nails are tapped into walnut (small metal tink on a wood knock), thread winding is a soft
|
|
355
|
+
// cotton rub with a low muffled pluck each time it snaps round a nail (throttled), kraft tags are nailed on with one
|
|
356
|
+
// tap as they land, pyrography burns with a quiet sizzle and ember crackle, and the camera travel is a smooth low
|
|
357
|
+
// slide along the board. The wrong guess is a slack, out-of-tune twang; the correction and the 97% ring are taut
|
|
358
|
+
// consonant thrums. Timings mirror explainSA / sceneA..D above (AX windows, wind() is linear, droppedTag back-ease).
|
|
359
|
+
explainerSound(k) {
|
|
360
|
+
const D = 15, E = H.ease;
|
|
361
|
+
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; };
|
|
362
|
+
const land = (t0) => when(t0, t0 + 0.28, E.back, 1); // droppedTag: back ease over 0.28 s
|
|
363
|
+
// hammer tap on a nail: metal tink + wood knock
|
|
364
|
+
const tap = (t, g, p) => { k.noise(t, { dur: 0.02, filter: 'bandpass', f: 2300, q: 5, gain: 0.22 * g, a: 0.001, pan: p }); k.tone(t, { f: 1150, dur: 0.05, gain: 0.05 * g, a: 0.001, pan: p }); k.tone(t, { f: [[0, 210], [0.05, 140]], dur: 0.08, gain: 0.22 * g, a: 0.001, pan: p }); };
|
|
365
|
+
const tagOn = (t0, p, g = 1) => tap(land(t0), 0.75 * g, p);
|
|
366
|
+
// thread snapping round a nail: low muffled pluck
|
|
367
|
+
const pluck = (t, f, g, p) => { k.tone(t, { f, type: 'triangle', dur: 0.28, gain: 0.12 * g, a: 0.002, filter: 'lowpass', f2: [[0, 1400], [0.2, 400]], pan: p }); k.noise(t, { dur: 0.015, filter: 'bandpass', f: 1200, q: 1, gain: 0.06 * g, a: 0.001, pan: p }); };
|
|
368
|
+
// winding over [t0,t1] with N chords (linear): cotton rub + a pluck per chord, at most `rate` per second
|
|
369
|
+
const wind = (t0, t1, N, g, p, f = 196, rate = 8) => {
|
|
370
|
+
const d = t1 - t0;
|
|
371
|
+
k.noise(t0, { dur: d + 0.05, color: 'pink', filter: 'bandpass', f: 900, q: 0.9, gain: 0.07 * g, a: Math.min(0.1, d * 0.3), hold: d - 0.15, pan: p, minor: true });
|
|
372
|
+
let last = -1;
|
|
373
|
+
k.withMinor(() => { for (let i = 0; i < N; i++) { const t = t0 + ((i + 1) / N) * d; if (t - last < 1 / rate) continue; last = t; pluck(t, f * (1 + 0.06 * (i % 3)), 0.7 * g, p + ((i % 5) - 2) * 0.06); } });
|
|
374
|
+
};
|
|
375
|
+
// pyrography: soft sizzle with seeded ember crackle
|
|
376
|
+
const burn = (t0, t1, p) => {
|
|
377
|
+
const d = t1 - t0, r = H.rng(Math.round(t0 * 100));
|
|
378
|
+
k.noise(t0, { dur: d + 0.1, color: 'pink', filter: 'bandpass', f: 2200, q: 0.6, gain: 0.05, a: 0.06, hold: d - 0.1, pan: p, minor: true });
|
|
379
|
+
k.withMinor(() => { for (let t = t0 + 0.03; t < t1; t += 0.05 + r() * 0.07) k.noise(t, { dur: 0.008, filter: 'bandpass', f: 1400 + r() * 900, q: 1, gain: 0.07 + r() * 0.08, a: 0.001, pan: p + (r() - 0.5) * 0.3 }); });
|
|
380
|
+
};
|
|
381
|
+
// camera travels along the board
|
|
382
|
+
const travel = (t0, t1) => { const d = t1 - t0; k.noise(t0, { dur: d, color: 'brown', filter: 'lowpass', f: [[0, 250], [d * 0.5, 520], [d, 220]], gain: 0.16, a: d * 0.45 }); };
|
|
383
|
+
const thrum = (t, notes, g) => notes.forEach((n, i) => k.tone(t + i * 0.035, { f: n, type: 'triangle', dur: 1.1, gain: 0.07 * g, a: 0.003, filter: 'lowpass', f2: [[0, 2200], [0.8, 600]], verb: 0.2, pan: (i - (notes.length - 1) / 2) * 0.2 }));
|
|
384
|
+
|
|
385
|
+
k.bed('room', 0, D, { gain: 0.35 });
|
|
386
|
+
|
|
387
|
+
// ---- A: nails tapped round the ellipse (0.05 + i*0.012), title burned in, ring of thread wound 0.3-2.2
|
|
388
|
+
k.withMinor(() => { for (let i = 0; i < 40; i += 6) { const a = (i / 40) * 6.283; tap(0.05 + i * 0.012, 0.6, Math.cos(a) * 0.6); } });
|
|
389
|
+
burn(0.1, 0.8, -0.15);
|
|
390
|
+
burn(0.75, 1.45, 0.1);
|
|
391
|
+
wind(0.3, 2.2, 40, 0.8, 0, 175, 5);
|
|
392
|
+
tagOn(1.9, -0.7); // header tag (lands 1.9 + 0.3*0.37)
|
|
393
|
+
|
|
394
|
+
// ---- B: travel to the network; it winds 2.6-4.2 (second layer to 4.2), tags, the 7-segment counter
|
|
395
|
+
travel(2.15, 2.75);
|
|
396
|
+
wind(2.6, 4.2, 45, 0.7, 0.2, 220, 4);
|
|
397
|
+
tagOn(2.9, 0.15); tagOn(3.0, -0.45);
|
|
398
|
+
// counter shows floor(p*30)/30 steps of p = AX(2.75, 5.0) through inOut: each step re-strings the digits
|
|
399
|
+
{ let prev = 0, last = -1; for (let t = 2.76; t <= 5.0; t += 0.002) { const s = Math.floor(AX(t, 2.75, 5.0) * 30); if (s !== prev && s < 30 && t - last >= 1 / 9) { k.withMinor(() => pluck(t, 330 + s * 4, 0.55, -0.55)); last = t; } prev = s; } }
|
|
400
|
+
tap(5.0, 1.3, -0.5); thrum(5.0, ['G2', 'D3', 'G3'], 0.9); // 1,000,000 strung
|
|
401
|
+
|
|
402
|
+
// ---- loop: push in, tag, guess path winds, red ring of nails, error back, rewind, cat ✓
|
|
403
|
+
travel(6.0, 6.5);
|
|
404
|
+
tagOn(6.2, -0.45);
|
|
405
|
+
wind(6.9, 7.3, 2, 1.1, 0.2, 247);
|
|
406
|
+
tagOn(7.3, 0.6, 0.8);
|
|
407
|
+
k.withMinor(() => { for (let i = 0; i < 14; i += 3) tap(7.85 + i * 0.02, 0.5, 0.55); }); // ring nails go in
|
|
408
|
+
wind(7.85, 8.2, 28, 0.6, 0.55, 165, 9);
|
|
409
|
+
tagOn(8.05, 0.55, 1.1);
|
|
410
|
+
// check: wrong — a slack, out-of-tune twang on the dog thread
|
|
411
|
+
[98, 139].forEach((f, i) => k.tone(8.16, { f: [[0, f], [0.35, f * 0.94]], type: 'sawtooth', dur: 0.5, gain: 0.08, a: 0.003, filter: 'lowpass', f2: [[0, 1300], [0.4, 300]], pan: 0.5 - i * 0.1 }));
|
|
412
|
+
k.play('thud', 8.16, { f: 110, gain: 0.5, pan: 0.5 });
|
|
413
|
+
// red error thread runs back dog -> hidden -> input (8.35-8.85): a rub falling right to left
|
|
414
|
+
k.noise(8.35, { dur: 0.5, color: 'pink', filter: 'bandpass', f: [[0, 1300], [0.5, 600]], q: 1.2, gain: 0.1, a: 0.1, pan: 0.2 });
|
|
415
|
+
tagOn(8.4, 0.35, 0.8);
|
|
416
|
+
tagOn(8.95, 0, 0.8);
|
|
417
|
+
// adjust 8.95-9.45: threads unwound from the dog path, wraps added to the cat path
|
|
418
|
+
k.noise(8.95, { dur: 0.5, color: 'pink', filter: 'bandpass', f: [[0, 700], [0.5, 1100]], q: 0.9, gain: 0.09, a: 0.15, minor: true });
|
|
419
|
+
k.withMinor(() => [0, 1, 2, 3].forEach((i) => pluck(9.0 + i * 0.12, [196, 220, 247, 262][i], 0.8, -0.1)));
|
|
420
|
+
wind(9.4, 9.8, 2, 1.1, 0.1, 262);
|
|
421
|
+
tagOn(9.85, 0.6, 1.1); thrum(land(9.85), ['C3', 'G3', 'E4'], 1.1); // cat ✓
|
|
422
|
+
|
|
423
|
+
// ---- C: travel, tags, chart nails + counter, red ring round 97%
|
|
424
|
+
travel(10.35, 10.95);
|
|
425
|
+
tagOn(10.6, -0.2); tagOn(10.7, 0.6, 0.6);
|
|
426
|
+
k.noise(10.95, { dur: 1.4, color: 'pink', filter: 'bandpass', f: 900, q: 0.9, gain: 0.06, a: 0.1, hold: 1.1, pan: 0.4, minor: true }); // chart thread rubs on
|
|
427
|
+
k.withMinor(() => { for (let i = 1; i < 10; i++) tap(10.95 + (i / 9) * 1.35, 0.45, 0.1 + i * 0.06); }); // a nail at each point
|
|
428
|
+
{ let prev = 0, last = -1; for (let t = 10.96; t <= 12.3; t += 0.002) { const s = Math.floor(H.ease.inOut(AX(t, 10.95, 12.3)) * 18); if (s !== prev && t - last >= 1 / 9) { k.withMinor(() => pluck(t, 260 + s * 9, 0.5, -0.45)); last = t; } prev = s; } }
|
|
429
|
+
tap(12.3, 1.5, -0.4);
|
|
430
|
+
k.play('impact', 12.3, { gain: 1.1, pan: -0.3 });
|
|
431
|
+
thrum(12.32, ['C3', 'G3', 'C4', 'E4'], 1.6); // the 97% hit
|
|
432
|
+
wind(12.3, 12.7, 36, 0.8, -0.3, 196, 9); // red thread wound round the ring
|
|
433
|
+
|
|
434
|
+
// ---- D: travel, cardioid winds 13.0-14.2, burn the takeaway, resolve
|
|
435
|
+
travel(12.85, 13.35);
|
|
436
|
+
wind(13.0, 14.2, 72, 0.5, 0, 175, 5);
|
|
437
|
+
burn(13.2, 13.65, -0.1);
|
|
438
|
+
burn(13.6, 14.05, 0.1);
|
|
439
|
+
thrum(14.05, ['F2', 'C3', 'A3', 'C4'], 0.8); thrum(14.3, ['C3', 'G3', 'C4', 'E4'], 1.1);
|
|
440
|
+
},
|
|
441
|
+
draw(ctx, W, H_, t) {
|
|
442
|
+
if (!WOOD) { WOOD = H.layer(W, H_); woodBoard(WOOD.getContext('2d'), W, H_, 0); }
|
|
443
|
+
// slow camera push across the whole shot
|
|
444
|
+
const push = 1 + 0.03 * H.ease.inOut(H.clamp(t / 7));
|
|
445
|
+
ctx.save();
|
|
446
|
+
ctx.translate(560, 340); ctx.scale(push, push); ctx.translate(-560, -340);
|
|
447
|
+
ctx.drawImage(WOOD, -20, -20, W + 40, H_ + 40);
|
|
448
|
+
// nail pop-in helper: tapped in over ~3 frames with a tiny bounce
|
|
449
|
+
const nailAt = (x, y, r, seed, t0) => {
|
|
450
|
+
if (t < t0) return;
|
|
451
|
+
const k = H.clamp((t - t0) / 0.1);
|
|
452
|
+
nail(ctx, x, y, r * (1 + 0.35 * (1 - k) * (1 - k)), seed);
|
|
453
|
+
};
|
|
454
|
+
|
|
455
|
+
const cx = 470, cy = 300, R = 172;
|
|
456
|
+
const N = 120;
|
|
457
|
+
// circle nails, leaving a gap at the bottom where the neck joins
|
|
458
|
+
const gapA = 0.36; // radians each side of straight down
|
|
459
|
+
const circ = [];
|
|
460
|
+
for (let i = 0; i < N; i++) {
|
|
461
|
+
const a = -Math.PI / 2 + (i / N) * TAU; // i=0 at top -> cardioid cusp sits low, lobe fills the glass
|
|
462
|
+
circ.push([cx + Math.cos(a) * R, cy + Math.sin(a) * R, a]);
|
|
463
|
+
}
|
|
464
|
+
const isGap = (p) => { let da = Math.abs(((p[2] - Math.PI / 2) % TAU + TAU) % TAU); da = Math.min(da, TAU - da); return da < gapA; };
|
|
465
|
+
|
|
466
|
+
// progress of winding (animatable); at frameT everything is wound
|
|
467
|
+
|
|
468
|
+
// 1) glass glow: fixed-skip chords -> bright annulus whose inner edge is a circle envelope
|
|
469
|
+
const halo = [], halo2 = [];
|
|
470
|
+
for (let i = 0; i < N; i++) {
|
|
471
|
+
const a = circ[i], b = circ[(i + 34) % N], c = circ[(i + 47) % N];
|
|
472
|
+
if (!isGap(a) && !isGap(b)) halo.push([a, b]);
|
|
473
|
+
if (!isGap(a) && !isGap(c) && i % 2 === 0) halo2.push([a, c]);
|
|
474
|
+
}
|
|
475
|
+
thread(ctx, wind(halo2, ph(t, 2.3, 3.4)), '#e8702e', 0.6, 1.0);
|
|
476
|
+
thread(ctx, wind(halo, ph(t, 1.3, 2.7)), '#ffc93a', 0.82, 1.15);
|
|
477
|
+
|
|
478
|
+
// 2) cardioid (times-two table) in cream: its cusp sits on the filament
|
|
479
|
+
const card = [];
|
|
480
|
+
for (let i = 0; i < N; i += 1) {
|
|
481
|
+
const a = circ[i], c = circ[(i * 2) % N];
|
|
482
|
+
if (!isGap(a) && !isGap(c)) card.push([a, c]);
|
|
483
|
+
}
|
|
484
|
+
thread(ctx, wind(card, ph(t, 2.6, 3.7)), '#f6ead2', 0.5, 0.9);
|
|
485
|
+
|
|
486
|
+
// filament: zig-zag between two short rows of nails, wound many times
|
|
487
|
+
const fil = [], filN = [];
|
|
488
|
+
const fy = cy + 30;
|
|
489
|
+
for (let k = 0; k < 6; k++) { filN.push([cx - 50 + k * 20, fy - 14], [cx - 40 + k * 20, fy + 14]); }
|
|
490
|
+
for (let k = 0; k < filN.length - 1; k++) fil.push([filN[k], filN[k + 1]]);
|
|
491
|
+
for (let k = 0; k < filN.length - 2; k += 2) fil.push([filN[k], filN[k + 2]]);
|
|
492
|
+
// filament supports down to the neck
|
|
493
|
+
const sup = [[filN[0], [cx - 30, cy + R * 0.9]], [filN[filN.length - 1], [cx + 30, cy + R * 0.9]]];
|
|
494
|
+
thread(ctx, wind(sup, ph(t, 3.6, 3.8)), '#c9ccd2', 0.8, 1.0);
|
|
495
|
+
thread(ctx, wind(fil, ph(t, 3.7, 4.2)), '#ff8c2a', 0.95, 1.3);
|
|
496
|
+
thread(ctx, wind(fil, ph(t, 4.0, 4.4)), '#ffe08a', 0.6, 0.8);
|
|
497
|
+
|
|
498
|
+
// 3) neck + screw base: nails in two columns, zig-zag cross-hatched silver thread
|
|
499
|
+
const baseTop = cy + Math.cos(gapA) * R + 4, baseL = cx - Math.sin(gapA) * R, baseR = cx + Math.sin(gapA) * R;
|
|
500
|
+
const neckBot = baseTop + 46;
|
|
501
|
+
const nL = [], nR = [];
|
|
502
|
+
const rows = 9;
|
|
503
|
+
for (let k = 0; k <= rows; k++) {
|
|
504
|
+
const y = baseTop + (k / rows) * 104;
|
|
505
|
+
const narrow = k < 3 ? (k / 3) * 14 : 14 + (k >= rows - 1 ? (k - rows + 2) * 16 : 0);
|
|
506
|
+
nL.push([baseL + narrow, y]); nR.push([baseR - narrow, y]);
|
|
507
|
+
}
|
|
508
|
+
const base = [];
|
|
509
|
+
for (let k = 0; k < rows; k++) { base.push([nL[k], nR[k + 1]]); base.push([nR[k], nL[k + 1]]); base.push([nL[k], nR[k]]); }
|
|
510
|
+
// threads also tie the neck into the bulb rim
|
|
511
|
+
const left0 = circ.find((p) => !isGap(p) && p[0] < cx && p[1] > cy + R * 0.85) || circ[6];
|
|
512
|
+
base.push([nL[0], circ[Math.round(N * gapA / TAU) + 1]]); base.push([nR[0], circ[N - Math.round(N * gapA / TAU) - 1]]);
|
|
513
|
+
thread(ctx, wind(base, ph(t, 3.9, 4.7)), '#d4d7dc', 0.85, 1.05);
|
|
514
|
+
// tip
|
|
515
|
+
const tip = [cx, nL[rows][1] + 22];
|
|
516
|
+
const tipSeg = [[nL[rows], tip], [nR[rows], tip], [nL[rows - 1], tip], [nR[rows - 1], tip]];
|
|
517
|
+
thread(ctx, wind(tipSeg, ph(t, 4.6, 4.8)), '#9aa0a8', 0.75, 1.05);
|
|
518
|
+
|
|
519
|
+
// 4) rays: each ray is several wraps between two nails (thicker, slightly fanned)
|
|
520
|
+
const rays = [];
|
|
521
|
+
const rayNails = [];
|
|
522
|
+
const rayAngles = [-150, -118, -90, -62, -30, 0, 180].map((d) => (d * Math.PI) / 180);
|
|
523
|
+
rayAngles.forEach((a, k) => {
|
|
524
|
+
const r0 = R + 30, r1 = R + 30 + (k === 2 ? 70 : 56);
|
|
525
|
+
const p0 = [cx + Math.cos(a) * r0, cy + Math.sin(a) * r0], p1 = [cx + Math.cos(a) * r1, cy + Math.sin(a) * r1];
|
|
526
|
+
const px = -Math.sin(a) * 4, py = Math.cos(a) * 4;
|
|
527
|
+
const q0 = [p0[0] - px, p0[1] - py], q1 = [p0[0] + px, p0[1] + py], q2 = [p1[0] - px * 1.6, p1[1] - py * 1.6], q3 = [p1[0] + px * 1.6, p1[1] + py * 1.6];
|
|
528
|
+
rayNails.push(q0, q1, q2, q3);
|
|
529
|
+
const len = H.ease.out(ph(t, 4.5 + k * 0.07, 4.75 + k * 0.07));
|
|
530
|
+
if (len > 0) {
|
|
531
|
+
const L = (a0, b0) => [a0, [a0[0] + (b0[0] - a0[0]) * len, a0[1] + (b0[1] - a0[1]) * len]];
|
|
532
|
+
rays.push(L(q0, q2), L(q1, q3), L(q0, q3), L(q1, q2), L(q0, q2), L(q1, q3));
|
|
533
|
+
}
|
|
534
|
+
});
|
|
535
|
+
thread(ctx, rays, '#ffc93a', 0.85, 1.15);
|
|
536
|
+
|
|
537
|
+
// 5) the bulb lights up: warm bloom round the filament + a soft glow off the gold and filament threads
|
|
538
|
+
const glowK = 0.35 * ph(t, 1.3, 2.7) + 0.65 * ph(t, 3.7, 4.4);
|
|
539
|
+
if (glowK > 0) {
|
|
540
|
+
const GS = 440, gx0 = cx - GS / 2, gy0 = cy - GS / 2 + 10, done = t >= 4.4;
|
|
541
|
+
let gb = done ? BURNC.heroGlow : null;
|
|
542
|
+
if (!gb) {
|
|
543
|
+
const gl = H.layer(GS, GS), gc = gl.getContext('2d');
|
|
544
|
+
gc.translate(-gx0, -gy0); gc.lineCap = 'round';
|
|
545
|
+
const strokeSet = (segs, col, w) => { if (!segs.length) return; gc.strokeStyle = col; gc.lineWidth = w; gc.beginPath(); for (const [a, b] of segs) { gc.moveTo(a[0], a[1]); gc.lineTo(b[0], b[1]); } gc.stroke(); };
|
|
546
|
+
strokeSet(wind(halo, ph(t, 1.3, 2.7)), '#ffc93a', 2.2);
|
|
547
|
+
strokeSet(wind(fil, ph(t, 3.7, 4.2)), '#ffb050', 4);
|
|
548
|
+
gb = blurred(gl, 4); if (done) BURNC.heroGlow = gb;
|
|
549
|
+
}
|
|
550
|
+
ctx.save(); ctx.globalCompositeOperation = 'screen';
|
|
551
|
+
const g = ctx.createRadialGradient(cx, fy, 0, cx, fy, 270);
|
|
552
|
+
g.addColorStop(0, `rgba(255,205,120,${0.34 * glowK})`); g.addColorStop(0.4, `rgba(255,170,80,${0.15 * glowK})`); g.addColorStop(1, 'rgba(255,150,60,0)');
|
|
553
|
+
ctx.fillStyle = g; ctx.fillRect(cx - 280, fy - 280, 560, 560);
|
|
554
|
+
ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = 0.35 * Math.min(1, glowK * 1.4);
|
|
555
|
+
ctx.drawImage(gb, gx0 - 8, gy0 - 8); ctx.restore();
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
// nails on top of threads (threads wrap around shafts under the heads)
|
|
559
|
+
let s = 1;
|
|
560
|
+
circ.forEach((p, i) => { if (!isGap(p)) nailAt(p[0], p[1], 3.3, s++, 0.05 + (i / N) * 0.85); });
|
|
561
|
+
nL.forEach((p, k) => nailAt(p[0], p[1], 3.3, s++, 0.9 + k * 0.025));
|
|
562
|
+
nR.forEach((p, k) => nailAt(p[0], p[1], 3.3, s++, 0.92 + k * 0.025));
|
|
563
|
+
nailAt(tip[0], tip[1], 3.3, s++, 1.18);
|
|
564
|
+
filN.forEach((p, k) => nailAt(p[0], p[1], 2.8, s++, 1.0 + k * 0.02));
|
|
565
|
+
nailAt(cx - 30, cy + R * 0.9, 2.8, s++, 1.0); nailAt(cx + 30, cy + R * 0.9, 2.8, s++, 1.02);
|
|
566
|
+
rayNails.forEach((p, k) => nailAt(p[0], p[1], 3.0, s++, 1.05 + k * 0.012));
|
|
567
|
+
|
|
568
|
+
// ---------- pyrography lettering ----------
|
|
569
|
+
const TX = 1000, TY = 318;
|
|
570
|
+
const burn = H.layer(W, H_), bc = burn.getContext('2d');
|
|
571
|
+
bc.textAlign = 'center'; bc.textBaseline = 'alphabetic';
|
|
572
|
+
bc.font = '700 92px "Caveat"';
|
|
573
|
+
bc.fillStyle = '#000';
|
|
574
|
+
bc.fillText('Bright', TX - 20, TY);
|
|
575
|
+
bc.fillText('Ideas', TX + 30, TY + 92);
|
|
576
|
+
// burn-in: the pen travels left->right over each line (line 1 then line 2)
|
|
577
|
+
const b1 = ph(t, 4.9, 5.5), b2 = ph(t, 5.45, 6.0);
|
|
578
|
+
const reveal = H.layer(W, H_), rv = reveal.getContext('2d');
|
|
579
|
+
const x0 = TX - 130, x1 = TX + 82, y0 = TX - 72, y1 = TX + 140;
|
|
580
|
+
if (b1 > 0) rv.fillRect(0, TY - 110, x0 + (x1 - x0) * b1, 132);
|
|
581
|
+
if (b2 > 0) rv.fillRect(0, TY + 22, y0 + (y1 - y0) * b2, 110);
|
|
582
|
+
bc.globalCompositeOperation = 'destination-in'; bc.drawImage(reveal, 0, 0); bc.globalCompositeOperation = 'source-over';
|
|
583
|
+
// toasted rim: the walnut round the burn is singed paler and golden (same treatment as the explainer)
|
|
584
|
+
if (b1 > 0) {
|
|
585
|
+
const rx0 = TX - 300, ry0 = TY - 150, rw = 620, rh = 330, done = b2 >= 1;
|
|
586
|
+
let tb = done ? BURNC.heroToast : null;
|
|
587
|
+
if (!tb) { const tl = H.layer(rw, rh); tl.getContext('2d').drawImage(burn, rx0, ry0, rw, rh, 0, 0, rw, rh); tb = blurred(tl, 12, 'rgb(214,150,80)'); if (done) BURNC.heroToast = tb; }
|
|
588
|
+
ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.globalAlpha = 0.5; ctx.drawImage(tb, rx0 - 24, ry0 - 24); ctx.restore();
|
|
589
|
+
}
|
|
590
|
+
// scorch halo
|
|
591
|
+
ctx.save();
|
|
592
|
+
ctx.filter = 'blur(4px)'; ctx.globalAlpha = 0.55; ctx.globalCompositeOperation = 'multiply';
|
|
593
|
+
ctx.drawImage(burn, 0, 0);
|
|
594
|
+
ctx.filter = 'none';
|
|
595
|
+
ctx.globalAlpha = 1;
|
|
596
|
+
// burnt core: dark umber, slightly uneven
|
|
597
|
+
const core = H.layer(W, H_), cc = core.getContext('2d');
|
|
598
|
+
cc.drawImage(burn, 0, 0);
|
|
599
|
+
cc.globalCompositeOperation = 'source-in';
|
|
600
|
+
const cg = cc.createLinearGradient(TX - 200, 0, TX + 200, 0);
|
|
601
|
+
cg.addColorStop(0, '#1d0d05'); cg.addColorStop(0.5, '#2b1408'); cg.addColorStop(1, '#170903');
|
|
602
|
+
cc.fillStyle = cg; cc.fillRect(0, 0, W, H_);
|
|
603
|
+
// uneven pen pressure: lighter scorched patches inside strokes
|
|
604
|
+
const bn = H.makeNoise(5);
|
|
605
|
+
cc.globalCompositeOperation = 'source-atop';
|
|
606
|
+
for (let y = TY - 90; y < TY + 30 + 92; y += 4) for (let x = TX - 220; x < TX + 220; x += 4) {
|
|
607
|
+
const v = bn.n2(x * 0.03, y * 0.03);
|
|
608
|
+
if (v > 0.05) { cc.fillStyle = `rgba(120,62,24,${Math.min(0.6, v * 1.2)})`; cc.fillRect(x, y, 4, 4); }
|
|
609
|
+
}
|
|
610
|
+
ctx.globalCompositeOperation = 'source-over'; ctx.globalAlpha = 0.92;
|
|
611
|
+
ctx.drawImage(core, 0, 0);
|
|
612
|
+
ctx.restore();
|
|
613
|
+
// ember at the pen tip while burning, with a wisp of smoke
|
|
614
|
+
const ember = (bx, by, k) => {
|
|
615
|
+
if (k <= 0 || k >= 1) return;
|
|
616
|
+
const g = ctx.createRadialGradient(bx, by, 0, bx, by, 26);
|
|
617
|
+
g.addColorStop(0, 'rgba(255,190,90,0.9)'); g.addColorStop(0.3, 'rgba(255,110,30,0.45)'); g.addColorStop(1, 'rgba(255,80,20,0)');
|
|
618
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.fillStyle = g; ctx.fillRect(bx - 30, by - 30, 60, 60); ctx.restore();
|
|
619
|
+
const sm = ctx.createRadialGradient(bx + 6, by - 40, 0, bx + 6, by - 40, 36);
|
|
620
|
+
sm.addColorStop(0, 'rgba(220,210,200,0.12)'); sm.addColorStop(1, 'rgba(220,210,200,0)');
|
|
621
|
+
ctx.fillStyle = sm; ctx.fillRect(bx - 40, by - 90, 90, 90);
|
|
622
|
+
};
|
|
623
|
+
ember(x0 + (x1 - x0) * b1, TY - 30 + Math.sin(t * 40) * 14, b1);
|
|
624
|
+
ember(y0 + (y1 - y0) * b2, TY + 62 + Math.sin(t * 40) * 14, b2);
|
|
625
|
+
// small burnt underline flourish + caption
|
|
626
|
+
const fl = ph(t, 5.95, 6.25);
|
|
627
|
+
if (fl > 0) {
|
|
628
|
+
ctx.save();
|
|
629
|
+
ctx.strokeStyle = 'rgba(28,12,4,0.85)'; ctx.lineWidth = 2.4; ctx.lineCap = 'round';
|
|
630
|
+
ctx.beginPath(); ctx.rect(TX - 130, TY + 100, 290 * fl, 40); ctx.clip();
|
|
631
|
+
ctx.beginPath(); ctx.moveTo(TX - 120, TY + 122); ctx.bezierCurveTo(TX - 40, TY + 112, TX + 60, TY + 132, TX + 150, TY + 116); ctx.stroke();
|
|
632
|
+
ctx.restore();
|
|
633
|
+
}
|
|
634
|
+
if (t >= 6.2) {
|
|
635
|
+
ctx.save(); ctx.font = '400 26px "Caveat"'; ctx.fillStyle = 'rgba(28,12,4,0.8)'; ctx.textAlign = 'center';
|
|
636
|
+
ctx.fillText('~ series no. 7 ~', TX + 10, TY + 160); ctx.restore();
|
|
637
|
+
}
|
|
638
|
+
ctx.restore(); // camera
|
|
639
|
+
|
|
640
|
+
// light falloff + fine film grain
|
|
641
|
+
H.vignette(ctx, W, H_, 0.42, '20,8,2');
|
|
642
|
+
H.grain(ctx, W, H_, 0.035, 12 + Math.floor(t * 12));
|
|
643
|
+
},
|
|
644
|
+
});
|
|
645
|
+
}
|