dsh-drawai 0.3.3 → 0.4.0

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.
Files changed (533) hide show
  1. package/LICENSE +4142 -0
  2. package/README.md +43 -10
  3. package/lib/audio-media.js +147 -0
  4. package/lib/audio-service.js +66 -0
  5. package/lib/canvas-format.js +134 -0
  6. package/lib/client.js +5283 -223
  7. package/lib/comfyui-default-workflow.mjs +1825 -0
  8. package/lib/comfyui-workflow.mjs +426 -0
  9. package/lib/document-commands.js +12 -0
  10. package/lib/document-core.js +29 -0
  11. package/lib/drawing-tasks.js +26 -0
  12. package/lib/imagegen.mjs +596 -0
  13. package/lib/index.js +1758 -129
  14. package/lib/media.js +26 -13
  15. package/lib/mgstyle-core.js +75 -0
  16. package/lib/motion/apply.js +180 -0
  17. package/lib/motion/asset-pack-lock.js +10 -0
  18. package/lib/motion/asset-pack.js +335 -0
  19. package/lib/motion/assets.js +290 -0
  20. package/lib/motion/audio.js +103 -0
  21. package/lib/motion/audiolevel.js +63 -0
  22. package/lib/motion/audiomix.js +144 -0
  23. package/lib/motion/beats.js +337 -0
  24. package/lib/motion/builtin-assets.js +62 -0
  25. package/lib/motion/canvas-spec.js +442 -0
  26. package/lib/motion/capture.js +95 -0
  27. package/lib/motion/cards.js +80 -0
  28. package/lib/motion/collect.js +447 -0
  29. package/lib/motion/delivery.js +112 -0
  30. package/lib/motion/edge-speech.py +90 -0
  31. package/lib/motion/entry.jsx +73 -0
  32. package/lib/motion/export.js +200 -0
  33. package/lib/motion/ffmpeg.js +42 -0
  34. package/lib/motion/fonts.js +71 -0
  35. package/lib/motion/jobs.js +113 -0
  36. package/lib/motion/json.js +4 -0
  37. package/lib/motion/media-catalog.js +59 -0
  38. package/lib/motion/mgstyles/BRIEF-TEMPLATE.md +63 -0
  39. package/lib/motion/mgstyles/briefs/01-flat-vector.md +104 -0
  40. package/lib/motion/mgstyles/briefs/02-line-art.md +97 -0
  41. package/lib/motion/mgstyles/briefs/03-isometric.md +108 -0
  42. package/lib/motion/mgstyles/briefs/05-cel-boil.md +145 -0
  43. package/lib/motion/mgstyles/briefs/06-collage.md +149 -0
  44. package/lib/motion/mgstyles/briefs/07-liquid.md +106 -0
  45. package/lib/motion/mgstyles/briefs/08-morph.md +111 -0
  46. package/lib/motion/mgstyles/briefs/09-bauhaus.md +150 -0
  47. package/lib/motion/mgstyles/briefs/10-synthwave.md +151 -0
  48. package/lib/motion/mgstyles/briefs/12-aurora-glass.md +147 -0
  49. package/lib/motion/mgstyles/briefs/18-hanazi.md +140 -0
  50. package/lib/motion/mgstyles/briefs/19-paperclip.md +156 -0
  51. package/lib/motion/mgstyles/briefs/20-pixel.md +118 -0
  52. package/lib/motion/mgstyles/briefs/22-hud.md +118 -0
  53. package/lib/motion/mgstyles/catalog.d/01-flat-vector.json +34 -0
  54. package/lib/motion/mgstyles/catalog.d/02-line-art.json +34 -0
  55. package/lib/motion/mgstyles/catalog.d/03-isometric.json +35 -0
  56. package/lib/motion/mgstyles/catalog.d/05-cel-boil.json +28 -0
  57. package/lib/motion/mgstyles/catalog.d/06-collage.json +28 -0
  58. package/lib/motion/mgstyles/catalog.d/07-liquid.json +28 -0
  59. package/lib/motion/mgstyles/catalog.d/08-morph.json +28 -0
  60. package/lib/motion/mgstyles/catalog.d/09-bauhaus.json +28 -0
  61. package/lib/motion/mgstyles/catalog.d/10-synthwave.json +28 -0
  62. package/lib/motion/mgstyles/catalog.d/12-aurora-glass.json +28 -0
  63. package/lib/motion/mgstyles/catalog.d/18-hanazi.json +28 -0
  64. package/lib/motion/mgstyles/catalog.d/19-paperclip.json +28 -0
  65. package/lib/motion/mgstyles/catalog.d/20-pixel.json +28 -0
  66. package/lib/motion/mgstyles/catalog.d/22-hud.json +28 -0
  67. package/lib/motion/mgstyles/catalog.json +1356 -0
  68. package/lib/motion/mgstyles/upstream/README.md +147 -0
  69. package/lib/motion/mgstyles/upstream/README.zh-CN.md +147 -0
  70. package/lib/motion/mgstyles/upstream/demos/01-flat-vector/cues.json +1 -0
  71. package/lib/motion/mgstyles/upstream/demos/01-flat-vector/index.html +712 -0
  72. package/lib/motion/mgstyles/upstream/demos/02-line-art/cues.json +331 -0
  73. package/lib/motion/mgstyles/upstream/demos/02-line-art/film.js +676 -0
  74. package/lib/motion/mgstyles/upstream/demos/02-line-art/index.html +18 -0
  75. package/lib/motion/mgstyles/upstream/demos/02-line-art/tools/dump_timing.mjs +26 -0
  76. package/lib/motion/mgstyles/upstream/demos/03-isometric/cues.json +109 -0
  77. package/lib/motion/mgstyles/upstream/demos/03-isometric/index.html +25 -0
  78. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/anim.js +82 -0
  79. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/main.js +219 -0
  80. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/night5.js +180 -0
  81. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/render.js +202 -0
  82. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title.js +167 -0
  83. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title5.js +294 -0
  84. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/ui.js +141 -0
  85. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/world.js +1399 -0
  86. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/cues.json +157 -0
  87. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/index.html +34 -0
  88. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/engine.js +326 -0
  89. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/scene.js +2036 -0
  90. package/lib/motion/mgstyles/upstream/demos/06-collage/assets/gen/layout.json +157 -0
  91. package/lib/motion/mgstyles/upstream/demos/06-collage/cues.json +80 -0
  92. package/lib/motion/mgstyles/upstream/demos/06-collage/index.html +889 -0
  93. package/lib/motion/mgstyles/upstream/demos/07-liquid/cues.json +100 -0
  94. package/lib/motion/mgstyles/upstream/demos/07-liquid/index.html +108 -0
  95. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/engine.js +153 -0
  96. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/links.js +64 -0
  97. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/lookdev.js +46 -0
  98. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/scene.js +963 -0
  99. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/shaders.js +647 -0
  100. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/textsdf.js +114 -0
  101. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/type.js +50 -0
  102. package/lib/motion/mgstyles/upstream/demos/08-morph/cues.json +131 -0
  103. package/lib/motion/mgstyles/upstream/demos/08-morph/glyphs.json +1 -0
  104. package/lib/motion/mgstyles/upstream/demos/08-morph/index.html +901 -0
  105. package/lib/motion/mgstyles/upstream/demos/09-bauhaus/cues.json +270 -0
  106. package/lib/motion/mgstyles/upstream/demos/09-bauhaus/film.js +697 -0
  107. package/lib/motion/mgstyles/upstream/demos/09-bauhaus/index.html +12 -0
  108. package/lib/motion/mgstyles/upstream/demos/10-synthwave/cues.json +44 -0
  109. package/lib/motion/mgstyles/upstream/demos/10-synthwave/index.html +1028 -0
  110. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/cues.json +67 -0
  111. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/index.html +16 -0
  112. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/main.js +967 -0
  113. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/shaders.js +468 -0
  114. package/lib/motion/mgstyles/upstream/demos/18-hanazi/cues.json +117 -0
  115. package/lib/motion/mgstyles/upstream/demos/18-hanazi/index.html +541 -0
  116. package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/countries-110m.json +1 -0
  117. package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/stickers/meta.json +34 -0
  118. package/lib/motion/mgstyles/upstream/demos/19-paperclip/cues.json +285 -0
  119. package/lib/motion/mgstyles/upstream/demos/19-paperclip/index.html +599 -0
  120. package/lib/motion/mgstyles/upstream/demos/20-pixel/cues.json +42 -0
  121. package/lib/motion/mgstyles/upstream/demos/20-pixel/film.js +914 -0
  122. package/lib/motion/mgstyles/upstream/demos/20-pixel/index.html +18 -0
  123. package/lib/motion/mgstyles/upstream/demos/22-hud/cues.json +108 -0
  124. package/lib/motion/mgstyles/upstream/demos/22-hud/index.html +125 -0
  125. package/lib/motion/mgstyles/upstream/demos/22-hud/js/holo.js +275 -0
  126. package/lib/motion/mgstyles/upstream/demos/22-hud/js/hud.js +855 -0
  127. package/lib/motion/mgstyles/upstream/demos/22-hud/js/post.js +150 -0
  128. package/lib/motion/mgstyles/upstream/demos/22-hud/js/util.js +65 -0
  129. package/lib/motion/mgstyles/upstream/demos/22-hud/js/v5.js +142 -0
  130. package/lib/motion/mgstyles/upstream/harness/render.mjs +319 -0
  131. package/lib/motion/mgstyles/upstream/manifest.json +269 -0
  132. package/lib/motion/mgstyles/upstream/prompts/01-flat-vector.md +98 -0
  133. package/lib/motion/mgstyles/upstream/prompts/02-line-art.md +95 -0
  134. package/lib/motion/mgstyles/upstream/prompts/03-isometric.md +97 -0
  135. package/lib/motion/mgstyles/upstream/prompts/05-cel-boil.md +95 -0
  136. package/lib/motion/mgstyles/upstream/prompts/06-collage.md +95 -0
  137. package/lib/motion/mgstyles/upstream/prompts/07-liquid.md +98 -0
  138. package/lib/motion/mgstyles/upstream/prompts/08-morph.md +95 -0
  139. package/lib/motion/mgstyles/upstream/prompts/09-bauhaus.md +96 -0
  140. package/lib/motion/mgstyles/upstream/prompts/10-synthwave.md +95 -0
  141. package/lib/motion/mgstyles/upstream/prompts/12-aurora-glass.md +96 -0
  142. package/lib/motion/mgstyles/upstream/prompts/18-hanazi.md +95 -0
  143. package/lib/motion/mgstyles/upstream/prompts/19-paperclip.md +96 -0
  144. package/lib/motion/mgstyles/upstream/prompts/20-pixel.md +96 -0
  145. package/lib/motion/mgstyles/upstream/prompts/22-hud.md +95 -0
  146. package/lib/motion/mgstyles/upstream/rubric.md +51 -0
  147. package/lib/motion/mgstyles/upstream/template.md +36 -0
  148. package/lib/motion/pixelqa.js +198 -0
  149. package/lib/motion/preview.js +95 -0
  150. package/lib/motion/project.js +395 -0
  151. package/lib/motion/qa.js +290 -0
  152. package/lib/motion/reference-media.jsx +10 -0
  153. package/lib/motion/render-settings.js +112 -0
  154. package/lib/motion/render.js +159 -0
  155. package/lib/motion/scene-kit.js +183 -0
  156. package/lib/motion/sfx.js +232 -0
  157. package/lib/motion/shotcraft/ClipCard.tsx +204 -0
  158. package/lib/motion/shotcraft/catalog.json +2461 -0
  159. package/lib/motion/shotcraft/upstream/SKILL.md +286 -0
  160. package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -0
  161. package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -0
  162. package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -0
  163. package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -0
  164. package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -0
  165. package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -0
  166. package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -0
  167. package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -0
  168. package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -0
  169. package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -0
  170. package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -0
  171. package/lib/motion/shotcraft/upstream/assets/scripts/__pycache__/sfx-source.cpython-313.pyc +0 -0
  172. package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -0
  173. package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -0
  174. package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -0
  175. package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -0
  176. package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -0
  177. package/lib/motion/shotcraft/upstream/demos/README.md +120 -0
  178. package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -0
  179. package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -0
  180. package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -0
  181. package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -0
  182. package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -0
  183. package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -0
  184. package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +389 -0
  185. package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -0
  186. package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -0
  187. package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -0
  188. package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -0
  189. package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -0
  190. package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -0
  191. package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -0
  192. package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -0
  193. package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -0
  194. package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -0
  195. package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -0
  196. package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -0
  197. package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -0
  198. package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -0
  199. package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -0
  200. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -0
  201. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -0
  202. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -0
  203. package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -0
  204. package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -0
  205. package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -0
  206. package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -0
  207. package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -0
  208. package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -0
  209. package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -0
  210. package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -0
  211. package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -0
  212. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -0
  213. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -0
  214. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -0
  215. package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -0
  216. package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -0
  217. package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -0
  218. package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -0
  219. package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -0
  220. package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -0
  221. package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -0
  222. package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -0
  223. package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -0
  224. package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -0
  225. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -0
  226. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -0
  227. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -0
  228. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -0
  229. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -0
  230. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -0
  231. package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -0
  232. package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -0
  233. package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -0
  234. package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -0
  235. package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -0
  236. package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -0
  237. package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -0
  238. package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -0
  239. package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -0
  240. package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -0
  241. package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -0
  242. package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -0
  243. package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -0
  244. package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -0
  245. package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -0
  246. package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -0
  247. package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -0
  248. package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -0
  249. package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -0
  250. package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -0
  251. package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -0
  252. package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -0
  253. package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -0
  254. package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -0
  255. package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -0
  256. package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -0
  257. package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -0
  258. package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -0
  259. package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -0
  260. package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -0
  261. package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -0
  262. package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -0
  263. package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -0
  264. package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -0
  265. package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -0
  266. package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -0
  267. package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -0
  268. package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -0
  269. package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -0
  270. package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -0
  271. package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -0
  272. package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -0
  273. package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -0
  274. package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -0
  275. package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -0
  276. package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -0
  277. package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -0
  278. package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -0
  279. package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -0
  280. package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -0
  281. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -0
  282. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -0
  283. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -0
  284. package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -0
  285. package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -0
  286. package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -0
  287. package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -0
  288. package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -0
  289. package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -0
  290. package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -0
  291. package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -0
  292. package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -0
  293. package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -0
  294. package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -0
  295. package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -0
  296. package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -0
  297. package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -0
  298. package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -0
  299. package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -0
  300. package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -0
  301. package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -0
  302. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -0
  303. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -0
  304. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -0
  305. package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -0
  306. package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -0
  307. package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -0
  308. package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -0
  309. package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -0
  310. package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -0
  311. package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -0
  312. package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -0
  313. package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -0
  314. package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -0
  315. package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -0
  316. package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -0
  317. package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -0
  318. package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -0
  319. package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -0
  320. package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -0
  321. package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -0
  322. package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -0
  323. package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -0
  324. package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -0
  325. package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -0
  326. package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -0
  327. package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -0
  328. package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -0
  329. package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -0
  330. package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -0
  331. package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -0
  332. package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -0
  333. package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -0
  334. package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -0
  335. package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -0
  336. package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -0
  337. package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -0
  338. package/lib/motion/shotcraft/upstream/manifest.json +2715 -0
  339. package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -0
  340. package/lib/motion/shotcraft/upstream/references/final-review.md +121 -0
  341. package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -0
  342. package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -0
  343. package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -0
  344. package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -0
  345. package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -0
  346. package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -0
  347. package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -0
  348. package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -0
  349. package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -0
  350. package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -0
  351. package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -0
  352. package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -0
  353. package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -0
  354. package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -0
  355. package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -0
  356. package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -0
  357. package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -0
  358. package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -0
  359. package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -0
  360. package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -0
  361. package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -0
  362. package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -0
  363. package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -0
  364. package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -0
  365. package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -0
  366. package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -0
  367. package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -0
  368. package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -0
  369. package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -0
  370. package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -0
  371. package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -0
  372. package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -0
  373. package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -0
  374. package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -0
  375. package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -0
  376. package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -0
  377. package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -0
  378. package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -0
  379. package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -0
  380. package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -0
  381. package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -0
  382. package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -0
  383. package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -0
  384. package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -0
  385. package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -0
  386. package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -0
  387. package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -0
  388. package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -0
  389. package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -0
  390. package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -0
  391. package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -0
  392. package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -0
  393. package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -0
  394. package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -0
  395. package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -0
  396. package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -0
  397. package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -0
  398. package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -0
  399. package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -0
  400. package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -0
  401. package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -0
  402. package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -0
  403. package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -0
  404. package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -0
  405. package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -0
  406. package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -0
  407. package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -0
  408. package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -0
  409. package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -0
  410. package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -0
  411. package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -0
  412. package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -0
  413. package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -0
  414. package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -0
  415. package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -0
  416. package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -0
  417. package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -0
  418. package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -0
  419. package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -0
  420. package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -0
  421. package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -0
  422. package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -0
  423. package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -0
  424. package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -0
  425. package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -0
  426. package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -0
  427. package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -0
  428. package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -0
  429. package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -0
  430. package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -0
  431. package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -0
  432. package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -0
  433. package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -0
  434. package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -0
  435. package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -0
  436. package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -0
  437. package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -0
  438. package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -0
  439. package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -0
  440. package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -0
  441. package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -0
  442. package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -0
  443. package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -0
  444. package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -0
  445. package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -0
  446. package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -0
  447. package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -0
  448. package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -0
  449. package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -0
  450. package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -0
  451. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -0
  452. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -0
  453. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -0
  454. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -0
  455. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -0
  456. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -0
  457. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -0
  458. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -0
  459. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -0
  460. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -0
  461. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -0
  462. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -0
  463. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -0
  464. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -0
  465. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -0
  466. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -0
  467. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -0
  468. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -0
  469. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -0
  470. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -0
  471. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -0
  472. package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -0
  473. package/lib/motion/shotcraft/upstream/references/workbench.md +157 -0
  474. package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -0
  475. package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -0
  476. package/lib/motion/shotcraft/upstream/template/package.json +20 -0
  477. package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -0
  478. package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -0
  479. package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -0
  480. package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -0
  481. package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -0
  482. package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -0
  483. package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -0
  484. package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -0
  485. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -0
  486. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -0
  487. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -0
  488. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -0
  489. package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -0
  490. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -0
  491. package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +389 -0
  492. package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -0
  493. package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -0
  494. package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -0
  495. package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -0
  496. package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -0
  497. package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -0
  498. package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -0
  499. package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -0
  500. package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -0
  501. package/lib/motion/spec.js +113 -0
  502. package/lib/motion/style-engine.js +153 -0
  503. package/lib/motion/style-frames.js +268 -0
  504. package/lib/motion/sync.js +214 -0
  505. package/lib/motion/theme.js +121 -0
  506. package/lib/motion/voice.js +417 -0
  507. package/lib/motion-agent-files.js +82 -0
  508. package/lib/motion-agent-protocol.js +66 -0
  509. package/lib/motion-agent-tools.js +196 -0
  510. package/lib/motion-agent.js +171 -0
  511. package/lib/motion-capture.js +92 -0
  512. package/lib/motion-core.js +238 -0
  513. package/lib/motion-film-review.js +71 -0
  514. package/lib/motion-library.js +210 -0
  515. package/lib/motion-planner.js +254 -0
  516. package/lib/motion-project.js +80 -0
  517. package/lib/motion-render-worker.js +145 -0
  518. package/lib/motion-session.js +32 -0
  519. package/lib/motion-source.js +49 -0
  520. package/lib/motion-tasks.js +452 -0
  521. package/lib/motion-vision.js +43 -0
  522. package/lib/mxfile.js +27 -7
  523. package/lib/routing/node-runtime.js +14 -0
  524. package/lib/routing/router.js +211 -0
  525. package/lib/routing/vendor/README.md +96 -0
  526. package/lib/routing/vendor/libavoid-routing.js +753 -0
  527. package/lib/routing/vendor/libavoid.min.js +59 -0
  528. package/lib/shotcard-core.js +39 -0
  529. package/lib/shotcard-reference.js +56 -0
  530. package/lib/style-kernel.js +37 -3
  531. package/lib/task-patches.js +26 -2
  532. package/lib/video-export.js +17 -2
  533. package/package.json +29 -4
