@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,656 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------- 1950s broadsheet front page on yellowed newsprint ----------
|
|
3
|
+
const PAPER = [228, 214, 178];
|
|
4
|
+
const INK = [30, 28, 27];
|
|
5
|
+
const SYL = ['lo', 're', 'm', 'ip', 'sum', 'dol', 'or', 'sit', 'am', 'et', 'con', 'sec', 'te', 'tur', 'ad', 'pis', 'cing', 'el', 'it', 'sed', 'do', 'ei', 'us', 'mod', 'tem', 'por', 'in', 'ci', 'di', 'unt', 'ut', 'la', 'bo', 'ma', 'gna', 'ali', 'qua', 'en', 'im', 'mi', 'nim', 've', 'ni', 'quis', 'nos', 'tru', 'ex', 'er', 'ta', 'tion', 'ul', 'lam', 'co', 'nis', 'al', 'iq', 'uip', 'ea', 'com', 'mo', 'du', 'aut', 'ir', 'ure'];
|
|
6
|
+
const L = 48, R = 1232, COLW = 184, GUT = 16;
|
|
7
|
+
const colX = (c) => L + c * (COLW + GUT);
|
|
8
|
+
|
|
9
|
+
const words = (r, n) => {
|
|
10
|
+
const out = []; let cap = true;
|
|
11
|
+
for (let i = 0; i < n; i++) {
|
|
12
|
+
let w = ''; const k = r.chance(0.15) ? 1 : r.int(1, 3);
|
|
13
|
+
for (let j = 0; j < k; j++) w += r.pick(SYL);
|
|
14
|
+
if (r.chance(0.03)) w = String(r.int(2, 1957));
|
|
15
|
+
if (cap) w = w[0].toUpperCase() + w.slice(1);
|
|
16
|
+
cap = false;
|
|
17
|
+
if (r.chance(0.09)) { w += '.'; cap = true; } else if (r.chance(0.07)) w += ',';
|
|
18
|
+
out.push(w);
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
// justified greeked column text; returns y after
|
|
24
|
+
const column = (x, r, cx, y0, y1, opts = {}) => {
|
|
25
|
+
const lh = 12.6, wcache = {};
|
|
26
|
+
x.font = '400 10.4px "Libre Baskerville"'; x.textBaseline = 'alphabetic'; x.textAlign = 'left';
|
|
27
|
+
const sw = x.measureText(' ').width;
|
|
28
|
+
const mw = (w) => wcache[w] ?? (wcache[w] = x.measureText(w).width);
|
|
29
|
+
let y = y0 + lh, first = true, dlDone = false;
|
|
30
|
+
let pool = [];
|
|
31
|
+
while (y < y1) {
|
|
32
|
+
if (opts.dateline && !dlDone) { dlDone = true;
|
|
33
|
+
x.font = '700 10.4px "Libre Baskerville"'; const dl = opts.dateline; x.fillText(dl, cx + 10, y);
|
|
34
|
+
const dw = x.measureText(dl).width; x.font = '400 10.4px "Libre Baskerville"';
|
|
35
|
+
// continue same line
|
|
36
|
+
let lineW = dw + 10 + sw, lw = [];
|
|
37
|
+
if (pool.length < 30) pool = pool.concat(words(r, 60));
|
|
38
|
+
while (pool.length && lineW + mw(pool[0]) <= COLW) { lw.push(pool.shift()); lineW += mw(lw[lw.length - 1]) + sw; }
|
|
39
|
+
let xx = cx + 10 + dw + sw; const extra = (COLW - (lineW - sw)) / Math.max(1, lw.length);
|
|
40
|
+
for (const w of lw) { x.fillText(w, xx, y); xx += mw(w) + sw + extra; }
|
|
41
|
+
y += lh; first = false; continue;
|
|
42
|
+
}
|
|
43
|
+
const indent = first ? 10 : 0; first = false;
|
|
44
|
+
if (pool.length < 30) pool = pool.concat(words(r, 60));
|
|
45
|
+
const line = []; let lw = indent;
|
|
46
|
+
while (pool.length && lw + mw(pool[0]) <= COLW) { const w = pool.shift(); line.push(w); lw += mw(w) + sw; }
|
|
47
|
+
const endPara = r.chance(0.08) || (line.length && line[line.length - 1].endsWith('.') && r.chance(0.3));
|
|
48
|
+
const natural = lw - sw;
|
|
49
|
+
const gap = endPara ? sw : sw + (COLW - natural) / Math.max(1, line.length - 1);
|
|
50
|
+
let xx = cx + indent;
|
|
51
|
+
for (const w of line) { x.fillText(w, xx, y); xx += mw(w) + gap; }
|
|
52
|
+
y += lh;
|
|
53
|
+
if (endPara) { first = true; if (opts.subheads && r.chance(0.18) && y < y1 - 40) { x.font = '700 10.4px "Libre Baskerville"'; x.textAlign = 'center'; x.fillText(r.pick(['Calculates in Seconds', 'Tubes Glow All Night', 'Memory on Tape', 'Asks Its Own Questions']), cx + COLW / 2, y + 4); x.textAlign = 'left'; x.font = '400 10.4px "Libre Baskerville"'; y += lh + 6; } }
|
|
54
|
+
}
|
|
55
|
+
return y;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
const rule = (x, x0, x1, y, h) => x.fillRect(x0, y, x1 - x0, h);
|
|
59
|
+
|
|
60
|
+
// the photograph (greyscale), later screened to a coarse halftone
|
|
61
|
+
const photo = (w, h, t) => {
|
|
62
|
+
const c = H.layer(w, h), x = c.getContext('2d');
|
|
63
|
+
let g = x.createLinearGradient(0, 0, 0, h);
|
|
64
|
+
g.addColorStop(0, '#262626'); g.addColorStop(0.7, '#4c4c4c'); g.addColorStop(0.72, '#8a8a8a'); g.addColorStop(1, '#b0b0b0');
|
|
65
|
+
x.fillStyle = g; x.fillRect(0, 0, w, h);
|
|
66
|
+
// tape cabinets
|
|
67
|
+
for (let i = 0; i < 2; i++) {
|
|
68
|
+
const cx = 18 + i * 118;
|
|
69
|
+
g = x.createLinearGradient(cx, 0, cx + 108, 0); g.addColorStop(0, '#d8d8d8'); g.addColorStop(1, '#8c8c8c');
|
|
70
|
+
x.fillStyle = g; x.fillRect(cx, 14, 108, 200);
|
|
71
|
+
x.fillStyle = '#2a2a2a'; x.fillRect(cx + 10, 26, 88, 104);
|
|
72
|
+
for (const ry of [54, 104]) {
|
|
73
|
+
const a = t * (i ? 1.3 : -1.1) + ry;
|
|
74
|
+
x.fillStyle = '#bdbdbd'; x.beginPath(); x.arc(cx + 54, ry, 23, 0, 7); x.fill();
|
|
75
|
+
x.fillStyle = '#3a3a3a'; x.beginPath(); x.arc(cx + 54, ry, 15, 0, 7); x.fill();
|
|
76
|
+
x.fillStyle = '#e8e8e8'; for (let k = 0; k < 3; k++) { const aa = a + k * 2.094; x.beginPath(); x.arc(cx + 54 + Math.cos(aa) * 9, ry + Math.sin(aa) * 9, 3.5, 0, 7); x.fill(); }
|
|
77
|
+
x.beginPath(); x.arc(cx + 54, ry, 3, 0, 7); x.fill();
|
|
78
|
+
}
|
|
79
|
+
x.fillStyle = '#5a5a5a'; for (let k = 0; k < 4; k++) x.fillRect(cx + 14 + k * 22, 146, 14, 8);
|
|
80
|
+
x.fillStyle = 'rgba(0,0,0,0.35)'; x.fillRect(cx + 108, 20, 8, 196);
|
|
81
|
+
}
|
|
82
|
+
// console with lamp grid
|
|
83
|
+
g = x.createLinearGradient(256, 0, 400, 0); g.addColorStop(0, '#b4b4b4'); g.addColorStop(1, '#6a6a6a');
|
|
84
|
+
x.fillStyle = g; x.fillRect(256, 60, 144, 150);
|
|
85
|
+
x.fillStyle = '#141414'; x.fillRect(266, 72, 124, 84);
|
|
86
|
+
const lr = H.rng(4 + Math.floor(t * 4));
|
|
87
|
+
for (let j = 0; j < 5; j++) for (let i = 0; i < 8; i++) { x.fillStyle = lr.chance(0.55) ? '#ffffff' : '#4a4a4a'; x.beginPath(); x.arc(276 + i * 15, 84 + j * 15, 4.2, 0, 7); x.fill(); }
|
|
88
|
+
x.fillStyle = '#d0d0d0'; x.fillRect(248, 196, 160, 14);
|
|
89
|
+
x.fillStyle = '#2a2a2a'; x.fillRect(256, 210, 144, 34);
|
|
90
|
+
// the robot (rim-lit against the dark wall)
|
|
91
|
+
x.save(); x.translate(424, 6); x.scale(1.22, 1.0);
|
|
92
|
+
const rx = 0;
|
|
93
|
+
g = x.createLinearGradient(rx, 0, rx + 104, 0); g.addColorStop(0, '#ffffff'); g.addColorStop(0.45, '#c8c8c8'); g.addColorStop(1, '#5a5a5a');
|
|
94
|
+
x.fillStyle = 'rgba(0,0,0,0.5)'; x.beginPath(); x.ellipse(rx + 58, 246, 64, 8, 0, 0, 7); x.fill();
|
|
95
|
+
x.fillStyle = g;
|
|
96
|
+
x.beginPath(); x.roundRect(rx + 22, 28, 60, 52, 12); x.fill();
|
|
97
|
+
x.fillRect(rx + 44, 80, 16, 10);
|
|
98
|
+
x.beginPath(); x.roundRect(rx + 6, 90, 92, 98, 8); x.fill();
|
|
99
|
+
x.fillRect(rx - 10, 96, 15, 76); x.fillRect(rx + 99, 96, 15, 76);
|
|
100
|
+
x.beginPath(); x.arc(rx - 3, 178, 9, 0, 7); x.arc(rx + 107, 178, 9, 0, 7); x.fill();
|
|
101
|
+
x.fillRect(rx + 20, 188, 24, 58); x.fillRect(rx + 60, 188, 24, 58);
|
|
102
|
+
x.fillStyle = '#101010'; x.fillRect(rx + 50, 10, 4, 18); x.beginPath(); x.arc(rx + 52, 9, 6, 0, 7); x.fill();
|
|
103
|
+
x.fillStyle = '#141414'; x.beginPath(); x.arc(rx + 40, 50, 10, 0, 7); x.arc(rx + 64, 50, 10, 0, 7); x.fill();
|
|
104
|
+
x.fillStyle = '#ffffff'; x.beginPath(); x.arc(rx + 40, 50, 5, 0, 7); x.arc(rx + 64, 50, 5, 0, 7); x.fill();
|
|
105
|
+
x.fillStyle = '#1a1a1a'; x.fillRect(rx + 36, 66, 32, 6);
|
|
106
|
+
x.fillStyle = '#1e1e1e'; x.fillRect(rx + 24, 106, 56, 38);
|
|
107
|
+
x.fillStyle = '#f0f0f0'; for (let k = 0; k < 3; k++) { x.beginPath(); x.arc(rx + 36 + k * 16, 125, 4.5, 0, 7); x.fill(); }
|
|
108
|
+
x.fillStyle = '#2a2a2a'; x.fillRect(rx + 6, 160, 92, 4); x.fillRect(rx + 50, 188, 4, 58);
|
|
109
|
+
x.restore();
|
|
110
|
+
// flash falloff + grain, softened like an engraved-from-photo plate
|
|
111
|
+
const v = x.createRadialGradient(w * 0.55, h * 0.4, 40, w * 0.5, h * 0.5, w * 0.7);
|
|
112
|
+
v.addColorStop(0, 'rgba(255,255,255,0.12)'); v.addColorStop(1, 'rgba(0,0,0,0.4)');
|
|
113
|
+
x.fillStyle = v; x.fillRect(0, 0, w, h);
|
|
114
|
+
const o = H.layer(w, h), ox = o.getContext('2d'); ox.filter = 'blur(1.4px)'; ox.drawImage(c, 0, 0);
|
|
115
|
+
return ox.getImageData(0, 0, w, h).data;
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
// lays out one side of the page into ctx x (black on transparent)
|
|
119
|
+
const layout = (x, r, t, back) => {
|
|
120
|
+
x.fillStyle = '#000'; x.strokeStyle = '#000';
|
|
121
|
+
if (back) {
|
|
122
|
+
// simpler back page: a big ad block, columns, and a headline (shows through mirrored)
|
|
123
|
+
x.font = '700 54px "Oswald"'; x.textAlign = 'center'; x.fillText('CITY COUNCIL VOTES NEW BUDGET', 640, 90);
|
|
124
|
+
x.globalAlpha = 0.55; x.fillRect(640, 140, 592, 300); x.globalAlpha = 1;
|
|
125
|
+
x.font = '700 64px "Playfair Display"'; x.globalCompositeOperation = 'destination-out'; x.fillText('SALE', 936, 260); x.globalCompositeOperation = 'source-over';
|
|
126
|
+
for (let c = 0; c < 6; c++) if (c < 3 || true) column(x, r, colX(c), c >= 3 ? 450 : 120, 720);
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
// top line + ears
|
|
130
|
+
x.font = '400 11px "Libre Baskerville"'; x.textAlign = 'left'; x.fillText('LATE CITY EDITION', L, 34);
|
|
131
|
+
x.textAlign = 'right'; x.fillText('Weather: Fair, mild today; clear tonight.', R, 34);
|
|
132
|
+
x.lineWidth = 1.2;
|
|
133
|
+
x.strokeRect(L, 50, 190, 70); x.strokeRect(R - 190, 50, 190, 70);
|
|
134
|
+
x.font = 'italic 400 13px "Libre Baskerville"'; x.textAlign = 'center';
|
|
135
|
+
x.fillText('“All the News', L + 95, 78); x.fillText('That Computes”', L + 95, 98);
|
|
136
|
+
x.font = '700 11px "Libre Baskerville"'; x.fillText('TODAY’S INDEX', R - 95, 70);
|
|
137
|
+
x.font = '400 10.5px "Libre Baskerville"'; x.fillText('Radio & TV . . . . 46', R - 95, 88); x.fillText('Science . . . . . . 22', R - 95, 104);
|
|
138
|
+
// masthead
|
|
139
|
+
x.font = '400 90px "UnifrakturMaguntia"'; x.textAlign = 'center'; x.fillText('The Daily Algorithm', 640, 118);
|
|
140
|
+
// dateline rules
|
|
141
|
+
rule(x, L, R, 130, 3.2); rule(x, L, R, 136, 1);
|
|
142
|
+
x.font = '700 10.5px "Libre Baskerville"'; x.textAlign = 'left'; x.fillText('VOL. CVI . . . No. 36,512', L, 150);
|
|
143
|
+
x.textAlign = 'center'; x.fillText('NEW YORK, THURSDAY, OCTOBER 9, 1958', 640, 150);
|
|
144
|
+
x.textAlign = 'right'; x.fillText('FIVE CENTS', R, 150);
|
|
145
|
+
rule(x, L, R, 157, 1); rule(x, L, R, 160, 2.6);
|
|
146
|
+
// banner headline
|
|
147
|
+
x.font = '700 100px "Oswald"'; x.textAlign = 'left';
|
|
148
|
+
const head = 'ELECTRONIC BRAIN LEARNS TO THINK';
|
|
149
|
+
const hw = x.measureText(head).width;
|
|
150
|
+
x.save(); x.translate(L, 252); x.scale((R - L) / hw, 1); x.fillText(head, 0, 0); x.restore();
|
|
151
|
+
rule(x, L, R, 266, 1.3);
|
|
152
|
+
// column rules
|
|
153
|
+
// photo frame (filled later) spans cols 0-2
|
|
154
|
+
const PX = L, PY = 278, PW_ = colX(3) - GUT - L, PH_ = 262;
|
|
155
|
+
x.lineWidth = 1; x.strokeRect(PX, PY, PW_, PH_);
|
|
156
|
+
for (let c = 1; c < 6; c++) { const y0 = c < 3 ? PY + PH_ + 54 : 274; rule(x, colX(c) - GUT / 2 - 0.5, colX(c) - GUT / 2 + 0.5, y0, 720 - y0); }
|
|
157
|
+
// caption
|
|
158
|
+
x.font = '700 11px "Libre Baskerville"'; x.textAlign = 'left'; x.fillText('“ROBBIE” AT WORK:', L, PY + PH_ + 18);
|
|
159
|
+
const capW = x.measureText('“ROBBIE” AT WORK:').width + 5;
|
|
160
|
+
x.font = 'italic 400 11px "Libre Baskerville"';
|
|
161
|
+
x.fillText('The machine and its tape memory in the laboratory', L + capW, PY + PH_ + 18);
|
|
162
|
+
x.fillText('yesterday. It answered 40 questions without error.', L, PY + PH_ + 32);
|
|
163
|
+
x.font = '400 9px "Libre Baskerville"'; x.textAlign = 'right'; x.fillText('The Daily Algorithm (by Staff Photographer)', PX + PW_, PY + PH_ + 46);
|
|
164
|
+
for (let c = 0; c < 3; c++) column(x, r, colX(c), PY + PH_ + 52, 720);
|
|
165
|
+
// lead story decks in col 3
|
|
166
|
+
const dk = colX(3) + COLW / 2;
|
|
167
|
+
x.textAlign = 'center';
|
|
168
|
+
x.font = '700 20px "Libre Baskerville"'; ['MACHINE', 'SOLVES', 'PROBLEMS', 'UNAIDED'].forEach((s, i) => x.fillText(s, dk, 300 + i * 24));
|
|
169
|
+
rule(x, dk - 30, dk + 30, 384, 1);
|
|
170
|
+
x.font = 'italic 400 13px "Libre Baskerville"'; ['Scientists Report Device', 'Can ‘Learn’ From Its', 'Own Mistakes'].forEach((s, i) => x.fillText(s, dk, 404 + i * 16));
|
|
171
|
+
rule(x, dk - 30, dk + 30, 446, 1);
|
|
172
|
+
x.font = '400 10.5px "Libre Baskerville"'; x.fillText('By A STAFF CORRESPONDENT', dk, 464);
|
|
173
|
+
column(x, r, colX(3), 470, 720, { dateline: 'CAMBRIDGE, Mass., Oct. 8—', subheads: true });
|
|
174
|
+
column(x, r, colX(4), 276, 720, { subheads: true });
|
|
175
|
+
// second story col 5
|
|
176
|
+
const d2 = colX(5) + COLW / 2;
|
|
177
|
+
x.textAlign = 'center'; x.font = '700 17px "Libre Baskerville"';
|
|
178
|
+
['ROCKET TEST', 'SET FOR DAWN'].forEach((s, i) => x.fillText(s, d2, 296 + i * 21));
|
|
179
|
+
rule(x, d2 - 26, d2 + 26, 330, 1);
|
|
180
|
+
x.font = 'italic 400 12px "Libre Baskerville"'; ['Launch Crews Ready at', 'Desert Range'].forEach((s, i) => x.fillText(s, d2, 348 + i * 15));
|
|
181
|
+
column(x, r, colX(5), 372, 600, { dateline: 'WHITE SANDS, Oct. 8—' });
|
|
182
|
+
x.font = 'italic 400 10px "Libre Baskerville"'; x.textAlign = 'center'; x.fillText('Continued on Page 31, Column 4', d2, 612);
|
|
183
|
+
rule(x, colX(5), colX(5) + COLW, 622, 1);
|
|
184
|
+
x.font = '700 14px "Libre Baskerville"'; x.fillText('STOCKS EDGE UP', d2, 642);
|
|
185
|
+
column(x, r, colX(5), 650, 720);
|
|
186
|
+
return { PX, PY, PW_, PH_ };
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
// ================= EXPLAINER: reading the paper — front page, then turn to the science page =================
|
|
190
|
+
const NE = H.ease, ncl = H.clamp, nseg = (t, a, b) => ncl((t - a) / (b - a));
|
|
191
|
+
const nhash = (x, y, s) => { let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(s, 1442695041)) | 0; h = Math.imul(h ^ (h >>> 13), 1274126177); h ^= h >>> 16; return (h >>> 0) / 4294967296; };
|
|
192
|
+
let NX = null;
|
|
193
|
+
|
|
194
|
+
const graySrc = (w, h, fn, blur = 1.1) => { const c = H.layer(w, h), x = c.getContext('2d'); fn(x); const o = H.layer(w, h), ox = o.getContext('2d'); ox.filter = `blur(${blur}px)`; ox.drawImage(c, 0, 0); return { d: ox.getImageData(0, 0, w, h).data, w, h }; };
|
|
195
|
+
const catSrc = () => graySrc(200, 200, (x) => {
|
|
196
|
+
let g = x.createLinearGradient(0, 0, 200, 200); g.addColorStop(0, '#4a4a4a'); g.addColorStop(1, '#1c1c1c'); x.fillStyle = g; x.fillRect(0, 0, 200, 200);
|
|
197
|
+
x.fillStyle = '#d6d6d6'; x.beginPath(); x.ellipse(100, 215, 84, 52, 0, 0, 7); x.fill();
|
|
198
|
+
x.fillStyle = '#f4f4f4'; x.beginPath(); x.moveTo(38, 100); x.lineTo(44, 18); x.lineTo(86, 58); x.lineTo(114, 58); x.lineTo(156, 18); x.lineTo(162, 100); x.ellipse(100, 112, 64, 58, 0, 0, Math.PI); x.closePath(); x.fill();
|
|
199
|
+
x.fillStyle = '#8a8a8a'; x.beginPath(); x.moveTo(52, 44); x.lineTo(56, 76); x.lineTo(76, 64); x.closePath(); x.moveTo(148, 44); x.lineTo(144, 76); x.lineTo(124, 64); x.closePath(); x.fill();
|
|
200
|
+
x.fillStyle = '#000'; x.beginPath(); x.ellipse(76, 106, 13, 15, 0, 0, 7); x.ellipse(124, 106, 13, 15, 0, 0, 7); x.fill();
|
|
201
|
+
x.fillStyle = '#fff'; x.beginPath(); x.arc(72, 101, 4.5, 0, 7); x.arc(120, 101, 4.5, 0, 7); x.fill();
|
|
202
|
+
x.fillStyle = '#222'; x.beginPath(); x.moveTo(90, 130); x.lineTo(110, 130); x.lineTo(100, 142); x.closePath(); x.fill();
|
|
203
|
+
x.strokeStyle = '#555'; x.lineWidth = 2.5; x.beginPath(); for (const s of [-1, 1]) { x.moveTo(100 + s * 14, 142); x.lineTo(100 + s * 62, 134); x.moveTo(100 + s * 14, 147); x.lineTo(100 + s * 60, 152); } x.stroke();
|
|
204
|
+
}, 0.8);
|
|
205
|
+
const dogSrc = () => graySrc(200, 200, (x) => {
|
|
206
|
+
let g = x.createLinearGradient(0, 0, 200, 200); g.addColorStop(0, '#9a9a9a'); g.addColorStop(1, '#5a5a5a'); x.fillStyle = g; x.fillRect(0, 0, 200, 200);
|
|
207
|
+
g = x.createRadialGradient(85, 90, 10, 100, 110, 80); g.addColorStop(0, '#ffffff'); g.addColorStop(1, '#d0d0d0');
|
|
208
|
+
x.fillStyle = g; x.beginPath(); x.ellipse(100, 108, 56, 64, 0, 0, 7); x.fill();
|
|
209
|
+
x.fillStyle = '#0a0a0a'; for (const s of [-1, 1]) { x.beginPath(); x.ellipse(100 + s * 58, 112, 22, 50, s * 0.25, 0, 7); x.fill(); }
|
|
210
|
+
x.fillStyle = '#dedede'; x.beginPath(); x.ellipse(100, 140, 30, 24, 0, 0, 7); x.fill();
|
|
211
|
+
x.fillStyle = '#111'; x.beginPath(); x.ellipse(100, 128, 13, 9, 0, 0, 7); x.fill(); x.beginPath(); x.arc(78, 96, 8, 0, 7); x.arc(122, 96, 8, 0, 7); x.fill();
|
|
212
|
+
x.fillStyle = '#fff'; x.beginPath(); x.arc(75, 93, 3, 0, 7); x.arc(119, 93, 3, 0, 7); x.fill();
|
|
213
|
+
});
|
|
214
|
+
const machineSrc = (t) => graySrc(280, 170, (x) => {
|
|
215
|
+
let g = x.createLinearGradient(0, 0, 0, 170); g.addColorStop(0, '#2a2a2a'); g.addColorStop(0.78, '#4a4a4a'); g.addColorStop(0.8, '#9a9a9a'); g.addColorStop(1, '#b4b4b4'); x.fillStyle = g; x.fillRect(0, 0, 280, 170);
|
|
216
|
+
for (let i = 0; i < 2; i++) {
|
|
217
|
+
const cx = 10 + i * 82; g = x.createLinearGradient(cx, 0, cx + 74, 0); g.addColorStop(0, '#e0e0e0'); g.addColorStop(1, '#8a8a8a');
|
|
218
|
+
x.fillStyle = g; x.fillRect(cx, 10, 74, 128); x.fillStyle = '#222'; x.fillRect(cx + 7, 18, 60, 74);
|
|
219
|
+
for (const ry of [38, 72]) { const a = t * (i ? 2.1 : -1.7) + ry; x.fillStyle = '#c8c8c8'; x.beginPath(); x.arc(cx + 37, ry, 15, 0, 7); x.fill(); x.fillStyle = '#333'; x.beginPath(); x.arc(cx + 37, ry, 9, 0, 7); x.fill(); x.fillStyle = '#eee'; for (let k = 0; k < 3; k++) { const aa = a + k * 2.094; x.beginPath(); x.arc(cx + 37 + Math.cos(aa) * 6, ry + Math.sin(aa) * 6, 2.4, 0, 7); x.fill(); } }
|
|
220
|
+
x.fillStyle = 'rgba(0,0,0,0.4)'; x.fillRect(cx + 74, 14, 6, 126);
|
|
221
|
+
}
|
|
222
|
+
g = x.createLinearGradient(178, 0, 272, 0); g.addColorStop(0, '#b0b0b0'); g.addColorStop(1, '#5c5c5c'); x.fillStyle = g; x.fillRect(178, 30, 94, 108);
|
|
223
|
+
x.fillStyle = '#121212'; x.fillRect(186, 40, 78, 58);
|
|
224
|
+
const lr = H.rng(4 + Math.floor(t * 4));
|
|
225
|
+
for (let j = 0; j < 4; j++) for (let i = 0; i < 5; i++) { x.fillStyle = lr.chance(0.5) ? '#fff' : '#444'; x.beginPath(); x.arc(196 + i * 14.5, 50 + j * 13, 3.6, 0, 7); x.fill(); }
|
|
226
|
+
});
|
|
227
|
+
// coarse 45° halftone of a grey source into page rect
|
|
228
|
+
const halfDots = (k, src, x0, y0, dw, dh, cell = 4.6) => {
|
|
229
|
+
const ca = Math.SQRT1_2, ext = Math.hypot(dw, dh);
|
|
230
|
+
k.save(); k.beginPath(); k.rect(x0, y0, dw, dh); k.clip(); k.translate(x0, y0); k.fillStyle = '#000'; k.beginPath();
|
|
231
|
+
for (let v = -ext; v < ext; v += cell) for (let u = -ext; u < ext; u += cell) {
|
|
232
|
+
const px = (u - v) * ca, py = (u + v) * ca;
|
|
233
|
+
if (px < -cell || py < -cell || px > dw + cell || py > dh + cell) continue;
|
|
234
|
+
const sx = ncl(px * src.w / dw, 0, src.w - 1) | 0, sy = ncl(py * src.h / dh, 0, src.h - 1) | 0;
|
|
235
|
+
const dark = 1 - ncl((src.d[(sy * src.w + sx) * 4] / 255 - 0.06) / 0.86);
|
|
236
|
+
const rr = cell * 0.62 * Math.sqrt(Math.max(0, dark * 1.08 - 0.02)); if (rr < 0.3) continue;
|
|
237
|
+
k.moveTo(px + rr, py); k.arc(px, py, rr, 0, Math.PI * 2);
|
|
238
|
+
}
|
|
239
|
+
k.fill(); k.restore(); k.lineWidth = 1; k.strokeRect(x0, y0, dw, dh);
|
|
240
|
+
};
|
|
241
|
+
// newspaper diagram arrow: hairline-crisp shaft, solid head, drawn on
|
|
242
|
+
const npArrow = (k, p0, cp, p1, u, w = 3.5) => {
|
|
243
|
+
if (u <= 0) return;
|
|
244
|
+
const q = (s) => [(1 - s) * (1 - s) * p0[0] + 2 * (1 - s) * s * cp[0] + s * s * p1[0], (1 - s) * (1 - s) * p0[1] + 2 * (1 - s) * s * cp[1] + s * s * p1[1]];
|
|
245
|
+
k.save(); k.lineWidth = w; k.lineCap = 'butt'; k.beginPath(); k.moveTo(p0[0], p0[1]); for (let i = 1; i <= 30 * u; i++) { const p = q(i / 30); k.lineTo(p[0], p[1]); }
|
|
246
|
+
const e = q(u), b = q(Math.max(0, u - 0.04)), a = Math.atan2(e[1] - b[1], e[0] - b[0]); k.lineTo(e[0], e[1]); k.stroke();
|
|
247
|
+
k.beginPath(); k.moveTo(e[0] + Math.cos(a) * 14, e[1] + Math.sin(a) * 14); k.lineTo(e[0] + Math.cos(a + 2.6) * 13, e[1] + Math.sin(a + 2.6) * 13); k.lineTo(e[0] + Math.cos(a - 2.6) * 13, e[1] + Math.sin(a - 2.6) * 13); k.closePath(); k.fill(); k.restore();
|
|
248
|
+
return q;
|
|
249
|
+
};
|
|
250
|
+
const txt = (k, s, font, x, y, align = 'left', ls = 0) => { k.font = font; k.textAlign = align; k.textBaseline = 'alphabetic'; k.letterSpacing = ls + 'px'; k.fillText(s, x, y); k.letterSpacing = '0px'; };
|
|
251
|
+
const reverseBar = (k, x0, y0, w, h, s) => { k.fillRect(x0, y0, w, h); k.save(); k.globalCompositeOperation = 'destination-out'; txt(k, s, '700 15px "Libre Baskerville"', x0 + 12, y0 + h / 2 + 5, 'left', 2); k.restore(); };
|
|
252
|
+
|
|
253
|
+
// ---------------- the two sheets (page coords 1280 x 720) ----------------
|
|
254
|
+
const frontPage = (k, t) => {
|
|
255
|
+
const r = H.rng(19), S = NX;
|
|
256
|
+
k.fillStyle = '#000'; k.strokeStyle = '#000';
|
|
257
|
+
txt(k, 'LATE CITY EDITION', '400 11px "Libre Baskerville"', L, 34); txt(k, 'Weather: Fair, mild today; clear tonight.', '400 11px "Libre Baskerville"', R, 34, 'right');
|
|
258
|
+
k.lineWidth = 1.2; k.strokeRect(L, 50, 190, 70); k.strokeRect(R - 190, 50, 190, 70);
|
|
259
|
+
txt(k, '“All the News', 'italic 400 13px "Libre Baskerville"', L + 95, 78, 'center'); txt(k, 'That Computes”', 'italic 400 13px "Libre Baskerville"', L + 95, 98, 'center');
|
|
260
|
+
txt(k, 'SPECIAL REPORT', '700 11px "Libre Baskerville"', R - 95, 76, 'center'); txt(k, 'Inside: Page 14', 'italic 400 12px "Libre Baskerville"', R - 95, 96, 'center');
|
|
261
|
+
txt(k, 'The Daily Algorithm', '400 90px "UnifrakturMaguntia"', 640, 118, 'center');
|
|
262
|
+
k.fillRect(L, 130, R - L, 3.2); k.fillRect(L, 136, R - L, 1);
|
|
263
|
+
txt(k, 'VOL. CVI . . . No. 36,512', '700 10.5px "Libre Baskerville"', L, 150); txt(k, 'NEW YORK, THURSDAY, OCTOBER 9, 1958', '700 10.5px "Libre Baskerville"', 640, 150, 'center'); txt(k, 'FIVE CENTS', '700 10.5px "Libre Baskerville"', R, 150, 'right');
|
|
264
|
+
k.fillRect(L, 157, R - L, 1); k.fillRect(L, 160, R - L, 2.6);
|
|
265
|
+
k.font = '700 104px "Oswald"'; const hw = k.measureText('HOW DOES AI LEARN?').width;
|
|
266
|
+
k.save(); k.translate(640, 262); k.scale(Math.min(1, (R - L) / hw), 1); txt(k, 'HOW DOES AI LEARN?', '700 104px "Oswald"', 0, 0, 'center'); k.restore();
|
|
267
|
+
txt(k, 'Scientists Say the Machines Are Taught by Example, Not by Rules', 'italic 400 19px "Libre Baskerville"', 640, 292, 'center');
|
|
268
|
+
k.fillRect(L, 304, R - L, 1.3);
|
|
269
|
+
// infographic
|
|
270
|
+
const BX = L, BY = 316, BW = 772, BH = 396;
|
|
271
|
+
k.lineWidth = 1.5; k.strokeRect(BX, BY, BW, BH);
|
|
272
|
+
reverseBar(k, BX, BY, BW, 34, 'BY THE NUMBERS: THE TRAINING PILE');
|
|
273
|
+
const kinds = ['cat', 'dog', 'cat', 'cat', 'dog', 'cat', 'dog', 'dog'];
|
|
274
|
+
for (let i = 0; i < 8; i++) {
|
|
275
|
+
const t0 = 2.95 + i * 0.17; if (t < t0) continue;
|
|
276
|
+
const cx = 72 + (i % 4) * 104, cy = 366 + ((i / 4) | 0) * 124, kind = kinds[i];
|
|
277
|
+
halfDots(k, kind === 'cat' ? S.cat : S.dog, cx, cy, 92, 92, 3.3);
|
|
278
|
+
txt(k, kind.toUpperCase(), '700 21px "Oswald"', cx + 46, cy + 116, 'center', 3);
|
|
279
|
+
}
|
|
280
|
+
const aq = npArrow(k, [500, 470], [545, 445], [592, 470], NE.out(nseg(t, 3.75, 4.1)));
|
|
281
|
+
if (t > 3.9) txt(k, 'FED IN', '700 19px "Oswald"', 546, 508, 'center', 2);
|
|
282
|
+
halfDots(k, machineSrc(t), 602, 380, 206, 125, 4.2);
|
|
283
|
+
txt(k, 'THE MACHINE', '700 20px "Oswald"', 705, 530, 'center', 2);
|
|
284
|
+
// pictures stepping along the arrow into the machine (stop-motion, re-set each move)
|
|
285
|
+
if (t > 4.3 && t < 5.9) { const ph = Math.floor((t - 4.3) * 6); for (let j = 0; j < 2; j++) { const u = ((ph + j * 3) % 6) / 6; const p = [500 + u * 92, 470 - Math.sin(u * Math.PI) * 25]; k.fillStyle = '#fff'; halfDots(k, (ph + j) % 2 ? S.dog : S.cat, p[0] - 20, p[1] - 52, 40, 40, 2.8); } k.fillStyle = '#000'; }
|
|
286
|
+
// counter
|
|
287
|
+
k.fillRect(BX + 16, 628, BW - 32, 1);
|
|
288
|
+
txt(k, 'EXAMPLES STUDIED', '700 18px "Oswald"', 340, 650, 'left', 2);
|
|
289
|
+
const n = Math.round(1e6 * Math.pow(nseg(t, 3.0, 5.4), 2.3));
|
|
290
|
+
if (t > 2.9) txt(k, n.toLocaleString('en-US'), '700 60px "Oswald"', 72, 704, 'left', 1);
|
|
291
|
+
if (t > 5.4) txt(k, '— and counting.', 'italic 400 19px "Libre Baskerville"', 340, 690);
|
|
292
|
+
// columns
|
|
293
|
+
k.fillRect(840, 316, 1, 404);
|
|
294
|
+
txt(k, 'MACHINE PASSES', '700 15px "Libre Baskerville"', 848 + 184 / 2, 334, 'center'); txt(k, 'MILLION-PICTURE TEST', '700 15px "Libre Baskerville"', 848 + 92, 352, 'center');
|
|
295
|
+
column(k, r, 848, 362, 720, { dateline: 'CAMBRIDGE, Mass., Oct. 8—', subheads: true });
|
|
296
|
+
k.fillRect(1040, 316, 1, 404);
|
|
297
|
+
txt(k, 'ROCKET TEST', '700 15px "Libre Baskerville"', 1048 + 92, 334, 'center'); txt(k, 'SET FOR DAWN', '700 15px "Libre Baskerville"', 1048 + 92, 352, 'center');
|
|
298
|
+
column(k, r, 1048, 362, 720, { dateline: 'WHITE SANDS, Oct. 8—' });
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const insidePage = (k, t) => {
|
|
302
|
+
const r = H.rng(44), S = NX;
|
|
303
|
+
k.fillStyle = '#000'; k.strokeStyle = '#000';
|
|
304
|
+
txt(k, '14', '700 12px "Libre Baskerville"', L, 34); txt(k, 'THE DAILY ALGORITHM, THURSDAY, OCTOBER 9, 1958', '400 11px "Libre Baskerville"', 640, 34, 'center'); txt(k, 'L+', '700 12px "Libre Baskerville"', R, 34, 'right');
|
|
305
|
+
k.fillRect(L, 42, R - L, 1);
|
|
306
|
+
txt(k, 'Science & Invention', 'italic 400 40px "Playfair Display"', L, 88);
|
|
307
|
+
txt(k, 'A WEEKLY REPORT ON THE NEW MACHINES', '700 11px "Libre Baskerville"', R, 86, 'right', 2);
|
|
308
|
+
k.fillRect(L, 100, R - L, 4); k.fillRect(L, 107, R - L, 1);
|
|
309
|
+
txt(k, 'HOW THE MACHINE LEARNS', '700 46px "Oswald"', L, 160, 'left', 1);
|
|
310
|
+
txt(k, 'A Step-by-Step Look Inside the Electronic Brain', 'italic 400 16px "Libre Baskerville"', R, 158, 'right');
|
|
311
|
+
// diagram
|
|
312
|
+
halfDots(k, S.cat, 70, 210, 200, 190, 4.0);
|
|
313
|
+
txt(k, 'A NEW PICTURE', '700 21px "Oswald"', 170, 426, 'center', 2);
|
|
314
|
+
npArrow(k, [282, 300], [362, 284], [446, 300], NE.out(nseg(t, 7.0, 7.35)));
|
|
315
|
+
if (t > 7.1) txt(k, '1. GUESS', '700 26px "Oswald"', 362, 264, 'center', 2);
|
|
316
|
+
halfDots(k, machineSrc(t), 456, 212, 280, 170, 4.6);
|
|
317
|
+
txt(k, 'THE MACHINE', '700 21px "Oswald"', 596, 404, 'center', 2);
|
|
318
|
+
const adj = NE.inOut(nseg(t, 9.0, 9.5));
|
|
319
|
+
[-2.3, -0.7, 0.9].forEach((a0, i) => { const kx = 552 + i * 44, ky = 438, a = a0 + adj * [1.9, -1.5, 2.4][i]; k.lineWidth = 2.5; k.beginPath(); k.arc(kx, ky, 15, 0, 7); k.stroke(); k.lineWidth = 4; k.beginPath(); k.moveTo(kx, ky); k.lineTo(kx + Math.cos(a) * 13, ky + Math.sin(a) * 13); k.stroke(); });
|
|
320
|
+
npArrow(k, [748, 300], [818, 284], [890, 300], NE.out(nseg(t, 7.4, 7.65)));
|
|
321
|
+
if (t > 7.65) {
|
|
322
|
+
k.lineWidth = 2.5; k.strokeRect(898, 236, 250, 124);
|
|
323
|
+
const right = t > 9.6;
|
|
324
|
+
txt(k, right ? 'cat' : 'dog?', 'italic 400 76px "Playfair Display"', 1023, 322, 'center');
|
|
325
|
+
if (!right && t > 8.0) { k.save(); k.lineWidth = 9; k.translate(1148, 236); const u = NE.back(nseg(t, 8.0, 8.15)); k.scale(u, u); k.beginPath(); k.moveTo(-20, -20); k.lineTo(20, 20); k.moveTo(20, -20); k.lineTo(-20, 20); k.stroke(); k.restore(); }
|
|
326
|
+
if (right) { k.save(); k.lineWidth = 9; k.lineJoin = 'miter'; k.translate(1148, 236); const u = NE.back(nseg(t, 9.6, 9.8)); k.scale(u, u); k.beginPath(); k.moveTo(-20, 0); k.lineTo(-6, 15); k.lineTo(22, -20); k.stroke(); k.restore(); }
|
|
327
|
+
}
|
|
328
|
+
if (t > 8.0) txt(k, '2. CHECK', '700 26px "Oswald"', 1023, 212, 'center', 2);
|
|
329
|
+
if (t > 8.2 && t < 9.6) txt(k, 'Wrong — it’s a cat.', 'italic 400 28px "Libre Baskerville"', 1023, 393, 'center');
|
|
330
|
+
if (t > 9.6) txt(k, 'Right, this time.', 'italic 400 28px "Libre Baskerville"', 1023, 393, 'center');
|
|
331
|
+
const back = npArrow(k, [1023, 404], [880, 476], [652, 444], NE.inOut(nseg(t, 8.55, 9.0)));
|
|
332
|
+
if (t > 8.75) {
|
|
333
|
+
// paste-up knockout: the rule passes *behind* the label (a paper-coloured halo is cut out of the ink first)
|
|
334
|
+
k.save(); k.globalCompositeOperation = 'destination-out'; k.font = '700 26px "Oswald"'; k.textAlign = 'center'; k.textBaseline = 'alphabetic'; k.letterSpacing = '2px';
|
|
335
|
+
k.lineWidth = 11; k.lineJoin = 'round'; k.strokeText('3. ADJUST', 860, 446); k.letterSpacing = '0px'; k.restore();
|
|
336
|
+
txt(k, '3. ADJUST', '700 26px "Oswald"', 860, 446, 'center', 2);
|
|
337
|
+
}
|
|
338
|
+
if (back && t > 9.8) { const u = ((t - 9.8) * 1.1) % 1, p = back(u); k.beginPath(); k.arc(p[0], p[1], 6, 0, 7); k.fill(); }
|
|
339
|
+
k.fillRect(L, 456, R - L, 1.3);
|
|
340
|
+
// pull quote (typeset in left to right as the reader gets there)
|
|
341
|
+
const pq = NE.inOut(nseg(t, 13.0, 13.5));
|
|
342
|
+
k.fillRect(L, 474, 584 * NE.out(nseg(t, 12.95, 13.2)), 3);
|
|
343
|
+
if (pq > 0) { k.save(); k.beginPath(); k.rect(L, 476, 584 * pq, 120); k.clip();
|
|
344
|
+
txt(k, '“More examples', 'italic 400 44px "Playfair Display"', 340, 524, 'center'); txt(k, '→ better guesses.”', 'italic 400 44px "Playfair Display"', 340, 574, 'center'); k.restore(); }
|
|
345
|
+
k.fillRect(L, 592, 584 * NE.out(nseg(t, 13.3, 13.55)), 3);
|
|
346
|
+
for (let c = 0; c < 3; c++) column(k, r, colX(c), 600, 720);
|
|
347
|
+
k.fillRect(640, 466, 1, 254);
|
|
348
|
+
// chart
|
|
349
|
+
const CX0 = 652, CY0 = 466;
|
|
350
|
+
txt(k, 'ACCURACY OVER TRAINING ROUNDS', '700 18px "Oswald"', CX0 + 10, CY0 + 22, 'left', 1.5);
|
|
351
|
+
const X0 = 700, X1 = 1040, Y0 = 680, Y1 = 500, yv = (v) => Y0 - (v - 50) / 50 * (Y0 - Y1);
|
|
352
|
+
k.lineWidth = 1; for (let v = 60; v <= 100; v += 10) { k.save(); k.setLineDash([2, 4]); k.beginPath(); k.moveTo(X0, yv(v)); k.lineTo(X1, yv(v)); k.stroke(); k.restore(); txt(k, String(v), '400 11px "Libre Baskerville"', X0 - 8, yv(v) + 4, 'right'); }
|
|
353
|
+
k.lineWidth = 1.5; k.beginPath(); k.moveTo(X0, Y1 - 10); k.lineTo(X0, Y0); k.lineTo(X1, Y0); k.stroke();
|
|
354
|
+
txt(k, 'TRAINING ROUNDS →', '700 14px "Oswald"', X1, Y0 + 22, 'right', 1.5);
|
|
355
|
+
const prog = NE.inOut(nseg(t, 10.9, 12.3)), acc = (u) => 61 + 36 * (1 - Math.exp(-u * 3.4)) / (1 - Math.exp(-3.4)) + Math.sin(u * 37) * 1.8 * (1 - u);
|
|
356
|
+
k.lineWidth = 3.5; k.lineJoin = 'round'; k.beginPath(); for (let i = 0; i <= 60 * prog; i++) { const u = i / 60; i ? k.lineTo(X0 + u * (X1 - X0), yv(acc(u))) : k.moveTo(X0, yv(acc(0))); } k.stroke();
|
|
357
|
+
if (prog > 0) { const ex = X0 + prog * (X1 - X0), ey = yv(acc(prog)); k.beginPath(); k.arc(ex, ey, 5, 0, 7); k.fill(); }
|
|
358
|
+
k.lineWidth = 2; k.strokeRect(1062, 520, 160, 132);
|
|
359
|
+
txt(k, 'ACCURACY', '700 17px "Oswald"', 1142, 546, 'center', 2);
|
|
360
|
+
if (t > 10.6) txt(k, Math.round(61 + 36 * prog) + '%', '700 66px "Oswald"', 1142, 626, 'center');
|
|
361
|
+
txt(k, 'Source: the laboratory', 'italic 400 10px "Libre Baskerville"', 1222, 712, 'right');
|
|
362
|
+
};
|
|
363
|
+
|
|
364
|
+
// ---------------- camera + printing ----------------
|
|
365
|
+
const CAM = [
|
|
366
|
+
[0, 640, 205, 1.3], [2.2, 640, 215, 1.55], [2.9, 470, 455, 1.35], [5.5, 470, 465, 1.4], [5.85, 640, 360, 0.92],
|
|
367
|
+
[6.55, 640, 360, 0.92], [7.0, 380, 318, 1.6], [7.45, 390, 318, 1.6], [7.9, 830, 318, 1.6], [10.5, 820, 322, 1.62], [11.0, 942, 530, 1.9], [12.85, 948, 532, 1.95],
|
|
368
|
+
[13.3, 340, 530, 1.9], [14.5, 345, 532, 1.98], [15.2, 640, 360, 1.0],
|
|
369
|
+
];
|
|
370
|
+
const camAt = (t) => {
|
|
371
|
+
if (t <= CAM[0][0]) return CAM[0].slice(1);
|
|
372
|
+
for (let i = 0; i < CAM.length - 1; i++) if (t < CAM[i + 1][0]) { const a = CAM[i], b = CAM[i + 1], u = NE.inOut(nseg(t, a[0], b[0])); return [H.lerp(a[1], b[1], u), H.lerp(a[2], b[2], u), H.lerp(a[3], b[3], u)]; }
|
|
373
|
+
return CAM[CAM.length - 1].slice(1);
|
|
374
|
+
};
|
|
375
|
+
const renderSheet = (W, H_, si, t, cam) => {
|
|
376
|
+
const [cx, cy, z] = cam;
|
|
377
|
+
const ink = H.layer(W, H_), k = ink.getContext('2d');
|
|
378
|
+
k.setTransform(z, 0, 0, z, W / 2 - cx * z, H_ / 2 - cy * z);
|
|
379
|
+
(si === 0 ? frontPage : insidePage)(k, t);
|
|
380
|
+
const fd = k.getImageData(0, 0, W, H_).data, bd = NX.back[si];
|
|
381
|
+
const img = new ImageData(W, H_), o = img.data, { low, GW, GH } = NX;
|
|
382
|
+
const iz = 1 / z;
|
|
383
|
+
for (let y = 0; y < H_; y++) {
|
|
384
|
+
const py = cy + (y - H_ / 2) * iz, ipy = py | 0;
|
|
385
|
+
for (let x = 0; x < W; x++) {
|
|
386
|
+
const i = (y * W + x) * 4, px = cx + (x - W / 2) * iz, ipx = px | 0;
|
|
387
|
+
if (px < 0 || py < 0 || px >= 1280 || py >= 720) {
|
|
388
|
+
const dd = Math.max(-px, px - 1280, -py, py - 720), sh = 1 - 0.5 * Math.exp(-dd / 10);
|
|
389
|
+
const n = (nhash(x, y, 3) - 0.5) * 6; o[i] = (38 + n) * sh; o[i + 1] = (32 + n) * sh; o[i + 2] = (28 + n) * sh; o[i + 3] = 255; continue;
|
|
390
|
+
}
|
|
391
|
+
const li = ((py * GH / 720) | 0) * GW + ((px * GW / 1280) | 0), m = low[li * 2], f = low[li * 2 + 1], hh = nhash(ipx, ipy, 1);
|
|
392
|
+
const edge = Math.min(px, 1280 - px, py * 1.3, 720 - py) / 120, age = 0.07 * (1 - ncl(edge)) + m * 0.035 + f * 0.015;
|
|
393
|
+
let r = PAPER[0] * (1 - age * 0.7) + (hh - 0.5) * 12, g = PAPER[1] * (1 - age) + (hh - 0.5) * 12, b = PAPER[2] * (1 - age * 1.6) + (hh - 0.5) * 12;
|
|
394
|
+
if (nhash(ipx >> 1, ipy >> 1, 4) > 0.9975) { r -= 40; g -= 40; b -= 38; }
|
|
395
|
+
const bt = (bd[ipy * 1280 + ipx] / 255) * 0.075; r *= 1 - bt; g *= 1 - bt * 1.05; b *= 1 - bt * 1.1;
|
|
396
|
+
const a = fd[i + 3] / 255;
|
|
397
|
+
if (a > 0) {
|
|
398
|
+
let cov = ncl((a - (0.5 + (nhash(ipx, ipy, 2) - 0.5) * 0.35 + f * 0.15)) * 3 + 0.5) * 0.65 + a * 0.35;
|
|
399
|
+
let dens = 0.86 + m * 0.12 + (nhash(ipx, ipy, 3) - 0.5) * 0.12; if (nhash(ipx, ipy, 5) < 0.025) dens *= 0.45;
|
|
400
|
+
cov *= ncl(dens); r = r * (1 - cov) + INK[0] * cov; g = g * (1 - cov) + INK[1] * cov; b = b * (1 - cov) + INK[2] * cov;
|
|
401
|
+
}
|
|
402
|
+
o[i] = r; o[i + 1] = g; o[i + 2] = b; o[i + 3] = 255;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
const c = H.layer(W, H_); c.getContext('2d').putImageData(img, 0, 0); return c;
|
|
406
|
+
};
|
|
407
|
+
// the reader's red grease pencil (sits on top of the print)
|
|
408
|
+
const pencilRing = (ctx, cam, W, H_, cx, cy, rx, ry, u, seed) => {
|
|
409
|
+
if (u <= 0) return;
|
|
410
|
+
const [ccx, ccy, z] = cam, rr = H.rng(seed);
|
|
411
|
+
ctx.save(); ctx.setTransform(z, 0, 0, z, W / 2 - ccx * z, H_ / 2 - ccy * z); ctx.lineCap = 'round';
|
|
412
|
+
for (let pass = 0; pass < 3; pass++) {
|
|
413
|
+
ctx.strokeStyle = `rgba(196,38,34,${0.55 - pass * 0.12})`; ctx.lineWidth = 3.6 - pass * 0.8; ctx.beginPath();
|
|
414
|
+
const jx = rr.range(-2, 2), jy = rr.range(-2, 2);
|
|
415
|
+
for (let i = 0; i <= 64 * u; i++) { const a = -2.4 + (i / 64) * Math.PI * 2.25, wob = 1 + 0.04 * Math.sin(i * 0.7 + pass); const px = cx + jx + Math.cos(a) * rx * wob, py = cy + jy + Math.sin(a) * ry * wob; i ? ctx.lineTo(px, py) : ctx.moveTo(px, py); }
|
|
416
|
+
ctx.stroke();
|
|
417
|
+
}
|
|
418
|
+
ctx.restore();
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
const npExplainer = (ctx, W, H_, t) => {
|
|
422
|
+
if (!NX) {
|
|
423
|
+
const nz = H.makeNoise(9), GW = 320, GH = 180, low = new Float32Array(GW * GH * 2);
|
|
424
|
+
for (let y = 0; y < GH; y++) for (let x = 0; x < GW; x++) { low[(y * GW + x) * 2] = nz.fbm2(x * 0.03, y * 0.03, 4); low[(y * GW + x) * 2 + 1] = nz.fbm2(x * 0.125 + 50, y * 0.125, 3); }
|
|
425
|
+
const back = [77, 91].map((sd) => { const c = H.layer(1280, 720), x = c.getContext('2d'); x.translate(1280, 0); x.scale(-1, 1); layout(x, H.rng(sd), 0, true); const b = H.layer(1280, 720), bx = b.getContext('2d'); bx.filter = 'blur(1.2px)'; bx.drawImage(c, 0, 0); const d = bx.getImageData(0, 0, 1280, 720).data, a = new Uint8Array(1280 * 720); for (let i = 0; i < a.length; i++) a[i] = d[i * 4 + 3]; return a; });
|
|
426
|
+
NX = { low, GW, GH, back, cat: catSrc(), dog: dogSrc() };
|
|
427
|
+
}
|
|
428
|
+
const T0 = 5.85, T1 = 6.55;
|
|
429
|
+
const cam = camAt(t);
|
|
430
|
+
if (t < T0) ctx.drawImage(renderSheet(W, H_, 0, t, cam), 0, 0);
|
|
431
|
+
else if (t >= T1) ctx.drawImage(renderSheet(W, H_, 1, t, cam), 0, 0);
|
|
432
|
+
else {
|
|
433
|
+
// page turn: the front sheet swings over on its left edge, revealing the science page
|
|
434
|
+
ctx.drawImage(renderSheet(W, H_, 1, t, cam), 0, 0);
|
|
435
|
+
const u = NE.inOut(nseg(t, T0, T1)), c = Math.cos(Math.PI * u), z = cam[2];
|
|
436
|
+
const spine = W / 2 - cam[0] * z, top = H_ / 2 - cam[1] * z, pw = 1280 * z, ph = 720 * z;
|
|
437
|
+
// shadow the turning leaf throws onto the page beneath
|
|
438
|
+
const sx = spine + pw * Math.max(0, c);
|
|
439
|
+
const sg = ctx.createLinearGradient(sx, 0, sx + 140 * (1 - Math.abs(c)) + 10, 0); sg.addColorStop(0, 'rgba(0,0,0,0.35)'); sg.addColorStop(1, 'rgba(0,0,0,0)');
|
|
440
|
+
ctx.fillStyle = sg; ctx.fillRect(sx, top, 160, ph);
|
|
441
|
+
const leaf = c > 0 ? renderSheet(W, H_, 0, T0, [640, 360, 1]) : null;
|
|
442
|
+
ctx.save(); ctx.translate(spine, top);
|
|
443
|
+
const lift = Math.sin(Math.PI * u) * 22 * z;
|
|
444
|
+
if (c > 0) { ctx.transform(c, -lift / pw * 0.6, 0, 1, 0, 0); ctx.drawImage(leaf, 0, 0, W, H_, 0, 0, pw, ph); ctx.fillStyle = `rgba(0,0,0,${0.35 * (1 - c)})`; ctx.fillRect(0, 0, pw, ph); }
|
|
445
|
+
else { ctx.transform(c, lift / pw * 0.6, 0, 1, 0, 0); ctx.fillStyle = `rgb(${PAPER[0] - 20},${PAPER[1] - 22},${PAPER[2] - 26})`; ctx.fillRect(0, 0, pw, ph); }
|
|
446
|
+
ctx.restore();
|
|
447
|
+
}
|
|
448
|
+
// reader's pencil marks (emphasis)
|
|
449
|
+
if (t >= T1) {
|
|
450
|
+
if (t > 8.1 && t < 9.6) pencilRing(ctx, cam, W, H_, 1023, 300, 146, 74, nseg(t, 8.1, 8.45), 3);
|
|
451
|
+
if (t > 12.35) pencilRing(ctx, cam, W, H_, 1142, 590, 100, 82, nseg(t, 12.35, 12.7), 5);
|
|
452
|
+
if (t > 13.55) { const [ccx, ccy, z] = cam; ctx.save(); ctx.setTransform(z, 0, 0, z, W / 2 - ccx * z, H_ / 2 - ccy * z); ctx.strokeStyle = 'rgba(196,38,34,0.6)'; ctx.lineWidth = 3; ctx.lineCap = 'round'; const u = NE.out(nseg(t, 13.55, 13.85)); ctx.beginPath(); ctx.moveTo(150, 584); ctx.quadraticCurveTo(340, 590, 150 + 380 * u, 582 + 4 * u); ctx.stroke(); ctx.restore(); }
|
|
453
|
+
} else if (t > 5.4) pencilRing(ctx, cam, W, H_, 230, 680, 175, 36, nseg(t, 5.4, 5.75), 7);
|
|
454
|
+
};
|
|
455
|
+
|
|
456
|
+
registerStyle({
|
|
457
|
+
id: 'newsprint',
|
|
458
|
+
name: 'Vintage Newsprint',
|
|
459
|
+
category: 'Print',
|
|
460
|
+
description: 'A 1950s broadsheet front page: blackletter masthead, condensed banner headline, dense justified greeked columns and a coarse halftone wire photo, printed in thin uneven ink on yellowed pulp with show-through from the reverse.',
|
|
461
|
+
motion: 'The classic spinning-newspaper reveal: the page whirls up out of the dark and slaps flat to fill the frame, then the camera creeps in on the headline while the console lamps blink and the tape reels turn in the halftone photo.',
|
|
462
|
+
fonts: ['400 90px "UnifrakturMaguntia"', '700 100px "Oswald"', '400 10.4px "Libre Baskerville"', '700 10.4px "Libre Baskerville"', 'italic 400 13px "Libre Baskerville"', '700 64px "Playfair Display"', 'italic 400 44px "Playfair Display"', '700 21px "Oswald"'],
|
|
463
|
+
duration: 4.5,
|
|
464
|
+
frameT: 4.4,
|
|
465
|
+
readsPixels: true,
|
|
466
|
+
explainerDuration: 15.5,
|
|
467
|
+
explainerNote: 'The viewer reads the paper: the camera tracks from the banner headline down to a By-the-Numbers infographic, the page physically turns to an inside Science spread whose diagram, chart and pull quote carry the rest; arrows are crisp newspaper-diagram rules, numbers are set in Oswald, and emphasis is a reader\'s red grease-pencil ring.',
|
|
468
|
+
explainer(ctx, W, H_, t) { npExplainer(ctx, W, H_, t); },
|
|
469
|
+
// Sound: someone reading the morning paper. The broadsheet rustles in their hands as the eye moves on, the
|
|
470
|
+
// page turn is the big paper sweep and flop, the reader's red grease pencil squeaks out the rings and the
|
|
471
|
+
// underline (waxy, low), diagram rules are drawn with a dry pen, counters tick quietly, the pull quote is set
|
|
472
|
+
// with a burst of linotype clatter, and the hits are soft knocks of a knuckle on the page.
|
|
473
|
+
// Timings mirror CAM / frontPage / insidePage / npExplainer above (T0 5.85, T1 6.55, nseg windows).
|
|
474
|
+
// Voiceover mode: room bed drops; rustles, ticks, pen rules, labels and the linotype are minor.
|
|
475
|
+
explainerSound(k) {
|
|
476
|
+
const D = 15.5, T0 = 5.85, T1 = 6.55;
|
|
477
|
+
const pan = (px) => ncl((px / 1280) * 2 - 1, -1, 1) * 0.55;
|
|
478
|
+
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; };
|
|
479
|
+
// the reader shifts the broadsheet: a soft, darker rustle (the paper really is being handled)
|
|
480
|
+
const rustle = (t, d, g) => { k.play('paper', t, { dur: d, gain: 0.22 * g, minor: true }); k.noise(t, { minor: true, dur: d, color: 'pink', filter: 'lowpass', f: 900, gain: 0.08 * g, a: d * 0.4 }); };
|
|
481
|
+
// knuckle / finger on the page: a soft papery knock
|
|
482
|
+
const knock = (t, g, p, minor) => { k.tone(t, { minor: !!minor, f: [[0, 160], [0.06, 85]], dur: 0.1, gain: 0.32 * g, pan: p }); k.noise(t, { minor: !!minor, dur: 0.05, color: 'pink', filter: 'bandpass', f: 700, q: 0.9, gain: 0.3 * g, a: 0.002, pan: p }); };
|
|
483
|
+
const tap = (t, g, p) => k.noise(t, { minor: true, dur: 0.03, filter: 'bandpass', f: 1100, q: 1.2, gain: 0.22 * g, a: 0.001, pan: p });
|
|
484
|
+
const tick = (t, g, p) => k.noise(t, { minor: true, dur: 0.018, filter: 'bandpass', f: 2000, q: 1.6, gain: 0.22 * g, a: 0.001, pan: p });
|
|
485
|
+
const rulePen = (t, d, g, p) => k.play('stroke', t, { material: 'pen', dur: d, gain: g * 1.5, pan: p, minor: true });
|
|
486
|
+
const grease = (t, d, g, p) => k.play('stroke', t, { material: 'chalk', dur: d, gain: g, pan: p });
|
|
487
|
+
|
|
488
|
+
k.bed('room', 0, D, { gain: 0.24 });
|
|
489
|
+
|
|
490
|
+
// ---- front page: the reader opens up the paper on the banner headline
|
|
491
|
+
k.play('paper', 0.0, { dur: 0.5, gain: 0.55 });
|
|
492
|
+
knock(0.12, 0.5, 0);
|
|
493
|
+
rustle(2.2, 0.45, 1); // eye drops down to BY THE NUMBERS
|
|
494
|
+
for (let i = 0; i < 8; i++) tap(2.95 + i * 0.17, 0.8, pan(72 + (i % 4) * 104 + 46)); // training pictures appear
|
|
495
|
+
// counter n = 1e6 * nseg(3.0, 5.4)^2.3, ticking faster as it climbs (<= ~11/s)
|
|
496
|
+
for (let t = 3.05; t < 5.36; ) { const s = nseg(t, 3.0, 5.4); tick(t, 0.75 + 0.35 * s, pan(200)); t += 0.18 - 0.09 * s; }
|
|
497
|
+
rulePen(3.75, 0.3, 1.2, pan(546)); // FED IN arrow ruled
|
|
498
|
+
tap(3.9, 0.6, pan(546));
|
|
499
|
+
// 1,000,000 — and counting, ringed in red grease pencil
|
|
500
|
+
knock(5.4, 0.75, pan(230));
|
|
501
|
+
grease(5.42, 0.33, 0.9, pan(230));
|
|
502
|
+
rustle(5.5, 0.3, 0.8); // the reader lifts the paper to turn it
|
|
503
|
+
|
|
504
|
+
// ---- the page turn (5.85 -> 6.55): sheet lifts, sweeps over on its left edge and flops down
|
|
505
|
+
k.play('paper', T0, { dur: T1 - T0, gain: 0.9 });
|
|
506
|
+
k.noise(T0, { dur: T1 - T0 + 0.1, color: 'pink', filter: 'bandpass', f: [[0, 300], [(T1 - T0) * 0.5, 1000], [T1 - T0 + 0.1, 280]], q: 0.7, gain: 0.32, a: (T1 - T0) * 0.5 });
|
|
507
|
+
k.play('slap', T1 - 0.04, { gain: 0.35, pan: -0.4 });
|
|
508
|
+
knock(T1 - 0.03, 0.45, -0.4);
|
|
509
|
+
|
|
510
|
+
// ---- inside page: GUESS / CHECK / ADJUST
|
|
511
|
+
rulePen(7.0, 0.35, 1.2, pan(362));
|
|
512
|
+
tap(7.1, 0.7, pan(362)); // 1. GUESS
|
|
513
|
+
rulePen(7.4, 0.25, 1.1, pan(818));
|
|
514
|
+
knock(7.65, 0.5, pan(1023), true); // answer box "dog?"
|
|
515
|
+
// wrong: X struck, CHECK, ring in grease pencil, "Wrong — it's a cat."
|
|
516
|
+
k.play('stamp', 8.0, { gain: 0.85, pan: pan(1100) });
|
|
517
|
+
k.play('error', 8.14, { f: 'E3', gain: 0.65, pan: pan(1050) });
|
|
518
|
+
grease(8.12, 0.33, 0.9, pan(1023));
|
|
519
|
+
rulePen(8.55, 0.45, 1.2, pan(860)); // 3. ADJUST arrow back to the knobs
|
|
520
|
+
tap(8.75, 0.6, pan(860));
|
|
521
|
+
for (let i = 1; i <= 4; i++) k.play('tock', when(9.0, 9.5, NE.inOut, (i - 0.5) / 4), { f: 500 + i * 25, gain: 0.3, pan: pan(596), minor: true }); // knobs turned
|
|
522
|
+
knock(9.6, 0.8, pan(1023)); // "cat" — Right, this time.
|
|
523
|
+
k.play('success', when(9.6, 9.8, NE.back, 1), { f: 'C5', gain: 0.5, pan: pan(1100) });
|
|
524
|
+
|
|
525
|
+
// ---- the chart
|
|
526
|
+
rustle(10.45, 0.4, 0.8); // eye moves down to the chart
|
|
527
|
+
knock(10.6, 0.4, pan(1142), true); // ACCURACY figure appears
|
|
528
|
+
rulePen(10.95, 1.35, 0.8, pan(870)); // curve ruled along
|
|
529
|
+
for (let t = 10.9, last = 61, lastT = -1; t < 12.32; t += 1 / 120) { const v = Math.round(61 + 36 * NE.inOut(nseg(t, 10.9, 12.3))); if (v !== last && t - lastT >= 0.1) { tick(t, 0.9, pan(1142)); last = v; lastT = t; } }
|
|
530
|
+
// 97%: the biggest hit, then the reader rings it hard
|
|
531
|
+
k.play('impact', 12.3, { gain: 1.1, pan: pan(1142) });
|
|
532
|
+
knock(12.3, 1.1, pan(1142));
|
|
533
|
+
grease(12.37, 0.33, 1.1, pan(1142));
|
|
534
|
+
|
|
535
|
+
// ---- the pull quote: rule, linotype sets it left to right, rule, red underline
|
|
536
|
+
rustle(12.85, 0.4, 0.8);
|
|
537
|
+
rulePen(12.95, 0.25, 1.0, -0.3);
|
|
538
|
+
k.play('typing', 13.0, { n: 7, rate: 0.07, gain: 0.55, minor: true });
|
|
539
|
+
rulePen(13.3, 0.25, 1.0, -0.2);
|
|
540
|
+
grease(13.55, 0.3, 0.9, -0.2);
|
|
541
|
+
['C4', 'G4', 'C5', 'E5'].forEach((f, i) => k.play('ding', 13.6 + i * 0.06, { f, gain: 0.34 - i * 0.05, pan: (i - 1.5) * 0.2 }));
|
|
542
|
+
// the reader lowers the paper: the whole page in view
|
|
543
|
+
rustle(14.5, 0.6, 1.1);
|
|
544
|
+
},
|
|
545
|
+
draw(ctx, W, H_, t) {
|
|
546
|
+
// ---- front ink layer ----
|
|
547
|
+
const ink = H.layer(W, H_), k = ink.getContext('2d');
|
|
548
|
+
const geo = layout(k, H.rng(19), t, false);
|
|
549
|
+
// halftone the photo into the frame (45°, coarse screen, with dot gain)
|
|
550
|
+
const pw = Math.floor(geo.PW_) - 4, ph = geo.PH_ - 4, ph0 = photo(pw, ph, t);
|
|
551
|
+
k.save(); k.beginPath(); k.rect(geo.PX + 2, geo.PY + 2, pw, ph); k.clip();
|
|
552
|
+
k.translate(geo.PX + 2, geo.PY + 2);
|
|
553
|
+
const cell = 5.2, ca = Math.cos(Math.PI / 4), sa = Math.sin(Math.PI / 4), ext = Math.hypot(pw, ph);
|
|
554
|
+
k.beginPath();
|
|
555
|
+
for (let v = -ext; v < ext; v += cell) for (let u = -ext; u < ext; u += cell) {
|
|
556
|
+
const px = u * ca - v * sa, py = u * sa + v * ca;
|
|
557
|
+
if (px < -cell || py < -cell || px > pw + cell || py > ph + cell) continue;
|
|
558
|
+
const sx = Math.min(pw - 1, Math.max(0, px | 0)), sy = Math.min(ph - 1, Math.max(0, py | 0));
|
|
559
|
+
const dark = 1 - H.clamp((ph0[(sy * pw + sx) * 4] / 255 - 0.08) / 0.84);
|
|
560
|
+
const rr = cell * 0.62 * Math.sqrt(Math.max(0, dark * 1.08 - 0.02));
|
|
561
|
+
if (rr < 0.35) continue;
|
|
562
|
+
k.moveTo(px + rr, py); k.arc(px, py, rr, 0, Math.PI * 2);
|
|
563
|
+
}
|
|
564
|
+
k.fill(); k.restore();
|
|
565
|
+
|
|
566
|
+
// ---- back page (mirrored, for show-through) ----
|
|
567
|
+
const back = H.layer(W, H_), bk = back.getContext('2d');
|
|
568
|
+
bk.translate(W, 0); bk.scale(-1, 1); layout(bk, H.rng(77), t, true);
|
|
569
|
+
const bkb = H.layer(W, H_), bbx = bkb.getContext('2d'); bbx.filter = 'blur(1.2px)'; bbx.drawImage(back, 0, 0);
|
|
570
|
+
const bd = bbx.getImageData(0, 0, W, H_).data;
|
|
571
|
+
// ink smear: a dragged ghost of the front ink near the headline
|
|
572
|
+
const sm = H.layer(W, H_), smx = sm.getContext('2d'); smx.filter = 'blur(2px)';
|
|
573
|
+
for (let i = 1; i <= 6; i++) smx.drawImage(ink, i * 2.2, 0.4 * i);
|
|
574
|
+
const sd = smx.getImageData(0, 0, W, H_).data;
|
|
575
|
+
const fd = k.getImageData(0, 0, W, H_).data;
|
|
576
|
+
|
|
577
|
+
// ---- paper + composite ----
|
|
578
|
+
const nz = H.makeNoise(9);
|
|
579
|
+
const GW = 160, GH = 90, low = new Float32Array(GW * GH * 2);
|
|
580
|
+
for (let y = 0; y < GH; y++) for (let x = 0; x < GW; x++) { low[(y * GW + x) * 2] = nz.fbm2(x * 0.06, y * 0.06, 4); low[(y * GW + x) * 2 + 1] = nz.fbm2(x * 0.25 + 50, y * 0.25, 3); }
|
|
581
|
+
const lowAt = (x, y, c) => { const fx = x * (GW - 1) / W, fy = y * (GH - 1) / H_, x0 = fx | 0, y0 = fy | 0, tx = fx - x0, ty = fy - y0, x1 = Math.min(GW - 1, x0 + 1), y1 = Math.min(GH - 1, y0 + 1);
|
|
582
|
+
const a = low[(y0 * GW + x0) * 2 + c], b = low[(y0 * GW + x1) * 2 + c], cc = low[(y1 * GW + x0) * 2 + c], d = low[(y1 * GW + x1) * 2 + c];
|
|
583
|
+
return (a * (1 - tx) + b * tx) * (1 - ty) + (cc * (1 - tx) + d * tx) * ty; };
|
|
584
|
+
const hash = (x, y, s) => { let h = (Math.imul(x, 374761393) + Math.imul(y, 668265263) + Math.imul(s, 1442695041)) | 0; h = Math.imul(h ^ (h >>> 13), 1274126177); h ^= h >>> 16; return (h >>> 0) / 4294967296; };
|
|
585
|
+
const img = ctx.createImageData(W, H_), o = img.data;
|
|
586
|
+
const smearY0 = 200, smearY1 = 262, smearX0 = 760, smearX1 = 1010;
|
|
587
|
+
const creaseY = 470;
|
|
588
|
+
for (let y = 0; y < H_; y++) {
|
|
589
|
+
for (let x = 0; x < W; x++) {
|
|
590
|
+
const i = (y * W + x) * 4;
|
|
591
|
+
const m = lowAt(x, y, 0), f = lowAt(x, y, 1), hh = hash(x, y, 1);
|
|
592
|
+
// yellowed pulp: browner at edges + blotchy, with fibre grain
|
|
593
|
+
const edge = Math.min(x, W - x, y * 1.3, H_ - y) / 120;
|
|
594
|
+
const age = 0.07 * (1 - H.clamp(edge)) + m * 0.035 + f * 0.015;
|
|
595
|
+
let r = PAPER[0] * (1 - age * 0.7) + (hh - 0.5) * 12;
|
|
596
|
+
let g = PAPER[1] * (1 - age * 1.0) + (hh - 0.5) * 12;
|
|
597
|
+
let b = PAPER[2] * (1 - age * 1.6) + (hh - 0.5) * 12;
|
|
598
|
+
if (hash(x >> 1, y >> 1, 4) > 0.9975) { r -= 40; g -= 40; b -= 38; } // dark pulp flecks
|
|
599
|
+
// fold crease
|
|
600
|
+
const cd = y - creaseY - Math.sin(x * 0.004) * 2;
|
|
601
|
+
const crease = Math.exp(-cd * cd / 6) * 0.07 - Math.exp(-(cd - 3) * (cd - 3) / 8) * 0.05;
|
|
602
|
+
r *= 1 - crease; g *= 1 - crease; b *= 1 - crease;
|
|
603
|
+
// show-through from the reverse
|
|
604
|
+
const bt = (bd[i + 3] / 255) * 0.075;
|
|
605
|
+
r *= 1 - bt; g *= 1 - bt * 1.05; b *= 1 - bt * 1.1;
|
|
606
|
+
// front ink: thin, uneven, absorbed into pulp
|
|
607
|
+
let a = fd[i + 3] / 255;
|
|
608
|
+
if (a > 0) {
|
|
609
|
+
const thr = 0.5 + (hash(x, y, 2) - 0.5) * 0.35 + f * 0.15;
|
|
610
|
+
let cov = H.clamp((a - thr) * 3 + 0.5) * 0.65 + a * 0.35;
|
|
611
|
+
let dens = 0.86 + m * 0.12 + (hash(x, y, 3) - 0.5) * 0.12;
|
|
612
|
+
if (hash(x, y, 5) < 0.025) dens *= 0.45;
|
|
613
|
+
cov *= H.clamp(dens);
|
|
614
|
+
r = r * (1 - cov) + INK[0] * cov; g = g * (1 - cov) + INK[1] * cov; b = b * (1 - cov) + INK[2] * cov;
|
|
615
|
+
}
|
|
616
|
+
// smear
|
|
617
|
+
if (y > smearY0 && y < smearY1 && x > smearX0 && x < smearX1) {
|
|
618
|
+
const sx = Math.sin(Math.PI * (x - smearX0) / (smearX1 - smearX0)) * Math.sin(Math.PI * (y - smearY0) / (smearY1 - smearY0));
|
|
619
|
+
const s = (sd[i + 3] / 255) * 0.35 * sx;
|
|
620
|
+
r *= 1 - s; g *= 1 - s; b *= 1 - s;
|
|
621
|
+
}
|
|
622
|
+
o[i] = r; o[i + 1] = g; o[i + 2] = b; o[i + 3] = 255;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
const page = H.layer(W, H_), pc = page.getContext('2d');
|
|
626
|
+
pc.putImageData(img, 0, 0);
|
|
627
|
+
// thumb smudge
|
|
628
|
+
const g = ctx.createRadialGradient(1150, 600, 4, 1150, 600, 46); g.addColorStop(0, 'rgba(40,36,30,0.10)'); g.addColorStop(1, 'rgba(40,36,30,0)');
|
|
629
|
+
pc.fillStyle = g; pc.beginPath(); pc.ellipse(1150, 600, 46, 34, 0.5, 0, 7); pc.fill();
|
|
630
|
+
|
|
631
|
+
// ---- camera: spinning-newspaper reveal, then a slow push toward the headline ----
|
|
632
|
+
const SPIN = 1.45;
|
|
633
|
+
const pose = (tt) => { const e = H.ease.out(H.clamp(tt / SPIN)); return { rot: (1 - e) * -Math.PI * 4.2, sc: H.lerp(0.04, 1, e) }; };
|
|
634
|
+
const push = 1 + 0.04 * H.ease.inOut(H.clamp((t - 1.5) / 3.0));
|
|
635
|
+
if (t < SPIN + 0.05) {
|
|
636
|
+
const bg = ctx.createRadialGradient(W / 2, H_ / 2, 50, W / 2, H_ / 2, 760);
|
|
637
|
+
bg.addColorStop(0, '#2b2622'); bg.addColorStop(1, '#0b0a09');
|
|
638
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
639
|
+
const drawAt = (tt, a) => {
|
|
640
|
+
const p = pose(tt);
|
|
641
|
+
ctx.save(); ctx.globalAlpha = a; ctx.translate(W / 2, H_ / 2); ctx.rotate(p.rot); ctx.scale(p.sc, p.sc);
|
|
642
|
+
ctx.drawImage(page, -W / 2, -H_ / 2); ctx.restore();
|
|
643
|
+
};
|
|
644
|
+
// smeared trail of the spin (motion blur)
|
|
645
|
+
for (let k = 4; k >= 1; k--) drawAt(t - k * 0.018, 0.14);
|
|
646
|
+
const p = pose(t);
|
|
647
|
+
ctx.save(); ctx.translate(W / 2, H_ / 2); ctx.rotate(p.rot); ctx.scale(p.sc, p.sc);
|
|
648
|
+
ctx.shadowColor = 'rgba(0,0,0,0.6)'; ctx.shadowBlur = 40 * p.sc; ctx.shadowOffsetY = 12 * p.sc;
|
|
649
|
+
ctx.drawImage(page, -W / 2, -H_ / 2); ctx.restore();
|
|
650
|
+
} else {
|
|
651
|
+
const fx = 640, fy = 230;
|
|
652
|
+
ctx.drawImage(page, fx - fx / push, fy - fy / push, W / push, H_ / push, 0, 0, W, H_);
|
|
653
|
+
}
|
|
654
|
+
},
|
|
655
|
+
});
|
|
656
|
+
}
|