@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,991 @@
|
|
|
1
|
+
{
|
|
2
|
+
// ---------- isometric helpers ----------
|
|
3
|
+
const C30 = Math.cos(Math.PI / 6);
|
|
4
|
+
const makeIso = (ox, oy, s) => (x, y, z = 0) => [ox + (x - y) * C30 * s, oy + (x + y) * 0.5 * s - z * s];
|
|
5
|
+
const SHADE_DARK = [92, 88, 150];
|
|
6
|
+
const shade = (base, k = 1) => ({
|
|
7
|
+
top: H.rgba(H.mix(base, '#ffffff', 0.42 * k)),
|
|
8
|
+
left: H.rgba(H.mix(base, '#ffffff', 0.08)),
|
|
9
|
+
right: H.rgba(H.mix(base, SHADE_DARK, 0.28 * k)),
|
|
10
|
+
edge: H.rgba(H.mix(base, '#ffffff', 0.75), 0.9),
|
|
11
|
+
});
|
|
12
|
+
function poly(ctx, pts, fill) {
|
|
13
|
+
ctx.beginPath(); ctx.moveTo(pts[0][0], pts[0][1]);
|
|
14
|
+
for (let i = 1; i < pts.length; i++) ctx.lineTo(pts[i][0], pts[i][1]);
|
|
15
|
+
ctx.closePath(); if (fill) { ctx.fillStyle = fill; ctx.fill(); }
|
|
16
|
+
}
|
|
17
|
+
function box(ctx, P, x, y, z, w, d, h, c, edges = true) {
|
|
18
|
+
const px = P(x, y, 0)[1] - P(x, y, 1)[1], hp = h * px; // pixels per unit, face height in px
|
|
19
|
+
// ambient-occlusion contact strip where a box stands on the floor (faces drawn next cover its inner half)
|
|
20
|
+
if (Math.abs(z) < 0.01 && hp > 6) {
|
|
21
|
+
const a = P(x, y + d, 0), b = P(x + w, y + d, 0), e = P(x + w, y, 0);
|
|
22
|
+
ctx.save(); ctx.lineCap = 'butt'; ctx.lineJoin = 'round';
|
|
23
|
+
for (const [lw, al] of [[Math.min(12, px * 0.3), 0.07], [Math.min(6, px * 0.15), 0.1], [Math.min(3, px * 0.07), 0.14]]) {
|
|
24
|
+
ctx.strokeStyle = `rgba(60,50,110,${al})`; ctx.lineWidth = lw;
|
|
25
|
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(e[0], e[1]); ctx.stroke();
|
|
26
|
+
}
|
|
27
|
+
ctx.restore();
|
|
28
|
+
}
|
|
29
|
+
poly(ctx, [P(x + w, y, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x + w, y, z + h)], c.right);
|
|
30
|
+
poly(ctx, [P(x, y + d, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x, y + d, z + h)], c.left);
|
|
31
|
+
// tonal falloff on the side faces: a touch lighter at the top edge, darker toward the floor
|
|
32
|
+
if (hp > 10) {
|
|
33
|
+
const t0 = P(x + w, y + d, z + h), b0 = P(x + w, y + d, z);
|
|
34
|
+
const g = ctx.createLinearGradient(0, t0[1], 0, b0[1]);
|
|
35
|
+
g.addColorStop(0, 'rgba(255,255,255,0.13)'); g.addColorStop(0.4, 'rgba(255,255,255,0)'); g.addColorStop(1, 'rgba(60,50,110,0.10)');
|
|
36
|
+
ctx.beginPath();
|
|
37
|
+
const f1 = [P(x + w, y, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x + w, y, z + h)], f2 = [P(x, y + d, z), P(x + w, y + d, z), P(x + w, y + d, z + h), P(x, y + d, z + h)];
|
|
38
|
+
for (const f of [f1, f2]) { ctx.moveTo(f[0][0], f[0][1]); for (let i = 1; i < 4; i++) ctx.lineTo(f[i][0], f[i][1]); ctx.closePath(); }
|
|
39
|
+
ctx.fillStyle = g; ctx.fill();
|
|
40
|
+
}
|
|
41
|
+
poly(ctx, [P(x, y, z + h), P(x + w, y, z + h), P(x + w, y + d, z + h), P(x, y + d, z + h)], c.top);
|
|
42
|
+
if (edges) {
|
|
43
|
+
ctx.strokeStyle = c.edge; ctx.lineWidth = 1; ctx.lineJoin = 'round';
|
|
44
|
+
ctx.beginPath();
|
|
45
|
+
const a = P(x, y + d, z + h), b = P(x + w, y + d, z + h), e = P(x + w, y, z + h), f = P(x + w, y + d, z);
|
|
46
|
+
ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(e[0], e[1]);
|
|
47
|
+
ctx.moveTo(b[0], b[1]); ctx.lineTo(f[0], f[1]);
|
|
48
|
+
ctx.stroke();
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
// convex hull (monotone chain) of 2D world points
|
|
52
|
+
function hull(pts) {
|
|
53
|
+
pts = pts.slice().sort((a, b) => a[0] - b[0] || a[1] - b[1]);
|
|
54
|
+
const cr = (o, a, b) => (a[0] - o[0]) * (b[1] - o[1]) - (a[1] - o[1]) * (b[0] - o[0]);
|
|
55
|
+
const lo = [], up = [];
|
|
56
|
+
for (const p of pts) { while (lo.length >= 2 && cr(lo[lo.length - 2], lo[lo.length - 1], p) <= 0) lo.pop(); lo.push(p); }
|
|
57
|
+
for (let i = pts.length - 1; i >= 0; i--) { const p = pts[i]; while (up.length >= 2 && cr(up[up.length - 2], up[up.length - 1], p) <= 0) up.pop(); up.push(p); }
|
|
58
|
+
up.pop(); lo.pop(); return lo.concat(up);
|
|
59
|
+
}
|
|
60
|
+
function cylinder(ctx, P, s, cx, cy, z, r, h, base) {
|
|
61
|
+
const b = P(cx, cy, z), tp = P(cx, cy, z + h);
|
|
62
|
+
const rx = 1.2247 * r * s, ry = 0.7071 * r * s;
|
|
63
|
+
const g = ctx.createLinearGradient(b[0] - rx, 0, b[0] + rx, 0);
|
|
64
|
+
g.addColorStop(0, H.rgba(H.mix(base, '#ffffff', 0.15)));
|
|
65
|
+
g.addColorStop(0.35, H.rgba(H.mix(base, '#ffffff', 0.3)));
|
|
66
|
+
g.addColorStop(0.55, H.rgba(base));
|
|
67
|
+
g.addColorStop(1, H.rgba(H.mix(base, SHADE_DARK, 0.32)));
|
|
68
|
+
ctx.fillStyle = g;
|
|
69
|
+
ctx.beginPath(); ctx.ellipse(b[0], b[1], rx, ry, 0, 0, Math.PI); ctx.lineTo(tp[0] - rx, tp[1]);
|
|
70
|
+
ctx.ellipse(tp[0], tp[1], rx, ry, 0, Math.PI, 0, true); ctx.closePath(); ctx.fill();
|
|
71
|
+
ctx.fillStyle = H.rgba(H.mix(base, '#ffffff', 0.5));
|
|
72
|
+
ctx.beginPath(); ctx.ellipse(tp[0], tp[1], rx, ry, 0, 0, Math.PI * 2); ctx.fill();
|
|
73
|
+
return { b, tp, rx, ry };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ================= EXPLAINER: "How does AI learn?" as one long isometric factory the camera travels along =================
|
|
77
|
+
function petFace(c, kind, x, y, r, col, ink) {
|
|
78
|
+
c.save(); c.translate(x, y);
|
|
79
|
+
c.fillStyle = col;
|
|
80
|
+
if (kind === 'cat') {
|
|
81
|
+
for (const sx of [-1, 1]) { c.beginPath(); c.moveTo(sx * 0.85 * r, -0.2 * r); c.lineTo(sx * 0.62 * r, -1.15 * r); c.lineTo(sx * 0.12 * r, -0.78 * r); c.closePath(); c.fill(); }
|
|
82
|
+
} else {
|
|
83
|
+
c.fillStyle = H.rgba(H.mix(col, ink, 0.35));
|
|
84
|
+
for (const sx of [-1, 1]) { c.beginPath(); c.ellipse(sx * 0.88 * r, 0.05 * r, 0.32 * r, 0.62 * r, sx * 0.35, 0, 7); c.fill(); }
|
|
85
|
+
c.fillStyle = col;
|
|
86
|
+
}
|
|
87
|
+
c.beginPath(); c.arc(0, 0, r, 0, 7); c.fill();
|
|
88
|
+
c.fillStyle = ink;
|
|
89
|
+
for (const sx of [-1, 1]) { c.beginPath(); c.arc(sx * 0.36 * r, -0.12 * r, r * 0.12, 0, 7); c.fill(); }
|
|
90
|
+
if (kind === 'cat') {
|
|
91
|
+
c.beginPath(); c.moveTo(-0.1 * r, 0.18 * r); c.lineTo(0.1 * r, 0.18 * r); c.lineTo(0, 0.3 * r); c.closePath(); c.fill();
|
|
92
|
+
c.strokeStyle = ink; c.lineWidth = Math.max(0.6, r * 0.05);
|
|
93
|
+
c.beginPath(); for (const sx of [-1, 1]) for (const dy of [0.22, 0.36]) { c.moveTo(sx * 0.3 * r, dy * r); c.lineTo(sx * 0.85 * r, (dy - 0.06) * r); } c.stroke();
|
|
94
|
+
} else {
|
|
95
|
+
c.beginPath(); c.ellipse(0, 0.32 * r, 0.2 * r, 0.14 * r, 0, 0, 7); c.fill();
|
|
96
|
+
}
|
|
97
|
+
c.restore();
|
|
98
|
+
}
|
|
99
|
+
const wallX = (ctx, p) => ctx.transform(C30, 0.5, 0, 1, p[0], p[1]); // plane facing +y (lower-left), px units
|
|
100
|
+
const floorX = (ctx, p) => ctx.transform(C30, 0.5, -C30, 0.5, p[0], p[1]); // floor plane, px units
|
|
101
|
+
function tag(ctx, x, y, text, o = {}) {
|
|
102
|
+
ctx.save();
|
|
103
|
+
ctx.font = o.font || '600 13px "Inter"';
|
|
104
|
+
const fsz = parseInt((ctx.font.match(/(\d+)px/) || [0, 14])[1]);
|
|
105
|
+
const w = ctx.measureText(text).width + (o.icon ? fsz * 2.4 : fsz * 1.4), h = o.h || Math.round(fsz * 1.75);
|
|
106
|
+
const k = o.k ?? 1;
|
|
107
|
+
ctx.translate(x, y); ctx.scale(k, k);
|
|
108
|
+
ctx.shadowColor = 'rgba(80,70,140,0.2)'; ctx.shadowBlur = 14; ctx.shadowOffsetY = 4;
|
|
109
|
+
ctx.fillStyle = o.bg || '#ffffff'; ctx.beginPath(); ctx.roundRect(-w / 2, -h / 2, w, h, h / 2); ctx.fill();
|
|
110
|
+
ctx.shadowColor = 'transparent';
|
|
111
|
+
if (o.icon) { ctx.fillStyle = o.iconCol || '#ff6b5b'; ctx.beginPath(); ctx.arc(-w / 2 + fsz * 0.9, 0, fsz * 0.3, 0, 7); ctx.fill(); }
|
|
112
|
+
ctx.fillStyle = o.col || '#2d3058'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle';
|
|
113
|
+
ctx.fillText(text, o.icon ? fsz * 0.5 : 0, 1);
|
|
114
|
+
ctx.restore();
|
|
115
|
+
}
|
|
116
|
+
function numberTag(ctx, x, y, num, label, o = {}) {
|
|
117
|
+
const k = o.k ?? 1; if (k <= 0.01) return;
|
|
118
|
+
ctx.save(); ctx.translate(x, y); ctx.scale(k, k);
|
|
119
|
+
ctx.font = '800 66px "Inter"'; const nw = ctx.measureText(num).width;
|
|
120
|
+
ctx.font = '600 24px "Inter"'; const lw = ctx.measureText(label).width;
|
|
121
|
+
const w = Math.max(nw, lw) + 56, h = 118;
|
|
122
|
+
ctx.shadowColor = 'rgba(80,70,140,0.25)'; ctx.shadowBlur = 22; ctx.shadowOffsetY = 6;
|
|
123
|
+
ctx.fillStyle = o.bg || '#ffffff'; ctx.beginPath(); ctx.roundRect(-w / 2, -h / 2, w, h, 26); ctx.fill(); ctx.shadowColor = 'transparent';
|
|
124
|
+
ctx.textAlign = 'center'; ctx.textBaseline = 'alphabetic';
|
|
125
|
+
ctx.fillStyle = o.sub || '#7b7aa3'; ctx.fillText(label, 0, -h / 2 + 34);
|
|
126
|
+
ctx.font = '800 66px "Inter"'; ctx.fillStyle = o.col || '#2d3058'; ctx.fillText(num, 0, h / 2 - 18);
|
|
127
|
+
ctx.restore();
|
|
128
|
+
}
|
|
129
|
+
function arrow(ctx, pts, prog, col, dash, w = 2) {
|
|
130
|
+
// pts: quadratic [a, ctrl, b] in screen space, drawn on up to prog
|
|
131
|
+
if (prog <= 0) return;
|
|
132
|
+
const [a, c, b] = pts;
|
|
133
|
+
const q = (u) => [(1 - u) * (1 - u) * a[0] + 2 * u * (1 - u) * c[0] + u * u * b[0], (1 - u) * (1 - u) * a[1] + 2 * u * (1 - u) * c[1] + u * u * b[1]];
|
|
134
|
+
ctx.save(); ctx.strokeStyle = col; ctx.lineWidth = w; ctx.lineCap = 'round'; if (dash) ctx.setLineDash(dash);
|
|
135
|
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); const n = 30; for (let i = 1; i <= n; i++) { const p = q(prog * i / n); ctx.lineTo(p[0], p[1]); } ctx.stroke();
|
|
136
|
+
ctx.setLineDash([]);
|
|
137
|
+
const e = q(prog), e0 = q(Math.max(0, prog - 0.04)), ang = Math.atan2(e[1] - e0[1], e[0] - e0[0]);
|
|
138
|
+
ctx.fillStyle = col; ctx.translate(e[0], e[1]); ctx.rotate(ang);
|
|
139
|
+
ctx.beginPath(); ctx.moveTo(4, 0); ctx.lineTo(-7, -5.5); ctx.lineTo(-7, 5.5); ctx.closePath(); ctx.fill();
|
|
140
|
+
ctx.restore();
|
|
141
|
+
return q;
|
|
142
|
+
}
|
|
143
|
+
function isoExplainer(ctx, W, H_, t) {
|
|
144
|
+
const ph = (a, b) => H.clamp((t - a) / (b - a));
|
|
145
|
+
const E = (a, b) => H.ease.inOut(ph(a, b));
|
|
146
|
+
const EO = (a, b) => H.ease.out(ph(a, b));
|
|
147
|
+
const ACC = '#ff6b5b', MINT = '#3ccf9a', INK = '#2d3058';
|
|
148
|
+
// ---------- camera keyframes: [t, fx, fy, scale] ----------
|
|
149
|
+
const KF = [[0, 11, 4, 0.5, 24], [1.6, 11, 4, 0.5, 26], [2.8, 4.6, 3.4, 2.0, 46], [5.9, 5.6, 3.6, 1.9, 46], [6.9, 15.3, 5.0, 1.8, 54], [10.3, 15.3, 5.0, 1.8, 54], [11.1, 22.9, 3.6, 2.9, 68], [12.9, 22.9, 3.6, 2.9, 68], [13.8, 13, 4, 0.8, 27], [20, 13, 4, 0.8, 27]];
|
|
150
|
+
let k = 0; while (k < KF.length - 2 && t > KF[k + 1][0]) k++;
|
|
151
|
+
const [t0, fx0, fy0, fz0, s0] = KF[k], [t1, fx1, fy1, fz1, s1] = KF[k + 1];
|
|
152
|
+
const u = H.ease.inOut(H.clamp((t - t0) / (t1 - t0)));
|
|
153
|
+
const fx = H.lerp(fx0, fx1, u), fy = H.lerp(fy0, fy1, u), fz = H.lerp(fz0, fz1, u), s = H.lerp(s0, s1, u);
|
|
154
|
+
const bob = Math.sin(t * 1.2) * 3;
|
|
155
|
+
const P = (x, y, z = 0) => [640 + ((x - fx) - (y - fy)) * C30 * s, 390 + bob + ((x - fx) + (y - fy)) * 0.5 * s - (z - fz) * s];
|
|
156
|
+
const LX = 26, LY = 8, TH = 0.9;
|
|
157
|
+
|
|
158
|
+
// ---------- background ----------
|
|
159
|
+
const bg = ctx.createLinearGradient(0, 0, W, H_);
|
|
160
|
+
bg.addColorStop(0, '#f6efe8'); bg.addColorStop(0.55, '#efeaf4'); bg.addColorStop(1, '#e4e3f3');
|
|
161
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
162
|
+
ctx.fillStyle = 'rgba(120,110,170,0.10)';
|
|
163
|
+
for (let y = 18; y < H_; y += 28) for (let x = 18 + ((y / 28) % 2) * 14; x < W; x += 28) ctx.fillRect(x, y, 2, 2);
|
|
164
|
+
{ const glow = ctx.createRadialGradient(660, 380, 40, 660, 380, 620); glow.addColorStop(0, 'rgba(255,255,255,0.7)'); glow.addColorStop(1, 'rgba(255,255,255,0)'); ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H_); }
|
|
165
|
+
// long soft shadow of the slab
|
|
166
|
+
{
|
|
167
|
+
const L = H.layer(W, H_), lc = L.getContext('2d');
|
|
168
|
+
lc.filter = `blur(${Math.round(s * 0.5)}px)`;
|
|
169
|
+
const G = (x, y) => { const p = P(x + 2.2, y, -TH); return [p[0], p[1] + s * 3.2]; };
|
|
170
|
+
poly(lc, [G(0, 0), G(LX, 0), G(LX, LY), G(0, LY)], 'rgb(90,80,150)');
|
|
171
|
+
ctx.globalAlpha = 0.14; ctx.drawImage(L, 0, 0); ctx.globalAlpha = 1;
|
|
172
|
+
}
|
|
173
|
+
// slab
|
|
174
|
+
box(ctx, P, 0.3, 0.3, -TH - 0.6, LX - 0.6, LY - 0.6, 0.6, shade('#b9addf'), false);
|
|
175
|
+
box(ctx, P, 0, 0, -TH, LX, LY, TH, shade('#cfc6ea'), false);
|
|
176
|
+
poly(ctx, [P(0, 0), P(LX, 0), P(LX, LY), P(0, LY)], '#fbf8f3');
|
|
177
|
+
ctx.strokeStyle = 'rgba(170,160,205,0.2)'; ctx.lineWidth = 1; ctx.beginPath();
|
|
178
|
+
for (let i = 1; i < LX; i++) { const a = P(i, 0), b = P(i, LY); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
|
|
179
|
+
for (let i = 1; i < LY; i++) { const a = P(0, i), b = P(LX, i); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
|
|
180
|
+
ctx.stroke();
|
|
181
|
+
// zone tints + painted floor labels
|
|
182
|
+
poly(ctx, [P(0.3, 0.3), P(9.6, 0.3), P(9.6, 3.6), P(0.3, 3.6)], 'rgba(255,214,196,0.35)');
|
|
183
|
+
poly(ctx, [P(19.2, 0.6), P(25.7, 0.6), P(25.7, 6.6), P(19.2, 6.6)], 'rgba(205,240,224,0.45)');
|
|
184
|
+
const floorText = (x, y, str, a = 1, size = 22) => { const o = P(x, y); ctx.save(); floorX(ctx, o); ctx.scale(s / 35, s / 35); ctx.globalAlpha = a; ctx.font = `800 ${size}px "Inter"`; ctx.fillStyle = 'rgba(150,140,200,0.55)'; ctx.fillText(str, 0, 0); ctx.restore(); };
|
|
185
|
+
floorText(0.9, 7.6, 'TRAINING DATA', 1, 20);
|
|
186
|
+
floorText(19.6, 7.4, 'TRAINING ROUNDS →', EO(10.6, 11.4), 20);
|
|
187
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.95)'; ctx.lineWidth = 1.5;
|
|
188
|
+
ctx.beginPath(); { const a = P(0, LY), b = P(LX, LY), c = P(LX, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(c[0], c[1]); } ctx.stroke();
|
|
189
|
+
|
|
190
|
+
// ---------- scene objects (painter-sorted) ----------
|
|
191
|
+
const objs = [], shadows = [];
|
|
192
|
+
const add = (key, fn) => objs.push({ key, fn });
|
|
193
|
+
const R = H.rng(31);
|
|
194
|
+
// data stacks at the back: grow while the counter runs
|
|
195
|
+
const cnt = Math.floor(1e6 * Math.pow(EO(2.8, 5.7), 1.6));
|
|
196
|
+
const grow = 0.25 + 2.2 * Math.pow(EO(2.6, 5.8), 1.2);
|
|
197
|
+
[[1.0, 1.0], [3.2, 1.0], [5.4, 1.0], [7.6, 1.0]].forEach(([x, y], i) => {
|
|
198
|
+
const h = Math.max(0.15, grow * (0.8 + 0.25 * Math.sin(i * 2.3)));
|
|
199
|
+
shadows.push([x, y, 1.3, 1.6, h]);
|
|
200
|
+
add(x + 0.65 + y + 0.8, () => {
|
|
201
|
+
const n = Math.max(1, Math.round(h / 0.16));
|
|
202
|
+
for (let j = 0; j < n; j++) box(ctx, P, x + Math.sin(j * 1.7 + i) * 0.04, y, j * 0.16, 1.3, 1.6, 0.14, shade(j % 2 ? '#ffffff' : (i % 2 ? '#ffd9cf' : '#dbe3f6')), false);
|
|
203
|
+
// top card shows a pet face in the floor plane
|
|
204
|
+
const o = P(x + 0.65, y + 0.8, n * 0.16);
|
|
205
|
+
ctx.save(); floorX(ctx, o); ctx.scale(s / 40, s / 40); petFace(ctx, i % 2 ? 'dog' : 'cat', 0, 0, 13, i % 2 ? '#f2c39a' : '#c8cde6', INK); ctx.restore();
|
|
206
|
+
});
|
|
207
|
+
});
|
|
208
|
+
// billboard counter over the data zone
|
|
209
|
+
add(4.4 + 0.4, () => {
|
|
210
|
+
for (const x of [1.4, 7.6]) box(ctx, P, x, 0.35, 0, 0.18, 0.18, 3.0, shade('#d8d4ea'), false);
|
|
211
|
+
box(ctx, P, 1.0, 0.25, 3.0, 7.2, 0.25, 1.15, shade('#ffffff'));
|
|
212
|
+
const o = P(1.0, 0.5, 4.15);
|
|
213
|
+
ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
|
|
214
|
+
ctx.font = '800 32px "Inter"'; ctx.fillStyle = INK; ctx.fillText('DATASET', 60, 36);
|
|
215
|
+
ctx.restore();
|
|
216
|
+
});
|
|
217
|
+
// conveyor belt (segmented for correct depth)
|
|
218
|
+
for (let i = 0; i < 10; i++) {
|
|
219
|
+
add(i + 0.5 + 5.5, () => {
|
|
220
|
+
box(ctx, P, i, 5.0, 0, 1, 1.0, 0.4, shade('#8e97c9'), false);
|
|
221
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.35)'; ctx.lineWidth = 1;
|
|
222
|
+
const off = ((t * 2.2) % 0.5);
|
|
223
|
+
for (let k2 = 0; k2 < 2; k2++) { const xx = i + k2 * 0.5 + off; if (xx > i + 1) continue; const a = P(xx, 5.0, 0.4), b = P(xx, 6.0, 0.4); ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke(); }
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
// example cards riding the belt (beat 2) + the single test card (beat 3)
|
|
227
|
+
const cards = [];
|
|
228
|
+
if (t > 2.4 && t < 6.6) {
|
|
229
|
+
for (let j = 0; j < 9; j++) {
|
|
230
|
+
const x = (t - 2.4) * 2.6 - j * 2.6 + 0.3;
|
|
231
|
+
if (x < 0.2 || x > 9.4) continue;
|
|
232
|
+
cards.push({ x, kind: (j * 7 + 3) % 3 === 0 ? 'dog' : ((j * 5) % 2 ? 'cat' : 'dog'), lab: true });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (t > 5.9 && t < 7.4) cards.push({ x: H.lerp(0.4, 9.6, E(5.9, 7.3)), kind: 'cat', test: true });
|
|
236
|
+
for (const cd of cards) {
|
|
237
|
+
add(cd.x + 0.5 + 5.5 + 0.6, () => {
|
|
238
|
+
const y = 5.45;
|
|
239
|
+
box(ctx, P, cd.x, y, 0.4, 1.0, 0.1, 1.0, shade(cd.test ? '#ffd9cf' : '#ffffff'), false);
|
|
240
|
+
const o = P(cd.x + 0.5, y + 0.1, 0.9);
|
|
241
|
+
ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
|
|
242
|
+
petFace(ctx, cd.kind, 0, 0, 12, cd.kind === 'cat' ? '#b9c2ea' : '#f2c39a', INK);
|
|
243
|
+
ctx.restore();
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
// the model: a machine with a glass-top neural net and three tuning dials
|
|
247
|
+
const adj = E(8.6, 9.3);
|
|
248
|
+
const nodes = [];
|
|
249
|
+
for (let c = 0; c < 3; c++) for (let r = 0; r < 3; r++) nodes.push([10.8 + c * 1.2, 3.9 + r * 1.25, c]);
|
|
250
|
+
shadows.push([10, 3.2, 4, 3.8, 2.6]);
|
|
251
|
+
add(12 + 5.1, () => {
|
|
252
|
+
box(ctx, P, 10, 3.2, 0, 4, 3.8, 2.6, shade('#aab6e8'));
|
|
253
|
+
// intake slot on the front
|
|
254
|
+
poly(ctx, [P(10.25, 7, 0.45), P(10.95, 7, 0.45), P(10.95, 7, 1.5), P(10.25, 7, 1.5)], '#4a5488');
|
|
255
|
+
// dials on the +y face
|
|
256
|
+
const err = ph(7.4, 8.6) * (1 - ph(9.3, 9.8));
|
|
257
|
+
for (let d = 0; d < 3; d++) {
|
|
258
|
+
const o = P(11.6 + d * 0.85, 7, 1.15);
|
|
259
|
+
ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
|
|
260
|
+
ctx.fillStyle = '#eef1fa'; ctx.beginPath(); ctx.arc(0, 0, 13, 0, 7); ctx.fill();
|
|
261
|
+
ctx.strokeStyle = err > 0 ? H.rgba(ACC, 0.4 + 0.6 * err) : '#8e9bd0'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.arc(0, 0, 13, 0, 7); ctx.stroke();
|
|
262
|
+
const ang = -0.9 + d * 0.7 + adj * (d === 1 ? -1.6 : 1.1) + Math.sin(t * 9) * 0.15 * (adj > 0 && adj < 1 ? 1 : 0);
|
|
263
|
+
ctx.strokeStyle = INK; ctx.lineWidth = 3; ctx.lineCap = 'round'; ctx.beginPath(); ctx.moveTo(0, 0); ctx.lineTo(Math.cos(ang) * 10, Math.sin(ang) * 10); ctx.stroke();
|
|
264
|
+
ctx.restore();
|
|
265
|
+
}
|
|
266
|
+
// net on top (glass panel)
|
|
267
|
+
poly(ctx, [P(10.3, 3.5, 2.61), P(13.7, 3.5, 2.61), P(13.7, 6.7, 2.61), P(10.3, 6.7, 2.61)], 'rgba(230,236,255,0.9)');
|
|
268
|
+
const wts = (i, j) => { const a = Math.abs(Math.sin(i * 3.1 + j * 1.7)), b = Math.abs(Math.cos(i * 2.3 + j * 0.9)); return H.lerp(a, b, adj); };
|
|
269
|
+
const pulse = ph(6.9, 7.4) * (1 - ph(7.4, 7.6)) + ph(9.3, 9.6) * (1 - ph(9.6, 9.9));
|
|
270
|
+
for (let i = 0; i < 9; i++) for (let j = 0; j < 9; j++) {
|
|
271
|
+
if (nodes[j][2] !== nodes[i][2] + 1) continue;
|
|
272
|
+
const a = P(nodes[i][0], nodes[i][1], 2.62), b = P(nodes[j][0], nodes[j][1], 2.62);
|
|
273
|
+
const w = wts(i, j), errCol = H.mix([142, 155, 208], H.hex(ACC), ph(8.0, 8.6) * (1 - ph(9.0, 9.4)));
|
|
274
|
+
ctx.strokeStyle = H.rgba(errCol, 0.35 + 0.6 * w); ctx.lineWidth = 0.6 + 2.6 * w * (s / 40);
|
|
275
|
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
|
|
276
|
+
}
|
|
277
|
+
for (const [x, y, c] of nodes) {
|
|
278
|
+
const p = P(x, y, 2.62), rr = 0.17 * s;
|
|
279
|
+
const lit = pulse > 0 ? Math.max(0, Math.sin((t * 6) - c * 1.2)) * pulse : 0;
|
|
280
|
+
ctx.fillStyle = H.rgba(H.mix('#7d8fd6', '#ffe28a', lit)); ctx.beginPath(); ctx.ellipse(p[0], p[1], rr, rr * 0.7, 0, 0, 7); ctx.fill();
|
|
281
|
+
ctx.fillStyle = 'rgba(255,255,255,0.7)'; ctx.beginPath(); ctx.ellipse(p[0] - rr * 0.3, p[1] - rr * 0.25, rr * 0.35, rr * 0.22, 0, 0, 7); ctx.fill();
|
|
282
|
+
}
|
|
283
|
+
});
|
|
284
|
+
// output monitor
|
|
285
|
+
const shake = (t > 7.4 && t < 7.8) ? Math.sin(t * 90) * 0.06 : 0;
|
|
286
|
+
shadows.push([15.3, 4.4, 3.2, 0.6, 3.2]);
|
|
287
|
+
add(16.9 + 4.8, () => {
|
|
288
|
+
box(ctx, P, 16.6, 4.5, 0, 0.4, 0.4, 1.0, shade('#d8d4ea'), false);
|
|
289
|
+
box(ctx, P, 15.2 + shake, 4.4, 1.0, 3.4, 0.5, 2.3, shade('#4a5488'));
|
|
290
|
+
const sx0 = 15.4 + shake, sx1 = 18.4 + shake;
|
|
291
|
+
const wrong = t > 7.05 && t < 9.4, right = t >= 9.4;
|
|
292
|
+
const scr = wrong && t > 7.4 ? '#ffe3df' : right ? '#dcf7ec' : '#eef1fa';
|
|
293
|
+
poly(ctx, [P(sx0, 4.9, 1.2), P(sx1, 4.9, 1.2), P(sx1, 4.9, 3.1), P(sx0, 4.9, 3.1)], scr);
|
|
294
|
+
{ // glass: inner shade along the bezel + a diagonal sheen band
|
|
295
|
+
const a0 = P(sx0, 4.9, 3.1), a1 = P(sx1, 4.9, 1.2);
|
|
296
|
+
const ig = ctx.createLinearGradient(0, a0[1], 0, P(sx0, 4.9, 1.2)[1]); ig.addColorStop(0, 'rgba(60,50,110,0.10)'); ig.addColorStop(0.18, 'rgba(60,50,110,0)'); ig.addColorStop(1, 'rgba(255,255,255,0.12)');
|
|
297
|
+
poly(ctx, [P(sx0, 4.9, 1.2), P(sx1, 4.9, 1.2), P(sx1, 4.9, 3.1), P(sx0, 4.9, 3.1)], ig);
|
|
298
|
+
const sg = ctx.createLinearGradient(a0[0], a0[1], a1[0], a1[1]);
|
|
299
|
+
sg.addColorStop(0.32, 'rgba(255,255,255,0)'); sg.addColorStop(0.4, 'rgba(255,255,255,0.32)'); sg.addColorStop(0.5, 'rgba(255,255,255,0.08)'); sg.addColorStop(0.56, 'rgba(255,255,255,0)');
|
|
300
|
+
poly(ctx, [P(sx0, 4.9, 1.2), P(sx1, 4.9, 1.2), P(sx1, 4.9, 3.1), P(sx0, 4.9, 3.1)], sg);
|
|
301
|
+
}
|
|
302
|
+
const o = P(sx0, 4.9, 3.1);
|
|
303
|
+
ctx.save(); wallX(ctx, o); ctx.scale(s / 40, s / 40);
|
|
304
|
+
const SW = 3 * 40, SH = 1.9 * 40;
|
|
305
|
+
if (t > 6.9 && t < 7.05) { ctx.fillStyle = '#9aa6d6'; for (let i = 0; i < 3; i++) { ctx.beginPath(); ctx.arc(SW / 2 - 14 + i * 14, SH / 2, 4, 0, 7); ctx.fill(); } }
|
|
306
|
+
if (wrong || right) {
|
|
307
|
+
petFace(ctx, right ? 'cat' : 'dog', 26, SH / 2, 15, right ? '#b9c2ea' : '#f2c39a', INK);
|
|
308
|
+
ctx.font = '800 30px "Inter"'; ctx.fillStyle = INK; ctx.fillText(right ? 'cat' : 'dog?', 46, SH / 2 + 11);
|
|
309
|
+
}
|
|
310
|
+
ctx.restore();
|
|
311
|
+
});
|
|
312
|
+
// accuracy chart: one bar per training round
|
|
313
|
+
const ACCS = [61, 72, 80, 86, 90, 93, 95, 97];
|
|
314
|
+
const roundT = (i) => 10.9 + i * 0.22;
|
|
315
|
+
ACCS.forEach((a, i) => {
|
|
316
|
+
const x = 19.7 + i * 0.72, y = 3.4, hh = (a - 50) / 50 * 4.4 * EO(roundT(i), roundT(i) + 0.35);
|
|
317
|
+
if (hh <= 0.01) return;
|
|
318
|
+
shadows.push([x, y, 0.5, 0.6, hh]);
|
|
319
|
+
add(x + 0.25 + y + 0.3, () => box(ctx, P, x, y, 0, 0.5, 0.6, hh, shade(i === 7 ? '#ff8a7a' : H.mix('#aab6e8', '#8fd9bd', i / 7))));
|
|
320
|
+
});
|
|
321
|
+
add(19.4 + 2, () => box(ctx, P, 19.4, 2.9, 0, 6.2, 0.3, 0.2, shade('#d8d4ea'), false));
|
|
322
|
+
// decorative trees
|
|
323
|
+
for (const [tx, ty] of [[0.7, 7.3], [25.3, 0.8], [25.2, 7.2], [9.3, 0.7]]) {
|
|
324
|
+
shadows.push([tx - 0.3, ty - 0.3, 0.6, 0.6, 1.8]);
|
|
325
|
+
add(tx + ty, () => {
|
|
326
|
+
const b = P(tx, ty), top = P(tx, ty, 1.4); const rr = 0.6 * s;
|
|
327
|
+
ctx.strokeStyle = '#b79a8a'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(b[0], b[1]); ctx.lineTo(top[0], top[1] + 4); ctx.stroke();
|
|
328
|
+
ctx.fillStyle = '#7fc8a8'; ctx.beginPath(); ctx.arc(top[0], top[1] - rr * 0.3, rr, 0, 7); ctx.fill();
|
|
329
|
+
ctx.fillStyle = '#a3dcc0'; ctx.beginPath(); ctx.arc(top[0] - rr * 0.22, top[1] - rr * 0.5, rr * 0.72, 0, 7); ctx.fill();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
// shadows (one layer)
|
|
333
|
+
{
|
|
334
|
+
const L = H.layer(W, H_), lc = L.getContext('2d');
|
|
335
|
+
for (const [x, y, w, d, h] of shadows) {
|
|
336
|
+
const base = [[x, y], [x + w, y], [x + w, y + d], [x, y + d]];
|
|
337
|
+
poly(lc, hull(base.concat(base.map(p => [p[0] + 1.1 * h, p[1] + 0.12 * h]))).map(p => P(p[0], p[1])), '#000');
|
|
338
|
+
}
|
|
339
|
+
lc.globalCompositeOperation = 'destination-in'; poly(lc, [P(0, 0), P(LX, 0), P(LX, LY), P(0, LY)], '#000');
|
|
340
|
+
lc.globalCompositeOperation = 'source-in'; lc.fillStyle = 'rgb(110,96,170)'; lc.fillRect(0, 0, W, H_);
|
|
341
|
+
ctx.globalAlpha = 0.16; ctx.drawImage(L, 0, 0); ctx.globalAlpha = 1;
|
|
342
|
+
}
|
|
343
|
+
objs.sort((a, b) => a.key - b.key);
|
|
344
|
+
for (const o of objs) { ctx.save(); o.fn(); ctx.restore(); }
|
|
345
|
+
|
|
346
|
+
// ---------- screen-space annotation layer: tags that land on things, connectors ----------
|
|
347
|
+
// beat 2: label tags riding on each card
|
|
348
|
+
for (const cd of cards) {
|
|
349
|
+
if (cd.test) {
|
|
350
|
+
const p = P(cd.x + 0.5, 5.5, 1.75);
|
|
351
|
+
tag(ctx, p[0], p[1] - 14, '?', { k: EO(5.9, 6.2), font: '800 26px "Inter"', bg: ACC, col: '#fff' });
|
|
352
|
+
continue;
|
|
353
|
+
}
|
|
354
|
+
const p = P(cd.x + 0.5, 5.5, 1.75);
|
|
355
|
+
const k = EO(2.4 + 0.0, 2.8) * H.clamp((cd.x - 0.2) * 3) * H.clamp((9.4 - cd.x) * 3);
|
|
356
|
+
if (k > 0.05) tag(ctx, p[0], p[1] - 16, cd.kind, { k, font: '700 24px "Inter"', icon: true, iconCol: cd.kind === 'cat' ? '#7f8fd8' : '#f2a46b' });
|
|
357
|
+
}
|
|
358
|
+
if (t > 2.6 && t < 6.4) { const b = P(4.6, 0.4, 4.4); numberTag(ctx, b[0], b[1] - 70, cnt.toLocaleString('en-US'), 'examples', { k: H.ease.back(ph(2.6, 2.95)) * (1 - E(6.0, 6.4)) }); }
|
|
359
|
+
// flow arrow: data -> model
|
|
360
|
+
{
|
|
361
|
+
const a = P(1.2, 6.6, 2.2), b = P(10.2, 6.6, 2.0);
|
|
362
|
+
arrow(ctx, [a, [(a[0] + b[0]) / 2, Math.min(a[1], b[1]) - 0.9 * s], b], E(3.0, 3.9) * (1 - ph(6.2, 6.5)), 'rgba(74,84,136,0.75)', [6, 6], 2);
|
|
363
|
+
if (t > 3.6 && t < 6.4) { const m = P(9.3, 7.0, 3.2); tag(ctx, m[0], m[1] - 0.6 * s, 'into the model', { k: EO(3.6, 3.9), font: '700 24px "Inter"', bg: '#4a5488', col: '#fff' }); }
|
|
364
|
+
}
|
|
365
|
+
// beat 3 loop: Guess -> Check -> Adjust
|
|
366
|
+
if (t > 6.6 && t < 10.6) {
|
|
367
|
+
const fade = 1 - ph(10.3, 10.6);
|
|
368
|
+
ctx.save(); ctx.globalAlpha = fade;
|
|
369
|
+
const mTop = P(12.6, 5.6, 2.75), scrL = P(15.4, 4.9, 2.7), scrTop = P(16.9, 4.7, 3.4), mBack = P(12.4, 5.2, 2.9);
|
|
370
|
+
// Guess
|
|
371
|
+
arrow(ctx, [mTop, [(mTop[0] + scrL[0]) / 2, Math.min(mTop[1], scrL[1]) - 1.4 * s], scrL], E(6.8, 7.15), INK, null, 2.5);
|
|
372
|
+
if (t > 6.85) { const g = [(mTop[0] + scrL[0]) / 2, Math.min(mTop[1], scrL[1]) - 0.75 * s]; tag(ctx, g[0], g[1] - 14, '1 Guess', { k: H.ease.back(ph(6.85, 7.15)), font: '800 26px "Inter"' }); }
|
|
373
|
+
// Check: emphasis ring on the wrong answer
|
|
374
|
+
if (t > 7.4) {
|
|
375
|
+
tag(ctx, scrTop[0] + 0.4 * s, scrTop[1] - 0.6 * s, '2 Check', { k: H.ease.back(ph(7.45, 7.75)), font: '800 26px "Inter"' });
|
|
376
|
+
if (t < 9.4) {
|
|
377
|
+
const c = P(16.9, 4.9, 2.15), pr = EO(7.45, 7.9);
|
|
378
|
+
ctx.save(); ctx.strokeStyle = ACC; ctx.lineWidth = 3; ctx.globalAlpha = fade * (0.6 + 0.4 * Math.sin(t * 10));
|
|
379
|
+
ctx.beginPath(); ctx.ellipse(c[0], c[1], 1.95 * s * (1.4 - 0.4 * pr), 1.25 * s * (1.4 - 0.4 * pr), -0.05, 0, Math.PI * 2 * pr); ctx.stroke(); ctx.restore();
|
|
380
|
+
if (t < 7.6) { ctx.fillStyle = `rgba(255,107,91,${0.25 * (1 - ph(7.4, 7.6))})`; ctx.fillRect(0, 0, W, H_); }
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
// Adjust: error signal travels back
|
|
384
|
+
if (t > 8.0) {
|
|
385
|
+
const q = arrow(ctx, [scrTop, [(scrTop[0] + mBack[0]) / 2, scrTop[1] - 2.2 * s], mBack], E(8.0, 8.6), ACC, [7, 5], 2.5);
|
|
386
|
+
if (q && t < 8.9) { const p2 = q(E(8.0, 8.6)); ctx.fillStyle = 'rgba(255,107,91,0.3)'; ctx.beginPath(); ctx.arc(p2[0], p2[1], 10, 0, 7); ctx.fill(); }
|
|
387
|
+
const dl = P(12.45, 7.0, 0.4);
|
|
388
|
+
tag(ctx, dl[0], dl[1] + 0.55 * s, '3 Adjust', { k: H.ease.back(ph(8.5, 8.8)), font: '800 26px "Inter"', bg: ACC, col: '#fff' });
|
|
389
|
+
const lt = P(14.0, 3.2, 4.4);
|
|
390
|
+
tag(ctx, lt[0], lt[1], 'error!', { k: H.ease.back(ph(8.1, 8.35)) * (1 - ph(9.2, 9.4)), font: '700 22px "Inter"', bg: '#ffe3df', col: ACC });
|
|
391
|
+
}
|
|
392
|
+
// truth label under the screen + verdict badge stamped on the screen corner
|
|
393
|
+
if (t > 7.5 && t < 9.4) { const c = P(16.9, 4.9, 0.85); tag(ctx, c[0], c[1] + 16, "answer: cat", { k: H.ease.back(ph(7.6, 7.9)), font: '700 26px "Inter"', bg: INK, col: '#fff' }); }
|
|
394
|
+
if (t > 7.4) {
|
|
395
|
+
const right = t >= 9.4, b = P(18.5 + shake, 4.9, 3.25);
|
|
396
|
+
const kk = right ? H.ease.back(ph(9.45, 9.75)) : H.ease.back(ph(7.4, 7.65));
|
|
397
|
+
ctx.save(); ctx.translate(b[0], b[1]); ctx.scale(kk, kk);
|
|
398
|
+
ctx.fillStyle = right ? MINT : ACC; ctx.beginPath(); ctx.arc(0, 0, 19, 0, 7); ctx.fill();
|
|
399
|
+
ctx.strokeStyle = '#fff'; ctx.lineWidth = 4; ctx.lineCap = 'round'; ctx.lineJoin = 'round'; ctx.beginPath();
|
|
400
|
+
if (right) { ctx.moveTo(-8, 0); ctx.lineTo(-2, 7); ctx.lineTo(9, -7); } else { ctx.moveTo(-7, -7); ctx.lineTo(7, 7); ctx.moveTo(7, -7); ctx.lineTo(-7, 7); }
|
|
401
|
+
ctx.stroke(); ctx.restore();
|
|
402
|
+
}
|
|
403
|
+
if (t > 9.4) { const c = P(16.9, 4.7, 3.4); tag(ctx, c[0] + 0.4 * s, c[1] - 0.6 * s, 'Guess again ✓', { k: H.ease.back(ph(9.45, 9.75)), font: '800 26px "Inter"', bg: MINT, col: '#fff' }); }
|
|
404
|
+
ctx.restore();
|
|
405
|
+
}
|
|
406
|
+
// beat 4: counting accuracy tag that rides the newest bar
|
|
407
|
+
if (t > 10.8) {
|
|
408
|
+
const i = Math.min(7, Math.max(0, Math.floor((t - 10.9) / 0.22)));
|
|
409
|
+
const cur = Math.round(H.lerp(61, 97, H.ease.inOut(ph(10.9, 12.6))));
|
|
410
|
+
const x = 19.7 + i * 0.72 + 0.25, hh = (ACCS[i] - 50) / 50 * 4.4;
|
|
411
|
+
const p = P(x, 3.7, hh + 0.2);
|
|
412
|
+
const pop = t > 12.6 ? 1 + 0.18 * Math.sin(Math.min(1, (t - 12.6) / 0.35) * Math.PI) : 1;
|
|
413
|
+
ctx.strokeStyle = 'rgba(74,84,136,0.6)'; ctx.lineWidth = 1.3; ctx.beginPath(); ctx.moveTo(p[0], p[1]); ctx.lineTo(p[0], p[1] - 42); ctx.stroke();
|
|
414
|
+
ctx.fillStyle = INK; ctx.beginPath(); ctx.arc(p[0], p[1], 3.5, 0, 7); ctx.fill();
|
|
415
|
+
numberTag(ctx, p[0], p[1] - 100, `${cur}%`, 'Accuracy', { k: H.ease.back(ph(10.85, 11.15)) * pop, bg: cur >= 97 ? ACC : '#ffffff', col: cur >= 97 ? '#fff' : INK, sub: cur >= 97 ? 'rgba(255,255,255,0.85)' : '#7b7aa3' });
|
|
416
|
+
if (t > 12.6) { // sparkle emphasis
|
|
417
|
+
ctx.save(); ctx.strokeStyle = ACC; ctx.lineWidth = 2; ctx.globalAlpha = 1 - ph(12.6, 13.2);
|
|
418
|
+
const rr2 = 80 + 60 * EO(12.6, 13.2); ctx.beginPath(); ctx.arc(p[0], p[1] - 100, rr2, 0, 7); ctx.stroke(); ctx.restore();
|
|
419
|
+
}
|
|
420
|
+
const p0 = P(19.95, 3.7, (61 - 50) / 50 * 4.4 + 0.15);
|
|
421
|
+
if (t < 13.4 && t > 11.0) tag(ctx, p0[0] - 40, p0[1] - 68, 'round 1: 61%', { k: EO(10.9, 11.2) * (1 - ph(13.0, 13.4)), font: '700 22px "Inter"', col: '#7b7aa3' });
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
// ---------- hook: big question that shrinks into the corner header ----------
|
|
425
|
+
{
|
|
426
|
+
const sh = E(1.5, 2.4);
|
|
427
|
+
const a = EO(0.15, 0.7);
|
|
428
|
+
const size = H.lerp(66, 30, sh);
|
|
429
|
+
ctx.save(); ctx.globalAlpha = a;
|
|
430
|
+
if (sh < 1) { ctx.fillStyle = `rgba(251,248,243,${0.7 * (1 - sh)})`; ctx.fillRect(0, 0, W, H_); }
|
|
431
|
+
ctx.font = `800 ${size}px "Inter"`; ctx.fillStyle = INK;
|
|
432
|
+
const w = ctx.measureText('How does AI learn?').width;
|
|
433
|
+
const x = H.lerp(640 - w / 2, 48, sh), y = H.lerp(330, 62, sh) + (1 - a) * 20;
|
|
434
|
+
if (sh > 0) { ctx.save(); ctx.globalAlpha = a * sh; ctx.shadowColor = 'rgba(80,70,140,0.18)'; ctx.shadowBlur = 16; ctx.shadowOffsetY = 4; ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.roundRect(x - 18, y - size * 1.6, w + 36, size * 2.1, 14); ctx.fill(); ctx.restore(); ctx.fillStyle = INK; }
|
|
435
|
+
ctx.fillText('How does AI learn?', x, y);
|
|
436
|
+
ctx.fillStyle = ACC; ctx.beginPath(); ctx.roundRect(x + 2, y - size * 1.32, H.lerp(40, 22, sh), H.lerp(7, 4, sh), 3); ctx.fill();
|
|
437
|
+
ctx.restore();
|
|
438
|
+
}
|
|
439
|
+
// ---------- takeaway ----------
|
|
440
|
+
if (t > 13.0) {
|
|
441
|
+
const k2 = H.ease.back(ph(13.0, 13.5));
|
|
442
|
+
ctx.save(); ctx.translate(640, 640 + (1 - EO(13.0, 13.4)) * 40); ctx.scale(k2, k2);
|
|
443
|
+
ctx.font = '800 44px "Inter"';
|
|
444
|
+
const str = 'More examples → better guesses', w = ctx.measureText(str).width + 80;
|
|
445
|
+
ctx.shadowColor = 'rgba(80,70,140,0.25)'; ctx.shadowBlur = 24; ctx.shadowOffsetY = 8;
|
|
446
|
+
ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.roundRect(-w / 2, -44, w, 88, 44); ctx.fill(); ctx.shadowColor = 'transparent';
|
|
447
|
+
ctx.fillStyle = ACC; ctx.beginPath(); ctx.arc(-w / 2 + 30, 0, 7, 0, 7); ctx.fill();
|
|
448
|
+
ctx.fillStyle = INK; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.fillText(str, 12, 1);
|
|
449
|
+
ctx.restore();
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// ---------- sound: a tidy toy factory (wooden-block pops, a soft belt motor, gentle camera glides) ----------
|
|
454
|
+
function isoSound(k) {
|
|
455
|
+
const D = 14.5;
|
|
456
|
+
const ph = (t, a, b) => H.clamp((t - a) / (b - a));
|
|
457
|
+
const inv = (u) => (u < 0.5 ? Math.cbrt(u / 4) : 1 - Math.cbrt((1 - u) * 2) / 2); // inverse of H.ease.inOut
|
|
458
|
+
// camera keyframes copied from isoExplainer (KF), used to pan sounds to where their object sits on screen
|
|
459
|
+
const KF = [[0, 11, 4, 0.5, 24], [1.6, 11, 4, 0.5, 26], [2.8, 4.6, 3.4, 2.0, 46], [5.9, 5.6, 3.6, 1.9, 46], [6.9, 15.3, 5.0, 1.8, 54], [10.3, 15.3, 5.0, 1.8, 54], [11.1, 22.9, 3.6, 2.9, 68], [12.9, 22.9, 3.6, 2.9, 68], [13.8, 13, 4, 0.8, 27], [20, 13, 4, 0.8, 27]];
|
|
460
|
+
const pan = (x, y, t) => {
|
|
461
|
+
let i = 0; while (i < KF.length - 2 && t > KF[i + 1][0]) i++;
|
|
462
|
+
const [t0, fx0, fy0, , s0] = KF[i], [t1, fx1, fy1, , s1] = KF[i + 1], u = H.ease.inOut(H.clamp((t - t0) / (t1 - t0)));
|
|
463
|
+
const sx = 640 + ((x - H.lerp(fx0, fx1, u)) - (y - H.lerp(fy0, fy1, u))) * C30 * H.lerp(s0, s1, u);
|
|
464
|
+
return H.clamp(sx / 640 - 1, -1, 1) * 0.6;
|
|
465
|
+
};
|
|
466
|
+
// toy pop: a little lacquered-wood block snapping into place (body tock + soft pop), never squeaky
|
|
467
|
+
const toy = (t, f, g, p, minor) => { const o = { pan: p, minor }; k.play('pop', t, { ...o, f, gain: 0.8 * g }); k.play('tock', t + 0.004, { ...o, f: f * 0.9, gain: 0.35 * g }); };
|
|
468
|
+
// camera glide: a low, smooth air movement that swells with the eased camera speed (no swoosh)
|
|
469
|
+
const glide = (t0, t1, g = 1) => { const d = t1 - t0; k.noise(t0, { dur: d + 0.15, color: 'pink', filter: 'bandpass', f: [[0, 220], [d * 0.5, 520], [d, 240]], q: 0.7, gain: 0.045 * g, a: d * 0.5, verb: 0.1 }); };
|
|
470
|
+
// counter ticks: one per visible change, at most ~12/s, spaced by how fast the value is moving
|
|
471
|
+
const counter = (a, b, val, f, g, p, minGap = 1 / 12, avoid = []) => {
|
|
472
|
+
let last = -1, lastV = val(a);
|
|
473
|
+
for (let t = a; t <= b + 1e-6; t += 1 / 120) {
|
|
474
|
+
const v = val(t); if (v === lastV) continue;
|
|
475
|
+
const speed = Math.abs(val(Math.min(b, t + 0.05)) - val(Math.max(a, t - 0.05)));
|
|
476
|
+
const top = Math.abs(val(b) - val(a)) * 0.1 / (b - a) * 2.2; // roughly the peak change per 0.1 s
|
|
477
|
+
const gap = H.clamp(minGap / Math.max(0.2, Math.min(1, speed / top)), minGap, 0.4);
|
|
478
|
+
if (t - last >= gap && !avoid.some((x) => Math.abs(x - t) < 0.07)) { k.play('tick', t, { f, gain: g, pan: p, minor: true }); k.tone(t, { f: f / 8, dur: 0.03, gain: 0.05 * g, pan: p, minor: true }); last = t; }
|
|
479
|
+
lastV = v;
|
|
480
|
+
}
|
|
481
|
+
};
|
|
482
|
+
|
|
483
|
+
k.bed('room', 0, D, { gain: 0.25 });
|
|
484
|
+
k.music('pad', 0, D, { bpm: 100, gain: 0.32, chords: [['C4', 'E4', 'G4', 'B4'], ['F3', 'A3', 'C4', 'E4'], ['A3', 'C4', 'E4', 'G4'], ['F3', 'A3', 'C4', 'E4'], ['G3', 'B3', 'D4', 'F4'], ['C4', 'E4', 'G4', 'C5']] });
|
|
485
|
+
|
|
486
|
+
// ---- hook: the question fades up (EO 0.15-0.7), then slides small into the corner header (1.5-2.4)
|
|
487
|
+
toy(0.2, 520, 0.7, 0);
|
|
488
|
+
k.noise(1.5, { dur: 0.9, color: 'pink', filter: 'lowpass', f: [[0, 700], [0.9, 300]], gain: 0.06, a: 0.35, minor: true });
|
|
489
|
+
glide(1.6, 2.8); // camera dives onto the training-data shelves
|
|
490
|
+
|
|
491
|
+
// ---- beat 2: data. belt motor + slat ticks while the cards ride, cards clack onto the belt once a second
|
|
492
|
+
k.tone(2.4, { f: 72, type: 'sawtooth', dur: 4.3, gain: 0.025, a: 0.3, hold: 3.6, filter: 'lowpass', f2: 260, minor: true }); // belt motor
|
|
493
|
+
for (let t = 2.4 + (0.5 - ((2.4 * 2.2) % 0.5)) / 2.2; t < 6.5; t += 0.5 / 2.2) if (t > 2.9 && (t - 2.36) % 1 > 0.1) k.play('tock', t, { f: 330, gain: 0.22, pan: pan(5, 5.5, t), minor: true }); // a belt slat passes
|
|
494
|
+
for (let j = 0; j < 5; j++) { const t = Math.max(2.4, 2.4 + j - 0.1 / 2.6); if (t < 6.5) toy(t, 430, 0.55, pan(0.6, 5.5, t), true); } // card lands on the belt
|
|
495
|
+
k.play('pop', 2.45, { f: 560, gain: 0.5, pan: pan(1, 5.5, 2.45), minor: true }); // cat/dog tags pop on
|
|
496
|
+
toy(2.66, 600, 0.9, pan(4.6, 0.4, 2.66)); // examples counter pops up over the billboard
|
|
497
|
+
counter(2.85, 5.66, (t) => Math.floor(1e6 * Math.pow(H.ease.out(ph(t, 2.8, 5.7)), 1.6)), 3400, 0.9, pan(4.6, 0.4, 4), 1 / 8);
|
|
498
|
+
// dashed flow arrow draws (E 3.0-3.9): soft dabs, then the "into the model" tag
|
|
499
|
+
for (let i = 1; i <= 5; i++) { const u = i / 5, t = 3.0 + 0.9 * inv(u); k.noise(t, { dur: 0.025, filter: 'bandpass', f: 1800, q: 1.4, gain: 0.12, a: 0.002, pan: pan(1.2 + 9 * u, 6.6, t), minor: true }); }
|
|
500
|
+
toy(3.63, 470, 0.8, pan(9.3, 7, 3.63));
|
|
501
|
+
// 1,000,000 lands (EO reaches 1 at 5.7)
|
|
502
|
+
toy(5.7, 640, 1.1, pan(4.6, 0.4, 5.7));
|
|
503
|
+
k.play('ding', 5.7, { f: 'G5', gain: 0.45, pan: pan(4.6, 0.4, 5.7) });
|
|
504
|
+
|
|
505
|
+
// ---- beat 3: test card -> guess -> check -> adjust
|
|
506
|
+
toy(5.93, 380, 0.9, pan(0.9, 5.5, 5.93)); // coral "?" on the test card
|
|
507
|
+
glide(5.9, 6.9);
|
|
508
|
+
k.play('tock', 7.3, { f: 300, gain: 0.35, pan: pan(10.25, 7, 7.3), minor: true }); // test card slides into the intake slot
|
|
509
|
+
k.play('swish', 6.8, { dur: 0.32, gain: 0.25, pan: pan(14, 5, 6.95), minor: true }); // Guess arrow arcs over
|
|
510
|
+
toy(6.9, 560, 0.85, pan(14, 5, 6.9)); // "1 Guess" tag
|
|
511
|
+
k.tone(7.05, { f: 'E4', type: 'triangle', dur: 0.18, gain: 0.12, pan: pan(16.9, 4.9, 7.05) }); // "dog?" pops on the monitor
|
|
512
|
+
// CHECK: wrong. red flash, monitor shakes, X badge stamps
|
|
513
|
+
k.play('thud', 7.4, { f: 120, gain: 0.7, pan: pan(16.9, 4.9, 7.4) });
|
|
514
|
+
k.play('error', 7.42, { f: 'A3', gain: 0.85, pan: pan(16.9, 4.9, 7.4) });
|
|
515
|
+
toy(7.64, 440, 0.75, pan(16.9, 4.9, 7.64)); // "answer: cat"
|
|
516
|
+
// ADJUST: error signal travels back along the dashed arc (E 8.0-8.6), then "error!" and "3 Adjust"
|
|
517
|
+
k.tone(8.0, { f: [[0, 520], [0.6, 260]], type: 'triangle', dur: 0.6, gain: 0.06, a: 0.25, pan: pan(14.6, 5, 8.3), minor: true });
|
|
518
|
+
k.noise(8.0, { dur: 0.6, color: 'pink', filter: 'bandpass', f: [[0, 900], [0.6, 500]], q: 1.2, gain: 0.07, a: 0.3, pan: pan(14.6, 5, 8.3), minor: true });
|
|
519
|
+
toy(8.15, 680, 0.6, pan(14, 3.2, 8.15), true); // "error!"
|
|
520
|
+
toy(8.55, 500, 0.85, pan(12.45, 7, 8.55)); // "3 Adjust"
|
|
521
|
+
// dials turn (E 8.6-9.3, with a wobble): even ratchet clicks across the turn
|
|
522
|
+
for (let i = 0; i < 5; i++) { k.play('click', 8.66 + i * 0.145, { f: 1100 + (i % 3) * 120, gain: 0.7, pan: pan(12.4, 7, 9), minor: true }); }
|
|
523
|
+
// CORRECT: screen turns to cat (9.4), check badge + "Guess again" pop
|
|
524
|
+
k.tone(9.4, { f: 'C5', type: 'triangle', dur: 0.15, gain: 0.1, pan: pan(16.9, 4.9, 9.4) });
|
|
525
|
+
k.play('success', 9.46, { f: 'C5', gain: 0.8, pan: pan(18.3, 4.9, 9.46) });
|
|
526
|
+
toy(9.48, 620, 0.7, pan(17.3, 4.7, 9.48), true);
|
|
527
|
+
|
|
528
|
+
// ---- beat 4: the camera glides to the chart, bars pop up one round at a time (61 -> 97)
|
|
529
|
+
glide(10.3, 11.1);
|
|
530
|
+
['C4', 'D4', 'E4', 'G4', 'A4', 'C5', 'D5', 'E5'].forEach((n, i) => { const t = 10.9 + i * 0.22 + 0.03; k.tone(t, { f: [[0, k.hz(n)], [0.08, k.hz(n) * 0.75]], dur: 0.12, gain: 0.16, pan: pan(19.7 + i * 0.72, 3.4, t), minor: i < 7 }); k.play('tock', t, { f: k.hz(n) * 0.8, gain: 0.22, pan: pan(19.7 + i * 0.72, 3.4, t), minor: true }); });
|
|
531
|
+
counter(10.92, 12.56, (t) => Math.round(H.lerp(61, 97, H.ease.inOut(ph(t, 10.9, 12.6)))), 2900, 0.7, pan(23, 3.7, 12), 1 / 12, [0, 1, 2, 3, 4, 5, 6, 7].map((i) => 10.93 + i * 0.22));
|
|
532
|
+
// 97% (the biggest hit): coral flip, the tag swells, a ring expands
|
|
533
|
+
k.play('impact', 12.6, { gain: 0.75, pan: pan(25, 3.7, 12.6) });
|
|
534
|
+
['C5', 'E5', 'G5', 'C6'].forEach((n, i) => k.play('ding', 12.6 + i * 0.03, { f: n, gain: 0.42 - i * 0.05, pan: (i - 1.5) * 0.2 }));
|
|
535
|
+
k.noise(12.62, { dur: 0.6, filter: 'bandpass', f: [[0, 1200], [0.6, 2400]], q: 2, gain: 0.04, a: 0.05, verb: 0.3, minor: true }); // ring shimmer
|
|
536
|
+
glide(12.9, 13.8, 0.9); // pull back out to the whole floor
|
|
537
|
+
// takeaway pill bounces in (back 13.0-13.5) and resolves
|
|
538
|
+
toy(13.08, 470, 0.9, 0);
|
|
539
|
+
['G4', 'C5', 'E5'].forEach((n, i) => k.play('ding', 13.3 + i * 0.08, { f: n, gain: 0.35 - i * 0.06, pan: (i - 1) * 0.25 }));
|
|
540
|
+
k.tone(13.3, { f: 'C3', type: 'triangle', dur: 1.2, gain: 0.12, a: 0.02 });
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
registerStyle({
|
|
544
|
+
id: 'isometric',
|
|
545
|
+
name: 'Isometric Pastel',
|
|
546
|
+
category: 'Dimensional',
|
|
547
|
+
description: 'Clean vector isometric illustration: true 30° projection, flat three-tone face shading, soft pastel palette with a single coral accent, tiny people for scale and long soft shadows on a floating tile.',
|
|
548
|
+
motion: 'The floating tile rises into frame, buildings, tanks and racks pop up one by one on springy overshoot, then LEDs blink, fans spin, data cubes stream up and tiny workers stroll during a slow camera push.',
|
|
549
|
+
duration: 5,
|
|
550
|
+
fonts: ['800 46px "Inter"', '400 18px "Inter"', '600 13px "Inter"', '800 20px "Inter"'],
|
|
551
|
+
frameT: 4.6,
|
|
552
|
+
readsPixels: false,
|
|
553
|
+
explainerDuration: 14.5,
|
|
554
|
+
explainerNote: 'One long isometric factory floor (data shelves → conveyor → glass-top neural machine → monitor → bar chart) that the camera glides along beat to beat, with white label tags pinned to objects, curved dashed connectors, a counting billboard and an accuracy tag riding the newest bar.',
|
|
555
|
+
explainer: isoExplainer,
|
|
556
|
+
explainerSound: isoSound,
|
|
557
|
+
draw(ctx, W, H_, t) {
|
|
558
|
+
const R = H.rng(11);
|
|
559
|
+
const s = 35;
|
|
560
|
+
const bob = Math.sin(t * 1.2) * 4;
|
|
561
|
+
const ph = (a, b) => H.clamp((t - a) / (b - a));
|
|
562
|
+
const rise = H.ease.out(ph(0, 1.1));
|
|
563
|
+
const P = makeIso(745, 118 + bob + (1 - rise) * 110, s);
|
|
564
|
+
const TILE = 12, TH = 0.9;
|
|
565
|
+
|
|
566
|
+
// ---------- background ----------
|
|
567
|
+
const bg = ctx.createLinearGradient(0, 0, W, H_);
|
|
568
|
+
bg.addColorStop(0, '#f6efe8'); bg.addColorStop(0.55, '#efeaf4'); bg.addColorStop(1, '#e4e3f3');
|
|
569
|
+
ctx.fillStyle = bg; ctx.fillRect(0, 0, W, H_);
|
|
570
|
+
// faint dot grid
|
|
571
|
+
ctx.fillStyle = 'rgba(120,110,170,0.10)';
|
|
572
|
+
for (let y = 18; y < H_; y += 28) for (let x = 18 + ((y / 28) % 2) * 14; x < W; x += 28) ctx.fillRect(x, y, 2, 2);
|
|
573
|
+
// soft glow behind tile
|
|
574
|
+
const glow = ctx.createRadialGradient(745, 380, 40, 745, 380, 560);
|
|
575
|
+
glow.addColorStop(0, 'rgba(255,255,255,0.75)'); glow.addColorStop(1, 'rgba(255,255,255,0)');
|
|
576
|
+
ctx.fillStyle = glow; ctx.fillRect(0, 0, W, H_);
|
|
577
|
+
// slow camera push on the world (title stays put)
|
|
578
|
+
ctx.save();
|
|
579
|
+
{ const z = 0.955 + 0.045 * H.ease.inOut(H.clamp(t / 5)); ctx.translate(745, 380); ctx.scale(z, z); ctx.translate(-745, -380); }
|
|
580
|
+
ctx.globalAlpha = ph(0, 0.45);
|
|
581
|
+
|
|
582
|
+
// ground shadow of floating tile (long, soft, to the lower right)
|
|
583
|
+
{
|
|
584
|
+
const L = H.layer(W, H_), lc = L.getContext('2d');
|
|
585
|
+
const G = makeIso(745 + 60, 118 + 130 - bob * 0.6, s);
|
|
586
|
+
lc.filter = 'blur(16px)';
|
|
587
|
+
poly(lc, [G(0, 0, -TH), G(TILE, 0, -TH), G(TILE + 3, TILE, -TH), G(3, TILE, -TH)].map(p => p), 'rgba(90,80,150,1)');
|
|
588
|
+
ctx.globalAlpha = 0.16 * rise; ctx.drawImage(L, 0, 0); ctx.globalAlpha = ph(0, 0.45);
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ---------- floating tile slab ----------
|
|
592
|
+
const slab = shade('#cfc6ea');
|
|
593
|
+
// under-layer (soil) slightly inset
|
|
594
|
+
box(ctx, P, 0.35, 0.35, -TH - 0.7, TILE - 0.7, TILE - 0.7, 0.7, shade('#b9addf'), false);
|
|
595
|
+
box(ctx, P, 0, 0, -TH, TILE, TILE, TH, slab, false);
|
|
596
|
+
// floor top
|
|
597
|
+
poly(ctx, [P(0, 0, 0), P(TILE, 0, 0), P(TILE, TILE, 0), P(0, TILE, 0)], '#fbf8f3');
|
|
598
|
+
// paving grid
|
|
599
|
+
ctx.strokeStyle = 'rgba(170,160,205,0.22)'; ctx.lineWidth = 1;
|
|
600
|
+
ctx.beginPath();
|
|
601
|
+
for (let i = 1; i < TILE; i++) {
|
|
602
|
+
let a = P(i, 0, 0), b = P(i, TILE, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]);
|
|
603
|
+
a = P(0, i, 0); b = P(TILE, i, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]);
|
|
604
|
+
}
|
|
605
|
+
ctx.stroke();
|
|
606
|
+
// grass patch (left corner)
|
|
607
|
+
poly(ctx, [P(0.3, 7.3, 0), P(2.9, 7.3, 0), P(2.9, 11.7, 0), P(0.3, 11.7, 0)], '#d5efe1');
|
|
608
|
+
poly(ctx, [P(9.4, 9.2, 0), P(11.7, 9.2, 0), P(11.7, 11.7, 0), P(9.4, 11.7, 0)], '#d5efe1');
|
|
609
|
+
// walkway stripes
|
|
610
|
+
ctx.strokeStyle = 'rgba(255,200,120,0.9)'; ctx.lineWidth = 2; ctx.setLineDash([7, 7]);
|
|
611
|
+
ctx.beginPath(); { const a = P(3.2, 7.55, 0), b = P(9.0, 7.55, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
|
|
612
|
+
{ const a = P(3.2, 11.3, 0), b = P(9.0, 11.3, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
|
|
613
|
+
ctx.stroke(); ctx.setLineDash([]);
|
|
614
|
+
// painted floor marking "DC-01"
|
|
615
|
+
{
|
|
616
|
+
const o = P(8.9, 7.0, 0);
|
|
617
|
+
ctx.save(); ctx.transform(C30, 0.5, -C30, 0.5, o[0], o[1]);
|
|
618
|
+
ctx.font = '800 30px "Inter"'; ctx.fillStyle = 'rgba(160,150,205,0.45)'; ctx.fillText('DC-01', 0, 0);
|
|
619
|
+
ctx.restore();
|
|
620
|
+
}
|
|
621
|
+
// slab front edge highlight
|
|
622
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.95)'; ctx.lineWidth = 1.5;
|
|
623
|
+
ctx.beginPath(); { const a = P(0, TILE, 0), b = P(TILE, TILE, 0), c = P(TILE, 0, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(c[0], c[1]); } ctx.stroke();
|
|
624
|
+
|
|
625
|
+
ctx.globalAlpha = 1;
|
|
626
|
+
// ---------- scene objects ----------
|
|
627
|
+
const objs = [];
|
|
628
|
+
const shadowBoxes = []; // [x,y,w,d,h]
|
|
629
|
+
const add = (key, fn, at, base) => objs.push({ key, fn, at, base });
|
|
630
|
+
|
|
631
|
+
// main data hall (back)
|
|
632
|
+
const hall = { x: 0.7, y: 0.7, w: 5.2, d: 3.6, h: 3.0 };
|
|
633
|
+
shadowBoxes.push([hall.x, hall.y, hall.w, hall.d, hall.h, 0.75]);
|
|
634
|
+
add(hall.x + hall.w / 2 + hall.y + hall.d / 2, () => {
|
|
635
|
+
const c = shade('#dbe3f6');
|
|
636
|
+
box(ctx, P, hall.x, hall.y, 0, hall.w, hall.d, hall.h, c);
|
|
637
|
+
// window band on left (front) face
|
|
638
|
+
const y = hall.y + hall.d;
|
|
639
|
+
for (let i = 0; i < 6; i++) {
|
|
640
|
+
const x0 = hall.x + 0.3 + i * 0.8;
|
|
641
|
+
poly(ctx, [P(x0, y, 1.1), P(x0 + 0.6, y, 1.1), P(x0 + 0.6, y, 2.4), P(x0, y, 2.4)], '#a9b8e6');
|
|
642
|
+
poly(ctx, [P(x0, y, 1.9), P(x0 + 0.25, y, 2.4), P(x0 + 0.4, y, 2.4), P(x0 + 0.08, y, 1.75)], 'rgba(255,255,255,0.55)');
|
|
643
|
+
}
|
|
644
|
+
// door
|
|
645
|
+
poly(ctx, [P(hall.x + 4.75, y, 0), P(hall.x + 5.05, y, 0), P(hall.x + 5.05, y, 0.9), P(hall.x + 4.75, y, 0.9)], '#8e9bd0');
|
|
646
|
+
// vents on right face
|
|
647
|
+
const x = hall.x + hall.w;
|
|
648
|
+
for (let i = 0; i < 4; i++) {
|
|
649
|
+
const y0 = hall.y + 0.35 + i * 0.8;
|
|
650
|
+
for (let k = 0; k < 4; k++) {
|
|
651
|
+
const zz = 1.0 + k * 0.32;
|
|
652
|
+
poly(ctx, [P(x, y0, zz), P(x, y0 + 0.55, zz), P(x, y0 + 0.55, zz + 0.12), P(x, y0, zz + 0.12)], 'rgba(110,105,170,0.35)');
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
// signage strip
|
|
656
|
+
poly(ctx, [P(hall.x + 0.3, y, 2.55), P(hall.x + 2.6, y, 2.55), P(hall.x + 2.6, y, 2.82), P(hall.x + 0.3, y, 2.82)], '#ff6b5b');
|
|
657
|
+
// roof units: AC fans
|
|
658
|
+
for (let i = 0; i < 3; i++) {
|
|
659
|
+
const fx = hall.x + 0.5 + i * 1.6, fy = hall.y + 0.5;
|
|
660
|
+
box(ctx, P, fx, fy, hall.h, 1.2, 1.2, 0.35, shade('#eef1fa'));
|
|
661
|
+
const cpt = P(fx + 0.6, fy + 0.6, hall.h + 0.35);
|
|
662
|
+
ctx.fillStyle = '#9aa6d6'; ctx.beginPath(); ctx.ellipse(cpt[0], cpt[1], 0.45 * 1.2247 * s * 0.85, 0.45 * 0.7071 * s * 0.85, 0, 0, 7); ctx.fill();
|
|
663
|
+
ctx.save(); ctx.translate(cpt[0], cpt[1]); ctx.scale(1, 0.577);
|
|
664
|
+
ctx.rotate(t * 6 + i);
|
|
665
|
+
ctx.fillStyle = '#eef1fa';
|
|
666
|
+
for (let b = 0; b < 3; b++) { ctx.rotate(Math.PI * 2 / 3); ctx.beginPath(); ctx.ellipse(5, 0, 7, 2.6, 0, 0, 7); ctx.fill(); }
|
|
667
|
+
ctx.restore();
|
|
668
|
+
}
|
|
669
|
+
// solar panels on roof front
|
|
670
|
+
for (let i = 0; i < 4; i++) {
|
|
671
|
+
const px = hall.x + 0.4 + i * 1.2, py = hall.y + 2.1;
|
|
672
|
+
poly(ctx, [P(px, py, hall.h + 0.1), P(px + 1.0, py, hall.h + 0.1), P(px + 1.0, py + 1.1, hall.h + 0.45), P(px, py + 1.1, hall.h + 0.45)], '#7d8fd6');
|
|
673
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.45)'; ctx.lineWidth = 0.8; ctx.beginPath();
|
|
674
|
+
for (let k = 1; k < 3; k++) { const a = P(px + k / 3, py, hall.h + 0.1), b = P(px + k / 3, py + 1.1, hall.h + 0.45); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
|
|
675
|
+
const a = P(px, py + 0.55, hall.h + 0.275), b = P(px + 1, py + 0.55, hall.h + 0.275); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]);
|
|
676
|
+
ctx.stroke();
|
|
677
|
+
}
|
|
678
|
+
}, 0.75, P(hall.x + hall.w / 2, hall.y + hall.d / 2, 0));
|
|
679
|
+
|
|
680
|
+
// cooling tanks (right corner)
|
|
681
|
+
const tanks = [[9.2, 1.6, 0.85, 2.7], [10.6, 3.4, 0.75, 2.2]];
|
|
682
|
+
for (const [cx, cy, r, h] of tanks) {
|
|
683
|
+
shadowBoxes.push([cx - r * 0.8, cy - r * 0.8, r * 1.6, r * 1.6, h, 1.0 + (cx - 9.2) * 0.12]);
|
|
684
|
+
add(cx + cy, () => {
|
|
685
|
+
const base = H.hex('#bfe7da');
|
|
686
|
+
const c = cylinder(ctx, P, s, cx, cy, 0, r, h, base);
|
|
687
|
+
// bands
|
|
688
|
+
ctx.strokeStyle = 'rgba(90,150,140,0.35)'; ctx.lineWidth = 1.5;
|
|
689
|
+
for (const zz of [0.5, h - 0.5]) { const p = P(cx, cy, zz); ctx.beginPath(); ctx.ellipse(p[0], p[1], c.rx, c.ry, 0, 0, Math.PI); ctx.stroke(); }
|
|
690
|
+
// top rim + vent
|
|
691
|
+
ctx.fillStyle = H.rgba(H.mix(base, '#5a8f86', 0.35)); ctx.beginPath(); ctx.ellipse(c.tp[0], c.tp[1], c.rx * 0.45, c.ry * 0.45, 0, 0, 7); ctx.fill();
|
|
692
|
+
// ladder
|
|
693
|
+
ctx.strokeStyle = 'rgba(80,120,120,0.5)'; ctx.lineWidth = 1;
|
|
694
|
+
const lx = c.b[0] - c.rx * 0.35;
|
|
695
|
+
ctx.beginPath(); ctx.moveTo(lx, c.b[1] + c.ry * 0.93); ctx.lineTo(lx, c.tp[1] + c.ry * 0.93);
|
|
696
|
+
ctx.moveTo(lx + 6, c.b[1] + c.ry * 0.9); ctx.lineTo(lx + 6, c.tp[1] + c.ry * 0.9);
|
|
697
|
+
for (let yy = c.tp[1] + c.ry + 4; yy < c.b[1] + c.ry; yy += 6) { ctx.moveTo(lx, yy); ctx.lineTo(lx + 6, yy); }
|
|
698
|
+
ctx.stroke();
|
|
699
|
+
}, 1.0 + (cx - 9.2) * 0.12, P(cx, cy, 0));
|
|
700
|
+
}
|
|
701
|
+
// pipes: from tanks to hall (drawn at depth just after hall, before racks)
|
|
702
|
+
add(5.5, () => {
|
|
703
|
+
const pipe = (pts, col, wpx) => {
|
|
704
|
+
const sp = pts.map(p => P(...p));
|
|
705
|
+
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
|
|
706
|
+
ctx.strokeStyle = H.rgba(H.mix(col, SHADE_DARK, 0.3)); ctx.lineWidth = wpx;
|
|
707
|
+
ctx.beginPath(); sp.forEach((p, i) => i ? ctx.lineTo(p[0], p[1]) : ctx.moveTo(p[0], p[1])); ctx.stroke();
|
|
708
|
+
ctx.strokeStyle = H.rgba(col); ctx.lineWidth = wpx * 0.6;
|
|
709
|
+
ctx.beginPath(); sp.forEach((p, i) => i ? ctx.lineTo(p[0], p[1] - wpx * 0.15) : ctx.moveTo(p[0], p[1] - wpx * 0.15)); ctx.stroke();
|
|
710
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.7)'; ctx.lineWidth = 1.2;
|
|
711
|
+
ctx.beginPath(); sp.forEach((p, i) => i ? ctx.lineTo(p[0], p[1] - wpx * 0.3) : ctx.moveTo(p[0], p[1] - wpx * 0.3)); ctx.stroke();
|
|
712
|
+
// flanges
|
|
713
|
+
for (let i = 1; i < sp.length - 1; i++) { ctx.fillStyle = H.rgba(H.mix(col, SHADE_DARK, 0.15)); ctx.beginPath(); ctx.arc(sp[i][0], sp[i][1], wpx * 0.62, 0, 7); ctx.fill(); }
|
|
714
|
+
};
|
|
715
|
+
pipe([[8.4, 1.4, 1.6], [6.6, 1.4, 1.6], [6.6, 1.4, 1.6], [hall.x + hall.w, 1.4, 1.6]], H.hex('#c8d2f0'), 9);
|
|
716
|
+
pipe([[9.9, 3.4, 1.0], [7.2, 3.4, 1.0], [7.2, 3.4, 1.0], [hall.x + hall.w, 3.4, 1.0]], H.hex('#ffd2c4'), 7);
|
|
717
|
+
// pipe supports
|
|
718
|
+
for (const xx of [6.9, 7.8]) box(ctx, P, xx, 1.25, 0, 0.18, 0.3, 1.45, shade('#d8d4ea'), false);
|
|
719
|
+
}, 1.45, P(7.5, 2.4, 1.3));
|
|
720
|
+
|
|
721
|
+
// server racks (two rows, open-air cutaway)
|
|
722
|
+
const racks = [];
|
|
723
|
+
const rowY = [5.4, 8.1];
|
|
724
|
+
for (let r = 0; r < 2; r++) for (let i = 0; i < 4; i++) {
|
|
725
|
+
const accent = r === 0 && i === 2;
|
|
726
|
+
racks.push({ x: 3.4 + i * 1.3, y: rowY[r], w: 1.05, d: 0.95, h: accent ? 2.5 : 2.15, accent, id: r * 4 + i });
|
|
727
|
+
}
|
|
728
|
+
for (const rk of racks) {
|
|
729
|
+
shadowBoxes.push([rk.x, rk.y, rk.w, rk.d, rk.h, 1.15 + rk.id * 0.08]);
|
|
730
|
+
add(rk.x + rk.w / 2 + rk.y + rk.d / 2, () => {
|
|
731
|
+
const base = rk.accent ? '#ff8a7a' : '#aab6e8';
|
|
732
|
+
const c = shade(base);
|
|
733
|
+
box(ctx, P, rk.x, rk.y, 0, rk.w, rk.d, rk.h, c);
|
|
734
|
+
// front panel inset on left face (facing viewer)
|
|
735
|
+
const y = rk.y + rk.d;
|
|
736
|
+
const fx0 = rk.x + 0.1, fx1 = rk.x + rk.w - 0.1;
|
|
737
|
+
poly(ctx, [P(fx0, y, 0.12), P(fx1, y, 0.12), P(fx1, y, rk.h - 0.12), P(fx0, y, rk.h - 0.12)], rk.accent ? '#7a3a52' : '#4a5488');
|
|
738
|
+
const units = rk.accent ? 7 : 6;
|
|
739
|
+
const uh = (rk.h - 0.3) / units;
|
|
740
|
+
const lr = H.rng(100 + rk.id);
|
|
741
|
+
for (let u = 0; u < units; u++) {
|
|
742
|
+
const z0 = 0.17 + u * uh;
|
|
743
|
+
poly(ctx, [P(fx0 + 0.05, y, z0), P(fx1 - 0.05, y, z0), P(fx1 - 0.05, y, z0 + uh * 0.72), P(fx0 + 0.05, y, z0 + uh * 0.72)], rk.accent ? '#93465f' : '#5d68a0');
|
|
744
|
+
// slats
|
|
745
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.12)'; ctx.lineWidth = 0.8; ctx.beginPath();
|
|
746
|
+
for (let k = 0; k < 4; k++) { const a = P(fx0 + 0.33 + k * 0.1, y, z0 + 0.06), b = P(fx0 + 0.33 + k * 0.1, y, z0 + uh * 0.66); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); }
|
|
747
|
+
ctx.stroke();
|
|
748
|
+
// LEDs
|
|
749
|
+
for (let k = 0; k < 2; k++) {
|
|
750
|
+
const ph = lr() * 10, rate = lr.range(2, 6);
|
|
751
|
+
const on = Math.sin(t * rate + ph) > -0.2;
|
|
752
|
+
const lp = P(fx0 + 0.13 + k * 0.1, y, z0 + uh * 0.36);
|
|
753
|
+
const col = rk.accent ? '#ffe28a' : (lr() < 0.2 ? '#ffb38a' : '#8ff0c8');
|
|
754
|
+
if (on) {
|
|
755
|
+
ctx.fillStyle = H.rgba(col, 0.35); ctx.beginPath(); ctx.arc(lp[0], lp[1], 3.6, 0, 7); ctx.fill();
|
|
756
|
+
ctx.fillStyle = col;
|
|
757
|
+
} else ctx.fillStyle = 'rgba(30,30,60,0.6)';
|
|
758
|
+
ctx.beginPath(); ctx.arc(lp[0], lp[1], 1.5, 0, 7); ctx.fill();
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
// top label strip on the right face
|
|
762
|
+
poly(ctx, [P(rk.x + rk.w, rk.y + 0.15, rk.h - 0.35), P(rk.x + rk.w, rk.y + rk.d - 0.15, rk.h - 0.35), P(rk.x + rk.w, rk.y + rk.d - 0.15, rk.h - 0.2), P(rk.x + rk.w, rk.y + 0.15, rk.h - 0.2)], 'rgba(255,255,255,0.35)');
|
|
763
|
+
if (rk.accent) {
|
|
764
|
+
// glow halo on top
|
|
765
|
+
const tp = P(rk.x + rk.w / 2, rk.y + rk.d / 2, rk.h);
|
|
766
|
+
const g = ctx.createRadialGradient(tp[0], tp[1], 2, tp[0], tp[1], 60);
|
|
767
|
+
g.addColorStop(0, 'rgba(255,120,100,0.35)'); g.addColorStop(1, 'rgba(255,120,100,0)');
|
|
768
|
+
ctx.fillStyle = g; ctx.beginPath(); ctx.arc(tp[0], tp[1], 60, 0, 7); ctx.fill();
|
|
769
|
+
}
|
|
770
|
+
}, 1.15 + rk.id * 0.08, P(rk.x + rk.w / 2, rk.y + rk.d / 2, 0));
|
|
771
|
+
}
|
|
772
|
+
// overhead cable tray along each row (drawn right after the row, before the next row)
|
|
773
|
+
for (let r = 0; r < 2; r++) {
|
|
774
|
+
const ry = rowY[r] + 0.3, z = 2.62, x0 = 3.25, x1 = 8.6;
|
|
775
|
+
const lastKey = racks[r * 4 + 3].x + 0.53 + rowY[r] + 0.48;
|
|
776
|
+
add(lastKey + 0.01, () => {
|
|
777
|
+
box(ctx, P, x0, ry, z, x1 - x0, 0.35, 0.1, shade('#d9d6ee'));
|
|
778
|
+
// cables lying in the tray
|
|
779
|
+
const cols = ['#4a5488', '#ff6b5b', '#7fc8a8'];
|
|
780
|
+
cols.forEach((c, k) => {
|
|
781
|
+
const a = P(x0 + 0.05, ry + 0.09 + k * 0.09, z + 0.12), b = P(x1 - 0.05, ry + 0.09 + k * 0.09, z + 0.12);
|
|
782
|
+
ctx.strokeStyle = c; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.stroke();
|
|
783
|
+
});
|
|
784
|
+
// drops into racks
|
|
785
|
+
for (let i = 0; i < 4; i++) {
|
|
786
|
+
const rk = racks[r * 4 + i];
|
|
787
|
+
const a = P(rk.x + 0.5, ry + 0.2, z), b = P(rk.x + 0.5, ry + 0.2, rk.h);
|
|
788
|
+
ctx.strokeStyle = i === 2 && r === 0 ? '#ff6b5b' : '#4a5488'; ctx.lineWidth = 1.6;
|
|
789
|
+
ctx.beginPath(); ctx.moveTo(a[0], a[1]); ctx.bezierCurveTo(a[0] + 3, a[1] + 4, b[0] - 3, b[1] - 4, b[0], b[1]); ctx.stroke();
|
|
790
|
+
}
|
|
791
|
+
}, 1.95 + r * 0.12, P(5.9, ry, 2.62));
|
|
792
|
+
}
|
|
793
|
+
// ground cable run from rack row to hall (a channel on the floor)
|
|
794
|
+
add(4.0, () => {
|
|
795
|
+
ctx.strokeStyle = '#6b74ad'; ctx.lineWidth = 2.2; ctx.lineCap = 'round';
|
|
796
|
+
for (let k = 0; k < 3; k++) {
|
|
797
|
+
const pts = [P(3.0 + k * 0.15, 5.9, 0.02), P(3.0 + k * 0.15, 4.3 + 0.0, 0.02)];
|
|
798
|
+
ctx.strokeStyle = k === 1 ? '#ff6b5b' : '#6b74ad';
|
|
799
|
+
ctx.beginPath(); ctx.moveTo(...pts[0]); ctx.lineTo(...pts[1]); ctx.stroke();
|
|
800
|
+
}
|
|
801
|
+
}, 1.9, P(3.1, 5.1, 0));
|
|
802
|
+
|
|
803
|
+
// trees
|
|
804
|
+
const trees = [[1.0, 8.2, 1.0], [1.9, 9.6, 1.25], [0.9, 10.9, 0.85], [10.0, 10.4, 1.05], [11.1, 11.0, 0.8], [7.4, 0.9, 0.0]];
|
|
805
|
+
for (const [tx, ty, sc] of trees) {
|
|
806
|
+
if (!sc) continue;
|
|
807
|
+
shadowBoxes.push([tx - 0.3 * sc, ty - 0.3 * sc, 0.6 * sc, 0.6 * sc, 2.0 * sc, 0.95 + (tx + ty) * 0.035]);
|
|
808
|
+
add(tx + ty, () => {
|
|
809
|
+
const b = P(tx, ty, 0), top = P(tx, ty, 1.5 * sc);
|
|
810
|
+
ctx.strokeStyle = '#b79a8a'; ctx.lineWidth = 3; ctx.beginPath(); ctx.moveTo(b[0], b[1]); ctx.lineTo(top[0], top[1] + 4); ctx.stroke();
|
|
811
|
+
const sway = Math.sin(t * 1.5 + tx) * 1.2;
|
|
812
|
+
const rr = 0.62 * sc * s;
|
|
813
|
+
const cxp = top[0] + sway, cyp = top[1] - rr * 0.3;
|
|
814
|
+
ctx.fillStyle = '#7fc8a8'; ctx.beginPath(); ctx.arc(cxp, cyp, rr, 0, 7); ctx.fill();
|
|
815
|
+
ctx.fillStyle = '#a3dcc0'; ctx.beginPath(); ctx.arc(cxp - rr * 0.22, cyp - rr * 0.2, rr * 0.72, 0, 7); ctx.fill();
|
|
816
|
+
ctx.fillStyle = '#c4ecd6'; ctx.beginPath(); ctx.arc(cxp - rr * 0.4, cyp - rr * 0.42, rr * 0.25, 0, 7); ctx.fill();
|
|
817
|
+
// shading crescent
|
|
818
|
+
ctx.save(); ctx.beginPath(); ctx.arc(cxp, cyp, rr, 0, 7); ctx.clip();
|
|
819
|
+
ctx.fillStyle = 'rgba(80,120,140,0.18)'; ctx.beginPath(); ctx.arc(cxp - rr * 0.6, cyp - rr * 0.6, rr * 1.35, 0, 7); ctx.arc(cxp, cyp, rr * 2, 0, 7, true); ctx.fill('evenodd');
|
|
820
|
+
ctx.restore();
|
|
821
|
+
}, 0.95 + (tx + ty) * 0.035, P(tx, ty, 0));
|
|
822
|
+
}
|
|
823
|
+
// bench + lamp post
|
|
824
|
+
add(2.6 + 7.9, () => { box(ctx, P, 2.3, 7.7, 0, 0.35, 1.0, 0.35, shade('#e7d6c9')); }, 1.5, P(2.5, 8.2, 0));
|
|
825
|
+
|
|
826
|
+
// people
|
|
827
|
+
const person = (x, y, shirt, face, carry) => {
|
|
828
|
+
const p = P(x, y, 0);
|
|
829
|
+
const hh = 0.95 * s;
|
|
830
|
+
ctx.fillStyle = 'rgba(80,70,140,0.18)'; ctx.beginPath(); ctx.ellipse(p[0] + 4, p[1] + 1, 6, 2.5, 0, 0, 7); ctx.fill();
|
|
831
|
+
ctx.strokeStyle = '#4a4f78'; ctx.lineWidth = 2.6; ctx.lineCap = 'round';
|
|
832
|
+
ctx.beginPath(); ctx.moveTo(p[0] - 2, p[1]); ctx.lineTo(p[0] - 1.5, p[1] - hh * 0.42); ctx.moveTo(p[0] + 2, p[1] - 1); ctx.lineTo(p[0] + 1.5, p[1] - hh * 0.42); ctx.stroke();
|
|
833
|
+
ctx.fillStyle = shirt;
|
|
834
|
+
ctx.beginPath(); ctx.roundRect(p[0] - 4.5, p[1] - hh * 0.8, 9, hh * 0.42, 3.5); ctx.fill();
|
|
835
|
+
ctx.fillStyle = 'rgba(255,255,255,0.35)'; ctx.fillRect(p[0] - 4.5, p[1] - hh * 0.78, 3, hh * 0.36);
|
|
836
|
+
ctx.fillStyle = '#f1c6a8'; ctx.beginPath(); ctx.arc(p[0], p[1] - hh * 0.9, 3.8, 0, 7); ctx.fill();
|
|
837
|
+
ctx.fillStyle = face; ctx.beginPath(); ctx.arc(p[0] + 0.6, p[1] - hh * 0.93, 3.9, Math.PI * 1.05, Math.PI * 2.05); ctx.fill();
|
|
838
|
+
if (carry) { ctx.fillStyle = '#e9ecf8'; ctx.save(); ctx.translate(p[0] + 5, p[1] - hh * 0.6); ctx.transform(1, -0.5, 0, 1, 0, 0); ctx.fillRect(0, 0, 8, 5); ctx.fillStyle = '#ff6b5b'; ctx.fillRect(1, 1, 6, 3); ctx.restore(); }
|
|
839
|
+
};
|
|
840
|
+
const walk = H.clamp((t - 2.7) * 0.32);
|
|
841
|
+
add(7.0 + 7.3, () => person(6.6, 7.2, '#ff6b5b', '#3d3550', true), 2.25, P(6.6, 7.2, 0));
|
|
842
|
+
add(3.0 + 7.4, () => person(3.0, 7.6, '#7f8fd8', '#6b4a3a', false), 2.4, P(3.0, 7.6, 0));
|
|
843
|
+
add(9.6 + 5.1, () => person(9.6, 5.1, '#9fd8c2', '#2f2a40', false), 2.55, P(9.6, 5.1, 0));
|
|
844
|
+
add(4.2 + 10.9 + walk * 3, () => person(4.2 + walk * 3, 10.9, '#f5b78f', '#3d3550', false), 2.7, P(4.2 + walk * 3, 10.9, 0));
|
|
845
|
+
|
|
846
|
+
// rising data cubes from the GPU rack
|
|
847
|
+
const acc = racks[2];
|
|
848
|
+
add(99, () => {
|
|
849
|
+
for (let i = 0; i < 6; i++) {
|
|
850
|
+
const ph = (Math.max(0, t - 2.4) * 0.45 + i / 6) % 1;
|
|
851
|
+
if (t < 2.4 + i * 0.37) continue;
|
|
852
|
+
const z = acc.h + 0.3 + ph * 3.2;
|
|
853
|
+
const ox = acc.x + acc.w / 2 - 0.15 + Math.sin(i * 2.1) * 0.35, oy = acc.y + acc.d / 2 - 0.15 + Math.cos(i * 1.7) * 0.35;
|
|
854
|
+
const a = Math.sin(ph * Math.PI);
|
|
855
|
+
ctx.globalAlpha = a;
|
|
856
|
+
const sz = 0.26 * (1 - ph * 0.4);
|
|
857
|
+
box(ctx, P, ox, oy, z, sz, sz, sz, shade(i % 2 ? '#ff8a7a' : '#ffc2b4'), false);
|
|
858
|
+
ctx.globalAlpha = 1;
|
|
859
|
+
}
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
// ---------- shadows on floor (single layer, no doubled overlaps) ----------
|
|
863
|
+
{
|
|
864
|
+
const L = H.layer(W, H_), lc = L.getContext('2d');
|
|
865
|
+
const sv = [1.15, 0.12];
|
|
866
|
+
lc.fillStyle = '#000';
|
|
867
|
+
for (const [x, y, w, d, h0, at] of shadowBoxes) {
|
|
868
|
+
const kk = H.ease.out(ph(at, at + 0.5)); if (kk <= 0) continue;
|
|
869
|
+
const h = h0 * kk;
|
|
870
|
+
const base = [[x, y], [x + w, y], [x + w, y + d], [x, y + d]];
|
|
871
|
+
const pts = base.concat(base.map(p => [p[0] + sv[0] * h, p[1] + sv[1] * h]));
|
|
872
|
+
const hl = hull(pts).map(p => P(p[0], p[1], 0));
|
|
873
|
+
poly(lc, hl, '#000');
|
|
874
|
+
}
|
|
875
|
+
lc.globalCompositeOperation = 'destination-in';
|
|
876
|
+
poly(lc, [P(0, 0, 0), P(TILE, 0, 0), P(TILE, TILE, 0), P(0, TILE, 0)], '#000');
|
|
877
|
+
lc.globalCompositeOperation = 'source-in';
|
|
878
|
+
lc.fillStyle = 'rgb(110,96,170)'; lc.fillRect(0, 0, W, H_);
|
|
879
|
+
ctx.globalAlpha = 0.17; ctx.drawImage(L, 0, 0); ctx.globalAlpha = 1;
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
// ---------- painter's sort + draw ----------
|
|
883
|
+
objs.sort((a, b) => a.key - b.key);
|
|
884
|
+
for (const o of objs) {
|
|
885
|
+
const k = o.at == null ? 1 : H.ease.back(ph(o.at, o.at + 0.5));
|
|
886
|
+
if (k <= 0.001) continue;
|
|
887
|
+
ctx.save();
|
|
888
|
+
if (k !== 1 && o.base) { ctx.translate(o.base[0], o.base[1]); ctx.scale(k, k); ctx.translate(-o.base[0], -o.base[1]); }
|
|
889
|
+
o.fn(); ctx.restore();
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// small floating debris cubes under the island
|
|
893
|
+
for (const [x, y, z, sz] of [[6.5, 13.4, -2.9, 0.45], [12.6, 12.0, -3.2, 0.35], [13.2, 6.5, -2.4, 0.42]]) {
|
|
894
|
+
box(ctx, P, x, y, z + Math.sin(t + x) * 0.15 - (1 - rise) * 2, sz, sz, sz, shade('#cfc6ea'), false);
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// ---------- satellite mini-tile (lower left) with data link ----------
|
|
898
|
+
{
|
|
899
|
+
const b2 = Math.sin(t * 1.2 + 1.3) * 5;
|
|
900
|
+
const mt = H.ease.back(ph(1.3, 1.95));
|
|
901
|
+
const Q = makeIso(250, 452 + b2 + (1 - mt) * 140, 35);
|
|
902
|
+
ctx.save(); ctx.globalAlpha = ph(1.3, 1.6);
|
|
903
|
+
// its soft shadow
|
|
904
|
+
const L = H.layer(W, H_), lc = L.getContext('2d');
|
|
905
|
+
const G = makeIso(250 + 30, 452 + 70, 35);
|
|
906
|
+
lc.filter = 'blur(12px)';
|
|
907
|
+
poly(lc, [G(0, 0, 0), G(3, 0, 0), G(4, 3, 0), G(1, 3, 0)], 'rgb(90,80,150)');
|
|
908
|
+
ctx.globalAlpha = 0.14 * ph(1.3, 1.9); ctx.drawImage(L, 0, 0); ctx.globalAlpha = ph(1.3, 1.6);
|
|
909
|
+
box(ctx, Q, 0.15, 0.15, -1.2, 2.7, 2.7, 0.5, shade('#b9addf'), false);
|
|
910
|
+
box(ctx, Q, 0, 0, -0.7, 3, 3, 0.7, shade('#cfc6ea'), false);
|
|
911
|
+
poly(ctx, [Q(0, 0, 0), Q(3, 0, 0), Q(3, 3, 0), Q(0, 3, 0)], '#fbf8f3');
|
|
912
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.95)'; ctx.lineWidth = 1.5;
|
|
913
|
+
ctx.beginPath(); { const a = Q(0, 3, 0), b = Q(3, 3, 0), c = Q(3, 0, 0); ctx.moveTo(a[0], a[1]); ctx.lineTo(b[0], b[1]); ctx.lineTo(c[0], c[1]); } ctx.stroke();
|
|
914
|
+
// shadow of dish base
|
|
915
|
+
poly(ctx, [Q(1.0, 1.0, 0), Q(2.6, 1.1, 0), Q(2.6, 2.0, 0), Q(1.0, 2.0, 0)], 'rgba(110,96,170,0.16)');
|
|
916
|
+
box(ctx, Q, 1.0, 1.0, 0, 1.0, 1.0, 0.55, shade('#dbe3f6'));
|
|
917
|
+
{ const tb = Q(2.4, 0.55, 0), tt = Q(2.4, 0.55, 0.9); const rr = 11;
|
|
918
|
+
poly(ctx, [Q(2.25, 0.4, 0), Q(2.95, 0.45, 0), Q(2.95, 0.75, 0), Q(2.25, 0.7, 0)], 'rgba(110,96,170,0.14)');
|
|
919
|
+
ctx.strokeStyle = '#b79a8a'; ctx.lineWidth = 2.5; ctx.beginPath(); ctx.moveTo(tb[0], tb[1]); ctx.lineTo(tt[0], tt[1]); ctx.stroke();
|
|
920
|
+
ctx.fillStyle = '#7fc8a8'; ctx.beginPath(); ctx.arc(tt[0], tt[1] - 4, rr, 0, 7); ctx.fill();
|
|
921
|
+
ctx.fillStyle = '#a3dcc0'; ctx.beginPath(); ctx.arc(tt[0] - 2.5, tt[1] - 6.5, rr * 0.72, 0, 7); ctx.fill(); }
|
|
922
|
+
const pole = Q(1.5, 1.5, 0.55), poleTop = Q(1.5, 1.5, 1.4);
|
|
923
|
+
ctx.strokeStyle = '#8e9bd0'; ctx.lineWidth = 4; ctx.beginPath(); ctx.moveTo(pole[0], pole[1]); ctx.lineTo(poleTop[0], poleTop[1]); ctx.stroke();
|
|
924
|
+
// dish (tilted toward the main island)
|
|
925
|
+
ctx.save(); ctx.translate(poleTop[0] + 4, poleTop[1] - 8); ctx.rotate(0.6);
|
|
926
|
+
const dg = ctx.createLinearGradient(-30, -30, 30, 30);
|
|
927
|
+
dg.addColorStop(0, '#ffffff'); dg.addColorStop(1, '#c9cfee');
|
|
928
|
+
ctx.fillStyle = '#a9b3e0'; ctx.beginPath(); ctx.ellipse(2, 3, 34, 15, 0, 0, 7); ctx.fill();
|
|
929
|
+
ctx.fillStyle = dg; ctx.beginPath(); ctx.ellipse(0, 0, 34, 15, 0, 0, 7); ctx.fill();
|
|
930
|
+
ctx.strokeStyle = 'rgba(140,150,200,0.5)'; ctx.lineWidth = 1; ctx.beginPath(); ctx.ellipse(0, 0, 20, 8.5, 0, 0, 7); ctx.stroke();
|
|
931
|
+
ctx.strokeStyle = '#8e9bd0'; ctx.lineWidth = 1.6; ctx.beginPath(); ctx.moveTo(-20, 4); ctx.lineTo(0, -26); ctx.lineTo(20, 4); ctx.stroke();
|
|
932
|
+
ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.arc(0, -27, 3.5, 0, 7); ctx.fill();
|
|
933
|
+
ctx.restore();
|
|
934
|
+
// dashed data arc to the hall roof
|
|
935
|
+
const from = [poleTop[0] + 4 + Math.sin(0.6) * 27, poleTop[1] - 8 - Math.cos(0.6) * 27];
|
|
936
|
+
const to = P(3.3, 5.75, 2.75);
|
|
937
|
+
const cx = (from[0] + to[0]) / 2 - 50, cy = (from[1] + to[1]) / 2 - 110;
|
|
938
|
+
ctx.restore();
|
|
939
|
+
const arcP = H.ease.inOut(ph(2.1, 2.9));
|
|
940
|
+
const qb = (u) => [(1 - u) * (1 - u) * from[0] + 2 * u * (1 - u) * cx + u * u * to[0], (1 - u) * (1 - u) * from[1] + 2 * u * (1 - u) * cy + u * u * to[1]];
|
|
941
|
+
ctx.strokeStyle = 'rgba(255,107,91,0.7)'; ctx.lineWidth = 1.6; ctx.setLineDash([4, 6]); ctx.lineDashOffset = -t * 30;
|
|
942
|
+
ctx.beginPath(); ctx.moveTo(from[0], from[1]); for (let k = 1; k <= 40; k++) { const q = qb(arcP * k / 40); ctx.lineTo(q[0], q[1]); } if (arcP > 0) ctx.stroke(); ctx.setLineDash([]);
|
|
943
|
+
for (let i = 0; i < 3; i++) {
|
|
944
|
+
if (t < 2.9 + i * 0.5) continue;
|
|
945
|
+
const u = ((t - 2.9 - i * 0.5) * 0.3 + i / 3) % 1;
|
|
946
|
+
const x = (1 - u) * (1 - u) * from[0] + 2 * u * (1 - u) * cx + u * u * to[0];
|
|
947
|
+
const y = (1 - u) * (1 - u) * from[1] + 2 * u * (1 - u) * cy + u * u * to[1];
|
|
948
|
+
ctx.fillStyle = 'rgba(255,107,91,0.25)'; ctx.beginPath(); ctx.arc(x, y, 7, 0, 7); ctx.fill();
|
|
949
|
+
ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.arc(x, y, 3, 0, 7); ctx.fill();
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// ---------- label tag ----------
|
|
954
|
+
{
|
|
955
|
+
const anchor = P(acc.x + acc.w / 2, acc.y + acc.d / 2, acc.h + 0.1);
|
|
956
|
+
const tx = anchor[0] + 120, ty = anchor[1] - 150;
|
|
957
|
+
const lp = H.ease.out(ph(2.75, 3.15)), tp = H.ease.back(ph(3.05, 3.45));
|
|
958
|
+
ctx.save(); if (lp <= 0) ctx.globalAlpha = 0;
|
|
959
|
+
ctx.strokeStyle = '#4a4f78'; ctx.lineWidth = 1.3;
|
|
960
|
+
const l1 = anchor[1] - (ty + 30), l2 = tx - 4 - anchor[0], dl = (l1 + l2) * lp;
|
|
961
|
+
ctx.beginPath(); ctx.moveTo(anchor[0], anchor[1]); ctx.lineTo(anchor[0], anchor[1] - Math.min(dl, l1)); if (dl > l1) ctx.lineTo(anchor[0] + (dl - l1), ty + 30); ctx.stroke();
|
|
962
|
+
ctx.fillStyle = '#4a4f78'; ctx.beginPath(); ctx.arc(anchor[0], anchor[1], 3.5, 0, 7); ctx.fill();
|
|
963
|
+
ctx.fillStyle = '#fff'; ctx.beginPath(); ctx.arc(anchor[0], anchor[1], 1.5, 0, 7); ctx.fill();
|
|
964
|
+
ctx.font = '600 13px "Inter"';
|
|
965
|
+
const label = 'GPU CLUSTER', sub = '1.2 PFLOPS · 74°F';
|
|
966
|
+
const wbox = Math.max(ctx.measureText(label).width, 0) + 54;
|
|
967
|
+
ctx.globalAlpha = tp > 0 ? Math.min(1, ph(3.05, 3.25)) : 0;
|
|
968
|
+
ctx.translate(tx, ty + 31); ctx.scale(Math.max(0.001, tp), Math.max(0.001, tp)); ctx.translate(-tx, -(ty + 31));
|
|
969
|
+
ctx.save(); ctx.shadowColor = 'rgba(80,70,140,0.18)'; ctx.shadowBlur = 18; ctx.shadowOffsetY = 6;
|
|
970
|
+
ctx.fillStyle = '#ffffff'; ctx.beginPath(); ctx.roundRect(tx, ty + 6, wbox + 40, 50, 12); ctx.fill(); ctx.restore();
|
|
971
|
+
ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.roundRect(tx + 12, ty + 18, 26, 26, 7); ctx.fill();
|
|
972
|
+
// tiny chip glyph
|
|
973
|
+
ctx.strokeStyle = '#fff'; ctx.lineWidth = 1.6; ctx.strokeRect(tx + 19, ty + 25, 12, 12);
|
|
974
|
+
ctx.beginPath(); for (let k = 0; k < 3; k++) { ctx.moveTo(tx + 22 + k * 3, ty + 22); ctx.lineTo(tx + 22 + k * 3, ty + 25); ctx.moveTo(tx + 22 + k * 3, ty + 37); ctx.lineTo(tx + 22 + k * 3, ty + 40); } ctx.stroke();
|
|
975
|
+
ctx.fillStyle = '#2d3058'; ctx.fillText(label, tx + 48, ty + 29);
|
|
976
|
+
ctx.font = '400 12px "Inter"'; ctx.fillStyle = '#8a88ad'; ctx.fillText(sub, tx + 48, ty + 46);
|
|
977
|
+
ctx.restore();
|
|
978
|
+
}
|
|
979
|
+
ctx.restore(); // end camera push
|
|
980
|
+
|
|
981
|
+
// ---------- title ----------
|
|
982
|
+
const bw = 34 * H.ease.out(ph(0.25, 0.7));
|
|
983
|
+
if (bw > 0.5) { ctx.fillStyle = '#ff6b5b'; ctx.beginPath(); ctx.roundRect(72, 92, bw, 6, 3); ctx.fill(); }
|
|
984
|
+
const t1 = H.ease.out(ph(0.4, 0.95)), t2 = H.ease.out(ph(0.65, 1.2));
|
|
985
|
+
ctx.save(); ctx.globalAlpha = t1; ctx.font = '800 46px "Inter"'; ctx.fillStyle = '#2d3058';
|
|
986
|
+
ctx.fillText('Where AI lives', 70, 152 + (1 - t1) * 18); ctx.restore();
|
|
987
|
+
ctx.save(); ctx.globalAlpha = t2; ctx.font = '400 18px "Inter"'; ctx.fillStyle = '#7b7aa3';
|
|
988
|
+
ctx.fillText('Inside a modern data center', 72, 186 + (1 - t2) * 12); ctx.restore();
|
|
989
|
+
},
|
|
990
|
+
});
|
|
991
|
+
}
|