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,1028 @@
1
+ <!doctype html>
2
+ <html><head><meta charset="utf-8">
3
+ <title>NEON DRIVE · Midnight</title>
4
+ <link rel="stylesheet" href="/assets/fonts/fonts.css">
5
+ <style>
6
+ @font-face{font-family:'Mr Dafoe';src:url('/demos/10-synthwave/assets/fonts/MrDafoe-Regular.ttf') format('truetype');font-display:block}
7
+ html,body{margin:0;padding:0;background:#000;overflow:hidden;width:1920px;height:1080px}
8
+ #gl{position:absolute;left:0;top:0;width:1920px;height:1080px;display:block}
9
+ #probe{position:absolute;left:-9999px;top:0;font-size:40px}
10
+ </style>
11
+ <script type="importmap">{"imports":{
12
+ "three":"/node_modules/three/build/three.module.js","three/addons/":"/node_modules/three/examples/jsm/",
13
+ "simplex-noise":"/node_modules/simplex-noise/dist/esm/simplex-noise.js",
14
+ "opentype":"/node_modules/opentype.js/dist/opentype.mjs"}}</script>
15
+ </head><body>
16
+ <canvas id="gl"></canvas>
17
+ <div id="probe"><span style="font-family:'Mr Dafoe'">Midnight</span><span style="font-family:Archivo;font-style:italic;font-weight:900;font-stretch:125%">NEON</span><span style="font-family:Michroma">HI</span><span style="font-family:VT323">PLAY</span></div>
18
+ <svg id="svgm" width="0" height="0" style="position:absolute;left:-10px;top:-10px"></svg>
19
+ <script>window.addEventListener("unhandledrejection",e=>console.error("UNHANDLED",e.reason&&e.reason.stack||e.reason));window.addEventListener("error",e=>console.error("ERR",e.message));</script>
20
+ <script type="module">
21
+ import * as THREE from 'three';
22
+ import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js';
23
+ import { RenderPass } from 'three/addons/postprocessing/RenderPass.js';
24
+ import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js';
25
+ import { ShaderPass } from 'three/addons/postprocessing/ShaderPass.js';
26
+ import { createNoise2D } from 'simplex-noise';
27
+ import * as OT from 'opentype';
28
+ const opentype = OT.default ?? OT;
29
+
30
+ const W = 1920, H = 1080, FPS = 30;
31
+ window.DEMO = { width: W, height: H, fps: FPS, duration: 10, motionBlur: { samples: 1, shutter: 0.5 } };
32
+ const DPR = window.devicePixelRatio || 1;
33
+ const CUES = await (await fetch('/demos/10-synthwave/cues.json')).json();
34
+ // reel v5: style-name title + link elements (window.linkAt) + ?nolink=dot,horizon,crtline,crtdot. Default path untouched.
35
+ const V5 = new URLSearchParams(location.search).get('v5') === '1';
36
+ const NOLINK = new Set(V5 ? (new URLSearchParams(location.search).get('nolink') || '').split(',').filter(Boolean) : []);
37
+
38
+ // ---------- helpers ----------
39
+ const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));
40
+ const lerp = (a, b, k) => a + (b - a) * k;
41
+ const smooth = k => { k = clamp(k); return k * k * (3 - 2 * k); };
42
+ const eInOutSine = k => -(Math.cos(Math.PI * clamp(k)) - 1) / 2;
43
+ const eOutExpo = k => { k = clamp(k); return k === 1 ? 1 : 1 - Math.pow(2, -10 * k); };
44
+ const eOutCubic = k => 1 - Math.pow(1 - clamp(k), 3);
45
+ const eInCubic = k => Math.pow(clamp(k), 3);
46
+ const eInQuad = k => Math.pow(clamp(k), 2);
47
+ const eInQuart = k => Math.pow(clamp(k), 4);
48
+ function mulberry32(a) { return function () { a |= 0; a = a + 0x6D2B79F5 | 0; let t = Math.imul(a ^ a >>> 15, 1 | a); t = t + Math.imul(t ^ t >>> 7, 61 | t) ^ t; return ((t ^ t >>> 14) >>> 0) / 4294967296; }; }
49
+ const hash1 = n => { const r = mulberry32((n * 2654435761) >>> 0); r(); return r(); };
50
+
51
+ // ---------- time remap (tape stutter) ----------
52
+ function sceneTime(t) {
53
+ const fr = Math.round(t * FPS);
54
+ if (fr < 30) return Math.floor(fr / 2) * 2 / FPS; // 15 fps while tracking
55
+ const r0 = Math.round(CUES.tape_rewind[0] * FPS), r1 = Math.round(CUES.tape_rewind[1] * FPS), s0 = Math.round(CUES.slam * FPS);
56
+ if (fr >= r0 && fr < r1) return (r0 - 1 - (fr - r0)) / FPS; // 2 frames backwards
57
+ if (fr >= r1 && fr < s0) return Math.floor(fr / 2) * 2 / FPS; // 15 fps drag
58
+ const st = Math.round(CUES.stop * FPS);
59
+ if (fr >= st) return CUES.stop + (Math.floor(fr / 2) * 2 - st) / FPS * .5; // STOP: tape winds down
60
+ return fr >= s0 ? Math.max(t, CUES.slam) : t;
61
+ }
62
+ // ---------- camera path (precomputed from speed curve) ----------
63
+ function speed(t) {
64
+ if (t < 1.0) return 18 + 4 * t;
65
+ if (t < 2.0) return 22 + 26 * smooth(t - 1);
66
+ if (t < 2.6) return 48;
67
+ if (t < 3.8) return 48 + 13 * smooth((t - 2.6) / 1.2); // surge into the tape drag
68
+ if (t < CUES.slam) return 61 - 35 * smooth((t - 3.8) / 0.4);
69
+ return 34 + 30 * Math.exp(-(t - CUES.slam) * 2.4);
70
+ }
71
+ const ZT = [], DT = 1 / 2400; { let z = 0; for (let i = 0; i <= 10.6 / DT; i++) { ZT.push(z); const t = i * DT; z -= 0.5 * (speed(t) + speed(t + DT)) * DT; } }
72
+ const camZ = t => { const f = clamp(t, 0, 10.5) / DT, i = Math.floor(f), k = f - i; return lerp(ZT[i], ZT[i + 1], k); };
73
+
74
+ console.log('S1 cues');
75
+ // ---------- fonts ----------
76
+ await document.fonts.load("italic 900 expanded 100px Archivo");
77
+ await document.fonts.load("italic 900 100px Archivo");
78
+ await document.fonts.load("100px 'Mr Dafoe'");
79
+ await document.fonts.load("100px Michroma");
80
+ await document.fonts.load("100px VT323");
81
+ if (V5) { for (const f of ['Alimama ShuHeiTi', 'Douyin Sans', 'HarmonyOS Sans SC']) await document.fonts.load(`400px '${f}'`, '复古'); await document.fonts.load("400 30px Michroma", '80s · Y2K'); }
82
+ await document.fonts.ready;
83
+
84
+ console.log('S2 fonts');
85
+ // ---------- renderer / composer ----------
86
+ const canvas = document.getElementById('gl');
87
+ THREE.ColorManagement.enabled = false;
88
+ const renderer = new THREE.WebGLRenderer({ canvas, antialias: false, preserveDrawingBuffer: true, powerPreference: 'high-performance' });
89
+ renderer.outputColorSpace = THREE.LinearSRGBColorSpace;
90
+ renderer.setPixelRatio(DPR); renderer.setSize(W, H, false);
91
+ const rt = new THREE.WebGLRenderTarget(W * DPR, H * DPR, { type: THREE.HalfFloatType, samples: 4 });
92
+ const composer = new EffectComposer(renderer, rt);
93
+ composer.setPixelRatio(1); composer.setSize(W * DPR, H * DPR);
94
+
95
+ const SHIFT = 75; // horizon at y = 540 + SHIFT (shift-lens, verticals stay vertical)
96
+ const FOV = 50, FPX = (H / 2) / Math.tan(FOV / 2 * Math.PI / 180);
97
+ const HORIZON = H / 2 + SHIFT;
98
+ const cam = new THREE.PerspectiveCamera(FOV, W / H, 0.1, 4000);
99
+ cam.setViewOffset(W, H, 0, -SHIFT, W, H);
100
+ cam.rotation.order = 'YXZ';
101
+ const scene = new THREE.Scene();
102
+ scene.background = new THREE.Color(0x000000);
103
+
104
+ const HAZE = new THREE.Vector3(1.0, 0.24, 0.55);
105
+ const U = { uTime: { value: 0 }, uFrame: { value: 0 }, uPulseAge: { value: 99 }, uPulse: { value: 0 }, uLift: { value: 0 },
106
+ uHaze: { value: HAZE }, uShoot: { value: -1 }, uBlur: { value: 0 }, uTLl: { value: new THREE.Vector3(0, -9, 0) }, uTLr: { value: new THREE.Vector3(0, -9, 0) }, uCarI: { value: 0 } };
107
+
108
+ // ---------- sky ----------
109
+ const skyMat = new THREE.ShaderMaterial({
110
+ uniforms: U, side: THREE.BackSide, depthWrite: false, depthTest: false,
111
+ vertexShader: `varying vec3 vW; void main(){ vW = (modelMatrix*vec4(position,1.)).xyz; gl_Position = projectionMatrix*viewMatrix*vec4(vW,1.); }`,
112
+ fragmentShader: `
113
+ varying vec3 vW; uniform float uFrame, uLift, uShoot; uniform vec3 uHaze;
114
+ float h21(vec2 p){ vec3 p3 = fract(vec3(p.xyx)*.1031); p3 += dot(p3, p3.yzx+33.33); return fract((p3.x+p3.y)*p3.z); }
115
+ float segd(vec2 p, vec2 a, vec2 b){ vec2 pa=p-a, ba=b-a; float h=clamp(dot(pa,ba)/dot(ba,ba),0.,1.); return length(pa-ba*h); }
116
+ void main(){
117
+ vec3 d = normalize(vW - cameraPosition);
118
+ float e = d.y;
119
+ float pxa = e / max(fwidth(e), 1e-6); // px above the horizon
120
+ vec3 c = vec3(.46,.07,.40);
121
+ c = mix(c, vec3(.30,.05,.40), smoothstep(.015, .13, e));
122
+ c = mix(c, vec3(.10,.025,.24), smoothstep(.10, .28, e));
123
+ c = mix(c, vec3(.022,.006,.07), smoothstep(.24, .50, e));
124
+ float upx = max(pxa, 0.);
125
+ c += uHaze * (exp(-upx / 12.) * .50 + exp(-upx / 55.) * .30);
126
+ c = pxa < 0. ? uHaze * .45 : c;
127
+ c += vec3(1., .82, .96) * exp(-pxa * pxa / 2.2) * 1.25; // hot horizon line
128
+ // stars
129
+ vec2 sp = vec2(atan(d.x, -d.z), asin(clamp(e,-1.,1.))) * 70.;
130
+ vec2 id = floor(sp), f = fract(sp) - .5;
131
+ float h = h21(id);
132
+ vec2 jit = vec2(h21(id+7.1), h21(id+3.7)) - .5;
133
+ float pxs = max(fwidth(sp.x), 1e-4); // cell units per pixel
134
+ float r = length(f - jit*.7) / pxs; // pixels from star centre
135
+ float tw = .65 + .35*sin(uFrame*.37*(1.+h*3.) + h*40.);
136
+ float big = step(.994, h);
137
+ float star = step(.88, h) * exp(-r*r/(big>0.? 1.9:0.7)) * (0.35 + 0.65*fract(h*91.)) * tw;
138
+ vec2 fr = abs((f - jit*.7)/pxs);
139
+ star += big * (exp(-fr.x*.9)*exp(-fr.y*fr.y*.9) + exp(-fr.y*.9)*exp(-fr.x*fr.x*.9)) * .35 * tw;
140
+ star *= smoothstep(.04, .16, e);
141
+ c += vec3(.95,.9,1.) * star * 1.7;
142
+ // shooting star
143
+ if (uShoot >= 0.) {
144
+ vec2 q = vec2(atan(d.x, -d.z), asin(clamp(e, -.999, .999)));
145
+ vec2 A = vec2(-.62, .40), B = vec2(-.30, .30);
146
+ vec2 hd = mix(A, B, uShoot), dir = normalize(B - A);
147
+ vec2 tl = hd - dir * .10 * sin(3.14159*uShoot);
148
+ float dd = segd(q, tl, hd) / max(fwidth(q.x),1e-5);
149
+ float along = clamp(dot(q - tl, dir) / .10, 0., 1.);
150
+ c += vec3(.8,.95,1.) * exp(-dd*dd*.35) * along * sin(3.14159*uShoot) * 1.2;
151
+ }
152
+ gl_FragColor = vec4(c * (1. + uLift*.25), 1.);
153
+ }`
154
+ });
155
+ const sky = new THREE.Mesh(new THREE.SphereGeometry(3000, 64, 32), skyMat); sky.renderOrder = -10; scene.add(sky);
156
+
157
+ // ---------- sun ----------
158
+ const SUN_D = 1150, SUN_RPX = 250, SUN_R = SUN_RPX / FPX * SUN_D;
159
+ const sunMat = new THREE.ShaderMaterial({
160
+ uniforms: Object.assign({ uSunTime: { value: 0 } }, U), transparent: true, depthWrite: false,
161
+ blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneMinusSrcAlphaFactor,
162
+ vertexShader: `varying vec2 vP; void main(){ vP = position.xy / ${SUN_R.toFixed(3)}; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
163
+ fragmentShader: `
164
+ varying vec2 vP; uniform float uSunTime, uLift;
165
+ void main(){
166
+ float r = length(vP);
167
+ float aa = fwidth(r) * 1.2;
168
+ float disc = 1. - smoothstep(1. - aa, 1. + aa, r);
169
+ float y = vP.y;
170
+ vec3 c = mix(vec3(1.,.24,.56), vec3(1.,.40,.28), smoothstep(-1., -.25, y));
171
+ c = mix(c, vec3(1.,.62,.22), smoothstep(-.3, .25, y));
172
+ c = mix(c, vec3(1.,.86,.36), smoothstep(.15, .65, y));
173
+ c = mix(c, vec3(1.,.96,.62), smoothstep(.6, 1., y));
174
+ // band cut-outs: widen toward the bottom, scroll downward
175
+ float w = .38 - y;
176
+ float ph = w * 8.5 - uSunTime * .55;
177
+ float gapF = clamp(w * .42, 0., .62) * step(0., w);
178
+ float fp = fract(ph);
179
+ float afw = fwidth(ph) * 1.2;
180
+ float gap = smoothstep(gapF + afw, gapF - afw, fp) * step(.001, gapF);
181
+ float a = disc * (1. - gap);
182
+ vec3 halo = vec3(1.,.28,.52) * (exp(-max(r-1.,0.)*3.2)*.30 + exp(-max(r-1.,0.)*.9)*.10) * (1.-disc*(1.-gap)) * (1.+uLift*.6);
183
+ gl_FragColor = vec4(c * a * (.84 + uLift*.14) + halo, a);
184
+ }`
185
+ });
186
+ const sun = new THREE.Mesh(new THREE.PlaneGeometry(SUN_R * 5, SUN_R * 5), sunMat); sun.renderOrder = -5; scene.add(sun);
187
+
188
+ // ---------- mountains ----------
189
+ const noise2 = createNoise2D(mulberry32(1986));
190
+ function ridged(x, z) { let s = 0, a = .55, f = 1; for (let o = 0; o < 5; o++) { s += a * (1 - Math.abs(noise2(x * f, z * f))); a *= .5; f *= 2.03; } return s; }
191
+ const MG = new THREE.PlaneGeometry(1900, 380, 95, 19); MG.rotateX(-Math.PI / 2);
192
+ { const p = MG.attributes.position; for (let i = 0; i < p.count; i++) { const x = p.getX(i), z = p.getZ(i); const ax = Math.abs(x);
193
+ const env = smooth((ax - 150) / 380) * (0.45 + 0.55 * smooth((-z + 190) / 380));
194
+ const h = env * (18 + 150 * Math.pow(ridged(x * .0042 + 3.1, z * .006), 1.6) * .75);
195
+ p.setY(i, Math.max(0, h)); } MG.computeVertexNormals(); }
196
+ const mtMat = new THREE.ShaderMaterial({
197
+ uniforms: U,
198
+ vertexShader: `varying vec2 vUv; varying float vH; varying vec3 vW; void main(){ vUv = uv; vH = position.y; vW = (modelMatrix*vec4(position,1.)).xyz; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
199
+ fragmentShader: `
200
+ varying vec2 vUv; varying float vH; varying vec3 vW; uniform vec3 uHaze; uniform float uLift;
201
+ void main(){
202
+ vec2 g = vUv * vec2(95., 19.);
203
+ vec2 fw = max(fwidth(g), 1e-4);
204
+ vec2 d = abs(fract(g - .5) - .5) / fw;
205
+ float gd = g.x + g.y; float dd = abs(fract(gd - .5) - .5) / max(fwidth(gd),1e-4);
206
+ float m = min(min(d.x, d.y), dd);
207
+ float core = 1. - smoothstep(.25, 1.1, m);
208
+ float glow = exp(-m / 2.2);
209
+ float h = clamp(vH / 120., 0., 1.);
210
+ vec3 fill = mix(vec3(.05,.0,.12), vec3(.10,.02,.20), h);
211
+ vec3 wire = mix(vec3(1.,.18,.80), vec3(.22,.88,1.), smoothstep(.12, .7, h));
212
+ vec3 c = fill + wire * (core * .85 + glow * .22) * (1. + uLift*.4);
213
+ c = mix(c, uHaze * .55, exp(-vH / 9.) * .62);
214
+ vec3 v = normalize(vW - cameraPosition); float pxb = max(-v.y / max(fwidth(v.y), 1e-6), 0.);
215
+ c += (vec3(1., .82, .96) * exp(-pxb*pxb / 2.2) * 1.25 + uHaze * exp(-pxb / 14.) * .45) * (1. - smoothstep(0., 3., vH));
216
+ gl_FragColor = vec4(c, 1.);
217
+ }`
218
+ });
219
+ const mountains = new THREE.Mesh(MG, mtMat); scene.add(mountains);
220
+
221
+ // ---------- floor grid ----------
222
+ const floorMat = new THREE.ShaderMaterial({
223
+ uniforms: U,
224
+ vertexShader: `varying vec3 vW; void main(){ vW = (modelMatrix*vec4(position,1.)).xyz; gl_Position = projectionMatrix*viewMatrix*vec4(vW,1.); }`,
225
+ fragmentShader: `
226
+ varying vec3 vW; uniform vec3 uHaze, uTLl, uTLr; uniform float uPulseAge, uPulse, uLift, uBlur, uCarI, uTime;
227
+ float streak(vec3 v, vec3 L, float wz){ vec3 l = normalize(L - cameraPosition); float de = l.y - v.y; // de > 0: below the contact line
228
+ float da = atan(v.x, -v.z) - atan(l.x, -l.z) + .0035 * sin(wz * 1.9 + uTime * 5.) * min(max(de, 0.) * 10., 1.);
229
+ float sg = .0048 + max(de, 0.) * .04;
230
+ return exp(-da*da / (2.*sg*sg)) * exp(-max(de, 0.) * 5.) * smoothstep(-.001, .01, de); }
231
+ void main(){
232
+ vec2 p = vW.xz / vec2(2.5, 5.0) + vec2(.5, 0.);
233
+ vec2 fw = max(fwidth(p), 1e-5);
234
+ vec2 fb = fw + vec2(0., uBlur); // transverse lines smeared by this sample's travel (no strobing)
235
+ vec2 d = abs(fract(p - .5) - .5) / fb;
236
+ vec2 fade = 1. - smoothstep(.10, .42, fw);
237
+ float eC = max(1.5 * fw.y / (1.5 * fw.y + uBlur), .12), eG = max(6.8 * fw.y / (6.8 * fw.y + uBlur), .10); // energy-conserving smear
238
+ vec2 core = (1. - smoothstep(.3, 1.35, d)) * fade * vec2(1., eC);
239
+ vec2 glow = (exp(-d / 3.4) + exp(-d / 15.) * .16) * fade * vec2(1., eG);
240
+ float line = max(core.x, core.y);
241
+ float gl = max(glow.x, glow.y);
242
+ float dist = length(vW.xz - cameraPosition.xz);
243
+ vec3 lc = vec3(1., .16, .82);
244
+ vec3 base = mix(vec3(.018,.0,.05), vec3(.09,.01,.15), smoothstep(30., 700., dist));
245
+ float wave = exp(-abs(dist - uPulseAge * 240.) / 10.) * exp(-uPulseAge * 1.3) * uPulse;
246
+ float pump = 1. + .30 * exp(-uPulseAge * 7.) * min(uPulse * 2., 1.) + min(uLift * .35, .10);
247
+ vec3 c = base + lc * gl * .50 * pump + mix(lc, vec3(1.,.85,.97), .5) * line * pump;
248
+ c += lc * (line * 1.6 + gl * 1.0) * wave;
249
+ vec3 v = normalize(vW - cameraPosition);
250
+ float pxb = max(-v.y / max(fwidth(v.y), 1e-6), 0.); // px below the horizon
251
+ // car: contact shadow, red road glow behind it, tail-light streaks on the glossy floor
252
+ vec2 mid = vec2((uTLl.x + uTLr.x) * .5, uTLl.z);
253
+ vec2 cs = vW.xz - (mid + vec2(0., -2.25));
254
+ c *= 1. - .85 * exp(-(cs.x*cs.x / 1.3 + cs.y*cs.y / 6.)) * min(uCarI, 1.);
255
+ vec2 cp = vW.xz - (mid + vec2(0., 1.6));
256
+ c += vec3(1., .08, .22) * exp(-(cp.x*cp.x / 1.4 + cp.y*cp.y / 7.)) * uCarI * .22;
257
+ float brk = .3 + .7 * smoothstep(.02, .14, abs(fract(p.y - .5) - .5)); // broken by the transverse lines
258
+ float wrip = .35 + .65 * smoothstep(.25, .85, .5 + .5 * sin(vW.z * 5.5 + uTime * 3.)); // wet-road ripple dashes
259
+ c += vec3(1., .10, .16) * (streak(v, uTLl, vW.z) + streak(v, uTLr, vW.z)) * brk * wrip * .55 * uCarI / (1. + .3 * uCarI);
260
+ // banded sun: a rippling vertical reflection streak down the floor centre
261
+ float az = atan(v.x, -v.z);
262
+ float rip = .55 + .45 * smoothstep(-.3, .7, sin(pxb * .42 - uTime * 7.));
263
+ float refl = exp(-az*az / .006) * exp(-pxb / 150.) * rip;
264
+ vec2 far = (1. - fade) * min(vec2(1.), fw * 3.2); // lines that merged: keep their mean light (clamped)
265
+ c += lc * min((far.x * .5 + far.y) * .30 * pump, .22);
266
+ float fog = smoothstep(150., 950., dist);
267
+ c = mix(c, uHaze * .55, fog * .70);
268
+ c = mix(c, vec3(.102, .024, .188), (smoothstep(3., 8., pxb) - smoothstep(18., 30., pxb)) * .85); // dark band: the horizon reads as a line
269
+ c += vec3(1., .42, .35) * refl * .62 * (1. + min(uLift, .2) * .5);
270
+ c += vec3(1., .82, .96) * exp(-pxb*pxb / 2.2) * 1.25 + uHaze * (exp(-pxb / 3.2) * .40 + exp(-pxb / 60.) * .05);
271
+ gl_FragColor = vec4(c, 1.);
272
+ }`
273
+ });
274
+ const floor = new THREE.Mesh(new THREE.PlaneGeometry(5000, 5000), floorMat); floor.rotation.x = -Math.PI / 2; scene.add(floor);
275
+ // v5 ?nolink=dot: no sun (disc + halo) and no sun streak on the floor; ?nolink=horizon: no hot 2 px horizon line
276
+ if (NOLINK.size) { const sub = (m, a, b) => { if (!m.fragmentShader.includes(a)) console.error('nolink: shader patch failed', a); m.fragmentShader = m.fragmentShader.replace(a, b); };
277
+ if (NOLINK.has('dot')) { sun.visible = false; sub(floorMat, 'c += vec3(1., .42, .35) * refl', 'c += vec3(0.) * refl'); }
278
+ if (NOLINK.has('horizon')) { sub(skyMat, 'vec3(1., .82, .96) * exp(-pxa * pxa / 2.2) * 1.25', 'vec3(0.)');
279
+ for (const m of [mtMat, floorMat]) sub(m, 'vec3(1., .82, .96) * exp(-pxb*pxb / 2.2) * 1.25', 'vec3(0.)'); } }
280
+
281
+ console.log('S3 scene');
282
+ // ---------- palms ----------
283
+ function makePalmTexture(seed, rimDir) {
284
+ const PW = 900, PH = 1800, c = document.createElement('canvas'); c.width = PW; c.height = PH;
285
+ const g = c.getContext('2d');
286
+ const passes = [[rimDir * PW * .011, 'rgb(255,60,185)'], [0, '#0a0118']];
287
+ for (const [dx, col] of passes) {
288
+ const r = mulberry32(seed);
289
+ g.fillStyle = col; g.strokeStyle = col; g.lineCap = 'round'; g.lineJoin = 'round';
290
+ const lean = (r() - .5) * PW * .22 - rimDir * PW * .08;
291
+ const bx = PW * .5 + dx - lean * .3, by = PH, tx = PW * .5 + lean + dx, ty = PH * .31, cx = PW * .5 + dx - lean * .1, cy = PH * .72;
292
+ const pt = k => [(1 - k) * (1 - k) * bx + 2 * (1 - k) * k * cx + k * k * tx, (1 - k) * (1 - k) * by + 2 * (1 - k) * k * cy + k * k * ty];
293
+ const N = 90, L = [], R = [];
294
+ for (let i = 0; i <= N; i++) { const k = i / N, [x, y] = pt(k), [x2, y2] = pt(Math.min(1, k + .01)); let nx = -(y2 - y), ny = x2 - x; const l = Math.hypot(nx, ny) || 1; nx /= l; ny /= l;
295
+ const wdt = lerp(PW * .036, PW * .019, k) * (1 + .10 * Math.max(0, Math.sin(k * 140))); L.push([x + nx * wdt, y + ny * wdt]); R.push([x - nx * wdt, y - ny * wdt]); }
296
+ g.beginPath(); g.moveTo(...L[0]); L.forEach(p => g.lineTo(...p)); R.reverse().forEach(p => g.lineTo(...p)); g.closePath(); g.fill();
297
+ const NF = 12;
298
+ for (let f = 0; f < NF; f++) {
299
+ const a = -Math.PI * 1.02 + (f / (NF - 1)) * Math.PI * 1.04 + (r() - .5) * .22;
300
+ const len = PW * (.36 + r() * .16);
301
+ const droop = PW * (.18 + r() * .22) * (0.35 + 0.65 * Math.abs(Math.cos(a)));
302
+ const sp = s => [tx + Math.cos(a) * len * s, ty + Math.sin(a) * len * s * .6 + droop * s * s];
303
+ g.lineWidth = PW * .011; g.beginPath(); for (let i = 0; i <= 30; i++) { const [x, y] = sp(i / 30); i ? g.lineTo(x, y) : g.moveTo(x, y); } g.stroke();
304
+ for (let i = 2; i <= 36; i++) { const s = i / 36, [x, y] = sp(s), [x2, y2] = sp(Math.min(1, s + .02));
305
+ const ang = Math.atan2(y2 - y, x2 - x), ll = PW * .10 * Math.sin(Math.PI * Math.min(1, s * 1.08)) * (0.8 + .4 * r());
306
+ for (const side of [-1, 1]) { const la = ang + side * (1.0 + .25 * r()) + .3 * side * s;
307
+ const ex = x + Math.cos(la) * ll * .8 + Math.cos(ang) * ll * .55, ey = y + Math.sin(la) * ll * .8 + ll * .45 * s + Math.sin(ang) * ll * .55;
308
+ g.beginPath(); g.moveTo(x - Math.cos(ang) * PW * .007, y - Math.sin(ang) * PW * .007); g.lineTo(ex, ey); g.lineTo(x + Math.cos(ang) * PW * .013, y + Math.sin(ang) * PW * .013); g.closePath(); g.fill(); } }
309
+ }
310
+ g.beginPath(); g.arc(tx, ty, PW * .028, 0, 7); g.fill();
311
+ }
312
+ const tex = new THREE.CanvasTexture(c); tex.anisotropy = 8; tex.generateMipmaps = true; tex.minFilter = THREE.LinearMipmapLinearFilter;
313
+ return tex;
314
+ }
315
+ const palmTex = { '-1': [makePalmTexture(11, 1), makePalmTexture(23, 1)], '1': [makePalmTexture(37, -1), makePalmTexture(51, -1)] };
316
+ const PALM_H = 13.5, PALM_W = PALM_H * .5;
317
+ const palmGeo = new THREE.PlaneGeometry(PALM_W, PALM_H); palmGeo.translate(0, PALM_H / 2 - .2, 0);
318
+ const palms = CUES.palm_pass.map((tp, i) => {
319
+ const side = CUES.palm_side[i];
320
+ const m = new THREE.Mesh(palmGeo, new THREE.MeshBasicMaterial({ map: palmTex[String(side)][i % 2], transparent: true, depthWrite: false }));
321
+ const late = tp > CUES.slam, s = 0.9 + .25 * hash1(i + 9);
322
+ m.position.set(side * (late ? 22 + 3 * hash1(i + 3) : 8.2 + 1.6 * hash1(i + 3)), 0, camZ(tp + 0.22)); m.scale.setScalar(s); m.renderOrder = 5;
323
+ m.userData = { late, side, s };
324
+ scene.add(m); return m;
325
+ });
326
+
327
+ const rowPalms = [];
328
+ { const z0 = camZ(1.0) - 12, z1 = camZ(6.4), SP = 21;
329
+ for (let side of [-1, 1]) for (let z = z0 - (side > 0 ? SP / 2 : 0), i = 0; z > z1; z -= SP * (0.85 + .3 * hash1(i * 5 + side + 40)), i++) {
330
+ const m = new THREE.Mesh(palmGeo, new THREE.MeshBasicMaterial({ map: palmTex[String(side)][(i + (side > 0 ? 1 : 0)) % 2], transparent: true, depthWrite: false }));
331
+ m.position.set(side * (14.5 + 3.5 * hash1(i * 3 + side + 70)), 0, z); m.scale.setScalar(0.8 + .35 * hash1(i * 7 + side + 90)); m.renderOrder = 5;
332
+ scene.add(m); rowPalms.push(m); } }
333
+ console.log('S4 palms', rowPalms.length);
334
+ // ---------- roadside reflector posts (the speed read) ----------
335
+ const POST_X = 6.25, POST_SP = 8, CAP_L = .16;
336
+ const postZs = []; for (let z = camZ(0) - 3; z > camZ(10.5) - 1150; z -= POST_SP) postZs.push(z);
337
+ const postU = { uStr: { value: 1 }, uGlow: { value: 1 }, uHaze: U.uHaze };
338
+ const postVS = `attribute vec3 aCol; varying vec3 vC; varying float vD; uniform float uStr;
339
+ void main(){ vec3 p = position; p.z *= uStr; vec4 w = modelMatrix * instanceMatrix * vec4(p, 1.); vC = aCol; vD = length(w.xz - cameraPosition.xz); gl_Position = projectionMatrix * viewMatrix * w; }`;
340
+ const capMat = new THREE.ShaderMaterial({ uniforms: postU, vertexShader: postVS,
341
+ fragmentShader: `varying vec3 vC; varying float vD; uniform float uStr, uGlow; uniform vec3 uHaze;
342
+ void main(){ vec3 c = vC * 2.4 * uGlow / pow(uStr, .6); c = mix(c, uHaze * .55, smoothstep(150., 950., vD) * .7); gl_FragColor = vec4(c, 1.); }` });
343
+ const bodyMat = new THREE.ShaderMaterial({ uniforms: postU, vertexShader: postVS,
344
+ fragmentShader: `varying vec3 vC; varying float vD; uniform vec3 uHaze;
345
+ void main(){ vec3 c = vec3(.03,.008,.06) + vC * .08; c = mix(c, uHaze * .55, smoothstep(150., 950., vD) * .7); gl_FragColor = vec4(c, 1.); }` });
346
+ const capGeo = new THREE.BoxGeometry(.14, .20, CAP_L); capGeo.translate(0, .95, 0);
347
+ const bodyGeo = new THREE.BoxGeometry(.09, .86, .09); bodyGeo.translate(0, .43, 0);
348
+ const NP = postZs.length * 2;
349
+ const caps = new THREE.InstancedMesh(capGeo, capMat, NP), bodies = new THREE.InstancedMesh(bodyGeo, bodyMat, NP);
350
+ { const M = new THREE.Matrix4(), col = new Float32Array(NP * 3); let i = 0;
351
+ postZs.forEach((z, k) => { for (const sd of [-1, 1]) { M.makeTranslation(sd < 0 ? -POST_X - 2.5 : POST_X, 0, z); caps.setMatrixAt(i, M); bodies.setMatrixAt(i, M);
352
+ col.set(((k + (sd > 0 ? 1 : 0)) % 2 === 0) ? [.21, .91, 1.] : [1., .17, .84], i * 3); i++; } });
353
+ const a = new THREE.InstancedBufferAttribute(col, 3); caps.geometry.setAttribute('aCol', a); bodies.geometry.setAttribute('aCol', a); }
354
+ caps.frustumCulled = bodies.frustumCulled = false; scene.add(caps, bodies);
355
+
356
+ // ---------- the car: the film's subject ----------
357
+ function carGap(ts) { // metres ahead of the camera
358
+ if (ts < CUES.slam) return ts < 2.6 ? lerp(15, 9.5, eInOutSine(ts / 2.6)) : lerp(9.5, 7.0, smooth((ts - 2.6) / 1.2));
359
+ const u = ts - CUES.slam; return 7.0 + 8 * u + 260 * u * u; // launch into the sun, readable over ~20 frames
360
+ }
361
+ const LANE = CUES.lane_change, laneK = ts => eInOutSine((ts - LANE[0]) / (LANE[1] - LANE[0]));
362
+ const carX = ts => .35 * Math.sin(Math.min(ts, CUES.slam) * .8 + 1.2) - 2.5 * laneK(Math.min(ts, CUES.slam));
363
+ const carZat = ts => camZ(ts) - carGap(ts);
364
+ const car = new THREE.Group(); scene.add(car);
365
+ const carMat = new THREE.ShaderMaterial({
366
+ vertexShader: `varying vec3 vW; varying vec3 vL; void main(){ vL = position + (modelMatrix[3].xyz - modelMatrix[3].xyz); vW = (modelMatrix*vec4(position,1.)).xyz; gl_Position = projectionMatrix*viewMatrix*vec4(vW,1.); }`,
367
+ fragmentShader: `varying vec3 vW; varying vec3 vL;
368
+ void main(){
369
+ vec3 N = normalize(cross(dFdx(vW), dFdy(vW)));
370
+ vec3 V = normalize(cameraPosition - vW);
371
+ if (dot(N, V) < 0.) N = -N;
372
+ float nv = clamp(dot(N, V), 0., 1.), fr = pow(1. - nv, 3.), up = clamp(N.y, 0., 1.);
373
+ vec3 c = vec3(.012, .004, .03);
374
+ c += vec3(1., .34, .62) * up * (.02 + .8 * fr) * (.4 + .6 * smoothstep(0., -2.6, vL.z)); // top planes mirror the horizon: dark deck, brighter roof (gloss black)
375
+ c += vec3(1., .14, .86) * fr * (1. - up) * 1.1; // magenta rim on the flanks
376
+ float rear = smoothstep(.85, .97, abs(N.z)) * step(-.03, vL.z), fy = max(fwidth(vL.y), 1e-4);
377
+ c += vec3(1., .61, .91) * 1.3 * rear * (exp(-pow((vL.y - .805) / max(.006, fy), 2.)) + .35 * exp(-pow((vL.y - .40) / max(.004, fy), 2.)));
378
+ gl_FragColor = vec4(c, 1.);
379
+ }` });
380
+ const extr = (pts, width, bevel = .035) => { const sh = new THREE.Shape(pts.map(([u, v]) => new THREE.Vector2(u, v)));
381
+ const g = new THREE.ExtrudeGeometry(sh, { depth: width - 2 * bevel, bevelEnabled: true, bevelThickness: bevel, bevelSize: bevel, bevelSegments: 1, curveSegments: 1 });
382
+ g.rotateY(Math.PI / 2); g.translate(-(width - 2 * bevel) / 2, 0, 0); return g; };
383
+ carMat.side = THREE.DoubleSide;
384
+ // v3 body: lofted rear-view sections (rear fenders wider than the tumblehome greenhouse, roof ~55 % of the body)
385
+ function loft(st) {
386
+ const rings = st.map(([z, hw, ys, yb, hg, yr, hr]) => { const pts = [[0, ys], [hw - .10, ys], [hw, ys + .10], [hw, yb], [hw - .07, yb + .045], [hg, yb + .065], [hr, yr], [0, yr + .015]];
387
+ const r = pts.map(([x, y]) => [x, y, z]); for (let i = pts.length - 2; i > 0; i--) r.push([-pts[i][0], pts[i][1], z]); return r; });
388
+ const n = rings[0].length, pos = [], idx = []; rings.forEach(r => r.forEach(p => pos.push(...p)));
389
+ for (let k = 0; k < rings.length - 1; k++) for (let i = 0; i < n; i++) { const a = k * n + i, b = k * n + (i + 1) % n; idx.push(a, a + n, b, b, a + n, b + n); }
390
+ for (const k of [0, rings.length - 1]) { const c0 = pos.length / 3; let cy = 0; rings[k].forEach(p => cy += p[1]); pos.push(0, cy / n, rings[k][0][2]);
391
+ for (let i = 0; i < n; i++) idx.push(c0, k * n + i, k * n + (i + 1) % n); }
392
+ const g = new THREE.BufferGeometry(); g.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3)); g.setIndex(idx); return g;
393
+ }
394
+ // z hw ys yb hg yr hr
395
+ const body = new THREE.Mesh(loft([[0, .95, .30, .84, .80, .90, .78], [-.08, 1.0, .24, .88, .82, .94, .80], [-.95, 1.0, .22, .92, .78, .97, .74],
396
+ [-1.75, .99, .22, .93, .76, 1.30, .54], [-2.65, .96, .22, .92, .76, 1.31, .55], [-3.25, .94, .22, .86, .74, .90, .72], [-4.3, .86, .26, .60, .70, .64, .66]]), carMat);
397
+ car.add(body);
398
+ const tyreMat = new THREE.ShaderMaterial({ vertexShader: carMat.vertexShader, fragmentShader: `varying vec3 vW;
399
+ void main(){ vec3 N = normalize(cross(dFdx(vW), dFdy(vW))); vec3 V = normalize(cameraPosition - vW); float fr = pow(1. - abs(dot(N, V)), 3.);
400
+ gl_FragColor = vec4(vec3(.006, .004, .01) + vec3(1., .2, .7) * fr * .22, 1.); }` });
401
+ const carBits = [body];
402
+ const addBit = (geo, mat, x, y, z) => { const m = new THREE.Mesh(geo, mat); m.position.set(x, y, z); car.add(m); carBits.push(m); return m; };
403
+ { const tg = new THREE.CylinderGeometry(.33, .33, .30, 24); tg.rotateZ(Math.PI / 2);
404
+ for (const sd of [-1, 1]) for (const wz of [-.80, -3.45]) addBit(tg, tyreMat, sd * .82, .33, wz);
405
+ addBit(new THREE.BoxGeometry(1.86, .035, .34), carMat, 0, 1.165, -.20); // wing blade
406
+ for (const sd of [-1, 1]) { addBit(new THREE.BoxGeometry(.022, .15, .40), carMat, sd * .93, 1.13, -.20); // end plates
407
+ addBit(new THREE.BoxGeometry(.04, .24, .07), carMat, sd * .50, 1.03, -.24); } // thin struts (visible gap)
408
+ for (let k = 0; k < 4; k++) addBit(new THREE.BoxGeometry(1.80, .018, .05), carMat, 0, .675 + k * .042, .03); // louvres
409
+ const eg = new THREE.CylinderGeometry(.05, .05, .14, 16); eg.rotateX(Math.PI / 2);
410
+ for (const sd of [-1, 1]) addBit(eg, tyreMat, sd * .56, .33, .02); }
411
+ const exU = { uEx: { value: 1 } };
412
+ const exMat = new THREE.ShaderMaterial({ uniforms: exU, vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
413
+ fragmentShader: `varying vec2 vUv; uniform float uEx; void main(){ float r = length(vUv - .5) * 2.; gl_FragColor = vec4(vec3(1., .32, .08) * smoothstep(1., .2, r) * .7 * uEx, 1.); }` });
414
+ const exDiscs = [-1, 1].map(sd => { const m = new THREE.Mesh(new THREE.CircleGeometry(.036, 16), exMat); m.position.set(sd * .56, .33, .092); car.add(m); return m; });
415
+ { const pc = document.createElement('canvas'); pc.width = 256; pc.height = 64; const g = pc.getContext('2d');
416
+ const gr = g.createLinearGradient(0, 0, 0, 64); gr.addColorStop(0, '#d9d0ec'); gr.addColorStop(1, '#8c80a8'); g.fillStyle = gr; g.fillRect(0, 0, 256, 64);
417
+ g.strokeStyle = '#3a2f55'; g.lineWidth = 4; g.strokeRect(3, 3, 250, 58); g.fillStyle = '#1c1233'; g.font = '34px Michroma'; g.textAlign = 'center'; g.textBaseline = 'middle'; g.fillText('NDR 86', 128, 34);
418
+ const ptex = new THREE.CanvasTexture(pc); ptex.colorSpace = THREE.NoColorSpace;
419
+ const plate = new THREE.Mesh(new THREE.PlaneGeometry(.52, .13), new THREE.ShaderMaterial({ uniforms: { map: { value: ptex } },
420
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
421
+ fragmentShader: `varying vec2 vUv; uniform sampler2D map; void main(){ gl_FragColor = vec4(texture2D(map, vUv).rgb * (.16 + .20 * vUv.y), 1.); }` }));
422
+ plate.position.set(0, .50, .006); car.add(plate); carBits.push(plate); }
423
+ const tlU = { uTL: { value: 1 } };
424
+ const tailLight = new THREE.Mesh(new THREE.PlaneGeometry(1.74, .19), new THREE.ShaderMaterial({ uniforms: tlU,
425
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
426
+ fragmentShader: `varying vec2 vUv; uniform float uTL; void main(){ float e = smoothstep(0., .04, vUv.x) * smoothstep(1., .96, vUv.x);
427
+ vec3 c = vec3(1., .03, .06) * uTL * 3.0 * (.75 + .25 * e) + vec3(1., .5, .5) * max(uTL - 1., 0.) * .14; gl_FragColor = vec4(c, 1.); }` }));
428
+ tailLight.position.set(0, .74, .008); car.add(tailLight);
429
+ // silhouette mask (R = car body, G = chrome coverage): lets the VHS pass punch the car out of the slam flash
430
+ const maskRT = new THREE.WebGLRenderTarget(W * DPR, H * DPR, { samples: 4 });
431
+ const maskW = new THREE.MeshBasicMaterial({ color: 0xff0000, side: THREE.DoubleSide }), maskK = new THREE.MeshBasicMaterial({ color: 0x000000, side: THREE.DoubleSide }), maskT = new THREE.MeshBasicMaterial({ color: 0x0000ff, side: THREE.DoubleSide });
432
+
433
+ // light trails left by the launch (camera-facing ribbons, intensity by time since the car passed)
434
+ const TN = 72, TV = 2 * (TN + 1) * 2;
435
+ const tPos = new Float32Array(TV * 3), tI = new Float32Array(TV), tS = new Float32Array(TV);
436
+ const tIdx = []; for (let sd = 0; sd < 2; sd++) for (let j = 0; j < TN; j++) { const a = (sd * (TN + 1) + j) * 2; tIdx.push(a, a + 1, a + 2, a + 1, a + 3, a + 2); }
437
+ for (let k = 0; k < TV; k++) tS[k] = k % 2 ? 1 : -1;
438
+ const trailGeo = new THREE.BufferGeometry(); trailGeo.setIndex(tIdx);
439
+ trailGeo.setAttribute('position', new THREE.BufferAttribute(tPos, 3)); trailGeo.setAttribute('aI', new THREE.BufferAttribute(tI, 1)); trailGeo.setAttribute('aS', new THREE.BufferAttribute(tS, 1));
440
+ const trails = new THREE.Mesh(trailGeo, new THREE.ShaderMaterial({ transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, side: THREE.DoubleSide,
441
+ vertexShader: `attribute float aI, aS; varying float vI, vS; void main(){ vI = aI; vS = aS; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
442
+ fragmentShader: `varying float vI, vS; void main(){ float x = 1. - vS*vS; vec3 c = vec3(1., .12, .24) * vI * x*x*x * 1.1 + vec3(1., .42, .54) * vI * x * .22 + vec3(1., .8, .8) * max(vI - 1., 0.) * pow(x, 10.) * .5; gl_FragColor = vec4(c, 1.); }` }));
443
+ trails.frustumCulled = false; trails.visible = false; scene.add(trails);
444
+ function updateTrails(ts, kick) {
445
+ const S0 = CUES.slam; trails.visible = ts >= S0; if (!trails.visible) return;
446
+ const cz = camZ(ts), gap = carGap(ts), zStart = carZat(S0);
447
+ const dN = Math.max(2.5, cz - zStart), dF = Math.min(gap, 1150), cx0 = carX(S0), cy = .74;
448
+ for (let sd = 0; sd < 2; sd++) { const lx = cx0 + (sd ? .62 : -.62);
449
+ for (let j = 0; j <= TN; j++) {
450
+ const d = dN < dF ? dN * Math.pow(dF / dN, j / TN) : dF, z = cz - d;
451
+ let lo = S0, hi = ts; for (let it = 0; it < 22; it++) { const m = (lo + hi) / 2; if (carZat(m) > z) lo = m; else hi = m; }
452
+ const age = ts - hi;
453
+ let I = (2.4 * Math.exp(-Math.max(gap - d, 0) / 40) + .45 * Math.exp(-age * .4) + .30 + kick) * smooth((d - 2.5) / 9) * (dN < dF ? 1 : 0);
454
+ let nx = -(cy - cam.position.y), ny = lx - cam.position.x; const l = Math.hypot(nx, ny) || 1; nx /= l; ny /= l;
455
+ const w = .175 * (1 + .3 * Math.exp(-age * 4)), a = (sd * (TN + 1) + j) * 2;
456
+ tPos.set([lx + nx * w, cy + ny * w, z, lx - nx * w, cy - ny * w, z], a * 3); tI[a] = tI[a + 1] = I; } }
457
+ trailGeo.attributes.position.needsUpdate = true; trailGeo.attributes.aI.needsUpdate = true;
458
+ }
459
+
460
+ // ---------- overlay (type) ----------
461
+ const overlay = new THREE.Scene();
462
+ const ocam = new THREE.OrthographicCamera(-W / 2, W / 2, H / 2, -H / 2, -10, 10);
463
+ const toO = (x, y) => [x - W / 2, H / 2 - y];
464
+ const premulMat = (uniforms, frag) => new THREE.ShaderMaterial({ uniforms, transparent: true, depthTest: false, depthWrite: false,
465
+ blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneMinusSrcAlphaFactor,
466
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`, fragmentShader: frag });
467
+ const canvasTex = c => { const t = new THREE.CanvasTexture(c); t.premultiplyAlpha = true; t.generateMipmaps = false; t.minFilter = THREE.LinearFilter; return t; };
468
+
469
+ // --- chrome title (prerendered) ---
470
+ // v5: the chrome block is the style name 「复古」 (Alimama ShuHeiTi, stretched x V5T.sx and sheared V5T.sh in the mesh)
471
+ const V5T = { inkH: 312, sx: 1.14, sh: .22, cy: 300, ls: .07, font: new URLSearchParams(location.search).get('v5font') || 'Alimama ShuHeiTi', ext: .62, ol: 7 };
472
+ const TITLE = V5 ? '复古' : 'NEON DRIVE', TS = 1.5, TITLE_W = 1400, TITLE_CY = V5 ? V5T.cy : 300;
473
+ const chromeC = document.createElement('canvas'), faceC = document.createElement('canvas');
474
+ let TB = {};
475
+ {
476
+ const mctx = chromeC.getContext('2d');
477
+ const setF = V5 ? (g, fs) => { g.font = `${fs}px '${V5T.font}'`; g.letterSpacing = `${(fs * V5T.ls).toFixed(1)}px`; g.textBaseline = 'alphabetic'; }
478
+ : (g, fs) => { g.font = `italic 900 ${fs}px Archivo`; g.fontStretch = 'expanded'; g.letterSpacing = `${(fs * .015).toFixed(1)}px`; g.textBaseline = 'alphabetic'; };
479
+ setF(mctx, 200); let m = mctx.measureText(TITLE);
480
+ const fs = V5 ? 200 * V5T.inkH * TS / (m.actualBoundingBoxAscent + m.actualBoundingBoxDescent) : 200 * TITLE_W * TS / m.width; setF(mctx, fs); m = mctx.measureText(TITLE);
481
+ // v5 (CJK): the ink box runs below the baseline, so asc = full ink height, y = ink bottom and text is drawn at ty = y - dsc
482
+ const dsc = V5 ? m.actualBoundingBoxDescent : 0, inkL = V5 ? m.actualBoundingBoxLeft : 0;
483
+ const asc = m.actualBoundingBoxAscent + dsc, tw = V5 ? m.actualBoundingBoxLeft + m.actualBoundingBoxRight : m.width, pad = 90 * TS, ext = 16 * TS;
484
+ const CW = Math.ceil(tw + pad * 2), CH = Math.ceil(asc + pad * 2 + ext);
485
+ for (const c of [chromeC, faceC]) { c.width = CW; c.height = CH; }
486
+ const g = chromeC.getContext('2d'); setF(g, fs);
487
+ const x = pad + inkL, y = pad + asc, y0 = y - asc, ty = y - dsc;
488
+ g.lineJoin = 'round';
489
+ // neon rim at the back of the extrusion
490
+ const N = 14, dx = .30 * TS, dy = (V5 ? V5T.ext : 1.05) * TS; // v5: shallower extrusion keeps the CJK counters open
491
+ g.save(); g.shadowColor = 'rgba(255,0,176,0.9)'; g.shadowBlur = 16 * TS; g.strokeStyle = '#ff49d6'; g.lineWidth = 2.6 * TS;
492
+ g.strokeText(TITLE, x + N * dx, ty + N * dy); g.restore();
493
+ for (let k = N; k >= 1; k--) { const q = k / N; g.fillStyle = `rgb(${Math.round(lerp(92, 20, q))},${Math.round(lerp(16, 3, q))},${Math.round(lerp(128, 44, q))})`; g.fillText(TITLE, x + k * dx, ty + k * dy); }
494
+ g.strokeStyle = '#0a0014'; g.lineWidth = (V5 ? V5T.ol : 9) * TS; g.strokeText(TITLE, x, ty);
495
+ const rim = g.createLinearGradient(0, y0 - 6 * TS, 0, y + 6 * TS);
496
+ [[0, '#ffffff'], [.22, '#8ea4dc'], [.47, '#ffffff'], [.53, '#3a1830'], [.78, '#ffab66'], [1, '#fff4e0']].forEach(([o, c]) => rim.addColorStop(o, c));
497
+ g.strokeStyle = rim; g.lineWidth = 4.6 * TS; g.strokeText(TITLE, x, ty);
498
+ const face = g.createLinearGradient(0, y0, 0, y);
499
+ [[0, '#0b1a4d'], [.12, '#1d4fae'], [.30, '#5fb8ff'], [.44, '#d8f6ff'], [.494, '#ffffff'], [.506, '#2b0a26'], [.56, '#5a1438'], [.70, '#c2452f'], [.84, '#ff9a3c'], [.94, '#ffd98a'], [1, '#fff6dc']]
500
+ .forEach(([o, c]) => face.addColorStop(o, c));
501
+ g.fillStyle = face; g.fillText(TITLE, x, ty);
502
+ const hz = y0 + asc * .5;
503
+ const L2 = document.createElement('canvas'); L2.width = CW; L2.height = CH; const l2 = L2.getContext('2d'); setF(l2, fs);
504
+ l2.fillStyle = '#000'; l2.fillText(TITLE, x, ty); l2.globalCompositeOperation = 'source-in';
505
+ l2.drawImage((() => { const Z = document.createElement('canvas'); Z.width = CW; Z.height = CH; const z = Z.getContext('2d');
506
+ for (let k = 1; k <= 7; k++) { const yy = hz + (y - hz) * Math.pow(k / 7.4, 1.55); z.fillStyle = `rgba(255,${80 + k * 14},${210 - k * 12},${.46 - k * .035})`; z.fillRect(0, yy, CW, 1.3 * TS); }
507
+ for (let k = -9; k <= 9; k++) { z.strokeStyle = 'rgba(255,120,190,0.13)'; z.lineWidth = 1.1 * TS; z.beginPath(); z.moveTo(CW / 2 + k * 60 * TS, hz + 2 * TS); z.lineTo(CW / 2 + k * 260 * TS, y + 30 * TS); z.stroke(); }
508
+ const sg = z.createLinearGradient(0, y0, 0, y0 + asc * .2); sg.addColorStop(0, 'rgba(255,255,255,0.22)'); sg.addColorStop(1, 'rgba(255,255,255,0)'); z.fillStyle = sg; z.fillRect(0, y0, CW, asc * .2);
509
+ return Z; })(), 0, 0);
510
+ g.drawImage(L2, 0, 0);
511
+ // bevel slivers: top-left highlight, bottom-right shade
512
+ const sliver = (ox, oy, col, a) => { const S = document.createElement('canvas'); S.width = CW; S.height = CH; const s = S.getContext('2d'); setF(s, fs);
513
+ s.fillStyle = col; s.fillText(TITLE, x, ty); s.globalCompositeOperation = 'destination-out'; s.fillText(TITLE, x + ox, ty + oy); g.save(); g.globalAlpha = a; g.drawImage(S, 0, 0); g.restore(); };
514
+ sliver(2.2 * TS, 3.2 * TS, '#ffffff', .9);
515
+ sliver(-2.0 * TS, -2.6 * TS, '#1a0620', .55);
516
+ const f = faceC.getContext('2d'); setF(f, fs); f.fillStyle = '#fff'; f.fillText(TITLE, x, ty);
517
+ TB = { CW, CH, x, y, asc, tw, faceCx: x - inkL + tw / 2, faceCy: y - asc / 2 };
518
+ }
519
+ const chromeTex = canvasTex(chromeC), faceTex = canvasTex(faceC);
520
+ const chromeU = { map: { value: chromeTex }, face: { value: faceTex }, uGlint: { value: -9 }, uFlash: { value: 0 }, uAlpha: { value: 0 }, uAsp: { value: TB.CW / TB.CH } };
521
+ const chromeMesh = new THREE.Mesh(new THREE.PlaneGeometry(TB.CW / TS, TB.CH / TS), premulMat(chromeU, `
522
+ varying vec2 vUv; uniform sampler2D map, face; uniform float uGlint, uFlash, uAlpha, uAsp;
523
+ void main(){
524
+ vec4 c = texture2D(map, vUv); float fm = texture2D(face, vUv).a;
525
+ float x = vUv.x * uAsp + vUv.y * .9;
526
+ float b1 = (x - uGlint) / .09, b2 = (x - uGlint + .22) / .03;
527
+ float band = exp(-b1*b1) * .75 + exp(-b2*b2) * .45;
528
+ vec3 rgb = c.rgb + vec3(1., .96, .9) * band * fm + vec3(1.,.95,.98) * uFlash * c.a;
529
+ gl_FragColor = vec4(rgb, c.a) * uAlpha;
530
+ }`));
531
+ chromeMesh.geometry.translate(-(TB.faceCx - TB.CW / 2) / TS, (TB.faceCy - TB.CH / 2) / TS, 0);
532
+ if (V5) chromeMesh.geometry.applyMatrix4(new THREE.Matrix4().set(V5T.sx, V5T.sh, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1)); // expanded + italic, like the Archivo block
533
+ overlay.add(chromeMesh);
534
+ const chromeMaskMat = new THREE.ShaderMaterial({ uniforms: chromeU, transparent: true, depthTest: false, depthWrite: false, blending: THREE.AdditiveBlending,
535
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
536
+ fragmentShader: `varying vec2 vUv; uniform sampler2D map; uniform float uAlpha; void main(){ gl_FragColor = vec4(0., texture2D(map, vUv).a * uAlpha, 0., 1.); }` });
537
+ const reflC = document.createElement('canvas'); reflC.width = Math.ceil(TB.CW / TS); reflC.height = Math.ceil(TB.CH / TS);
538
+ { const r = reflC.getContext('2d'); r.filter = 'blur(3px)'; r.drawImage(chromeC, 0, 0, reflC.width, reflC.height); r.filter = 'none';
539
+ const id = r.getImageData(0, 0, reflC.width, reflC.height), d = id.data;
540
+ for (let i = 0; i < d.length; i += 4) { const a = d[i + 3] / 255; if (a < .01) continue; const L = (.3 * d[i] + .59 * d[i + 1] + .11 * d[i + 2]) / 255 / a;
541
+ const k = Math.min(1, Math.max(0, (L - .55) / .3)); d[i + 3] = Math.round(d[i + 3] * k * k * (3 - 2 * k)); } r.putImageData(id, 0, 0); }
542
+ const reflU = { map: { value: canvasTex(reflC) }, uAlpha: { value: 0 }, uT: { value: 0 }, uB: { value: reflC.height / 6 } };
543
+ const reflMesh = new THREE.Mesh(new THREE.PlaneGeometry(TB.CW / TS, TB.CH / TS), new THREE.ShaderMaterial({ uniforms: reflU, transparent: true, depthTest: false, depthWrite: false, side: THREE.DoubleSide,
544
+ blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneFactor,
545
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
546
+ fragmentShader: `varying vec2 vUv; uniform sampler2D map; uniform float uAlpha, uT, uB;
547
+ void main(){ vec2 uv = vUv; float bnd = floor(vUv.y * uB); uv.x += sin(bnd * 2.1 + uT * 4.) * .0028 + sin(vUv.y * 70. + uT * 5.) * .0008; vec4 c = texture2D(map, uv) * (.8 + .2 * mod(bnd, 2.)); float f = 1. - smoothstep(.2, .85, vUv.y); gl_FragColor = vec4(c.rgb * uAlpha * f, 0.); }` }));
548
+ reflMesh.geometry.translate(-(TB.faceCx - TB.CW / 2) / TS, (TB.faceCy - TB.CH / 2) / TS, 0);
549
+ overlay.add(reflMesh); reflMesh.visible = false; // round 2: dropped (never legible over the grid)
550
+ const TITLE_LEFT = W / 2 - TB.tw / TS / 2, TITLE_TOP = TITLE_CY - TB.asc / TS / 2;
551
+ // v5: layout px of a point (u across, v down, 0..1) of the sheared + stretched 「复古」 ink box
552
+ const tpt = (u, v) => { const dx = (u - .5) * TB.tw / TS, dyUp = (.5 - v) * TB.asc / TS; return [W / 2 + V5T.sx * dx + V5T.sh * dyUp, TITLE_CY - dyUp]; };
553
+
554
+ console.log('S5 chrome');
555
+ // --- neon script (per-frame) ---
556
+ const fontBuf = await (await fetch('/demos/10-synthwave/assets/fonts/MrDafoe-Regular.ttf')).arrayBuffer();
557
+ const dafoe = opentype.parse(fontBuf);
558
+ const SFS = V5 ? 208 : 226, SCRIPT = V5 ? 'Synthwave' : 'Midnight', SCX = V5 ? 1215 : 1195, SCY = V5 ? 545 : 470, SROT = -0.13;
559
+ const sPath = dafoe.getPath(SCRIPT, 0, 0, SFS);
560
+ const sbb = sPath.getBoundingBox();
561
+ const contours = []; { let cur = '';
562
+ for (const c of sPath.commands) { if (c.type === 'M') { if (cur) contours.push(cur); cur = `M${c.x} ${c.y}`; }
563
+ else if (c.type === 'L') cur += `L${c.x} ${c.y}`; else if (c.type === 'C') cur += `C${c.x1} ${c.y1} ${c.x2} ${c.y2} ${c.x} ${c.y}`;
564
+ else if (c.type === 'Q') cur += `Q${c.x1} ${c.y1} ${c.x} ${c.y}`; else if (c.type === 'Z') cur += 'Z'; }
565
+ if (cur) contours.push(cur); }
566
+ const svgm = document.getElementById('svgm');
567
+ const CT = contours.map(d => { const e = document.createElementNS('http://www.w3.org/2000/svg', 'path'); e.setAttribute('d', d); svgm.appendChild(e); return { d, p2: new Path2D(d), el: e, len: e.getTotalLength(), x: 0 }; });
568
+ CT.forEach(c => { c.x = c.el.getPointAtLength(0).x; });
569
+ const TOTAL = CT.reduce((a, c) => a + c.len, 0);
570
+ const glyphP = new Path2D(sPath.toPathData(2));
571
+ const SCW = 1500, SCH = 700, scriptC = document.createElement('canvas'); scriptC.width = SCW; scriptC.height = SCH;
572
+ const sctx = scriptC.getContext('2d');
573
+ const scriptTex = canvasTex(scriptC);
574
+ const scriptU = { map: { value: scriptTex }, uAlpha: { value: 1 }, uGain: { value: 1 } };
575
+ const scriptMesh = new THREE.Mesh(new THREE.PlaneGeometry(SCW, SCH), premulMat(scriptU, `varying vec2 vUv; uniform sampler2D map; uniform float uAlpha, uGain; void main(){ vec4 c = texture2D(map, vUv); gl_FragColor = vec4(c.rgb * uGain, c.a) * uAlpha; }`));
576
+ scriptMesh.position.set(...toO(SCX, SCY), 0); overlay.add(scriptMesh);
577
+ CT.reduce((a, c) => { c.s0 = a; return a + c.len; }, 0);
578
+ const LIT = [[7, 'rgba(255,20,170,0.55)', 'rgba(255,0,168,1)', 30], [4, '#ff4fd6', '#ff3dcf', 4], [1.6, '#fff0fb', null, 0]];
579
+ const GLASS = [[4.5, 'rgba(90,47,82,0.35)', null, 0], [1, 'rgba(255,160,225,0.30)', null, 0]];
580
+ function strokeRange(g, a, b, passes) {
581
+ for (const [lw, col, sc, sb] of passes) {
582
+ g.lineWidth = lw; g.strokeStyle = col; g.shadowColor = sc || 'transparent'; g.shadowBlur = sb;
583
+ for (const c of CT) { const s0 = Math.max(a, c.s0), e0 = Math.min(b, c.s0 + c.len); if (e0 <= s0) continue;
584
+ if (s0 <= c.s0 + .01 && e0 >= c.s0 + c.len - .01) { g.setLineDash([]); g.lineDashOffset = 0; }
585
+ else { g.setLineDash([e0 - s0, c.len + 20]); g.lineDashOffset = -(s0 - c.s0); }
586
+ g.stroke(c.p2); } }
587
+ g.setLineDash([]); g.lineDashOffset = 0; g.shadowBlur = 0;
588
+ }
589
+ function drawScript(p, lvl) {
590
+ const g = sctx; g.setTransform(1, 0, 0, 1, 0, 0); g.clearRect(0, 0, SCW, SCH);
591
+ if (p <= 0 && lvl <= 0) return;
592
+ g.translate(SCW / 2, SCH / 2); g.rotate(SROT); g.translate(-(sbb.x1 + sbb.x2) / 2, -(sbb.y1 + sbb.y2) / 2);
593
+ g.lineCap = 'round'; g.lineJoin = 'round';
594
+ const back = Math.max(smooth(p * 3), lvl > 0 ? 1 : 0);
595
+ if (back > 0) { g.globalAlpha = back * .62; g.shadowColor = 'rgba(12,0,30,1)'; g.shadowBlur = 26; g.fillStyle = 'rgba(40,4,60,1)'; g.fill(glyphP); g.fill(glyphP); g.shadowBlur = 0; g.globalAlpha = 1; }
596
+ const drawn = lvl > 0 ? TOTAL : p * TOTAL;
597
+ strokeRange(g, 0, drawn, GLASS); // unlit glass tube
598
+ if (lvl >= .2) { // struck: the whole tube fires (OFF frames = glass only)
599
+ g.globalAlpha = lvl;
600
+ g.shadowColor = 'rgba(255,0,168,0.95)'; g.shadowBlur = 34; g.fillStyle = '#ff2fb8'; g.fill(glyphP);
601
+ g.shadowColor = '#ff47d4'; g.shadowBlur = 5; g.fillStyle = '#ff8fe4'; g.fill(glyphP);
602
+ g.shadowBlur = 0; g.fillStyle = '#ffe6f8'; g.fill(glyphP);
603
+ strokeRange(g, 0, TOTAL, LIT);
604
+ g.globalAlpha = 1;
605
+ } else if (lvl <= 0 && p > 0 && p < 1) { // tracing: only the write-on head is hot
606
+ strokeRange(g, drawn - 60, drawn, LIT);
607
+ const c = CT.find(c => drawn >= c.s0 && drawn < c.s0 + c.len);
608
+ if (c) { const h = c.el.getPointAtLength(drawn - c.s0); const rg = g.createRadialGradient(h.x, h.y, 0, h.x, h.y, 34); rg.addColorStop(0, 'rgba(255,255,255,1)'); rg.addColorStop(.18, 'rgba(255,190,245,.9)'); rg.addColorStop(1, 'rgba(255,0,170,0)'); g.fillStyle = rg; g.beginPath(); g.arc(h.x, h.y, 34, 0, 7); g.fill(); }
609
+ }
610
+ }
611
+
612
+ // --- tagline ---
613
+ const TAG = V5 ? '80s · Y2K' : 'SUMMER 1986 · IN HI-FI STEREO', TGW = 1700, TGH = 160, TAG_Y = V5 ? 100 : 176, TAG_FS = V5 ? 32 : 25;
614
+ const tagC = document.createElement('canvas'); tagC.width = TGW; tagC.height = TGH; const tctx = tagC.getContext('2d');
615
+ const tagTex = canvasTex(tagC);
616
+ const tagMesh = new THREE.Mesh(new THREE.PlaneGeometry(TGW, TGH), premulMat({ map: { value: tagTex } }, `varying vec2 vUv; uniform sampler2D map; void main(){ gl_FragColor = texture2D(map, vUv); }`));
617
+ tagMesh.position.set(...toO(W / 2, TAG_Y), 0); overlay.add(tagMesh);
618
+ function drawTag(k) {
619
+ const g = tctx; g.setTransform(1, 0, 0, 1, 0, 0); g.clearRect(0, 0, TGW, TGH); if (k <= 0) return;
620
+ const e = eOutCubic(k), sp = lerp(26, 11.5, e);
621
+ g.font = `400 ${TAG_FS}px Michroma`; g.letterSpacing = `${sp.toFixed(2)}px`; g.textAlign = 'center'; g.textBaseline = 'middle';
622
+ const tw = g.measureText(TAG).width;
623
+ g.globalAlpha = clamp(k * 2.2);
624
+ for (const [sb, sc, fc] of [[26, 'rgba(0,220,255,0.75)', 'rgba(120,250,255,0.55)'], [4, 'rgba(60,240,255,1)', '#8ff9ff'], [0, 'transparent', '#e9ffff']]) {
625
+ g.shadowBlur = sb; g.shadowColor = sc; g.fillStyle = fc; g.fillText(TAG, TGW / 2 + sp / 2, TGH / 2); }
626
+ g.shadowBlur = 0;
627
+ const rl = 150 * eOutExpo((k - .25) / .75), gap = 26;
628
+ if (rl > 0) { for (const s of [-1, 1]) { const x0 = TGW / 2 + s * (tw / 2 + gap); const gr = g.createLinearGradient(x0, 0, x0 + s * rl, 0); gr.addColorStop(0, 'rgba(143,249,255,0.95)'); gr.addColorStop(1, 'rgba(143,249,255,0)');
629
+ g.fillStyle = gr; g.shadowColor = 'rgba(0,220,255,0.9)'; g.shadowBlur = 8; g.fillRect(Math.min(x0, x0 + s * rl), TGH / 2 - 1, rl, 2); } }
630
+ g.globalAlpha = 1; g.shadowBlur = 0;
631
+ }
632
+
633
+ // --- flare / glints (additive, half-res) ---
634
+ const FW = 960, FH = 540, flareC = document.createElement('canvas'); flareC.width = FW; flareC.height = FH; const fctx = flareC.getContext('2d');
635
+ const flareTex = canvasTex(flareC);
636
+ const flareMesh = new THREE.Mesh(new THREE.PlaneGeometry(W, H), new THREE.ShaderMaterial({ uniforms: { map: { value: flareTex } }, transparent: true, depthTest: false, depthWrite: false,
637
+ blending: THREE.CustomBlending, blendSrc: THREE.OneFactor, blendDst: THREE.OneFactor,
638
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
639
+ fragmentShader: `varying vec2 vUv; uniform sampler2D map; void main(){ gl_FragColor = texture2D(map, vUv); }` }));
640
+ flareMesh.renderOrder = 10; overlay.add(flareMesh);
641
+ function star4(g, x, y, r, a, rot) {
642
+ if (a <= 0) return; g.save(); g.translate(x, y); g.rotate(rot); g.globalCompositeOperation = 'lighter';
643
+ const rg = g.createRadialGradient(0, 0, 0, 0, 0, r * .45); rg.addColorStop(0, `rgba(255,255,255,${a})`); rg.addColorStop(.3, `rgba(255,200,240,${a * .5})`); rg.addColorStop(1, 'rgba(255,80,200,0)');
644
+ g.fillStyle = rg; g.beginPath(); g.arc(0, 0, r * .45, 0, 7); g.fill();
645
+ for (let i = 0; i < 4; i++) { g.rotate(Math.PI / 2); const lg = g.createLinearGradient(0, 0, r, 0); lg.addColorStop(0, `rgba(255,255,255,${a})`); lg.addColorStop(1, 'rgba(255,255,255,0)');
646
+ g.fillStyle = lg; g.beginPath(); g.moveTo(0, -r * .045); g.lineTo(r, 0); g.lineTo(0, r * .045); g.closePath(); g.fill(); }
647
+ g.restore();
648
+ }
649
+ function drawFlare(t, sx, sy, I, glints) {
650
+ const g = fctx; g.setTransform(.5, 0, 0, .5, 0, 0); g.clearRect(0, 0, W, H); g.globalCompositeOperation = 'lighter';
651
+ if (I > 0.002) {
652
+ const cx = W / 2, cy = H / 2;
653
+ // anamorphic streak
654
+ let lg = g.createLinearGradient(0, 0, W, 0); lg.addColorStop(0, 'rgba(60,160,255,0)'); lg.addColorStop(sx / W, `rgba(170,235,255,${.9 * I})`); lg.addColorStop(1, 'rgba(60,160,255,0)');
655
+ g.fillStyle = lg; for (const [hh, a] of [[26, .18], [8, .45], [2.5, 1]]) { g.globalAlpha = a; g.fillRect(0, sy - hh / 2, W, hh); } g.globalAlpha = 1;
656
+ // core
657
+ let rg = g.createRadialGradient(sx, sy, 0, sx, sy, 150 * (0.5 + I)); rg.addColorStop(0, `rgba(255,255,255,${I})`); rg.addColorStop(.12, `rgba(255,190,240,${.55 * I})`); rg.addColorStop(.5, `rgba(255,60,170,${.12 * I})`); rg.addColorStop(1, 'rgba(255,0,120,0)');
658
+ g.fillStyle = rg; g.beginPath(); g.arc(sx, sy, 150 * (0.5 + I), 0, 7); g.fill();
659
+ star4(g, sx, sy, 260 * (.45 + I), Math.min(1, I * 1.1), .0);
660
+ // ghosts along the optical axis
661
+ const gh = [[.55, 26, '255,120,60', .20], [1.35, 60, '80,200,255', .10], [1.7, 18, '255,80,200', .26], [2.1, 110, '255,150,90', .07], [2.5, 38, '120,255,220', .12]];
662
+ for (const [k, r, c, a] of gh) { const x = sx + (cx - sx) * k, y = sy + (cy - sy) * k; const g2 = g.createRadialGradient(x, y, r * .55, x, y, r);
663
+ g2.addColorStop(0, `rgba(${c},${a * I * .6})`); g2.addColorStop(.85, `rgba(${c},${a * I})`); g2.addColorStop(1, `rgba(${c},0)`); g.fillStyle = g2; g.beginPath(); g.arc(x, y, r, 0, 7); g.fill(); }
664
+ }
665
+ for (const gl of glints) star4(g, gl.x, gl.y, gl.r, gl.a, gl.rot);
666
+ g.globalCompositeOperation = 'source-over';
667
+ }
668
+
669
+ function drawSpill(I) {
670
+ if (I <= .003) return; const g = fctx; g.save(); g.setTransform(.5, 0, 0, .5, 0, 0); g.globalCompositeOperation = 'lighter';
671
+ g.translate(SCX - 30, SCY - 60); g.scale(1, 260 / 700);
672
+ let rg = g.createRadialGradient(0, 0, 0, 0, 0, 350); rg.addColorStop(0, `rgba(255,61,207,${Math.min(1, .40 * I)})`); rg.addColorStop(.45, `rgba(255,40,190,${Math.min(1, .15 * I)})`); rg.addColorStop(1, 'rgba(255,0,170,0)');
673
+ g.fillStyle = rg; g.beginPath(); g.arc(0, 0, 350, 0, 7); g.fill();
674
+ g.setTransform(.5, 0, 0, .5, 0, 0); g.translate(SCX, HORIZON + 3); g.scale(1.54, 1);
675
+ rg = g.createRadialGradient(0, 0, 0, 0, 0, 260); rg.addColorStop(0, `rgba(255,61,207,${Math.min(1, .30 * I)})`); rg.addColorStop(.4, `rgba(255,61,207,${Math.min(1, .12 * I)})`); rg.addColorStop(1, 'rgba(255,0,170,0)');
676
+ g.fillStyle = rg; g.beginPath(); g.arc(0, 0, 260, 0, Math.PI); g.fill();
677
+ g.setTransform(.5, 0, 0, .5, 0, 0); g.translate(SCX - 40, HORIZON + 2); g.scale(.12, 1);
678
+ rg = g.createRadialGradient(0, 0, 0, 0, 0, 300); rg.addColorStop(0, `rgba(255,150,230,${Math.min(1, .26 * I)})`); rg.addColorStop(1, 'rgba(255,0,170,0)');
679
+ g.fillStyle = rg; g.beginPath(); g.arc(0, 0, 300, 0, Math.PI); g.fill();
680
+ g.restore();
681
+ }
682
+ // --- OSD (composited after VHS pass) ---
683
+ const osdC = document.createElement('canvas'); osdC.width = W; osdC.height = H; const octx = osdC.getContext('2d');
684
+ const osdTex = canvasTex(osdC);
685
+ function drawOSD(t, mask, fr) {
686
+ const g = octx; g.clearRect(0, 0, W, H);
687
+ const left = lerp(240 + 70, 118, mask), top = lerp(92, 92, mask), right = lerp(1680 - 70, 1802, mask);
688
+ g.font = '400 84px VT323'; g.textBaseline = 'top'; g.letterSpacing = '2px';
689
+ const txt = (s, x, y, al = 'left', a = 1) => { g.textAlign = al; g.globalAlpha = a; g.fillStyle = 'rgba(0,0,0,0.85)'; g.fillText(s, x + 4, y + 4); g.fillStyle = '#f4f4ff'; g.fillText(s, x, y); g.globalAlpha = 1; };
690
+ if (t >= CUES.osd_play && t < 2.6) {
691
+ txt('PLAY', left, top);
692
+ const w = g.measureText('PLAY').width, tx = left + w + 26, ty = top + 18;
693
+ for (const [o, c] of [[4, 'rgba(0,0,0,0.85)'], [0, '#f4f4ff']]) { g.fillStyle = c; g.beginPath(); g.moveTo(tx + o, ty + o); g.lineTo(tx + 42 + o, ty + 24 + o); g.lineTo(tx + o, ty + 48 + o); g.closePath(); g.fill(); }
694
+ }
695
+ if (fr >= FSTOP) { g.font = '400 64px VT323'; txt('STOP', left, 56); const w = g.measureText('STOP').width, sx = left + w + 20, sy = 56 + 13;
696
+ for (const [o, c] of [[4, 'rgba(0,0,0,0.85)'], [0, '#f4f4ff']]) { g.fillStyle = c; g.fillRect(sx + o, sy + o, 34, 34); } }
697
+ if (t < 1.0) { g.font = '400 60px VT323'; txt('TRACKING', left, lerp(1080 - 170, 1080 - 150, mask), 'left', (Math.floor(t * 6) % 2) ? .95 : .55); }
698
+ g.font = '400 46px VT323';
699
+ const s = Math.floor(t), cs = `SP 0:00:${String(s).padStart(2, '0')}`;
700
+ txt(cs, right, 1080 - 118, 'right', t < 1 ? .9 : lerp(.9, .5, clamp((t - 2.4) / 1.5)));
701
+ }
702
+
703
+ console.log('S6 overlays');
704
+ // ---------- passes ----------
705
+ composer.addPass(new RenderPass(scene, cam));
706
+ const rp2 = new RenderPass(overlay, ocam); rp2.clear = false; rp2.clearDepth = true; composer.addPass(rp2);
707
+ // sanitize before bloom: kill NaN/Inf/negatives so one bad pixel can never smear the whole frame
708
+ const sanitize = new ShaderPass({ uniforms: { tDiffuse: { value: null } },
709
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
710
+ fragmentShader: `uniform sampler2D tDiffuse; varying vec2 vUv; void main(){ vec3 c = texture2D(tDiffuse, vUv).rgb;
711
+ bool bad = !(c.r == c.r) || !(c.g == c.g) || !(c.b == c.b) || c.r > 1e4 || c.g > 1e4 || c.b > 1e4;
712
+ c = bad ? vec3(0.) : min(max(c, vec3(0.)), vec3(24.)); gl_FragColor = vec4(c, 1.); }` });
713
+ composer.addPass(sanitize);
714
+ const bloom = new UnrealBloomPass(new THREE.Vector2(W * DPR, H * DPR), 0.55, 0.6, 0.78); composer.addPass(bloom);
715
+ const vhs = new ShaderPass({
716
+ uniforms: { tDiffuse: { value: null }, tOSD: { value: osdTex }, uRes: { value: new THREE.Vector2(W * DPR, H * DPR) }, uFrame: { value: 0 }, uTrack: { value: 0 }, uStatic: { value: 0 },
717
+ uGlitch: { value: 0 }, uGSeed: { value: 0 }, uMask: { value: 1 }, uFlash: { value: 0 }, uDim: { value: 1 }, uShake: { value: new THREE.Vector2() },
718
+ uSplit: { value: 0 }, uVSlip: { value: 0 }, uCrtX: { value: 1 }, uCrtY: { value: 1 }, uCrtG: { value: 0 }, uCrtA: { value: 0 }, tMask: { value: null }, uSil: { value: 0 } },
719
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
720
+ fragmentShader: `
721
+ precision highp float;
722
+ uniform sampler2D tDiffuse, tOSD, tMask; uniform float uSil; uniform vec2 uRes, uShake; uniform float uFrame, uTrack, uStatic, uGlitch, uGSeed, uMask, uFlash, uDim, uSplit, uVSlip, uCrtX, uCrtY, uCrtG, uCrtA;
723
+ varying vec2 vUv;
724
+ float h11(float p){ p = fract(p*.1031); p *= p + 33.33; p *= p + p; return fract(p); }
725
+ float h21(vec2 p){ vec3 p3 = fract(vec3(p.xyx)*.1031); p3 += dot(p3, p3.yzx+33.33); return fract((p3.x+p3.y)*p3.z); }
726
+ float vn(float x){ float i = floor(x), f = fract(x); f = f*f*(3.-2.*f); return mix(h11(i), h11(i+1.), f); }
727
+ vec3 toYIQ(vec3 c){ return vec3(dot(c,vec3(.299,.587,.114)), dot(c,vec3(.596,-.274,-.322)), dot(c,vec3(.211,-.523,.312))); }
728
+ vec3 toRGB(vec3 c){ return vec3(c.x+.956*c.y+.621*c.z, c.x-.272*c.y-.647*c.z, c.x-1.106*c.y+1.703*c.z); }
729
+ const vec2 CSS = vec2(1920., 1080.);
730
+ void main(){
731
+ vec2 P0 = vUv * CSS;
732
+ // CRT power-off: the raster squashes to a line, then the line to a dot
733
+ vec2 cc = vec2(960., 540.), sc2 = vec2(uCrtX, uCrtY);
734
+ vec2 px = (P0 - cc) / sc2 + cc;
735
+ float inside = (1. - smoothstep(-1., 1., abs(P0.x - cc.x) - 960.*sc2.x - 1.)) * (1. - smoothstep(-1., 1., abs(P0.y - cc.y) - 540.*sc2.y - 1.));
736
+ px.y += uVSlip; // vertical-hold slip
737
+ vec2 vuv = px / CSS;
738
+ float yTop = CSS.y - px.y, ln = floor(yTop), fr = uFrame;
739
+ float wob = (vn(yTop*.03 + fr*.9) - .5) * (1.1 + 12.*uTrack);
740
+ wob += (vn(yTop*.45 + fr*7.3) - .5) * (.35 + 2.5*uTrack);
741
+ float bandC = 1. - fract(fr*.023 + .18);
742
+ float band = smoothstep(.075, 0., abs(vuv.y - bandC)) * uTrack;
743
+ wob += band * (h21(vec2(ln*.5, fr)) - .5) * 70.;
744
+ // tape line tears: thin row bands shoved sideways (whole lines - never blocks)
745
+ float gh = 6. + 14.*h11(uGSeed*1.7+.3);
746
+ float gRow = floor((yTop + h11(uGSeed)*80.) / gh);
747
+ float gOn = uGlitch * step(.82, h21(vec2(gRow, uGSeed)));
748
+ wob += gOn * (h21(vec2(gRow, uGSeed+5.)) < .5 ? -1. : 1.) * (8. + 32.*h21(vec2(gRow, uGSeed+3.)));
749
+ float hs = 1. - smoothstep(5., 17., px.y);
750
+ wob += hs * (16. + 24.*h21(vec2(ln, fr)));
751
+ vec2 uv = vuv + (vec2(wob, 0.) + uShake) / CSS;
752
+ vec2 o1 = vec2(1., 0.) / CSS;
753
+ vec3 c0 = texture2D(tDiffuse, uv).rgb;
754
+ float Y = toYIQ(c0).x * .6 + .2 * (toYIQ(texture2D(tDiffuse, uv + o1).rgb).x + toYIQ(texture2D(tDiffuse, uv - o1).rgb).x);
755
+ vec2 iq = vec2(0.); float ws = 0., lag = 1. + gOn * 3.;
756
+ for (int i = 0; i < 9; i++) { float fi = float(i); float w = exp(-fi*.32); iq += toYIQ(texture2D(tDiffuse, uv - o1*(fi*1.7 + lag)).rgb).yz * w; ws += w; }
757
+ iq /= ws;
758
+ vec3 col = toRGB(vec3(Y, iq * 1.1));
759
+ float spx = mix(1.2, 2.2, max(uSplit, gOn));
760
+ float rr = texture2D(tDiffuse, uv + o1*spx).r, bb = texture2D(tDiffuse, uv - o1*spx).b;
761
+ col.r = mix(col.r, rr, .6); col.b = mix(col.b, bb, .6);
762
+ // red chroma smear trailing right (VHS red bleed on saturated pinks)
763
+ float rs = 0., rw = 0.;
764
+ for (int i = 1; i <= 6; i++) { float fi = float(i); float w = exp(-fi*.38); rs += texture2D(tDiffuse, uv - o1*(fi + spx)).r * w; rw += w; }
765
+ col.r = max(col.r, rs / rw * .82);
766
+ // static + tracking snow
767
+ float sn = h21(vec2(floor(px.x*.5) + fr*37., ln*1.3 + fr*17.));
768
+ col = mix(col, vec3(sn*.9), uStatic);
769
+ col += band * sn*sn * .55;
770
+ float sline = step(.992 - band*.25 - uTrack*.02, h21(vec2(ln, fr*3.1)));
771
+ col += sline * smoothstep(.55, .95, vn(px.x/70. + ln*1.7 + fr*3.)) * (.12 + .55*uTrack) * vec3(.9,.95,1.);
772
+ col = mix(col, vec3(h21(vec2(px.x + fr, ln))) * .55, hs * .55);
773
+ vec4 mk = texture2D(tMask, uv);
774
+ col *= uDim; col += uFlash * vec3(1., .92, .97) * (1. - .85 * mk.g) * (1. - mk.r) * (1. - mk.b); // chrome + car sit in front of the flash
775
+ if (uSil > .001) { vec2 o2 = 2. / CSS; float e = max(max(texture2D(tMask, uv + vec2(o2.x, 0.)).r, texture2D(tMask, uv - vec2(o2.x, 0.)).r),
776
+ max(texture2D(tMask, uv + vec2(0., o2.y)).r, texture2D(tMask, uv - vec2(0., o2.y)).r));
777
+ col = mix(col, vec3(.010, .0, .020), mk.r * uSil); col += vec3(1., .24, .81) * clamp(e - mk.r - mk.b, 0., 1.) * uSil * 1.5; }
778
+ col *= mod(yTop, 3.) < 1. ? .88 : 1.;
779
+ col += (h21(vec2(px.x*1.37 + fr*13.1, px.y*.91 + fr*7.7)) - .5) * .05;
780
+ vec2 q = vuv - .5; col *= 1. - dot(q*vec2(.95,1.15), q*vec2(.95,1.15)) * .62;
781
+ vec4 os = texture2D(tOSD, vuv); col = col * (1. - os.a) + os.rgb;
782
+ float mw = mix(1440., 1920., uMask), mh = mix(1040., 1080., uMask), rad = mix(38., 0., uMask);
783
+ vec2 p = abs(px - vec2(960., 540.)) - vec2(mw*.5 - rad, mh*.5 - rad);
784
+ float sd = length(max(p, 0.)) + min(max(p.x, p.y), 0.) - rad;
785
+ col *= 1. - smoothstep(-.8, .8, sd);
786
+ col *= inside * (1. + (1. - uCrtY) * 1.4) * step(.02, uCrtX);
787
+ float lx = max(abs(P0.x - 960.) - 960.*uCrtX, 0.), ly = max(abs(P0.y - 540.) - 540.*uCrtY, 0.);
788
+ col += vec3(1., .92, .98) * uCrtG * exp(-ly*ly / 1.1) * exp(-lx*lx / 5.); // the hot line / dot
789
+ col += vec3(.72, .84, 1.) * uCrtA * 10. * exp(-length(vec2(lx, ly * 1.6)) / 60.); // blue-white bloom around it only
790
+ gl_FragColor = vec4(clamp(col, 0., 1.), 1.);
791
+ }`
792
+ });
793
+ vhs.uniforms.tOSD.value = osdTex; vhs.uniforms.tMask.value = maskRT.texture; // ShaderPass clones uniforms (and textures) - point back at the live canvas texture
794
+ composer.addPass(vhs);
795
+ // ---------- oncoming near-miss (1.0-2.3): a head-on pair of cyan-white headlights, screen-space with analytic motion streaks,
796
+ // occluded by our car (mask R/B), anamorphic flare + wet-road reflection streaks; inserted before bloom so it blooms and gets the VHS pass
797
+ const ONC = CUES.oncoming;
798
+ const hlPass = new ShaderPass({ uniforms: { tDiffuse: { value: null }, tMask: { value: null }, uOn: { value: 0 },
799
+ uA: { value: [new THREE.Vector4(), new THREE.Vector4()] }, uL: { value: [new THREE.Vector4(), new THREE.Vector4()] } },
800
+ vertexShader: `varying vec2 vUv; void main(){ vUv = uv; gl_Position = projectionMatrix*modelViewMatrix*vec4(position,1.); }`,
801
+ fragmentShader: `uniform sampler2D tDiffuse, tMask; uniform float uOn; uniform vec4 uA[2]; uniform vec4 uL[2]; varying vec2 vUv;
802
+ void main(){ vec3 c = texture2D(tDiffuse, vUv).rgb;
803
+ if (uOn > 0.) { vec2 p = vec2(vUv.x * 1920., (1. - vUv.y) * 1080.); vec4 mk = texture2D(tMask, vUv); float occ = 1. - clamp(mk.r + mk.b, 0., 1.);
804
+ for (int i = 0; i < 2; i++) { vec2 a = uA[i].xy, b = uA[i].zw, ab = b - a; float h = clamp(dot(p - a, ab) / max(dot(ab, ab), 1e-4), 0., 1.);
805
+ vec2 d = p - (a + ab * h); float dl = length(d), r = uL[i].x, I = uL[i].y, yc = uL[i].z, rl = uL[i].w;
806
+ float e = (2. * r + 4.) / (length(ab) + 2. * r + 4.); // a moving light spreads its energy along the streak
807
+ float core = exp(-dl * dl / (r * r)) * 5. * e;
808
+ float halo = (exp(-dl / (r * 2. + 5.)) * .50 + exp(-dl / (r * 6. + 30.)) * .08) * sqrt(e);
809
+ float ana = exp(-d.y * d.y / (r * r * .06 + 1.)) * exp(-abs(d.x) / (150. + 22. * r)) * .75;
810
+ float dy = p.y - yc, rx = p.x - .5 * (a.x + b.x), wx = r * 1.1 + 2. + max(dy, 0.) * .03;
811
+ float rf = dy > 0. ? exp(-rx * rx / (wx * wx)) * exp(-dy / rl) * (.55 + .45 * sin(dy * .23 - rx * .02)) * smoothstep(0., 5., dy) : 0.;
812
+ c += I * (vec3(.85, 1., 1.) * core * occ + vec3(.30, .85, 1.) * halo * (.45 + .55 * occ) + vec3(.25, .75, 1.) * ana + vec3(.18, .70, 1.) * rf * .8 * occ);
813
+ } }
814
+ gl_FragColor = vec4(c, 1.); }` });
815
+ hlPass.uniforms.tMask.value = maskRT.texture; composer.insertPass(hlPass, 2);
816
+
817
+ // ---------- frame ----------
818
+ const KICKS = []; for (let t = CUES.drop; t < CUES.outro + 1e-6; t += 60 / CUES.bpm) KICKS.push(+t.toFixed(4));
819
+ const QS = new URLSearchParams(location.search);
820
+ const MBS = Math.max(1, +(QS.get('mb') || 1)), SHUT = 0.5; // pass --query mb=N with --mb N so analytic streaks match the sample count
821
+ const HIT = tc => Math.round(tc * FPS);
822
+ const vp3 = new THREE.Vector3();
823
+ const proj = (x, y, z) => { vp3.set(x, y, z).project(cam); return [(vp3.x + 1) / 2 * W, (1 - vp3.y) / 2 * H, vp3.z]; };
824
+ const STRIKE = CUES.neon_strike.map(([tc, l]) => [HIT(tc), l]);
825
+ const neonLevel = fr => { if (fr < STRIKE[0][0]) return 0; let l = 1; for (const [f, v] of STRIKE) if (fr >= f) l = v; return l; };
826
+ const CRT = { 295: [1, .30, .25, 0], 296: [1, .0019, 1, .030], 297: [.26, .0019, 1.1, .022], 298: [.0073, .013, 1.3, .036], 299: [.0055, .010, .35, .012] };
827
+ const FS = HIT(CUES.slam), FSTOP = HIT(CUES.stop), FIG = HIT(CUES.ignite), F9 = HIT(CUES.outro), FDRAG = HIT(CUES.tape_rewind[0]);
828
+ const GL1 = { x: 0, y: 0 }, GL2 = { x: 0, y: 0 };
829
+ // camera pose for film time t (shared by renderAt and the v5 linkAt, which poses a clone: pure function of t)
830
+ function poseCam(t, cam) {
831
+ const fr = Math.round(t * FPS), ts = sceneTime(t), fi = Math.floor(t * FPS + 1e-4);
832
+ const S0 = CUES.slam, ig = CUES.ignite, uS = Math.max(0, ts - S0);
833
+ const stepped = fr < 30 || (fr >= FDRAG && fr < FS) || fr >= FSTOP;
834
+ // ---- camera: build = FOV ramp + drop (speed read), punch back on the slam, push-ins on the hero and the end hold
835
+ const tilt = eInOutSine((ts - CUES.tilt_up[0]) / (CUES.tilt_up[1] - CUES.tilt_up[0]));
836
+ const [b0, b1] = CUES.build_ramp; const build = fr < FS ? eInQuad((ts - b0) / (b1 - b0)) : 0;
837
+ const post = fr >= FS ? .82 + .18 * eOutCubic(uS / .5) : 0; // punch = a cut on the downbeat, then a settle
838
+ const fovB = fr < FS ? lerp(50, 64, build) : lerp(64, 50, post);
839
+ let P = 1; if (t >= ig) P *= 1 + .05 * eOutExpo((t - ig) / .8); P *= 1 + .066 * eInOutSine((t - 7.2) / 2.4);
840
+ cam.fov = 2 * Math.atan(Math.tan(fovB / 2 * Math.PI / 180) / P) * 180 / Math.PI; cam.updateProjectionMatrix();
841
+ const camY = fr < FS ? lerp(lerp(2.4, 1.45, tilt), 1.10, build)
842
+ : lerp(1.10, 1.45, post) + .40 * smooth(uS / 3) - .15 * eInOutSine((ts - 7.2) / 2.8);
843
+ let roll = 0.030 * Math.sin(ts * 1.25 + .6) * smooth((ts - .8) / 1.2) * (1 - .7 * smooth((ts - 4) / 1.5));
844
+ CUES.palm_pass.forEach((tp, i) => { if (tp < S0) { const k = (ts - (tp - .2)) / .4; if (k > 0 && k < 1) roll -= CUES.palm_side[i] * 0.026 * eInOutSine(k < .5 ? k * 2 : 2 - k * 2); } });
845
+ const lkc = clamp((ts - LANE[0] - .2) / (LANE[1] - LANE[0] + .05)), endD = eInOutSine((ts - CUES.end_dolly[0]) / (CUES.end_dolly[1] - CUES.end_dolly[0]));
846
+ roll += .035 * Math.sin(Math.PI * lkc);
847
+ cam.position.set(0.9 * Math.sin(ts * .55) - 2.5 * eInOutSine(lkc) + 1.0 * endD, camY + .05 * Math.sin(ts * 2.1), camZ(ts));
848
+ cam.rotation.set(-0.30 * (1 - tilt), 0.012 * Math.sin(ts * .4) + .011 * endD, roll);
849
+ cam.updateMatrixWorld();
850
+ return { fr, ts, fi, S0, ig, uS, stepped, P };
851
+ }
852
+ window.renderAt = async (t) => {
853
+ const { fr, ts, fi, S0, ig, uS, stepped, P } = poseCam(t, cam);
854
+ ocam.zoom = P; ocam.position.y = -SHIFT * (1 - 1 / P); ocam.updateProjectionMatrix();
855
+ sky.position.copy(cam.position);
856
+ // ---- sun (rises 16 px more through the end hold; band scroll surges on the ignite)
857
+ const rise = eInOutSine((ts - CUES.sun_rise[0]) / (CUES.sun_rise[1] - CUES.sun_rise[0]));
858
+ const sunPx = lerp(-150, 110, rise) + 16 * eInOutSine((ts - 7.2) / 2.8);
859
+ sun.position.set(cam.position.x * .96, cam.position.y + sunPx / FPX * SUN_D, cam.position.z - SUN_D);
860
+ sunMat.uniforms.uSunTime.value = ts + (t >= ig ? .8 * eOutCubic((t - ig) / .45) : 0);
861
+ mountains.position.set(cam.position.x * .9, 0, cam.position.z - 830);
862
+ floor.position.set(cam.position.x, 0, cam.position.z);
863
+ for (const p of palms) { p.rotation.y = Math.atan2(cam.position.x - p.position.x, cam.position.z - p.position.z);
864
+ if (p.userData.late) { const { side, s } = p.userData; const [sx, , sz] = proj(p.position.x - side * PALM_W / 2 * s, PALM_H * s * .8, p.position.z);
865
+ const lx = 960 + (sx - 960) / P; // title box lives in layout px (x 250-1670)
866
+ p.material.opacity = sz > 1 ? 1 : side < 0 ? smooth((250 - lx) / 160) : smooth((lx - 1670) / 160); } }
867
+ for (const p of rowPalms) { p.visible = fr < FS; p.rotation.y = Math.atan2(cam.position.x - p.position.x, cam.position.z - p.position.z); }
868
+ // ---- pulses: kicks after the drop, build pulses before it (each fires the shock wave)
869
+ let lastK = -99, amp = 0, bAge = 99, bAmp = 0;
870
+ for (const k of KICKS) if (fr >= HIT(k)) { lastK = k; amp = (k === CUES.drop || k === CUES.outro) ? 1.0 : .38; }
871
+ if (fr < FS) for (const [k, a2] of CUES.build_pulses) if (fr >= HIT(k)) { lastK = k; amp = a2; bAge = ts - k; bAmp = a2; }
872
+ U.uPulseAge.value = Math.max(0, ts - lastK); U.uPulse.value = amp;
873
+ U.uFrame.value = fi; U.uTime.value = ts;
874
+ U.uBlur.value = stepped ? 0 : speed(ts) * SHUT / FPS / MBS / 5;
875
+ postU.uStr.value = stepped ? 1 : 1 + speed(ts) * SHUT / FPS / MBS / CAP_L;
876
+ postU.uGlow.value = 1 + 1.6 * bAmp * Math.exp(-bAge * 6) + (fr >= FS ? .8 * amp * Math.exp(-U.uPulseAge.value * 6) : 0);
877
+ const lvl = neonLevel(fr) * (fr >= FIG + 7 ? .97 + .03 * hash1(fi) : 1);
878
+ U.uLift.value = t >= ig && lvl >= .2 ? lvl * (.06 + .24 * Math.exp(-(t - ig) * 3.5)) : 0;
879
+ U.uShoot.value = (t > 7.55 && t < 8.1) ? (t - 7.55) / .55 : -1;
880
+ // ---- the car
881
+ const gap = carGap(ts), cxw = carX(ts), czw = camZ(ts) - gap;
882
+ car.visible = gap < 1100 && ts < CUES.car_vanish + 1 / 60;
883
+ const antic = fr >= FS - 2 && fr < FS, lkr = (ts - LANE[0]) / (LANE[1] - LANE[0]), swv = lkr > 0 && lkr < 1 && ts < S0 ? Math.sin(Math.PI * lkr) : 0;
884
+ car.position.set(cxw, .012 * Math.sin(ts * 13.0) - (antic ? .04 : 0), czw);
885
+ car.rotation.set(antic ? -.012 : 0, -.035 * Math.cos(Math.min(ts, S0) * .8 + 1.2) + .12 * swv, .01 * Math.sin(ts * 1.7) - .03 * swv);
886
+ const tl = antic ? 2.5 : fr < FS ? 1 + 1.4 * bAmp * Math.exp(-bAge * 7) : 1 + 2.6 * Math.exp(-uS * 2.2);
887
+ exU.uEx.value = antic ? 2.2 : fr >= FS ? 1 + 1.5 * Math.exp(-uS * 3) : 1;
888
+ tlU.uTL.value = tl;
889
+ U.uCarI.value = car.visible && fr < FS ? tl * smooth((260 - gap) / 220) : 0; // reflections die on the launch frame
890
+ U.uTLl.value.set(cxw - .62, 0, czw + .05); U.uTLr.value.set(cxw + .62, 0, czw + .05);
891
+ updateTrails(ts, fr >= FS ? .35 * amp * Math.exp(-U.uPulseAge.value * 5) : 0);
892
+
893
+ // ---- chrome slam: dim empty breath until 4.133, 2-frame run-in, flash peak ON the 4.200 frame
894
+ let sc = 1, al = 0;
895
+ if (fr >= FS - 2 && fr < FS) { const k = clamp((t - (S0 - 3 / 30)) / (3 / 30)); sc = lerp(2.2, 1.0, eInQuart(k)); al = fr === FS - 2 ? .6 : 1; }
896
+ else if (fr >= FS) { sc = 1 - .045 * Math.sin(uS * 24) * Math.exp(-uS * 7.5); al = 1; }
897
+ chromeMesh.scale.set(sc, sc, 1); chromeMesh.position.set(...toO(W / 2, TITLE_CY), 0);
898
+ chromeU.uAlpha.value = al; chromeU.uFlash.value = fr >= FS ? .16 * Math.exp(-uS * 13) : 0;
899
+ reflMesh.scale.set(sc, -sc, 1); reflMesh.position.set(...toO(W / 2, 2 * HORIZON - TITLE_CY), 0);
900
+ reflU.uAlpha.value = al * .20 * (1 + (fr >= FS ? .6 * Math.exp(-uS * 6) : 0)); reflU.uT.value = ts;
901
+ const gk = (t - CUES.glint[0]) / (CUES.glint[1] - CUES.glint[0]);
902
+ const gk2 = (t - CUES.glint_end[0]) / (CUES.glint_end[1] - CUES.glint_end[0]);
903
+ chromeU.uGlint.value = (gk > 0 && gk < 1) ? lerp(-1.2, chromeU.uAsp.value + 1.4, eInOutSine(gk))
904
+ : (gk2 > 0 && gk2 < 1) ? lerp(-1.2, chromeU.uAsp.value + 1.4, eInOutSine(gk2)) : -9;
905
+
906
+ // ---- neon script: unlit glass trace, then a real strike (frame-exact ON/OFF), spill light
907
+ const [n0, n1] = CUES.neon_trace; const pTrace = eInOutSine((t - n0) / (n1 - n0));
908
+ drawScript(t >= n0 ? pTrace : 0, lvl); scriptTex.needsUpdate = true;
909
+ const beat9 = fr >= F9 ? Math.exp(-(t - CUES.outro) * 4) : 0;
910
+ scriptU.uAlpha.value = 1;
911
+ scriptU.uGain.value = 1 + (t >= ig ? lvl * .12 * Math.exp(-(t - ig) * 2.5) : 0) + .25 * beat9 * lvl;
912
+ drawTag((t - CUES.tagline[0]) / (CUES.tagline[1] - CUES.tagline[0])); tagTex.needsUpdate = true;
913
+
914
+ // ---- flare + glints + spill
915
+ const [fx, fy] = V5 ? tpt(1.0, -.02) : [TITLE_LEFT + TB.tw / TS * .86, TITLE_TOP + 8]; // v5: slam flare on the empty top-right corner of 古
916
+ const If = fr >= FS ? (Math.exp(-uS * 2.8) * .75 + .06) : 0;
917
+ const glints = [];
918
+ const gs = (tc, x, y, dur, r) => { const k = (t - (tc - dur / 2)) / dur; if (k > 0 && k < 1) glints.push({ x, y, r: r * Math.sin(Math.PI * k), a: Math.sin(Math.PI * k), rot: k * .8 }); };
919
+ if (V5) { gs(CUES.glint_star, ...tpt(.06, .03), .5, 150); gs(CUES.glint2_star, ...tpt(.64, .40), .55, 120); gs(9.30, ...tpt(.33, .02), .6, 90); }
920
+ else {
921
+ gs(CUES.glint_star, TITLE_LEFT + TB.tw / TS * .085, TITLE_TOP + 6, .5, 150);
922
+ gs(CUES.glint2_star, TITLE_LEFT + TB.tw / TS * .62, TITLE_TOP + TB.asc / TS * .5, .55, 120);
923
+ gs(9.30, TITLE_LEFT + TB.tw / TS * .30, TITLE_TOP + 4, .6, 90); }
924
+ if (t > ig && t < ig + .5) gs(ig + .12, SCX - 470, SCY - 40, .3, 110);
925
+ if (car.visible && Math.abs(ts - CUES.car_vanish) < .25) { const [sx, sy] = proj(cxw, .74, czw); gs(CUES.car_vanish, 960 + (sx - 960) / P, HORIZON + (sy - HORIZON) / P, .5, 80); }
926
+ drawFlare(t, fx, fy, If, glints);
927
+ drawSpill(t >= ig ? (lvl >= .2 ? lvl : 0) * (.24 + .30 * Math.exp(-(t - ig) * 3)) * (1 + .25 * beat9) : 0);
928
+ flareTex.needsUpdate = true;
929
+
930
+ // ---- post
931
+ const mask = t < CUES.mask_open[0] ? 0 : eOutExpo((t - CUES.mask_open[0]) / (CUES.mask_open[1] - CUES.mask_open[0]));
932
+ drawOSD(t, mask, fr); osdTex.needsUpdate = true;
933
+ const V = vhs.uniforms;
934
+ V.uFrame.value = fi; V.uMask.value = mask;
935
+ V.uTrack.value = t < 1 ? (.75 + .25 * Math.sin(t * 9)) : fr >= FSTOP ? .3 + .5 * smooth((t - CUES.stop) / .17)
936
+ : Math.max(.9 * Math.exp(-(t - 1) * 12), (fr >= FDRAG && fr < FS) ? .55 : 0);
937
+ V.uStatic.value = t < 1 ? clamp(.42 - .30 * t + .12 * (hash1(fi + 500) - .5)) : fr >= FSTOP + 2 ? .06 : 0;
938
+ let glv = 0, gseed = 0; for (const gt of CUES.glitches) { const g0 = HIT(gt); if (fr >= g0 && fr < g0 + 2) { glv = 1; gseed = gt * 13.7 + fr; } }
939
+ if (fr >= FDRAG && fr < FS && fr % 3 === 0) { glv = .8; gseed = fr * 3.1; }
940
+ if (fr >= FSTOP && fr < FSTOP + 2) { glv = 1; gseed = fr * 5.3; }
941
+ V.uGlitch.value = glv; V.uGSeed.value = gseed;
942
+ V.uSplit.value = t < 1.2 ? 1 : t < 1.5 ? 1 - smooth((t - 1.2) / .3) : fr >= FSTOP ? 1 : 0;
943
+ V.uVSlip.value = fr === HIT(CUES.vslip) ? 12 : fr === FSTOP ? -12 : 0;
944
+ const crt = (V5 && ((NOLINK.has('crtline') && (fr === 296 || fr === 297)) || (NOLINK.has('crtdot') && fr >= 298))) ? [0, 1, 0, 0] : CRT[fr] || [1, 1, 0, 0]; V.uCrtX.value = crt[0]; V.uCrtY.value = crt[1]; V.uCrtG.value = crt[2]; V.uCrtA.value = crt[3];
945
+ V.uFlash.value = fr >= FS ? .34 * Math.exp(-uS * 20) : 0;
946
+ V.uDim.value = (fr >= FDRAG + 1 && fr < FS) ? lerp(1, .5, smooth((t - 3.9) / .2)) : 1;
947
+ const sh = fr >= FS ? 11 * Math.exp(-uS * 8.5) : 0;
948
+ V.uShake.value.set(sh * (hash1(fi * 7 + 1) - .5) * 2, sh * (hash1(fi * 7 + 2) - .5) * 2);
949
+ bloom.strength = .55 + (fr >= FS ? .30 * Math.exp(-uS * 6) : 0) + (t >= ig && lvl >= .2 ? lvl * .14 * Math.exp(-(t - ig) * 2.5) : 0) + .15 * beat9;
950
+
951
+ // ---- oncoming near-miss: position from cues (passes the camera plane at ONC.pass), streak = relative motion over one sub-sample
952
+ let hlOn = 0;
953
+ { const hh = SHUT / FPS / MBS / 2 * +(QS.get('hhk') || 3), HU = hlPass.uniforms, ahead = tt => (camZ(tt) - camZ(ONC.pass)) + ONC.v * (ONC.pass - tt);
954
+ const zr = tt => cam.position.z - ahead(tt), s = ahead(ts);
955
+ for (let i = 0; i < 2; i++) { const x = ONC.x + (i ? ONC.half : -ONC.half);
956
+ if (fr >= FS || stepped && fr >= 30 || s < .7 || s > 600 || t < ONC.start) { HU.uL.value[i].set(1, 0, 0, 1); continue; }
957
+ const [ax, ay] = proj(x, ONC.y, zr(ts - hh)), [bx, by] = proj(x, ONC.y, zr(ts + hh)), [, yc] = proj(x, 0, zr(ts));
958
+ const r = clamp(150 / s, 1.7, 18), I = smooth((s - .7) / 2.6) * clamp(.30 + 7 / s, .30, 1.1) * smooth((t - ONC.start) / .2);
959
+ HU.uA.value[i].set(ax, ay, bx, by); HU.uL.value[i].set(r, I, yc, Math.min(320, 14 + 4200 / s)); hlOn = 1; }
960
+ HU.uOn.value = hlOn; }
961
+ V.uSil.value = fr >= FS && car.visible ? (fr < FS + 3 ? 1 : Math.exp(-(uS - .1) * 9)) : 0;
962
+ // mask render: car body (R) + chrome coverage (G)
963
+ { const ac = renderer.autoClear; renderer.autoClear = false; renderer.setRenderTarget(maskRT); renderer.setClearColor(0x000000, 1); renderer.clear();
964
+ if (car.visible && (V.uSil.value > .001 || hlOn)) { const saved = []; car.traverse(o => { if (o.isMesh) { saved.push([o, o.material]); o.material = (o === tailLight || exDiscs.includes(o)) ? maskT : carBits.includes(o) ? maskW : maskK; } });
965
+ renderer.render(car, cam); for (const [o, m] of saved) o.material = m; }
966
+ if (V.uFlash.value > .001) { const m0 = chromeMesh.material; chromeMesh.material = chromeMaskMat; renderer.render(chromeMesh, ocam); chromeMesh.material = m0; }
967
+ renderer.setRenderTarget(null); renderer.autoClear = ac; }
968
+ if (QS.get('nobloom')) bloom.enabled = false;
969
+ if (QS.get('fixsky')) U.uFrame.value = 22;
970
+ if (QS.get('fixvhs')) V.uFrame.value = 22;
971
+ composer.render(0);
972
+ renderer.getContext().finish();
973
+ };
974
+ // ---------- reel v5: link elements in 1920x1080 output px, pure function of t (poses a camera clone) ----------
975
+ if (V5) {
976
+ const lcam = cam.clone(), v3a = new THREE.Vector3();
977
+ const pj = (x, y, z) => { v3a.set(x, y, z).project(lcam); return [(v3a.x + 1) / 2 * W, (1 - v3a.y) / 2 * H]; };
978
+ const hx = c => '#' + c.map(v => Math.round(clamp(v) * 255).toString(16).padStart(2, '0')).join('');
979
+ window.linkAt = (t) => {
980
+ const { fr, ts, fi, uS, P } = poseCam(t, lcam);
981
+ // VHS pass: shake (after the slam) + the one-frame v-hold slips displace the picture; CRT-off from frame 295
982
+ const sh = fr >= FS ? 11 * Math.exp(-uS * 8.5) : 0;
983
+ const ox = -sh * (hash1(fi * 7 + 1) - .5) * 2, oy = sh * (hash1(fi * 7 + 2) - .5) * 2 + (fr === HIT(CUES.vslip) ? 12 : fr === FSTOP ? -12 : 0);
984
+ const live = fr < 295 ? 1 : 0, r1 = v => +v.toFixed(1);
985
+ const mask = t < CUES.mask_open[0] ? 0 : eOutExpo((t - CUES.mask_open[0]) / (CUES.mask_open[1] - CUES.mask_open[0]));
986
+ const xl = 960 - lerp(1440, 1920, mask) / 2, xr = 960 + lerp(1440, 1920, mask) / 2;
987
+ const els = [];
988
+ // horizon = vanishing line of the floor (hot 2 px line in sky/floor/mountain shaders)
989
+ const cp = lcam.position, [ax, ay] = pj(cp.x - 1e6 * Math.sin(1.2), cp.y, cp.z - 1e6 * Math.cos(1.2)), [bx, by] = pj(cp.x + 1e6 * Math.sin(1.2), cp.y, cp.z - 1e6 * Math.cos(1.2));
990
+ const hy = x => ay + (x - ax) * (by - ay) / (bx - ax);
991
+ const hz = { id: 'horizon', type: 'line', x0: r1(xl + ox), y0: r1(hy(xl) + oy), x1: r1(xr + ox), y1: r1(hy(xr) + oy), w: 3, a: live && hy(960) > 0 && hy(960) < H ? 1 : 0,
992
+ stroke: '#ffd1f5', note: 'hot 2 px horizon line (white-pink core, magenta haze above/below); the sun is hidden below it' };
993
+ // the sun (the dot): a plane at SUN_D, same formulas as renderAt
994
+ const rise = eInOutSine((ts - CUES.sun_rise[0]) / (CUES.sun_rise[1] - CUES.sun_rise[0]));
995
+ const sunPx = lerp(-150, 110, rise) + 16 * eInOutSine((ts - 7.2) / 2.8);
996
+ const sx = cp.x * .96, sy = cp.y + sunPx / FPX * SUN_D, sz = cp.z - SUN_D;
997
+ const [lx, ly] = pj(sx - SUN_R, sy, sz), [rx, ry] = pj(sx + SUN_R, sy, sz), [tx, tyy] = pj(sx, sy + SUN_R, sz), [bx2, by2] = pj(sx, sy - SUN_R, sz);
998
+ const cx = (lx + rx + tx + bx2) / 4, cy = (ly + ry + tyy + by2) / 4, rX = Math.hypot(rx - lx, ry - ly) / 2, rY = Math.hypot(tx - bx2, tyy - by2) / 2;
999
+ const vis = clamp((hy(cx) - (cy - rY)) / (2 * rY));
1000
+ const period = 2 * rY / 2 / 8.5; // band period (disc units 1/8.5) in px
1001
+ els.push({ id: 'dot', type: 'circle', x: r1(cx + ox), y: r1(cy + oy), r: r1((rX + rY) / 2), ry: r1(rY), a: live && vis > .02 ? 1 : 0,
1002
+ look: 'sun-stripes', fill: '#ef933d', stroke: '#ff4785', sw: 0, grad: ['#e6c465', '#ff6283'], // rendered pixels (top of disc / lowest visible band)
1003
+ vis: +vis.toFixed(3), clip: { below: 'horizon', y: r1(hy(cx) + oy) },
1004
+ stripes: { from: .38, period: r1(period), gap0: 0, gap1: .58, scroll: r1(.55 / 8.5 * rY) + ' px/s down', gapColor: '#d838b0' },
1005
+ note: 'synthwave sun: shader gradient #fff59e (top) > #ffdb5c > #ff9e38 > #ff6647 > #ff3d8f (disc bottom) x0.84 + bloom (renders #e6c465 top, #ef933d mid, #fe7868-#ff6283 lowest bands); horizontal cut-out bands below +0.38 r, period r/8.5, gaps widen downward 0 > 58 % and scroll down (sky/haze magenta shows through); soft pink halo #ff4785; hidden below the horizon (clip.y)' });
1006
+ els.push(hz);
1007
+ // the chrome 「复古」 block (overlay layout px -> zoom P about (960, 615) -> VHS offsets)
1008
+ let sc = 1, al = 0;
1009
+ if (fr >= FS - 2 && fr < FS) { const k = clamp((t - (CUES.slam - 3 / 30)) / (3 / 30)); sc = lerp(2.2, 1.0, eInQuart(k)); al = fr === FS - 2 ? .6 : 1; }
1010
+ else if (fr >= FS) { sc = 1 - .045 * Math.sin(uS * 24) * Math.exp(-uS * 7.5); al = 1; }
1011
+ const tW = (TB.tw / TS * V5T.sx + V5T.sh * TB.asc / TS) * sc * P, tH = TB.asc / TS * sc * P;
1012
+ els.push({ id: 'title', type: 'rect', x: r1(960 + ox), y: r1(615 + (TITLE_CY - 615) * P + oy), w: r1(tW), h: r1(tH), rot: 0, shear: V5T.sh, a: live * al,
1013
+ note: 'chrome 「复古」 ink box (italic parallelogram, shear 0.22 = top leans right); slams in 4.133-4.2' });
1014
+ // CRT power-off: full-width line (9.867), 500 px line (9.900), dot (9.933, 9.967)
1015
+ const crt = CRT[fr];
1016
+ if (crt && (fr === 296 || fr === 297)) els.push({ id: 'crtline', type: 'line', x0: r1(960 - 960 * crt[0]), y0: 540, x1: r1(960 + 960 * crt[0]), y1: 540, w: 3, a: 1, stroke: '#ffebfa', note: 'CRT power-off: raster squashed to a hot line' });
1017
+ if (crt && fr >= 298) els.push({ id: 'crtdot', type: 'circle', x: 960, y: 540, r: r1(960 * crt[0]), a: fr === 298 ? 1 : .35, look: 'glow', fill: '#ffebfa', stroke: '#b8d6ff', sw: 0,
1018
+ note: 'CRT power-off dot: hot white phosphor dot with a ~60 px blue-white bloom, on black' });
1019
+ return els;
1020
+ };
1021
+ }
1022
+ console.log('S7 passes');
1023
+ await window.renderAt(0);
1024
+ await window.renderAt(4.3);
1025
+ console.log('S8 ready');
1026
+ window.__ready = true;
1027
+ </script>
1028
+ </body></html>