@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,801 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ================= Oscilloscope Vector Art: XY-mode beam on an analogue lab scope =================
|
|
3
|
+
// One P31 beam draws everything: brightness ~ 1/beam speed (bright dwell dots at corners and turns, dim fast
|
|
4
|
+
// retrace lines between figures), bloom + halation, internal 8x10 graticule, a lab instrument front panel.
|
|
5
|
+
|
|
6
|
+
// ---------- geometry ----------
|
|
7
|
+
const GX = 54, GY = 40, DIV = 80, GW = 800, GH = 640, CX = GX + GW / 2, CY = GY + GH / 2;
|
|
8
|
+
const GL = { x: 28, y: 22, w: 852, h: 676, r: 58 };
|
|
9
|
+
const P31 = [125, 255, 74], AMBER = [255, 170, 46], RED = [255, 84, 46];
|
|
10
|
+
|
|
11
|
+
// ---------- Hershey-style stroke font (cap height 10 units, y down) ----------
|
|
12
|
+
const arc = (cx, cy, rx, ry, a0, a1, n) => {
|
|
13
|
+
n = n || Math.max(6, Math.ceil(Math.abs(a1 - a0) / 9)); const o = [];
|
|
14
|
+
for (let i = 0; i <= n; i++) { const a = (a0 + (a1 - a0) * i / n) * Math.PI / 180; o.push([cx + rx * Math.cos(a), cy + ry * Math.sin(a)]); }
|
|
15
|
+
return o;
|
|
16
|
+
};
|
|
17
|
+
const G = {
|
|
18
|
+
A: [[[0, 10], [3.6, 0], [7.2, 10]], [[1.35, 6.4], [5.85, 6.4]]],
|
|
19
|
+
B: [[[0, 10], [0, 0], [4, 0], ...arc(4, 2.5, 2.6, 2.5, -90, 90), [0, 5]], [[0, 5], [4.3, 5], ...arc(4.3, 7.5, 2.8, 2.5, -90, 90), [0, 10]]],
|
|
20
|
+
C: [arc(4, 5, 4, 5, -35, -325)],
|
|
21
|
+
D: [[[0, 0], [0, 10], [2.8, 10], ...arc(2.8, 5, 4.2, 5, 90, -90), [0, 0]]],
|
|
22
|
+
E: [[[6.4, 0], [0, 0], [0, 10], [6.4, 10]], [[0, 5], [4.4, 5]]],
|
|
23
|
+
F: [[[6.4, 0], [0, 0], [0, 10]], [[0, 5], [4.4, 5]]],
|
|
24
|
+
G: [[...arc(4, 5, 4, 5, -35, -360), [4.4, 5]]],
|
|
25
|
+
H: [[[0, 0], [0, 10]], [[7, 0], [7, 10]], [[0, 5], [7, 5]]],
|
|
26
|
+
I: [[[0, 0], [0, 10]]],
|
|
27
|
+
J: [[[5, 0], [5, 7.2], ...arc(2.5, 7.2, 2.5, 2.8, 0, 180)]],
|
|
28
|
+
K: [[[0, 0], [0, 10]], [[6.8, 0], [0, 6.6]], [[2.4, 4.2], [7, 10]]],
|
|
29
|
+
L: [[[0, 0], [0, 10], [6, 10]]],
|
|
30
|
+
M: [[[0, 10], [0, 0], [4.2, 10], [8.4, 0], [8.4, 10]]],
|
|
31
|
+
N: [[[0, 10], [0, 0], [7, 10], [7, 0]]],
|
|
32
|
+
O: [arc(4, 5, 4, 5, -90, 270)],
|
|
33
|
+
P: [[[0, 10], [0, 0], [4, 0], ...arc(4, 2.75, 2.8, 2.75, -90, 90), [0, 5.5]]],
|
|
34
|
+
Q: [arc(4, 5, 4, 5, -90, 270), [[4.6, 7], [8.2, 10.6]]],
|
|
35
|
+
R: [[[0, 10], [0, 0], [4, 0], ...arc(4, 2.75, 2.8, 2.75, -90, 90), [0, 5.5]], [[3.6, 5.5], [7, 10]]],
|
|
36
|
+
S: [[...arc(3.5, 2.55, 3.4, 2.55, -25, -270), ...arc(3.5, 7.5, 3.6, 2.5, -90, 155)]],
|
|
37
|
+
T: [[[0, 0], [7.2, 0]], [[3.6, 0], [3.6, 10]]],
|
|
38
|
+
U: [[[0, 0], [0, 6.4], ...arc(3.5, 6.4, 3.5, 3.6, 180, 0), [7, 0]]],
|
|
39
|
+
V: [[[0, 0], [3.6, 10], [7.2, 0]]],
|
|
40
|
+
W: [[[0, 0], [2.4, 10], [4.8, 0], [7.2, 10], [9.6, 0]]],
|
|
41
|
+
X: [[[0, 0], [7, 10]], [[7, 0], [0, 10]]],
|
|
42
|
+
Y: [[[0, 0], [3.6, 5], [7.2, 0]], [[3.6, 5], [3.6, 10]]],
|
|
43
|
+
Z: [[[0, 0], [7, 0], [0, 10], [7, 10]]],
|
|
44
|
+
0: [arc(3.5, 5, 3.5, 5, -90, 270)],
|
|
45
|
+
1: [[[1, 2], [3.4, 0], [3.4, 10]]],
|
|
46
|
+
2: [[...arc(3.5, 3, 3.5, 3, -165, 15), [0, 10], [7, 10]]],
|
|
47
|
+
3: [[...arc(3.4, 2.6, 3.2, 2.6, -160, 90), ...arc(3.4, 7.5, 3.6, 2.5, -90, 160)]],
|
|
48
|
+
4: [[[5.4, 10], [5.4, 0], [0, 7], [7.4, 7]]],
|
|
49
|
+
5: [[[6.4, 0], [1, 0], [0.5, 4.6], ...arc(3.5, 6.9, 3.5, 3.1, -140, 155)]],
|
|
50
|
+
6: [[...arc(3.8, 5, 3.8, 5, -55, -270), ...arc(3.6, 7.1, 3.5, 2.9, 90, -270)]],
|
|
51
|
+
7: [[[0, 0], [7, 0], [2.4, 10]]],
|
|
52
|
+
8: [arc(3.5, 2.5, 3, 2.5, 90, 450), arc(3.5, 7.4, 3.5, 2.6, -90, 270)],
|
|
53
|
+
9: [[...arc(3.4, 3, 3.4, 3, 0, 360), ...arc(3.2, 5, 3.6, 5, 0, 125)]],
|
|
54
|
+
'?': [[...arc(3, 2.6, 3, 2.6, -165, 60), [3, 6.6], [3, 7.4]], [[3, 9.5], [3, 10]]],
|
|
55
|
+
'%': [[[0, 10], [8, 0]], arc(1.6, 1.8, 1.6, 1.8, 0, 360), arc(6.4, 8.2, 1.6, 1.8, 0, 360)],
|
|
56
|
+
'.': [[[0.4, 9.4], [0.4, 10]]],
|
|
57
|
+
',': [[[0.8, 9.4], [0.8, 10.2], [0, 11.8]]],
|
|
58
|
+
':': [[[0.4, 3.4], [0.4, 4]], [[0.4, 9.4], [0.4, 10]]],
|
|
59
|
+
'-': [[[0, 5.5], [5, 5.5]]],
|
|
60
|
+
"'": [[[0.5, 0], [0.5, 3]]],
|
|
61
|
+
'/': [[[0, 10], [6, 0]]],
|
|
62
|
+
'>': [[[0, 5], [9, 5]], [[5.6, 1.8], [9, 5], [5.6, 8.2]]],
|
|
63
|
+
'#': [[[0, 5.8], [2.8, 9.6], [8.4, 0]]],
|
|
64
|
+
'+': [[[3.5, 2], [3.5, 8]], [[0.5, 5], [6.5, 5]]],
|
|
65
|
+
};
|
|
66
|
+
const GWID = {}; for (const k in G) GWID[k] = Math.max(...G[k].flat().map((p) => p[0]));
|
|
67
|
+
const ADV = 3.4, SPACE = 5.5;
|
|
68
|
+
const textW = (str, cap) => { const u = cap / 10; let w = 0; for (const ch of str) w += ch === ' ' ? SPACE : (GWID[ch] == null ? 5 : GWID[ch] + ADV); return (w - ADV) * u; };
|
|
69
|
+
const textStrokes = (str, x, y, cap) => {
|
|
70
|
+
const u = cap / 10, out = []; let px = x;
|
|
71
|
+
for (const ch of str) {
|
|
72
|
+
if (ch === ' ') { px += SPACE * u; continue; }
|
|
73
|
+
const g = G[ch]; if (!g) { px += 5 * u; continue; }
|
|
74
|
+
for (const s of g) out.push(s.map(([gx, gy]) => [px + gx * u, y + gy * u]));
|
|
75
|
+
px += (GWID[ch] + ADV) * u;
|
|
76
|
+
}
|
|
77
|
+
return out;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// ---------- beam buffer ----------
|
|
81
|
+
let B = null, XF = null, GA = 1;
|
|
82
|
+
const resetBeam = () => { B = { lines: [], segs: [], dots: [] }; XF = null; GA = 1; };
|
|
83
|
+
const plen = (p) => { let L = 0; for (let i = 1; i < p.length; i++) L += Math.hypot(p[i][0] - p[i - 1][0], p[i][1] - p[i - 1][1]); return L; };
|
|
84
|
+
const cutLen = (p, len) => {
|
|
85
|
+
const o = [p[0]];
|
|
86
|
+
for (let i = 1; i < p.length; i++) {
|
|
87
|
+
const d = Math.hypot(p[i][0] - p[i - 1][0], p[i][1] - p[i - 1][1]);
|
|
88
|
+
if (d >= len) { const k = d ? len / d : 0; o.push([p[i - 1][0] + (p[i][0] - p[i - 1][0]) * k, p[i - 1][1] + (p[i][1] - p[i - 1][1]) * k, p[i][2]]); return o; }
|
|
89
|
+
o.push(p[i]); len -= d;
|
|
90
|
+
}
|
|
91
|
+
return o;
|
|
92
|
+
};
|
|
93
|
+
const tailOf = (p, len) => { const L = plen(p); if (L <= len) return p; const r = p.slice().reverse(); return cutLen(r, len).reverse(); };
|
|
94
|
+
// emit strokes: o = { f (0..1 traced), a, col, w, timed (brightness ~ 1/speed), xf, retrace, head, dots }
|
|
95
|
+
function beam(strokes, o = {}) {
|
|
96
|
+
const a = (o.a == null ? 1 : o.a) * GA; if (a < 0.004) return;
|
|
97
|
+
const f = o.f == null ? 1 : o.f; if (f <= 0 || !strokes.length) return;
|
|
98
|
+
const col = o.col || P31, w = o.w || 2.2;
|
|
99
|
+
const tf = (p) => { let q = o.xf ? o.xf(p) : p; if (XF) q = XF(q); return q.length > 2 || p[2] == null ? q : [q[0], q[1], p[2]]; };
|
|
100
|
+
const lens = strokes.map(plen), total = lens.reduce((s, v) => s + v, 0);
|
|
101
|
+
let left = total * f, prev = null, last = null;
|
|
102
|
+
for (let k = 0; k < strokes.length && left > 0; k++) {
|
|
103
|
+
let s = strokes[k]; if (s.length < 2) continue;
|
|
104
|
+
if (lens[k] > left) s = cutLen(s, left);
|
|
105
|
+
left -= lens[k];
|
|
106
|
+
const pts = s.map(tf);
|
|
107
|
+
if (o.retrace !== false && prev) B.lines.push({ pts: [prev, pts[0]], a: a * 0.09, col, w: Math.max(1, w * 0.45) });
|
|
108
|
+
if (o.timed) {
|
|
109
|
+
let m = 0; const n = pts.length - 1; for (let i = 1; i <= n; i++) m += Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]); m /= Math.max(1, n);
|
|
110
|
+
const buckets = [];
|
|
111
|
+
for (let i = 1; i <= n; i++) {
|
|
112
|
+
const L = Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1]) + 0.01;
|
|
113
|
+
let fac = H.clamp(Math.pow(m / L, 0.65), 0.3, 2.2) * (pts[i][2] == null ? 1 : pts[i][2]);
|
|
114
|
+
const lv = Math.max(0, Math.min(9, Math.round((fac - 0.3) / 0.21)));
|
|
115
|
+
(buckets[lv] = buckets[lv] || []).push(pts[i - 1][0], pts[i - 1][1], pts[i][0], pts[i][1]);
|
|
116
|
+
}
|
|
117
|
+
buckets.forEach((arr, lv) => { if (arr) { const fac = 0.3 + lv * 0.21; B.segs.push({ arr, a: a * Math.min(1, fac), col, w: w * (0.75 + 0.22 * fac), hot: fac > 1.2 ? (fac - 1.2) * 0.5 * a : 0 }); } });
|
|
118
|
+
} else {
|
|
119
|
+
const L = lens[k] / Math.max(1, s.length - 1);
|
|
120
|
+
const sp = H.clamp(1.18 - L / 520, 0.62, 1.08);
|
|
121
|
+
B.lines.push({ pts, a: a * sp, col, w });
|
|
122
|
+
if (o.dots !== false && pts.length < 40) {
|
|
123
|
+
const dA = a * 0.55;
|
|
124
|
+
B.dots.push({ x: pts[0][0], y: pts[0][1], a: dA, r: w * 0.95, col });
|
|
125
|
+
B.dots.push({ x: pts[pts.length - 1][0], y: pts[pts.length - 1][1], a: dA, r: w * 0.95, col });
|
|
126
|
+
for (let i = 1; i < pts.length - 1; i++) {
|
|
127
|
+
const a1 = Math.atan2(pts[i][1] - pts[i - 1][1], pts[i][0] - pts[i - 1][0]), a2 = Math.atan2(pts[i + 1][1] - pts[i][1], pts[i + 1][0] - pts[i][0]);
|
|
128
|
+
let d = Math.abs(a2 - a1); if (d > Math.PI) d = 2 * Math.PI - d;
|
|
129
|
+
if (d > 0.85) B.dots.push({ x: pts[i][0], y: pts[i][1], a: dA * 1.1, r: w * 1.05, col });
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
prev = pts[pts.length - 1]; last = pts;
|
|
134
|
+
}
|
|
135
|
+
if (f < 1 && last && o.head !== false) {
|
|
136
|
+
const tl = tailOf(last, 60);
|
|
137
|
+
if (tl.length > 1) B.lines.push({ pts: tl, a: Math.min(1, a * 0.8), col, w: w * 1.5 });
|
|
138
|
+
const h = last[last.length - 1];
|
|
139
|
+
B.dots.push({ x: h[0], y: h[1], a: Math.min(1, a * 1.3), r: w * 2.3, col, hot: 1 });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const dot = (x, y, r, a, col = P31) => { let p = [x, y]; if (XF) p = XF(p); B.dots.push({ x: p[0], y: p[1], r, a: a * GA, col, hot: 1 }); };
|
|
143
|
+
const text = (str, x, y, cap, o = {}) => {
|
|
144
|
+
const w = textW(str, cap), x0 = o.align === 'c' ? x - w / 2 : o.align === 'r' ? x - w : x;
|
|
145
|
+
beam(textStrokes(str, x0, y, cap), Object.assign({ w: H.clamp(cap * 0.068, 2.1, 5.2) }, o));
|
|
146
|
+
return w;
|
|
147
|
+
};
|
|
148
|
+
const curve = (fn, n) => { const o = []; for (let i = 0; i <= n; i++) o.push(fn(i / n)); return o; };
|
|
149
|
+
const ring = (cx, cy, rx, ry, n = 28) => curve((u) => [cx + rx * Math.cos(u * 6.2832), cy + ry * Math.sin(u * 6.2832)], n);
|
|
150
|
+
const arrowS = (x1, y1, x2, y2, hs = 15) => { const an = Math.atan2(y2 - y1, x2 - x1); return [[[x1, y1], [x2, y2]], [[x2 + Math.cos(an + 2.55) * hs, y2 + Math.sin(an + 2.55) * hs], [x2, y2], [x2 + Math.cos(an - 2.55) * hs, y2 + Math.sin(an - 2.55) * hs]]]; };
|
|
151
|
+
const catmull = (P, seg = 6) => {
|
|
152
|
+
const o = [];
|
|
153
|
+
for (let i = 0; i < P.length - 1; i++) {
|
|
154
|
+
const p0 = P[Math.max(0, i - 1)], p1 = P[i], p2 = P[i + 1], p3 = P[Math.min(P.length - 1, i + 2)];
|
|
155
|
+
for (let k = 0; k < seg; k++) {
|
|
156
|
+
const t = k / seg, t2 = t * t, t3 = t2 * t;
|
|
157
|
+
o.push([0, 1].map((d) => 0.5 * (2 * p1[d] + (-p0[d] + p2[d]) * t + (2 * p0[d] - 5 * p1[d] + 4 * p2[d] - p3[d]) * t2 + (-p0[d] + 3 * p1[d] - 3 * p2[d] + p3[d]) * t3)));
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
o.push(P[P.length - 1]); return o;
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ---------- beam figures (unit-size, y down) ----------
|
|
164
|
+
const tx = (S, x, y, s) => S.map((st) => st.map(([px, py]) => [x + px * s, y + py * s]));
|
|
165
|
+
const CAT = (() => {
|
|
166
|
+
const head = [[0.82, -0.39], ...arc(0, 0.1, 1, 0.85, -35, 215, 34), [-0.86, -1.18], [-0.34, -0.69], ...arc(0, 0.1, 1, 0.85, -110, -70, 4), [0.86, -1.18], [0.82, -0.39]];
|
|
167
|
+
const eyeL = arc(-0.38, -0.02, 0.17, 0.11, 0, 360, 14), eyeR = arc(0.38, -0.02, 0.17, 0.11, 0, 360, 14);
|
|
168
|
+
return [head, eyeL, [[-0.38, -0.12], [-0.38, 0.08]], eyeR, [[0.38, -0.12], [0.38, 0.08]],
|
|
169
|
+
[[-0.1, 0.25], [0.1, 0.25], [0, 0.37], [-0.1, 0.25]], [[0, 0.37], [0, 0.44], ...arc(-0.12, 0.44, 0.12, 0.11, 0, 165, 8)], arc(0.12, 0.44, 0.12, 0.11, 180, 15, 8),
|
|
170
|
+
[[-0.36, 0.3], [-1.28, 0.14]], [[-0.36, 0.36], [-1.3, 0.38]], [[0.36, 0.3], [1.28, 0.14]], [[0.36, 0.36], [1.3, 0.38]]];
|
|
171
|
+
})();
|
|
172
|
+
const DOG = (() => {
|
|
173
|
+
const head = arc(0, 0.05, 0.74, 0.86, -90, 270, 34);
|
|
174
|
+
const earL = catmull([[-0.5, -0.66], [-0.92, -0.74], [-1.22, -0.42], [-1.2, 0.18], [-0.98, 0.5], [-0.78, 0.3], [-0.72, -0.15]]);
|
|
175
|
+
const earR = earL.map(([x, y]) => [-x, y]);
|
|
176
|
+
const nose = []; for (let i = 0; i <= 40; i++) { const u = i / 40, a = u * 6.2832 * 3.2, r = 1 - u * 0.75; nose.push([Math.cos(a) * 0.15 * r, 0.3 + Math.sin(a) * 0.09 * r]); }
|
|
177
|
+
return [head, earL, earR, arc(-0.3, -0.14, 0.09, 0.09, 0, 360, 10), arc(0.3, -0.14, 0.09, 0.09, 0, 360, 10), arc(0, 0.42, 0.36, 0.26, 200, 340, 12), nose,
|
|
178
|
+
[[0, 0.39], [0, 0.52], ...arc(-0.13, 0.52, 0.13, 0.1, 0, 165, 8)], arc(0.13, 0.52, 0.13, 0.1, 180, 15, 8), [[-0.09, 0.6], ...arc(0, 0.62, 0.09, 0.12, 180, 0, 8)]];
|
|
179
|
+
})();
|
|
180
|
+
|
|
181
|
+
// neural net wireframe: 3-4-2 nodes in 3D, rotates about Y
|
|
182
|
+
const LAY = [3, 4, 2];
|
|
183
|
+
function netGeo(cx, cy, w, h, rot) {
|
|
184
|
+
const nodes = [];
|
|
185
|
+
LAY.forEach((n, l) => { for (let k = 0; k < n; k++) {
|
|
186
|
+
const x = (l - 1) * w / 2, y = (k - (n - 1) / 2) * h / 3.6, z = (k % 2 ? 1 : -1) * w * 0.12 + (l - 1) * w * 0.05;
|
|
187
|
+
const xr = x * Math.cos(rot) - z * Math.sin(rot), zr = x * Math.sin(rot) + z * Math.cos(rot), p = 1 / (1 + zr / (w * 3.2));
|
|
188
|
+
nodes.push({ x: cx + xr * p, y: cy + y * p, s: p, l });
|
|
189
|
+
} });
|
|
190
|
+
const edges = []; let base = 0;
|
|
191
|
+
for (let l = 0; l < LAY.length - 1; l++) { for (let i = 0; i < LAY[l]; i++) for (let j = 0; j < LAY[l + 1]; j++) edges.push([base + i, base + LAY[l] + j, l]); base += LAY[l]; }
|
|
192
|
+
return { nodes, edges };
|
|
193
|
+
}
|
|
194
|
+
const W0 = (() => { const r = H.rng(5); return Array.from({ length: 20 }, () => r.range(0.25, 1)); })();
|
|
195
|
+
const W1 = (() => { const r = H.rng(9); return Array.from({ length: 20 }, () => r.range(0.25, 1)); })();
|
|
196
|
+
// o: { f, a, wk (0..1 blend W0->W1), act[] node flash, bp(l) backprop amber per layer, nodeR }
|
|
197
|
+
function net(cx, cy, w, h, rot, o = {}) {
|
|
198
|
+
const g = netGeo(cx, cy, w, h, rot), f = o.f == null ? 1 : o.f, a = o.a == null ? 1 : o.a, R = o.nodeR || 12;
|
|
199
|
+
g.edges.forEach(([i, j, l], e) => {
|
|
200
|
+
const A = g.nodes[i], Bn = g.nodes[j], wt = H.lerp(W0[e], W1[e], o.wk || 0);
|
|
201
|
+
const amb = o.bp ? o.bp(l) : 0, col = amb > 0 ? H.mix(P31, AMBER, amb) : P31;
|
|
202
|
+
const dx = Bn.x - A.x, dy = Bn.y - A.y, L = Math.hypot(dx, dy), ux = dx / L, uy = dy / L;
|
|
203
|
+
beam([[[A.x + ux * R * A.s, A.y + uy * R * A.s], [Bn.x - ux * R * Bn.s, Bn.y - uy * R * Bn.s]]], { f: H.clamp(f * 1.6 - 0.25 - e * 0.012), a: a * (0.3 + 0.7 * wt) * (1 + amb * 0.5), col, w: 1.2 + wt * 1.5, dots: false, retrace: false });
|
|
204
|
+
});
|
|
205
|
+
g.nodes.forEach((n, i) => {
|
|
206
|
+
const fl = o.act ? o.act(n.l) : 0;
|
|
207
|
+
beam([ring(n.x, n.y, R * n.s, R * n.s, 18)], { f: H.clamp(f * 1.3 - i * 0.03), a: a * (0.9 + fl), w: 2.4, col: o.nodeCol ? o.nodeCol(n.l) : P31, dots: false });
|
|
208
|
+
if (fl > 0.05) dot(n.x, n.y, R * 0.7 * n.s, Math.min(1, fl) * a);
|
|
209
|
+
});
|
|
210
|
+
return g;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// spiral-wound sphere (one continuous beam), e = latitude extent 0..1, returns timed points
|
|
214
|
+
function sphere(cx, cy, R, spin, tilt, e, wob, n = 1500, turns = 22) {
|
|
215
|
+
const ct = Math.cos(tilt), st = Math.sin(tilt);
|
|
216
|
+
return curve((s) => {
|
|
217
|
+
const phi = Math.PI / 2 + (s - 0.5) * Math.PI * e, th = 6.2832 * turns * s + spin;
|
|
218
|
+
const rr = 1 + wob * Math.sin(5 * th - spin * 2 + 3 * phi);
|
|
219
|
+
let x = Math.sin(phi) * Math.cos(th) * rr, y = -Math.cos(phi) * rr, z = Math.sin(phi) * Math.sin(th) * rr;
|
|
220
|
+
const y2 = y * ct - z * st, z2 = y * st + z * ct;
|
|
221
|
+
return [cx + x * R, cy + y2 * R, 0.62 + 0.38 * (z2 + 1) / 2];
|
|
222
|
+
}, n);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// ---------- cached layers ----------
|
|
226
|
+
const C = {};
|
|
227
|
+
const rrect = (g, x, y, w, h, r) => { g.beginPath(); g.moveTo(x + r, y); g.arcTo(x + w, y, x + w, y + h, r); g.arcTo(x + w, y + h, x, y + h, r); g.arcTo(x, y + h, x, y, r); g.arcTo(x, y, x + w, y, r); g.closePath(); };
|
|
228
|
+
const INK = '#22313f', BLUE = '#3d6688';
|
|
229
|
+
const VD = ['5', '2', '1', '.5', '.2', '.1', '50', '20', '10', '5', '2'];
|
|
230
|
+
const TD = ['X-Y', '.5', '.2', '.1', '50', '20', '10', '5', '2', '1', '.5'];
|
|
231
|
+
const KA = (idx) => (-135 + idx * 27) * Math.PI / 180;
|
|
232
|
+
function lbl(g, s, x, y, size = 11, align = 'center', col = INK, wt = 600) { g.font = `${wt} ${size}px "Inter"`; g.fillStyle = col; g.textAlign = align; g.textBaseline = 'middle'; if (g.letterSpacing !== undefined) g.letterSpacing = '1px'; g.fillText(s, x, y); if (g.letterSpacing !== undefined) g.letterSpacing = '0px'; }
|
|
233
|
+
function scale(g, x, y, r, labels, mvFrom) {
|
|
234
|
+
labels.forEach((s, i) => {
|
|
235
|
+
const a = KA(i), ca = Math.sin(a), sa = -Math.cos(a);
|
|
236
|
+
g.strokeStyle = INK; g.lineWidth = 1.4; g.beginPath(); g.moveTo(x + ca * (r - 9), y + sa * (r - 9)); g.lineTo(x + ca * (r - 2), y + sa * (r - 2)); g.stroke();
|
|
237
|
+
lbl(g, s, x + ca * (r + 9), y + sa * (r + 9), 10, 'center', mvFrom != null && i >= mvFrom ? BLUE : INK, 700);
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
function buildPanel(W, H_) {
|
|
241
|
+
const c = H.layer(W, H_), g = c.getContext('2d');
|
|
242
|
+
// instrument panel: warm light-grey enamel
|
|
243
|
+
const pg = g.createLinearGradient(0, 0, 0, H_); pg.addColorStop(0, '#cdcabf'); pg.addColorStop(1, '#b3b0a5');
|
|
244
|
+
g.fillStyle = pg; g.fillRect(0, 0, W, H_);
|
|
245
|
+
const r = H.rng(31);
|
|
246
|
+
for (let i = 0; i < 14000; i++) { const v = r() < 0.5 ? 0 : 255; g.fillStyle = `rgba(${v},${v},${v},${0.035 + r() * 0.03})`; g.fillRect(r() * W, r() * H_, 1 + r(), 1 + r()); }
|
|
247
|
+
// CRT bezel: dark moulded plastic around the glass
|
|
248
|
+
g.save(); g.shadowColor = 'rgba(0,0,0,0.45)'; g.shadowBlur = 18; g.shadowOffsetY = 6;
|
|
249
|
+
rrect(g, 6, 4, 890, 712, 34); const bg = g.createLinearGradient(0, 0, 0, 720); bg.addColorStop(0, '#2e3032'); bg.addColorStop(1, '#151617'); g.fillStyle = bg; g.fill(); g.restore();
|
|
250
|
+
rrect(g, 6.5, 4.5, 889, 711, 34); g.strokeStyle = 'rgba(255,255,255,0.12)'; g.lineWidth = 1; g.stroke();
|
|
251
|
+
// stepped inner lip
|
|
252
|
+
rrect(g, GL.x - 10, GL.y - 10, GL.w + 20, GL.h + 20, GL.r + 8); g.fillStyle = '#0b0c0c'; g.fill();
|
|
253
|
+
rrect(g, GL.x - 10, GL.y - 10, GL.w + 20, GL.h + 20, GL.r + 8); g.strokeStyle = 'rgba(255,255,255,0.07)'; g.lineWidth = 1.5; g.stroke();
|
|
254
|
+
// bezel screws
|
|
255
|
+
[[18, 16], [884, 16], [18, 704], [884, 704]].forEach(([x, y]) => {
|
|
256
|
+
const sg = g.createRadialGradient(x - 2, y - 2, 1, x, y, 7); sg.addColorStop(0, '#d8d8d4'); sg.addColorStop(1, '#5c5c58');
|
|
257
|
+
g.fillStyle = sg; g.beginPath(); g.arc(x, y, 6, 0, 7); g.fill(); g.strokeStyle = '#2a2a28'; g.lineWidth = 1.6; g.beginPath(); g.moveTo(x - 4, y - 2); g.lineTo(x + 4, y + 2); g.stroke();
|
|
258
|
+
});
|
|
259
|
+
// ---- right-hand front panel
|
|
260
|
+
const X0 = 902;
|
|
261
|
+
g.font = '700 30px "Space Grotesk"'; g.fillStyle = INK; g.textAlign = 'left'; g.textBaseline = 'alphabetic'; g.fillText('XY-2', 920, 58);
|
|
262
|
+
lbl(g, 'OSCILLOGRAPH', 1012, 40, 12, 'left', INK, 800); lbl(g, 'DUAL TRACE · 20 MHz', 1012, 60, 10, 'left', BLUE, 600);
|
|
263
|
+
g.fillStyle = BLUE; g.fillRect(916, 74, 350, 3);
|
|
264
|
+
// row 1 labels
|
|
265
|
+
lbl(g, 'INTENSITY', 944, 160, 9); lbl(g, 'FOCUS', 1012, 160, 9); lbl(g, 'BEAM FIND', 1080, 160, 9); lbl(g, 'POWER', 1172, 160, 9);
|
|
266
|
+
// vertical section
|
|
267
|
+
g.strokeStyle = BLUE; g.lineWidth = 1.6; rrect(g, 914, 184, 356, 232, 8); g.stroke();
|
|
268
|
+
g.fillStyle = '#c4c1b6'; g.fillRect(1040, 176, 104, 16); lbl(g, 'VERTICAL', 1092, 184, 11, 'center', BLUE, 800);
|
|
269
|
+
scale(g, 986, 296, 58, VD, 6); scale(g, 1198, 296, 58, VD, 6);
|
|
270
|
+
lbl(g, 'VOLTS/DIV', 1092, 296, 10, 'center', INK, 800); lbl(g, 'mV', 1092, 316, 10, 'center', BLUE, 700);
|
|
271
|
+
lbl(g, 'CH 1 · X', 986, 394, 11, 'center', INK, 800); lbl(g, 'CH 2 · Y', 1198, 394, 11, 'center', INK, 800);
|
|
272
|
+
// horizontal section
|
|
273
|
+
g.strokeStyle = BLUE; rrect(g, 914, 432, 356, 182, 8); g.stroke();
|
|
274
|
+
g.fillStyle = '#bebbb0'; g.fillRect(1030, 424, 124, 16); lbl(g, 'HORIZONTAL', 1092, 432, 11, 'center', BLUE, 800);
|
|
275
|
+
scale(g, 1004, 524, 58, TD, 4); lbl(g, 'TIME/DIV', 1004, 600, 10, 'center', INK, 800);
|
|
276
|
+
lbl(g, 'MODE', 1186, 456, 10, 'center', INK, 800);
|
|
277
|
+
lbl(g, 'X-Y', 1150, 482, 10, 'right', INK, 800); lbl(g, 'ALT', 1150, 522, 10, 'right', INK, 800); lbl(g, 'CHOP', 1150, 562, 10, 'right', INK, 800);
|
|
278
|
+
// inputs
|
|
279
|
+
lbl(g, 'CH 1 OR X', 960, 634, 9); lbl(g, 'CH 2 OR Y', 1060, 634, 9); lbl(g, 'EXT TRIG', 1160, 634, 9); lbl(g, 'CAL .5V', 1242, 634, 9);
|
|
280
|
+
lbl(g, '1MΩ 20pF 400V PK MAX', 1060, 706, 8, 'center', BLUE, 600);
|
|
281
|
+
[960, 1060, 1160].forEach((x) => {
|
|
282
|
+
const y = 666; g.save(); g.shadowColor = 'rgba(0,0,0,.4)'; g.shadowBlur = 6; g.shadowOffsetY = 3;
|
|
283
|
+
g.beginPath(); for (let i = 0; i < 6; i++) { const a = i * Math.PI / 3 + 0.5; g[i ? 'lineTo' : 'moveTo'](x + Math.cos(a) * 21, y + Math.sin(a) * 21); } g.closePath();
|
|
284
|
+
const ng = g.createLinearGradient(x - 20, y - 20, x + 20, y + 20); ng.addColorStop(0, '#e9e9e4'); ng.addColorStop(1, '#7d7d78'); g.fillStyle = ng; g.fill(); g.restore();
|
|
285
|
+
const cg = g.createRadialGradient(x - 4, y - 4, 2, x, y, 15); cg.addColorStop(0, '#f2f2ee'); cg.addColorStop(1, '#8c8c87'); g.fillStyle = cg; g.beginPath(); g.arc(x, y, 14, 0, 7); g.fill();
|
|
286
|
+
g.fillStyle = '#1c1c1b'; g.beginPath(); g.arc(x, y, 8.5, 0, 7); g.fill(); g.fillStyle = '#d8b25a'; g.beginPath(); g.arc(x, y, 2.6, 0, 7); g.fill();
|
|
287
|
+
g.fillStyle = '#b8b8b2'; [[-15, 0], [15, 0]].forEach(([dx]) => { g.beginPath(); g.arc(x + dx, y, 3, 0, 7); g.fill(); });
|
|
288
|
+
});
|
|
289
|
+
g.fillStyle = '#9a9a94'; g.beginPath(); g.arc(1242, 666, 9, 0, 7); g.fill(); g.strokeStyle = '#d8d8d0'; g.lineWidth = 3; g.beginPath(); g.arc(1242, 656, 7, Math.PI, 0); g.stroke();
|
|
290
|
+
// panel edge lighting + seam between bezel and panel
|
|
291
|
+
const sh = g.createLinearGradient(896, 0, 930, 0); sh.addColorStop(0, 'rgba(0,0,0,0.25)'); sh.addColorStop(1, 'rgba(0,0,0,0)'); g.fillStyle = sh; g.fillRect(896, 0, 34, H_);
|
|
292
|
+
return c;
|
|
293
|
+
}
|
|
294
|
+
function buildGlass(W, H_) {
|
|
295
|
+
const c = H.layer(W, H_), g = c.getContext('2d');
|
|
296
|
+
const rg = g.createRadialGradient(CX, CY, 60, CX, CY, 560); rg.addColorStop(0, '#111a14'); rg.addColorStop(0.7, '#0a100c'); rg.addColorStop(1, '#040605');
|
|
297
|
+
rrect(g, GL.x, GL.y, GL.w, GL.h, GL.r); g.fillStyle = rg; g.fill();
|
|
298
|
+
const r = H.rng(77);
|
|
299
|
+
for (let i = 0; i < 26000; i++) { g.fillStyle = `rgba(150,190,150,${r() * 0.05})`; g.fillRect(GL.x + r() * GL.w, GL.y + r() * GL.h, 1, 1); }
|
|
300
|
+
return c;
|
|
301
|
+
}
|
|
302
|
+
function buildGrat(W, H_) {
|
|
303
|
+
const c = H.layer(W, H_), g = c.getContext('2d');
|
|
304
|
+
g.strokeStyle = 'rgba(170,205,180,0.22)'; g.lineWidth = 1;
|
|
305
|
+
for (let i = 0; i <= 10; i++) { g.beginPath(); g.moveTo(GX + i * DIV + 0.5, GY); g.lineTo(GX + i * DIV + 0.5, GY + GH); g.stroke(); }
|
|
306
|
+
for (let j = 0; j <= 8; j++) { g.beginPath(); g.moveTo(GX, GY + j * DIV + 0.5); g.lineTo(GX + GW, GY + j * DIV + 0.5); g.stroke(); }
|
|
307
|
+
g.strokeStyle = 'rgba(170,205,180,0.34)';
|
|
308
|
+
for (let i = 0; i <= 50; i++) { const x = GX + i * DIV / 5 + 0.5, l = i % 5 ? 5 : 0; if (l) { g.beginPath(); g.moveTo(x, CY - l); g.lineTo(x, CY + l); g.stroke(); } }
|
|
309
|
+
for (let j = 0; j <= 40; j++) { const y = GY + j * DIV / 5 + 0.5, l = j % 5 ? 5 : 0; if (l) { g.beginPath(); g.moveTo(CX - l, y); g.lineTo(CX + l, y); g.stroke(); } }
|
|
310
|
+
// 0% / 10% / 90% / 100% rise-time marks
|
|
311
|
+
g.setLineDash([2, 4]); g.strokeStyle = 'rgba(170,205,180,0.26)';
|
|
312
|
+
[CY - 2.5 * DIV, CY + 2.5 * DIV, CY - 2 * DIV, CY + 2 * DIV].forEach((y) => { g.beginPath(); g.moveTo(GX, y + 0.5); g.lineTo(GX + GW, y + 0.5); g.stroke(); });
|
|
313
|
+
g.setLineDash([]);
|
|
314
|
+
g.font = '600 10px "Inter"'; g.fillStyle = 'rgba(170,205,180,0.4)'; g.textAlign = 'left'; g.textBaseline = 'middle';
|
|
315
|
+
[['100', CY - 2.5 * DIV], ['90', CY - 2 * DIV], ['10', CY + 2 * DIV], ['0%', CY + 2.5 * DIV]].forEach(([s, y]) => g.fillText(s, GX + 4, y - 7));
|
|
316
|
+
return c;
|
|
317
|
+
}
|
|
318
|
+
function buildRefl(W, H_) {
|
|
319
|
+
const c = H.layer(W, H_), g = c.getContext('2d');
|
|
320
|
+
rrect(g, GL.x, GL.y, GL.w, GL.h, GL.r); g.save(); g.clip();
|
|
321
|
+
// curved-faceplate edge darkening
|
|
322
|
+
const ed = g.createRadialGradient(CX, CY, 300, CX, CY, 600); ed.addColorStop(0, 'rgba(0,0,0,0)'); ed.addColorStop(1, 'rgba(0,0,0,0.55)'); g.fillStyle = ed; g.fillRect(0, 0, W, H_);
|
|
323
|
+
// soft window reflection across the upper left of the glass
|
|
324
|
+
const rf = g.createLinearGradient(GL.x, GL.y, GL.x + 520, GL.y + 380); rf.addColorStop(0, 'rgba(220,235,255,0.075)'); rf.addColorStop(0.45, 'rgba(220,235,255,0.025)'); rf.addColorStop(0.5, 'rgba(220,235,255,0)');
|
|
325
|
+
g.fillStyle = rf; g.beginPath(); g.moveTo(GL.x, GL.y); g.lineTo(GL.x + 640, GL.y); g.lineTo(GL.x, GL.y + 470); g.fill();
|
|
326
|
+
g.restore();
|
|
327
|
+
rrect(g, GL.x + 1, GL.y + 1, GL.w - 2, GL.h - 2, GL.r); g.strokeStyle = 'rgba(200,230,210,0.10)'; g.lineWidth = 2; g.stroke();
|
|
328
|
+
return c;
|
|
329
|
+
}
|
|
330
|
+
const get = (k, fn, W, H_) => C[k] || (C[k] = fn(W, H_));
|
|
331
|
+
|
|
332
|
+
// ---------- dynamic panel parts ----------
|
|
333
|
+
function knob(g, x, y, r, ang, skirt) {
|
|
334
|
+
const sh = g.createRadialGradient(x + 3, y + 6, r * 0.6, x + 3, y + 6, r * (skirt ? 1.5 : 1.35)); sh.addColorStop(0, 'rgba(0,0,0,0.45)'); sh.addColorStop(1, 'rgba(0,0,0,0)');
|
|
335
|
+
g.fillStyle = sh; g.beginPath(); g.arc(x + 3, y + 6, r * (skirt ? 1.5 : 1.35), 0, 7); g.fill();
|
|
336
|
+
const sx = Math.sin(ang), sy = -Math.cos(ang);
|
|
337
|
+
if (skirt) {
|
|
338
|
+
const kg = g.createRadialGradient(x - r * 0.4, y - r * 0.5, 2, x, y, r * 1.3); kg.addColorStop(0, '#4a4b4c'); kg.addColorStop(1, '#151515');
|
|
339
|
+
g.fillStyle = kg; g.beginPath(); g.arc(x, y, r * 1.28, 0, 7); g.fill();
|
|
340
|
+
g.fillStyle = '#f0efe8'; g.beginPath(); g.moveTo(x + sx * r * 1.26, y + sy * r * 1.26); g.lineTo(x + sx * r * 1.05 + sy * 4, y + sy * r * 1.05 - sx * 4); g.lineTo(x + sx * r * 1.05 - sy * 4, y + sy * r * 1.05 + sx * 4); g.fill();
|
|
341
|
+
}
|
|
342
|
+
const bg = g.createRadialGradient(x - r * 0.35, y - r * 0.45, 1, x, y, r); bg.addColorStop(0, '#3e3e3e'); bg.addColorStop(1, '#0c0c0c');
|
|
343
|
+
g.fillStyle = bg; g.beginPath(); g.arc(x, y, r, 0, 7); g.fill();
|
|
344
|
+
g.strokeStyle = 'rgba(255,255,255,0.13)'; g.lineWidth = 1;
|
|
345
|
+
for (let i = 0; i < 40; i++) { const a = ang + i * Math.PI / 20; g.beginPath(); g.moveTo(x + Math.cos(a) * r * 0.86, y + Math.sin(a) * r * 0.86); g.lineTo(x + Math.cos(a) * r * 0.99, y + Math.sin(a) * r * 0.99); g.stroke(); }
|
|
346
|
+
const cg = g.createLinearGradient(x - r, y - r, x + r * 0.6, y + r * 0.6); cg.addColorStop(0, '#f4f4f0'); cg.addColorStop(1, '#8e8e8a');
|
|
347
|
+
g.fillStyle = cg; g.beginPath(); g.arc(x, y, r * 0.6, 0, 7); g.fill();
|
|
348
|
+
g.strokeStyle = 'rgba(0,0,0,0.08)'; for (let k = 2; k < 8; k++) { g.beginPath(); g.arc(x, y, r * 0.075 * k, 0, 7); g.stroke(); }
|
|
349
|
+
g.strokeStyle = '#fbfbf6'; g.lineWidth = Math.max(2, r * 0.08); g.lineCap = 'round';
|
|
350
|
+
g.beginPath(); g.moveTo(x + sx * r * 0.62, y + sy * r * 0.62); g.lineTo(x + sx * r * 0.93, y + sy * r * 0.93); g.stroke();
|
|
351
|
+
g.strokeStyle = '#2a2a2a'; g.lineWidth = 2; g.beginPath(); g.moveTo(x + sx * r * 0.1, y + sy * r * 0.1); g.lineTo(x + sx * r * 0.55, y + sy * r * 0.55); g.stroke(); g.lineCap = 'butt';
|
|
352
|
+
}
|
|
353
|
+
function button(g, x, y, pressed) {
|
|
354
|
+
g.fillStyle = 'rgba(0,0,0,0.35)'; rrect(g, x - 17, y - 9, 36, 22, 4); g.fill();
|
|
355
|
+
const bg = g.createLinearGradient(0, y - 11, 0, y + 11); bg.addColorStop(0, pressed ? '#9c9a92' : '#ecebe5'); bg.addColorStop(1, pressed ? '#c9c7bf' : '#a9a79f');
|
|
356
|
+
g.fillStyle = bg; rrect(g, x - 18, y - (pressed ? 9 : 11), 36, 20, 4); g.fill();
|
|
357
|
+
}
|
|
358
|
+
function led(g, x, y, on, col) {
|
|
359
|
+
if (on) { const lg = g.createRadialGradient(x, y, 0, x, y, 16); lg.addColorStop(0, H.rgba(col, 0.9)); lg.addColorStop(0.3, H.rgba(col, 0.35)); lg.addColorStop(1, H.rgba(col, 0)); g.fillStyle = lg; g.beginPath(); g.arc(x, y, 16, 0, 7); g.fill(); }
|
|
360
|
+
g.fillStyle = on ? H.rgba(H.mix(col, [255, 255, 255], 0.5), 1) : '#3b2a22'; g.beginPath(); g.arc(x, y, 4.5, 0, 7); g.fill();
|
|
361
|
+
g.strokeStyle = 'rgba(0,0,0,.5)'; g.lineWidth = 1; g.stroke();
|
|
362
|
+
}
|
|
363
|
+
function panelLive(g, st) {
|
|
364
|
+
const k = st.kn;
|
|
365
|
+
knob(g, 944, 128, 17, (k.inten * 270 - 135) * Math.PI / 180, false);
|
|
366
|
+
knob(g, 1012, 128, 17, 0.3, false);
|
|
367
|
+
button(g, 1080, 128, false);
|
|
368
|
+
button(g, 1172, 128, st.power > 0);
|
|
369
|
+
led(g, 1222, 128, st.power > 0.5, [255, 140, 40]);
|
|
370
|
+
knob(g, 986, 296, 34, k.ch1, true);
|
|
371
|
+
knob(g, 1198, 296, 34, k.ch2, true);
|
|
372
|
+
knob(g, 1004, 524, 34, k.tdiv, true);
|
|
373
|
+
button(g, 1186, 482, st.xy); led(g, 1232, 482, st.xy && st.power > 0.5, [120, 255, 90]);
|
|
374
|
+
button(g, 1186, 522, !st.xy); led(g, 1232, 522, !st.xy && st.power > 0.5, [120, 255, 90]);
|
|
375
|
+
button(g, 1186, 562, false); led(g, 1232, 562, false, [120, 255, 90]);
|
|
376
|
+
}
|
|
377
|
+
const snapKnob = (t, changes, idx0) => { // changes: [[time, idx]] -> angle with a quick detent snap
|
|
378
|
+
let a = KA(idx0);
|
|
379
|
+
for (const [tc, idx] of changes) { if (t < tc) break; const prev = a, p = H.clamp((t - tc) / 0.12); a = H.lerp(prev, KA(idx), H.ease.back(p)); }
|
|
380
|
+
return a;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
// ---------- frame render ----------
|
|
384
|
+
const L = {};
|
|
385
|
+
const lay = (k, w, h) => L[k] || (L[k] = H.layer(w, h));
|
|
386
|
+
function renderFrame(ctx, W, H_, st) {
|
|
387
|
+
ctx.drawImage(get('panel', buildPanel, W, H_), 0, 0);
|
|
388
|
+
panelLive(ctx, st);
|
|
389
|
+
// beam layer
|
|
390
|
+
const bl = lay('beam', W, H_), b = bl.getContext('2d');
|
|
391
|
+
b.setTransform(1, 0, 0, 1, 0, 0); b.globalCompositeOperation = 'source-over'; b.clearRect(0, 0, W, H_);
|
|
392
|
+
b.globalCompositeOperation = 'lighter'; b.lineCap = 'round'; b.lineJoin = 'round';
|
|
393
|
+
const I = st.inten;
|
|
394
|
+
// phosphor core colour. Thin traces read white-green; a wide (heavily driven) trace keeps its P31 body and only
|
|
395
|
+
// its centre goes white-hot, so big lettering stays phosphor green instead of flat white.
|
|
396
|
+
const WH = [235, 255, 225];
|
|
397
|
+
const coreOf = (col, hot, w) => H.mix(col, WH, w > 3 ? 0.36 + 0.16 * Math.min(1, hot || 0) : 0.55 + 0.25 * Math.min(1, hot || 0));
|
|
398
|
+
const pathL = (it) => { b.beginPath(); it.pts.forEach((p, i) => (i ? b.lineTo(p[0], p[1]) : b.moveTo(p[0], p[1]))); };
|
|
399
|
+
const pathS = (it) => { b.beginPath(); const A = it.arr; for (let i = 0; i < A.length; i += 4) { b.moveTo(A[i], A[i + 1]); b.lineTo(A[i + 2], A[i + 3]); } };
|
|
400
|
+
const strokeAll = (pass) => {
|
|
401
|
+
for (const it of B.lines) {
|
|
402
|
+
const a = Math.min(1, it.a * I); if (a < 0.003) continue;
|
|
403
|
+
b.strokeStyle = pass ? H.rgba(coreOf(it.col, 0, it.w), a) : H.rgba(it.col, a * 0.2); b.lineWidth = pass ? it.w : it.w * 3.4;
|
|
404
|
+
pathL(it); b.stroke();
|
|
405
|
+
if (pass && it.w > 3) { b.strokeStyle = H.rgba(H.mix(it.col, WH, 0.82), a * 0.75); b.lineWidth = it.w * 0.42; b.stroke(); }
|
|
406
|
+
}
|
|
407
|
+
for (const it of B.segs) {
|
|
408
|
+
const a = Math.min(1, it.a * I); if (a < 0.003) continue;
|
|
409
|
+
b.strokeStyle = pass ? H.rgba(coreOf(it.col, it.hot, it.w), a) : H.rgba(it.col, a * 0.2); b.lineWidth = pass ? it.w : it.w * 3.4;
|
|
410
|
+
pathS(it); b.stroke();
|
|
411
|
+
if (pass && it.w > 3) { b.strokeStyle = H.rgba(H.mix(it.col, WH, 0.82), a * 0.75); b.lineWidth = it.w * 0.42; b.stroke(); }
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
strokeAll(0); strokeAll(1);
|
|
415
|
+
for (const d of B.dots) {
|
|
416
|
+
const a = Math.min(1, d.a * I); if (a < 0.01) continue;
|
|
417
|
+
b.fillStyle = H.rgba(d.col, a * 0.28); b.beginPath(); b.arc(d.x, d.y, d.r * 2.4, 0, 7); b.fill();
|
|
418
|
+
// dwell dots on wide lettering: green body with a small white-hot centre (beam head dots stay fully hot)
|
|
419
|
+
if (!d.hot && d.r > 3) {
|
|
420
|
+
b.fillStyle = H.rgba(coreOf(d.col, 0, d.r), a); b.beginPath(); b.arc(d.x, d.y, d.r, 0, 7); b.fill();
|
|
421
|
+
b.fillStyle = H.rgba(H.mix(d.col, WH, 0.85), a * 0.8); b.beginPath(); b.arc(d.x, d.y, d.r * 0.45, 0, 7); b.fill();
|
|
422
|
+
} else { b.fillStyle = H.rgba(coreOf(d.col, 1), a); b.beginPath(); b.arc(d.x, d.y, d.r, 0, 7); b.fill(); }
|
|
423
|
+
}
|
|
424
|
+
// bloom on small buffers
|
|
425
|
+
const s1 = lay('s1', W / 2, H_ / 2), g1 = s1.getContext('2d'), s2 = lay('s2', W / 4, H_ / 4), g2 = s2.getContext('2d');
|
|
426
|
+
g1.globalCompositeOperation = 'source-over'; g1.clearRect(0, 0, W / 2, H_ / 2); g1.filter = 'blur(3px)'; g1.drawImage(bl, 0, 0, W / 2, H_ / 2); g1.filter = 'none';
|
|
427
|
+
g2.globalCompositeOperation = 'source-over'; g2.clearRect(0, 0, W / 4, H_ / 4); g2.filter = 'blur(5px)'; g2.drawImage(bl, 0, 0, W / 4, H_ / 4); g2.filter = 'none';
|
|
428
|
+
// screen
|
|
429
|
+
ctx.save(); rrect(ctx, GL.x, GL.y, GL.w, GL.h, GL.r); ctx.clip();
|
|
430
|
+
ctx.drawImage(get('glass', buildGlass, W, H_), 0, 0);
|
|
431
|
+
// faint phosphor ambient lift from total beam energy
|
|
432
|
+
ctx.globalCompositeOperation = 'lighter';
|
|
433
|
+
ctx.globalAlpha = Math.min(1, 0.9 * st.bloom); ctx.drawImage(s2, 0, 0, W, H_);
|
|
434
|
+
ctx.globalAlpha = Math.min(1, 0.85 * Math.sqrt(st.bloom)); ctx.drawImage(s1, 0, 0, W, H_);
|
|
435
|
+
ctx.globalAlpha = 1; ctx.drawImage(bl, 0, 0);
|
|
436
|
+
if (st.bloom > 1.05) { ctx.globalAlpha = Math.min(1, (st.bloom - 1) * 0.5); ctx.drawImage(s2, -8, -8, W + 16, H_ + 16); }
|
|
437
|
+
ctx.globalAlpha = 1; ctx.globalCompositeOperation = 'source-over';
|
|
438
|
+
ctx.drawImage(get('grat', buildGrat, W, H_), 0, 0);
|
|
439
|
+
ctx.drawImage(get('refl', buildRefl, W, H_), 0, 0);
|
|
440
|
+
ctx.restore();
|
|
441
|
+
// phosphor light spilling onto the bezel lip
|
|
442
|
+
ctx.save(); ctx.globalCompositeOperation = 'lighter'; ctx.globalAlpha = 0.28; ctx.drawImage(s2, -6, -6, W + 12, H_ + 12); ctx.restore();
|
|
443
|
+
H.vignette(ctx, W, H_, 0.35);
|
|
444
|
+
}
|
|
445
|
+
const baseState = () => ({ power: 1, xy: true, inten: 1, bloom: 1, kn: { inten: 0.55, ch1: KA(3), ch2: KA(3), tdiv: KA(0) } });
|
|
446
|
+
|
|
447
|
+
// ================= STILL / CLIP =================
|
|
448
|
+
function drawStill(ctx, W, H_, t) {
|
|
449
|
+
resetBeam();
|
|
450
|
+
const st = baseState(), ph = (a, d) => H.ease.inOut(H.clamp((t - a) / d));
|
|
451
|
+
st.power = t > 0.12 ? 1 : 0;
|
|
452
|
+
st.xy = t >= 1.0;
|
|
453
|
+
st.kn.tdiv = snapKnob(t, [[1.0, 0]], 5);
|
|
454
|
+
st.kn.inten = 0.3 + 0.25 * ph(0.12, 0.5);
|
|
455
|
+
const jit = H.rng(900 + Math.floor(t * 30));
|
|
456
|
+
const spin = t * 0.9, R = 178, sx = CX, sy = 296;
|
|
457
|
+
if (t >= 0.12) {
|
|
458
|
+
// power-on: dot blooms at centre, then the timebase sweep pulls it into a line
|
|
459
|
+
const dotA = ph(0.12, 0.25) * (1 - ph(0.4, 0.2));
|
|
460
|
+
if (dotA > 0) dot(CX, CY, 5 + 4 * dotA, dotA);
|
|
461
|
+
if (t < 1.0) {
|
|
462
|
+
const fx = ph(0.35, 0.3), amp = 150 * ph(0.55, 0.45);
|
|
463
|
+
beam([curve((u) => [GX + u * GW, CY - amp * Math.sin(6.2832 * (2 * u + t * 0.6))], 220)], { f: fx, w: 2.4, timed: true });
|
|
464
|
+
} else if (t < 1.65) {
|
|
465
|
+
// X-Y pressed: the swept sine folds into a circle
|
|
466
|
+
const m = H.ease.inOut(H.clamp((t - 1.0) / 0.6));
|
|
467
|
+
beam([curve((u) => {
|
|
468
|
+
const tb = [GX + u * GW, CY - 150 * Math.sin(6.2832 * (2 * u + t * 0.6))];
|
|
469
|
+
const xy = [sx + R * Math.cos(6.2832 * u + spin), sy - R * Math.sin(0.38) * Math.sin(6.2832 * u + spin)];
|
|
470
|
+
return [H.lerp(tb[0], xy[0], m), H.lerp(tb[1], xy[1], m)];
|
|
471
|
+
}, 260)], { w: 2.4, timed: true });
|
|
472
|
+
} else {
|
|
473
|
+
const e = H.ease.inOut(H.clamp((t - 1.65) / 1.0));
|
|
474
|
+
beam([sphere(sx, sy, R, spin, 0.38, e, 0.02 * e + 0.004 * Math.sin(t * 7), 1500, 22)], { w: 2.0, timed: true, a: 0.9 });
|
|
475
|
+
// orbit ring with oscilloscope-music ripples
|
|
476
|
+
const rf = ph(2.2, 0.7);
|
|
477
|
+
if (rf > 0) beam([curve((u) => { const th = u * 6.2832, rr = 1 + 0.006 * Math.sin(12 * th + t * 5); const x = Math.cos(th) * 300 * rr, y = Math.sin(th) * 64 * rr, c = Math.cos(-0.17), s = Math.sin(-0.17); return [sx + x * c - y * s, sy + 10 + x * s + y * c]; }, 420)], { f: rf, w: 2.0, timed: true, a: 0.85 });
|
|
478
|
+
}
|
|
479
|
+
// satellite: drifting 1:2 Lissajous
|
|
480
|
+
const lf = ph(3.0, 0.5);
|
|
481
|
+
if (lf > 0) beam([curve((u) => { const a = u * 6.2832; return [768 + 56 * Math.sin(a + t * 1.3), 128 + 46 * Math.sin(2 * a)]; }, 160)], { f: lf, w: 1.9, timed: true, a: 0.85 });
|
|
482
|
+
// CRT readouts
|
|
483
|
+
const rd = ph(2.9, 0.5);
|
|
484
|
+
text('CH1 .5V', GX + 18, GY + 16, 14, { f: rd, a: 0.5 }); text('CH2 .5V', GX + 18, GY + 38, 14, { f: rd, a: 0.5 });
|
|
485
|
+
text(t >= 1.0 ? 'X-Y' : '1MS', GX + GW - 18, GY + GH - 32, 14, { align: 'r', f: ph(0.6, 0.3), a: 0.5 });
|
|
486
|
+
// title
|
|
487
|
+
const T = 'THE SHAPE OF A THOUGHT', cap = Math.min(36, 740 / textW(T, 1));
|
|
488
|
+
text(T, CX, 568, cap, { align: 'c', f: H.clamp((t - 2.6) / 1.15), a: 1 });
|
|
489
|
+
// underline cursor
|
|
490
|
+
const uf = ph(3.75, 0.4);
|
|
491
|
+
if (uf > 0) beam([[[CX - 120, 622], [CX + 120, 622]]], { f: uf, w: 2.2, a: 0.75 });
|
|
492
|
+
}
|
|
493
|
+
st.bloom = 1 + 0.6 * Math.max(0, 1 - Math.abs(t - 0.3) / 0.25);
|
|
494
|
+
st.inten = 0.92 + 0.08 * jit();
|
|
495
|
+
renderFrame(ctx, W, H_, st);
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// ================= EXPLAINER =================
|
|
499
|
+
const EXD = 15;
|
|
500
|
+
// timing constants (shared with the sound score)
|
|
501
|
+
const TX = {
|
|
502
|
+
title: [0.3, 1.5], shrink: [2.0, 0.55], net2: [2.5, 0.7], model2: [3.0, 0.4], conv: [2.7, 0.4], cnt: [3.0, 5.6], itemsT0: 2.75, itemsDT: 0.5, itemSpeed: 270,
|
|
503
|
+
col1: 5.8, dot1: 6.1, exp1: 6.25, // collapse to dot -> expand
|
|
504
|
+
cat3: 6.25, arr1: 6.85, row: 6.45, arr2: 7.1, guess: 7.3, check: 7.9, cross: 7.9, itsCat: 8.05, adjust: 8.5, back: [8.5, 8.75], bpNet: [8.75, 9.15], weights: [8.8, 9.3], loopArc: 8.9,
|
|
505
|
+
guess2: 9.3, fwd: [9.3, 9.65], cat2: 9.65, tick: 9.97, check2: 10.0,
|
|
506
|
+
sqX: [10.35, 10.6], sweep: [10.85, 12.75], hit: 12.75, sqY: [12.95, 13.2], take: [13.25, 14.05], liss: [14.0, 0.5],
|
|
507
|
+
};
|
|
508
|
+
const fmt = (n) => String(n).replace(/\B(?=(\d{3})+(?!\d))/g, ',');
|
|
509
|
+
const acc = (u) => 0.61 + 0.36 * (1 - Math.exp(-3.4 * u)) / (1 - Math.exp(-3.4));
|
|
510
|
+
const fade = (t, t0, d) => (t < t0 ? 1 : Math.pow(Math.max(0, 1 - (t - t0) / d), 2));
|
|
511
|
+
|
|
512
|
+
function exScene(t) {
|
|
513
|
+
const st = baseState();
|
|
514
|
+
const ph = (a, d) => H.ease.inOut(H.clamp((t - a) / d));
|
|
515
|
+
const lin = (a, d) => H.clamp((t - a) / d);
|
|
516
|
+
const frame = Math.floor(t * 30), jr = H.rng(3000 + frame);
|
|
517
|
+
|
|
518
|
+
// ---- mode + transforms
|
|
519
|
+
let sx = 1, sy = 1, mode = 'xy';
|
|
520
|
+
if (t < TX.dot1) { sx = sy = 1 - H.ease.in(lin(TX.col1, TX.dot1 - TX.col1)); }
|
|
521
|
+
else if (t < TX.exp1) { sx = sy = 0; }
|
|
522
|
+
else if (t < 6.6) { sx = sy = H.ease.out(lin(TX.exp1, 0.35)); }
|
|
523
|
+
if (t < 0.45) { sx = sy = Math.max(0, H.ease.out(lin(0.12, 0.33))); }
|
|
524
|
+
if (t >= TX.sqX[0] && t < TX.sweep[0]) { sx = t < TX.sqX[1] ? 1 - H.ease.in(lin(TX.sqX[0], TX.sqX[1] - TX.sqX[0])) : 0; }
|
|
525
|
+
if (t >= TX.sqX[1] && t < TX.sqY[1]) mode = 'tb';
|
|
526
|
+
if (t >= TX.sqY[0] && t < TX.take[0]) { sy = t < TX.sqY[1] ? 1 - H.ease.in(lin(TX.sqY[0], TX.sqY[1] - TX.sqY[0])) : 0; }
|
|
527
|
+
// wrong-guess tremor: the whole trace shudders as the check fails
|
|
528
|
+
const shud = t > TX.check && t < TX.check + 0.6 ? (1 - (t - TX.check) / 0.6) : 0;
|
|
529
|
+
XF = (p) => {
|
|
530
|
+
let x = CX + (p[0] - CX) * sx, y = CY + (p[1] - CY) * sy;
|
|
531
|
+
if (shud > 0) { x += shud * 7 * Math.sin(p[1] * 0.06 + t * 70); y += shud * 2 * Math.sin(p[0] * 0.05 + t * 50); }
|
|
532
|
+
return [x, y, p[2]];
|
|
533
|
+
};
|
|
534
|
+
if (t > TX.col1 && t < TX.exp1 + 0.35) { const k2 = 1 - sx; dot(CX, CY, 3 + 5 * k2, k2); }
|
|
535
|
+
|
|
536
|
+
// ---- BEAT 1: hook
|
|
537
|
+
const hdrRe = (t0) => H.clamp((t - t0) / 0.3);
|
|
538
|
+
if (t < TX.exp1) {
|
|
539
|
+
const s = ph(TX.shrink[0], TX.shrink[1]), s2 = ph(TX.shrink[0] + 0.15, TX.shrink[1]);
|
|
540
|
+
const capA = 66, capB = 30;
|
|
541
|
+
const l1 = 'HOW DOES', l2 = 'AI LEARN?';
|
|
542
|
+
const w1a = textW(l1, capA), w2a = textW(l2, capA);
|
|
543
|
+
const cap = H.lerp(capA, capB, s);
|
|
544
|
+
const x1 = H.lerp(CX - w1a / 2, 74, s), y1 = H.lerp(232, 56, s);
|
|
545
|
+
const x2 = H.lerp(CX - w2a / 2, 74 + textW(l1 + ' ', capB), s2), y2 = H.lerp(336, 56, s2), cap2 = H.lerp(capA, capB, s2);
|
|
546
|
+
const f = H.clamp((t - TX.title[0]) / (TX.title[1] - TX.title[0]));
|
|
547
|
+
const f1 = H.clamp(f * 2), f2 = H.clamp(f * 2 - 1);
|
|
548
|
+
text(l1, x1, y1, cap, { f: f1, w: H.lerp(4.6, 2.7, s) });
|
|
549
|
+
text(l2, x2, y2, cap2, { f: f2, w: H.lerp(4.6, 2.7, s2) });
|
|
550
|
+
// underline sine drawn under the question
|
|
551
|
+
const uf = ph(1.4, 0.45) * (1 - s);
|
|
552
|
+
if (uf > 0.01) beam([curve((u) => [CX - 250 + u * 500, 452 + 16 * Math.sin(u * 6.2832 * 4 + t * 3)], 140)], { f: ph(1.4, 0.45), a: 1 - s, w: 2.6, timed: true });
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
// ---- BEAT 2: data streams into the model
|
|
556
|
+
if (t >= TX.net2[0] && t < TX.exp1) {
|
|
557
|
+
const rot = 0.35 * Math.sin(t * 1.1);
|
|
558
|
+
const absorbs = [];
|
|
559
|
+
// examples: cat / dog faces with labels riding the stream
|
|
560
|
+
const pick = H.rng(44);
|
|
561
|
+
for (let i = 0; i < 12; i++) {
|
|
562
|
+
const isCat = pick() < 0.5, t0 = TX.itemsT0 + i * TX.itemsDT;
|
|
563
|
+
for (let gh = 0; gh < 3; gh++) {
|
|
564
|
+
const tt = t - gh * 0.035; if (tt < t0) continue;
|
|
565
|
+
const x = 30 + (tt - t0) * TX.itemSpeed * (1 + Math.max(0, tt - 4.6) * 0.5);
|
|
566
|
+
if (x > 590) { if (gh === 0 && x < 640) absorbs.push(1 - (x - 590) / 50); continue; }
|
|
567
|
+
GA = gh === 0 ? 1 : 0.16 / gh;
|
|
568
|
+
const shrink = x > 470 ? 1 - (x - 470) / 150 : 1, y = 214 + (x > 470 ? (x - 470) * 0.25 : 0);
|
|
569
|
+
beam(tx(isCat ? CAT : DOG, x, y, 40 * shrink), { w: 2.1, dots: gh === 0 });
|
|
570
|
+
if (shrink > 0.6 && gh === 0) text(isCat ? 'CAT' : 'DOG', x, 278, 24, { align: 'c', a: shrink });
|
|
571
|
+
GA = 1;
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const fl = absorbs.length ? Math.max(...absorbs) : 0;
|
|
575
|
+
net(690, 238, 210, 250, rot, { f: H.clamp((t - TX.net2[0]) / TX.net2[1]), act: (l) => fl * (l === 0 ? 1 : l === 1 ? 0.6 : 0.3) });
|
|
576
|
+
text('MODEL', 690, 378, 26, { align: 'c', f: H.clamp((t - TX.model2[0]) / TX.model2[1]) });
|
|
577
|
+
beam(arrowS(70, 330, 545, 330, 16), { f: ph(TX.conv[0], TX.conv[1]), a: 0.8, w: 2.2 });
|
|
578
|
+
// counter
|
|
579
|
+
const cp = H.clamp((t - TX.cnt[0]) / (TX.cnt[1] - TX.cnt[0])), n = Math.round(1e6 * Math.pow(cp, 2.3));
|
|
580
|
+
const cf = H.clamp((t - TX.cnt[0]) / 0.3);
|
|
581
|
+
text(fmt(n), CX, 470, 70, { align: 'c', f: cf, w: 4.6 });
|
|
582
|
+
text('EXAMPLES', CX, 566, 30, { align: 'c', f: H.clamp((t - 3.2) / 0.35) });
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// ---- BEAT 3: guess -> check -> adjust
|
|
586
|
+
if (t >= TX.exp1 && t < TX.sweep[0]) {
|
|
587
|
+
const H3 = 'HOW DOES AI LEARN?';
|
|
588
|
+
text(H3, 74, 56, 30, { w: 2.7, f: hdrRe(6.25) });
|
|
589
|
+
const catF = H.clamp((t - TX.cat3) / 0.55);
|
|
590
|
+
beam(tx(CAT, 158, 300, 74), { f: catF, w: 2.6 });
|
|
591
|
+
const pulse1 = t > TX.fwd[0] && t < TX.fwd[1] ? (t - TX.fwd[0]) / (TX.fwd[1] - TX.fwd[0]) : -1;
|
|
592
|
+
beam(arrowS(266, 300, 318, 300, 13), { f: ph(TX.arr1, 0.2), w: 2.2 });
|
|
593
|
+
// net: activation flash on guesses, amber back-propagation on adjust
|
|
594
|
+
const actT = (l, t0) => { const d = t - (t0 + l * 0.1); return d > 0 && d < 0.35 ? 1 - d / 0.35 : 0; };
|
|
595
|
+
const bpT = (l) => { const d = t - (TX.bpNet[0] + (1 - l) * 0.18); return d > 0 && d < 0.5 ? Math.sin(Math.PI * d / 0.5) : 0; };
|
|
596
|
+
const wk = ph(TX.weights[0], TX.weights[1] - TX.weights[0]);
|
|
597
|
+
const jitN = t > TX.guess && t < TX.guess2 ? 1.6 : 0;
|
|
598
|
+
net(432, 300, 180, 230, 0.25 * Math.sin(t * 0.9), {
|
|
599
|
+
f: H.clamp((t - 6.28) / 0.5), wk, bp: bpT, nodeR: 11,
|
|
600
|
+
act: (l) => Math.max(actT(l, 7.1), actT(l, 9.42)),
|
|
601
|
+
nodeCol: (l) => H.mix(P31, AMBER, Math.min(1, bpT(Math.min(l, 1)) * 0.8)),
|
|
602
|
+
});
|
|
603
|
+
text('MODEL', 432, 444, 26, { align: 'c', f: H.clamp((t - 6.6) / 0.3) });
|
|
604
|
+
beam(arrowS(532, 300, 578, 300, 13), { f: ph(TX.arr2, 0.2), w: 2.2 });
|
|
605
|
+
if (pulse1 >= 0) { const p = pulse1 < 0.4 ? H.lerp(270, 320, pulse1 / 0.4) : pulse1 < 0.75 ? H.lerp(330, 530, (pulse1 - 0.4) / 0.35) : H.lerp(535, 580, (pulse1 - 0.75) / 0.25); dot(p, 300, 6, 1); }
|
|
606
|
+
// wrong guess: glitching, amber-shifted trace
|
|
607
|
+
const dogA = fade(t, TX.guess2 - 0.05, 0.3), wrong = H.clamp((t - TX.guess) / 0.25) * 0.85;
|
|
608
|
+
if (t >= TX.guess && dogA > 0) {
|
|
609
|
+
const gl = t < TX.guess2 ? 1 - 0.7 * H.clamp((t - TX.check) / 0.3) : 0, rr = H.rng(500 + frame); // detune settles once checked, so the crossed-out word stays readable
|
|
610
|
+
const tears = Array.from({ length: 8 }, () => (rr() < 0.25 ? (rr() - 0.5) * 12 : 0));
|
|
611
|
+
const xf = (p) => [p[0] + gl * (Math.sin(p[1] * 0.21 + t * 40) * 1.5 + tears[Math.floor(((p[1] - 270) / 70) * 8) & 7]), p[1] + gl * Math.sin(p[0] * 0.13 + t * 33) * 0.8];
|
|
612
|
+
const col = H.mix(P31, AMBER, wrong);
|
|
613
|
+
text('DOG?', 596, 270, 58, { f: H.clamp((t - TX.guess) / 0.4), col, xf, a: dogA, w: 4.2 });
|
|
614
|
+
// ghost copy: red-shifted, offset (misconverged beam)
|
|
615
|
+
text('DOG?', 598, 269, 58, { f: H.clamp((t - TX.guess) / 0.4), col: RED, xf, a: dogA * 0.15 * wrong, w: 2.5, dots: false, head: false, retrace: false });
|
|
616
|
+
// CHECK: cross it out
|
|
617
|
+
const xs = H.clamp((t - TX.cross) / 0.22), w4 = textW('DOG?', 58);
|
|
618
|
+
if (xs > 0) beam([[[586, 258], [606 + w4, 340]], [[606 + w4, 258], [586, 340]]], { f: xs, col: RED, a: dogA, w: 3.6 });
|
|
619
|
+
text("IT'S A CAT", 596 + textW('DOG?', 58) / 2, 360, 28, { align: 'c', f: H.clamp((t - TX.itsCat) / 0.35), col: AMBER, a: dogA * 0.95 });
|
|
620
|
+
// error signal travels back from the guess into the net
|
|
621
|
+
if (t > TX.back[0] && t < TX.back[1]) { const p = (t - TX.back[0]) / (TX.back[1] - TX.back[0]); dot(H.lerp(590, 532, p), 300, 7, 1, AMBER); }
|
|
622
|
+
}
|
|
623
|
+
// corrected guess, snapping clean
|
|
624
|
+
if (t >= TX.cat2) {
|
|
625
|
+
text('CAT', 600, 270, 58, { f: H.clamp((t - TX.cat2) / 0.3), w: 4.4 });
|
|
626
|
+
const cx0 = 600 + textW('CAT', 58) + 26; beam([[[cx0, 300], [cx0 + 18, 326], [cx0 + 60, 262]]], { f: H.clamp((t - TX.tick) / 0.18), w: 4.4 });
|
|
627
|
+
}
|
|
628
|
+
// loop row
|
|
629
|
+
const row = ['GUESS', 'CHECK', 'ADJUST'], cap = 30, gap = 18, arL = 46;
|
|
630
|
+
const wds = row.map((s) => textW(s, cap)), tot = wds.reduce((a, b) => a + b, 0) + 2 * (arL + gap * 2);
|
|
631
|
+
let x = CX - tot / 2; const xs = [];
|
|
632
|
+
const starts = [TX.guess, TX.check, TX.adjust, TX.guess2, TX.check2];
|
|
633
|
+
const stg = t < TX.check ? 0 : t < TX.adjust ? 1 : t < TX.guess2 ? 2 : t < TX.check2 ? 3 : 4, active = [0, 1, 2, 0, 1][stg];
|
|
634
|
+
const rowF = H.clamp((t - TX.row) / 0.6);
|
|
635
|
+
row.forEach((s, i) => {
|
|
636
|
+
xs.push(x + wds[i] / 2);
|
|
637
|
+
const on = t > 7.15 && i === active;
|
|
638
|
+
text(s, x, 548, cap, { f: H.clamp(rowF * 3 - i), a: on ? 1 : 0.5, col: on && i > 0 && t < TX.guess2 ? H.mix(P31, AMBER, 0.6) : P31 });
|
|
639
|
+
if (on) beam([[[x - 12, 536], [x + wds[i] + 12, 536], [x + wds[i] + 12, 590], [x - 12, 590], [x - 12, 536]]], { f: H.clamp((t - starts[stg]) / 0.15), a: 0.8, w: 2 });
|
|
640
|
+
x += wds[i];
|
|
641
|
+
if (i < 2) { beam(arrowS(x + gap, 563, x + gap + arL, 563, 11), { f: H.clamp(rowF * 3 - i - 0.5), a: 0.55 }); x += arL + gap * 2; }
|
|
642
|
+
});
|
|
643
|
+
// loop closes: return arc from ADJUST back to GUESS
|
|
644
|
+
const la = H.clamp((t - TX.loopArc) / 0.4);
|
|
645
|
+
if (la > 0) {
|
|
646
|
+
const pts = curve((u) => { const x0 = xs[2], x1 = xs[0]; const xx = H.lerp(x0, x1, u); return [xx, 600 + Math.sin(u * Math.PI) * 42]; }, 30);
|
|
647
|
+
beam([pts, [[xs[0] - 10, 612], [xs[0], 600], [xs[0] + 10, 612]]], { f: la, a: 0.7, w: 2.2 });
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
|
|
651
|
+
// ---- BEAT 4: timebase — loss decays, accuracy climbs
|
|
652
|
+
if (t >= TX.sqX[1] && t < TX.take[0]) {
|
|
653
|
+
const H3 = 'HOW DOES AI LEARN?';
|
|
654
|
+
if (t >= TX.sweep[0]) text(H3, 74, 56, 30, { w: 2.7, f: hdrRe(TX.sweep[0]) });
|
|
655
|
+
if (t < TX.sweep[0]) { beam([[[CX, GY + 20], [CX, GY + GH - 20]]], { w: 2.4 }); }
|
|
656
|
+
else {
|
|
657
|
+
const u = H.clamp((t - TX.sweep[0]) / (TX.sweep[1] - TX.sweep[0])), xe = GX + u * GW;
|
|
658
|
+
const ay = (v) => 450 - (acc(v) - 0.5) / 0.5 * 300;
|
|
659
|
+
const nzA = H.makeNoise(17);
|
|
660
|
+
const accPts = curve((v) => [GX + v * GW, ay(v) + nzA.n2(v * 40, 0.5) * 9 * (1 - v)], 160).filter((p) => p[0] <= xe + 0.1);
|
|
661
|
+
const lossPts = curve((v) => [GX + v * GW, 556 - 66 * Math.exp(-3 * v) * Math.cos(6.2832 * 7 * v) - 6 * nzA.n2(v * 60, 3) * Math.exp(-2.5 * v)], 320).filter((p) => p[0] <= xe + 0.1);
|
|
662
|
+
if (accPts.length > 1) beam([accPts], { w: 3, timed: true, head: false });
|
|
663
|
+
if (lossPts.length > 1) beam([lossPts], { w: 2.2, timed: true, head: false, a: 0.75 });
|
|
664
|
+
if (u < 1) { dot(xe, ay(u), 6, 1); dot(xe, 556 - 66 * Math.exp(-3 * u) * Math.cos(6.2832 * 7 * u), 5, 0.8); }
|
|
665
|
+
text('ACCURACY', 356, 150, 26, { f: H.clamp((t - TX.sweep[0] - 0.05) / 0.3) });
|
|
666
|
+
text('ERROR', 74, 446, 28, { f: H.clamp((t - TX.sweep[0] - 0.2) / 0.3), a: 0.8 });
|
|
667
|
+
text('TRAINING ROUNDS >', GX + GW - 20, 634, 28, { align: 'r', f: H.clamp((t - TX.sweep[0] - 0.4) / 0.4), a: 0.7 });
|
|
668
|
+
const v = Math.round(acc(u) * 100), hit = t >= TX.hit;
|
|
669
|
+
text(v + '%', 74, 116, 86, { f: H.clamp((t - TX.sweep[0]) / 0.25), w: hit ? 6 : 5 });
|
|
670
|
+
if (hit) {
|
|
671
|
+
const k = H.clamp((t - TX.hit) / 0.2), wN = textW('97%', 86);
|
|
672
|
+
beam([[[60, 104], [86 + wN, 104], [86 + wN, 216], [60, 216], [60, 104]]], { f: k, w: 2.8 });
|
|
673
|
+
dot(GX + GW, ay(1), 9, 1);
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
// ---- BEAT 5: takeaway
|
|
679
|
+
if (t >= TX.take[0]) {
|
|
680
|
+
const f = H.clamp((t - TX.take[0]) / (TX.take[1] - TX.take[0]));
|
|
681
|
+
text('MORE EXAMPLES >', CX, 216, 50, { align: 'c', f: H.clamp(f * 2), w: 3.2 });
|
|
682
|
+
text('BETTER GUESSES', CX, 316, 50, { align: 'c', f: H.clamp(f * 2 - 1), w: 3.2 });
|
|
683
|
+
const lf = ph(TX.liss[0], TX.liss[1]);
|
|
684
|
+
if (lf > 0) beam([curve((u) => { const a = u * 6.2832; return [CX + 120 * Math.sin(3 * a + 0.5 + t * 0.4), 510 + 60 * Math.sin(2 * a)]; }, 360)], { f: lf, w: 2.2, timed: true, a: 0.9 });
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
// ---- panel + beam state
|
|
688
|
+
st.xy = !(t >= TX.sqX[1] && t < TX.sqY[1]);
|
|
689
|
+
st.kn.tdiv = snapKnob(t, [[TX.sqX[1], 6], [TX.sqY[1], 0]], 0);
|
|
690
|
+
st.kn.ch1 = snapKnob(t, [[8.85, 4]], 3); st.kn.ch2 = snapKnob(t, [[9.0, 2]], 3);
|
|
691
|
+
const hitE = t > TX.hit ? Math.exp(-(t - TX.hit) / 0.35) : 0;
|
|
692
|
+
const snap = t > TX.tick ? Math.exp(-(t - TX.tick) / 0.2) : 0;
|
|
693
|
+
st.kn.inten = 0.55 + 0.3 * hitE;
|
|
694
|
+
st.bloom = 1 + 1.9 * hitE + 0.5 * snap + (sx < 0.05 && sy < 0.05 && t > 0.1 ? 0.8 : 0) + 0.4 * shud;
|
|
695
|
+
st.inten = (0.93 + 0.07 * jr()) * (1 + 0.35 * hitE);
|
|
696
|
+
return st;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// ================= SOUND =================
|
|
700
|
+
function oscSound(k) {
|
|
701
|
+
const D = EXD, T = TX;
|
|
702
|
+
const px = (x) => H.clamp(x / 900) * 1.2 - 0.6;
|
|
703
|
+
// the picture as sound: a beam drawing a figure = low filtered XY buzz (two detuned saws, like L/R channels)
|
|
704
|
+
const draw = (t, dur, f, p = 0, g = 1, minor = true) => {
|
|
705
|
+
const o = { type: 'sawtooth', dur, a: Math.min(0.05, dur * 0.25), hold: dur * 0.65, filter: 'lowpass', f2: [[0, 500], [dur, 1100]], q: 2, pan: p, minor };
|
|
706
|
+
k.tone(t, Object.assign({}, o, { f, gain: 0.038 * g }));
|
|
707
|
+
k.tone(t, Object.assign({}, o, { f: f * 1.5, gain: 0.019 * g, detune: 6, pan: -p }));
|
|
708
|
+
};
|
|
709
|
+
// relay / rotary switch: a sharp mechanical click + a little thunk
|
|
710
|
+
const relay = (t, g = 1, p = 0.7) => { k.noise(t, { dur: 0.02, filter: 'bandpass', f: 2200, q: 1.5, gain: 0.35 * g, a: 0.001, pan: p }); k.noise(t + 0.03, { dur: 0.015, filter: 'bandpass', f: 1500, q: 2, gain: 0.22 * g, a: 0.001, pan: p }); k.tone(t, { f: [[0, 180], [0.05, 90]], dur: 0.07, gain: 0.18 * g, pan: p }); };
|
|
711
|
+
const detent = (t, g = 1, p = 0.7) => k.noise(t, { dur: 0.012, filter: 'bandpass', f: 1800, q: 2, gain: 0.22 * g, a: 0.001, pan: p, minor: true });
|
|
712
|
+
// beam collapse: falling filtered sweep; expand: rising
|
|
713
|
+
const sweepTone = (t, dur, f0, f1, g = 1) => { k.tone(t, { f: [[0, f0], [dur, f1]], type: 'sawtooth', dur, gain: 0.06 * g, a: 0.01, hold: dur * 0.6, filter: 'lowpass', f2: 900, q: 3 }); k.noise(t, { dur, color: 'brown', filter: 'lowpass', f: [[0, f0 * 4], [dur, f1 * 4]], gain: 0.12 * g, a: 0.01 }); };
|
|
714
|
+
|
|
715
|
+
k.bed('hum', 0, D, { gain: 0.6 });
|
|
716
|
+
k.bed('room', 0, D, { gain: 0.5 });
|
|
717
|
+
k.noise(0, { dur: D, color: 'pink', filter: 'bandpass', f: 1400, q: 0.7, gain: 0.012, a: 0.3, hold: D - 0.6, bus: 'amb' }); // flyback / transformer hiss
|
|
718
|
+
if (!k.vo) k.tone(0, { f: 'A1', type: 'triangle', dur: D, gain: 0.02, a: 0.6, hold: D - 1.2, filter: 'lowpass', f2: 300, bus: 'amb' });
|
|
719
|
+
|
|
720
|
+
// ---- BEAT 1: power-on dot, title written by the beam
|
|
721
|
+
k.tone(0.12, { f: [[0, 60], [0.3, 110]], type: 'sine', dur: 0.4, gain: 0.14, a: 0.01 }); relay(0.1, 0.55, 0.6);
|
|
722
|
+
draw(T.title[0], 0.62, 'A2', 0, 1.1); draw(T.title[0] + 0.62, 0.6, 'E2', 0, 1.1);
|
|
723
|
+
draw(1.4, 0.45, 'A2', 0, 0.7); // the sine underline
|
|
724
|
+
k.noise(T.shrink[0], { dur: 0.55, color: 'brown', filter: 'lowpass', f: [[0, 500], [0.55, 160]], gain: 0.1, a: 0.2, pan: -0.4 }); // shrink into the corner
|
|
725
|
+
// ---- BEAT 2: net draws, conveyor arrow, examples stream in
|
|
726
|
+
draw(T.net2[0], T.net2[1], 'D2', px(690), 0.9);
|
|
727
|
+
draw(T.conv[0], T.conv[1], 'A2', px(300), 0.6);
|
|
728
|
+
draw(T.model2[0], T.model2[1], 'E2', px(690), 0.6);
|
|
729
|
+
k.withMinor(() => {
|
|
730
|
+
for (let i = 0; i < 6; i++) { const ta = T.itemsT0 + i * T.itemsDT + (590 - 30) / T.itemSpeed; if (ta > T.col1 - 0.1) break; k.tone(ta - (ta > 4.6 ? 0.25 : 0), { f: [[0, 'A3'], [0.12, 'E3']], type: 'triangle', dur: 0.16, gain: 0.07, filter: 'lowpass', f2: 900, pan: px(600) }); }
|
|
731
|
+
// counter: ticks thinning out as the number accelerates (≤12/s)
|
|
732
|
+
let last = -1; for (let f = 0; f < (T.cnt[1] - T.cnt[0]) * 60; f++) { const t = T.cnt[0] + f / 60; if (t - last >= 1 / 11) { k.noise(t, { dur: 0.012, filter: 'bandpass', f: 1300, q: 3, gain: 0.1, a: 0.001, pan: 0 }); last = t; } }
|
|
733
|
+
});
|
|
734
|
+
k.tone(T.cnt[1], { f: 'E3', type: 'triangle', dur: 0.4, gain: 0.09, filter: 'lowpass', f2: 1200 }); k.tone(T.cnt[1], { f: 'A3', type: 'triangle', dur: 0.4, gain: 0.07, filter: 'lowpass', f2: 1200 });
|
|
735
|
+
// ---- transition 1: collapse to a dot, relay, re-expand
|
|
736
|
+
sweepTone(T.col1, T.dot1 - T.col1, 220, 55, 1);
|
|
737
|
+
relay(T.dot1 + 0.05, 1.2);
|
|
738
|
+
sweepTone(T.exp1, 0.35, 55, 200, 0.8);
|
|
739
|
+
// ---- BEAT 3
|
|
740
|
+
draw(T.cat3, 0.55, 'A2', px(158), 1);
|
|
741
|
+
draw(T.row, 0.6, 'E2', 0, 0.6);
|
|
742
|
+
k.withMinor(() => { k.tone(T.arr1, { f: 'E3', type: 'triangle', dur: 0.15, gain: 0.06, filter: 'lowpass', f2: 900 }); k.tone(T.arr2, { f: 'A3', type: 'triangle', dur: 0.15, gain: 0.06, filter: 'lowpass', f2: 900 }); });
|
|
743
|
+
// wrong guess: DOG? written by a detuned, wobbling beam + soft static burst
|
|
744
|
+
k.tone(T.guess, { f: [[0, 98], [0.15, 92], [0.3, 101], [0.45, 90]], type: 'sawtooth', dur: 0.5, gain: 0.08, hold: 0.3, filter: 'lowpass', f2: 900, q: 4, pan: px(680) });
|
|
745
|
+
k.tone(T.guess, { f: [[0, 104], [0.2, 96], [0.45, 107]], type: 'sawtooth', dur: 0.5, gain: 0.07, hold: 0.3, filter: 'lowpass', f2: 900, q: 4, pan: px(680) });
|
|
746
|
+
k.noise(T.guess, { dur: 0.35, color: 'pink', filter: 'bandpass', f: 1200, q: 0.8, gain: 0.08, a: 0.02, pan: px(680) });
|
|
747
|
+
// CHECK: the cross + shudder — dissonant low buzz and a noise hit
|
|
748
|
+
k.play('buzz', T.check, { f: 73, dur: 0.45, gain: 0.7, pan: px(680) });
|
|
749
|
+
k.noise(T.check, { dur: 0.3, color: 'pink', filter: 'lowpass', f: [[0, 2000], [0.3, 400]], gain: 0.16, a: 0.002, pan: px(680) });
|
|
750
|
+
k.tone(T.check, { f: [[0, 'Bb2'], [0.5, 'A2']], type: 'square', dur: 0.5, gain: 0.04, hold: 0.3, filter: 'lowpass', f2: 700 });
|
|
751
|
+
draw(T.itsCat, 0.35, 'G2', px(680), 0.5);
|
|
752
|
+
// ADJUST: error dot runs back (falling), weights retune (knob detents + gliding tones)
|
|
753
|
+
k.tone(T.back[0], { f: [[0, 330], [0.25, 165]], type: 'triangle', dur: 0.3, gain: 0.08, filter: 'lowpass', f2: 1200, pan: px(560) });
|
|
754
|
+
k.tone(T.bpNet[0], { f: [[0, 110], [0.4, 147]], type: 'sawtooth', dur: 0.45, gain: 0.05, hold: 0.25, filter: 'lowpass', f2: 700, pan: px(430) });
|
|
755
|
+
k.tone(T.bpNet[0] + 0.1, { f: [[0, 165], [0.4, 220]], type: 'sawtooth', dur: 0.45, gain: 0.035, hold: 0.25, filter: 'lowpass', f2: 700, pan: px(430) });
|
|
756
|
+
for (let i = 0; i < 3; i++) { detent(8.85 + i * 0.04, 0.9, 0.55); detent(9.0 + i * 0.04, 0.9, 0.85); }
|
|
757
|
+
draw(T.loopArc, 0.4, 'A2', 0, 0.5);
|
|
758
|
+
// GUESS again: forward pulse, then CAT written clean — the correction resolves
|
|
759
|
+
k.tone(T.fwd[0], { f: [[0, 'E3'], [0.3, 'A3']], type: 'triangle', dur: 0.32, gain: 0.07, filter: 'lowpass', f2: 1000, pan: px(400) });
|
|
760
|
+
draw(T.cat2, 0.3, 'A2', px(680), 0.8, false);
|
|
761
|
+
['A3', 'C#4', 'E4'].forEach((n, i) => k.tone(T.tick + i * 0.03, { f: n, type: 'triangle', dur: 0.7, gain: 0.07, a: 0.004, hold: 0.1, verb: 0.2, pan: px(760) }));
|
|
762
|
+
k.tone(T.tick, { f: 'A2', type: 'sine', dur: 0.6, gain: 0.12 });
|
|
763
|
+
// ---- transition 2: X gain to zero (vertical line), relay to timebase
|
|
764
|
+
sweepTone(T.sqX[0], T.sqX[1] - T.sqX[0], 200, 70, 0.9);
|
|
765
|
+
relay(T.sqX[1], 1.2); detent(T.sqX[1] + 0.05, 1); detent(T.sqX[1] + 0.09, 1);
|
|
766
|
+
// ---- BEAT 4: sweep draws both traces; tone rises with accuracy, ticks per % (≤12/s)
|
|
767
|
+
const SW = T.sweep[1] - T.sweep[0];
|
|
768
|
+
k.tone(T.sweep[0], { f: [[0, 82], [SW * 0.3, 98], [SW, 110]], type: 'sawtooth', dur: SW, gain: 0.04, a: 0.05, hold: SW * 0.85, filter: 'lowpass', f2: [[0, 400], [SW, 1200]], q: 2 });
|
|
769
|
+
k.tone(T.sweep[0], { f: [[0, 55], [SW, 41]], type: 'triangle', dur: SW, gain: 0.06, a: 0.05, hold: SW * 0.5, filter: 'lowpass', f2: 300 }); // error trace: dying low wobble
|
|
770
|
+
k.withMinor(() => { let lastV = 61, lastT = -1; for (let f = 0; f < SW * 60; f++) { const t = T.sweep[0] + f / 60, v = Math.round(acc(f / 60 / SW) * 100); if (v > lastV && t - lastT >= 1 / 12) { k.noise(t, { dur: 0.012, filter: 'bandpass', f: 1500, q: 3, gain: 0.1, a: 0.001, pan: -0.5 }); lastV = v; lastT = t; } } });
|
|
771
|
+
// 97%: beam flares — the biggest hit: deep thump + a clean, open A chord
|
|
772
|
+
k.play('thud', T.hit, { f: 95, gain: 1.15 });
|
|
773
|
+
['A2', 'E3', 'A3', 'C#4', 'E4'].forEach((n, i) => k.tone(T.hit, { f: n, type: i < 2 ? 'sawtooth' : 'triangle', dur: 1.1, gain: i < 2 ? 0.065 : 0.09, a: 0.006, hold: 0.3, filter: 'lowpass', f2: 1600, verb: 0.25, pan: (i - 2) * 0.15 }));
|
|
774
|
+
k.noise(T.hit, { dur: 0.6, color: 'pink', filter: 'lowpass', f: [[0, 2200], [0.6, 300]], gain: 0.17, a: 0.004, verb: 0.3 });
|
|
775
|
+
// ---- transition 3: Y gain to zero, relay back to X-Y
|
|
776
|
+
sweepTone(T.sqY[0], T.sqY[1] - T.sqY[0], 180, 70, 0.8);
|
|
777
|
+
relay(T.sqY[1], 1.2); detent(T.sqY[1] + 0.04, 1);
|
|
778
|
+
// ---- BEAT 5: takeaway written, lands on a resolved chord
|
|
779
|
+
draw(T.take[0], 0.4, 'A2', 0, 0.9); draw(T.take[0] + 0.4, 0.4, 'E2', 0, 0.9);
|
|
780
|
+
draw(T.liss[0], T.liss[1], 'A2', 0, 0.6);
|
|
781
|
+
['A2', 'E3', 'A3', 'E4'].forEach((n, i) => k.tone(T.take[1], { f: n, type: 'triangle', dur: 1.0, gain: 0.07, a: 0.01, hold: 0.25, verb: 0.3, pan: (i - 1.5) * 0.2 }));
|
|
782
|
+
k.tone(T.take[1], { f: 'A1', type: 'sine', dur: 0.95, gain: 0.14, a: 0.01 });
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
registerStyle({
|
|
786
|
+
id: 'oscilloscope',
|
|
787
|
+
name: 'Oscilloscope Vector Art',
|
|
788
|
+
category: 'Retro Digital',
|
|
789
|
+
description: 'Oscilloscope-music XY art on an analogue lab scope: one P31 green beam draws everything, bright where it slows at turns and dim on fast retrace lines, with bloom and halation behind an internal 8x10 graticule, framed by a moulded CRT bezel and a knobbed instrument front panel.',
|
|
790
|
+
motion: 'Power-on: the dot blooms, the timebase pulls it into a sine sweep, the X-Y button snaps in and the waveform folds into a circle that winds itself into a spinning spiral-sphere while the beam writes the vector text.',
|
|
791
|
+
fonts: ['600 12px "Inter"', '700 12px "Inter"', '800 12px "Inter"', '700 30px "Space Grotesk"'],
|
|
792
|
+
duration: 5,
|
|
793
|
+
frameT: 4.7,
|
|
794
|
+
readsPixels: false,
|
|
795
|
+
explainerDuration: EXD,
|
|
796
|
+
explainerNote: 'Everything is drawn by one scope beam: transitions are the instrument\'s own (picture collapses to a dot and re-expands, X gain to zero then the timebase sweep, Y gain to zero back to X-Y), arrows and Hershey-style labels are beam strokes, counters are redrawn vector numerals, and the wrong guess shudders amber while the 97% flares the bloom.',
|
|
797
|
+
draw(ctx, W, H_, t) { drawStill(ctx, W, H_, t); },
|
|
798
|
+
explainer(ctx, W, H_, t) { resetBeam(); const st = exScene(t); renderFrame(ctx, W, H_, st); },
|
|
799
|
+
explainerSound(k) { oscSound(k); },
|
|
800
|
+
});
|
|
801
|
+
}
|