@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,799 @@
|
|
|
1
|
+
{
|
|
2
|
+
const TAU = Math.PI * 2;
|
|
3
|
+
const hx = (h) => H.hex(h);
|
|
4
|
+
|
|
5
|
+
// ---- fabric prints (u,v local px) -> rgb ----
|
|
6
|
+
const PRINTS = [
|
|
7
|
+
{ kind: 'gingham', a: hx('#c9634d'), b: hx('#f3eadb') },
|
|
8
|
+
{ kind: 'dots', a: hx('#e2b24a'), b: hx('#f7efdc') },
|
|
9
|
+
{ kind: 'stripes', a: hx('#8fae84'), b: hx('#eee4cf') },
|
|
10
|
+
{ kind: 'floral', a: hx('#d98f86'), b: hx('#f6d9cf'), c: hx('#fff5e6') },
|
|
11
|
+
{ kind: 'denim', a: hx('#5f7ea3'), b: hx('#8aa3c2') },
|
|
12
|
+
{ kind: 'linen', a: hx('#e9dcc2'), b: hx('#d8c8aa') },
|
|
13
|
+
{ kind: 'gingham', a: hx('#6f9a8a'), b: hx('#f1ecdf') },
|
|
14
|
+
{ kind: 'dots', a: hx('#b9563f'), b: hx('#f1d4bf') },
|
|
15
|
+
];
|
|
16
|
+
const printCol = (p, u, v) => {
|
|
17
|
+
switch (p.kind) {
|
|
18
|
+
case 'gingham': {
|
|
19
|
+
const a = Math.floor(u / 14) % 2, b = Math.floor(v / 14) % 2, n = a + b;
|
|
20
|
+
return n === 2 ? p.a : n === 1 ? H.mix(p.a, p.b, 0.55) : p.b;
|
|
21
|
+
}
|
|
22
|
+
case 'dots': {
|
|
23
|
+
const row = Math.floor(v / 20), ox = row % 2 ? 10 : 0;
|
|
24
|
+
const du = ((u + ox) % 20) - 10, dv = (v % 20) - 10;
|
|
25
|
+
return du * du + dv * dv < 20 ? p.b : p.a;
|
|
26
|
+
}
|
|
27
|
+
case 'stripes': return (u % 18) < 7 ? p.a : (u % 18) < 9 ? H.mix(p.a, p.b, 0.5) : p.b;
|
|
28
|
+
case 'floral': {
|
|
29
|
+
const row = Math.floor(v / 26), ox = row % 2 ? 13 : 0;
|
|
30
|
+
const du = ((u + ox) % 26) - 13, dv = (v % 26) - 13, d = du * du + dv * dv;
|
|
31
|
+
if (d < 5) return p.c;
|
|
32
|
+
const pet = [[4, 0], [-4, 0], [0, 4], [0, -4]].some(([x, y]) => (du - x) ** 2 + (dv - y) ** 2 < 8);
|
|
33
|
+
return pet ? p.b : p.a;
|
|
34
|
+
}
|
|
35
|
+
case 'denim': return ((u + v) % 5) < 2 ? p.b : p.a;
|
|
36
|
+
default: return p.a;
|
|
37
|
+
}
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const HQ = {}; // hero quilt cache (per canvas size)
|
|
41
|
+
const feltTex = (seed) => {
|
|
42
|
+
const S = 256, cv = H.layer(S, S), x = cv.getContext('2d'), r = H.rng(seed);
|
|
43
|
+
const img = x.createImageData(S, S), d = img.data;
|
|
44
|
+
for (let i = 0; i < d.length; i += 4) { const n = r(); d[i] = d[i + 1] = d[i + 2] = n > 0.5 ? 255 : 0; d[i + 3] = Math.abs(n - 0.5) * 70; }
|
|
45
|
+
x.putImageData(img, 0, 0);
|
|
46
|
+
x.lineCap = 'round';
|
|
47
|
+
for (let n = 0; n < 1400; n++) {
|
|
48
|
+
const px = r() * S, py = r() * S, a = r() * TAU, l = r.range(2, 7);
|
|
49
|
+
x.strokeStyle = r() < 0.5 ? `rgba(255,255,255,${r.range(0.08, 0.2)})` : `rgba(0,0,0,${r.range(0.08, 0.18)})`;
|
|
50
|
+
x.lineWidth = r.range(0.4, 0.9);
|
|
51
|
+
for (let oy = -1; oy <= 1; oy++) for (let ox = -1; ox <= 1; ox++) {
|
|
52
|
+
x.beginPath(); x.moveTo(px + ox * S, py + oy * S);
|
|
53
|
+
x.quadraticCurveTo(px + ox * S + Math.cos(a + 1) * l, py + oy * S + Math.sin(a + 1) * l, px + ox * S + Math.cos(a) * l * 1.6, py + oy * S + Math.sin(a) * l * 1.6);
|
|
54
|
+
x.stroke();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return cv;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
// =====================================================================
|
|
61
|
+
// EXPLAINER: the camera pans along one long quilt. Felt pieces drop on
|
|
62
|
+
// with a lifted shadow, thread sews arrows and outlines on as we watch,
|
|
63
|
+
// labels are woven tags stitched to what they name, buttons are the weights.
|
|
64
|
+
// =====================================================================
|
|
65
|
+
const FX = {};
|
|
66
|
+
const fwalk = (pts, step, fn) => {
|
|
67
|
+
let acc = 0, next = 0;
|
|
68
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
69
|
+
const [ax, ay] = pts[i], [bx, by] = pts[i + 1], L = Math.hypot(bx - ax, by - ay) || 1e-6, a = Math.atan2(by - ay, bx - ax);
|
|
70
|
+
while (next <= acc + L) { const u = (next - acc) / L; fn(ax + (bx - ax) * u, ay + (by - ay) * u, a, next); next += step; }
|
|
71
|
+
acc += L;
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const fthread = (ctx, x0, y0, x1, y1, col, w = 2.4) => {
|
|
75
|
+
const rgb = hx(col);
|
|
76
|
+
ctx.lineCap = 'round';
|
|
77
|
+
ctx.strokeStyle = 'rgba(40,25,15,0.38)'; ctx.lineWidth = w + 1.2; ctx.beginPath(); ctx.moveTo(x0 + 0.8, y0 + 1.2); ctx.lineTo(x1 + 0.8, y1 + 1.2); ctx.stroke();
|
|
78
|
+
const g = ctx.createLinearGradient(x0, y0, x1, y1);
|
|
79
|
+
g.addColorStop(0, H.rgba(rgb.map((v) => v * 0.65))); g.addColorStop(0.5, H.rgba(rgb.map((v) => Math.min(255, v * 1.1 + 10)))); g.addColorStop(1, H.rgba(rgb.map((v) => v * 0.65)));
|
|
80
|
+
ctx.strokeStyle = g; ctx.lineWidth = w; ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke();
|
|
81
|
+
ctx.fillStyle = 'rgba(30,15,10,0.45)'; ctx.beginPath(); ctx.arc(x0, y0, 1.1, 0, TAU); ctx.arc(x1, y1, 1.1, 0, TAU); ctx.fill();
|
|
82
|
+
};
|
|
83
|
+
const frun = (ctx, pts, col, len = 12, gap = 7, w = 3, pr = 1) => {
|
|
84
|
+
if (pr <= 0 || pts.length < 2) return;
|
|
85
|
+
let total = 0; for (let i = 0; i < pts.length - 1; i++) total += Math.hypot(pts[i + 1][0] - pts[i][0], pts[i + 1][1] - pts[i][1]);
|
|
86
|
+
let start = null;
|
|
87
|
+
fwalk(pts, (len + gap) / 2, (x, y, a, dd) => {
|
|
88
|
+
if (dd > total * pr) return;
|
|
89
|
+
const k = Math.round(dd / ((len + gap) / 2));
|
|
90
|
+
if (k % 2 === 0) start = [x, y]; else if (start) { fthread(ctx, start[0], start[1], start[0] + Math.cos(a) * len, start[1] + Math.sin(a) * len, col, w); start = null; }
|
|
91
|
+
});
|
|
92
|
+
};
|
|
93
|
+
// stitched arrow: running stitch shaft + two stitched head strokes once complete
|
|
94
|
+
const farrow = (ctx, pts, col, pr, w = 3.4) => {
|
|
95
|
+
frun(ctx, pts, col, 14, 7, w, pr);
|
|
96
|
+
if (pr >= 1) { const [x1, y1] = pts[pts.length - 1], [x0, y0] = pts[pts.length - 2], a = Math.atan2(y1 - y0, x1 - x0); fthread(ctx, x1, y1, x1 + Math.cos(a + 2.5) * 24, y1 + Math.sin(a + 2.5) * 24, col, w); fthread(ctx, x1, y1, x1 + Math.cos(a - 2.5) * 24, y1 + Math.sin(a - 2.5) * 24, col, w); }
|
|
97
|
+
};
|
|
98
|
+
const fcurve = (ax, ay, cx, cy, bx, by) => Array.from({ length: 31 }, (_, i) => { const u = i / 30; return [(1 - u) * (1 - u) * ax + 2 * u * (1 - u) * cx + u * u * bx, (1 - u) * (1 - u) * ay + 2 * u * (1 - u) * cy + u * u * by]; });
|
|
99
|
+
const fpat = () => FX.pat || (FX.pat = H.layer(2, 2).getContext('2d').createPattern(feltTex(55), 'repeat'));
|
|
100
|
+
// a felt piece cut to polygon pts (local coords, origin top-left), fuzz + felt texture + soft edge
|
|
101
|
+
const feltSpr = (key, pts, col, w, h, extra) => {
|
|
102
|
+
if (FX[key]) return FX[key];
|
|
103
|
+
const pad = 12, C = H.layer(Math.ceil(w + pad * 2), Math.ceil(h + pad * 2)), c = C.getContext('2d');
|
|
104
|
+
c.translate(pad, pad);
|
|
105
|
+
const path = () => { c.beginPath(); pts.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); };
|
|
106
|
+
path(); c.fillStyle = col; c.fill();
|
|
107
|
+
const fr = H.rng(key.length * 31 + w);
|
|
108
|
+
c.lineCap = 'round';
|
|
109
|
+
fwalk([...pts, pts[0]], 1.6, (x, y, a) => { const nx = Math.sin(a), ny = -Math.cos(a), l = fr.range(0.5, 2.6), tw = fr.range(-0.8, 0.8); c.strokeStyle = col; c.lineWidth = fr.range(0.5, 1.1); c.beginPath(); c.moveTo(x - nx * 1.5, y - ny * 1.5); c.lineTo(x + (nx + tw * ny) * l, y + (ny - tw * nx) * l); c.stroke(); });
|
|
110
|
+
c.save(); c.setTransform(1, 0, 0, 1, 0, 0); c.globalCompositeOperation = 'source-atop';
|
|
111
|
+
const p = fpat(); p.setTransform(new DOMMatrix().translateSelf(fr() * 256, fr() * 256)); c.fillStyle = p; c.fillRect(0, 0, C.width, C.height); c.restore();
|
|
112
|
+
c.save(); c.globalCompositeOperation = 'source-atop'; c.filter = 'blur(3px)'; c.lineWidth = 7; c.strokeStyle = 'rgba(0,0,0,0.22)'; path(); c.stroke(); c.translate(-2, -2); c.lineWidth = 4; c.strokeStyle = 'rgba(255,255,255,0.12)'; path(); c.stroke(); c.restore();
|
|
113
|
+
if (extra) extra(c);
|
|
114
|
+
C.pad = pad; C.w = w; C.h = h;
|
|
115
|
+
return (FX[key] = C);
|
|
116
|
+
};
|
|
117
|
+
// felt letters: cut glyphs with fuzzy edges, a darker cut rim and a soft padded bevel.
|
|
118
|
+
// o.back: colour of a slightly larger felt cut-out the letters are appliqued onto (layered felt, for contrast).
|
|
119
|
+
const feltText = (key, str, px, col, font = '700 PXpx "Fredoka"', o = {}) => {
|
|
120
|
+
if (FX[key]) return FX[key];
|
|
121
|
+
const f = font.replace('PX', px), m = H.layer(4, 4).getContext('2d'); m.font = f;
|
|
122
|
+
const w = Math.ceil(m.measureText(str).width) + 8, h = Math.ceil(px * 1.2), bw = o.back ? Math.round(px * 0.14) : 0, pad = 12 + bw;
|
|
123
|
+
const C = H.layer(w + pad * 2, h + pad * 2), c = C.getContext('2d'), r = H.rng(key.length * 13 + px);
|
|
124
|
+
const tx = pad + 4, ty = pad + px * 0.95, dk = H.rgba(hx(col).map((v) => v * 0.6), 0.6);
|
|
125
|
+
c.font = f; c.lineJoin = 'round';
|
|
126
|
+
if (o.back) { // backing felt: dilated glyphs, own fuzz, texture and rim, with a small shadow from the letter layer
|
|
127
|
+
const B = H.layer(C.width, C.height), b = B.getContext('2d');
|
|
128
|
+
b.font = f; b.lineJoin = 'round'; b.fillStyle = b.strokeStyle = o.back;
|
|
129
|
+
for (let i = 0; i < 18; i++) { b.globalAlpha = 0.2; b.lineWidth = bw * 2 + r.gauss() * 1.6; const ox = r.gauss() * 1.2, oy = r.gauss() * 1.2; b.strokeText(str, tx + ox, ty + oy); b.fillText(str, tx + ox, ty + oy); }
|
|
130
|
+
b.globalAlpha = 1; b.lineWidth = bw * 2 - 1; b.strokeText(str, tx, ty); b.fillText(str, tx, ty);
|
|
131
|
+
b.globalCompositeOperation = 'source-atop'; const pb = fpat(); pb.setTransform(new DOMMatrix().translateSelf(r() * 256, r() * 256)); b.fillStyle = pb; b.fillRect(0, 0, B.width, B.height);
|
|
132
|
+
{ // shaded cut edge: dark band = silhouette minus an eroded copy, softened
|
|
133
|
+
const D = H.layer(C.width, C.height), dd = D.getContext('2d');
|
|
134
|
+
dd.fillStyle = 'rgba(70,45,25,0.42)'; dd.fillRect(0, 0, D.width, D.height);
|
|
135
|
+
dd.globalCompositeOperation = 'destination-out'; dd.font = f; dd.lineJoin = 'round'; dd.lineWidth = bw * 2 - 7; dd.strokeText(str, tx + 0.8, ty + 0.8); dd.fillText(str, tx + 0.8, ty + 0.8);
|
|
136
|
+
b.filter = 'blur(2px)'; b.drawImage(D, 0, 0); b.filter = 'none';
|
|
137
|
+
}
|
|
138
|
+
c.drawImage(B, 0, 0);
|
|
139
|
+
c.save(); c.shadowColor = 'rgba(40,20,10,0.45)'; c.shadowBlur = 4; c.shadowOffsetX = 1.5; c.shadowOffsetY = 2.5;
|
|
140
|
+
}
|
|
141
|
+
const L = H.layer(C.width, C.height), l = L.getContext('2d');
|
|
142
|
+
l.font = f; l.lineJoin = 'round';
|
|
143
|
+
l.strokeStyle = dk; l.lineWidth = 3; l.strokeText(str, tx, ty); // darker cut edge under the fuzz
|
|
144
|
+
l.fillStyle = col;
|
|
145
|
+
for (let i = 0; i < 26; i++) { l.globalAlpha = 0.18; l.fillText(str, tx + r.gauss() * 1.4, ty + r.gauss() * 1.4); }
|
|
146
|
+
l.globalAlpha = 1; l.fillText(str, tx, ty);
|
|
147
|
+
l.globalCompositeOperation = 'source-atop'; const p = fpat(); p.setTransform(new DOMMatrix().translateSelf(r() * 256, r() * 256)); l.fillStyle = p; l.fillRect(0, 0, C.width, C.height);
|
|
148
|
+
// padded felt: shaded rim, light catching the top-left
|
|
149
|
+
l.filter = 'blur(2.5px)'; l.lineWidth = Math.max(4, px * 0.06); l.strokeStyle = 'rgba(30,15,5,0.3)'; l.strokeText(str, tx, ty);
|
|
150
|
+
l.lineWidth = Math.max(2, px * 0.03); l.strokeStyle = 'rgba(255,250,235,0.22)'; l.strokeText(str, tx - 1.5, ty - 1.5); l.filter = 'none';
|
|
151
|
+
c.drawImage(L, 0, 0);
|
|
152
|
+
if (o.back) c.restore();
|
|
153
|
+
C.pad = pad; C.w = w; C.h = h;
|
|
154
|
+
return (FX[key] = C);
|
|
155
|
+
};
|
|
156
|
+
// satin-stitched embroidery floss: close slanted stitches across a thick band (heavy, raised, catches the light)
|
|
157
|
+
const fsatin = (ctx, x0, y0, x1, y1, col, w = 13) => {
|
|
158
|
+
const L = Math.hypot(x1 - x0, y1 - y0); if (L < 0.5) return;
|
|
159
|
+
const rgb = hx(col), ux = (x1 - x0) / L, uy = (y1 - y0) / L, nx = -uy, ny = ux, hw = w / 2, sl = w * 0.35;
|
|
160
|
+
ctx.save(); ctx.lineCap = 'round';
|
|
161
|
+
ctx.strokeStyle = 'rgba(40,20,10,0.42)'; ctx.lineWidth = w + 3; ctx.filter = 'blur(1.5px)';
|
|
162
|
+
ctx.beginPath(); ctx.moveTo(x0 + 1.5, y0 + 2.5); ctx.lineTo(x1 + 1.5, y1 + 2.5); ctx.stroke(); ctx.filter = 'none';
|
|
163
|
+
ctx.strokeStyle = H.rgba(rgb.map((v) => v * 0.55)); ctx.lineWidth = w;
|
|
164
|
+
ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke();
|
|
165
|
+
const dark = H.rgba(rgb.map((v) => v * 0.72)), lite = H.rgba(rgb.map((v) => Math.min(255, v * 1.18 + 18))), hi = 'rgba(255,255,255,0.35)';
|
|
166
|
+
for (let d = -hw * 0.5; d <= L + hw * 0.5; d += 2.1) {
|
|
167
|
+
const cx = x0 + ux * d, cy = y0 + uy * d;
|
|
168
|
+
const ax = cx - nx * hw - ux * sl / 2, ay = cy - ny * hw - uy * sl / 2, bx = cx + nx * hw + ux * sl / 2, by = cy + ny * hw + uy * sl / 2;
|
|
169
|
+
ctx.strokeStyle = dark; ctx.lineWidth = 2.3; ctx.beginPath(); ctx.moveTo(ax, ay); ctx.lineTo(bx, by); ctx.stroke();
|
|
170
|
+
ctx.strokeStyle = lite; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.moveTo(ax - 0.4, ay - 0.6); ctx.lineTo(bx - 0.4, by - 0.6); ctx.stroke();
|
|
171
|
+
ctx.strokeStyle = hi; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.moveTo(cx - nx * hw * 0.5 - 0.5, cy - ny * hw * 0.5 - 0.7); ctx.lineTo(cx - nx * hw * 0.1 - 0.5, cy - ny * hw * 0.1 - 0.7); ctx.stroke();
|
|
172
|
+
}
|
|
173
|
+
ctx.restore();
|
|
174
|
+
};
|
|
175
|
+
// woven label with satin-stitched text
|
|
176
|
+
const tagSpr = (str, px, ink = '#2f5f9a') => {
|
|
177
|
+
const key = 'tag' + str + px + ink;
|
|
178
|
+
if (FX[key]) return FX[key];
|
|
179
|
+
const m = H.layer(4, 4).getContext('2d'); m.font = `700 ${px}px "Fredoka"`;
|
|
180
|
+
const lw = Math.ceil(m.measureText(str).width + px * 1.4), lh = Math.ceil(px * 1.75), pad = 10;
|
|
181
|
+
const C = H.layer(lw + pad * 2, lh + pad * 2), c = C.getContext('2d');
|
|
182
|
+
c.translate(pad, pad);
|
|
183
|
+
c.fillStyle = '#f3ecdc'; c.fillRect(0, 0, lw, lh);
|
|
184
|
+
c.save(); c.beginPath(); c.rect(0, 0, lw, lh); c.clip();
|
|
185
|
+
c.strokeStyle = 'rgba(120,100,70,0.12)'; c.lineWidth = 1; for (let k = -lw; k < lw; k += 3) { c.beginPath(); c.moveTo(k, 0); c.lineTo(k + lh, lh); c.stroke(); }
|
|
186
|
+
c.fillStyle = '#c8443a'; c.fillRect(0, 4, lw, 2.5); c.fillRect(0, lh - 6.5, lw, 2.5);
|
|
187
|
+
const fg = c.createLinearGradient(0, 0, 12, 0); fg.addColorStop(0, 'rgba(90,60,30,0.3)'); fg.addColorStop(1, 'rgba(90,60,30,0)'); c.fillStyle = fg; c.fillRect(0, 0, 12, lh);
|
|
188
|
+
const fg2 = c.createLinearGradient(lw, 0, lw - 12, 0); fg2.addColorStop(0, 'rgba(90,60,30,0.3)'); fg2.addColorStop(1, 'rgba(90,60,30,0)'); c.fillStyle = fg2; c.fillRect(lw - 12, 0, 12, lh);
|
|
189
|
+
c.restore();
|
|
190
|
+
const M = H.layer(lw, lh), mc = M.getContext('2d');
|
|
191
|
+
mc.font = `700 ${px}px "Fredoka"`; mc.textAlign = 'center'; mc.textBaseline = 'middle'; mc.fillStyle = '#fff'; mc.fillText(str, lw / 2, lh / 2 + 2);
|
|
192
|
+
mc.globalCompositeOperation = 'source-in'; mc.fillStyle = ink; mc.fillRect(0, 0, lw, lh);
|
|
193
|
+
mc.globalCompositeOperation = 'source-atop'; mc.lineWidth = 1;
|
|
194
|
+
for (let k = -lh; k < lw + lh; k += 2) { mc.strokeStyle = 'rgba(255,255,255,0.28)'; mc.beginPath(); mc.moveTo(k, 0); mc.lineTo(k + lh * 0.35, lh); mc.stroke(); mc.strokeStyle = 'rgba(0,0,30,0.25)'; mc.beginPath(); mc.moveTo(k + 1, 0); mc.lineTo(k + 1 + lh * 0.35, lh); mc.stroke(); }
|
|
195
|
+
c.save(); c.shadowColor = 'rgba(20,10,5,0.45)'; c.shadowBlur = 1.5; c.shadowOffsetX = 0.8; c.shadowOffsetY = 1.2; c.drawImage(M, 0, 0); c.restore();
|
|
196
|
+
frun(c, [[7, 4], [7, lh - 4]], '#c8443a', 6, 4, 1.8); frun(c, [[lw - 7, 4], [lw - 7, lh - 4]], '#c8443a', 6, 4, 1.8);
|
|
197
|
+
C.pad = pad; C.w = lw; C.h = lh;
|
|
198
|
+
return (FX[key] = C);
|
|
199
|
+
};
|
|
200
|
+
const circPts = (cx, cy, rx, ry, n = 90, wob = 0.02, seed = 0) => { const nz = FX.nz || (FX.nz = H.makeNoise(21)); return Array.from({ length: n }, (_, i) => { const a = (i / n) * TAU, k = 1 + nz.n2(Math.cos(a) + seed, Math.sin(a)) * wob; return [cx + Math.cos(a) * rx * k, cy + Math.sin(a) * ry * k]; }); };
|
|
201
|
+
|
|
202
|
+
const EXPLAIN = (ctx, W, H_, t) => {
|
|
203
|
+
const seg = (a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
204
|
+
const prog = (a, b) => H.clamp((t - a) / (b - a));
|
|
205
|
+
// drop-on: falls in large with lifted shadow, squashes, settles
|
|
206
|
+
const put = (spr, x, y, st, o = {}) => {
|
|
207
|
+
if (!spr || t < st) return;
|
|
208
|
+
const u = H.clamp((t - st) / 0.45), lift = 1 - H.ease.out(u), sq = u > 0.45 && u < 1 ? Math.sin(((u - 0.45) / 0.55) * Math.PI) * 0.04 : 0;
|
|
209
|
+
const cx = x + spr.w / 2, cy = y + spr.h / 2;
|
|
210
|
+
ctx.save();
|
|
211
|
+
ctx.shadowColor = `rgba(40,20,10,${0.45 - lift * 0.2})`; ctx.shadowBlur = (o.sb ?? 10) + lift * 26; ctx.shadowOffsetX = (o.sx ?? 4) + lift * 18; ctx.shadowOffsetY = (o.sy ?? 6) + lift * 26;
|
|
212
|
+
ctx.globalAlpha = H.clamp(u * 5);
|
|
213
|
+
ctx.translate(cx, cy - lift * 30); if (o.rot) ctx.rotate(o.rot); ctx.scale((1 + lift * 0.14 + sq) * (o.s || 1), (1 + lift * 0.14 - sq) * (o.s || 1)); ctx.translate(-cx, -cy);
|
|
214
|
+
ctx.drawImage(spr, x - spr.pad, y - spr.pad); ctx.restore();
|
|
215
|
+
};
|
|
216
|
+
// ---- camera pan along the quilt ----
|
|
217
|
+
const cam = 1280 * seg(1.9, 2.6) + 700 * seg(5.6, 6.3) + 1300 * seg(10.0, 10.7);
|
|
218
|
+
// ---- quilt (baked once, 2560 wide, tiled) ----
|
|
219
|
+
if (!FX.quilt) {
|
|
220
|
+
const QW = 2560, P = 160, C = H.layer(QW, H_), c = C.getContext('2d'), img = c.createImageData(QW, H_), d = img.data;
|
|
221
|
+
const nz = H.makeNoise(21), r = H.rng(314), cols = QW / P, rows = Math.ceil(H_ / P) + 1, pick = [];
|
|
222
|
+
for (let j = 0; j < rows; j++) for (let i = 0; i < cols; i++) { let k; do { k = r.int(0, PRINTS.length - 1); } while ((i > 0 && pick[j * cols + i - 1] === k) || (j > 0 && pick[(j - 1) * cols + i] === k)); pick.push(k); }
|
|
223
|
+
const grain = H.rng(9), lnz = H.makeNoise(77);
|
|
224
|
+
for (let y = 0; y < H_; y++) {
|
|
225
|
+
const yy = y + 30, j = Math.floor(yy / P), v = yy - j * P;
|
|
226
|
+
// broad uneven light, periodic over the 2560px tile (sampled on a cylinder so the tile wrap has no seam), every 8px
|
|
227
|
+
if (y % 8 === 0) { // coarse 8px grid rows, blended vertically below
|
|
228
|
+
FX.lg0 = FX.lg1 || null; FX.lg1 = []; const y8 = y + 8;
|
|
229
|
+
for (let x = 0; x <= QW; x += 8) { const a = (x / QW) * TAU; FX.lg1.push(0.85 + 0.15 * lnz.fbm3(Math.cos(a) * 0.9, Math.sin(a) * 0.9, y8 * 0.0035, 3)); }
|
|
230
|
+
if (!FX.lg0) { FX.lg0 = []; for (let x = 0; x <= QW; x += 8) { const a = (x / QW) * TAU; FX.lg0.push(0.85 + 0.15 * lnz.fbm3(Math.cos(a) * 0.9, Math.sin(a) * 0.9, y * 0.0035, 3)); } }
|
|
231
|
+
}
|
|
232
|
+
const yt = (y % 8) / 8, lrow = FX.lg0.map((v, q) => v + (FX.lg1[q] - v) * yt);
|
|
233
|
+
for (let x = 0; x < QW; x++) {
|
|
234
|
+
const i = Math.floor(x / P), u = x - i * P, pr = PRINTS[pick[j * cols + i]];
|
|
235
|
+
const col = H.mix(printCol(pr, Math.max(0, u + nz.n2(x * 0.02, y * 0.02) * 1.2), Math.max(0, v + nz.n2(x * 0.02 + 9, y * 0.02) * 1.2)), [214, 200, 178], 0.3);
|
|
236
|
+
const wx = x % 3, wy = y % 3, over = (Math.floor(x / 3) + Math.floor(y / 3)) & 1;
|
|
237
|
+
const wv = 1 + (over ? (wx === 1 ? 0.05 : -0.02) : (wy === 1 ? 0.05 : -0.02)) - (wx === 0 && wy === 0 ? 0.06 : 0) + (grain() - 0.5) * 0.06;
|
|
238
|
+
const e = Math.min(u, P - u, v, P - v), puff = 0.93 - 0.36 * Math.exp(-e / 14) + (0.08 * (P / 2 - u + (P / 2 - v))) / P, seam = Math.exp(-(e * e) / 2.5) * 0.45;
|
|
239
|
+
const lq = x >> 3, lf = lrow[lq] + ((lrow[lq + 1] ?? lrow[0]) - lrow[lq]) * ((x & 7) / 8);
|
|
240
|
+
const s = wv * puff * (1 - seam) * lf, k = (y * QW + x) * 4;
|
|
241
|
+
d[k] = col[0] * s; d[k + 1] = col[1] * s; d[k + 2] = col[2] * s; d[k + 3] = 255;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
c.putImageData(img, 0, 0);
|
|
245
|
+
for (let gx = 9; gx < QW; gx += P) frun(c, [[gx, -10], [gx, H_ + 10]], '#f4ead6', 10, 7, 2.4);
|
|
246
|
+
for (let gy = -21; gy < H_ + P; gy += P) frun(c, [[-10, gy], [QW + 10, gy]], '#f4ead6', 10, 7, 2.4);
|
|
247
|
+
FX.quilt = C;
|
|
248
|
+
}
|
|
249
|
+
const qx = -(cam % 2560);
|
|
250
|
+
ctx.drawImage(FX.quilt, qx, 0); ctx.drawImage(FX.quilt, qx + 2560, 0);
|
|
251
|
+
|
|
252
|
+
ctx.save(); ctx.translate(-cam, 0); // ======== world ========
|
|
253
|
+
|
|
254
|
+
// ---- BEAT 1: felt letters dropped on ----
|
|
255
|
+
{
|
|
256
|
+
const cols = ['#d9564a', '#2f5f9a', '#e8b04a', '#6fb3a6', '#ef9aa6'];
|
|
257
|
+
const line = (str, px, y, st, off) => {
|
|
258
|
+
const m = H.layer(4, 4).getContext('2d'); m.font = `700 ${px}px "Fredoka"`;
|
|
259
|
+
let x = 640 - (m.measureText(str).width + str.length * 6) / 2, i = 0;
|
|
260
|
+
for (const ch of str) { const w = m.measureText(ch).width; if (ch !== ' ') put(feltText('L' + ch + px + (i + off) % 5, ch, px, cols[(i + off) % 5]), x, y, st + i * 0.07, { rot: (H.rng(i + off)() - 0.5) * 0.12 }); x += w + 6; i++; }
|
|
261
|
+
};
|
|
262
|
+
// felt backing ovals the letters sit on
|
|
263
|
+
put(feltSpr('b1back', circPts(330, 150, 330, 150, 120, 0.03, 1), '#f3ecdc', 660, 300), 310, 150, 0.0, { sb: 16 });
|
|
264
|
+
line('How does', 100, 178, 0.15, 0);
|
|
265
|
+
line('AI learn?', 116, 296, 0.75, 2);
|
|
266
|
+
// blanket stitch around the backing, sewn on
|
|
267
|
+
const bp = prog(0.4, 1.8), ring = circPts(640, 300, 340, 160, 160, 0.03, 1);
|
|
268
|
+
for (let i = 0; i < ring.length * bp; i += 2) { const [x, y] = ring[i], dx = x - 640, dy = y - 300, d = Math.hypot(dx / 340, dy / 160) || 1, ux = dx / 340 / d, uy = dy / 160 / d, n = Math.hypot(ux, uy); fthread(ctx, x - (ux / n) * 14, y - (uy / n) * 14, x + (ux / n) * 2, y + (uy / n) * 2, '#c8443a', 2.6); if (i > 1) fthread(ctx, ring[i - 2][0], ring[i - 2][1], x, y, '#c8443a', 2.6); }
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ---- BEAT 2: labelled patches slide along a stitched path into the felt brain ----
|
|
272
|
+
const BX = 2350, BY = 380; // brain centre (world)
|
|
273
|
+
const brain = feltSpr('brain', (() => { const pts = []; for (let i = 0; i < 100; i++) { const a = (i / 100) * TAU, k = 1 + 0.08 * Math.sin(a * 7) + 0.04 * Math.sin(a * 13); pts.push([150 + Math.cos(a) * 145 * k, 115 + Math.sin(a) * 112 * k]); } return pts; })(), '#e9879a', 300, 230,
|
|
274
|
+
(c) => { [[[150, 10], [150, 220]], [[60, 70], [100, 95], [80, 130], [110, 165]], [[200, 60], [240, 85], [215, 120], [250, 150]], [[105, 40], [125, 75]], [[185, 175], [215, 200]], [[40, 120], [70, 150]]].forEach((ln) => frun(c, ln, '#a8455c', 9, 5, 2.4)); });
|
|
275
|
+
put(brain, BX - 150, BY - 115, 2.3, { sb: 20, sx: 8, sy: 12 });
|
|
276
|
+
put(tagSpr('model', 34), BX - 70, BY + 140, 2.6, { rot: -0.05 });
|
|
277
|
+
// buttons = the model's knobs (thread X turns when it adjusts)
|
|
278
|
+
const turn = H.ease.inOut(prog(8.9, 9.6));
|
|
279
|
+
[[-80, '#e8b04a'], [0, '#6fb3a6'], [80, '#d9564a']].forEach(([dx, col], i) => {
|
|
280
|
+
const x = BX + dx, y = BY + 222, R = 30, st = 2.5 + i * 0.1;
|
|
281
|
+
if (t < st) return;
|
|
282
|
+
const s = Math.max(0.15, H.ease.back(prog(st, st + 0.35))), rgb = hx(col);
|
|
283
|
+
ctx.save(); ctx.translate(x, y); ctx.scale(s, s);
|
|
284
|
+
ctx.shadowColor = 'rgba(40,20,10,0.5)'; ctx.shadowBlur = 8; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 5;
|
|
285
|
+
const g = ctx.createRadialGradient(-R * 0.35, -R * 0.4, R * 0.1, 0, 0, R); g.addColorStop(0, H.rgba(rgb.map((v) => Math.min(255, v * 1.25 + 20)))); g.addColorStop(0.7, H.rgba(rgb)); g.addColorStop(1, H.rgba(rgb.map((v) => v * 0.7)));
|
|
286
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(0, 0, R, 0, TAU); ctx.fill(); ctx.restore();
|
|
287
|
+
if (s < 0.99) return;
|
|
288
|
+
ctx.strokeStyle = H.rgba(rgb.map((v) => v * 0.6), 0.6); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(x, y, R * 0.72, 0, TAU); ctx.stroke();
|
|
289
|
+
const a = [0.2, 0.9, -0.4][i] + [1.4, -1.1, 1.6][i] * turn, hs = R * 0.3;
|
|
290
|
+
const hp = [0, 1, 2, 3].map((k) => [x + Math.cos(a + k * Math.PI / 2 + Math.PI / 4) * hs, y + Math.sin(a + k * Math.PI / 2 + Math.PI / 4) * hs]);
|
|
291
|
+
fthread(ctx, hp[0][0], hp[0][1], hp[2][0], hp[2][1], '#f4ead2', 3); fthread(ctx, hp[1][0], hp[1][1], hp[3][0], hp[3][1], '#f4ead2', 3);
|
|
292
|
+
ctx.fillStyle = 'rgba(255,255,255,0.55)'; ctx.beginPath(); ctx.ellipse(x - R * 0.45, y - R * 0.5, R * 0.18, R * 0.08, -0.6, 0, TAU); ctx.fill();
|
|
293
|
+
});
|
|
294
|
+
// example patches
|
|
295
|
+
const patch = (kind) => FX['patch' + kind] || (FX['patch' + kind] = (() => {
|
|
296
|
+
const cat = kind === 'cat';
|
|
297
|
+
const base = feltSpr('pbase' + kind, [[0, 0], [150, 0], [150, 150], [0, 150]], cat ? '#9fc6d6' : '#f1d27a', 150, 150, (c) => {
|
|
298
|
+
const head = cat ? [[30, 70], [38, 22], [62, 48], [88, 48], [112, 22], [120, 70], [112, 105], [75, 122], [38, 105]] : circPts(75, 75, 46, 42, 60, 0.02, 3);
|
|
299
|
+
c.save(); c.shadowColor = 'rgba(40,20,10,0.4)'; c.shadowBlur = 5; c.shadowOffsetX = 2; c.shadowOffsetY = 3;
|
|
300
|
+
c.beginPath(); head.forEach(([x, y], i) => (i ? c.lineTo(x, y) : c.moveTo(x, y))); c.closePath(); c.fillStyle = cat ? '#f08a3c' : '#a8673c'; c.fill();
|
|
301
|
+
if (!cat) { c.fillStyle = '#6e4428'; c.beginPath(); c.ellipse(32, 72, 13, 30, 0.25, 0, TAU); c.fill(); c.beginPath(); c.ellipse(118, 72, 13, 30, -0.25, 0, TAU); c.fill(); }
|
|
302
|
+
c.restore();
|
|
303
|
+
c.fillStyle = '#2a2433'; [[60, 70], [90, 70]].forEach(([x, y]) => { c.beginPath(); c.arc(x, y, 5.5, 0, TAU); c.fill(); });
|
|
304
|
+
c.fillStyle = cat ? '#e0607a' : '#2a2433'; c.beginPath(); c.ellipse(75, 88, 7, 5, 0, 0, TAU); c.fill();
|
|
305
|
+
frun(c, [[8, 8], [142, 8], [142, 142], [8, 142], [8, 8]], '#f4ead2', 8, 5, 2);
|
|
306
|
+
});
|
|
307
|
+
const tg = tagSpr(kind, 30, cat ? '#c8443a' : '#2f5f9a');
|
|
308
|
+
const C = H.layer(base.width, base.height + 50), c = C.getContext('2d');
|
|
309
|
+
c.drawImage(base, 0, 0);
|
|
310
|
+
c.save(); c.shadowColor = 'rgba(40,20,10,0.4)'; c.shadowBlur = 4; c.shadowOffsetX = 2; c.shadowOffsetY = 3; c.translate(base.pad + 75, base.pad + 150); c.rotate(-0.04); c.drawImage(tg, -tg.width / 2, -tg.height / 2); c.restore();
|
|
311
|
+
C.pad = base.pad; C.w = 150; C.h = 190;
|
|
312
|
+
return C;
|
|
313
|
+
})());
|
|
314
|
+
if (t < 6.4) {
|
|
315
|
+
// counter: felt digit tiles re-cut as the number climbs
|
|
316
|
+
if (t >= 2.45) {
|
|
317
|
+
const n = Math.round(1e6 * Math.pow(prog(2.7, 5.1), 2)), str = n.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
318
|
+
let x = 1280 + 50;
|
|
319
|
+
put(feltSpr('cback', [[0, 6], [440, 0], [446, 118], [4, 124]], '#2f4f7a', 446, 124, (c) => frun(c, [[10, 14], [434, 9], [438, 112], [12, 116], [10, 14]], '#f4ead2', 8, 5, 2)), 1310, 72, 2.4, { sb: 12 });
|
|
320
|
+
for (const ch of str) { const s = feltText('D' + ch, ch, 96, '#e8b04a'); put(s, x, 70, 2.45); x += s.w - 6; }
|
|
321
|
+
put(tagSpr('examples', 36, '#3f8c86'), x + 18, 104, 2.6);
|
|
322
|
+
}
|
|
323
|
+
farrow(ctx, fcurve(1300, 250, 1650, 205, 2140, 270), '#c8443a', prog(2.6, 3.4));
|
|
324
|
+
const KINDS = ['cat', 'dog', 'dog', 'cat', 'dog', 'cat', 'cat', 'dog'];
|
|
325
|
+
KINDS.forEach((k, i) => {
|
|
326
|
+
const s0 = 2.55 + i * 0.36, x = 1160 + (t - s0) * 640;
|
|
327
|
+
if (t < s0 || x > BX - 110) return;
|
|
328
|
+
const sh = H.clamp((BX - 110 - x) / 130);
|
|
329
|
+
ctx.save(); ctx.shadowColor = 'rgba(40,20,10,0.45)'; ctx.shadowBlur = 12; ctx.shadowOffsetX = 6; ctx.shadowOffsetY = 9;
|
|
330
|
+
const p = patch(k); ctx.translate(x + 75, 390 + 95); ctx.rotate(Math.sin(t * 6 + i) * 0.03); ctx.scale(sh, sh); ctx.drawImage(p, -75 - p.pad, -95 - p.pad); ctx.restore();
|
|
331
|
+
});
|
|
332
|
+
// brain re-drawn on top so patches go "into" it
|
|
333
|
+
if (t > 2.75) { ctx.save(); ctx.shadowColor = 'rgba(40,20,10,0.4)'; ctx.shadowBlur = 20; ctx.shadowOffsetX = 8; ctx.shadowOffsetY = 12; ctx.drawImage(brain, BX - 150 - brain.pad, BY - 115 - brain.pad); ctx.restore(); }
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// ---- BEAT 3: guess -> check -> adjust (a stitched loop) ----
|
|
337
|
+
if (t > 5.9) {
|
|
338
|
+
put(patch('cat'), 2000, 300, 6.25, { rot: -0.04 });
|
|
339
|
+
put(tagSpr('new photo', 28), 1990, 255, 6.4, { rot: -0.05 });
|
|
340
|
+
farrow(ctx, [[2160, 400], [2196, 400]], '#c8443a', prog(6.5, 6.7));
|
|
341
|
+
farrow(ctx, fcurve(2510, 330, 2600, 280, 2700, 320), '#c8443a', prog(6.8, 7.2));
|
|
342
|
+
put(tagSpr('Guess', 36), 2540, 210, 7.0, { rot: -0.06 });
|
|
343
|
+
const bub = feltSpr('bub', (() => { const pts = []; for (let i = 0; i <= 80; i++) { const a = 0.62 * Math.PI + (i / 80) * (2 * Math.PI - 0.22 * Math.PI); pts.push([185 + Math.cos(a) * 180, 105 + Math.sin(a) * 100]); } pts.push([55, 236]); return pts; })(), '#f6f1e7', 370, 240);
|
|
344
|
+
const wrong = t < 9.55, shake = t > 7.9 && t < 8.35 ? Math.sin(t * 70) * 7 : 0;
|
|
345
|
+
put(bub, 2740 + shake, 220, 7.25, { sb: 14 });
|
|
346
|
+
const ans = wrong ? feltText('Tdog', 'dog?', 104, '#2f5f9a') : feltText('Tcat', 'cat', 112, '#4f9a52');
|
|
347
|
+
put(ans, 2925 - ans.w / 2 + shake, 262, wrong ? 7.45 : 9.6);
|
|
348
|
+
if (wrong && t > 7.85) { // emphasis: red X + blanket-stitch ring sewn around the wrong guess
|
|
349
|
+
const xr = prog(7.85, 8.05);
|
|
350
|
+
if (xr > 0) { fsatin(ctx, 3060, 200, 3060 + 80 * xr, 200 + 80 * xr, '#c8443a', 14); if (xr > 0.5) fsatin(ctx, 3140, 200, 3140 - 80 * (xr - 0.5) * 2, 200 + 80 * (xr - 0.5) * 2, '#c8443a', 14); }
|
|
351
|
+
const ring = circPts(2925, 330, 225, 140, 120, 0.02, 9), rp = prog(7.9, 8.4);
|
|
352
|
+
for (let i = 0; i < ring.length * rp; i += 2) { const [x, y] = ring[i]; const dx = x - 2925, dy = y - 330, d = Math.hypot(dx, dy); fthread(ctx, x - (dx / d) * 12, y - (dy / d) * 12, x, y, '#c8443a', 2.6); if (i > 1) fthread(ctx, ring[i - 2][0], ring[i - 2][1], x, y, '#c8443a', 2.6); }
|
|
353
|
+
put(tagSpr('Check', 38, '#c8443a'), 2840, 485, 8.05, { rot: 0.05 });
|
|
354
|
+
farrow(ctx, fcurve(2860, 540, 2700, 660, 2480, 610), '#c8443a', prog(8.35, 8.85));
|
|
355
|
+
put(tagSpr('Adjust', 38, '#c8443a'), 2560, 640, 8.7, { rot: -0.03 });
|
|
356
|
+
}
|
|
357
|
+
if (!wrong) { const vr = prog(9.85, 10.05); if (vr > 0) { fsatin(ctx, 3070, 240, 3070 + 26 * Math.min(1, vr * 2), 240 + 26 * Math.min(1, vr * 2), '#4f9a52', 14); if (vr > 0.5) fsatin(ctx, 3096, 266, 3096 + 60 * (vr - 0.5) * 2, 266 - 70 * (vr - 0.5) * 2, '#4f9a52', 14); } }
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
// ---- BEAT 4: felt bar chart, buttons on top, accuracy counts up ----
|
|
361
|
+
if (t > 9.9) {
|
|
362
|
+
const OXw = 3600, base = 600, accs = [61, 72, 80, 86, 90, 93, 95, 97], cols = ['#e8b04a', '#6fb3a6'];
|
|
363
|
+
let shown = 61;
|
|
364
|
+
frun(ctx, [[OXw, base + 8], [OXw + 760, base + 8]], '#3d3548', 14, 7, 3.4, prog(10.5, 11.0));
|
|
365
|
+
accs.forEach((a, i) => {
|
|
366
|
+
const st = 10.65 + i * 0.25, hgt = Math.round(a * 3.6);
|
|
367
|
+
const bar = feltSpr('bar' + i, [[0, 0], [62, 0], [62, hgt], [0, hgt]], i === 7 ? '#d9564a' : cols[i % 2], 62, hgt, (c) => frun(c, [[8, 8], [54, 8], [54, hgt - 6], [8, hgt - 6], [8, 8]], '#f4ead2', 7, 5, 1.8));
|
|
368
|
+
put(bar, OXw + 30 + i * 90, base - hgt, st, { sb: 12 });
|
|
369
|
+
if (t >= st) shown = H.lerp(i ? accs[i - 1] : 61, a, prog(st, st + 0.25));
|
|
370
|
+
});
|
|
371
|
+
put(tagSpr('Accuracy', 44, '#3f8c86'), 3320, 140, 10.45, { rot: -0.03 });
|
|
372
|
+
const str = Math.round(shown) + '%', hot = t > 12.6;
|
|
373
|
+
const bounce = hot ? 1 + 0.18 * Math.sin(prog(12.6, 12.95) * Math.PI) : 1;
|
|
374
|
+
let x = 3315;
|
|
375
|
+
ctx.save(); ctx.translate(3430, 290); ctx.scale(bounce, bounce); ctx.translate(-3430, -290);
|
|
376
|
+
{ const s = feltText('A' + str + hot, str, 130, hot ? '#d9564a' : '#2f5f9a', undefined, { back: '#f4ecdc' }); put(s, x, 225, 10.5, { sb: 18, sx: 6, sy: 10 }); }
|
|
377
|
+
ctx.restore();
|
|
378
|
+
if (hot) { const star = feltSpr('star', Array.from({ length: 10 }, (_, i) => { const a = -Math.PI / 2 + (i / 10) * TAU, r = i % 2 ? 26 : 60; return [62 + Math.cos(a) * r, 62 + Math.sin(a) * r]; }), '#e8b04a', 124, 124, (c) => frun(c, Array.from({ length: 11 }, (_, i) => { const a = -Math.PI / 2 + (i / 10) * TAU, r = i % 2 ? 16 : 40; return [62 + Math.cos(a) * r, 62 + Math.sin(a) * r]; }), '#c8443a', 5, 4, 1.8)); put(star, OXw + 30 + 7 * 90 - 30, base - 97 * 3.6 - 130, 12.6, { rot: 0.1 }); }
|
|
379
|
+
put(tagSpr('training rounds', 32), 3830, 622, 11.0);
|
|
380
|
+
}
|
|
381
|
+
ctx.restore(); // ======== end world ========
|
|
382
|
+
|
|
383
|
+
// header tag + takeaway (screen space)
|
|
384
|
+
put(tagSpr('How does AI learn?', 30), 24, 16, 2.25, { rot: -0.02 });
|
|
385
|
+
if (t >= 13.0) {
|
|
386
|
+
const tg = tagSpr('More examples → better guesses', 46, '#2f5f9a');
|
|
387
|
+
put(tg, 640 - tg.w / 2, 600, 13.0, { sb: 16, rot: -0.01 });
|
|
388
|
+
}
|
|
389
|
+
// studio light + grain
|
|
390
|
+
ctx.save(); ctx.globalCompositeOperation = 'soft-light';
|
|
391
|
+
const lg = ctx.createRadialGradient(380 + t * 10, 190, 60, 600, 360, 900); lg.addColorStop(0, 'rgba(255,245,220,0.6)'); lg.addColorStop(1, 'rgba(30,20,10,0.62)');
|
|
392
|
+
ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_); ctx.restore();
|
|
393
|
+
H.vignette(ctx, W, H_, 0.55, '35,20,10');
|
|
394
|
+
H.grain(ctx, W, H_, 0.025, 6 + Math.floor(t * 12));
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
registerStyle({
|
|
398
|
+
id: 'felt-quilt',
|
|
399
|
+
name: 'Felt & Patchwork',
|
|
400
|
+
category: 'Paper & Craft',
|
|
401
|
+
description: 'A cosy patchwork quilt of cotton prints with puffy quilted squares, topped with fuzzy felt appliqué edged in blanket stitch, button "nodes" sewn on and joined by running-stitch thread, plus an embroidered label.',
|
|
402
|
+
motion: 'Thread draws itself stitch by stitch between the buttons, felt pieces drop on with a soft squash, and the quilt breathes with a slow puff of the padding.',
|
|
403
|
+
fonts: ['700 42px "Fredoka"'],
|
|
404
|
+
frameT: 4.8,
|
|
405
|
+
duration: 5,
|
|
406
|
+
readsPixels: true,
|
|
407
|
+
explainerDuration: 15,
|
|
408
|
+
explainerNote: 'The camera pans along one long patchwork quilt; felt pieces drop on with a lifted shadow and soft squash, arrows and emphasis rings are thread sewn on stitch by stitch, labels are woven tags stitched onto what they name, the counter is cut felt digits swapped as it climbs, and the model\'s weights are buttons whose thread-X turns when it adjusts.',
|
|
409
|
+
explainer(ctx, W, H_, t) { EXPLAIN(ctx, W, H_, t); },
|
|
410
|
+
// Sound: felt and buttons on a quilt. Felt pieces fall in with a lifted shadow and land as a soft, dull "fump" at
|
|
411
|
+
// the moment they touch (put(): squash starts at u = 0.45 of 0.45 s, so st + 0.2); thread sewn stitch by stitch is
|
|
412
|
+
// a needle through thick felt with the thread drawn after it (one pass about every 0.12 s); buttons click on as
|
|
413
|
+
// small wooden toks; the camera pans are the quilt drawn across the table. Times mirror EXPLAIN() above.
|
|
414
|
+
explainerSound(k) {
|
|
415
|
+
const LAND = 0.2;
|
|
416
|
+
const seg = (t, a, b) => H.ease.inOut(H.clamp((t - a) / (b - a)));
|
|
417
|
+
const cam = (t) => 1280 * seg(t, 1.9, 2.6) + 700 * seg(t, 5.6, 6.3) + 1300 * seg(t, 10.0, 10.7);
|
|
418
|
+
const pan = (wx, t) => H.clamp(((wx - cam(t)) / 1280) * 2 - 1, -1, 1) * 0.6;
|
|
419
|
+
let seed = 11; const rnd = () => ((seed = (seed * 16807) % 2147483647) / 2147483647);
|
|
420
|
+
const fump = (t, g = 1, p = 0, minor = true) => { // felt landing on quilted cotton: soft and dull
|
|
421
|
+
k.noise(t, { dur: 0.09, color: 'brown', filter: 'lowpass', f: 420, gain: 0.42 * g, a: 0.006, pan: p, minor });
|
|
422
|
+
k.noise(t, { dur: 0.05, color: 'pink', filter: 'bandpass', f: 700, q: 0.8, gain: 0.1 * g, a: 0.004, pan: p, minor });
|
|
423
|
+
k.tone(t, { f: [[0, 120], [0.07, 70]], dur: 0.09, gain: 0.12 * g, pan: p, minor });
|
|
424
|
+
};
|
|
425
|
+
const pass = (t, g = 1, p = 0, minor = true) => { // needle through felt, thread drawn after it
|
|
426
|
+
k.noise(t, { dur: 0.025, filter: 'bandpass', f: 800, q: 1.3, gain: 0.26 * g, a: 0.002, pan: p, minor });
|
|
427
|
+
const pd = 0.07 + rnd() * 0.03;
|
|
428
|
+
k.noise(t + 0.03, { dur: pd, filter: 'bandpass', f: [[0, 1400], [pd, 950]], q: 0.9, gain: 0.15 * g, a: pd * 0.6, pan: p, minor });
|
|
429
|
+
};
|
|
430
|
+
const sew = (t0, t1, g, p0, p1) => { for (let t = t0; t < t1 - 0.03; t += 0.115 + rnd() * 0.02) pass(t, g, p0 + (p1 - p0) * ((t - t0) / (t1 - t0))); };
|
|
431
|
+
const button = (t, g = 1, p = 0, minor = true) => { k.play('tock', t, { f: 520, gain: 0.8 * g, pan: p, minor }); k.noise(t, { dur: 0.04, color: 'brown', filter: 'lowpass', f: 500, gain: 0.2 * g, pan: p, minor }); };
|
|
432
|
+
const drag = (t, d, g = 1) => { // the quilt drawn along the table
|
|
433
|
+
k.noise(t, { dur: d + 0.1, color: 'pink', filter: 'bandpass', f: [[0, 320], [d * 0.5, 700], [d + 0.1, 320]], q: 0.6, gain: 0.1 * g, a: d * 0.45 });
|
|
434
|
+
k.noise(t, { dur: d + 0.1, color: 'brown', filter: 'lowpass', f: 280, gain: 0.075 * g, a: d * 0.5 });
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
k.bed('room', 0, 15, { gain: 0.4 });
|
|
438
|
+
k.music('pad', 0.2, 14.8, { bpm: 62, gain: 0.22, chords: [['C3', 'E3', 'G3', 'D4'], ['A2', 'E3', 'G3', 'C4'], ['F2', 'A2', 'C3', 'G3'], ['G2', 'B2', 'D3', 'A3']] });
|
|
439
|
+
|
|
440
|
+
// BEAT 1: backing oval, felt letters dropped on (0.15 + i*0.07, 0.75 + i*0.07), blanket stitch sewn round (0.4..1.8)
|
|
441
|
+
fump(0.0 + LAND, 1.1, 0, false);
|
|
442
|
+
{
|
|
443
|
+
const ts = [];
|
|
444
|
+
[...'How does'].forEach((ch, i) => ch !== ' ' && ts.push([0.15 + i * 0.07 + LAND, i / 8]));
|
|
445
|
+
[...'AI learn?'].forEach((ch, i) => ch !== ' ' && ts.push([0.75 + i * 0.07 + LAND, i / 9]));
|
|
446
|
+
let last = -1; ts.forEach(([t, u]) => { if (t - last >= 0.09) { fump(t, 0.55, (u - 0.5) * 0.8); last = t; } });
|
|
447
|
+
}
|
|
448
|
+
sew(0.4, 1.8, 0.6, -0.4, 0.4);
|
|
449
|
+
|
|
450
|
+
// BEAT 2
|
|
451
|
+
drag(1.9, 0.7, 1);
|
|
452
|
+
fump(2.25 + LAND, 0.5, -0.8); // header tag
|
|
453
|
+
fump(2.3 + LAND, 1.3, pan(2350, 2.5), false); // the felt brain
|
|
454
|
+
fump(2.4 + LAND, 0.8, pan(1530, 2.6)); fump(2.45 + LAND, 0.6, pan(1500, 2.65)); // counter backing + digits
|
|
455
|
+
[0, 1, 2].forEach((i) => button(2.5 + i * 0.1 + 0.13, 0.8, pan(2270 + i * 80, 2.7))); // buttons
|
|
456
|
+
fump(2.6 + LAND, 0.55, pan(2280, 2.8)); // 'model' + 'examples' tags
|
|
457
|
+
sew(2.6, 3.4, 0.75, pan(1300, 2.6), pan(2140, 3.4)); // red thread arrow
|
|
458
|
+
for (let i = 0; i < 8; i++) { const tin = 2.55 + i * 0.36 + (2350 - 110 - 1160) / 640; fump(tin, 0.4, pan(2240, tin)); } // patches tucked into the brain
|
|
459
|
+
{ // counter: felt digits swapped as n = 1e6 * prog(2.7, 5.1)^2 climbs
|
|
460
|
+
for (let t = 2.78; t < 5.04;) { const s = H.clamp((t - 2.7) / 2.4); k.noise(t, { dur: 0.04, color: 'brown', filter: 'lowpass', f: 600, gain: 0.3, a: 0.002, pan: pan(1500, t), minor: true }); k.noise(t, { dur: 0.02, filter: 'bandpass', f: 1100, q: 1.2, gain: 0.1, a: 0.001, pan: pan(1500, t), minor: true }); t += 0.15 - 0.06 * s; }
|
|
461
|
+
fump(5.1, 1.0, pan(1500, 5.1), false); button(5.1, 0.6, pan(1500, 5.1));
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// BEAT 3
|
|
465
|
+
drag(5.6, 0.7, 0.8);
|
|
466
|
+
fump(6.25 + LAND, 1.0, pan(2075, 6.45), false); // new cat patch
|
|
467
|
+
fump(6.4 + LAND, 0.5, pan(2050, 6.6));
|
|
468
|
+
sew(6.5, 6.7, 0.7, pan(2160, 6.5), pan(2196, 6.7));
|
|
469
|
+
sew(6.8, 7.2, 0.7, pan(2510, 6.8), pan(2700, 7.2));
|
|
470
|
+
fump(7.0 + LAND, 0.5, pan(2580, 7.2)); // 'Guess'
|
|
471
|
+
fump(7.25 + LAND, 0.9, pan(2925, 7.45)); // bubble
|
|
472
|
+
fump(7.45 + LAND, 0.75, pan(2925, 7.65)); // 'dog?'
|
|
473
|
+
// wrong guess (7.85): thick red thread yanked through as an X, a blanket-stitch ring sewn round it, the bubble jiggles
|
|
474
|
+
k.noise(7.85, { dur: 0.2, filter: 'bandpass', f: [[0, 1500], [0.2, 700]], q: 0.8, gain: 0.32, a: 0.01, pan: pan(3100, 7.9) });
|
|
475
|
+
k.play('thud', 7.86, { f: 110, gain: 0.6, pan: pan(3000, 7.9) });
|
|
476
|
+
k.tone(7.88, { f: 'D3', type: 'triangle', dur: 0.16, gain: 0.2, filter: 'lowpass', f2: 700 });
|
|
477
|
+
k.tone(8.04, { f: 'A2', type: 'triangle', dur: 0.32, gain: 0.22, filter: 'lowpass', f2: 600 });
|
|
478
|
+
sew(8.1, 8.4, 0.6, 0.3, 0.6);
|
|
479
|
+
fump(8.05 + LAND, 0.55, pan(2900, 8.25)); // 'Check'
|
|
480
|
+
sew(8.35, 8.85, 0.75, pan(2860, 8.35), pan(2480, 8.85)); // red thread arrow back to the buttons
|
|
481
|
+
fump(8.7 + LAND, 0.55, pan(2620, 8.9)); // 'Adjust'
|
|
482
|
+
for (let i = 1; i <= 5; i++) { let t = 8.9; while (seg(t, 8.9, 9.6) < i / 5 - 1e-6) t += 0.005; button(t, 0.45, pan(2350, t)); } // buttons twist
|
|
483
|
+
// correction: 'cat' dropped on, green check sewn (9.85..10.05)
|
|
484
|
+
fump(9.6 + LAND, 1.0, pan(2925, 9.8), false);
|
|
485
|
+
pass(9.85, 0.9, pan(3090, 9.9), false); pass(9.97, 0.7, pan(3110, 10));
|
|
486
|
+
k.play('success', 9.86, { f: 'G5', gain: 0.45, pan: pan(3090, 9.9) });
|
|
487
|
+
|
|
488
|
+
// BEAT 4
|
|
489
|
+
drag(10.0, 0.7, 1);
|
|
490
|
+
fump(10.45 + LAND, 0.55, pan(3420, 10.65)); fump(10.5 + LAND, 0.8, pan(3420, 10.7)); // 'Accuracy' tag + % digits
|
|
491
|
+
sew(10.5, 11.0, 0.5, pan(3600, 10.5), pan(4360, 11.0)); // axis thread
|
|
492
|
+
fump(11.0 + LAND, 0.45, pan(3950, 11.2)); // 'training rounds'
|
|
493
|
+
for (let i = 0; i < 8; i++) fump(10.65 + i * 0.25 + LAND, 0.6 + i * 0.04, pan(3660 + i * 90, 11 + i * 0.25)); // felt bars
|
|
494
|
+
{ // % digits swapped as `shown` climbs, at most ~7/s
|
|
495
|
+
const accs = [61, 72, 80, 86, 90, 93, 95, 97];
|
|
496
|
+
let last = -1, lt = -1;
|
|
497
|
+
for (let t = 10.66; t < 12.6; t += 1 / 120) {
|
|
498
|
+
const i = Math.min(7, Math.floor((t - 10.65) / 0.25)), st = 10.65 + i * 0.25;
|
|
499
|
+
const v = Math.round(H.lerp(i ? accs[i - 1] : 61, accs[i], H.clamp((t - st) / 0.25)));
|
|
500
|
+
if (v !== last && t - lt >= 0.14) { k.noise(t, { dur: 0.03, filter: 'bandpass', f: 1000, q: 1.2, gain: 0.16, a: 0.001, pan: -0.5, minor: true }); last = v; lt = t; }
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
// 97% (12.6): number turns red and bounces, a felt star drops on top: biggest hit
|
|
504
|
+
k.play('impact', 12.6, { gain: 1.55, pan: -0.2 });
|
|
505
|
+
k.play('ding', 12.6, { f: 'E5', gain: 0.6, pan: -0.1 });
|
|
506
|
+
fump(12.6 + LAND, 1.6, pan(4230, 12.8), false);
|
|
507
|
+
button(12.6 + LAND, 0.5, pan(4230, 12.8));
|
|
508
|
+
|
|
509
|
+
// BEAT 5: takeaway label drops on and resolves
|
|
510
|
+
fump(13.0 + LAND, 1.3, 0, false);
|
|
511
|
+
['C4', 'G4', 'E5'].forEach((f, i) => k.play('ding', 13.32 + i * 0.09, { f, gain: 0.38 - i * 0.03, pan: (i - 1) * 0.25 }));
|
|
512
|
+
},
|
|
513
|
+
draw(ctx, W, H_, t) {
|
|
514
|
+
const nz = H.makeNoise(21), r = H.rng(314);
|
|
515
|
+
const P = 160, OY = -30, OX = -40;
|
|
516
|
+
const breathe = 1 + 0.12 * Math.sin(t * 1.4 - 1.5);
|
|
517
|
+
const prog = (a, b) => H.clamp((t - a) / (b - a));
|
|
518
|
+
|
|
519
|
+
// ---------- quilt: per-pixel cotton prints, weave, puff & seams ----------
|
|
520
|
+
// The static part (print colour x weave x broad light falloff) and the puff/seam terms are cached per size;
|
|
521
|
+
// each frame only re-applies the breathing puff.
|
|
522
|
+
{
|
|
523
|
+
const key = W + 'x' + H_;
|
|
524
|
+
if (!HQ.key || HQ.key !== key) {
|
|
525
|
+
const N = W * H_, base = new Float32Array(N * 3), A = new Float32Array(N), B = new Float32Array(N);
|
|
526
|
+
const cols = Math.ceil((W - OX) / P) + 1, rows = Math.ceil((H_ - OY) / P) + 1;
|
|
527
|
+
const pick = [];
|
|
528
|
+
for (let j = 0; j < rows; j++) for (let i = 0; i < cols; i++) {
|
|
529
|
+
let k;
|
|
530
|
+
do { k = r.int(0, PRINTS.length - 1); } while ((i > 0 && pick[j * cols + i - 1] === k) || (j > 0 && pick[(j - 1) * cols + i] === k));
|
|
531
|
+
pick.push(k);
|
|
532
|
+
}
|
|
533
|
+
const grain = H.rng(9), lnz = H.makeNoise(77);
|
|
534
|
+
for (let y = 0; y < H_; y++) {
|
|
535
|
+
const yy = y - OY, j = Math.floor(yy / P), v = yy - j * P;
|
|
536
|
+
// broad, uneven light across the quilt (a real quilt never lies perfectly flat or evenly lit), sampled every 8px
|
|
537
|
+
const lrow = []; for (let x = 0; x <= W + 8; x += 8) lrow.push(0.9 + 0.1 * lnz.fbm2(x * 0.0022, y * 0.0035, 3));
|
|
538
|
+
for (let x = 0; x < W; x++) {
|
|
539
|
+
const xx = x - OX, i = Math.floor(xx / P), u = xx - i * P;
|
|
540
|
+
const pr = PRINTS[pick[j * cols + i]];
|
|
541
|
+
// slight fabric wobble in the print
|
|
542
|
+
const wu = u + nz.n2(x * 0.02, y * 0.02) * 1.2, wv = v + nz.n2(x * 0.02 + 9, y * 0.02) * 1.2;
|
|
543
|
+
const c = H.mix(printCol(pr, Math.max(0, wu), Math.max(0, wv)), [214, 200, 178], 0.3);
|
|
544
|
+
// plain weave: warp/weft over-under
|
|
545
|
+
const wx = x % 3, wy = y % 3, over = ((Math.floor(x / 3) + Math.floor(y / 3)) & 1);
|
|
546
|
+
let wv2 = 1 + (over ? (wx === 1 ? 0.05 : -0.02) : (wy === 1 ? 0.05 : -0.02)) - (wx === 0 && wy === 0 ? 0.06 : 0);
|
|
547
|
+
wv2 += (grain() - 0.5) * 0.06;
|
|
548
|
+
const lq = x >> 3, lt = (x & 7) / 8, m = wv2 * (lrow[lq] + (lrow[lq + 1] - lrow[lq]) * lt), n = y * W + x;
|
|
549
|
+
base[n * 3] = c[0] * m; base[n * 3 + 1] = c[1] * m; base[n * 3 + 2] = c[2] * m;
|
|
550
|
+
// puff: pillowy patches, lit from top-left, sinking into seams
|
|
551
|
+
const eu = Math.min(u, P - u), ev = Math.min(v, P - v), e = Math.min(eu, ev);
|
|
552
|
+
const seam = Math.exp(-(e * e) / 2.5) * 0.45;
|
|
553
|
+
A[n] = (0.95 + 0.08 * ((P / 2 - u) + (P / 2 - v)) / P) * (1 - seam);
|
|
554
|
+
B[n] = 0.36 * Math.exp(-e / 14) * (1 - seam);
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
HQ.key = key; HQ.base = base; HQ.A = A; HQ.B = B;
|
|
558
|
+
}
|
|
559
|
+
const img = ctx.createImageData(W, H_), d = img.data, base = HQ.base, A = HQ.A, B = HQ.B;
|
|
560
|
+
for (let n = 0, N = W * H_; n < N; n++) {
|
|
561
|
+
const s = A[n] - B[n] * breathe, k = n * 4, b = n * 3;
|
|
562
|
+
d[k] = base[b] * s; d[k + 1] = base[b + 1] * s; d[k + 2] = base[b + 2] * s; d[k + 3] = 255;
|
|
563
|
+
}
|
|
564
|
+
ctx.putImageData(img, 0, 0);
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// ---------- thread helpers ----------
|
|
568
|
+
const walk = (pts, step, fn) => {
|
|
569
|
+
// walk along polyline, calling fn(x,y,angle,dist)
|
|
570
|
+
let acc = 0, next = 0;
|
|
571
|
+
for (let i = 0; i < pts.length - 1; i++) {
|
|
572
|
+
const [ax, ay] = pts[i], [bx, by] = pts[i + 1], L = Math.hypot(bx - ax, by - ay), a = Math.atan2(by - ay, bx - ax);
|
|
573
|
+
while (next <= acc + L) { const u = (next - acc) / L; fn(ax + (bx - ax) * u, ay + (by - ay) * u, a, next); next += step; }
|
|
574
|
+
acc += L;
|
|
575
|
+
}
|
|
576
|
+
};
|
|
577
|
+
const threadSeg = (x0, y0, x1, y1, col, w = 2.4) => {
|
|
578
|
+
const rgb = hx(col);
|
|
579
|
+
ctx.lineCap = 'round';
|
|
580
|
+
ctx.strokeStyle = 'rgba(40,25,15,0.38)'; ctx.lineWidth = w + 1.2;
|
|
581
|
+
ctx.beginPath(); ctx.moveTo(x0 + 0.8, y0 + 1.2); ctx.lineTo(x1 + 0.8, y1 + 1.2); ctx.stroke();
|
|
582
|
+
const g = ctx.createLinearGradient(x0, y0, x1, y1);
|
|
583
|
+
g.addColorStop(0, H.rgba(rgb.map((v) => v * 0.65))); g.addColorStop(0.5, H.rgba(rgb.map((v) => Math.min(255, v * 1.1 + 10)))); g.addColorStop(1, H.rgba(rgb.map((v) => v * 0.65)));
|
|
584
|
+
ctx.strokeStyle = g; ctx.lineWidth = w;
|
|
585
|
+
ctx.beginPath(); ctx.moveTo(x0, y0); ctx.lineTo(x1, y1); ctx.stroke();
|
|
586
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.35)'; ctx.lineWidth = 0.7;
|
|
587
|
+
const nx = -(y1 - y0), ny = x1 - x0, L = Math.hypot(nx, ny) || 1;
|
|
588
|
+
ctx.beginPath(); ctx.moveTo(x0 * 0.8 + x1 * 0.2 - (nx / L) * 0.5, y0 * 0.8 + y1 * 0.2 - (ny / L) * 0.5); ctx.lineTo(x0 * 0.3 + x1 * 0.7 - (nx / L) * 0.5, y0 * 0.3 + y1 * 0.7 - (ny / L) * 0.5); ctx.stroke();
|
|
589
|
+
// holes where thread enters the cloth
|
|
590
|
+
ctx.fillStyle = 'rgba(30,15,10,0.45)';
|
|
591
|
+
ctx.beginPath(); ctx.arc(x0, y0, 1.1, 0, TAU); ctx.arc(x1, y1, 1.1, 0, TAU); ctx.fill();
|
|
592
|
+
};
|
|
593
|
+
const running = (pts, col, len = 9, gap = 6, w = 2.4, pr = 1) => {
|
|
594
|
+
if (pr <= 0) return;
|
|
595
|
+
let start = null, total = 0;
|
|
596
|
+
for (let i = 0; i < pts.length - 1; i++) total += Math.hypot(pts[i + 1][0] - pts[i][0], pts[i + 1][1] - pts[i][1]);
|
|
597
|
+
walk(pts, (len + gap) / 2, (x, y, a, dd) => {
|
|
598
|
+
const k = Math.round(dd / ((len + gap) / 2));
|
|
599
|
+
if (dd > total * pr) return;
|
|
600
|
+
if (k % 2 === 0) start = [x, y]; else if (start) { threadSeg(start[0], start[1], start[0] + Math.cos(a) * len, start[1] + Math.sin(a) * len, col, w); start = null; }
|
|
601
|
+
});
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
// quilting: running stitches echoing every seam
|
|
605
|
+
{
|
|
606
|
+
for (let gx = OX; gx < W + P; gx += P) for (const off of [9]) {
|
|
607
|
+
const pts = [[gx + off, -10], [gx + off, H_ + 10]];
|
|
608
|
+
running(pts, '#f4ead6', 10, 7, 2.4);
|
|
609
|
+
}
|
|
610
|
+
for (let gy = OY; gy < H_ + P; gy += P) for (const off of [9]) {
|
|
611
|
+
const pts = [[-10, gy + off], [W + 10, gy + off]];
|
|
612
|
+
running(pts, '#f4ead6', 10, 7, 2.4);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// ---------- felt appliqué ----------
|
|
617
|
+
const ftex = feltTex(55), fpat = ctx.createPattern(ftex, 'repeat');
|
|
618
|
+
const felt = (pts, col, o = {}) => {
|
|
619
|
+
if (o.at != null && t < o.at) return;
|
|
620
|
+
// layer sized to the piece (not the whole frame) to keep the fuzz/blur passes cheap
|
|
621
|
+
let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9; pts.forEach(([x, y]) => { x0 = Math.min(x0, x); y0 = Math.min(y0, y); x1 = Math.max(x1, x); y1 = Math.max(y1, y); });
|
|
622
|
+
const LX = Math.floor(x0) - 24, LY = Math.floor(y0) - 24, L = H.layer(Math.ceil(x1 - LX) + 24, Math.ceil(y1 - LY) + 24), c = L.getContext('2d');
|
|
623
|
+
c.translate(-LX, -LY);
|
|
624
|
+
const path = (cc) => { cc.beginPath(); pts.forEach(([x, y], i) => (i ? cc.lineTo(x, y) : cc.moveTo(x, y))); cc.closePath(); };
|
|
625
|
+
path(c); c.fillStyle = col; c.fill();
|
|
626
|
+
// fuzzy fibres poking past the cut edge
|
|
627
|
+
const fr = H.rng(o.seed || 3);
|
|
628
|
+
c.lineCap = 'round';
|
|
629
|
+
walk([...pts, pts[0]], 1.6, (x, y, a) => {
|
|
630
|
+
const nx = Math.sin(a), ny = -Math.cos(a), l = fr.range(0.5, 2.6), tw = fr.range(-0.8, 0.8);
|
|
631
|
+
c.strokeStyle = col; c.lineWidth = fr.range(0.5, 1.1);
|
|
632
|
+
c.beginPath(); c.moveTo(x - nx * 1.5, y - ny * 1.5); c.lineTo(x + (nx + tw * ny) * l * (o.outward ?? 1), y + (ny - tw * nx) * l * (o.outward ?? 1)); c.stroke();
|
|
633
|
+
});
|
|
634
|
+
c.save(); c.globalCompositeOperation = 'source-atop';
|
|
635
|
+
fpat.setTransform(new DOMMatrix().translateSelf(fr() * 256, fr() * 256));
|
|
636
|
+
c.fillStyle = fpat; c.fillRect(LX, LY, L.width, L.height);
|
|
637
|
+
// soft rounded felt edge: darker rim, lighter top-left
|
|
638
|
+
c.filter = 'blur(3px)'; c.lineWidth = 7; c.strokeStyle = 'rgba(0,0,0,0.22)'; path(c); c.stroke();
|
|
639
|
+
c.translate(-2, -2); c.lineWidth = 4; c.strokeStyle = 'rgba(255,255,255,0.12)'; path(c); c.stroke();
|
|
640
|
+
c.restore();
|
|
641
|
+
// drop-on: falls in slightly large with a lifted shadow, squashes and settles
|
|
642
|
+
const u = o.at == null ? 1 : H.clamp((t - o.at) / 0.5);
|
|
643
|
+
const lift = 1 - H.ease.out(u);
|
|
644
|
+
const sq = u < 1 ? Math.sin(Math.min(1, u * 1.6) * Math.PI) * 0.035 * (u > 0.45 ? 1 : 0) : 0;
|
|
645
|
+
const cx = pts.reduce((a, p) => a + p[0], 0) / pts.length, cy = pts.reduce((a, p) => a + p[1], 0) / pts.length;
|
|
646
|
+
ctx.save(); ctx.shadowColor = `rgba(40,20,10,${0.45 - lift * 0.2})`; ctx.shadowBlur = (o.sb ?? 12) + lift * 30; ctx.shadowOffsetX = (o.sx ?? 4) + lift * 22; ctx.shadowOffsetY = (o.sy ?? 6) + lift * 30;
|
|
647
|
+
ctx.globalAlpha = H.clamp(u * 5);
|
|
648
|
+
ctx.translate(cx, cy - lift * 24); ctx.scale(1 + lift * 0.14 + sq, 1 + lift * 0.14 - sq); ctx.translate(-cx, -cy);
|
|
649
|
+
ctx.drawImage(L, LX, LY); ctx.restore();
|
|
650
|
+
};
|
|
651
|
+
const circlePts = (cx, cy, R, n = 120, wob = 0.008, seed = 0) => Array.from({ length: n }, (_, i) => { const a = (i / n) * TAU; const k = 1 + nz.n2(Math.cos(a) + seed, Math.sin(a)) * wob; return [cx + Math.cos(a) * R * k, cy + Math.sin(a) * R * k]; });
|
|
652
|
+
const blobPts = (cx, cy, R, seed, amp, sx = 1, sy = 1, n = 70) => Array.from({ length: n }, (_, i) => { const a = (i / n) * TAU; const k = 1 + nz.fbm2(Math.cos(a) * 1.2 + seed, Math.sin(a) * 1.2 + seed, 3) * amp; return [cx + Math.cos(a) * R * k * sx, cy + Math.sin(a) * R * k * sy]; });
|
|
653
|
+
|
|
654
|
+
const G = { x: 560, y: 368, R: 206 };
|
|
655
|
+
// orbit ellipse thread (behind globe)
|
|
656
|
+
const orbit = (a0, a1) => { const pts = []; for (let a = a0; a <= a1; a += 0.02) { const x = Math.cos(a) * 360, y = Math.sin(a) * 118, rr = -0.22; pts.push([G.x + x * Math.cos(rr) - y * Math.sin(rr), G.y + 10 + x * Math.sin(rr) + y * Math.cos(rr)]); } return pts; };
|
|
657
|
+
running(orbit(Math.PI, TAU), '#f2c14e', 13, 8, 3.2, prog(1.6, 2.0));
|
|
658
|
+
// globe
|
|
659
|
+
const gpts = circlePts(G.x, G.y, G.R, 160, 0.01, 2);
|
|
660
|
+
felt(gpts, '#2f5f9a', { seed: 5, sb: 24, sx: 9, sy: 14, at: 0.1 });
|
|
661
|
+
// continents (green felt, running-stitched edges)
|
|
662
|
+
const conts = [
|
|
663
|
+
blobPts(G.x - 70, G.y - 60, 70, 3, 0.35, 1.0, 1.15), blobPts(G.x + 75, G.y + 40, 62, 8, 0.4, 0.9, 1.3), blobPts(G.x - 50, G.y + 110, 38, 12, 0.35, 1.3, 0.7), blobPts(G.x + 110, G.y - 110, 30, 17, 0.3, 1.2, 0.8),
|
|
664
|
+
];
|
|
665
|
+
// clip continents to the globe disc
|
|
666
|
+
const inside = (pts) => pts.map(([x, y]) => { const dx = x - G.x, dy = y - G.y, d = Math.hypot(dx, dy), m = G.R - 16; return d > m ? [G.x + (dx / d) * m, G.y + (dy / d) * m] : [x, y]; });
|
|
667
|
+
conts.forEach((c0, i) => {
|
|
668
|
+
const c = inside(c0);
|
|
669
|
+
felt(c, i % 2 ? '#8dbb5e' : '#73a650', { seed: 30 + i, sb: 6, sx: 2.5, sy: 3.5, at: 0.55 + i * 0.13 });
|
|
670
|
+
const inset = c.map(([x, y]) => { const cx = c.reduce((s, p) => s + p[0], 0) / c.length, cy = c.reduce((s, p) => s + p[1], 0) / c.length; const dx = x - cx, dy = y - cy, dd = Math.hypot(dx, dy) || 1; return [x - (dx / dd) * 7, y - (dy / dd) * 7]; });
|
|
671
|
+
running([...inset, inset[0]], '#f2c14e', 8, 5, 2.5, prog(1.3 + i * 0.12, 1.9 + i * 0.12));
|
|
672
|
+
});
|
|
673
|
+
// blanket stitch around the globe
|
|
674
|
+
{
|
|
675
|
+
const n = gpts.length, step = 2;
|
|
676
|
+
let prev = null;
|
|
677
|
+
const bp = prog(0.9, 1.9);
|
|
678
|
+
for (let i = 0; i < n * bp; i += step) {
|
|
679
|
+
const [x, y] = gpts[i], dx = x - G.x, dy = y - G.y, d = Math.hypot(dx, dy), ux = dx / d, uy = dy / d;
|
|
680
|
+
const ox = x + ux * 2, oy = y + uy * 2, ix = x - ux * 15, iy = y - uy * 15;
|
|
681
|
+
if (prev) threadSeg(prev[0], prev[1], ox, oy, '#f4ead2', 2.8);
|
|
682
|
+
threadSeg(ix, iy, ox, oy, '#f4ead2', 2.8);
|
|
683
|
+
prev = [ox, oy];
|
|
684
|
+
}
|
|
685
|
+
if (bp >= 1) threadSeg(prev[0], prev[1], gpts[0][0] + ((gpts[0][0] - G.x) / G.R) * 1.5, gpts[0][1] + ((gpts[0][1] - G.y) / G.R) * 1.5, '#f4ead2', 2.2);
|
|
686
|
+
}
|
|
687
|
+
// orbit front half
|
|
688
|
+
running(orbit(0, Math.PI), '#f2c14e', 13, 8, 3.2, prog(2.0, 2.4));
|
|
689
|
+
|
|
690
|
+
// ---------- buttons as network nodes, linked by thread ----------
|
|
691
|
+
const nodes = [
|
|
692
|
+
[G.x - 300, G.y - 70, 26, '#e8b04a', 4], [G.x + 330, G.y - 120, 30, '#d9564a', 4], [G.x + 250, G.y + 120, 22, '#f4efe4', 2],
|
|
693
|
+
[G.x - 210, G.y + 140, 24, '#6fb3a6', 4], [G.x - 40, G.y - 40, 20, '#f4efe4', 4], [G.x + 95, G.y + 45, 18, '#e8b04a', 2],
|
|
694
|
+
[G.x - 60, G.y + 115, 16, '#ef9aa6', 2], [G.x + 490, G.y + 20, 24, '#6fb3a6', 4], [G.x + 120, G.y - 115, 15, '#d9564a', 2],
|
|
695
|
+
];
|
|
696
|
+
const links = [[0, 4], [4, 5], [5, 2], [4, 8], [8, 1], [1, 7], [2, 7], [6, 3], [6, 5], [0, 3], [4, 6]];
|
|
697
|
+
const grow = prog(2.55, 3.7);
|
|
698
|
+
links.forEach(([a, b], li) => {
|
|
699
|
+
const A = nodes[a], B = nodes[b];
|
|
700
|
+
const mx = (A[0] + B[0]) / 2, my = (A[1] + B[1]) / 2 - 28 - (li % 3) * 8;
|
|
701
|
+
const pts = [];
|
|
702
|
+
const frac = H.clamp(grow * links.length - li * 0.6, 0, 1);
|
|
703
|
+
for (let k = 0; k <= 30 * frac; k++) { const u = k / 30; pts.push([(1 - u) * (1 - u) * A[0] + 2 * u * (1 - u) * mx + u * u * B[0], (1 - u) * (1 - u) * A[1] + 2 * u * (1 - u) * my + u * u * B[1]]); }
|
|
704
|
+
if (pts.length > 1) running(pts, '#c8443a', 12, 7, 3.2);
|
|
705
|
+
});
|
|
706
|
+
nodes.forEach(([x, y, R0, col, holes], i) => {
|
|
707
|
+
const bu = prog(2.05 + i * 0.07, 2.35 + i * 0.07);
|
|
708
|
+
if (bu <= 0) return;
|
|
709
|
+
const R = R0 * Math.max(0.15, H.ease.back(bu));
|
|
710
|
+
const rgb = hx(col);
|
|
711
|
+
ctx.save();
|
|
712
|
+
ctx.shadowColor = 'rgba(40,20,10,0.5)'; ctx.shadowBlur = 8; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 5;
|
|
713
|
+
const g = ctx.createRadialGradient(x - R * 0.35, y - R * 0.4, R * 0.1, x, y, R);
|
|
714
|
+
g.addColorStop(0, H.rgba(rgb.map((v) => Math.min(255, v * 1.25 + 20)))); g.addColorStop(0.7, H.rgba(rgb)); g.addColorStop(1, H.rgba(rgb.map((v) => v * 0.7)));
|
|
715
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(x, y, R, 0, TAU); ctx.fill();
|
|
716
|
+
ctx.restore();
|
|
717
|
+
// rim + dished centre
|
|
718
|
+
ctx.strokeStyle = H.rgba(rgb.map((v) => v * 0.6), 0.6); ctx.lineWidth = 1.5; ctx.beginPath(); ctx.arc(x, y, R * 0.72, 0, TAU); ctx.stroke();
|
|
719
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.4)'; ctx.lineWidth = 1.2; ctx.beginPath(); ctx.arc(x, y, R * 0.76, 0.2, 1.8); ctx.stroke();
|
|
720
|
+
ctx.beginPath(); ctx.arc(x, y, Math.max(0.5, R - 1.5), 3.4, 4.6); ctx.stroke();
|
|
721
|
+
// holes + thread
|
|
722
|
+
const hp = holes === 4 ? [[-1, -1], [1, -1], [1, 1], [-1, 1]] : [[-1, 0], [1, 0]];
|
|
723
|
+
const hs = R * 0.24;
|
|
724
|
+
const tc = col === '#d9564a' || col === '#ef9aa6' ? '#f4ead2' : '#c8443a';
|
|
725
|
+
if (bu < 1) return;
|
|
726
|
+
if (holes === 4) { threadSeg(x - hs, y - hs, x + hs, y + hs, tc, 2.2); threadSeg(x + hs, y - hs, x - hs, y + hs, tc, 2.2); }
|
|
727
|
+
else threadSeg(x - hs * 1.1, y, x + hs * 1.1, y, tc, 2.4);
|
|
728
|
+
hp.forEach(([a, b]) => { ctx.fillStyle = 'rgba(30,15,10,0.55)'; ctx.beginPath(); ctx.arc(x + a * hs * (holes === 2 ? 1.1 : 1), y + b * hs, R * 0.09, 0, TAU); ctx.fill(); });
|
|
729
|
+
// specular glint
|
|
730
|
+
ctx.fillStyle = 'rgba(255,255,255,0.55)'; ctx.beginPath(); ctx.ellipse(x - R * 0.45, y - R * 0.5, R * 0.18, R * 0.08, -0.6, 0, TAU); ctx.fill();
|
|
731
|
+
});
|
|
732
|
+
|
|
733
|
+
// ---------- felt heart + star accents ----------
|
|
734
|
+
{
|
|
735
|
+
const heart = []; for (let i = 0; i < 80; i++) { const a = (i / 80) * TAU; const x = 16 * Math.pow(Math.sin(a), 3), y = -(13 * Math.cos(a) - 5 * Math.cos(2 * a) - 2 * Math.cos(3 * a) - Math.cos(4 * a)); heart.push([1080 + x * 3.2, 150 + y * 3.2]); }
|
|
736
|
+
felt(heart, '#d9564a', { seed: 61, sb: 10, at: 0.3 });
|
|
737
|
+
const hi = heart.map(([x, y]) => [1080 + (x - 1080) * 0.78, 150 + (y - 150) * 0.78 + 3]);
|
|
738
|
+
running([...hi, hi[0]], '#f4ead2', 6, 5, 1.8, prog(0.9, 1.4));
|
|
739
|
+
const star = []; for (let i = 0; i < 10; i++) { const a = -Math.PI / 2 + (i / 10) * TAU, rr = i % 2 ? 17 : 38; star.push([175 + Math.cos(a) * rr, 120 + Math.sin(a) * rr]); }
|
|
740
|
+
felt(star, '#f2c14e', { seed: 62, sb: 9, at: 0.42 });
|
|
741
|
+
const si = star.map(([x, y]) => [175 + (x - 175) * 0.62, 120 + (y - 120) * 0.62 + 1]);
|
|
742
|
+
running([...si, si[0]], '#c8443a', 5, 4, 1.7, prog(1.0, 1.5));
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
// ---------- sewn-on woven label with satin-stitched lettering ----------
|
|
746
|
+
{
|
|
747
|
+
const lx = 1010, ly = 612, lw = 390, lh = 100, rot = -0.05;
|
|
748
|
+
const lu = prog(1.55, 2.0);
|
|
749
|
+
if (lu > 0) {
|
|
750
|
+
ctx.save(); ctx.translate(lx + (1 - H.ease.out(lu)) * 420, ly); ctx.rotate(rot - (1 - H.ease.out(lu)) * 0.25);
|
|
751
|
+
ctx.save(); ctx.shadowColor = 'rgba(40,20,10,0.45)'; ctx.shadowBlur = 10; ctx.shadowOffsetX = 3; ctx.shadowOffsetY = 5;
|
|
752
|
+
ctx.fillStyle = '#f3ecdc'; ctx.fillRect(-lw / 2, -lh / 2, lw, lh); ctx.restore();
|
|
753
|
+
// twill weave
|
|
754
|
+
ctx.save(); ctx.beginPath(); ctx.rect(-lw / 2, -lh / 2, lw, lh); ctx.clip();
|
|
755
|
+
ctx.strokeStyle = 'rgba(120,100,70,0.12)'; ctx.lineWidth = 1;
|
|
756
|
+
for (let k = -lw; k < lw; k += 3) { ctx.beginPath(); ctx.moveTo(k, -lh / 2); ctx.lineTo(k + lh, lh / 2); ctx.stroke(); }
|
|
757
|
+
// folded-under ends
|
|
758
|
+
const fg = ctx.createLinearGradient(-lw / 2, 0, -lw / 2 + 16, 0); fg.addColorStop(0, 'rgba(90,60,30,0.3)'); fg.addColorStop(1, 'rgba(90,60,30,0)');
|
|
759
|
+
ctx.fillStyle = fg; ctx.fillRect(-lw / 2, -lh / 2, 16, lh);
|
|
760
|
+
const fg2 = ctx.createLinearGradient(lw / 2, 0, lw / 2 - 16, 0); fg2.addColorStop(0, 'rgba(90,60,30,0.3)'); fg2.addColorStop(1, 'rgba(90,60,30,0)');
|
|
761
|
+
ctx.fillStyle = fg2; ctx.fillRect(lw / 2 - 16, -lh / 2, 16, lh);
|
|
762
|
+
// woven border lines
|
|
763
|
+
ctx.fillStyle = '#c8443a'; ctx.fillRect(-lw / 2, -lh / 2 + 6, lw, 3); ctx.fillRect(-lw / 2, lh / 2 - 9, lw, 3);
|
|
764
|
+
ctx.restore();
|
|
765
|
+
// satin-stitched text: thread lines clipped to glyphs
|
|
766
|
+
const M = H.layer(lw, lh), mc = M.getContext('2d');
|
|
767
|
+
mc.font = '700 42px "Fredoka"'; mc.textAlign = 'center'; mc.textBaseline = 'middle'; mc.fillStyle = '#fff';
|
|
768
|
+
mc.fillText('WORLD WIDE WEB', lw / 2, lh / 2 + 2);
|
|
769
|
+
mc.globalCompositeOperation = 'source-in';
|
|
770
|
+
mc.fillStyle = '#2f5f9a'; mc.fillRect(0, 0, lw, lh);
|
|
771
|
+
mc.globalCompositeOperation = 'source-atop';
|
|
772
|
+
mc.lineWidth = 1;
|
|
773
|
+
for (let k = -lh; k < lw + lh; k += 2) {
|
|
774
|
+
mc.strokeStyle = 'rgba(255,255,255,0.28)'; mc.beginPath(); mc.moveTo(k, 0); mc.lineTo(k + lh * 0.35, lh); mc.stroke();
|
|
775
|
+
mc.strokeStyle = 'rgba(0,0,30,0.25)'; mc.beginPath(); mc.moveTo(k + 1, 0); mc.lineTo(k + 1 + lh * 0.35, lh); mc.stroke();
|
|
776
|
+
}
|
|
777
|
+
// satin stitch fills the letters left to right
|
|
778
|
+
const tp = prog(2.0, 2.7);
|
|
779
|
+
ctx.save(); ctx.beginPath(); ctx.rect(-lw / 2, -lh / 2, lw * (0.12 + 0.76 * tp) + (tp >= 1 ? lw : 0), lh); ctx.clip();
|
|
780
|
+
ctx.shadowColor = 'rgba(20,10,5,0.45)'; ctx.shadowBlur = 1.5; ctx.shadowOffsetX = 0.8; ctx.shadowOffsetY = 1.2;
|
|
781
|
+
if (tp > 0) ctx.drawImage(M, -lw / 2, -lh / 2); ctx.restore();
|
|
782
|
+
ctx.restore();
|
|
783
|
+
// stitched down at both ends (in world coords)
|
|
784
|
+
const cr = Math.cos(rot), sr = Math.sin(rot), tw = (x, y) => [lx + x * cr - y * sr, ly + x * sr + y * cr];
|
|
785
|
+
running([tw(-lw / 2 + 9, -lh / 2 + 4), tw(-lw / 2 + 9, lh / 2 - 4)], '#c8443a', 7, 5, 2, prog(2.7, 2.9));
|
|
786
|
+
running([tw(lw / 2 - 9, -lh / 2 + 4), tw(lw / 2 - 9, lh / 2 - 4)], '#c8443a', 7, 5, 2, prog(2.9, 3.1));
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
// ---------- soft studio light ----------
|
|
791
|
+
ctx.save(); ctx.globalCompositeOperation = 'soft-light';
|
|
792
|
+
const lg = ctx.createRadialGradient(380 + t * 10, 190 + Math.sin(t * 0.7) * 15, 60, 600, 360, 900);
|
|
793
|
+
lg.addColorStop(0, 'rgba(255,245,220,0.55)'); lg.addColorStop(1, 'rgba(30,20,10,0.5)');
|
|
794
|
+
ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_); ctx.restore();
|
|
795
|
+
H.vignette(ctx, W, H_, 0.4, '35,20,10');
|
|
796
|
+
H.grain(ctx, W, H_, 0.025, 6 + Math.floor(t * 12));
|
|
797
|
+
},
|
|
798
|
+
});
|
|
799
|
+
}
|