@@ -0,0 +1,1399 @@
1
+ // ISOPOLIS — scene construction + pure-time update.
2
+ import * as THREE from 'three';
3
+ import { RoundedBoxGeometry } from 'three/addons/geometries/RoundedBoxGeometry.js';
4
+ import { mergeGeometries } from 'three/addons/utils/BufferGeometryUtils.js';
5
+ import { RoomEnvironment } from 'three/addons/environments/RoomEnvironment.js';
6
+ import * as A from './anim.js';
7
+ import { LAYER_GLOW, LAYER_BG } from './render.js';
8
+
9
+ export const PAL = {
10
+ bgTop: '#EEEAFC', bgMid: '#DAD3F7', bgLow: '#C4BAEF', dot: '#A79EE2',
11
+ grass: ['#94DFBD', '#8CDBB6', '#9DE3C4', '#86D6B0'], grassDeep: '#74CBA3',
12
+ plaza: '#F4F1FD', plaza2: '#ECE8FB', road: '#8781D0', mark: '#FBFAFF', water: '#8CC6FF',
13
+ white: '#FBFAFF', peach: '#FFC4AC', sky: '#A8D2FF', mint: '#B7ECD6', lilac: '#D9D1FA', butter: '#FFE6A1',
14
+ coral: '#FF9E86', deepSky: '#74A9F2', soil1: '#FFFFFF', soil2: '#CBC3F3', soil3: '#B2A8EC', soil4: '#9F95E1',
15
+ clay: '#F1EEFA', clayWin: '#D0D4EE',
16
+ clayGrass: '#E6E2F2', clayRoad: '#CFCBE6', clayWater: '#DDE3F4', clayPlaza: '#D9D1FA', clayPlaza2: '#D2C9F7',
17
+ voidK: ['#EDE9F9', '#E0DBF5', '#D5CEF0'], voidOn: ['#E4D8FF', '#C5B2FC', '#A58CF0'],
18
+ winOff: '#98ACEE', winOn: '#FFF0C8', data: '#62F0F2', ink: '#2C2960',
19
+ leaf: ['#7FD3A6', '#93DDB5', '#6CC79A', '#A9E6C4'], blossom: '#FFB9A3', lilacLeaf: '#C9BDF6', trunk: '#C9A48F',
20
+ };
21
+ const C = (h) => new THREE.Color(h);
22
+
23
+ // 12 x 12 layout, row = j (+Z, screen left-down), column = i (+X, screen right-down)
24
+ const MAP = [
25
+ 'o..t.f..t...', // round 2: turbine (0,0) removed ('o' = bare grass): it sat on the CORE's screen column
26
+ '.f.....f..ww',
27
+ '..rrrrrrrrww',
28
+ 't.rbbbbbbrww',
29
+ '..rbbppbbrw.',
30
+ 'f.rbpccpbr.f',
31
+ '..rbpccpbr.h',
32
+ 'ssrbbppbbrh.',
33
+ 'ssrbbbbbbr.h',
34
+ '..rrrrrrrrhf',
35
+ 'h.hfh.hfh..f',
36
+ '.hfh.hfh.hf.',
37
+ ];
38
+ const N = 12;
39
+ const tx = (i) => i - 5.5, tz = (j) => j - 5.5;
40
+
41
+ // ------------------------------------------------------------------ geometry / material helpers
42
+ const gcache = new Map();
43
+ function rbox(w, h, d, r = 0.05, seg = 2) {
44
+ const k = `rb${w.toFixed(3)}_${h.toFixed(3)}_${d.toFixed(3)}_${r}_${seg}`;
45
+ if (!gcache.has(k)) { const g = new RoundedBoxGeometry(w, h, d, seg, r); g.translate(0, h / 2, 0); gcache.set(k, g); }
46
+ return gcache.get(k);
47
+ }
48
+ function cyl(rt, rb, h, seg = 32) {
49
+ const k = `cy${rt}_${rb}_${h}_${seg}`;
50
+ if (!gcache.has(k)) { const g = new THREE.CylinderGeometry(rt, rb, h, seg, 1); g.translate(0, h / 2, 0); gcache.set(k, g); }
51
+ return gcache.get(k);
52
+ }
53
+ const mcache = new Map();
54
+ function std(hex, o = {}) {
55
+ const k = `${hex}_${o.rough ?? 0.72}_${o.env ?? 0.35}_${o.emissive ?? ''}_${o.ei ?? 1}_${o.metal ?? 0}`;
56
+ if (!mcache.has(k)) {
57
+ mcache.set(k, new THREE.MeshStandardMaterial({
58
+ color: C(hex), roughness: o.rough ?? 0.72, metalness: o.metal ?? 0, envMapIntensity: o.env ?? 0.35,
59
+ emissive: o.emissive ? C(o.emissive) : new THREE.Color(0), emissiveIntensity: o.ei ?? 1,
60
+ }));
61
+ }
62
+ return mcache.get(k);
63
+ }
64
+ function mesh(geo, mat, shadow = true) {
65
+ const m = new THREE.Mesh(geo, mat);
66
+ m.castShadow = shadow; m.receiveShadow = true;
67
+ return m;
68
+ }
69
+
70
+ // ------------------------------------------------------------------ window shader (object-space facade)
71
+ const WIN_VERT_PARS = 'varying vec3 vObj; varying vec3 vObjN;\n';
72
+ const WIN_FRAG_PARS = /* glsl */`
73
+ uniform float uTime, uLit0, uFlash, uStyle, uFloorH, uColW, uSeed, uLitFrac, uEmit, uBaseY;
74
+ uniform vec3 uSize, uWinOff, uWinOn, uClay, uClayWin; uniform float uGrow, uCyl, uSeam;
75
+ varying vec3 vObj; varying vec3 vObjN;
76
+ float hsh(vec3 p){ return fract(sin(dot(p, vec3(12.9898, 78.233, 37.719))) * 43758.5453); }
77
+ `;
78
+ const WIN_FRAG_MAIN = /* glsl */`
79
+ float wMask = 0.0; vec3 wEmit = vec3(0.0);
80
+ // clay -> colour: when the ignition pulse reaches the building, colour climbs the facade with a scan line
81
+ float fy = vObj.y + uBaseY;
82
+ float fAge = uTime - uFlash;
83
+ float ff = fAge * 20.0;
84
+ float fill = fAge < 0.0 ? 0.0 : 1.0 - smoothstep(ff - 0.12, ff, fy);
85
+ float fline = fAge < 0.0 ? 0.0 : exp(-pow((fy - ff) * 12.0, 2.0)) * (1.0 - smoothstep(0.0, 0.2, fAge - (uBaseY + uSize.y) / 20.0));
86
+ diffuseColor.rgb = mix(uClay, diffuseColor.rgb, fill);
87
+ wEmit += fline * vec3(0.55, 1.0, 1.0) * 1.1;
88
+ {
89
+ // blueprint edge: 1-2 px cyan outline on freshly grown walls, held while they grow, gone ~0.4 s later
90
+ float gA = uTime - uGrow - 0.033;
91
+ if (gA > 0.0 && fAge < 0.4) {
92
+ float edy = min(vObj.y, uSize.y - vObj.y);
93
+ float edv = uCyl > 0.5 ? 1e3 : max(uSize.x * 0.5 - abs(vObj.x), uSize.z * 0.5 - abs(vObj.z));
94
+ // round 2: top faces only get their outline (before, the whole cap face read as 'edge' while the walls grew)
95
+ float ee = abs(vObjN.y) > 0.6 ? (uCyl > 0.5 ? uSize.x * 0.5 - length(vObj.xz) : min(uSize.x * 0.5 - abs(vObj.x), uSize.z * 0.5 - abs(vObj.z))) : min(edy, edv);
96
+ float ew = fwidth(ee);
97
+ float eline = 1.0 - smoothstep(ew * 0.55, ew * 1.45, ee);
98
+ float eenv = (gA < 0.35 ? 1.0 : 0.25 + 0.75 * exp(-(gA - 0.35) / 0.14)) * (1.0 - fill);
99
+ diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.38, 0.94, 0.95), eline * eenv * 0.7);
100
+ wEmit += eline * eenv * vec3(0.38, 0.94, 0.95) * 1.2;
101
+ }
102
+ }
103
+ {
104
+ vec3 an = abs(vObjN);
105
+ if (an.y < 0.6) {
106
+ bool fx = an.x > an.z;
107
+ float u = fx ? vObj.z : vObj.x;
108
+ float fw = fx ? uSize.z : uSize.x;
109
+ float v = vObj.y;
110
+ float face = fx ? (vObjN.x > 0. ? 0. : 1.) : (vObjN.z > 0. ? 2. : 3.);
111
+ float mx = min(0.11, fw * 0.12), my0 = 0.18, my1 = 0.12;
112
+ float uu = u + fw * 0.5 - mx; float span = fw - 2. * mx;
113
+ float nc = max(1.0, floor(span / uColW + 0.5));
114
+ float cw = span / nc;
115
+ float cu = uu / cw; float ci = floor(cu); float fu = cu - ci;
116
+ float nr = max(1.0, floor((uSize.y - my0 - my1) / uFloorH));
117
+ float fh = (uSize.y - my0 - my1) / nr;
118
+ float rv = (v - my0) / fh; float ri = floor(rv); float fv = rv - ri;
119
+ float insideU = uStyle > 0.5 && uStyle < 1.5 ? 1.0 : step(0., uu) * step(uu, span);
120
+ float inside = insideU * step(0., rv) * step(rv, nr);
121
+ vec2 dq = vec2(fwidth(cu), fwidth(rv)) * 0.9;
122
+ float wx, wy;
123
+ if (uStyle < 0.5) {
124
+ wx = smoothstep(0.24 - dq.x, 0.24 + dq.x, fu) * (1. - smoothstep(0.76 - dq.x, 0.76 + dq.x, fu));
125
+ wy = smoothstep(0.3 - dq.y, 0.3 + dq.y, fv) * (1. - smoothstep(0.84 - dq.y, 0.84 + dq.y, fv));
126
+ } else if (uStyle < 1.5) {
127
+ wx = 1.;
128
+ ci = floor(atan(vObj.z, vObj.x) * 1.9);
129
+ wy = smoothstep(0.34 - dq.y, 0.34 + dq.y, fv) * (1. - smoothstep(0.8 - dq.y, 0.8 + dq.y, fv));
130
+ // round 2 (CORE): once the colour scan passes, the thick dormant band narrows to a 3-4 px lit seam on the ink body
131
+ if (uSeam > 0.5) wy = mix(wy, smoothstep(0.47 - dq.y, 0.47 + dq.y, fv) * (1. - smoothstep(0.61 - dq.y, 0.61 + dq.y, fv)), fill);
132
+ } else {
133
+ wx = smoothstep(0.32 - dq.x, 0.32 + dq.x, fu) * (1. - smoothstep(0.68 - dq.x, 0.68 + dq.x, fu));
134
+ wy = smoothstep(0.1 - dq.y, 0.1 + dq.y, fv) * (1. - smoothstep(0.9 - dq.y, 0.9 + dq.y, fv));
135
+ }
136
+ wMask = wx * wy * inside;
137
+ float h = hsh(vec3(ci, ri, face + uSeed));
138
+ float tOn = uLit0 + ri * 0.045 + h * 0.3;
139
+ float on = smoothstep(tOn, tOn + 0.04, uTime) * step(h, uLitFrac);
140
+ float pulse = step(uFlash, uTime);
141
+ float tP = uFlash + ri * 0.012 + h * 0.05;
142
+ on = max(on, smoothstep(tP, tP + 0.03, uTime) * step(h, max(uLitFrac, 0.86)));
143
+ float flash = pulse * exp(-max(uTime - tP, 0.0) * 8.0) * step(tP, uTime);
144
+ vec3 wc = mix(mix(uClayWin, uWinOn * 0.9, on), mix(uWinOff, uWinOn * 0.9, on), fill);
145
+ diffuseColor.rgb = mix(diffuseColor.rgb, wc, wMask);
146
+ float chase = uSeam > 0.5 ? 0.35 + 0.65 * pow(0.5 + 0.5 * cos(6.2832 * (fy * 0.4 - uTime * 0.5)), 3.0) : 1.0; // slow 2 s upward chase
147
+ wEmit += wMask * (uWinOn * on * uEmit * chase + vec3(0.75, 1.0, 1.0) * flash * 0.75);
148
+ }
149
+ }
150
+ `;
151
+ function winMat(U, hex, size, style = 0, o = {}) {
152
+ const m = new THREE.MeshStandardMaterial({ color: C(hex), roughness: o.rough ?? 0.68, metalness: 0, envMapIntensity: o.env ?? 0.4 });
153
+ const u = {
154
+ uLit0: { value: 99 }, uFlash: { value: 99 }, uSize: { value: new THREE.Vector3(...size) }, uStyle: { value: style },
155
+ uFloorH: { value: o.floorH ?? 0.3 }, uColW: { value: o.colW ?? 0.22 }, uSeed: { value: o.seed ?? 0.5 },
156
+ uWinOff: { value: C(o.winOff ?? PAL.winOff) }, uWinOn: { value: C(o.winOn ?? PAL.winOn) }, uLitFrac: { value: o.litFrac ?? 0.7 }, uEmit: { value: o.emit ?? 0.28 }, uBaseY: { value: o.baseY ?? 0 },
157
+ uClay: { value: C(o.clay ?? PAL.clay) }, uClayWin: { value: C(o.clayWin ?? PAL.clayWin) }, uGrow: { value: o.grow ?? 99 }, uCyl: { value: o.cyl ?? 0 }, uSeam: { value: o.seam ?? 0 },
158
+ };
159
+ m.userData.u = u;
160
+ m.onBeforeCompile = (sh) => {
161
+ Object.assign(sh.uniforms, u, { uTime: U.uTime });
162
+ sh.vertexShader = WIN_VERT_PARS + sh.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvObj = position; vObjN = normal;');
163
+ sh.fragmentShader = WIN_FRAG_PARS + sh.fragmentShader
164
+ .replace('#include <color_fragment>', '#include <color_fragment>\n' + WIN_FRAG_MAIN)
165
+ .replace('#include <roughnessmap_fragment>', '#include <roughnessmap_fragment>\nroughnessFactor = mix(roughnessFactor, 0.28, wMask);')
166
+ .replace('#include <emissivemap_fragment>', '#include <emissivemap_fragment>\ntotalEmissiveRadiance += wEmit;');
167
+ };
168
+ m.customProgramCacheKey = () => 'win-v6';
169
+ return m;
170
+ }
171
+
172
+ // plain material that stays clay-white until the ignition pulse reaches it (+ delay)
173
+ function clayMat(U, hex, o = {}) {
174
+ const m = new THREE.MeshStandardMaterial({ color: C(hex), roughness: o.rough ?? 0.6, metalness: 0, envMapIntensity: o.env ?? 0.35 });
175
+ const u = { uFlash: { value: 99 }, uDelay: { value: o.delay ?? 0 }, uClay: { value: C(PAL.clay) } };
176
+ m.userData.u = u;
177
+ m.onBeforeCompile = (sh) => {
178
+ Object.assign(sh.uniforms, u, { uTime: U.uTime });
179
+ sh.fragmentShader = 'uniform float uTime, uFlash, uDelay; uniform vec3 uClay;\n' + sh.fragmentShader.replace('#include <color_fragment>',
180
+ '#include <color_fragment>\ndiffuseColor.rgb = mix(uClay, diffuseColor.rgb, smoothstep(uFlash + uDelay, uFlash + uDelay + 0.05, uTime));');
181
+ };
182
+ m.customProgramCacheKey = () => 'clay-v1';
183
+ return m;
184
+ }
185
+
186
+ // soil with strata bands (object space y)
187
+ function soilMat(U) {
188
+ const m = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.85, envMapIntensity: 0.3 });
189
+ m.onBeforeCompile = (sh) => {
190
+ sh.uniforms.c1 = { value: C(PAL.soil1) }; sh.uniforms.c2 = { value: C(PAL.soil2) };
191
+ sh.uniforms.c3 = { value: C(PAL.soil3) }; sh.uniforms.c4 = { value: C(PAL.soil4) };
192
+ sh.vertexShader = 'varying vec3 vObj;\n' + sh.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvObj = position;');
193
+ sh.fragmentShader = 'uniform vec3 c1,c2,c3,c4; varying vec3 vObj;\n' + sh.fragmentShader.replace('#include <color_fragment>', `#include <color_fragment>
194
+ float y = vObj.y; float aa = fwidth(y) * 0.8;
195
+ vec3 col = c4;
196
+ col = mix(col, c3, smoothstep(-0.95 - aa, -0.95 + aa, y));
197
+ col = mix(col, c2, smoothstep(-0.62 - aa, -0.62 + aa, y));
198
+ col = mix(col, c1, smoothstep(-0.2 - aa, -0.2 + aa, y));
199
+ diffuseColor.rgb = col;`);
200
+ };
201
+ m.customProgramCacheKey = () => 'soil-v1';
202
+ return m;
203
+ }
204
+
205
+ // ------------------------------------------------------------------ path along a rounded rectangle loop
206
+ function loopPath(a, rc) {
207
+ const st = 2 * (a - rc), arc = Math.PI / 2 * rc, seg = st + arc, L = 4 * seg;
208
+ // counter-clockwise when seen from above (+Y): start at (a, 0, -(a-rc)) heading +z
209
+ const corners = [[a - rc, a - rc], [-(a - rc), a - rc], [-(a - rc), -(a - rc)], [a - rc, -(a - rc)]];
210
+ const starts = [[a, -(a - rc)], [a - rc, a], [-a, a - rc], [-(a - rc), -a]];
211
+ const dirs = [[0, 1], [-1, 0], [0, -1], [1, 0]];
212
+ return {
213
+ L,
214
+ at(s) {
215
+ s = ((s % L) + L) % L;
216
+ const k = Math.floor(s / seg), r = s - k * seg;
217
+ if (r < st) {
218
+ const [sx, sz] = starts[k], [dx, dz] = dirs[k];
219
+ return { x: sx + dx * r, z: sz + dz * r, ang: Math.atan2(dx, dz) };
220
+ }
221
+ const th = (r - st) / rc; // angle travelled on the corner
222
+ const [cx, cz] = corners[k];
223
+ const a0 = [0, Math.PI / 2, Math.PI, -Math.PI / 2][k]; // start angle (from +x toward +z)
224
+ const ang = a0 + th;
225
+ const dxz = [-Math.sin(ang), Math.cos(ang)];
226
+ return { x: cx + rc * Math.cos(ang), z: cz + rc * Math.sin(ang), ang: Math.atan2(dxz[0], dxz[1]) };
227
+ },
228
+ };
229
+ }
230
+
231
+ // ------------------------------------------------------------------ build
232
+ export function buildWorld(renderer, cues, opts = {}) {
233
+ const scene = new THREE.Scene();
234
+ const U = { uTime: { value: 0 } };
235
+ // growth front: screen-x (iso) -> time, piecewise linear (cues.sweep)
236
+ const SW = cues.sweep;
237
+ const sxOf = (x, z) => (x - z) / Math.SQRT2;
238
+ const front = (sx) => {
239
+ for (let k = 1; k < SW.length; k++) if (sx <= SW[k][0] || k === SW.length - 1) {
240
+ const [a, ta] = SW[k - 1], [b, tb] = SW[k];
241
+ return ta + (tb - ta) * A.clamp((sx - a) / (b - a), 0, 1.2);
242
+ }
243
+ return SW[SW.length - 1][1];
244
+ };
245
+ const updaters = [];
246
+ const onUpdate = (f) => updaters.push(f);
247
+
248
+ // environment for soft specular
249
+ const pmrem = new THREE.PMREMGenerator(renderer);
250
+ scene.environment = pmrem.fromScene(new RoomEnvironment(), 0.04).texture;
251
+ scene.environmentIntensity = 0.3;
252
+
253
+ // --- camera: true isometric
254
+ const VH = 18, aspect = 16 / 9;
255
+ const camera = new THREE.OrthographicCamera(-VH * aspect / 2, VH * aspect / 2, VH / 2, -VH / 2, 1, 400);
256
+ const DIR = new THREE.Vector3(1, 1, 1).normalize();
257
+ const camRight = new THREE.Vector3(1, 0, -1).normalize();
258
+ const camUp = new THREE.Vector3(-1, 2, -1).normalize();
259
+
260
+ // --- lights
261
+ const hemi = new THREE.HemisphereLight(C('#F6F3FF'), C('#C9BFF0'), 0.75);
262
+ scene.add(hemi);
263
+ const sunDir0 = new THREE.Vector3(-0.42, 1.0, 0.62).normalize();
264
+ const sun = new THREE.DirectionalLight(C('#FFF3E6'), 2.25);
265
+ sun.castShadow = true;
266
+ sun.shadow.mapSize.set(4096, 4096);
267
+ const sc = sun.shadow.camera; sc.left = -20; sc.right = 20; sc.top = 20; sc.bottom = -20; sc.near = 1; sc.far = 90;
268
+ sun.shadow.bias = -0.0004; sun.shadow.normalBias = 0.018;
269
+ scene.add(sun); scene.add(sun.target);
270
+ const fill = new THREE.DirectionalLight(C('#DCE4FF'), 0.3);
271
+ fill.position.set(6, 3, -2); scene.add(fill);
272
+ // round 2: clay-phase light (warm key x1.2, denser contact shadows, lavender fill) eases into the hero light after the wave
273
+ const SUN_H = C('#FFF3E6'), SUN_K = C('#FFF6EC'), HEMI_G = C('#C9BFF0'), HEMI_GK = C('#B6A7EE'), FILL_H = C('#DCE4FF'), FILL_K = C('#CDBFFF');
274
+
275
+ // --- background (screen quad: gradient + iso dot floor + island floor shadow)
276
+ const bgU = {
277
+ uInvVP: { value: new THREE.Matrix4() }, uFwd: { value: DIR.clone().negate() },
278
+ uTop: { value: C(PAL.voidOn[0]) }, uMid: { value: C(PAL.voidOn[1]) }, uLow: { value: C(PAL.voidOn[2]) }, uDot: { value: C(PAL.dot) },
279
+ uTopK: { value: C(PAL.voidK[0]) }, uMidK: { value: C(PAL.voidK[1]) }, uLowK: { value: C(PAL.voidK[2]) }, uVoidK: { value: 0 }, uWaveT: { value: 99 }, uWaveC: { value: new THREE.Vector2() },
280
+ uFloorY: { value: -7.5 }, uOff: { value: new THREE.Vector2() }, uShadowC: { value: new THREE.Vector2() },
281
+ uShadow: { value: 0 }, uDots: { value: 1 }, uTime: U.uTime, uZoom: { value: 1 }, uRipC: { value: new THREE.Vector2(0, 0) }, uRipT: { value: 99 }, uRipV: { value: 12 }, uRipD: { value: 8 }, uHot: { value: C(PAL.data) },
282
+ };
283
+ const bgMat = new THREE.ShaderMaterial({
284
+ uniforms: bgU, depthTest: false, depthWrite: false,
285
+ vertexShader: 'varying vec2 vNdc; void main(){ vNdc = position.xy; gl_Position = vec4(position.xy, 0.9999, 1.0); }',
286
+ fragmentShader: /* glsl */`
287
+ uniform mat4 uInvVP; uniform vec3 uFwd; uniform vec3 uTop, uMid, uLow, uDot, uHot; uniform float uFloorY, uShadow, uDots, uZoom, uTime, uRipT, uRipV, uRipD; uniform vec2 uRipC;
288
+ uniform vec2 uOff, uShadowC; varying vec2 vNdc;
289
+ uniform vec3 uTopK, uMidK, uLowK; uniform float uVoidK, uWaveT; uniform vec2 uWaveC;
290
+ float sdRoundBox(vec2 p, vec2 b, float r){ vec2 q = abs(p) - b + r; return length(max(q, 0.0)) + min(max(q.x, q.y), 0.0) - r; }
291
+ void main(){
292
+ vec2 uv = vNdc * 0.5 + 0.5;
293
+ float wa = uTime - uWaveT;
294
+ float wr = length((vNdc - uWaveC) * vec2(1.7778, 1.0));
295
+ float front = wa > 0.0 ? 0.15 + wa * 5.5 : -1.0;
296
+ float on = 1.0 - smoothstep(front - 0.35, front, wr);
297
+ float kk = uVoidK * (1.0 - on);
298
+ vec3 cOn = mix(mix(uLow, uMid, smoothstep(0.0, 0.55, uv.y)), uTop, smoothstep(0.5, 1.0, uv.y));
299
+ vec3 cK = mix(mix(uLowK, uMidK, smoothstep(0.0, 0.55, uv.y)), uTopK, smoothstep(0.5, 1.0, uv.y));
300
+ vec3 col = mix(cOn, cK, kk);
301
+ vec3 topC = mix(uTop, uTopK, kk);
302
+ // soft light pool behind the island
303
+ vec2 q = vNdc; q.x *= 1.7778;
304
+ col = mix(col, topC * 1.03, 0.35 * exp(-dot(q - vec2(0.15, 0.1), q - vec2(0.15, 0.1)) * 0.9));
305
+ col += uHot * exp(-pow((wr - front + 0.12) * 6.0, 2.0)) * 0.16 * exp(-max(wa, 0.0) * 2.2) * step(0.0, wa);
306
+ // iso floor
307
+ vec4 p = uInvVP * vec4(vNdc, -1.0, 1.0); p.xyz /= p.w;
308
+ float tt = (uFloorY - p.y) / uFwd.y; vec3 w = p.xyz + uFwd * tt;
309
+ vec2 g = w.xz + uOff;
310
+ vec2 cc = g - floor(g + 0.5);
311
+ float dd = length(cc);
312
+ float aa = fwidth(dd) * 1.2 + 0.004;
313
+ float ra = uTime - uRipT;
314
+ vec2 rq = abs(floor(g + 0.5) - uRipC);
315
+ float rr = max(rq.x, rq.y);
316
+ // round 2: one cyan ping from the title to the island's floor shadow: 5 px dots at 70 % on the front, 0.5 s wake
317
+ float dR = rr - ra * uRipV;
318
+ float ring = ra > 0.0 ? (dR > 0.0 ? exp(-pow(dR * 1.4, 2.0)) : exp(dR / (uRipV * 0.5))) * (1.0 - smoothstep(uRipD + 1.0, uRipD + 4.0, ra * uRipV)) : 0.0;
319
+ float r = mix(0.032 / sqrt(max(uZoom, 0.5)), 2.5 / (60.0 * uZoom), clamp(ring, 0.0, 1.0));
320
+ float dotm = 1.0 - smoothstep(r - aa, r + aa, dd);
321
+ float fade = exp(-dot(w.xz, w.xz) * 0.0035);
322
+ col = mix(col, mix(uDot, uHot, clamp(ring * 1.5, 0.0, 1.0)), dotm * max(0.55 * fade, ring * 0.7) * uDots);
323
+ // r2s2 bookend: the ping re-draws the opening's cyan blueprint grid (1.2 px lines) on its front only, so the end frame
324
+ // answers the first frame. Lines run through the dots (integer g), like the opening blueprint crosses.
325
+ float lineRing = ra > 0.0 ? exp(-pow(dR * 0.9, 2.0)) * (1.0 - smoothstep(uRipD + 0.5, uRipD + 3.0, ra * uRipV)) : 0.0;
326
+ vec2 ce = abs(fract(g) - 0.5);
327
+ float ld = 0.5 - max(ce.x, ce.y);
328
+ float lw = 0.6 / (60.0 * uZoom); float la = fwidth(ld) * 1.0 + 1e-4;
329
+ float lm = 1.0 - smoothstep(lw - la, lw + la, ld);
330
+ col = mix(col, uHot, lm * lineRing * 0.5 * uDots);
331
+ // floor shadow of the floating island
332
+ float sd = sdRoundBox(w.xz - uShadowC, vec2(5.0), 1.8);
333
+ float sh = 1.0 - smoothstep(-2.2, 2.8, sd);
334
+ float core = 1.0 - smoothstep(-2.6, 0.9, sd);
335
+ col = mix(col, col * vec3(0.70, 0.68, 0.88), (0.55 * sh + 0.4 * core) * uShadow);
336
+ gl_FragColor = vec4(col, 1.0);
337
+ }`,
338
+ });
339
+ const bgQuad = new THREE.Mesh(new THREE.PlaneGeometry(2, 2), bgMat);
340
+ bgQuad.frustumCulled = false; bgQuad.renderOrder = -1000; bgQuad.layers.set(LAYER_BG);
341
+ scene.add(bgQuad);
342
+
343
+ // ================================================================= ISLAND
344
+ const island = new THREE.Group(); scene.add(island);
345
+ const ringT = cues.rings;
346
+ const tiles = [];
347
+ const R = A.rng(7);
348
+ const cell = (i, j) => (j >= 0 && j < N && i >= 0 && i < N ? MAP[j][i] : ' ');
349
+ for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) {
350
+ const ch = cell(i, j);
351
+ const dx = i - 5.5, dz = j - 5.5;
352
+ const ring = Math.max(Math.abs(dx), Math.abs(dz)) - 0.5;
353
+ const ang = (Math.atan2(dz, dx) / (2 * Math.PI) + 1) % 1;
354
+ const tl = ringT[ring] + ang * 0.07 + (R() - 0.5) * 0.02;
355
+ let radial;
356
+ if (Math.abs(Math.abs(dx) - Math.abs(dz)) < 0.01) radial = new THREE.Vector3(Math.sign(dx), 0, Math.sign(dz)).normalize();
357
+ else if (Math.abs(dx) > Math.abs(dz)) radial = new THREE.Vector3(Math.sign(dx), 0, 0);
358
+ else radial = new THREE.Vector3(0, 0, Math.sign(dz));
359
+ const axis = new THREE.Vector3(0, 1, 0).cross(radial).normalize();
360
+ let type = 'grass';
361
+ if (ch === 'r') type = 'road'; else if ('pbc'.includes(ch)) type = 'plaza'; else if (ch === 'w') type = 'water';
362
+ tiles.push({ i, j, ch, type, x: tx(i), z: tz(j), tl, axis, ring, rot0: -(1.25 + R() * 0.3) });
363
+ }
364
+ // blueprint: the OS draws the 12x12 plan first; tiles snap onto it
365
+ const bpMat = new THREE.ShaderMaterial({
366
+ uniforms: { uTime: U.uTime, uA: { value: cues.blueprint[0] }, uB: { value: cues.blueprint[1] }, uCol: { value: C(PAL.data) }, uHot: { value: C('#FFFFFF') } },
367
+ transparent: true, depthWrite: false,
368
+ vertexShader: 'varying vec3 vW; void main(){ vec4 w = modelMatrix * vec4(position,1.); vW = w.xyz; gl_Position = projectionMatrix * viewMatrix * w; }',
369
+ fragmentShader: /* glsl */`
370
+ uniform float uTime, uA, uB; uniform vec3 uCol, uHot; varying vec3 vW;
371
+ void main(){
372
+ float r = max(abs(vW.x), abs(vW.z));
373
+ float u = clamp((uTime - uA) / (uB - uA), 0.0, 1.0);
374
+ float R = 1.9 + 4.3 * (1.0 - pow(1.0 - u, 2.0));
375
+ vec2 g = abs(fract(vW.xz + 0.5) - 0.5);
376
+ float lw = fwidth(vW.x);
377
+ float line = max(1.0 - smoothstep(lw * 0.8, lw * 1.8, g.x), 1.0 - smoothstep(lw * 0.8, lw * 1.8, g.y));
378
+ // cross ticks at intersections
379
+ vec2 c = abs(vW.xz - floor(vW.xz + 0.5));
380
+ float tick = (1.0 - smoothstep(lw * 1.3, lw * 2.4, min(c.x, c.y))) * step(max(c.x, c.y), 0.11);
381
+ float border = 1.0 - smoothstep(lw * 1.4, lw * 2.6, abs(r - 6.0));
382
+ float inside = 1.0 - smoothstep(R - 0.05, R, r);
383
+ float frontGlow = exp(-pow((r - R) * 3.0, 2.0)) * (1.0 - u * 0.6);
384
+ float head = exp(-pow((r - R) * 6.0, 2.0)) * (1.0 - u * 0.7);
385
+ float a = (line * 0.8 + tick * 1.0 + border * 1.0) * inside * (r < 6.02 ? 1.0 : 0.0);
386
+ a += head * 0.22 * (r < 6.02 ? 1.0 : 0.0);
387
+ a *= 1.0 - smoothstep(1.55, 1.85, uTime);
388
+ vec3 col = mix(uCol, uHot, clamp(frontGlow * 1.2 + head, 0.0, 1.0));
389
+ gl_FragColor = vec4(col, clamp(a, 0.0, 1.0) * 0.95);
390
+ }`,
391
+ });
392
+ const blueprint = new THREE.Mesh(new THREE.PlaneGeometry(12.6, 12.6).rotateX(-Math.PI / 2), bpMat);
393
+ blueprint.position.y = -0.338; blueprint.layers.set(LAYER_GLOW); island.add(blueprint);
394
+ onUpdate((T) => { blueprint.visible = T < 1.9; });
395
+
396
+ const tileGeo = new RoundedBoxGeometry(0.955, 0.34, 0.955, 2, 0.075);
397
+ const tileMat = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.82, metalness: 0, envMapIntensity: 0.3 });
398
+ const waterMat = new THREE.MeshStandardMaterial({ color: C(PAL.water), roughness: 0.14, metalness: 0, envMapIntensity: 1.0 });
399
+ // stylised pond: drifting broken wave dashes + slow tonal swell on the top faces (island-local coords, pure in uTime)
400
+ waterMat.onBeforeCompile = (sh) => {
401
+ sh.uniforms.uTime = U.uTime;
402
+ sh.vertexShader = 'varying vec3 vWq; varying float vNy;\n' + sh.vertexShader.replace('#include <begin_vertex>',
403
+ '#include <begin_vertex>\nvec4 wq4 = vec4(transformed, 1.0);\n#ifdef USE_INSTANCING\nwq4 = instanceMatrix * wq4;\n#endif\nvWq = wq4.xyz; vNy = normal.y;');
404
+ sh.fragmentShader = 'uniform float uTime; varying vec3 vWq; varying float vNy;\n' + sh.fragmentShader.replace('#include <color_fragment>', `#include <color_fragment>
405
+ {
406
+ vec2 q = vWq.xz; float t = uTime;
407
+ float top = smoothstep(0.75, 0.97, -vNy) * step(0.001, vFK.y); // round 2: the water face is the tile's underside
408
+ float w1 = sin((q.x * 0.8 + q.y * 1.0) * 7.0 - t * 2.2 + sin(q.x * 2.1 - q.y * 1.7 + t * 0.9) * 1.4);
409
+ float br = smoothstep(0.42, 0.78, sin(q.x * 3.1 - q.y * 2.3 + t * 0.6 + 1.7) * 0.5 + 0.5);
410
+ float w2 = sin((q.x * 1.1 - q.y * 0.35) * 11.0 - t * 3.1 + 2.0);
411
+ float br2 = smoothstep(0.6, 0.9, sin(q.x * 4.3 + q.y * 3.7 - t * 0.8) * 0.5 + 0.5);
412
+ float dash = max(smoothstep(0.88, 0.97, w1) * br, smoothstep(0.93, 0.99, w2) * br2 * 0.6);
413
+ float swell = sin(q.x * 1.3 + q.y * 0.9 + t * 0.5) * 0.5 + 0.5;
414
+ diffuseColor.rgb *= mix(1.0, 0.93 + 0.1 * swell, top);
415
+ diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.97, 0.99, 1.0), dash * top * 0.75);
416
+ }`);
417
+ };
418
+ waterMat.customProgramCacheKey = () => 'water-v1';
419
+ const land = tiles.filter((t) => t.type !== 'water'), wet = tiles.filter((t) => t.type === 'water');
420
+ const tileGeoW = tileGeo.clone();
421
+ const tileIM = new THREE.InstancedMesh(tileGeo, tileMat, land.length);
422
+ const waterIM = new THREE.InstancedMesh(tileGeoW, waterMat, wet.length);
423
+ // round 2: two-faced tiles. The top (+y) is clay, the underside (-y) carries the colour; the hero wave flips empty tiles
424
+ // onto their coloured face, the rest repaint under the hop. Per instance: clay colour, [flip mode, colour k], landing time.
425
+ for (const [g, n] of [[tileGeo, land.length], [tileGeoW, wet.length]]) {
426
+ g.setAttribute('aClay', new THREE.InstancedBufferAttribute(new Float32Array(n * 3), 3));
427
+ g.setAttribute('aFK', new THREE.InstancedBufferAttribute(new Float32Array(n * 2), 2));
428
+ g.setAttribute('aLand', new THREE.InstancedBufferAttribute(new Float32Array(n), 1));
429
+ }
430
+ const TILE_FACE_GLSL = /* glsl */`#include <color_fragment>
431
+ vec3 tEmit = vec3(0.0);
432
+ {
433
+ vec3 cF = diffuseColor.rgb;
434
+ float up = smoothstep(0.3, 0.75, vTN.y), dn = smoothstep(0.3, 0.75, -vTN.y);
435
+ vec3 side = mix(vClay, cF, vFK.y);
436
+ vec3 fl = mix(mix(side, vClay, up), mix(vClay, cF, step(0.001, vFK.y)), dn);
437
+ diffuseColor.rgb = vFK.x > 0.5 ? fl : mix(vClay, cF, vFK.y);
438
+ // landing rim: 2 px cyan outline on the clay face, decays over ~6 frames after contact
439
+ float ed = abs(0.385 - max(abs(vTP.x), abs(vTP.z)));
440
+ float ew = fwidth(ed);
441
+ float la = uTimeT - vLand;
442
+ float rim = (1.0 - smoothstep(ew * 0.8, ew * 1.9, ed)) * smoothstep(0.9, 0.99, vTN.y) * step(0.0, la) * exp(-max(la, 0.0) / 0.075);
443
+ diffuseColor.rgb = mix(diffuseColor.rgb, vec3(0.38, 0.94, 0.95), rim * 0.85);
444
+ tEmit = rim * vec3(0.38, 0.94, 0.95) * 1.3;
445
+ }`;
446
+ for (const m of [tileMat, waterMat]) {
447
+ const prev = m.onBeforeCompile, k0 = m.customProgramCacheKey.call(m);
448
+ m.onBeforeCompile = (sh, r) => {
449
+ prev.call(m, sh, r);
450
+ sh.uniforms.uTimeT = U.uTime;
451
+ sh.vertexShader = 'attribute vec3 aClay; attribute vec2 aFK; attribute float aLand;\nvarying vec3 vClay; varying vec2 vFK; varying float vLand; varying vec3 vTN; varying vec3 vTP;\n' +
452
+ sh.vertexShader.replace('#include <begin_vertex>', '#include <begin_vertex>\nvClay = aClay; vFK = aFK; vLand = aLand; vTN = normal; vTP = position;');
453
+ sh.fragmentShader = 'uniform float uTimeT; varying vec3 vClay; varying vec2 vFK; varying float vLand; varying vec3 vTN; varying vec3 vTP;\n' + sh.fragmentShader
454
+ .replace('#include <color_fragment>', TILE_FACE_GLSL)
455
+ .replace('#include <emissivemap_fragment>', '#include <emissivemap_fragment>\ntotalEmissiveRadiance += tEmit;');
456
+ };
457
+ m.customProgramCacheKey = () => k0 + '|faces2';
458
+ }
459
+ for (const im of [tileIM, waterIM]) { im.castShadow = true; im.receiveShadow = true; im.frustumCulled = false; island.add(im); }
460
+ land.forEach((t, k) => {
461
+ let col;
462
+ if (t.type === 'grass') col = PAL.grass[Math.floor(A.hash(t.i * 13 + t.j * 7) * 4)];
463
+ else if (t.type === 'road') col = PAL.road;
464
+ else col = (t.i + t.j) % 2 ? PAL.plaza : PAL.plaza2;
465
+ let clay = t.type === 'grass' ? PAL.clayGrass : t.type === 'road' ? PAL.clayRoad : ((t.i + t.j) % 2 ? PAL.clayPlaza : PAL.clayPlaza2);
466
+ if (t.type === 'plaza') col = (t.i + t.j) % 2 ? '#FFF0DA' : '#FFE7CB';
467
+ if (t.ch === 'c') { col = PAL.ink; clay = PAL.ink; }
468
+ t.col = C(col); t.clay = C(clay);
469
+ tileIM.setColorAt(k, t.col);
470
+ t.im = tileIM; t.k = k;
471
+ });
472
+ wet.forEach((t, k) => { t.col = C(PAL.water); t.clay = C(PAL.clayWater); waterIM.setColorAt(k, t.col); t.im = waterIM; t.k = k; });
473
+ waterMat.color.set('#ffffff');
474
+ const _m = new THREE.Matrix4(), _q = new THREE.Quaternion(), _s = new THREE.Vector3(), _p = new THREE.Vector3(), _off = new THREE.Matrix4().makeTranslation(0, 0.17, 0);
475
+ const FLIP_D = 5 / 30;
476
+ function tileMatrix(t, T) {
477
+ if (t.flip && T > t.ta) {
478
+ // hero flip: 180 deg about the axis parallel to the CORE-facing edge (inner edge rises first), 5 frames,
479
+ // easeOutBack 1.4 (the overshoot is the micro-bounce), 0.15 u lift at mid-flip; pivot = tile centre
480
+ const u = Math.min((T - t.ta) / FLIP_D, 1);
481
+ _q.setFromAxisAngle(t.faxis, Math.PI * A.easeOutBack(u, 1.4) * t.fsgn);
482
+ _s.set(1, 1, 1);
483
+ _p.set(t.x, -0.17 + 0.15 * Math.sin(Math.PI * u), t.z);
484
+ return _m.compose(_p, _q, _s);
485
+ }
486
+ const d = A.drop(T - t.tl, 0.3, 2.2, t.rot0, 0.075);
487
+ if (!d.vis) { _m.makeScale(0, 0, 0); return _m; }
488
+ _q.setFromAxisAngle(t.axis, d.rot);
489
+ _s.set(d.sxz, d.sy, d.sxz);
490
+ _p.set(t.x, -0.34 + d.y + pulseBump(t.x, t.z, T), t.z);
491
+ _m.compose(_p, _q, _s).multiply(_off);
492
+ return _m;
493
+ }
494
+ // tiny bump of the tiles when the hero pulse passes
495
+ const PULSE_T = cues.drop - 0.015, PULSE_V = cues.pulse_speed, PULSE_R0 = 1.0; // wave is born at the CORE footprint edge
496
+ function pulseBump(x, z, T) {
497
+ const r = Math.max(Math.abs(x), Math.abs(z));
498
+ const ta = PULSE_T + Math.max(r - PULSE_R0, 0) / PULSE_V;
499
+ const u = T - ta;
500
+ if (u < 0 || u > 0.35) return 0;
501
+ return 0; // the hero hop now lives in the vertex shader (hopPatch) so every object on a tile rides the wave
502
+ }
503
+ for (const t of tiles) {
504
+ const r = Math.max(Math.abs(t.x), Math.abs(t.z));
505
+ t.ta = PULSE_T + Math.max(r - PULSE_R0, 0) / PULSE_V;
506
+ const xd = Math.abs(t.x) >= Math.abs(t.z);
507
+ t.faxis = xd ? new THREE.Vector3(0, 0, 1) : new THREE.Vector3(1, 0, 0);
508
+ t.fsgn = xd ? -Math.sign(t.x) : Math.sign(t.z);
509
+ t.flip = false; // decided once everything standing on the island exists (see "which tiles flip")
510
+ const ga = t.im.geometry.attributes;
511
+ ga.aClay.setXYZ(t.k, t.clay.r, t.clay.g, t.clay.b); ga.aLand.setX(t.k, t.tl);
512
+ }
513
+ onUpdate((T) => {
514
+ // round 2: tiles land clay-side up (no drain); at the hero wave empty tiles flip onto their coloured face,
515
+ // tiles carrying objects repaint as they hop
516
+ for (const t of tiles) {
517
+ t.im.setMatrixAt(t.k, tileMatrix(t, T));
518
+ const k = t.flip ? A.clamp((T - t.ta) / FLIP_D) : A.smooth(A.inv(t.ta, t.ta + 0.06, T));
519
+ t.im.geometry.attributes.aFK.setXY(t.k, t.flip ? 1 : 0, k);
520
+ }
521
+ tileIM.instanceMatrix.needsUpdate = true; waterIM.instanceMatrix.needsUpdate = true;
522
+ tileGeo.attributes.aFK.needsUpdate = true; tileGeoW.attributes.aFK.needsUpdate = true;
523
+ });
524
+
525
+ // --- soil body (extrudes downward once the tiles are in)
526
+ const soil = new THREE.Group(); island.add(soil);
527
+ const sm = soilMat(U);
528
+ const s1 = mesh(new RoundedBoxGeometry(12.0, 1.3, 12.0, 4, 0.16).translate(0, -0.65, 0), sm);
529
+ const s2 = mesh(new RoundedBoxGeometry(9.2, 0.9, 9.2, 4, 0.2).translate(0, -0.45, 0), std(PAL.soil4, { rough: 0.9 }));
530
+ s2.position.y = -1.3;
531
+ const s3 = mesh(new RoundedBoxGeometry(6.0, 0.8, 6.0, 4, 0.22).translate(0, -0.4, 0), std('#8F85D6', { rough: 0.9 }));
532
+ s3.position.y = -2.2;
533
+ soil.add(s1, s2, s3);
534
+ soil.position.y = -0.345;
535
+ onUpdate((T) => {
536
+ const u = A.easeOutCubic(A.inv(cues.soil, cues.soil + 0.45, T));
537
+ const u2 = A.easeOutCubic(A.inv(cues.soil + 0.15, cues.soil + 0.6, T));
538
+ const u3 = A.easeOutCubic(A.inv(cues.soil + 0.3, cues.soil + 0.75, T));
539
+ s1.scale.set(1, Math.max(u, 1e-4), 1); s1.visible = u > 0;
540
+ s2.position.y = -1.3 * u; s2.scale.set(1, Math.max(u2, 1e-4), 1); s2.visible = u2 > 0;
541
+ s3.position.y = -1.3 * u - 0.9 * u2; s3.scale.set(1, Math.max(u3, 1e-4), 1); s3.visible = u3 > 0;
542
+ });
543
+
544
+ // --- road markings (dashes along the loop, drawn on as a sweep)
545
+ const roadLoop = loopPath(3.5, 0.5);
546
+ const dashGeo = new THREE.BoxGeometry(0.2, 0.012, 0.045).translate(0, 0.006, 0);
547
+ const nD = 44;
548
+ const dashIM = new THREE.InstancedMesh(dashGeo, std(PAL.mark, { rough: 0.6 }), nD);
549
+ dashIM.receiveShadow = true; dashIM.frustumCulled = false; island.add(dashIM);
550
+ onUpdate((T) => {
551
+ for (let k = 0; k < nD; k++) {
552
+ const s = (k + 0.5) / nD * roadLoop.L;
553
+ const p = roadLoop.at(s);
554
+ const u = A.easeOutCubic(A.inv(cues.roads + k * 0.008, cues.roads + k * 0.008 + 0.18, T));
555
+ _q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), p.ang + Math.PI / 2);
556
+ _s.set(u, u > 0.001 ? 1 : 0, u > 0.001 ? 1 : 0); _p.set(p.x, 0.001, p.z);
557
+ _m.compose(_p, _q, _s); dashIM.setMatrixAt(k, _m);
558
+ }
559
+ dashIM.instanceMatrix.needsUpdate = true;
560
+ });
561
+
562
+ // ================================================================= growth helpers
563
+ const WALL = (tau) => A.spring(tau, 14.8, 0.61, 0); // round 2: peak at ~8 frames, 9 % overshoot
564
+ let TGv = 0; // growth time: 90 deg shutter for the scale channels (set in update)
565
+ function growState(T, t0) {
566
+ const tau = T - t0;
567
+ // base slab drops in 0.1 s before t0
568
+ let bd;
569
+ if (tau < -0.1) bd = { y: 0, sy: 1, sxz: 0, vis: 0 };
570
+ else if (tau < 0) { const u = (tau + 0.1) / 0.1; bd = { y: 0, sy: 1, sxz: 0.12 + 0.88 * A.easeOutCubic(u), vis: 1 }; }
571
+ else bd = A.drop(tau, 0.1, 0.3, 0, 0.03);
572
+ const wall = tau > 1 / 30 ? WALL(tau - 1 / 30) : 0;
573
+ const rl = cues.roof_lag;
574
+ const rd = A.drop(tau - rl, 0.1, 0.3, 0, 0.025);
575
+ return { bd, wall, rd, tau };
576
+ }
577
+
578
+ // generic building: sections [{w,d,h,col,style,colW,floorH}], roof {col, h, details}
579
+ const buildings = [];
580
+ const flashDelay = (x, z) => PULSE_T + Math.max(Math.max(Math.abs(x), Math.abs(z)) - PULSE_R0, 0) / PULSE_V;
581
+ function makeBuilding(def) {
582
+ const g = new THREE.Group();
583
+ g.position.set(def.x, 0, def.z);
584
+ island.add(g);
585
+ const base = mesh(rbox(def.bw, 0.07, def.bd, 0.03), std(def.baseCol || '#E6E1F7', { rough: 0.8 }));
586
+ g.add(base);
587
+ const walls = new THREE.Group(); walls.position.y = 0.07; g.add(walls);
588
+ let y = 0; const mats = []; const clayMats = [];
589
+ for (const s of def.sections) {
590
+ let m;
591
+ if (s.shape === 'cyl') {
592
+ m = mesh(cyl(s.w / 2, s.w / 2, s.h, 40), winMat(U, s.col, [s.w, s.h, s.w], 1, { floorH: s.floorH ?? 0.28, seed: def.seed + y, litFrac: 0.45, baseY: y, cyl: 1, grow: def.t0 }));
593
+ } else {
594
+ m = mesh(rbox(s.w, s.h, s.d, s.r ?? 0.05), winMat(U, s.col, [s.w, s.h, s.d], s.style ?? 0, { floorH: s.floorH ?? 0.3, colW: s.colW ?? 0.22, seed: def.seed + y, litFrac: s.litFrac ?? 0.7, baseY: y, grow: def.t0 }));
595
+ }
596
+ m.position.y = y;
597
+ mats.push(m.material);
598
+ walls.add(m);
599
+ if (s.ledge) {
600
+ const lm = clayMat(U, s.ledge, { rough: 0.7, delay: (y + s.h) / 9 }); clayMats.push(lm);
601
+ const l = mesh(rbox(s.w + 0.06, 0.05, (s.d ?? s.w) + 0.06, 0.02), lm);
602
+ l.position.y = y + s.h - 0.02; walls.add(l);
603
+ }
604
+ y += s.h;
605
+ }
606
+ const H = y;
607
+ const roof = new THREE.Group(); g.add(roof);
608
+ const top = def.sections[def.sections.length - 1];
609
+ if (def.roofShape !== 'none') {
610
+ const rm = clayMat(U, def.roofCol, { rough: 0.6, delay: H / 9 }); clayMats.push(rm);
611
+ if (top.shape === 'cyl') roof.add(mesh(cyl(top.w / 2 + 0.04, top.w / 2 + 0.04, 0.08, 40), rm));
612
+ else roof.add(mesh(rbox(top.w + 0.06, 0.08, top.d + 0.06, 0.035), rm));
613
+ }
614
+ const details = new THREE.Group(); details.position.y = 0.08; roof.add(details);
615
+ (def.details || []).forEach((d) => details.add(d));
616
+ const lit0 = def.t0 + 0.42;
617
+ for (const m of mats) { m.userData.u.uLit0.value = lit0; m.userData.u.uFlash.value = flashDelay(def.x, def.z); }
618
+ for (const m of clayMats) m.userData.u.uFlash.value = flashDelay(def.x, def.z);
619
+ const b = { ...def, g, base, walls, roof, details, H, mats };
620
+ buildings.push(b);
621
+ return b;
622
+ }
623
+ onUpdate((T) => {
624
+ for (const b of buildings) {
625
+ if (b.custom) continue;
626
+ const st = growState(TGv, b.t0);
627
+ const vis = st.bd.vis;
628
+ b.g.visible = !!vis;
629
+ if (!vis) continue;
630
+ b.base.position.y = st.bd.y; b.base.scale.set(st.bd.sxz, st.bd.sy, st.bd.sxz);
631
+ b.walls.visible = st.wall > 0.002;
632
+ const w = Math.max(st.wall, 1e-4);
633
+ const pulse = pulseBump(b.x, b.z, T) * 0.5;
634
+ b.walls.scale.set(1 - 0.06 * Math.max(0, st.wall - 1), w * (1 + pulse), 1 - 0.06 * Math.max(0, st.wall - 1));
635
+ b.walls.visible = st.wall > 0.002;
636
+ b.walls.position.y = 0.07 + st.bd.y;
637
+ const topY = 0.07 + b.H * w * (1 + pulse);
638
+ b.roof.visible = st.rd.vis > 0;
639
+ b.roof.position.y = topY + st.rd.y;
640
+ b.roof.scale.set(st.rd.sxz, st.rd.sy, st.rd.sxz);
641
+ // rooftop details pop after the cap
642
+ const dp = A.spring(TGv - b.t0 - cues.roof_lag - 0.05, 24, 0.5, 10);
643
+ b.details.scale.setScalar(Math.max(dp, 1e-4)); b.details.visible = dp > 0.001;
644
+ }
645
+ });
646
+
647
+ // rooftop detail factories
648
+ const acUnit = (x, z, s = 1) => { const m = mesh(rbox(0.16 * s, 0.09 * s, 0.12 * s, 0.025), std('#E7E3F6', { rough: 0.6 })); m.position.set(x, 0, z); return m; };
649
+ const mast = (x, z, h = 0.5) => {
650
+ const g = new THREE.Group(); g.position.set(x, 0, z);
651
+ g.add(mesh(cyl(0.012, 0.018, h, 8), std('#E9E6F7', { rough: 0.5 })));
652
+ const l = mesh(new THREE.SphereGeometry(0.03, 12, 8), std('#FF7A6A', { emissive: '#FF6B5B', ei: 2.2 }), false);
653
+ l.position.y = h; g.add(l);
654
+ return g;
655
+ };
656
+ const garden = (w, d) => {
657
+ const g = new THREE.Group();
658
+ g.add(mesh(rbox(w, 0.04, d, 0.015), std(PAL.grassDeep, { rough: 0.9 })));
659
+ for (let k = 0; k < 3; k++) {
660
+ const s = mesh(new THREE.SphereGeometry(0.07, 14, 10), std(PAL.leaf[k % 4], { rough: 0.9 }));
661
+ s.position.set((k - 1) * w * 0.28, 0.08, (k % 2 ? 0.1 : -0.08) * d); g.add(s);
662
+ }
663
+ return g;
664
+ };
665
+ const solarRoof = (w, d) => {
666
+ const g = new THREE.Group();
667
+ for (let a = 0; a < 2; a++) for (let c = 0; c < 2; c++) {
668
+ const p = mesh(rbox(w * 0.4, 0.02, d * 0.36, 0.008), std('#5E7BD6', { rough: 0.3, env: 0.9 }));
669
+ p.position.set((a - 0.5) * w * 0.46, 0.06, (c - 0.5) * d * 0.44); p.rotation.x = -0.35; g.add(p);
670
+ }
671
+ return g;
672
+ };
673
+ const helipad = (r) => {
674
+ const g = new THREE.Group();
675
+ g.add(mesh(cyl(r, r, 0.03, 40), std('#6E6AB0', { rough: 0.7 })));
676
+ const ring = mesh(new THREE.TorusGeometry(r * 0.72, 0.012, 6, 48), std('#FFFFFF', { rough: 0.5 }), false);
677
+ ring.rotation.x = Math.PI / 2; ring.position.y = 0.032; g.add(ring);
678
+ const hb1 = mesh(new THREE.BoxGeometry(0.03, 0.01, 0.2), std('#FFFFFF'), false); hb1.position.set(-0.06, 0.035, 0);
679
+ const hb2 = hb1.clone(); hb2.position.x = 0.06;
680
+ const hb3 = mesh(new THREE.BoxGeometry(0.12, 0.01, 0.03), std('#FFFFFF'), false); hb3.position.y = 0.035;
681
+ g.add(hb1, hb2, hb3);
682
+ return g;
683
+ };
684
+
685
+ // ================================================================= DOWNTOWN
686
+ const dt = cues.downtown;
687
+ const lot = (i0, j0, wi, dj) => ({ x: tx(i0) + (wi - 1) / 2, z: tz(j0) + (dj - 1) / 2, fw: wi, fd: dj });
688
+ const defs = [];
689
+ const add = (id, l, o) => defs.push({ id, t0: dt[id], seed: defs.length * 3.1, ...l, bw: l.fw - 0.1, bd: l.fd - 0.1, ...o });
690
+ // A: stepped landmark, back corner
691
+ add('A', lot(3, 3, 2, 2), {
692
+ sections: [
693
+ { w: 1.62, d: 1.62, h: 1.25, col: PAL.white, ledge: PAL.lilac, colW: 0.2 },
694
+ { w: 1.28, d: 1.28, h: 0.7, col: '#EAF3FF', style: 1, floorH: 0.25, ledge: PAL.white, litFrac: 0.45 },
695
+ { w: 0.9, d: 0.9, h: 0.45, col: PAL.white, colW: 0.18 },
696
+ ], roofCol: PAL.deepSky, details: [mast(0.15, -0.1, 0.35), acUnit(-0.2, 0.18)],
697
+ });
698
+ add('B', lot(5, 3, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.6, col: '#EAF3FF', style: 1, floorH: 0.24, litFrac: 0.45 }], roofCol: PAL.white, details: [acUnit(0.12, 0.1), mast(-0.18, -0.15, 0.4)] });
699
+ add('C', lot(6, 3, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.6, col: PAL.peach }], roofCol: PAL.coral, details: [garden(0.5, 0.5)] });
700
+ add('D', lot(7, 3, 2, 2), {
701
+ sections: [{ shape: 'cyl', w: 1.5, h: 1.2, col: PAL.sky }, { shape: 'cyl', w: 1.16, h: 2.4, col: '#EAF3FF' }],
702
+ roofCol: PAL.white, details: [helipad(0.34)],
703
+ });
704
+ add('E', lot(3, 5, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.5, col: PAL.peach, ledge: PAL.white }], roofCol: PAL.white, details: [solarRoof(0.66, 0.66)] });
705
+ add('F', lot(3, 6, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.9, col: PAL.mint }], roofCol: PAL.white, details: [acUnit(0, 0), acUnit(0.2, -0.18, 0.8)] });
706
+ add('G', lot(8, 5, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 2.3, col: PAL.mint, ledge: PAL.peach }], roofCol: PAL.coral, details: [garden(0.52, 0.52)] });
707
+ add('H', lot(8, 6, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.7, col: PAL.butter }], roofCol: PAL.white, details: [solarRoof(0.66, 0.66)] });
708
+ add('I', lot(3, 7, 2, 2), {
709
+ sections: [{ w: 1.62, d: 1.62, h: 1.25, col: PAL.white, colW: 0.24, ledge: PAL.peach }, { w: 1.1, d: 1.1, h: 0.6, col: PAL.lilac }],
710
+ roofCol: PAL.peach, details: [garden(0.8, 0.8)],
711
+ });
712
+ add('J', lot(5, 8, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.15, col: PAL.sky, floorH: 0.26 }], roofCol: PAL.white, details: [acUnit(0.1, 0.1)] });
713
+ add('K', lot(6, 8, 1, 1), { sections: [{ w: 0.78, d: 0.78, h: 1.45, col: PAL.peach }], roofCol: PAL.white, details: [acUnit(-0.1, 0.12), acUnit(0.15, -0.12, 0.8)] });
714
+ // L: data center + dome
715
+ const dome = mesh(new THREE.SphereGeometry(0.52, 40, 20, 0, Math.PI * 2, 0, Math.PI / 2), std(PAL.white, { rough: 0.35, env: 0.8 }));
716
+ dome.position.set(0.25, 0, 0.25);
717
+ const domeRing = mesh(new THREE.TorusGeometry(0.52, 0.03, 8, 48), std(PAL.deepSky, { rough: 0.5 }));
718
+ domeRing.rotation.x = Math.PI / 2; domeRing.position.set(0.25, 0.01, 0.25);
719
+ add('L', lot(7, 7, 2, 2), {
720
+ sections: [{ w: 1.66, d: 1.66, h: 0.72, col: PAL.lilac, style: 2, colW: 0.12, floorH: 0.5, litFrac: 0.9 }],
721
+ roofCol: PAL.white, details: [dome, domeRing, acUnit(-0.5, -0.45), acUnit(-0.5, -0.15), acUnit(-0.2, -0.5)],
722
+ });
723
+ defs.forEach((d) => makeBuilding(d));
724
+
725
+ // ================================================================= HOUSES (grow in symmetric pairs)
726
+ const housesTiles = [];
727
+ for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) if (MAP[j][i] === 'h') housesTiles.push([i, j]);
728
+ // order by distance from centre (outermost first), paired
729
+ housesTiles.sort((a, b) => (Math.hypot(tx(b[0]), tz(b[1])) - Math.hypot(tx(a[0]), tz(a[1]))));
730
+ const roofCols = [PAL.coral, PAL.deepSky, PAL.peach, '#B9A8F3'];
731
+ const wallCols = [PAL.white, PAL.butter, PAL.lilac, PAL.mint, PAL.peach, PAL.sky];
732
+ housesTiles.forEach(([i, j], k) => {
733
+ const t0 = cues.houses[`${i},${j}`];
734
+ const rot = (A.hash(i * 3 + j * 11) > 0.5) ? 0 : Math.PI / 2;
735
+ const w = 0.52, d = 0.42, h = 0.36;
736
+ const g = new THREE.Group();
737
+ g.position.set(tx(i) + (A.hash(i + j) - 0.5) * 0.12, 0, tz(j) + (A.hash(i * j + 3) - 0.5) * 0.12);
738
+ island.add(g);
739
+ const base = mesh(rbox(0.66, 0.05, 0.56, 0.02), std(PAL.plaza, { rough: 0.8 }));
740
+ base.rotation.y = rot; g.add(base);
741
+ const walls = new THREE.Group(); walls.position.y = 0.05; walls.rotation.y = rot; g.add(walls);
742
+ const wm = winMat(U, wallCols[k % wallCols.length], [w, h, d], 0, { floorH: 0.16, colW: 0.17, seed: k * 1.7, litFrac: 0.8, grow: t0 });
743
+ walls.add(mesh(rbox(w, h, d, 0.03), wm));
744
+ // gable roof
745
+ const sh = new THREE.Shape(); sh.moveTo(-d / 2 - 0.05, 0); sh.lineTo(d / 2 + 0.05, 0); sh.lineTo(0, 0.24); sh.closePath();
746
+ const rg = new THREE.ExtrudeGeometry(sh, { depth: w + 0.08, bevelEnabled: true, bevelThickness: 0.012, bevelSize: 0.012, bevelSegments: 2 });
747
+ rg.translate(0, 0, -(w + 0.08) / 2); rg.rotateY(Math.PI / 2);
748
+ const roof = new THREE.Group(); roof.rotation.y = rot; g.add(roof);
749
+ const hrm = clayMat(U, roofCols[k % 4], { rough: 0.6, delay: 0.4 / 9 });
750
+ roof.add(mesh(rg, hrm));
751
+ const chim = mesh(rbox(0.06, 0.12, 0.06, 0.01), std(PAL.white)); chim.position.set(0.12, 0.1, 0.06); roof.add(chim);
752
+ wm.userData.u.uLit0.value = t0 + 0.4; wm.userData.u.uFlash.value = flashDelay(g.position.x, g.position.z);
753
+ hrm.userData.u.uFlash.value = flashDelay(g.position.x, g.position.z);
754
+ buildings.push({ id: 'h' + k, x: g.position.x, z: g.position.z, t0, g, base, walls, roof, details: new THREE.Group(), H: h, mats: [wm], house: true });
755
+ });
756
+
757
+ // ================================================================= TREES (instanced, pop in)
758
+ const treeSpots = [];
759
+ const TR = A.rng(99);
760
+ for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) {
761
+ const ch = MAP[j][i]; const x = tx(i), z = tz(j);
762
+ if (ch === 'f') {
763
+ const n = 3 + Math.floor(TR() * 2);
764
+ for (let k = 0; k < n; k++) treeSpots.push({ x: x + (TR() - 0.5) * 0.62, z: z + (TR() - 0.5) * 0.62, s: 0.8 + TR() * 0.45, kind: TR() < 0.35 ? 1 : 0 });
765
+ } else if (ch === 'p') {
766
+ TR(); TR(); TR(); // round 2: plaza trees removed so the plaza tiles can flip at the hit (RNG calls kept: other trees stay put)
767
+ } else if (ch === '.' && TR() < 0.55) {
768
+ const n = 1 + Math.floor(TR() * 2);
769
+ for (let k = 0; k < n; k++) treeSpots.push({ x: x + (TR() - 0.5) * 0.6, z: z + (TR() - 0.5) * 0.6, s: 0.65 + TR() * 0.4, kind: TR() < 0.3 ? 1 : 0 });
770
+ } else if (ch === 'h' && TR() < 0.6) {
771
+ treeSpots.push({ x: x + 0.34 * (TR() > 0.5 ? 1 : -1), z: z + 0.34 * (TR() > 0.5 ? 1 : -1), s: 0.6, kind: 0 });
772
+ }
773
+ }
774
+ treeSpots.forEach((s, k) => {
775
+ s.t0 = front(sxOf(s.x, s.z)) + 0.05 + A.hash(k * 5.3) * 0.12; // pops as the growth front passes
776
+ const r = A.hash(k * 2.1);
777
+ s.col = r < 0.1 ? PAL.blossom : r < 0.18 ? PAL.lilacLeaf : PAL.leaf[k % 4];
778
+ });
779
+ const roundT = treeSpots.filter((s) => s.kind === 0), coneT = treeSpots.filter((s) => s.kind === 1);
780
+ const foliageGeo = new THREE.IcosahedronGeometry(0.16, 3).translate(0, 0.3, 0);
781
+ const coneGeo = mergeGeometries([new THREE.ConeGeometry(0.15, 0.3, 20).translate(0, 0.3, 0), new THREE.ConeGeometry(0.12, 0.24, 20).translate(0, 0.44, 0)]);
782
+ const trunkGeo = cyl(0.022, 0.03, 0.2, 8);
783
+ const leafMat = std('#ffffff', { rough: 0.85, env: 0.3 });
784
+ const mkIM = (geo, list, mat) => { const im = new THREE.InstancedMesh(geo, mat, Math.max(1, list.length)); im.castShadow = true; im.receiveShadow = true; im.frustumCulled = false; island.add(im); return im; };
785
+ const roundIM = mkIM(foliageGeo, roundT, leafMat), coneIM = mkIM(coneGeo, coneT, leafMat);
786
+ const trunkIM = mkIM(trunkGeo, treeSpots, std(PAL.trunk, { rough: 0.9 }));
787
+ roundT.forEach((s, k) => roundIM.setColorAt(k, C(s.col)));
788
+ coneT.forEach((s, k) => coneIM.setColorAt(k, C(s.kind ? PAL.leaf[(k + 2) % 4] : s.col)));
789
+ onUpdate((T) => {
790
+ const place = (im, list) => list.forEach((s, k) => {
791
+ const p = A.spring(T - s.t0, 22, 0.45, 13) * s.s;
792
+ const wob = Math.sin((T - s.t0) * 9) * Math.exp(-(T - s.t0) * 5) * 0.12 * (T > s.t0 ? 1 : 0);
793
+ _q.setFromEuler(new THREE.Euler(wob, 0, wob * 0.6));
794
+ _s.setScalar(Math.max(p, 1e-4)); _p.set(s.x, pulseBump(s.x, s.z, T), s.z);
795
+ _m.compose(_p, _q, _s); im.setMatrixAt(k, _m);
796
+ });
797
+ place(roundIM, roundT); place(coneIM, coneT); place(trunkIM, treeSpots);
798
+ roundIM.instanceMatrix.needsUpdate = coneIM.instanceMatrix.needsUpdate = trunkIM.instanceMatrix.needsUpdate = true;
799
+ });
800
+
801
+ // ================================================================= WIND TURBINES
802
+ const turbines = [];
803
+ const tSpots = [];
804
+ for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) if (MAP[j][i] === 't') tSpots.push([i, j]);
805
+ tSpots.forEach(([i, j], k) => {
806
+ const g = new THREE.Group(); g.position.set(tx(i), 0, tz(j)); island.add(g);
807
+ const H = 2.3 + (k % 2) * 0.35;
808
+ const mast = mesh(cyl(0.035, 0.06, H, 16), std(PAL.white, { rough: 0.5 }));
809
+ g.add(mast);
810
+ const head = new THREE.Group(); head.position.y = H; g.add(head);
811
+ head.rotation.y = Math.PI / 4; // rotor faces the camera diagonal
812
+ head.add(mesh(rbox(0.1, 0.1, 0.24, 0.04).translate(0, -0.05, -0.05), std(PAL.white, { rough: 0.5 })));
813
+ const rotor = new THREE.Group(); rotor.position.z = 0.1; head.add(rotor);
814
+ rotor.add(mesh(new THREE.SphereGeometry(0.055, 16, 12), std(PAL.coral, { rough: 0.5 })));
815
+ for (let b = 0; b < 3; b++) {
816
+ const bl = mesh(rbox(0.07, 0.95, 0.02, 0.01).translate(0, 0.02, 0), std(PAL.white, { rough: 0.45 }));
817
+ bl.rotation.z = b * Math.PI * 2 / 3; rotor.add(bl);
818
+ }
819
+ turbines.push({ g, mast, head, rotor, H, t0: Math.round(front(sxOf(tx(i), tz(j))) / 0.125) * 0.125, ph: k * 0.9 });
820
+ });
821
+ onUpdate((T) => {
822
+ for (const tb of turbines) {
823
+ const tau = T - tb.t0;
824
+ const s = A.spring(tau, 16, 0.6, 5);
825
+ tb.g.visible = tau > 0;
826
+ tb.mast.scale.set(1, Math.max(s, 1e-4), 1);
827
+ tb.head.position.y = tb.H * s;
828
+ const hp = A.spring(tau - 0.25, 20, 0.5, 10);
829
+ tb.head.scale.setScalar(Math.max(hp, 1e-4));
830
+ // spin: accelerates in, then constant (closed form: integral of speed ramp)
831
+ const w = 4.2; const ramp = 0.8; const u = Math.max(0, tau - 0.3);
832
+ const ang = u < ramp ? w * u * u / (2 * ramp) : w * (u - ramp / 2);
833
+ tb.rotor.rotation.z = -ang - tb.ph;
834
+ }
835
+ });
836
+
837
+ // ================================================================= SOLAR FIELD (panels flip up)
838
+ const solar = [];
839
+ for (let j = 0; j < N; j++) for (let i = 0; i < N; i++) if (MAP[j][i] === 's') {
840
+ for (let a = 0; a < 2; a++) for (let c = 0; c < 2; c++) {
841
+ const g = new THREE.Group(); g.position.set(tx(i) + (a - 0.5) * 0.46, 0, tz(j) + (c - 0.5) * 0.44); island.add(g);
842
+ g.add(mesh(cyl(0.015, 0.015, 0.1, 6), std('#DCD7F2')));
843
+ const p = new THREE.Group(); p.position.y = 0.1; g.add(p);
844
+ p.add(mesh(rbox(0.38, 0.025, 0.3, 0.01).translate(0, -0.012, 0), std('#5E7BD6', { rough: 0.25, env: 1.0 })));
845
+ const fr = mesh(rbox(0.4, 0.012, 0.32, 0.006).translate(0, -0.022, 0), std(PAL.white)); p.add(fr);
846
+ solar.push({ g, p, t0: front(sxOf(g.position.x, g.position.z)) + a * 0.04 + c * 0.03 });
847
+ }
848
+ }
849
+ onUpdate((T) => {
850
+ for (const s of solar) {
851
+ const u = A.spring(T - s.t0, 20, 0.5, 8);
852
+ s.g.visible = T > s.t0; s.g.scale.setScalar(Math.max(Math.min(u * 1.2, 1), 1e-4));
853
+ s.p.rotation.z = -0.5 * u; // tilt toward the sun
854
+ }
855
+ });
856
+
857
+ // ================================================================= POND details (dock + ripples)
858
+ const dock = mesh(rbox(0.9, 0.05, 0.22, 0.02), std('#E9D9CF', { rough: 0.8 }));
859
+ dock.position.set(tx(10) - 0.1, 0.0, tz(3)); island.add(dock);
860
+ onUpdate((T) => { const u = A.spring(T - 2.2, 22, 0.5, 9); dock.visible = T > 2.2; dock.scale.setScalar(Math.max(u, 1e-4)); });
861
+ const rippleMat = new THREE.MeshBasicMaterial({ color: C('#FFFFFF'), transparent: true, opacity: 0.0, depthWrite: false });
862
+ const ripples = [];
863
+ for (let k = 0; k < 3; k++) {
864
+ const r = new THREE.Mesh(new THREE.RingGeometry(0.9, 1.0, 48).rotateX(-Math.PI / 2), rippleMat.clone());
865
+ r.position.set(tx(10) + 0.5, 0.004, tz(2) + 0.6 + k * 0.4); r.layers.set(LAYER_GLOW); island.add(r); ripples.push(r);
866
+ }
867
+ onUpdate((T) => {
868
+ ripples.forEach((r, k) => {
869
+ const ph = ((T * 0.45 + k / 3) % 1);
870
+ const on = A.smooth(A.inv(1.8, 2.3, T));
871
+ r.scale.setScalar(0.08 + ph * 0.32);
872
+ r.material.opacity = 0.5 * (1 - ph) * on;
873
+ });
874
+ });
875
+
876
+ // ================================================================= MONORAIL (pillars chain-grow, beam, train)
877
+ const railOff = -0.42; // over the inner curb
878
+ const rail = loopPath(3.5 + railOff, 0.5 + railOff);
879
+ const RAIL_H = 1.25;
880
+ const pillars = [];
881
+ const nP = 20;
882
+ for (let k = 0; k < nP; k++) {
883
+ const s = k / nP * rail.L;
884
+ const p = rail.at(s);
885
+ const g = new THREE.Group(); g.position.set(p.x, 0, p.z); island.add(g);
886
+ g.add(mesh(cyl(0.035, 0.05, RAIL_H, 14), std('#ECE8FA', { rough: 0.55 })));
887
+ pillars.push({ g, t0: cues.rail + k * 0.022 });
888
+ }
889
+ // beam: build from many short segments so it can be drawn on
890
+ const nB = 96;
891
+ const beamIM = new THREE.InstancedMesh(new RoundedBoxGeometry(1, 0.075, 0.12, 1, 0.025).translate(0, -0.04, 0), std('#E4DFF7', { rough: 0.5 }), nB);
892
+ beamIM.castShadow = true; beamIM.receiveShadow = true; beamIM.frustumCulled = false; island.add(beamIM);
893
+ const segL = rail.L / nB;
894
+ onUpdate((T) => {
895
+ for (const p of pillars) {
896
+ const u = A.spring(T - p.t0, 20, 0.6, 7);
897
+ p.g.visible = T > p.t0; p.g.scale.set(1, Math.max(u, 1e-4), 1);
898
+ }
899
+ for (let k = 0; k < nB; k++) {
900
+ const s = (k + 0.5) * segL;
901
+ const p = rail.at(s);
902
+ const t0 = cues.rail + 0.15 + k * 0.0048;
903
+ const u = A.easeOutCubic(A.inv(t0, t0 + 0.12, T));
904
+ _q.setFromAxisAngle(new THREE.Vector3(0, 1, 0), p.ang - Math.PI / 2);
905
+ _s.set(Math.max(u, 1e-4) * segL * 1.04, u > 0.001 ? 1 : 0, u > 0.001 ? 1 : 0); _p.set(p.x, RAIL_H + (1 - u) * 0.3, p.z);
906
+ _m.compose(_p, _q, _s); beamIM.setMatrixAt(k, _m);
907
+ }
908
+ beamIM.instanceMatrix.needsUpdate = true;
909
+ });
910
+ // train
911
+ const train = [];
912
+ const carLen = 0.78;
913
+ for (let k = 0; k < 3; k++) {
914
+ const g = new THREE.Group(); island.add(g);
915
+ const body = new THREE.Group(); g.add(body);
916
+ body.add(mesh(rbox(carLen, 0.22, 0.24, 0.09).translate(0, 0.02, 0), std(PAL.white, { rough: 0.35, env: 0.8 })));
917
+ const band = mesh(rbox(carLen - 0.1, 0.07, 0.25, 0.03).translate(0, 0.12, 0), std('#6D83DA', { rough: 0.2, env: 1.0, emissive: '#FFE39C', ei: 0 }));
918
+ body.add(band);
919
+ const stripe = mesh(rbox(carLen - 0.04, 0.025, 0.245, 0.01).translate(0, 0.05, 0), std(PAL.coral, { rough: 0.5 }));
920
+ body.add(stripe);
921
+ train.push({ g, body, band, k });
922
+ }
923
+ onUpdate((T) => {
924
+ const t0 = cues.train;
925
+ const tau = T - t0;
926
+ // closed-form motion: ramp to cruise speed v over 0.8 s
927
+ const v = 3.2, ramp = 0.8;
928
+ const u = Math.max(0, tau);
929
+ const dist = u < ramp ? v * u * u / (2 * ramp) : v * (u - ramp / 2);
930
+ const s0 = rail.L * 0.62;
931
+ const appear = A.spring(tau + 0.15, 22, 0.55, 8);
932
+ train.forEach((c) => {
933
+ const s = s0 + dist - c.k * (carLen + 0.05);
934
+ const p = rail.at(s);
935
+ c.g.position.set(p.x, RAIL_H + 0.0, p.z);
936
+ c.g.rotation.y = p.ang - Math.PI / 2;
937
+ c.g.visible = tau > -0.15;
938
+ c.g.scale.setScalar(Math.max(appear, 1e-4));
939
+ c.band.material.emissiveIntensity = T > PULSE_T ? 0.8 : 0;
940
+ });
941
+ });
942
+
943
+ // ================================================================= CARS on the ring road
944
+ const cars = [];
945
+ const carCols = [PAL.coral, PAL.white, PAL.deepSky, PAL.butter, PAL.peach, '#B9A8F3', PAL.white, PAL.mint];
946
+ const lanes = [loopPath(3.5 + 0.2, 0.5 + 0.2), loopPath(3.5 - 0.2, 0.5 - 0.2)];
947
+ for (let k = 0; k < 10; k++) {
948
+ const g = new THREE.Group(); island.add(g);
949
+ const lane = k % 2;
950
+ const col = carCols[k % carCols.length];
951
+ const body = new THREE.Group(); g.add(body);
952
+ body.add(mesh(rbox(0.3, 0.085, 0.15, 0.035).translate(0, 0.02, 0), std(col, { rough: 0.35, env: 0.7 })));
953
+ body.add(mesh(rbox(0.16, 0.07, 0.13, 0.03).translate(-0.02, 0.1, 0), std('#E8EEFF', { rough: 0.15, env: 1.0 })));
954
+ const hl = mesh(rbox(0.012, 0.022, 0.12, 0.006).translate(0.152, 0.055, 0), std('#FFFFFF', { emissive: '#FFF2C4', ei: 1.5 }), false);
955
+ body.add(hl);
956
+ cars.push({ g, body, lane, s0: (k / 10) * lanes[lane].L + A.hash(k) * 1.2, v: (lane ? -1 : 1) * (1.7 + A.hash(k * 3) * 0.5), t0: cues.cars + (k % 5) * 0.05 });
957
+ }
958
+ onUpdate((T) => {
959
+ for (const c of cars) {
960
+ const tau = T - c.t0;
961
+ const ramp = 0.7, u = Math.max(0, tau);
962
+ const dist = u < ramp ? c.v * u * u / (2 * ramp) : c.v * (u - ramp / 2);
963
+ const p = lanes[c.lane].at(c.s0 + dist);
964
+ c.g.position.set(p.x, pulseBump(p.x, p.z, T), p.z);
965
+ c.g.rotation.y = p.ang - Math.PI / 2 + (c.v < 0 ? Math.PI : 0);
966
+ const ap = A.spring(tau, 24, 0.5, 10);
967
+ c.g.visible = tau > 0; c.g.scale.setScalar(Math.max(ap, 1e-4));
968
+ }
969
+ });
970
+
971
+ // ================================================================= CORE TOWER (telescopes up)
972
+ const tw = cues.tower;
973
+ const core = new THREE.Group(); island.add(core);
974
+ const coreBase = mesh(cyl(0.92, 0.95, 0.2, 48), std(PAL.white, { rough: 0.5 }));
975
+ core.add(coreBase);
976
+ const coreBaseRing = mesh(new THREE.TorusGeometry(0.93, 0.025, 8, 64), std('#FFFFFF', { emissive: PAL.data, ei: 0 }), false);
977
+ coreBaseRing.rotation.x = Math.PI / 2; coreBaseRing.position.y = 0.2; core.add(coreBaseRing);
978
+ const SH1 = 2.3, SH2 = 2.0, SH3 = 1.6, SHA = 1.2;
979
+ const coreWin = (seed) => ({ floorH: 0.24, seed, litFrac: 0.0, winOn: '#7DF6F7', winOff: '#4A45A0', emit: 2.5, seam: 1, clay: PAL.ink, clayWin: '#6F69C8', rough: 0.34, env: 0.9, cyl: 1, grow: tw.seg1 });
980
+ const seg1 = mesh(cyl(0.6, 0.66, SH1, 48), winMat(U, PAL.ink, [1.2, SH1, 1.2], 1, coreWin(91)));
981
+ const seg2 = mesh(cyl(0.44, 0.44, SH2, 48), winMat(U, '#34307A', [0.88, SH2, 0.88], 1, { ...coreWin(92), clay: '#34307A', grow: tw.seg2 }));
982
+ const seg3 = mesh(cyl(0.3, 0.3, SH3, 40), winMat(U, PAL.ink, [0.6, SH3, 0.6], 1, { ...coreWin(93), grow: tw.seg3 }));
983
+ const cap3 = mesh(cyl(0.34, 0.34, 0.07, 40), std('#FFFFFF', { rough: 0.4, emissive: PAL.data, ei: 0.6 }));
984
+ const antenna = mesh(cyl(0.02, 0.035, SHA, 12), std(PAL.white, { rough: 0.4 }));
985
+ const beacon = mesh(new THREE.SphereGeometry(0.07, 20, 14), std('#FFFFFF', { emissive: PAL.data, ei: 0 }), false);
986
+ const halo = mesh(new THREE.TorusGeometry(0.78, 0.022, 12, 96), std('#FFFFFF', { rough: 0.3, emissive: PAL.data, ei: 0 }), false);
987
+ halo.rotation.x = Math.PI / 2;
988
+ const halo2 = mesh(new THREE.TorusGeometry(0.62, 0.013, 10, 96), std('#FFFFFF', { rough: 0.3, emissive: PAL.data, ei: 0 }), false);
989
+ halo2.rotation.x = Math.PI / 2;
990
+ core.add(seg1, seg2, seg3, cap3, antenna, beacon, halo, halo2);
991
+ for (const s of [seg1, seg2, seg3]) { s.material.userData.u.uLit0.value = tw.ring; s.material.userData.u.uFlash.value = PULSE_T; }
992
+ onUpdate((T) => {
993
+ const bd = A.drop(T - tw.base, 0.1, 0.35, 0, 0.04);
994
+ core.visible = bd.vis > 0;
995
+ coreBase.position.y = bd.y; coreBase.scale.set(bd.sxz, bd.sy, bd.sxz);
996
+ const s1 = A.spring(T - tw.seg1, 17, 0.6, 5);
997
+ const s2 = A.spring(T - tw.seg2, 17, 0.6, 5);
998
+ const s3 = A.spring(T - tw.seg3, 17, 0.6, 5);
999
+ const sa = A.spring(T - tw.antenna, 18, 0.55, 6);
1000
+ const y0 = 0.2 + bd.y;
1001
+ seg1.position.y = y0; seg1.scale.set(1, Math.max(s1, 1e-4), 1); seg1.visible = s1 > 0.002;
1002
+ const top1 = y0 + SH1 * s1;
1003
+ // segment 2 slides up out of segment 1
1004
+ seg2.position.y = top1 - SH2 + SH2 * s2 - 0.02; seg2.visible = s2 > 0.002 && s1 > 0.9;
1005
+ const top2 = seg2.position.y + SH2;
1006
+ seg3.position.y = top2 - SH3 + SH3 * s3 - 0.02; seg3.visible = s3 > 0.002;
1007
+ const top3 = seg3.position.y + SH3;
1008
+ cap3.position.y = top3; cap3.visible = s3 > 0.2;
1009
+ antenna.position.y = top3 + 0.07 - SHA + SHA * sa; antenna.visible = sa > 0.002;
1010
+ beacon.position.y = top3 + 0.07 + SHA * sa; beacon.visible = sa > 0.3;
1011
+ // halo rings assemble
1012
+ const hr = A.spring(T - tw.ring, 16, 0.5, 6);
1013
+ halo.visible = hr > 0.002; halo.scale.setScalar(Math.max(hr, 1e-4));
1014
+ halo.position.y = top1 + 0.35 + Math.sin(T * 2.2) * 0.04;
1015
+ const hr2 = A.spring(T - tw.ring - 0.1, 16, 0.5, 6);
1016
+ halo2.visible = hr2 > 0.002; halo2.scale.setScalar(Math.max(hr2, 1e-4));
1017
+ halo2.position.y = top2 + 0.25 + Math.sin(T * 2.2 + 1) * 0.04;
1018
+ // ignite at the drop
1019
+ const ig = T >= PULSE_T ? 1 : 0;
1020
+ const fl = ig * Math.exp(-(T - PULSE_T) * 3.0);
1021
+ beacon.material.emissiveIntensity = ig * (2.5 + 6 * fl) * (0.8 + 0.2 * Math.sin(T * 9));
1022
+ halo.material.emissiveIntensity = ig * (1.0 + 2.2 * fl);
1023
+ halo2.material.emissiveIntensity = ig * (0.8 + 1.8 * fl);
1024
+ coreBaseRing.material.emissiveIntensity = ig * (1.2 + 3 * fl);
1025
+ // anticipation: the CORE crouches (-6 % Y, +3 % XZ) 5.90-5.967, holds, then releases into a stretch on the hit
1026
+ const crouch = T < PULSE_T ? A.smooth(A.inv(5.90, 5.967, T)) : 0;
1027
+ const punch = ig * Math.exp(-(T - PULSE_T) * 6) * Math.cos((T - PULSE_T) * 26) * 0.07;
1028
+ core.scale.set(1 + 0.03 * crouch - punch * 0.45, 1 - 0.06 * crouch + punch, 1 + 0.03 * crouch - punch * 0.45);
1029
+ });
1030
+ const coreTop = () => new THREE.Vector3(0, 0.2 + SH1 + 0.35, 0);
1031
+ // reel v5 (read-only helper, unused by the default path): local-space top of the telescoping CORE stack as it is
1032
+ // posed for the last update(T) -- what a ball resting on the tower touches (base -> seg1 -> seg2 -> seg3 cap -> antenna tip).
1033
+ const coreStackTop = () => {
1034
+ let h = coreBase.position.y + 0.2 * coreBase.scale.y;
1035
+ if (seg1.visible) h = Math.max(h, seg1.position.y + SH1 * seg1.scale.y);
1036
+ if (seg2.visible) h = Math.max(h, seg2.position.y + SH2);
1037
+ if (seg3.visible) h = Math.max(h, seg3.position.y + SH3 + (cap3.visible ? 0.07 : 0));
1038
+ if (antenna.visible) h = Math.max(h, beacon.position.y);
1039
+ return h;
1040
+ };
1041
+
1042
+ // ---- ignition: light column + air shockwave + halo nodes
1043
+ const glowMatBase = (col, extra = {}) => new THREE.ShaderMaterial({
1044
+ uniforms: { uI: { value: 0 }, uCol: { value: C(col) }, ...extra },
1045
+ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide,
1046
+ vertexShader: 'varying vec2 vUv; varying vec3 vN; void main(){ vUv = uv; vN = normalize(normalMatrix * normal); gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.); }',
1047
+ fragmentShader: 'uniform float uI; uniform vec3 uCol; varying vec2 vUv; varying vec3 vN; void main(){ float f = pow(abs(vN.z), 1.6) * pow(1.0 - vUv.y, 1.3); float a = uI * f; gl_FragColor = vec4(uCol * a, a); }',
1048
+ });
1049
+ const beamGeo = new THREE.CylinderGeometry(0.1, 0.16, 16, 32, 1, true).translate(0, 8, 0);
1050
+ const beam = new THREE.Mesh(beamGeo, glowMatBase('#F2FFFF')); beam.layers.set(LAYER_GLOW); beam.frustumCulled = false;
1051
+ const beam2 = new THREE.Mesh(new THREE.CylinderGeometry(0.17, 0.26, 16, 32, 1, true).translate(0, 8, 0), glowMatBase(PAL.data)); beam2.layers.set(LAYER_GLOW); beam2.frustumCulled = false;
1052
+ core.add(beam, beam2);
1053
+ const shockMat = new THREE.MeshBasicMaterial({ color: C('#9FFBFF'), transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false, side: THREE.DoubleSide });
1054
+ const shock = new THREE.Mesh(new THREE.RingGeometry(0.978, 1.0, 128).rotateX(-Math.PI / 2), shockMat); shock.layers.set(LAYER_GLOW); core.add(shock);
1055
+ const shock2 = new THREE.Mesh(new THREE.RingGeometry(0.96, 1.0, 128).rotateX(-Math.PI / 2), shockMat.clone()); shock2.layers.set(LAYER_GLOW); core.add(shock2);
1056
+ const nodes = new THREE.Group(); halo.add(nodes);
1057
+ for (let k = 0; k < 6; k++) {
1058
+ const a = k / 6 * Math.PI * 2;
1059
+ const nd = mesh(new THREE.SphereGeometry(0.06, 14, 10), std('#FFFFFF', { rough: 0.3, emissive: PAL.data, ei: 1.6 }), false);
1060
+ nd.position.set(Math.cos(a) * 0.78, Math.sin(a) * 0.78, 0); nodes.add(nd);
1061
+ }
1062
+ onUpdate((T) => {
1063
+ const u = T - PULSE_T;
1064
+ const env = u < 0 ? 0 : (u < 0.012 ? u / 0.012 : Math.exp(-(u - 0.012) * 5.0));
1065
+ const topY = beacon.position.y;
1066
+ beam.position.y = topY; beam2.position.y = topY;
1067
+ const grow = A.easeOutCubic(A.inv(0, 0.05, u));
1068
+ beam.scale.set(1, Math.max(grow, 1e-3), 1); beam2.scale.set(1, Math.max(grow, 1e-3), 1);
1069
+ beam.material.uniforms.uI.value = env * 2.8 + (u > 0 ? 0.1 : 0);
1070
+ beam2.material.uniforms.uI.value = env * 0.4;
1071
+ beam.visible = beam2.visible = u > 0;
1072
+ const su = A.easeOutCubic(A.inv(0, 0.45, u));
1073
+ shock.visible = u > 0 && u < 0.45;
1074
+ shock.position.y = halo.position.y; shock.scale.setScalar(0.9 + su * 9.5);
1075
+ shock.material.opacity = Math.pow(1 - su, 1.8) * 0.95;
1076
+ const su2 = A.easeOutCubic(A.inv(0.06, 0.55, u));
1077
+ shock2.visible = u > 0.06 && u < 0.55;
1078
+ shock2.position.y = halo2.position.y; shock2.scale.setScalar(0.7 + su2 * 7.0);
1079
+ shock2.material.opacity = Math.pow(1 - su2, 1.8) * 0.55;
1080
+ // halo spin (closed form) + pre-glow before the drop
1081
+ halo.rotation.z = T * 1.2 + A.smooth(A.inv(5.7, 6.1, T)) * 2.5;
1082
+ const pre = A.smooth(A.inv(tw.ring + 0.05, PULSE_T, T));
1083
+ if (T < PULSE_T) { halo.material.emissiveIntensity = pre * 1.1; halo2.material.emissiveIntensity = pre * 0.8; }
1084
+ nodes.visible = halo.visible;
1085
+ // beacon pre-blinks
1086
+ if (T < PULSE_T && T > tw.antenna) {
1087
+ const bl = Math.max(Math.exp(-Math.pow((T - 5.625) * 30, 2)), Math.exp(-Math.pow((T - 5.875) * 30, 2)));
1088
+ beacon.material.emissiveIntensity = bl * 4;
1089
+ }
1090
+ });
1091
+
1092
+ // ================================================================= HERO PULSE (ground light wave)
1093
+ const pulseU = { uTime: U.uTime, uT0: { value: PULSE_T }, uV: { value: PULSE_V }, uCol: { value: C(PAL.data) } };
1094
+ const pulseMat = new THREE.ShaderMaterial({
1095
+ uniforms: pulseU, transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
1096
+ vertexShader: 'varying vec3 vW; void main(){ vec4 w = modelMatrix * vec4(position,1.); vW = w.xyz; gl_Position = projectionMatrix * viewMatrix * w; }',
1097
+ fragmentShader: /* glsl */`
1098
+ uniform float uTime, uT0, uV; uniform vec3 uCol; varying vec3 vW;
1099
+ void main(){
1100
+ float r = max(abs(vW.x), abs(vW.z));
1101
+ float age = uTime - uT0;
1102
+ float rt = 1.0 + age * uV;
1103
+ float d = r - rt;
1104
+ vec2 g = abs(fract(vW.xz + 0.5) - 0.5);
1105
+ float lw = fwidth(vW.x) * 1.5;
1106
+ float line = max(1.0 - smoothstep(0.018, 0.018 + lw, g.x), 1.0 - smoothstep(0.018, 0.018 + lw, g.y));
1107
+ float band = exp(-d * d * 5.0);
1108
+ float edge = exp(-d * d * 70.0);
1109
+ float after = step(d, 0.0) * exp(d * 0.7) * exp(-age * 1.4);
1110
+ float a = edge * 1.0 + band * 0.08 + line * (band * 2.0 + after * 0.35);
1111
+ a *= step(0.0, age) * (1.0 - smoothstep(5.96, 6.08, r)) * (1.0 - smoothstep(0.7, 1.15, age));
1112
+ gl_FragColor = vec4(uCol * a, a);
1113
+ }`,
1114
+ });
1115
+ const pulse = new THREE.Mesh(new THREE.PlaneGeometry(12.2, 12.2).rotateX(-Math.PI / 2), pulseMat);
1116
+ pulse.position.y = 0.012; pulse.layers.set(LAYER_GLOW); island.add(pulse);
1117
+ onUpdate((T) => { pulse.visible = T > PULSE_T - 0.01 && T < PULSE_T + 1.75; });
1118
+
1119
+ // ================================================================= DATA ARCS (core -> roofs)
1120
+ const arcs = [];
1121
+ const arcMatBase = {
1122
+ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending,
1123
+ vertexShader: 'varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.); }',
1124
+ fragmentShader: /* glsl */`
1125
+ uniform float uTime, uT0, uDur, uSpeed, uPh; uniform vec3 uCol; varying vec2 vUv;
1126
+ void main(){
1127
+ float x = vUv.x;
1128
+ float grow = clamp((uTime - uT0) / uDur, 0.0, 1.0);
1129
+ grow = 1.0 - pow(1.0 - grow, 3.0);
1130
+ if (x > grow) discard;
1131
+ float head = exp(-pow((grow - x) * 14.0, 2.0)) * step(grow, 0.999);
1132
+ float p = fract(x * 3.0 - (uTime - uT0) * uSpeed + uPh);
1133
+ float dash = smoothstep(0.0, 0.08, p) * (1.0 - smoothstep(0.12, 0.3, p));
1134
+ float a = 0.28 + dash * 1.5 + head * 2.6;
1135
+ gl_FragColor = vec4(uCol * a, a);
1136
+ }`,
1137
+ };
1138
+ const arcTargets = buildings.filter((b) => !b.house);
1139
+ arcTargets.forEach((b, k) => {
1140
+ const top = new THREE.Vector3(b.x, 0.07 + b.H + 0.12, b.z);
1141
+ const from = new THREE.Vector3(0, 0.2 + 1.7 + 0.35, 0);
1142
+ const mid = from.clone().lerp(top, 0.5); mid.y = Math.max(from.y, top.y) + 1.2 + A.hash(k) * 0.6;
1143
+ const curve = new THREE.QuadraticBezierCurve3(from, mid, top);
1144
+ const geo = new THREE.TubeGeometry(curve, 72, 0.021, 8, false);
1145
+ const m = new THREE.ShaderMaterial({
1146
+ ...arcMatBase,
1147
+ uniforms: { uTime: U.uTime, uT0: { value: PULSE_T + 0.05 + k * 0.035 }, uDur: { value: 0.45 }, uSpeed: { value: 1.3 + A.hash(k * 7) * 0.6 }, uPh: { value: A.hash(k * 3) }, uCol: { value: C(PAL.data) } },
1148
+ });
1149
+ const mm = new THREE.Mesh(geo, m); mm.layers.set(LAYER_GLOW); mm.frustumCulled = false; island.add(mm);
1150
+ // landing ring on the roof
1151
+ const ringM = new THREE.Mesh(new THREE.RingGeometry(0.1, 0.13, 32).rotateX(-Math.PI / 2), new THREE.MeshBasicMaterial({ color: C(PAL.data), transparent: true, opacity: 0, blending: THREE.AdditiveBlending, depthWrite: false }));
1152
+ ringM.position.copy(top).y -= 0.1; ringM.layers.set(LAYER_GLOW); island.add(ringM);
1153
+ arcs.push({ mesh: mm, ring: ringM, t0: m.uniforms.uT0.value, dur: 0.45, b });
1154
+ });
1155
+ onUpdate((T) => {
1156
+ for (const a of arcs) {
1157
+ a.mesh.visible = T > a.t0;
1158
+ const tl = a.t0 + a.dur;
1159
+ const u = T - tl;
1160
+ a.ring.visible = u > 0;
1161
+ if (u > 0) {
1162
+ const ph = (u * 0.9) % 1;
1163
+ a.ring.scale.setScalar(1 + ph * 2.2);
1164
+ a.ring.material.opacity = (1 - ph) * 0.9;
1165
+ }
1166
+ }
1167
+ });
1168
+
1169
+ // ================================================================= DRONES with packets (hop between roofs)
1170
+ const drones = [];
1171
+ const byId = Object.fromEntries(buildings.filter((b) => !b.house).map((b) => [b.id, b]));
1172
+ const routes = [
1173
+ { t0: 6.12, hops: ['L', 'G', 'C', 'K', 'E'] },
1174
+ { t0: 6.28, hops: ['L', 'E', 'J', 'H', 'F'] },
1175
+ { t0: 6.44, hops: ['I', 'H', 'F', 'G', 'J'] },
1176
+ { t0: 6.6, hops: ['D', 'K', 'I', 'C', 'H'] },
1177
+ { t0: 6.76, hops: ['L', 'F', 'G', 'J', 'K'] },
1178
+ ];
1179
+ const HOP = 1.05, DWELL = 0.32;
1180
+ routes.forEach((rt, k) => {
1181
+ const g = new THREE.Group(); island.add(g);
1182
+ const body = new THREE.Group(); body.scale.setScalar(1.9); g.add(body);
1183
+ body.add(mesh(rbox(0.14, 0.045, 0.14, 0.02), std(PAL.white, { rough: 0.4 })));
1184
+ for (let a = 0; a < 4; a++) {
1185
+ const ax = (a < 2 ? 1 : -1) * 0.1, az = (a % 2 ? 1 : -1) * 0.1;
1186
+ const arm = mesh(new THREE.BoxGeometry(0.1, 0.012, 0.012), std('#DAD5F0'));
1187
+ arm.position.set(ax / 2, 0.03, az / 2); arm.rotation.y = Math.atan2(-az, ax); body.add(arm);
1188
+ const r = mesh(new THREE.CylinderGeometry(0.055, 0.055, 0.006, 20), std('#CFC8F0', { rough: 0.4 }), false);
1189
+ r.position.set(ax, 0.045, az); body.add(r);
1190
+ }
1191
+ const eye = mesh(new THREE.SphereGeometry(0.02, 10, 8), std('#FFFFFF', { emissive: PAL.data, ei: 2.2 }), false);
1192
+ eye.position.set(0.075, 0.0, 0.0); body.add(eye);
1193
+ const line = mesh(new THREE.CylinderGeometry(0.004, 0.004, 0.1, 4).translate(0, -0.05, 0), std('#8E86C8'), false);
1194
+ body.add(line);
1195
+ const pkt = mesh(rbox(0.1, 0.1, 0.1, 0.022).translate(0, -0.1, 0), std([PAL.coral, PAL.butter, PAL.deepSky, PAL.peach, PAL.mint][k % 5], { rough: 0.6 }));
1196
+ pkt.position.y = -0.1; body.add(pkt);
1197
+ drones.push({ g, body, pkt, line, rt, k });
1198
+ });
1199
+ const roofPt = (id, dy) => { const b = byId[id]; return new THREE.Vector3(b.x, 0.07 + b.H + 0.08 + dy, b.z); };
1200
+ onUpdate((T) => {
1201
+ for (const d of drones) {
1202
+ const tau = T - d.rt.t0;
1203
+ d.g.visible = tau > -0.02;
1204
+ if (!d.g.visible) continue;
1205
+ const cyc = HOP + DWELL;
1206
+ const n = Math.min(d.rt.hops.length - 2, Math.floor(Math.max(0, tau) / cyc));
1207
+ const ph = Math.max(0, tau) - n * cyc;
1208
+ const a = roofPt(d.rt.hops[n], 0.34), b = roofPt(d.rt.hops[n + 1], 0.34);
1209
+ const u = A.easeInOutSine(A.clamp(ph / HOP));
1210
+ const p = a.clone().lerp(b, u);
1211
+ const arch = 0.55 + a.distanceTo(b) * 0.12;
1212
+ p.y += arch * Math.sin(Math.PI * u);
1213
+ // first take-off rises from the roof
1214
+ if (n === 0) p.y -= 0.25 * (1 - A.easeOutCubic(A.inv(0, 0.3, tau)));
1215
+ const dw = Math.max(0, ph - HOP);
1216
+ p.y += Math.sin(dw * 7 + d.k) * 0.025 * Math.min(1, dw * 4) + Math.sin(T * 3.1 + d.k * 2) * 0.012;
1217
+ d.g.position.copy(p);
1218
+ const heading = Math.atan2(b.x - a.x, b.z - a.z);
1219
+ d.g.rotation.y = heading - Math.PI / 2;
1220
+ // bank: pitch forward accelerating, back when braking
1221
+ d.body.rotation.z = -Math.sin(Math.PI * 2 * A.clamp(ph / HOP)) * 0.28 * (ph < HOP ? 1 : 0);
1222
+ // packet: carried during the hop, dropped on arrival, re-picked at the next take-off
1223
+ const dropT = Math.max(0, ph - HOP + 0.02);
1224
+ d.pkt.position.y = -0.1 - Math.min(dropT * dropT * 10, 0.3);
1225
+ d.pkt.visible = ph < HOP + 0.16;
1226
+ const pickup = A.spring(ph, 26, 0.5, 10);
1227
+ d.pkt.scale.setScalar(Math.max(n === 0 ? 1 : pickup, 1e-4));
1228
+ d.g.scale.setScalar(Math.max(A.spring(tau, 22, 0.55, 9), 1e-4));
1229
+ }
1230
+ });
1231
+
1232
+ // ================================================================= CLOUDS (foreground, parallax 1.0)
1233
+ const cloudGeo = (() => {
1234
+ const parts = [[0, 0, 0, 0.55], [0.55, -0.08, 0.1, 0.42], [-0.55, -0.1, -0.05, 0.4], [0.2, 0.25, -0.1, 0.42], [-0.25, 0.18, 0.15, 0.38], [0.9, -0.2, 0, 0.28], [-0.9, -0.22, 0.05, 0.26]];
1235
+ const gs = parts.map(([x, y, z, r]) => new THREE.IcosahedronGeometry(r, 4).translate(x, y, z));
1236
+ const g = mergeGeometries(gs);
1237
+ const pos = g.attributes.position; // flatten the bottom
1238
+ for (let i = 0; i < pos.count; i++) if (pos.getY(i) < -0.2) pos.setY(i, -0.2 + (pos.getY(i) + 0.2) * 0.35);
1239
+ g.computeVertexNormals();
1240
+ return g;
1241
+ })();
1242
+ const cloudMat = new THREE.MeshStandardMaterial({ color: C('#FFFFFF'), roughness: 0.95, envMapIntensity: 0.4, emissive: C('#F1EEFF'), emissiveIntensity: 0.25 });
1243
+ const clouds = [];
1244
+ // authored in camera-plane coordinates (sx right, sy up, world units); depth toward the camera
1245
+ const cloudDefs = opts.clouds;
1246
+ const fg = new THREE.Group(); scene.add(fg);
1247
+ const cR = new THREE.Vector3(1, 0, -1).normalize(), cU = new THREE.Vector3(-1, 2, -1).normalize();
1248
+ cloudDefs.forEach((c) => {
1249
+ const m = new THREE.Mesh(cloudGeo, cloudMat); m.castShadow = false; m.receiveShadow = false;
1250
+ m.scale.setScalar(c.s); fg.add(m); clouds.push({ m, ...c });
1251
+ });
1252
+ onUpdate((T) => {
1253
+ for (const c of clouds) {
1254
+ const sx = c.sx + T * c.v, sy = c.sy + Math.sin(T * 0.7 + c.sx) * 0.06;
1255
+ c.m.position.copy(cR).multiplyScalar(sx).addScaledVector(cU, sy).addScaledVector(DIR, 16);
1256
+ }
1257
+ });
1258
+
1259
+ // ================================================================= BACKGROUND CUBES (parallax 0.6)
1260
+ const bgGroup = new THREE.Group(); scene.add(bgGroup);
1261
+ const bgCubes = [];
1262
+ const BR = A.rng(31);
1263
+ const bgCols = [PAL.lilac, PAL.white, PAL.sky, PAL.peach, PAL.mint];
1264
+ const spots = opts.bgCubes || [[-10, -3, -6], [9, -2, 8], [-8, 1.5, 9], [11, 0.5, -2], [-12, -1, 2], [6, -4, -11], [-3, -4, 12], [13, -3, 5], [-6, 2.5, -12], [2, -5, -13]];
1265
+ spots.forEach((p, k) => {
1266
+ const s = p[3] ?? (0.35 + BR() * 0.5);
1267
+ const m = mesh(rbox(s, s, s, s * 0.18).translate(0, -s / 2, 0), std(p[4] ?? bgCols[k % bgCols.length], { rough: 0.7 }), false);
1268
+ m.receiveShadow = false;
1269
+ bgGroup.add(m); bgCubes.push({ m, p, ph: BR() * 6, t0: 0.3 + BR() * 1.4 });
1270
+ });
1271
+ onUpdate((T) => {
1272
+ for (const c of bgCubes) {
1273
+ const a = A.spring(T - c.t0, 14, 0.6, 3);
1274
+ c.m.visible = T > c.t0;
1275
+ c.m.scale.setScalar(Math.max(a, 1e-4));
1276
+ c.m.position.set(c.p[0], c.p[1] + Math.sin(T * 1.1 + c.ph) * 0.12, c.p[2]);
1277
+ c.m.rotation.y = c.ph + T * 0.15;
1278
+ }
1279
+ });
1280
+
1281
+ // ================================================================= HERO HOP (vertex shader, pure in uTime)
1282
+ const hopU = { uHopT: { value: PULSE_T }, uHopV: { value: PULSE_V } };
1283
+ const HOP_GLSL = `uniform float uTime; uniform float uHopT, uHopV;
1284
+ vec2 hopAt(vec2 o) {
1285
+ float r = max(abs(o.x), abs(o.y));
1286
+ if (r < 1.02 || r > 6.6) return vec2(0.0);
1287
+ float u = uTime - (uHopT + (r - 1.0) / uHopV);
1288
+ if (u < 0.0 || u > 0.6) return vec2(0.0);
1289
+ float y; float q = 0.0;
1290
+ if (u < 0.1) { float a = u / 0.1; y = 1.0 - (1.0 - a) * (1.0 - a); q = -0.03 * sin(3.14159 * a); }
1291
+ else { float b = min((u - 0.1) / 0.1667, 1.0); float x = b - 1.0; y = 1.0 - (1.0 + 2.7 * x * x * x + 1.7 * x * x); }
1292
+ if (u > 0.16) q = 0.04 * sin(3.14159 * clamp((u - 0.16) / 0.26, 0.0, 1.0));
1293
+ return vec2(0.25 * y, q);
1294
+ }
1295
+ `;
1296
+ const HOP_PROJECT = `vec4 mvPosition = vec4( transformed, 1.0 );
1297
+ vec4 hopO = vec4(0.0, 0.0, 0.0, 1.0);
1298
+ #ifdef USE_INSTANCING
1299
+ mvPosition = instanceMatrix * mvPosition; hopO = instanceMatrix * hopO;
1300
+ #endif
1301
+ vec4 hopW = modelMatrix * mvPosition; hopO = modelMatrix * hopO;
1302
+ vec2 hq = hopAt(hopO.xz);
1303
+ hopW.y = hopW.y * (1.0 - hq.y) + hq.x;
1304
+ mvPosition = viewMatrix * hopW;
1305
+ gl_Position = projectionMatrix * mvPosition;`;
1306
+ function hopPatch(m) {
1307
+ if (m.userData.hop) return; m.userData.hop = true;
1308
+ const prev = m.onBeforeCompile;
1309
+ const baseKey = m.customProgramCacheKey.call(m);
1310
+ m.onBeforeCompile = (sh, r) => {
1311
+ prev.call(m, sh, r);
1312
+ sh.uniforms.uTime = U.uTime; sh.uniforms.uHopT = hopU.uHopT; sh.uniforms.uHopV = hopU.uHopV;
1313
+ sh.vertexShader = HOP_GLSL + sh.vertexShader.replace('#include <project_vertex>', HOP_PROJECT);
1314
+ };
1315
+ m.customProgramCacheKey = () => baseKey + '|hop1';
1316
+ m.needsUpdate = true;
1317
+ }
1318
+ {
1319
+ const excluded = new Set();
1320
+ [core, ...drones.map((d) => d.g)].forEach((r) => r.traverse((o) => excluded.add(o)));
1321
+ const incl = new Set();
1322
+ island.traverse((o) => { if (o.isMesh && !excluded.has(o) && o.material && o.material.isMeshStandardMaterial) incl.add(o.material); });
1323
+ excluded.forEach((o) => { if (o.isMesh && incl.has(o.material)) o.material = o.material.clone(); });
1324
+ incl.forEach(hopPatch);
1325
+ }
1326
+
1327
+ // round 2: which tiles flip at the hit: bare grass / plaza / pond tiles with nothing standing on them
1328
+ {
1329
+ const occ = new Set();
1330
+ const mark = (x, z) => occ.add(Math.round(x + 5.5) + ',' + Math.round(z + 5.5));
1331
+ treeSpots.forEach((q) => mark(q.x, q.z));
1332
+ pillars.forEach((q) => mark(q.g.position.x, q.g.position.z));
1333
+ mark(dock.position.x, dock.position.z); mark(dock.position.x - 0.45, dock.position.z); mark(dock.position.x + 0.45, dock.position.z);
1334
+ ripples.forEach((q) => mark(q.position.x, q.position.z));
1335
+ for (const t of tiles) t.flip = '.opw'.includes(t.ch) && !occ.has(t.i + ',' + t.j);
1336
+ }
1337
+
1338
+ // ================================================================= update
1339
+ const pan = new THREE.Vector3();
1340
+ function setCamera(target, zoom) {
1341
+ camera.position.copy(target).addScaledVector(DIR, 120);
1342
+ camera.up.set(0, 1, 0);
1343
+ camera.lookAt(target);
1344
+ camera.zoom = zoom;
1345
+ camera.updateProjectionMatrix();
1346
+ camera.updateMatrixWorld();
1347
+ }
1348
+ // camera path: screen-space pan (sx, sy in world units along camRight / camUp), zoom
1349
+ const camKeys = opts.camKeys;
1350
+ function camAt(T) {
1351
+ const k = camKeys(T);
1352
+ return k;
1353
+ }
1354
+ function update(T, sub = { u: 0, v: 0 }, Tc, Tg) {
1355
+ U.uTime.value = T;
1356
+ TGv = Tg ?? T;
1357
+ const kc = 1 - A.smooth(A.inv(PULSE_T, PULSE_T + 0.4, T));
1358
+ sun.intensity = 2.25 * (1 + 0.2 * kc); sun.color.copy(SUN_H).lerp(SUN_K, kc);
1359
+ hemi.intensity = 0.75 * (1 - 0.3 * kc); hemi.groundColor.copy(HEMI_G).lerp(HEMI_GK, kc);
1360
+ fill.color.copy(FILL_H).lerp(FILL_K, kc); fill.intensity = 0.3 + 0.12 * kc;
1361
+ for (const f of updaters) f(T);
1362
+ // camera
1363
+ const k = camAt(Tc ?? T);
1364
+ const target = new THREE.Vector3().addScaledVector(camRight, k.x).addScaledVector(camUp, k.y);
1365
+ // project target onto the ground plane along DIR so the look-at stays on y=0 (pure translation anyway)
1366
+ setCamera(target, k.zoom);
1367
+ // parallax: fg (clouds) 1.0, city 0.8, bg 0.6 -> offsets relative to the city
1368
+ pan.copy(target);
1369
+ const flat = (v) => v.addScaledVector(DIR, -v.y / DIR.y); // same screen offset, but with y = 0
1370
+ fg.position.copy(pan).multiplyScalar(-0.25);
1371
+ bgGroup.position.copy(flat(pan.clone().multiplyScalar(0.25)));
1372
+ // bg floor offset (moves at 0.6)
1373
+ const pOff = bgGroup.position.clone().negate();
1374
+ bgU.uOff.value.set(pOff.x, pOff.z);
1375
+ bgU.uZoom.value = k.zoom;
1376
+ const invVP = new THREE.Matrix4().multiplyMatrices(camera.matrixWorld, camera.projectionMatrixInverse);
1377
+ bgU.uInvVP.value.copy(invVP);
1378
+ // island floor shadow (projected along the sun)
1379
+ const fy = bgU.uFloorY.value;
1380
+ const sy = -1.5 - fy;
1381
+ bgU.uShadowC.value.set(0.9 - pOff.x, -0.9 - pOff.z);
1382
+ bgU.uShadow.value = A.smooth(A.inv(1.6, 2.4, T));
1383
+ bgU.uVoidK.value = 1; // round 2: lavender void (about 60 % of the final saturation) until the hero wave repaints it
1384
+ bgU.uWaveT.value = PULSE_T;
1385
+ const wc = new THREE.Vector3(0, 1.5, 0).project(camera); bgU.uWaveC.value.set(wc.x, wc.y);
1386
+ // island bob in the end
1387
+ island.position.y = Math.sin(Math.max(0, T - 8.6) * 1.6) * 0.06 * A.smooth(A.inv(8.6, 9.2, T));
1388
+ // sun jitter (soft shadows)
1389
+ const j = new THREE.Vector3(sub.u, 0, sub.v).multiplyScalar(0.045);
1390
+ const L = sunDir0.clone().add(new THREE.Vector3().addScaledVector(camRight, j.x).addScaledVector(new THREE.Vector3(0.62, 0.3, 0.42).normalize(), j.z)).normalize();
1391
+ sun.target.position.set(0, 0, 0);
1392
+ sun.position.copy(L).multiplyScalar(40);
1393
+ sun.target.updateMatrixWorld();
1394
+ sun.updateMatrixWorld();
1395
+ }
1396
+
1397
+ return { hopPatch, scene, camera, update, U, buildings, byId, island, camRight, camUp, DIR, sunDir0, PAL, bgGroup, fg, onUpdate, sun, bgU,
1398
+ v5: { core, beacon, beam, coreStackTop, PULSE_T } };
1399
+ }