@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,689 @@
|
|
|
1
|
+
{
|
|
2
|
+
// Guilloché / spirograph security print: a fictional banknote — lathe-work rosettes built from
|
|
3
|
+
// layered hypotrochoids & phase-shifted sine rings, iris (split-fountain) inks, microtext, serials.
|
|
4
|
+
const TAU = Math.PI * 2;
|
|
5
|
+
|
|
6
|
+
// PR = how much of each curve the lathe pen has traced so far (0..1); 1 = complete
|
|
7
|
+
let PR = 1;
|
|
8
|
+
const ph = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
9
|
+
function curve(ctx, n, f) {
|
|
10
|
+
if (PR <= 0) return;
|
|
11
|
+
const m = Math.floor(n * PR);
|
|
12
|
+
ctx.beginPath();
|
|
13
|
+
for (let i = 0; i <= m; i++) { const p = f(i / n); i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1]); }
|
|
14
|
+
if (m < n) { const p = f(PR); ctx.lineTo(p[0], p[1]); }
|
|
15
|
+
ctx.stroke();
|
|
16
|
+
}
|
|
17
|
+
function iris(ctx, x0, y0, x1, y1, stops) {
|
|
18
|
+
const g = ctx.createLinearGradient(x0, y0, x1, y1);
|
|
19
|
+
stops.forEach((c, i) => g.addColorStop(i / (stops.length - 1), c));
|
|
20
|
+
return g;
|
|
21
|
+
}
|
|
22
|
+
// hypotrochoid scaled so its outer radius = rad
|
|
23
|
+
function hypo(ctx, cx, cy, R, r, d, rad, rot, n) {
|
|
24
|
+
const outer = R - r + d, s = rad / outer;
|
|
25
|
+
const turns = r / gcd(R, r);
|
|
26
|
+
curve(ctx, n, (u) => {
|
|
27
|
+
const t = u * TAU * turns;
|
|
28
|
+
const x = (R - r) * Math.cos(t) + d * Math.cos(((R - r) / r) * t);
|
|
29
|
+
const y = (R - r) * Math.sin(t) - d * Math.sin(((R - r) / r) * t);
|
|
30
|
+
const c = Math.cos(rot), sn = Math.sin(rot);
|
|
31
|
+
return [cx + s * (x * c - y * sn), cy + s * (x * sn + y * c)];
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function gcd(a, b) { return b ? gcd(b, a % b) : a; }
|
|
35
|
+
|
|
36
|
+
// sine-ring band: K phase-shifted copies of r(a)=r0+amp*sin(n a + phase) -> woven rope ring
|
|
37
|
+
function ringBand(ctx, cx, cy, r0, amp, n, K, phase0, sx = 1, sy = 1) {
|
|
38
|
+
for (let k = 0; k < K; k++) {
|
|
39
|
+
const ph = phase0 + (k / K) * Math.PI;
|
|
40
|
+
curve(ctx, Math.max(600, n * 40), (u) => {
|
|
41
|
+
const a = u * TAU, rr = r0 + amp * Math.sin(n * a + ph);
|
|
42
|
+
return [cx + rr * Math.cos(a) * sx, cy + rr * Math.sin(a) * sy];
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
// lace band: rotated copies of a looping hypotrochoid
|
|
47
|
+
function laceBand(ctx, cx, cy, lobes, loop, rad, copies, rot) {
|
|
48
|
+
// R = lobes+1, r = 1 -> (R-r)/r = lobes loops, d = loop
|
|
49
|
+
for (let k = 0; k < copies; k++) hypo(ctx, cx, cy, lobes + 1, 1, loop, rad, rot + (k / copies) * (TAU / lobes), lobes * 60);
|
|
50
|
+
}
|
|
51
|
+
function rosette(ctx, cx, cy, R, ink, ink2, t, o = {}) {
|
|
52
|
+
const spin = t * 0.05, rp = o.p == null ? 1 : o.p;
|
|
53
|
+
const sub = (a, b) => (PR = H.ease.inOut(H.clamp((rp - a) / (b - a))));
|
|
54
|
+
ctx.save();
|
|
55
|
+
ctx.lineWidth = o.lw || 0.55; ctx.strokeStyle = ink;
|
|
56
|
+
// outer rope + scallop
|
|
57
|
+
sub(0.6, 1); ringBand(ctx, cx, cy, R * 0.9, R * 0.075, o.n1 || 30, o.K1 || 7, spin);
|
|
58
|
+
sub(0.7, 0.92); curve(ctx, 1600, (u) => { const a = u * TAU, rr = R * (0.99 - 0.012 * Math.abs(Math.sin((o.n1 || 30) * a))); return [cx + rr * Math.cos(a), cy + rr * Math.sin(a)]; });
|
|
59
|
+
curve(ctx, 1600, (u) => { const a = u * TAU, rr = R * 0.805; return [cx + rr * Math.cos(a), cy + rr * Math.sin(a)]; });
|
|
60
|
+
// lace petals (second ink — two-colour lathe overprint)
|
|
61
|
+
ctx.strokeStyle = ink2;
|
|
62
|
+
sub(0.32, 0.72); laceBand(ctx, cx, cy, o.lobes || 18, o.loop || 2.6, R * 0.78, o.copies || 6, -spin);
|
|
63
|
+
// pointed petal family (|cos| envelope) bridging lace and inner rope
|
|
64
|
+
ctx.strokeStyle = ink;
|
|
65
|
+
sub(0.14, 0.45);
|
|
66
|
+
if (o.petal !== false) {
|
|
67
|
+
const pn = Math.round((o.lobes || 18) / 2);
|
|
68
|
+
for (let k = 0; k < 7; k++) {
|
|
69
|
+
curve(ctx, 1800, (u) => {
|
|
70
|
+
const a = u * TAU, e = Math.abs(Math.cos(pn * a + spin * 0.5));
|
|
71
|
+
const rr = R * (0.47 + 0.11 * Math.pow(e, 0.6) * (1 - k / 9) + k * 0.006);
|
|
72
|
+
return [cx + rr * Math.cos(a), cy + rr * Math.sin(a)];
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// inner fine rope
|
|
77
|
+
sub(0, 0.3); ringBand(ctx, cx, cy, R * 0.42, R * 0.045, (o.lobes || 18) * 2, o.K2 || 6, spin * 2);
|
|
78
|
+
PR = 1;
|
|
79
|
+
ctx.restore();
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// =====================================================================================
|
|
84
|
+
// EXPLAINER — a sheet of five security-printed notes; the camera pans note to note.
|
|
85
|
+
// The model is a lathe rosette: untrained its rings are out of register (scrambled phases
|
|
86
|
+
// and centres); each correction brings the lathe passes into register until the rosette is
|
|
87
|
+
// perfect. Counters are red numbering-machine serials; verdicts are rubber cancellation stamps.
|
|
88
|
+
// =====================================================================================
|
|
89
|
+
const SX = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
90
|
+
const GRN = '#173e38', RED = '#b3262b', PAPERC = '#ebe6cf';
|
|
91
|
+
let SPBG = null;
|
|
92
|
+
function spPaper(W, H_) {
|
|
93
|
+
if (SPBG) return SPBG;
|
|
94
|
+
SPBG = H.layer(W, H_); const c = SPBG.getContext('2d'), r = H.rng(77);
|
|
95
|
+
H.paper(c, W, H_, '#e8e4cf', 9, 0.05);
|
|
96
|
+
const g = c.createLinearGradient(0, 0, W, 0);
|
|
97
|
+
g.addColorStop(0, 'rgba(120,170,140,0.20)'); g.addColorStop(0.5, 'rgba(200,190,140,0.10)'); g.addColorStop(1, 'rgba(220,140,120,0.18)');
|
|
98
|
+
c.fillStyle = g; c.fillRect(0, 0, W, H_);
|
|
99
|
+
for (let i = 0; i < 140; i++) {
|
|
100
|
+
const x = r() * W, y = r() * H_, a = r() * TAU, L = 6 + r() * 10;
|
|
101
|
+
c.strokeStyle = r() < 0.5 ? 'rgba(200,40,60,0.55)' : 'rgba(40,80,190,0.5)'; c.lineWidth = 0.6;
|
|
102
|
+
c.beginPath(); c.moveTo(x, y); c.quadraticCurveTo(x + Math.cos(a + 1) * L * 0.6, y + Math.sin(a + 1) * L * 0.6, x + Math.cos(a) * L, y + Math.sin(a) * L); c.stroke();
|
|
103
|
+
}
|
|
104
|
+
c.lineWidth = 0.5; c.strokeStyle = iris(c, 0, 0, W, 0, ['rgba(40,120,100,0.30)', 'rgba(70,90,150,0.28)', 'rgba(170,70,90,0.28)']);
|
|
105
|
+
for (let k = 0; k < 2; k++) for (let y0 = -20; y0 < H_ + 20; y0 += 8) curve(c, 320, (u) => { const x = u * W; return [x, y0 + 4.5 * Math.sin(x / 26 + y0 / 37 + k * Math.PI) + 6 * Math.sin(x / 210 + y0 / 90)]; });
|
|
106
|
+
// rope frame
|
|
107
|
+
const m = 22, bw = 26;
|
|
108
|
+
c.fillStyle = 'rgba(232,228,207,0.92)'; c.fillRect(m, m, W - 2 * m, bw); c.fillRect(m, H_ - m - bw, W - 2 * m, bw); c.fillRect(m, m, bw, H_ - 2 * m); c.fillRect(W - m - bw, m, bw, H_ - 2 * m);
|
|
109
|
+
c.lineWidth = 0.6; c.strokeStyle = iris(c, 0, 0, W, H_, ['#1d5a48', '#2c4f7c', '#6b2f4f']);
|
|
110
|
+
const side = (x0, y0, x1, y1) => { const L = Math.hypot(x1 - x0, y1 - y0), tx = (x1 - x0) / L, ty = (y1 - y0) / L; for (let k = 0; k < 6; k++) curve(c, Math.floor(L / 2), (u) => { const s = u * L, off = (bw / 2 - 2) * Math.sin(s / 9 + (k / 6) * Math.PI); return [x0 + tx * s - ty * off, y0 + ty * s + tx * off]; }); };
|
|
111
|
+
side(m, m + bw / 2, W - m, m + bw / 2); side(m, H_ - m - bw / 2, W - m, H_ - m - bw / 2); side(m + bw / 2, m, m + bw / 2, H_ - m); side(W - m - bw / 2, m, W - m - bw / 2, H_ - m);
|
|
112
|
+
c.strokeStyle = '#1f3f3a'; c.lineWidth = 1.2; c.strokeRect(m, m, W - 2 * m, H_ - 2 * m); c.lineWidth = 0.7; c.strokeRect(m + bw + 3, m + bw + 3, W - 2 * (m + bw + 3), H_ - 2 * (m + bw + 3));
|
|
113
|
+
return SPBG;
|
|
114
|
+
}
|
|
115
|
+
// engraved text: filled with fine hatching, outlined; small offscreen layer per call
|
|
116
|
+
function engr(c, text, x, y, font, o = {}) {
|
|
117
|
+
c.save(); c.font = font; const m = c.measureText(text), w = Math.ceil(m.width) + 8, asc = Math.ceil(m.actualBoundingBoxAscent) + 4, h = asc + Math.ceil(m.actualBoundingBoxDescent) + 6; c.restore();
|
|
118
|
+
const ox = o.align === 'center' ? x - w / 2 : o.align === 'right' ? x - w : x;
|
|
119
|
+
const L = H.layer(w, h), lc = L.getContext('2d');
|
|
120
|
+
lc.font = font; lc.textBaseline = 'alphabetic'; lc.fillStyle = '#000'; lc.fillText(text, 4, asc);
|
|
121
|
+
lc.globalCompositeOperation = 'source-in'; lc.fillStyle = o.bg || PAPERC; lc.fillRect(0, 0, w, h);
|
|
122
|
+
lc.globalCompositeOperation = 'source-atop'; lc.strokeStyle = o.ink || GRN; lc.lineWidth = o.hw || 1.45;
|
|
123
|
+
const st = o.step || 2.2; for (let yy = 0; yy < h + 10; yy += st) { lc.beginPath(); lc.moveTo(0, yy); lc.lineTo(w, yy + w * 0.04); lc.stroke(); }
|
|
124
|
+
c.drawImage(L, ox, y - asc);
|
|
125
|
+
c.save(); c.font = font; c.textBaseline = 'alphabetic'; c.strokeStyle = o.ink || GRN; c.lineWidth = o.lw || 1.6; c.strokeText(text, ox + 4, y); c.restore();
|
|
126
|
+
return w;
|
|
127
|
+
}
|
|
128
|
+
function plain(c, text, x, y, font, o = {}) {
|
|
129
|
+
c.save(); c.font = font; c.fillStyle = o.ink || GRN; c.textAlign = o.align || 'left'; c.textBaseline = 'alphabetic';
|
|
130
|
+
try { c.letterSpacing = o.ls || '0px'; } catch (e) {} c.fillText(text, x, y); const w = c.measureText(text).width; c.restore(); return w;
|
|
131
|
+
}
|
|
132
|
+
// red numbering-machine digits: each digit wheel sits slightly off the baseline
|
|
133
|
+
function serial(c, text, x, y, size) {
|
|
134
|
+
c.save(); c.font = `700 ${size}px "Courier Prime"`; c.fillStyle = RED; c.textBaseline = 'alphabetic';
|
|
135
|
+
let xx = x; const r = H.rng(5);
|
|
136
|
+
for (const ch of text) { c.fillText(ch, xx, y + (/\d/.test(ch) ? (r() - 0.5) * size * 0.08 : 0)); xx += c.measureText(ch).width + size * 0.08; }
|
|
137
|
+
c.restore(); return xx - x;
|
|
138
|
+
}
|
|
139
|
+
// rubber cancellation stamp: red double ellipse with text, rough coverage
|
|
140
|
+
function stamp(c, text, x, y, w, h, rot, k) {
|
|
141
|
+
if (k <= 0) return; const s = 1 + 0.5 * Math.pow(1 - k, 2);
|
|
142
|
+
c.save(); c.translate(x, y); c.rotate(rot); c.scale(s, s); c.globalAlpha = 0.85 * Math.min(1, k * 2);
|
|
143
|
+
c.strokeStyle = RED; c.fillStyle = RED; c.lineWidth = 5; c.beginPath(); c.ellipse(0, 0, w / 2, h / 2, 0, 0, TAU); c.stroke();
|
|
144
|
+
c.lineWidth = 2; c.beginPath(); c.ellipse(0, 0, w / 2 - 9, h / 2 - 9, 0, 0, TAU); c.stroke();
|
|
145
|
+
c.font = `700 ${h * 0.34}px "Courier Prime"`; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(text, 0, 2);
|
|
146
|
+
c.globalCompositeOperation = 'destination-out'; const r = H.rng(13);
|
|
147
|
+
for (let i = 0; i < 90; i++) { c.globalAlpha = 0.5; c.fillRect(r.range(-w / 2, w / 2), r.range(-h / 2, h / 2), r.range(1, 4), r.range(1, 3)); }
|
|
148
|
+
c.restore();
|
|
149
|
+
}
|
|
150
|
+
// engraved double-rule arrow with hatched head
|
|
151
|
+
function eArrow(c, x0, y0, x1, y1, p, col = GRN) {
|
|
152
|
+
if (p <= 0) return; const x = x0 + (x1 - x0) * p, y = y0 + (y1 - y0) * p, a = Math.atan2(y1 - y0, x1 - x0), nx = -Math.sin(a) * 3, ny = Math.cos(a) * 3, hd = 22;
|
|
153
|
+
c.save(); c.strokeStyle = col; c.lineWidth = 1.2;
|
|
154
|
+
const bx = x - Math.cos(a) * hd * 0.8, by = y - Math.sin(a) * hd * 0.8;
|
|
155
|
+
c.beginPath(); c.moveTo(x0 + nx, y0 + ny); c.lineTo(bx + nx, by + ny); c.moveTo(x0 - nx, y0 - ny); c.lineTo(bx - nx, by - ny); c.stroke();
|
|
156
|
+
const p1 = [x - Math.cos(a - 0.45) * hd, y - Math.sin(a - 0.45) * hd], p2 = [x - Math.cos(a + 0.45) * hd, y - Math.sin(a + 0.45) * hd];
|
|
157
|
+
c.beginPath(); c.moveTo(x, y); c.lineTo(p1[0], p1[1]); c.lineTo(p2[0], p2[1]); c.closePath(); c.stroke(); c.save(); c.clip();
|
|
158
|
+
c.lineWidth = 0.8; for (let k = -30; k < 30; k += 2.4) { c.beginPath(); c.moveTo(x + k, y - 30); c.lineTo(x + k + 20, y + 30); c.stroke(); } c.restore();
|
|
159
|
+
c.restore();
|
|
160
|
+
}
|
|
161
|
+
// oval cartouche with a hatched pet silhouette + microtext label
|
|
162
|
+
function cartouche(c, kind, cx, cy, s, label) {
|
|
163
|
+
c.save(); c.fillStyle = PAPERC; c.beginPath(); c.ellipse(cx, cy, s * 0.5, s * 0.6, 0, 0, TAU); c.fill();
|
|
164
|
+
c.strokeStyle = GRN; c.lineWidth = 0.6; for (let k = 0; k < 3; k++) { c.beginPath(); c.ellipse(cx, cy, s * 0.5 - k * 2.5, s * 0.6 - k * 2.5, 0, 0, TAU); c.stroke(); }
|
|
165
|
+
c.beginPath(); c.arc(cx, cy + s * 0.06, s * 0.26, 0, TAU);
|
|
166
|
+
if (kind === 'cat') for (const k of [-1, 1]) { c.moveTo(cx + k * s * 0.05, cy - s * 0.12); c.lineTo(cx + k * s * 0.25, cy - s * 0.34); c.lineTo(cx + k * s * 0.26, cy - s * 0.02); c.closePath(); }
|
|
167
|
+
else for (const k of [-1, 1]) { c.moveTo(cx + k * s * 0.27, cy - s * 0.1); c.ellipse(cx + k * s * 0.27, cy + s * 0.08, s * 0.08, s * 0.2, k * 0.3, -Math.PI / 2, Math.PI * 1.5); }
|
|
168
|
+
c.save(); c.clip(); c.lineWidth = Math.max(0.7, s * 0.012);
|
|
169
|
+
for (let y = cy - s * 0.4; y < cy + s * 0.4; y += Math.max(1.6, s * 0.03)) { c.beginPath(); c.moveTo(cx - s * 0.4, y); c.lineTo(cx + s * 0.4, y); c.stroke(); }
|
|
170
|
+
c.restore(); c.lineWidth = 1.2; c.stroke();
|
|
171
|
+
c.fillStyle = PAPERC; for (const k of [-1, 1]) { c.beginPath(); c.arc(cx + k * s * 0.1, cy + s * 0.02, s * 0.04, 0, TAU); c.fill(); }
|
|
172
|
+
if (label) plain(c, label, cx, cy + s * 0.6 + 26, '700 22px "Libre Baskerville"', { align: 'center', ls: '2px' });
|
|
173
|
+
c.restore();
|
|
174
|
+
}
|
|
175
|
+
// the model: a lathe rosette whose passes are out of register by `chaos`
|
|
176
|
+
function modelRosette(c, cx, cy, R, chaos, t, draw = 1) {
|
|
177
|
+
const r = H.rng(404);
|
|
178
|
+
c.save(); c.fillStyle = 'rgba(236,232,212,0.88)'; c.beginPath(); c.arc(cx, cy, R * 1.03, 0, TAU); c.fill();
|
|
179
|
+
PR = H.ease.inOut(H.clamp(draw)); c.lineWidth = 0.6;
|
|
180
|
+
const ink1 = iris(c, cx - R, cy - R, cx + R, cy + R, ['#145243', '#1f6b6a', '#2f4f8f', '#5a2f6e']);
|
|
181
|
+
const ink2 = iris(c, cx, cy - R, cx, cy + R, ['#b24a2c', '#c98a3a', '#9a3d5e']);
|
|
182
|
+
const pass = (rad, amp, n, K, ink) => {
|
|
183
|
+
c.strokeStyle = ink;
|
|
184
|
+
for (let k = 0; k < K; k++) {
|
|
185
|
+
const ph = (k / K) * Math.PI + chaos * r.range(-2, 2), ox = chaos * r.range(-1, 1) * R * 0.2, oy = chaos * r.range(-1, 1) * R * 0.2, sc = 1 + chaos * r.range(-0.16, 0.16), nn = n + (chaos > 0.35 ? r.int(-2, 2) : 0);
|
|
186
|
+
curve(c, Math.max(600, nn * 40), (u) => { const a = u * TAU, rr = (rad + amp * Math.sin(nn * a + ph + t * 0.05)) * sc; return [cx + ox + rr * Math.cos(a), cy + oy + rr * Math.sin(a)]; });
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
pass(R * 0.9, R * 0.075, 30, 7, ink1);
|
|
190
|
+
pass(R * 0.64, R * 0.1, 22, 6, ink2);
|
|
191
|
+
pass(R * 0.4, R * 0.05, 36, 5, ink1);
|
|
192
|
+
PR = 1;
|
|
193
|
+
c.strokeStyle = GRN; c.lineWidth = 1.4; c.beginPath(); c.arc(cx, cy, R * 0.22, 0, TAU); c.fillStyle = PAPERC; c.fill(); c.stroke();
|
|
194
|
+
c.lineWidth = 0.5; for (let rr = 3; rr < R * 0.22; rr += 2.2) { c.beginPath(); c.arc(cx, cy, rr, 0, TAU); c.stroke(); }
|
|
195
|
+
c.fillStyle = PAPERC; c.fillRect(cx - R * 0.11, cy - R * 0.08, R * 0.22, R * 0.16);
|
|
196
|
+
c.restore();
|
|
197
|
+
plain(c, 'AI', cx, cy + R * 0.05, `600 ${R * 0.1}px "Cormorant Garamond"`, { align: 'center' });
|
|
198
|
+
}
|
|
199
|
+
// a woven sine-rope band running round a rectangle (x,y,w,h = outer edge, bw = band width), like the note border
|
|
200
|
+
function ropeRect(c, x, y, w, h, bw, ink, strands = 5, per = 9) {
|
|
201
|
+
c.save(); c.strokeStyle = ink; c.lineWidth = 0.55;
|
|
202
|
+
const side = (x0, y0, x1, y1) => { const L = Math.hypot(x1 - x0, y1 - y0), tx = (x1 - x0) / L, ty = (y1 - y0) / L; for (let k = 0; k < strands; k++) curve(c, Math.floor(L / 2), (u) => { const s = u * L, off = (bw / 2 - 1.5) * Math.sin(s / per + (k / strands) * Math.PI); return [x0 + tx * s - ty * off, y0 + ty * s + tx * off]; }); };
|
|
203
|
+
const h2 = bw / 2;
|
|
204
|
+
side(x, y + h2, x + w, y + h2); side(x, y + h - h2, x + w, y + h - h2); side(x + h2, y, x + h2, y + h); side(x + w - h2, y, x + w - h2, y + h);
|
|
205
|
+
c.restore();
|
|
206
|
+
}
|
|
207
|
+
// a line of microprint repeated to fill a width, centred on cx
|
|
208
|
+
function micro(c, text, cx, y, w, ink) {
|
|
209
|
+
c.save(); c.font = '600 7px "Inter"'; c.fillStyle = ink || GRN; c.textBaseline = 'alphabetic';
|
|
210
|
+
try { c.letterSpacing = '0.6px'; } catch (e) {}
|
|
211
|
+
let line = ''; while (c.measureText(line + text).width < w) line += text;
|
|
212
|
+
c.textAlign = 'center'; c.fillText(line, cx, y); c.restore();
|
|
213
|
+
}
|
|
214
|
+
function spHeader(c) {
|
|
215
|
+
// running head: a small engraved cartouche with a rope edge (30px italic)
|
|
216
|
+
const x = 54, y = 54, w = 330, h = 52;
|
|
217
|
+
c.save(); c.fillStyle = 'rgba(235,230,207,0.95)'; c.fillRect(x, y, w, h);
|
|
218
|
+
ropeRect(c, x, y, w, h, 9, iris(c, x, 0, x + w, 0, ['#1d5a48', '#2c4f7c', '#6b2f4f']), 3, 4.5);
|
|
219
|
+
c.strokeStyle = GRN; c.lineWidth = 0.9; c.strokeRect(x, y, w, h); c.lineWidth = 0.6; c.strokeRect(x + 10, y + 10, w - 20, h - 20);
|
|
220
|
+
c.restore();
|
|
221
|
+
plain(c, 'How does AI learn?', x + w / 2, y + 36, 'italic 400 28px "Libre Baskerville"', { align: 'center' });
|
|
222
|
+
}
|
|
223
|
+
const fmtS = (n) => Math.round(n).toLocaleString('en-US');
|
|
224
|
+
|
|
225
|
+
// ---------- the five notes (drawn at panel offset ox) ----------
|
|
226
|
+
function note0(c, t) { // hook
|
|
227
|
+
modelRosette(c, 640, 380, 250, 0, t, SX(t, 0.0, 1.9));
|
|
228
|
+
const k = H.ease.back(SX(t, 0.15, 0.6)), z = 1 + 0.05 * H.ease.inOut(SX(t, 0.6, 2.4));
|
|
229
|
+
c.save(); c.translate(640, 300); c.scale(k * z, k * z);
|
|
230
|
+
c.fillStyle = 'rgba(235,230,207,0.94)'; c.fillRect(-470, -70, 940, 120); c.strokeStyle = GRN; c.lineWidth = 0.8; c.strokeRect(-462, -62, 924, 104);
|
|
231
|
+
engr(c, 'HOW DOES AI LEARN?', 0, 20, '700 60px "Libre Baskerville"', { align: 'center' });
|
|
232
|
+
c.restore();
|
|
233
|
+
plain(c, 'Certificate of the Neural Reserve · Series A', 640, 210, 'italic 400 16px "Libre Baskerville"', { align: 'center' });
|
|
234
|
+
}
|
|
235
|
+
function note1(c, t) { // data
|
|
236
|
+
const rows = [200, 340, 480];
|
|
237
|
+
rows.forEach((y, i) => eArrow(c, 90, y, 770, y, H.ease.out(SX(t, 2.6 + i * 0.08, 3.1 + i * 0.08))));
|
|
238
|
+
const r = H.rng(31), K = 30;
|
|
239
|
+
for (let k = 0; k < K; k++) {
|
|
240
|
+
const kind = r() < 0.5 ? 'cat' : 'dog', ts = 2.55 + 3.1 * Math.pow(k / K, 0.8);
|
|
241
|
+
if (t < ts) continue; const x = 60 + (t - ts) * (470 + k * 14); if (x > 820) continue;
|
|
242
|
+
const fade = H.clamp((820 - x) / 60); c.save(); c.globalAlpha = fade; cartouche(c, kind, x, rows[k % 3], 76, kind.toUpperCase()); c.restore();
|
|
243
|
+
}
|
|
244
|
+
modelRosette(c, 1000, 330, 200, 1, t, SX(t, 2.15, 3.3));
|
|
245
|
+
if (t > 3.2) { engr(c, 'THE MODEL', 1000, 580 + (1 - H.ease.back(SX(t, 3.2, 3.5))) * 30, '700 34px "Libre Baskerville"', { align: 'center' }); plain(c, 'untrained: out of register', 1000, 614, 'italic 400 22px "Libre Baskerville"', { align: 'center' }); }
|
|
246
|
+
if (t > 2.7) {
|
|
247
|
+
const p = SX(t, 2.75, 5.0), n = p >= 1 ? 1e6 : Math.floor(Math.pow(10, 6 * H.ease.inOut(p))) - 1;
|
|
248
|
+
const w = serial(c, 'No. ' + fmtS(n), 90, 650, 64);
|
|
249
|
+
plain(c, 'EXAMPLES', 90 + w + 14, 646, '700 28px "Libre Baskerville"', { ls: '3px' });
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function note2(c, t) { // guess / check / adjust
|
|
253
|
+
cartouche(c, 'cat', 190, 300, 170, null);
|
|
254
|
+
plain(c, t >= 8.0 ? 'ANSWER: CAT' : 'NEW PICTURE', 190, 446, '700 24px "Libre Baskerville"', { align: 'center', ls: '2px', ink: t >= 8.0 ? RED : GRN });
|
|
255
|
+
eArrow(c, 300, 300, 420, 300, H.ease.out(SX(t, 6.5, 6.75)));
|
|
256
|
+
const adj = H.ease.inOut(SX(t, 8.9, 9.45));
|
|
257
|
+
modelRosette(c, 610, 300, 175, 0.8 - 0.5 * adj, t, 1);
|
|
258
|
+
eArrow(c, 800, 300, 880, 300, H.ease.out(SX(t, 7.05, 7.25)));
|
|
259
|
+
const right = t >= 9.6;
|
|
260
|
+
if (t >= 7.25 && !right) { engr(c, 'DOG?', 900, 330, '700 84px "Libre Baskerville"'); plain(c, 'GUESS', 990, 222 + (1 - H.ease.back(SX(t, 7.3, 7.55))) * -30, '700 28px "Libre Baskerville"', { align: 'center', ls: '4px' }); }
|
|
261
|
+
if (!right) stamp(c, 'WRONG ✗', 1050, 405, 230, 100, -0.18, SX(t, 7.9, 8.05));
|
|
262
|
+
if (t >= 8.35 && t < 9.6) { // error signal: red dotted run back to the lathe
|
|
263
|
+
const k = H.ease.inOut(SX(t, 8.35, 8.9));
|
|
264
|
+
c.save(); c.strokeStyle = RED; c.lineWidth = 2.5; c.setLineDash([3, 6]); c.beginPath();
|
|
265
|
+
const pts = [[1050, 455], [1050, 520], [610, 520], [610, 485]]; let L = 0; for (let i = 1; i < pts.length; i++) L += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]);
|
|
266
|
+
let left = L * k; c.moveTo(1050, 455); for (let i = 1; i < pts.length && left > 0; i++) { const d = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]), q = Math.min(1, left / d); c.lineTo(pts[i - 1][0] + (pts[i][0] - pts[i - 1][0]) * q, pts[i - 1][1] + (pts[i][1] - pts[i - 1][1]) * q); left -= d; }
|
|
267
|
+
c.stroke(); c.restore();
|
|
268
|
+
plain(c, 'error sent back', 830, 556, 'italic 400 26px "Libre Baskerville"', { align: 'center', ink: RED });
|
|
269
|
+
}
|
|
270
|
+
if (t >= 8.85 && t < 10.5) plain(c, 'ADJUST: bring into register', 610, 112 + (1 - H.ease.back(SX(t, 8.85, 9.1))) * -30, '700 26px "Libre Baskerville"', { align: 'center', ls: '1px', ink: RED });
|
|
271
|
+
if (right) { engr(c, 'CAT', 900, 330, '700 84px "Libre Baskerville"'); stamp(c, 'VERIFIED ✓', 1010, 410, 250, 100, 0.12, SX(t, 9.7, 9.85)); }
|
|
272
|
+
// loop strip
|
|
273
|
+
const words = ['GUESS', 'CHECK', 'ADJUST'];
|
|
274
|
+
const act = t < 7.25 ? -1 : t < 7.9 ? 0 : t < 8.35 ? 1 : t < 9.6 ? 2 : Math.floor((t - 9.6) / 0.16) % 3;
|
|
275
|
+
let x = 110;
|
|
276
|
+
words.forEach((w, i) => { x += engr(c, w, x, 640, '700 38px "Libre Baskerville"', { ink: i === act ? RED : GRN }) + 10; if (i < 2) { eArrow(c, x, 626, x + 50, 626, 1); x += 66; } });
|
|
277
|
+
plain(c, '↻ ×1,000,000', x + 10, 638, 'italic 400 26px "Libre Baskerville"');
|
|
278
|
+
}
|
|
279
|
+
function note3(c, t) { // progress
|
|
280
|
+
const p = H.ease.inOut(SX(t, 10.85, 12.3)), v = Math.round(61 + 36 * p);
|
|
281
|
+
plain(c, 'ACCURACY', 120, 170, '700 34px "Libre Baskerville"', { ls: '6px' });
|
|
282
|
+
const punch = t >= 12.3 ? 1 + 0.12 * Math.exp(-(t - 12.3) * 9) : 1;
|
|
283
|
+
c.save(); c.translate(120, 360); c.scale(punch, punch); engr(c, v + '%', 0, 0, '800 170px "Playfair Display"', { hw: 1.4, lw: 2 }); c.restore();
|
|
284
|
+
if (t >= 12.3) stamp(c, 'CERTIFIED', 330, 455, 280, 90, -0.08, SX(t, 12.3, 12.45));
|
|
285
|
+
// guilloché chart: a rope band climbing over training rounds
|
|
286
|
+
const x0 = 110, x1 = 620, y0 = 620, y1 = 470, k = H.ease.inOut(SX(t, 10.9, 12.3));
|
|
287
|
+
c.save(); c.strokeStyle = iris(c, x0, 0, x1, 0, ['#2f4f8f', '#1f6b6a', '#b24a2c']); c.lineWidth = 0.7;
|
|
288
|
+
const f = (u) => y0 - (y0 - y1 - 30) * (1 - Math.exp(-u * 3.5)) / (1 - Math.exp(-3.5));
|
|
289
|
+
PR = k; for (let q = 0; q < 7; q++) curve(c, 500, (u) => [x0 + (x1 - x0) * u, f(u) + 12 * Math.sin(u * 60 + (q / 7) * Math.PI)]); PR = 1;
|
|
290
|
+
c.strokeStyle = GRN; c.lineWidth = 1; c.beginPath(); c.moveTo(x0, y0 + 14); c.lineTo(x1, y0 + 14); c.stroke(); c.restore();
|
|
291
|
+
const tw = plain(c, 'training rounds', x0, y0 + 44, 'italic 400 24px "Libre Baskerville"');
|
|
292
|
+
eArrow(c, x0 + tw + 14, y0 + 36, x0 + tw + 84, y0 + 36, 1);
|
|
293
|
+
modelRosette(c, 930, 360, 230, 0.3 * (1 - p), t, 1);
|
|
294
|
+
plain(c, p < 1 ? 'coming into register…' : 'in register', 930, 630, 'italic 400 26px "Libre Baskerville"', { align: 'center' });
|
|
295
|
+
}
|
|
296
|
+
function note4(c, t) { // takeaway
|
|
297
|
+
modelRosette(c, 640, 360, 260, 0, t, 1);
|
|
298
|
+
const k = H.ease.back(SX(t, 13.3, 13.65));
|
|
299
|
+
c.save(); c.translate(640, 360); c.scale(k, k);
|
|
300
|
+
// banknote cartouche: translucent ground (the rosette shows faintly through), rope band, double rule, microprint, seals
|
|
301
|
+
c.fillStyle = 'rgba(235,230,207,0.88)'; c.fillRect(-500, -110, 1000, 210);
|
|
302
|
+
ropeRect(c, -500, -110, 1000, 210, 18, iris(c, -500, 0, 500, 0, ['#1d5a48', '#2c4f7c', '#6b2f4f']), 5, 7);
|
|
303
|
+
c.strokeStyle = GRN; c.lineWidth = 1.1; c.strokeRect(-500, -110, 1000, 210);
|
|
304
|
+
c.lineWidth = 0.8; c.strokeRect(-481, -91, 962, 172); c.lineWidth = 0.5; c.strokeRect(-477, -87, 954, 165);
|
|
305
|
+
micro(c, 'MORE EXAMPLES · BETTER GUESSES · NEURAL RESERVE · ', 0, -77, 900, GRN);
|
|
306
|
+
micro(c, 'TRAINED IN REGISTER · SERIES A · CERTIFIED ACCURATE · ', 0, 75, 900, RED);
|
|
307
|
+
const ink1 = iris(c, -460, -50, -380, 30, ['#145243', '#2f4f8f', '#5a2f6e']), ink2 = iris(c, 0, -50, 0, 30, ['#b24a2c', '#c98a3a', '#9a3d5e']);
|
|
308
|
+
for (const sx of [-1, 1]) {
|
|
309
|
+
const cx = sx * 418, cy = -4;
|
|
310
|
+
c.save(); c.fillStyle = 'rgba(235,230,207,0.9)'; c.beginPath(); c.arc(cx, cy, 50, 0, TAU); c.fill(); c.restore();
|
|
311
|
+
rosette(c, cx, cy, 50, ink1, ink2, t, { lobes: 12, n1: 22, K1: 5, copies: 4, K2: 4, lw: 0.5 });
|
|
312
|
+
c.save(); c.strokeStyle = GRN; c.lineWidth = 0.8; c.beginPath(); c.arc(cx, cy, 13, 0, TAU); c.stroke(); c.restore();
|
|
313
|
+
}
|
|
314
|
+
engr(c, 'MORE EXAMPLES', 0, -18, '700 56px "Libre Baskerville"', { align: 'center' });
|
|
315
|
+
// engraved double-rule arrow (as in the loop strip) instead of the thin font arrow; the line keeps its width and centre
|
|
316
|
+
c.save(); c.font = '700 56px "Libre Baskerville"'; const wA = c.measureText('→ ').width, wB = c.measureText('BETTER GUESSES').width; c.restore();
|
|
317
|
+
const lx = -(wA + wB) / 2 - 4;
|
|
318
|
+
c.save(); c.translate(lx - 22, 45); c.scale(1.6, 1.6); eArrow(c, 0, 0, (wA + 8) / 1.6, 0, 1, RED); c.restore();
|
|
319
|
+
engr(c, 'BETTER GUESSES', lx + wA, 66, '700 56px "Libre Baskerville"', { ink: RED });
|
|
320
|
+
c.restore();
|
|
321
|
+
}
|
|
322
|
+
const NOTES = [note0, note1, note2, note3, note4];
|
|
323
|
+
function explainSP(ctx, W, H_, t) {
|
|
324
|
+
const cuts = [2.1, 6.0, 10.5, 13.0], D = 0.6;
|
|
325
|
+
let cam = 0; cuts.forEach((c0) => { cam += H.ease.inOut(SX(t, c0, c0 + D)); });
|
|
326
|
+
const camX = cam * (W + 60);
|
|
327
|
+
const bg = spPaper(W, H_);
|
|
328
|
+
ctx.fillStyle = '#c9c4ad'; ctx.fillRect(0, 0, W, H_);
|
|
329
|
+
for (let i = 0; i < NOTES.length; i++) {
|
|
330
|
+
const ox = i * (W + 60) - camX; if (ox > W || ox < -W) continue;
|
|
331
|
+
// content is clipped to the inside of the rope border, so pieces slide in from under it
|
|
332
|
+
ctx.save(); ctx.translate(ox, 0); ctx.drawImage(bg, 0, 0); ctx.beginPath(); ctx.rect(48, 48, W - 96, H_ - 96); ctx.clip(); NOTES[i](ctx, t); ctx.restore();
|
|
333
|
+
// perforation gutter between notes
|
|
334
|
+
ctx.fillStyle = '#d8d3bc'; ctx.fillRect(ox + W, 0, 60, H_);
|
|
335
|
+
ctx.fillStyle = '#b9b39b'; for (let y = 6; y < H_; y += 14) { ctx.beginPath(); ctx.arc(ox + W + 30, y, 3, 0, TAU); ctx.fill(); }
|
|
336
|
+
}
|
|
337
|
+
if (t > 2.0) { ctx.save(); ctx.globalAlpha = H.clamp((t - 2.0) / 0.3); spHeader(ctx); ctx.restore(); }
|
|
338
|
+
// a raking glint each time a note settles
|
|
339
|
+
cuts.forEach((c0) => { const g = SX(t, c0 + D, c0 + D + 0.9); if (g > 0 && g < 1) { const gx = -300 + 1900 * g, lg = ctx.createLinearGradient(gx - 160, 0, gx + 160, 120); lg.addColorStop(0, 'rgba(255,255,245,0)'); lg.addColorStop(0.5, 'rgba(255,255,245,0.12)'); lg.addColorStop(1, 'rgba(255,255,245,0)'); ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_); ctx.restore(); } });
|
|
340
|
+
H.vignette(ctx, W, H_, 0.15, '40,50,30');
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
registerStyle({
|
|
344
|
+
id: 'spirograph',
|
|
345
|
+
name: 'Guilloché Security Print',
|
|
346
|
+
category: 'Generative & Type',
|
|
347
|
+
description: 'Banknote-grade lathe work: hairline hypotrochoid rosettes and woven sine-ring meshes in split-fountain iris inks on fibred security paper, with engraved serif lettering, microtext and red serials.',
|
|
348
|
+
motion: 'Like a geometric lathe plotting the plate: the underprint waves and border ropes trace on, each rosette band is drawn pen-pass by pen-pass, the engraved title is wiped in, the red serials are stamped digit by digit; then the meshes slowly counter-rotate into shimmering moiré and a light glint crosses the note.',
|
|
349
|
+
fonts: ['700 46px "Libre Baskerville"', '400 15px "Libre Baskerville"', 'italic 400 15px "Libre Baskerville"', '600 20px "Cormorant Garamond"', '700 22px "Courier Prime"', '400 22px "Courier Prime"', '800 120px "Playfair Display"', '600 7px "Inter"'],
|
|
350
|
+
duration: 7,
|
|
351
|
+
frameT: 6.9,
|
|
352
|
+
readsPixels: true,
|
|
353
|
+
explainerDuration: 14.5,
|
|
354
|
+
explainerNote: 'A sheet of five security-printed notes the camera pans across; the model is a lathe rosette whose passes start out of register and are brought into register by each correction, with engraved double-rule arrows, red numbering-machine counters and rubber cancellation stamps for WRONG / VERIFIED / CERTIFIED.',
|
|
355
|
+
explainer(ctx, W, H_, t) { explainSP(ctx, W, H_, t); },
|
|
356
|
+
// Sound: an engraving works. The rose-engine lathe hums and its stylus scratches metal while a rosette or the
|
|
357
|
+
// rope chart plots (scratch density follows the draw speed); counters are a red numbering machine (metal clack per
|
|
358
|
+
// step, a plunger chunk when it locks); verdicts are rubber cancellation stamps; the camera pan is the note sheet
|
|
359
|
+
// sliding across the bench. Timings mirror explainSP / note0..note4 above (cuts, SX windows, ease curves).
|
|
360
|
+
explainerSound(k) {
|
|
361
|
+
const D = 14.5, E = H.ease;
|
|
362
|
+
const pan = (px) => H.clamp((px / 1280) * 2 - 1, -1, 1) * 0.7;
|
|
363
|
+
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; };
|
|
364
|
+
// lathe pass over [t0,t1] with progress ease: motor drone + stylus scratch grains, denser where the pen moves fast
|
|
365
|
+
const lathe = (t0, t1, ease, g, p) => {
|
|
366
|
+
const d = t1 - t0;
|
|
367
|
+
k.tone(t0, { f: 58, type: 'sawtooth', dur: d + 0.15, gain: 0.05 * g, a: 0.12, hold: d - 0.15, filter: 'lowpass', f2: 260, pan: p, minor: true });
|
|
368
|
+
k.tone(t0, { f: 116, type: 'triangle', dur: d + 0.15, gain: 0.025 * g, a: 0.12, hold: d - 0.15, pan: p, minor: true });
|
|
369
|
+
k.withMinor(() => { for (let t = t0 + 0.02, i = 0; t < t1 - 0.02; i++) {
|
|
370
|
+
const u = (t - t0) / d, sp = (ease(Math.min(1, u + 0.01)) - ease(Math.max(0, u - 0.01))) / 0.02; // relative pen speed
|
|
371
|
+
k.noise(t, { dur: 0.035, filter: 'bandpass', f: 1700 + (i % 5) * 110, q: 3, gain: 0.1 * g * (0.3 + 0.5 * Math.min(1.6, sp)), a: 0.008, pan: p + Math.sin(i * 0.9) * 0.1 });
|
|
372
|
+
t += 0.032;
|
|
373
|
+
} });
|
|
374
|
+
};
|
|
375
|
+
// numbering machine: a metal clack per step of the eased count, at most `rate` per second
|
|
376
|
+
const numb = (t, g, p) => k.withMinor(() => { k.noise(t, { dur: 0.022, filter: 'bandpass', f: 2300, q: 1.6, gain: 0.3 * g, a: 0.001, pan: p }); k.tone(t, { f: 310, type: 'triangle', dur: 0.03, gain: 0.08 * g, pan: p }); });
|
|
377
|
+
const counter = (t0, t1, ease, steps, rate, p) => { let last = -1, prev = 0;
|
|
378
|
+
for (let t = t0; t <= t1; t += 0.002) { const s = Math.floor(ease(H.clamp((t - t0) / (t1 - t0))) * steps); if (s !== prev && t - last >= 1 / rate) { numb(t, 0.8 + 0.3 * ((t - t0) / (t1 - t0)), p); last = t; } prev = s; } };
|
|
379
|
+
const plunger = (t, g, p) => { k.play('thud', t, { f: 190, gain: 0.5 * g, pan: p }); k.noise(t + 0.005, { dur: 0.04, filter: 'bandpass', f: 1500, q: 2, gain: 0.35 * g, a: 0.001, pan: p }); };
|
|
380
|
+
// camera pan: the note sheet slides along the bench (smooth, no crinkle), cuts at c0 over D = 0.6 (inOut)
|
|
381
|
+
const slide = (c0) => { k.noise(c0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 380], [0.3, 900], [0.6, 420]], q: 0.8, gain: 0.16, a: 0.28 }); k.noise(c0 + 0.05, { dur: 0.5, color: 'brown', filter: 'lowpass', f: 300, gain: 0.12, a: 0.22 }); };
|
|
382
|
+
// engraved label settling: a small soft press of the plate
|
|
383
|
+
const tag = (t, p) => k.play('tock', t, { f: 360, gain: 0.3, pan: p, minor: true });
|
|
384
|
+
|
|
385
|
+
k.bed('room', 0, D, { gain: 0.3 });
|
|
386
|
+
k.bed('hum', 0, D, { gain: 0.08 });
|
|
387
|
+
|
|
388
|
+
// ---- note 0: the lathe plots the rosette (0-1.9 inOut); title plate presses in (back 0.15-0.6)
|
|
389
|
+
lathe(0.0, 1.9, E.inOut, 1, 0);
|
|
390
|
+
const tl = when(0.15, 0.6, E.back, 1);
|
|
391
|
+
k.play('press', tl, { gain: 0.5 }); k.noise(tl, { dur: 0.04, filter: 'bandpass', f: 1200, q: 1.5, gain: 0.15 });
|
|
392
|
+
|
|
393
|
+
// ---- pan to note 1: model rosette plots 2.15-3.3, three engraved arrows, THE MODEL, numbering machine
|
|
394
|
+
slide(2.1);
|
|
395
|
+
lathe(2.15, 3.3, E.inOut, 0.7, pan(1000));
|
|
396
|
+
[0, 1, 2].forEach((i) => k.noise(2.6 + i * 0.08, { dur: 0.4, filter: 'bandpass', f: [[0, 1300], [0.4, 1900]], q: 2.5, gain: 0.05, a: 0.1, pan: -0.4 + i * 0.05, minor: true })); // burin cuts the arrows
|
|
397
|
+
tag(when(3.2, 3.5, E.back, 1), pan(1000));
|
|
398
|
+
numb(2.7, 1, pan(200)); // "No. 0" printed
|
|
399
|
+
// n = 10^(6 inOut(SX(t, 2.75, 5.0))) - 1
|
|
400
|
+
counter(2.8, 4.98, E.inOut, 30, 11, pan(250));
|
|
401
|
+
plunger(5.0, 1.2, pan(250)); // locks on 1,000,000
|
|
402
|
+
|
|
403
|
+
// ---- pan to note 2: guess, WRONG stamp, error runs back, lathe re-indexes into register, VERIFIED
|
|
404
|
+
slide(6.0);
|
|
405
|
+
k.noise(6.5, { dur: 0.25, filter: 'bandpass', f: [[0, 1300], [0.25, 1900]], q: 2.5, gain: 0.06, a: 0.06, pan: pan(360), minor: true });
|
|
406
|
+
k.noise(7.05, { dur: 0.2, filter: 'bandpass', f: [[0, 1300], [0.2, 1900]], q: 2.5, gain: 0.06, a: 0.05, pan: pan(840), minor: true });
|
|
407
|
+
k.play('press', 7.25, { gain: 0.45, pan: pan(950) }); // DOG? appears
|
|
408
|
+
tag(when(7.3, 7.55, E.back, 1), pan(990));
|
|
409
|
+
k.play('stamp', 7.99, { gain: 0.9, pan: pan(1050) }); // WRONG ✗ hits (scale settles by 8.05)
|
|
410
|
+
k.play('error', 8.14, { f: 'G3', gain: 0.6, pan: pan(1050) });
|
|
411
|
+
// red dotted error line 8.35-8.9 (inOut): one numbering-style dab per ~1/9 of the run, right to left
|
|
412
|
+
for (let i = 1; i <= 8; i++) { const tt = when(8.35, 8.9, E.inOut, i / 8); k.withMinor(() => { k.noise(tt, { dur: 0.025, filter: 'bandpass', f: 1600, q: 1.2, gain: 0.18, a: 0.001, pan: pan(1050 - i * 55) }); k.tone(tt, { f: 220, dur: 0.03, gain: 0.05, pan: pan(1050 - i * 55) }); }); }
|
|
413
|
+
tag(when(8.85, 9.1, E.back, 1), pan(610));
|
|
414
|
+
// ADJUST 8.9-9.45: the lathe's index wheel clicks the passes back into register, then a short clean pass
|
|
415
|
+
for (let i = 0; i < 6; i++) k.play('click', 8.95 + i * 0.085, { f: 900 + i * 40, gain: 0.9, pan: pan(610), minor: true }); // even ratchet steps
|
|
416
|
+
k.tone(8.9, { f: [[0, 50], [0.55, 62]], type: 'sawtooth', dur: 0.65, gain: 0.05, a: 0.1, hold: 0.4, filter: 'lowpass', f2: 300, pan: pan(610), minor: true });
|
|
417
|
+
k.play('stamp', 9.83, { gain: 0.8, pan: pan(1010) }); // VERIFIED ✓
|
|
418
|
+
['C5', 'G5'].forEach((n, i) => k.play('ding', 9.96 + i * 0.08, { f: n, gain: 0.45, pan: pan(1010) }));
|
|
419
|
+
|
|
420
|
+
// ---- pan to note 3: rope chart plotted by the lathe, numbering machine 61 -> 97, CERTIFIED
|
|
421
|
+
slide(10.5);
|
|
422
|
+
lathe(10.9, 12.3, E.inOut, 0.7, pan(360));
|
|
423
|
+
counter(10.87, 12.28, E.inOut, 36, 9, pan(200));
|
|
424
|
+
k.play('impact', 12.3, { gain: 1.15, pan: pan(250) }); // 97% punches
|
|
425
|
+
plunger(12.3, 0.8, pan(250));
|
|
426
|
+
k.play('stamp', 12.43, { gain: 1, pan: pan(330) }); // CERTIFIED lands
|
|
427
|
+
['C4', 'G4', 'E5'].forEach((n, i) => k.play('ding', 12.6 + i * 0.06, { f: n, gain: 0.4, pan: (i - 1) * 0.2 }));
|
|
428
|
+
|
|
429
|
+
// ---- pan to note 4: takeaway plate presses in, resolving chime
|
|
430
|
+
slide(13.0);
|
|
431
|
+
const tk = when(13.3, 13.65, E.back, 1);
|
|
432
|
+
k.play('press', tk, { gain: 0.55 });
|
|
433
|
+
['C4', 'E4', 'G4', 'C5'].forEach((n, i) => k.play('ding', tk + 0.14 + i * 0.07, { f: n, gain: 0.38 - i * 0.04, pan: (i - 1.5) * 0.25 }));
|
|
434
|
+
},
|
|
435
|
+
draw(ctx, W, H_, t) {
|
|
436
|
+
const r = H.rng(77);
|
|
437
|
+
// ---------- paper ----------
|
|
438
|
+
H.paper(ctx, W, H_, '#e8e4cf', 9, 0.05);
|
|
439
|
+
// soft tinted underprint wash (pale green -> peach), offset printed
|
|
440
|
+
let g = ctx.createLinearGradient(0, 0, W, 0);
|
|
441
|
+
g.addColorStop(0, 'rgba(120,170,140,0.20)'); g.addColorStop(0.5, 'rgba(200,190,140,0.10)'); g.addColorStop(1, 'rgba(220,140,120,0.18)');
|
|
442
|
+
ctx.fillStyle = g; ctx.fillRect(0, 0, W, H_);
|
|
443
|
+
// security fibres
|
|
444
|
+
for (let i = 0; i < 140; i++) {
|
|
445
|
+
const x = r() * W, y = r() * H_, a = r() * TAU, L = 6 + r() * 10;
|
|
446
|
+
ctx.strokeStyle = r() < 0.5 ? 'rgba(200,40,60,0.55)' : 'rgba(40,80,190,0.5)';
|
|
447
|
+
ctx.lineWidth = 0.6;
|
|
448
|
+
ctx.beginPath(); ctx.moveTo(x, y);
|
|
449
|
+
ctx.quadraticCurveTo(x + Math.cos(a + 1) * L * 0.6, y + Math.sin(a + 1) * L * 0.6, x + Math.cos(a) * L, y + Math.sin(a) * L);
|
|
450
|
+
ctx.stroke();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ---------- background guilloché waves (fine, pale, iris inked) ----------
|
|
454
|
+
ctx.save();
|
|
455
|
+
ctx.lineWidth = 0.55;
|
|
456
|
+
ctx.strokeStyle = iris(ctx, 0, 0, W, 0, ['rgba(40,120,100,0.40)', 'rgba(70,90,150,0.38)', 'rgba(170,70,90,0.38)']);
|
|
457
|
+
for (let k = 0; k < 2; k++) {
|
|
458
|
+
PR = H.ease.inOut(ph(t, k * 0.25, 0.9 + k * 0.25));
|
|
459
|
+
for (let y0 = -20; y0 < H_ + 20; y0 += 7) {
|
|
460
|
+
curve(ctx, 320, (u) => {
|
|
461
|
+
const x = u * W;
|
|
462
|
+
return [x, y0 + 4.5 * Math.sin(x / 26 + y0 / 37 + k * Math.PI + t * 0.3) + 6 * Math.sin(x / 210 + y0 / 90)];
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
PR = 1;
|
|
467
|
+
ctx.restore();
|
|
468
|
+
|
|
469
|
+
// ---------- frame band (rope guilloché) ----------
|
|
470
|
+
const m = 26, bw = 30;
|
|
471
|
+
const pB = ph(t, 0.4, 1.5);
|
|
472
|
+
ctx.save();
|
|
473
|
+
ctx.globalAlpha = H.clamp(pB * 4);
|
|
474
|
+
ctx.fillStyle = 'rgba(232,228,207,0.92)';
|
|
475
|
+
ctx.fillRect(m, m, W - 2 * m, bw); ctx.fillRect(m, H_ - m - bw, W - 2 * m, bw);
|
|
476
|
+
ctx.fillRect(m, m, bw, H_ - 2 * m); ctx.fillRect(W - m - bw, m, bw, H_ - 2 * m);
|
|
477
|
+
ctx.lineWidth = 0.6;
|
|
478
|
+
const ropeInk = iris(ctx, 0, 0, W, H_, ['#1d5a48', '#2c4f7c', '#6b2f4f']);
|
|
479
|
+
ctx.strokeStyle = ropeInk;
|
|
480
|
+
const side = (x0, y0, x1, y1) => {
|
|
481
|
+
const L = Math.hypot(x1 - x0, y1 - y0), tx = (x1 - x0) / L, ty = (y1 - y0) / L, nx = -ty, ny = tx;
|
|
482
|
+
for (let k = 0; k < 7; k++) {
|
|
483
|
+
curve(ctx, Math.floor(L / 2), (u) => {
|
|
484
|
+
const s = u * L, off = (bw / 2 - 2) * Math.sin(s / 9 + (k / 7) * Math.PI);
|
|
485
|
+
return [x0 + tx * s + nx * off, y0 + ty * s + ny * off];
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
PR = H.ease.inOut(pB);
|
|
490
|
+
side(m, m + bw / 2, W - m, m + bw / 2);
|
|
491
|
+
side(m, H_ - m - bw / 2, W - m, H_ - m - bw / 2);
|
|
492
|
+
side(m + bw / 2, m, m + bw / 2, H_ - m);
|
|
493
|
+
side(W - m - bw / 2, m, W - m - bw / 2, H_ - m);
|
|
494
|
+
PR = 1;
|
|
495
|
+
ctx.globalAlpha = H.clamp(pB * 2 - 1);
|
|
496
|
+
ctx.strokeStyle = '#1f3f3a'; ctx.lineWidth = 1.2;
|
|
497
|
+
ctx.strokeRect(m - 0.5, m - 0.5, W - 2 * m + 1, H_ - 2 * m + 1);
|
|
498
|
+
ctx.lineWidth = 0.7; ctx.strokeRect(m + bw + 3, m + bw + 3, W - 2 * (m + bw + 3), H_ - 2 * (m + bw + 3));
|
|
499
|
+
ctx.restore();
|
|
500
|
+
|
|
501
|
+
// ---------- main rosette ----------
|
|
502
|
+
const RX = 400, RY = 368, RR = 238;
|
|
503
|
+
const pR = ph(t, 1.4, 4.0);
|
|
504
|
+
ctx.save();
|
|
505
|
+
ctx.globalAlpha = H.clamp(t - 0.9);
|
|
506
|
+
ctx.fillStyle = 'rgba(236,232,212,0.85)';
|
|
507
|
+
ctx.beginPath(); ctx.arc(RX, RY, (RR + 6) * (0.36 + 0.64 * H.clamp((pR - 0.08) / 0.92)), 0, TAU); ctx.fill();
|
|
508
|
+
ctx.restore();
|
|
509
|
+
rosette(ctx, RX, RY, RR,
|
|
510
|
+
iris(ctx, RX - RR, RY - RR, RX + RR, RY + RR, ['#145243', '#1f6b6a', '#2f4f8f', '#5a2f6e']),
|
|
511
|
+
iris(ctx, RX, RY - RR, RX, RY + RR, ['#b24a2c', '#c98a3a', '#9a3d5e']), t,
|
|
512
|
+
{ n1: 40, K1: 8, lobes: 22, loop: 3.2, copies: 5, K2: 6, lw: 0.55, p: pR });
|
|
513
|
+
// fine radial engraving burst between lace and medallion
|
|
514
|
+
ctx.save(); ctx.strokeStyle = 'rgba(23,62,56,0.55)'; ctx.lineWidth = 0.45;
|
|
515
|
+
const nb = Math.floor(360 * ph(t, 1.2, 1.6));
|
|
516
|
+
for (let i = 0; i < nb; i++) { const a = (i / 360) * TAU; ctx.beginPath(); ctx.moveTo(RX + Math.cos(a) * 84, RY + Math.sin(a) * 84); ctx.lineTo(RX + Math.cos(a) * (92 + 4 * Math.sin(i * 0.7)), RY + Math.sin(a) * (92 + 4 * Math.sin(i * 0.7))); ctx.stroke(); }
|
|
517
|
+
ctx.restore();
|
|
518
|
+
|
|
519
|
+
// medallion: engraved chip in a hatched oval, cut from the centre outward
|
|
520
|
+
const MR = 70;
|
|
521
|
+
const pM = ph(t, 0.9, 1.4);
|
|
522
|
+
ctx.save();
|
|
523
|
+
ctx.beginPath(); ctx.arc(RX, RY, Math.max(0.1, (MR + 6) * H.ease.out(pM)), 0, TAU); ctx.clip();
|
|
524
|
+
ctx.save();
|
|
525
|
+
ctx.beginPath(); ctx.arc(RX, RY, MR, 0, TAU); ctx.fillStyle = '#ebe6cf'; ctx.fill();
|
|
526
|
+
ctx.clip();
|
|
527
|
+
// engraved line-shading background (concentric fine circles = lathe ground)
|
|
528
|
+
ctx.strokeStyle = 'rgba(25,60,55,0.75)'; ctx.lineWidth = 0.5;
|
|
529
|
+
for (let rr = 4; rr < MR; rr += 2.2) { ctx.beginPath(); ctx.arc(RX, RY, rr, 0, TAU); ctx.stroke(); }
|
|
530
|
+
// chip body
|
|
531
|
+
const cs = 64;
|
|
532
|
+
ctx.fillStyle = '#ebe6cf'; ctx.fillRect(RX - cs / 2 - 6, RY - cs / 2 - 6, cs + 12, cs + 12);
|
|
533
|
+
ctx.strokeStyle = '#173e38'; ctx.lineWidth = 1.4;
|
|
534
|
+
ctx.strokeRect(RX - cs / 2, RY - cs / 2, cs, cs);
|
|
535
|
+
// horizontal engraved hatching inside chip (tone), denser lower-right for shading
|
|
536
|
+
ctx.lineWidth = 0.6;
|
|
537
|
+
for (let y = RY - cs / 2 + 3; y < RY + cs / 2; y += 2.4) {
|
|
538
|
+
ctx.beginPath(); ctx.moveTo(RX - cs / 2 + 3, y); ctx.lineTo(RX + cs / 2 - 3, y); ctx.stroke();
|
|
539
|
+
}
|
|
540
|
+
ctx.fillStyle = '#ebe6cf'; ctx.fillRect(RX - 17, RY - 17, 34, 34);
|
|
541
|
+
ctx.lineWidth = 1.1; ctx.strokeRect(RX - 17, RY - 17, 34, 34);
|
|
542
|
+
ctx.font = '600 20px "Cormorant Garamond"'; ctx.fillStyle = '#173e38'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
543
|
+
ctx.fillText('AI', RX, RY + 1);
|
|
544
|
+
// pins
|
|
545
|
+
ctx.lineWidth = 1.4;
|
|
546
|
+
for (let i = 0; i < 6; i++) {
|
|
547
|
+
const o = -cs / 2 + 8 + i * ((cs - 16) / 5);
|
|
548
|
+
ctx.beginPath(); ctx.moveTo(RX + o, RY - cs / 2); ctx.lineTo(RX + o, RY - cs / 2 - 7); ctx.moveTo(RX + o, RY + cs / 2); ctx.lineTo(RX + o, RY + cs / 2 + 7);
|
|
549
|
+
ctx.moveTo(RX - cs / 2, RY + o); ctx.lineTo(RX - cs / 2 - 7, RY + o); ctx.moveTo(RX + cs / 2, RY + o); ctx.lineTo(RX + cs / 2 + 7, RY + o); ctx.stroke();
|
|
550
|
+
}
|
|
551
|
+
ctx.restore();
|
|
552
|
+
ctx.save(); ctx.strokeStyle = '#173e38'; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.arc(RX, RY, MR, 0, TAU); ctx.stroke();
|
|
553
|
+
ctx.lineWidth = 0.6; ctx.beginPath(); ctx.arc(RX, RY, MR + 4, 0, TAU); ctx.stroke(); ctx.restore();
|
|
554
|
+
ctx.restore();
|
|
555
|
+
// microtext ring
|
|
556
|
+
const micro = 'ARTIFICIAL INTELLIGENCE • NEURAL RESERVE • ';
|
|
557
|
+
ctx.save(); ctx.font = '600 7px "Inter"'; ctx.fillStyle = '#173e38'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
558
|
+
const mr = MR + 10, nRep = Math.round((TAU * mr) / 5.2 / micro.length), nCh = nRep * micro.length;
|
|
559
|
+
const nShow = Math.floor(nCh * ph(t, 1.3, 1.9));
|
|
560
|
+
for (let i = 0; i < nShow; i++) {
|
|
561
|
+
const a = (i / nCh) * TAU - Math.PI / 2;
|
|
562
|
+
ctx.save(); ctx.translate(RX + Math.cos(a) * mr, RY + Math.sin(a) * mr); ctx.rotate(a + Math.PI / 2);
|
|
563
|
+
ctx.fillText(micro[i % micro.length], 0, 0); ctx.restore();
|
|
564
|
+
}
|
|
565
|
+
ctx.restore();
|
|
566
|
+
|
|
567
|
+
// ---------- corner rosettes with value numerals ----------
|
|
568
|
+
const corners = [[m + bw + 54, m + bw + 54], [W - m - bw - 54, m + bw + 54], [m + bw + 54, H_ - m - bw - 54], [W - m - bw - 54, H_ - m - bw - 54]];
|
|
569
|
+
corners.forEach(([x, y], i) => {
|
|
570
|
+
if (i === 0) return; // top-left sits under the main rosette's halo — keep it clean
|
|
571
|
+
const pc = ph(t, 1.8 + i * 0.35, 3.2 + i * 0.35);
|
|
572
|
+
if (pc <= 0) return;
|
|
573
|
+
ctx.save(); ctx.fillStyle = 'rgba(236,232,212,0.9)'; ctx.beginPath(); ctx.arc(x, y, 52 * (0.4 + 0.6 * H.ease.inOut(H.clamp(pc / 0.85))), 0, TAU); ctx.fill(); ctx.restore();
|
|
574
|
+
rosette(ctx, x, y, 50, iris(ctx, x - 50, y, x + 50, y, ['#1d5a48', '#3a3f86']), '#a8553a', t, { n1: 16, K1: 5, lobes: 10, loop: 2.2, copies: 3, K2: 4, lw: 0.5, p: pc });
|
|
575
|
+
ctx.save(); ctx.globalAlpha = H.clamp(pc * 6); ctx.fillStyle = '#ebe6cf'; ctx.beginPath(); ctx.arc(x, y, 22, 0, TAU); ctx.fill();
|
|
576
|
+
ctx.strokeStyle = '#173e38'; ctx.lineWidth = 0.8; ctx.stroke();
|
|
577
|
+
ctx.font = '700 26px "Libre Baskerville"'; ctx.fillStyle = '#173e38'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
578
|
+
ctx.fillText('1', x, y + 1); ctx.restore();
|
|
579
|
+
});
|
|
580
|
+
|
|
581
|
+
// ---------- security thread (windowed) ----------
|
|
582
|
+
const sx = 700;
|
|
583
|
+
const thrY = m + bw + 6 + (H_ - 2 * (m + bw)) * H.ease.inOut(ph(t, 0.9, 1.6));
|
|
584
|
+
for (let y = m + bw + 6; y < Math.min(thrY, H_ - m - bw - 6); y += 36) {
|
|
585
|
+
const gg = ctx.createLinearGradient(sx - 4, 0, sx + 4, 0);
|
|
586
|
+
gg.addColorStop(0, '#8d8f93'); gg.addColorStop(0.45, '#e9edf0'); gg.addColorStop(0.55, '#b9bcc0'); gg.addColorStop(1, '#6d7074');
|
|
587
|
+
ctx.fillStyle = gg; ctx.fillRect(sx - 4, y, 8, 20);
|
|
588
|
+
ctx.save(); ctx.translate(sx, y + 10); ctx.rotate(Math.PI / 2);
|
|
589
|
+
ctx.font = '600 7px "Inter"'; ctx.fillStyle = 'rgba(40,40,45,0.8)'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
590
|
+
ctx.fillText('AI•1B', 0, 0); ctx.restore();
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
// ---------- right panel: engraved title ----------
|
|
594
|
+
const TX = 1000;
|
|
595
|
+
ctx.save();
|
|
596
|
+
// engraved panel is wiped in left->right as the intaglio plate is pressed
|
|
597
|
+
ctx.beginPath(); ctx.rect(0, 0, 760 + 500 * H.ease.inOut(ph(t, 4.2, 5.1)), 140 + 0); ctx.rect(0, 140, 760 + 500 * H.ease.inOut(ph(t, 4.3, 5.2)), 200); ctx.clip();
|
|
598
|
+
ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic';
|
|
599
|
+
ctx.font = 'italic 400 15px "Libre Baskerville"'; ctx.fillStyle = '#23463f';
|
|
600
|
+
ctx.fillText('Certificate of the Neural Reserve', TX, 152);
|
|
601
|
+
// engraved headline: fill with fine horizontal line-shading, outlined
|
|
602
|
+
const title = ['ONE BILLION', 'PARAMETERS'];
|
|
603
|
+
ctx.font = '700 46px "Libre Baskerville"';
|
|
604
|
+
title.forEach((ln, li) => {
|
|
605
|
+
const y = 214 + li * 56;
|
|
606
|
+
ctx.save();
|
|
607
|
+
try { ctx.letterSpacing = '3px'; } catch (e) {}
|
|
608
|
+
// shadow line (engraver's drop shade)
|
|
609
|
+
ctx.fillStyle = 'rgba(60,40,50,0.35)'; ctx.fillText(ln, TX + 2, y + 2);
|
|
610
|
+
ctx.fillStyle = '#173e38'; ctx.fillText(ln, TX, y);
|
|
611
|
+
// hairline cuts through letters (engraved texture)
|
|
612
|
+
ctx.globalCompositeOperation = 'source-atop';
|
|
613
|
+
ctx.restore();
|
|
614
|
+
});
|
|
615
|
+
// engrave lines over title via clip of text path is not available; instead overlay lines in paper colour, masked by a layer
|
|
616
|
+
const lay = H.layer(W, H_), lc = lay.getContext('2d');
|
|
617
|
+
lc.font = '700 46px "Libre Baskerville"'; lc.textAlign = 'center';
|
|
618
|
+
try { lc.letterSpacing = '3px'; } catch (e) {}
|
|
619
|
+
lc.fillStyle = '#000'; title.forEach((ln, li) => lc.fillText(ln, TX, 214 + li * 56));
|
|
620
|
+
lc.globalCompositeOperation = 'source-atop'; lc.strokeStyle = 'rgba(232,228,207,0.75)'; lc.lineWidth = 0.7;
|
|
621
|
+
for (let y = 160; y < 290; y += 2.6) { lc.beginPath(); lc.moveTo(700, y); lc.lineTo(1300, y); lc.stroke(); }
|
|
622
|
+
ctx.save(); ctx.beginPath(); ctx.rect(0, 140, 760 + 500 * H.ease.inOut(ph(t, 4.3, 5.2)), 200); ctx.clip(); ctx.drawImage(lay, 0, 0); ctx.restore();
|
|
623
|
+
// rule + engraved label
|
|
624
|
+
ctx.strokeStyle = '#23463f'; ctx.lineWidth = 0.8;
|
|
625
|
+
ctx.beginPath(); ctx.moveTo(TX - 170, 300); ctx.lineTo(TX + 170, 300); ctx.stroke();
|
|
626
|
+
ctx.beginPath(); ctx.moveTo(TX - 150, 304); ctx.lineTo(TX + 150, 304); ctx.stroke();
|
|
627
|
+
ctx.font = '400 15px "Libre Baskerville"'; ctx.fillStyle = '#23463f';
|
|
628
|
+
try { ctx.letterSpacing = '4px'; } catch (e) {}
|
|
629
|
+
ctx.fillText('LEGAL TENDER FOR ALL COMPUTE', TX, 330);
|
|
630
|
+
ctx.restore();
|
|
631
|
+
|
|
632
|
+
// second rosette, lower right, with large lathe numeral
|
|
633
|
+
const BX = 960, BY = 486;
|
|
634
|
+
const pE = ph(t, 2.8, 4.0), pN = ph(t, 3.9, 4.5);
|
|
635
|
+
ctx.save(); ctx.globalAlpha = H.ease.inOut(H.clamp(pE * 1.4)); ctx.fillStyle = 'rgba(236,232,212,0.8)'; ctx.beginPath(); ctx.ellipse(BX, BY, 176, 98, 0, 0, TAU); ctx.fill(); ctx.restore();
|
|
636
|
+
ctx.save(); ctx.lineWidth = 0.55; ctx.strokeStyle = iris(ctx, BX - 200, BY, BX + 200, BY, ['#6a2f6a', '#2f4f8f', '#145243']);
|
|
637
|
+
PR = H.ease.inOut(pE);
|
|
638
|
+
for (let k = 0; k < 14; k++) {
|
|
639
|
+
curve(ctx, 1200, (u) => {
|
|
640
|
+
const a = u * TAU;
|
|
641
|
+
const s = 1 + 0.09 * Math.sin(28 * a + (k / 14) * TAU + t * 0.1) ;
|
|
642
|
+
return [BX + 168 * s * Math.cos(a), BY + 90 * s * Math.sin(a)];
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
PR = 1;
|
|
646
|
+
ctx.restore();
|
|
647
|
+
ctx.save();
|
|
648
|
+
ctx.globalAlpha = H.clamp(pN * 5);
|
|
649
|
+
ctx.fillStyle = '#ebe6cf'; ctx.beginPath(); ctx.ellipse(BX, BY, 132, 72, 0, 0, TAU); ctx.fill(); ctx.strokeStyle = '#173e38'; ctx.lineWidth = 0.6; ctx.beginPath(); ctx.ellipse(BX, BY, 138, 77, 0, 0, TAU); ctx.stroke(); ctx.beginPath(); ctx.ellipse(BX, BY, 132, 72, 0, 0, TAU);
|
|
650
|
+
ctx.strokeStyle = '#173e38'; ctx.lineWidth = 1; ctx.stroke();
|
|
651
|
+
{
|
|
652
|
+
const nl = H.layer(W, H_), nc = nl.getContext('2d');
|
|
653
|
+
nc.font = '800 104px "Playfair Display"'; nc.textAlign = 'center'; nc.textBaseline = 'middle';
|
|
654
|
+
nc.fillStyle = '#000'; nc.fillText('10⁹', BX, BY + 8);
|
|
655
|
+
nc.globalCompositeOperation = 'source-in';
|
|
656
|
+
nc.fillStyle = '#ebe6cf'; nc.fillRect(0, 0, W, H_); nc.globalCompositeOperation = 'source-atop';
|
|
657
|
+
nc.strokeStyle = '#173e38'; nc.lineWidth = 1.25;
|
|
658
|
+
for (let y = BY - 70; y < BY - 70 + 140 * pN; y += 2.0) { nc.beginPath(); nc.moveTo(BX - 160, y); nc.lineTo(BX + 160, y + 8); nc.stroke(); }
|
|
659
|
+
ctx.drawImage(nl, 0, 0);
|
|
660
|
+
ctx.globalAlpha = H.clamp(pN * 2 - 1);
|
|
661
|
+
ctx.lineWidth = 1.6; ctx.strokeStyle = '#173e38'; ctx.font = '800 104px "Playfair Display"'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
662
|
+
ctx.strokeText('10⁹', BX, BY + 8);
|
|
663
|
+
}
|
|
664
|
+
ctx.restore();
|
|
665
|
+
|
|
666
|
+
// ---------- serial numbers (letterpress red) ----------
|
|
667
|
+
ctx.save();
|
|
668
|
+
ctx.font = '700 22px "Courier Prime"'; ctx.fillStyle = '#b3262b';
|
|
669
|
+
try { ctx.letterSpacing = '2px'; } catch (e) {}
|
|
670
|
+
const SER = 'AI 0428 1937 B', tq = Math.floor(t * 15) / 15;
|
|
671
|
+
const n1 = Math.floor(SER.length * ph(tq, 5.0, 5.5)), n2 = Math.floor(SER.length * ph(tq, 5.2, 5.7));
|
|
672
|
+
ctx.fillText(SER.slice(0, n1), 790, 120);
|
|
673
|
+
ctx.save(); ctx.translate(196, 652); ctx.fillText(SER.slice(0, n2), 0, 0); ctx.restore();
|
|
674
|
+
ctx.restore();
|
|
675
|
+
|
|
676
|
+
// ---------- print finish: ink spread / paper tooth ----------
|
|
677
|
+
// hold: a soft raking-light glint travels across the note
|
|
678
|
+
const gl = ph(t, 5.5, 6.6);
|
|
679
|
+
if (gl > 0 && gl < 1) {
|
|
680
|
+
const gx = -300 + 1900 * gl;
|
|
681
|
+
const lg = ctx.createLinearGradient(gx - 160, 0, gx + 160, 120);
|
|
682
|
+
lg.addColorStop(0, 'rgba(255,255,245,0)'); lg.addColorStop(0.5, 'rgba(255,255,245,0.16)'); lg.addColorStop(1, 'rgba(255,255,245,0)');
|
|
683
|
+
ctx.save(); ctx.globalCompositeOperation = 'screen'; ctx.fillStyle = lg; ctx.fillRect(0, 0, W, H_); ctx.restore();
|
|
684
|
+
}
|
|
685
|
+
H.grain(ctx, W, H_, 0.045, 31 + Math.floor(t * 12));
|
|
686
|
+
H.vignette(ctx, W, H_, 0.15, '40,50,30');
|
|
687
|
+
},
|
|
688
|
+
});
|
|
689
|
+
}
|