dsh-drawai 0.3.4 → 0.4.1

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 (538) hide show
  1. package/LICENSE +4154 -0
  2. package/README.md +49 -11
  3. package/lib/audio-media.js +150 -0
  4. package/lib/audio-service.js +66 -0
  5. package/lib/canvas-format.js +134 -0
  6. package/lib/client.js +5090 -184
  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-runner.js +6 -4
  12. package/lib/drawing-tasks.js +26 -0
  13. package/lib/ffmpeg-manager.js +1056 -0
  14. package/lib/imagegen.mjs +596 -0
  15. package/lib/index.js +1762 -129
  16. package/lib/media.js +26 -13
  17. package/lib/mgstyle-core.js +75 -0
  18. package/lib/motion/apply.js +181 -0
  19. package/lib/motion/asset-pack-lock.js +10 -0
  20. package/lib/motion/asset-pack.js +335 -0
  21. package/lib/motion/assets.js +291 -0
  22. package/lib/motion/audio.js +103 -0
  23. package/lib/motion/audiolevel.js +63 -0
  24. package/lib/motion/audiomix.js +144 -0
  25. package/lib/motion/beats.js +338 -0
  26. package/lib/motion/builtin-assets.js +62 -0
  27. package/lib/motion/canvas-spec.js +442 -0
  28. package/lib/motion/capture.js +95 -0
  29. package/lib/motion/cards.js +80 -0
  30. package/lib/motion/collect.js +449 -0
  31. package/lib/motion/delivery.js +140 -0
  32. package/lib/motion/edge-speech.py +90 -0
  33. package/lib/motion/entry.jsx +73 -0
  34. package/lib/motion/export.js +200 -0
  35. package/lib/motion/ffmpeg.js +43 -0
  36. package/lib/motion/fonts.js +71 -0
  37. package/lib/motion/jobs.js +117 -0
  38. package/lib/motion/json.js +4 -0
  39. package/lib/motion/media-catalog.js +59 -0
  40. package/lib/motion/mgstyles/BRIEF-TEMPLATE.md +63 -0
  41. package/lib/motion/mgstyles/briefs/01-flat-vector.md +104 -0
  42. package/lib/motion/mgstyles/briefs/02-line-art.md +97 -0
  43. package/lib/motion/mgstyles/briefs/03-isometric.md +108 -0
  44. package/lib/motion/mgstyles/briefs/05-cel-boil.md +145 -0
  45. package/lib/motion/mgstyles/briefs/06-collage.md +149 -0
  46. package/lib/motion/mgstyles/briefs/07-liquid.md +106 -0
  47. package/lib/motion/mgstyles/briefs/08-morph.md +111 -0
  48. package/lib/motion/mgstyles/briefs/09-bauhaus.md +150 -0
  49. package/lib/motion/mgstyles/briefs/10-synthwave.md +151 -0
  50. package/lib/motion/mgstyles/briefs/12-aurora-glass.md +147 -0
  51. package/lib/motion/mgstyles/briefs/18-hanazi.md +140 -0
  52. package/lib/motion/mgstyles/briefs/19-paperclip.md +156 -0
  53. package/lib/motion/mgstyles/briefs/20-pixel.md +118 -0
  54. package/lib/motion/mgstyles/briefs/22-hud.md +118 -0
  55. package/lib/motion/mgstyles/catalog.d/01-flat-vector.json +34 -0
  56. package/lib/motion/mgstyles/catalog.d/02-line-art.json +34 -0
  57. package/lib/motion/mgstyles/catalog.d/03-isometric.json +35 -0
  58. package/lib/motion/mgstyles/catalog.d/05-cel-boil.json +28 -0
  59. package/lib/motion/mgstyles/catalog.d/06-collage.json +28 -0
  60. package/lib/motion/mgstyles/catalog.d/07-liquid.json +28 -0
  61. package/lib/motion/mgstyles/catalog.d/08-morph.json +28 -0
  62. package/lib/motion/mgstyles/catalog.d/09-bauhaus.json +28 -0
  63. package/lib/motion/mgstyles/catalog.d/10-synthwave.json +28 -0
  64. package/lib/motion/mgstyles/catalog.d/12-aurora-glass.json +28 -0
  65. package/lib/motion/mgstyles/catalog.d/18-hanazi.json +28 -0
  66. package/lib/motion/mgstyles/catalog.d/19-paperclip.json +28 -0
  67. package/lib/motion/mgstyles/catalog.d/20-pixel.json +28 -0
  68. package/lib/motion/mgstyles/catalog.d/22-hud.json +28 -0
  69. package/lib/motion/mgstyles/catalog.json +1356 -0
  70. package/lib/motion/mgstyles/upstream/README.md +147 -0
  71. package/lib/motion/mgstyles/upstream/README.zh-CN.md +147 -0
  72. package/lib/motion/mgstyles/upstream/demos/01-flat-vector/cues.json +1 -0
  73. package/lib/motion/mgstyles/upstream/demos/01-flat-vector/index.html +712 -0
  74. package/lib/motion/mgstyles/upstream/demos/02-line-art/cues.json +331 -0
  75. package/lib/motion/mgstyles/upstream/demos/02-line-art/film.js +676 -0
  76. package/lib/motion/mgstyles/upstream/demos/02-line-art/index.html +18 -0
  77. package/lib/motion/mgstyles/upstream/demos/02-line-art/tools/dump_timing.mjs +26 -0
  78. package/lib/motion/mgstyles/upstream/demos/03-isometric/cues.json +109 -0
  79. package/lib/motion/mgstyles/upstream/demos/03-isometric/index.html +25 -0
  80. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/anim.js +82 -0
  81. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/main.js +219 -0
  82. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/night5.js +180 -0
  83. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/render.js +202 -0
  84. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title.js +167 -0
  85. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title5.js +294 -0
  86. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/ui.js +141 -0
  87. package/lib/motion/mgstyles/upstream/demos/03-isometric/js/world.js +1399 -0
  88. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/cues.json +157 -0
  89. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/index.html +34 -0
  90. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/engine.js +326 -0
  91. package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/scene.js +2036 -0
  92. package/lib/motion/mgstyles/upstream/demos/06-collage/assets/gen/layout.json +157 -0
  93. package/lib/motion/mgstyles/upstream/demos/06-collage/cues.json +80 -0
  94. package/lib/motion/mgstyles/upstream/demos/06-collage/index.html +889 -0
  95. package/lib/motion/mgstyles/upstream/demos/07-liquid/cues.json +100 -0
  96. package/lib/motion/mgstyles/upstream/demos/07-liquid/index.html +108 -0
  97. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/engine.js +153 -0
  98. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/links.js +64 -0
  99. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/lookdev.js +46 -0
  100. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/scene.js +963 -0
  101. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/shaders.js +647 -0
  102. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/textsdf.js +114 -0
  103. package/lib/motion/mgstyles/upstream/demos/07-liquid/js/type.js +50 -0
  104. package/lib/motion/mgstyles/upstream/demos/08-morph/cues.json +131 -0
  105. package/lib/motion/mgstyles/upstream/demos/08-morph/glyphs.json +1 -0
  106. package/lib/motion/mgstyles/upstream/demos/08-morph/index.html +901 -0
  107. package/lib/motion/mgstyles/upstream/demos/09-bauhaus/cues.json +270 -0
  108. package/lib/motion/mgstyles/upstream/demos/09-bauhaus/film.js +697 -0
  109. package/lib/motion/mgstyles/upstream/demos/09-bauhaus/index.html +12 -0
  110. package/lib/motion/mgstyles/upstream/demos/10-synthwave/cues.json +44 -0
  111. package/lib/motion/mgstyles/upstream/demos/10-synthwave/index.html +1028 -0
  112. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/cues.json +67 -0
  113. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/index.html +16 -0
  114. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/main.js +967 -0
  115. package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/shaders.js +468 -0
  116. package/lib/motion/mgstyles/upstream/demos/18-hanazi/cues.json +117 -0
  117. package/lib/motion/mgstyles/upstream/demos/18-hanazi/index.html +541 -0
  118. package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/countries-110m.json +1 -0
  119. package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/stickers/meta.json +34 -0
  120. package/lib/motion/mgstyles/upstream/demos/19-paperclip/cues.json +285 -0
  121. package/lib/motion/mgstyles/upstream/demos/19-paperclip/index.html +599 -0
  122. package/lib/motion/mgstyles/upstream/demos/20-pixel/cues.json +42 -0
  123. package/lib/motion/mgstyles/upstream/demos/20-pixel/film.js +914 -0
  124. package/lib/motion/mgstyles/upstream/demos/20-pixel/index.html +18 -0
  125. package/lib/motion/mgstyles/upstream/demos/22-hud/cues.json +108 -0
  126. package/lib/motion/mgstyles/upstream/demos/22-hud/index.html +125 -0
  127. package/lib/motion/mgstyles/upstream/demos/22-hud/js/holo.js +275 -0
  128. package/lib/motion/mgstyles/upstream/demos/22-hud/js/hud.js +855 -0
  129. package/lib/motion/mgstyles/upstream/demos/22-hud/js/post.js +150 -0
  130. package/lib/motion/mgstyles/upstream/demos/22-hud/js/util.js +65 -0
  131. package/lib/motion/mgstyles/upstream/demos/22-hud/js/v5.js +142 -0
  132. package/lib/motion/mgstyles/upstream/harness/render.mjs +319 -0
  133. package/lib/motion/mgstyles/upstream/manifest.json +269 -0
  134. package/lib/motion/mgstyles/upstream/prompts/01-flat-vector.md +98 -0
  135. package/lib/motion/mgstyles/upstream/prompts/02-line-art.md +95 -0
  136. package/lib/motion/mgstyles/upstream/prompts/03-isometric.md +97 -0
  137. package/lib/motion/mgstyles/upstream/prompts/05-cel-boil.md +95 -0
  138. package/lib/motion/mgstyles/upstream/prompts/06-collage.md +95 -0
  139. package/lib/motion/mgstyles/upstream/prompts/07-liquid.md +98 -0
  140. package/lib/motion/mgstyles/upstream/prompts/08-morph.md +95 -0
  141. package/lib/motion/mgstyles/upstream/prompts/09-bauhaus.md +96 -0
  142. package/lib/motion/mgstyles/upstream/prompts/10-synthwave.md +95 -0
  143. package/lib/motion/mgstyles/upstream/prompts/12-aurora-glass.md +96 -0
  144. package/lib/motion/mgstyles/upstream/prompts/18-hanazi.md +95 -0
  145. package/lib/motion/mgstyles/upstream/prompts/19-paperclip.md +96 -0
  146. package/lib/motion/mgstyles/upstream/prompts/20-pixel.md +96 -0
  147. package/lib/motion/mgstyles/upstream/prompts/22-hud.md +95 -0
  148. package/lib/motion/mgstyles/upstream/rubric.md +51 -0
  149. package/lib/motion/mgstyles/upstream/template.md +36 -0
  150. package/lib/motion/pixelqa.js +198 -0
  151. package/lib/motion/preview.js +97 -0
  152. package/lib/motion/project.js +395 -0
  153. package/lib/motion/qa.js +290 -0
  154. package/lib/motion/reference-media.jsx +10 -0
  155. package/lib/motion/render-settings.js +112 -0
  156. package/lib/motion/render.js +161 -0
  157. package/lib/motion/runtime/package-lock.json +3058 -0
  158. package/lib/motion/runtime/package.json +16 -0
  159. package/lib/motion/scene-kit.js +183 -0
  160. package/lib/motion/sfx.js +232 -0
  161. package/lib/motion/shotcraft/ClipCard.tsx +204 -0
  162. package/lib/motion/shotcraft/catalog.json +2461 -0
  163. package/lib/motion/shotcraft/upstream/SKILL.md +286 -0
  164. package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -0
  165. package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -0
  166. package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -0
  167. package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -0
  168. package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -0
  169. package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -0
  170. package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -0
  171. package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -0
  172. package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -0
  173. package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -0
  174. package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -0
  175. package/lib/motion/shotcraft/upstream/assets/scripts/__pycache__/sfx-source.cpython-313.pyc +0 -0
  176. package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -0
  177. package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -0
  178. package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -0
  179. package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -0
  180. package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -0
  181. package/lib/motion/shotcraft/upstream/demos/README.md +120 -0
  182. package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -0
  183. package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -0
  184. package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -0
  185. package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -0
  186. package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -0
  187. package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -0
  188. package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +389 -0
  189. package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -0
  190. package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -0
  191. package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -0
  192. package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -0
  193. package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -0
  194. package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -0
  195. package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -0
  196. package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -0
  197. package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -0
  198. package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -0
  199. package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -0
  200. package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -0
  201. package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -0
  202. package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -0
  203. package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -0
  204. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -0
  205. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -0
  206. package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -0
  207. package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -0
  208. package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -0
  209. package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -0
  210. package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -0
  211. package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -0
  212. package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -0
  213. package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -0
  214. package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -0
  215. package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -0
  216. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -0
  217. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -0
  218. package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -0
  219. package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -0
  220. package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -0
  221. package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -0
  222. package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -0
  223. package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -0
  224. package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -0
  225. package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -0
  226. package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -0
  227. package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -0
  228. package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -0
  229. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -0
  230. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -0
  231. package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -0
  232. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -0
  233. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -0
  234. package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -0
  235. package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -0
  236. package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -0
  237. package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -0
  238. package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -0
  239. package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -0
  240. package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -0
  241. package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -0
  242. package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -0
  243. package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -0
  244. package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -0
  245. package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -0
  246. package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -0
  247. package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -0
  248. package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -0
  249. package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -0
  250. package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -0
  251. package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -0
  252. package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -0
  253. package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -0
  254. package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -0
  255. package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -0
  256. package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -0
  257. package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -0
  258. package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -0
  259. package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -0
  260. package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -0
  261. package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -0
  262. package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -0
  263. package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -0
  264. package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -0
  265. package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -0
  266. package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -0
  267. package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -0
  268. package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -0
  269. package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -0
  270. package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -0
  271. package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -0
  272. package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -0
  273. package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -0
  274. package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -0
  275. package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -0
  276. package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -0
  277. package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -0
  278. package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -0
  279. package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -0
  280. package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -0
  281. package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -0
  282. package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -0
  283. package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -0
  284. package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -0
  285. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -0
  286. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -0
  287. package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -0
  288. package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -0
  289. package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -0
  290. package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -0
  291. package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -0
  292. package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -0
  293. package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -0
  294. package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -0
  295. package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -0
  296. package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -0
  297. package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -0
  298. package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -0
  299. package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -0
  300. package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -0
  301. package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -0
  302. package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -0
  303. package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -0
  304. package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -0
  305. package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -0
  306. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -0
  307. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -0
  308. package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -0
  309. package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -0
  310. package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -0
  311. package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -0
  312. package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -0
  313. package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -0
  314. package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -0
  315. package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -0
  316. package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -0
  317. package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -0
  318. package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -0
  319. package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -0
  320. package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -0
  321. package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -0
  322. package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -0
  323. package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -0
  324. package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -0
  325. package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -0
  326. package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -0
  327. package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -0
  328. package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -0
  329. package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -0
  330. package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -0
  331. package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -0
  332. package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -0
  333. package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -0
  334. package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -0
  335. package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -0
  336. package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -0
  337. package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -0
  338. package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -0
  339. package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -0
  340. package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -0
  341. package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -0
  342. package/lib/motion/shotcraft/upstream/manifest.json +2715 -0
  343. package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -0
  344. package/lib/motion/shotcraft/upstream/references/final-review.md +121 -0
  345. package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -0
  346. package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -0
  347. package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -0
  348. package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -0
  349. package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -0
  350. package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -0
  351. package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -0
  352. package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -0
  353. package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -0
  354. package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -0
  355. package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -0
  356. package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -0
  357. package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -0
  358. package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -0
  359. package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -0
  360. package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -0
  361. package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -0
  362. package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -0
  363. package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -0
  364. package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -0
  365. package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -0
  366. package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -0
  367. package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -0
  368. package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -0
  369. package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -0
  370. package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -0
  371. package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -0
  372. package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -0
  373. package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -0
  374. package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -0
  375. package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -0
  376. package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -0
  377. package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -0
  378. package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -0
  379. package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -0
  380. package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -0
  381. package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -0
  382. package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -0
  383. package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -0
  384. package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -0
  385. package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -0
  386. package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -0
  387. package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -0
  388. package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -0
  389. package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -0
  390. package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -0
  391. package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -0
  392. package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -0
  393. package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -0
  394. package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -0
  395. package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -0
  396. package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -0
  397. package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -0
  398. package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -0
  399. package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -0
  400. package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -0
  401. package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -0
  402. package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -0
  403. package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -0
  404. package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -0
  405. package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -0
  406. package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -0
  407. package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -0
  408. package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -0
  409. package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -0
  410. package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -0
  411. package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -0
  412. package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -0
  413. package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -0
  414. package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -0
  415. package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -0
  416. package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -0
  417. package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -0
  418. package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -0
  419. package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -0
  420. package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -0
  421. package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -0
  422. package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -0
  423. package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -0
  424. package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -0
  425. package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -0
  426. package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -0
  427. package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -0
  428. package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -0
  429. package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -0
  430. package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -0
  431. package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -0
  432. package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -0
  433. package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -0
  434. package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -0
  435. package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -0
  436. package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -0
  437. package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -0
  438. package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -0
  439. package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -0
  440. package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -0
  441. package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -0
  442. package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -0
  443. package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -0
  444. package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -0
  445. package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -0
  446. package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -0
  447. package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -0
  448. package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -0
  449. package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -0
  450. package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -0
  451. package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -0
  452. package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -0
  453. package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -0
  454. package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -0
  455. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -0
  456. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -0
  457. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -0
  458. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -0
  459. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -0
  460. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -0
  461. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -0
  462. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -0
  463. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -0
  464. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -0
  465. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -0
  466. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -0
  467. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -0
  468. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -0
  469. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -0
  470. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -0
  471. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -0
  472. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -0
  473. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -0
  474. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -0
  475. package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -0
  476. package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -0
  477. package/lib/motion/shotcraft/upstream/references/workbench.md +157 -0
  478. package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -0
  479. package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -0
  480. package/lib/motion/shotcraft/upstream/template/package.json +20 -0
  481. package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -0
  482. package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -0
  483. package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -0
  484. package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -0
  485. package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -0
  486. package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -0
  487. package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -0
  488. package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -0
  489. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -0
  490. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -0
  491. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -0
  492. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -0
  493. package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -0
  494. package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -0
  495. package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +389 -0
  496. package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -0
  497. package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -0
  498. package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -0
  499. package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -0
  500. package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -0
  501. package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -0
  502. package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -0
  503. package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -0
  504. package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -0
  505. package/lib/motion/spec.js +113 -0
  506. package/lib/motion/style-engine.js +153 -0
  507. package/lib/motion/style-frames.js +271 -0
  508. package/lib/motion/sync.js +214 -0
  509. package/lib/motion/theme.js +121 -0
  510. package/lib/motion/video-runtime.js +192 -0
  511. package/lib/motion/voice.js +417 -0
  512. package/lib/motion-agent-files.js +82 -0
  513. package/lib/motion-agent-protocol.js +66 -0
  514. package/lib/motion-agent-tools.js +196 -0
  515. package/lib/motion-agent.js +171 -0
  516. package/lib/motion-capture.js +93 -0
  517. package/lib/motion-core.js +238 -0
  518. package/lib/motion-film-review.js +71 -0
  519. package/lib/motion-library.js +210 -0
  520. package/lib/motion-planner.js +254 -0
  521. package/lib/motion-project.js +87 -0
  522. package/lib/motion-render-worker.js +153 -0
  523. package/lib/motion-session.js +32 -0
  524. package/lib/motion-source.js +49 -0
  525. package/lib/motion-tasks.js +452 -0
  526. package/lib/motion-vision.js +44 -0
  527. package/lib/mxfile.js +27 -7
  528. package/lib/routing/node-runtime.js +14 -0
  529. package/lib/routing/router.js +211 -0
  530. package/lib/routing/vendor/README.md +96 -0
  531. package/lib/routing/vendor/libavoid-routing.js +753 -0
  532. package/lib/routing/vendor/libavoid.min.js +59 -0
  533. package/lib/shotcard-core.js +39 -0
  534. package/lib/shotcard-reference.js +56 -0
  535. package/lib/style-kernel.js +37 -3
  536. package/lib/task-patches.js +26 -2
  537. package/lib/video-export.js +49 -22
  538. package/package.json +34 -4
@@ -0,0 +1,967 @@
1
+ // Aurora — think in light. 12-aurora-glass
2
+ // Every frame is a pure function of t: renderAt(t) recomputes all state from cues.json + closed-form curves.
3
+ import { VS, FS_AURORA, FS_DOWN, FS_BLUR, FS_CARD, FS_OVER, FS_LENS, FS_FINAL, FS_GLYPH } from './shaders.js';
4
+
5
+ const W = 1920, H = 1080, FPS = 30;
6
+ const DPR = window.devicePixelRatio || 1;
7
+ const RW = Math.round(W * DPR), RH = Math.round(H * DPR);
8
+ const QW = Math.max(8, Math.round(RW / 4)), QH = Math.max(8, Math.round(RH / 4));
9
+ const Q = new URLSearchParams(location.search);
10
+ const NOUI = Q.has('noui');
11
+ // reel v5 (?v5=1): the style name is the end-card title, the glass ring is its 句号, window.linkAt(t) reports the dot.
12
+ // ?nolink=dot[,title] hides those elements and changes nothing else. Without v5=1 every code path is the pre-v5 one.
13
+ const V5 = Q.get('v5') === '1';
14
+ const NOLINK = new Set(V5 ? (Q.get('nolink') || '').split(',').map((s) => s.trim()).filter(Boolean) : []);
15
+ const HIDE_DOT = NOLINK.has('dot'), HIDE_TITLE = NOLINK.has('title');
16
+ // v5.2 (jury round 6): title contrast pass (deep pool + halo behind the glass title, brighter glass, clearer line 2). ?v52=0 = v5.1 look
17
+ const V52 = V5 && Q.get('v52') !== '0';
18
+
19
+ const cues = await (await fetch('/demos/12-aurora-glass/cues.json')).json();
20
+ let ENV = null;
21
+ try { const r = await fetch('/demos/12-aurora-glass/out/env.json'); if (r.ok) ENV = await r.json(); } catch (e) { ENV = null; }
22
+
23
+ // ------------------------------------------------------------------ math
24
+ const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));
25
+ const lerp = (a, b, t) => a + (b - a) * t;
26
+ const inv = (a, b, x) => clamp((x - a) / (b - a));
27
+ const eSine = (t) => -(Math.cos(Math.PI * clamp(t)) - 1) / 2;
28
+ const eOutSine = (t) => Math.sin(clamp(t) * Math.PI / 2);
29
+ const eInSine = (t) => 1 - Math.cos(clamp(t) * Math.PI / 2);
30
+ const eOutCubic = (t) => 1 - Math.pow(1 - clamp(t), 3);
31
+ const eOutQuart = (t) => 1 - Math.pow(1 - clamp(t), 4);
32
+ const eOutQuint = (t) => 1 - Math.pow(1 - clamp(t), 5);
33
+ const eInOutCubic = (t) => { t = clamp(t); return t < .5 ? 4 * t * t * t : 1 - Math.pow(-2 * t + 2, 3) / 2; };
34
+ const seg = (t, a, b, e = eSine) => e(inv(a, b, t));
35
+ const TAU = Math.PI * 2;
36
+ const D2R = Math.PI / 180;
37
+
38
+ // time-parameterised cubic Hermite through keys [{t, v:[..], m?:[..]}] (Catmull-Rom tangents, zero at ends)
39
+ function hermite(keys) {
40
+ const n = keys.length;
41
+ keys.forEach((k, i) => {
42
+ if (k.m) return;
43
+ if (i === 0 || i === n - 1) { k.m = k.v.map(() => 0); return; }
44
+ const a = keys[i - 1], b = keys[i + 1], s = k.s ?? 1;
45
+ k.m = k.v.map((_, j) => s * (b.v[j] - a.v[j]) / (b.t - a.t));
46
+ });
47
+ return (t) => {
48
+ if (t <= keys[0].t) return keys[0].v.slice();
49
+ if (t >= keys[n - 1].t) return keys[n - 1].v.slice();
50
+ let i = 0; while (t > keys[i + 1].t) i++;
51
+ const a = keys[i], b = keys[i + 1], h = b.t - a.t, s = (t - a.t) / h;
52
+ const s2 = s * s, s3 = s2 * s;
53
+ const h00 = 2 * s3 - 3 * s2 + 1, h10 = s3 - 2 * s2 + s, h01 = -2 * s3 + 3 * s2, h11 = s3 - s2;
54
+ return a.v.map((_, j) => h00 * a.v[j] + h10 * h * a.m[j] + h01 * b.v[j] + h11 * h * b.m[j]);
55
+ };
56
+ }
57
+ // monotone scalar keys (smoothstep between keys) — for sizes/levels where overshoot is unwanted
58
+ function keysS(list, ease = eSine) {
59
+ return (t) => {
60
+ if (t <= list[0][0]) return list[0][1];
61
+ for (let i = 0; i < list.length - 1; i++) {
62
+ const [t0, v0] = list[i], [t1, v1] = list[i + 1];
63
+ if (t <= t1) return lerp(v0, v1, ease(inv(t0, t1, t)));
64
+ }
65
+ return list[list.length - 1][1];
66
+ };
67
+ }
68
+
69
+ // 3D helpers
70
+ function rotM(rx, ry, rz) { // R = Rz * Ry * Rx (degrees)
71
+ const a = rx * D2R, b = ry * D2R, c = rz * D2R;
72
+ const ca = Math.cos(a), sa = Math.sin(a), cb = Math.cos(b), sb = Math.sin(b), cc = Math.cos(c), sc = Math.sin(c);
73
+ // columns = images of the basis vectors
74
+ const Rx = [[1, 0, 0], [0, ca, -sa], [0, sa, ca]];
75
+ const Ry = [[cb, 0, sb], [0, 1, 0], [-sb, 0, cb]];
76
+ const Rz = [[cc, -sc, 0], [sc, cc, 0], [0, 0, 1]];
77
+ const mul = (A, B) => A.map((r, i) => [0, 1, 2].map((j) => r[0] * B[0][j] + r[1] * B[1][j] + r[2] * B[2][j]));
78
+ const M = mul(Rz, mul(Ry, Rx));
79
+ return { U: [M[0][0], M[1][0], M[2][0]], V: [M[0][1], M[1][1], M[2][1]], N: [M[0][2], M[1][2], M[2][2]] };
80
+ }
81
+ const F = 2000; // focal length (px)
82
+ // one continuous, slow dolly: settle on the cluster -> lean in on the Focus toggle (the lens is born there)
83
+ // -> tilt up with the rising bead to the reply -> keep pushing while the glass parts. (x,y: camera shift, z: dolly)
84
+ // r2: the hook HOLDS the macro (drift <= 2 px/frame while a catch-light runs round the bevel), then ONE in-out-sine
85
+ // pull-back 0.5 -> 2.8 in log-distance (a straight dolly line); corner speed peak 36.7 -> 19.8 px/frame (camera simulator)
86
+ const HOOK = { p0: [q('hx', 200), q('hy', -120), q('hz', 900)], drift: [-20, 8, -24], t1: 0.5, t2: 2.8 };
87
+ const LAYOUT = [10, 40, 300]; // r2s2: overview no longer pulls back past the layout (less out-and-in travel)
88
+ const CAM_KEYS = [
89
+ { t: HOOK.t2, v: LAYOUT, m: [0, 0, 0] },
90
+ { t: 3.4, v: [-16, 36, 350] },
91
+ { t: 5.05, v: [-q('cx', 50), q('cy', 170), q('cz', 690)], m: [0, 0, 0] }, // lean in on the (1.25x) knob: bead birth
92
+ // r2: the pull-back follows the bead up to the reply and is FINISHED by 5.95 ...
93
+ { t: 5.95, v: [10, 30, 560], m: [0, 0, 40] },
94
+ // ... then a slow in-out push (+3.6 %) toward the lens while the light turns gold: the bloom comes toward the viewer
95
+ { t: 6.6, v: [6, 26, 612], m: [0, 0, 15] },
96
+ { t: 7.3, v: [20, 12, 607] },
97
+ { t: 8.6, v: [18, 0, 602] },
98
+ { t: 10.6, v: [6, -8, 617] },
99
+ ];
100
+ function q(k, d) { return Q.has(k) ? +Q.get(k) : d; }
101
+ const camK = hermite(CAM_KEYS);
102
+ function camera(t) {
103
+ if (t < HOOK.t2) {
104
+ const d = HOOK.drift, a = HOOK.p0, pa = a.map((v, j) => v + d[j] * Math.min(t, HOOK.t1));
105
+ if (t <= HOOK.t1) return { x: pa[0], y: pa[1], z: pa[2] };
106
+ const e = eSine(inv(HOOK.t1, HOOK.t2, t)), Db = F - pa[2], DL = F - LAYOUT[2];
107
+ const D = Math.exp(lerp(Math.log(Db), Math.log(DL), e)), w = (D - Db) / (DL - Db), k = (t - HOOK.t1) * (1 - e);
108
+ return { x: lerp(pa[0], LAYOUT[0], w) + d[0] * k, y: lerp(pa[1], LAYOUT[1], w) + d[1] * k, z: F - D + d[2] * k };
109
+ }
110
+ const v = camK(t); return { x: v[0], y: v[1], z: v[2] };
111
+ }
112
+ function camO(cam) { return [960 + cam.x, 540 + cam.y, -F + cam.z]; }
113
+ function project(P, O) { const dz = P[2] - O[2]; return [960 + F * (P[0] - O[0]) / dz, 540 + F * (P[1] - O[1]) / dz, F / dz]; }
114
+
115
+ // ------------------------------------------------------------------ palette
116
+ const hex = (h) => [parseInt(h.slice(1, 3), 16) / 255, parseInt(h.slice(3, 5), 16) / 255, parseInt(h.slice(5, 7), 16) / 255];
117
+ const PAL = {
118
+ base: '#040312', violet: '#7050FF', blue: '#2F46F2', cyan: '#34CFF0', magenta: '#B653F0', indigo: '#3320A8', periwinkle: '#8C8CFF',
119
+ };
120
+
121
+ // ------------------------------------------------------------------ aurora blobs: slow bezier drifts (20–40 s loops, we see a slice)
122
+ function loopBezier(pts, period, phase) { // closed cubic bezier loop through 4 anchor points with smooth tangents
123
+ const n = pts.length;
124
+ return (t) => {
125
+ let u = ((t / period + phase) % 1 + 1) % 1 * n;
126
+ const i = Math.floor(u), s = u - i;
127
+ const p0 = pts[(i - 1 + n) % n], p1 = pts[i], p2 = pts[(i + 1) % n], p3 = pts[(i + 2) % n];
128
+ // Catmull-Rom → Bezier
129
+ const c1 = [p1[0] + (p2[0] - p0[0]) / 6, p1[1] + (p2[1] - p0[1]) / 6];
130
+ const c2 = [p2[0] - (p3[0] - p1[0]) / 6, p2[1] - (p3[1] - p1[1]) / 6];
131
+ const a = 1 - s;
132
+ return [0, 1].map((k) => a * a * a * p1[k] + 3 * a * a * s * c1[k] + 3 * a * s * s * c2[k] + s * s * s * p2[k]);
133
+ };
134
+ }
135
+ // col, rx, ry, angle(deg), intensity, path (closed bezier through 4 anchors, period s, phase)
136
+ const BLOBS = [
137
+ { col: PAL.violet, rx: 470, ry: 300, ang: 18, i: 1.15, path: loopBezier([[600, 300], [780, 240], [840, 420], [560, 460]], 30, 0.05) },
138
+ { col: PAL.magenta, rx: 400, ry: 230, ang: -28, i: 0.9, path: loopBezier([[1560, 170], [1720, 330], [1380, 360], [1300, 130]], 34, 0.6) },
139
+ { col: PAL.cyan, rx: 360, ry: 260, ang: 32, i: 1.0, path: loopBezier([[1500, 860], [1320, 700], [1160, 660], [1420, 980]], 26, 0.12) },
140
+ { col: PAL.blue, rx: 500, ry: 250, ang: -8, i: 0.95, path: loopBezier([[820, 880], [560, 800], [700, 980], [1060, 920]], 36, 0.5) },
141
+ { col: PAL.indigo, rx: 560, ry: 380, ang: 0, i: 0.32, path: loopBezier([[1020, 540], [1160, 470], [1080, 640], [880, 600]], 40, 0.3) },
142
+ { col: PAL.periwinkle, rx: 240, ry: 170, ang: 10, i: 0.45, path: loopBezier([[1180, 420], [1060, 520], [900, 560], [1120, 360]], 24, 0.1) },
143
+ { col: '#3036D8', rx: 460, ry: 210, ang: -6, i: 0.55, path: loopBezier([[1180, 1090], [1320, 1040], [1060, 1010], [980, 1080]], 32, 0.2) },
144
+ ];
145
+ const NB = BLOBS.length;
146
+ // ------------------------------------------------------------------ WebGL setup
147
+ const canvas = document.getElementById('gl');
148
+ canvas.width = RW; canvas.height = RH;
149
+ const gl = canvas.getContext('webgl2', { preserveDrawingBuffer: true, antialias: false, alpha: false, premultipliedAlpha: false, powerPreference: 'high-performance' });
150
+ if (!gl) throw new Error('webgl2 unavailable');
151
+ const hasFloat = !!gl.getExtension('EXT_color_buffer_float');
152
+ gl.getExtension('OES_texture_float_linear');
153
+ const IFMT = hasFloat ? gl.RGBA16F : gl.RGBA8, TYPE = hasFloat ? gl.HALF_FLOAT : gl.UNSIGNED_BYTE;
154
+
155
+ function compile(fs) {
156
+ const mk = (type, src) => { const s = gl.createShader(type); gl.shaderSource(s, src); gl.compileShader(s);
157
+ if (!gl.getShaderParameter(s, gl.COMPILE_STATUS)) { console.error(gl.getShaderInfoLog(s)); throw new Error('shader compile'); } return s; };
158
+ const p = gl.createProgram(); gl.attachShader(p, mk(gl.VERTEX_SHADER, VS)); gl.attachShader(p, mk(gl.FRAGMENT_SHADER, fs)); gl.linkProgram(p);
159
+ if (!gl.getProgramParameter(p, gl.LINK_STATUS)) { console.error(gl.getProgramInfoLog(p)); throw new Error('link'); }
160
+ const loc = {}; const n = gl.getProgramParameter(p, gl.ACTIVE_UNIFORMS);
161
+ for (let i = 0; i < n; i++) { const u = gl.getActiveUniform(p, i); const name = u.name.replace(/\[0\]$/, ''); loc[name] = gl.getUniformLocation(p, u.name); }
162
+ return { p, loc };
163
+ }
164
+ const P = { aurora: compile(FS_AURORA), down: compile(FS_DOWN), blur: compile(FS_BLUR), card: compile(FS_CARD), over: compile(FS_OVER), lens: compile(FS_LENS), final: compile(FS_FINAL) };
165
+ if (V5) P.glyph = compile(FS_GLYPH); // v5.1: frosted-glass letterforms
166
+ const vao = gl.createVertexArray();
167
+
168
+ function makeTarget(w, h, fmt = IFMT, type = TYPE) {
169
+ const tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex);
170
+ gl.texImage2D(gl.TEXTURE_2D, 0, fmt, w, h, 0, gl.RGBA, type, null);
171
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
172
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
173
+ const fb = gl.createFramebuffer(); gl.bindFramebuffer(gl.FRAMEBUFFER, fb);
174
+ gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, tex, 0);
175
+ return { tex, fb, w, h };
176
+ }
177
+ const T = { sceneA: makeTarget(RW, RH), sceneB: makeTarget(RW, RH), lens: makeTarget(RW, RH), q1: makeTarget(QW, QH), q2: makeTarget(QW, QH), qL: makeTarget(QW, QH), qB: makeTarget(QW, QH) };
178
+ if (V5) { T.g1 = makeTarget(RW, RH); T.g2 = makeTarget(RW, RH); } // v5.1: bevel blur of the glass glyph mask
179
+
180
+ function canvasTex(mip) {
181
+ const tex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, tex);
182
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, mip ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);
183
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
184
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
185
+ return tex;
186
+ }
187
+ function upload(tex, cv, mip) {
188
+ gl.bindTexture(gl.TEXTURE_2D, tex);
189
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
190
+ gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
191
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, cv);
192
+ if (mip) gl.generateMipmap(gl.TEXTURE_2D);
193
+ }
194
+
195
+ // grain texture
196
+ const grainImg = new Image(); grainImg.src = '/assets/textures/grain/grain_fine_2048.png';
197
+ await grainImg.decode();
198
+ const grainTex = gl.createTexture(); gl.bindTexture(gl.TEXTURE_2D, grainTex);
199
+ gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, false);
200
+ gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, grainImg);
201
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
202
+ gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.REPEAT); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.REPEAT);
203
+
204
+ // fonts
205
+ const FONT_UI = V5 ? "'Inter', 'HarmonyOS Sans SC'" : "'Inter'", FONT_WORD = "'Sora'", FONT_CN = "'HarmonyOS Sans SC'";
206
+ // v5 copy: Chinese UI micro-copy + the title lockup
207
+ // v5.1: the title is HarmonyOS Black (900) rendered as frosted glass (FS_GLYPH); line 2 is HarmonyOS Medium
208
+ const V5T = { title: '玻璃拟态', sub: '弥散渐变', en: 'AURORA · GLASS', size: 216, track: 10, base: 580, w: 900, subSize: 54, subW: 500, subBase: 704, enSize: 32, enTrack: 6 };
209
+ const UI = V5 ? {
210
+ name: '极光', now: '刚刚', thinking: '思考中', ask: '帮我安排一个慢悠悠的周日。',
211
+ reply: ['十点,慢慢吃早餐。', '四点,去湖边走一走,', '正好赶上光变成金色。', '其余的,我都替你推掉了。'],
212
+ chips: ['加入日历', '3:30 提醒我'], listening: '聆听中', caption: '“……今晚留给家人”',
213
+ focus: '专注', focusSub: '仅家人可以联系你', glow: '光晕',
214
+ } : null;
215
+ if (V5) {
216
+ const cjk = V5T.title + '。' + V5T.sub + UI.name + UI.now + UI.thinking + UI.ask + UI.reply.join('') + UI.chips.join('') + UI.listening + UI.caption + UI.focus + UI.focusSub + UI.glow;
217
+ await Promise.all([300, 400, 500, 700].map((w) => document.fonts.load(`${w} 200px ${FONT_CN}`, cjk)).concat(
218
+ [document.fonts.load(`300 28px ${FONT_WORD}`, V5T.en), document.fonts.load(`${V5T.w} ${V5T.size}px ${FONT_CN}`, V5T.title + '。'),
219
+ document.fonts.load(`${V5T.subW} ${V5T.subSize}px ${FONT_CN}`, V5T.sub), document.fonts.load(`${V5T.subW} ${V5T.enSize}px ${FONT_CN}`, V5T.en)]));
220
+ }
221
+ await Promise.all([
222
+ document.fonts.load(`400 24px ${FONT_UI}`, 'Aa'), document.fonts.load(`500 24px ${FONT_UI}`, 'Aa'), document.fonts.load(`600 24px ${FONT_UI}`, 'Aa'),
223
+ document.fonts.load(`200 150px ${FONT_WORD}`, 'Aurora'), document.fonts.load(`300 30px ${FONT_WORD}`, 'Think in light.'),
224
+ document.fonts.load(`300 26px ${FONT_CN}`, '思考,自有光'),
225
+ ]);
226
+ await document.fonts.ready;
227
+
228
+ // ------------------------------------------------------------------ gaussian taps
229
+ function gaussTaps(sigma) {
230
+ const R = Math.min(29, Math.max(1, Math.ceil(sigma * 3)));
231
+ const w = []; let sum = 0;
232
+ for (let i = 0; i <= R; i++) { const v = Math.exp(-i * i / (2 * sigma * sigma)); w.push(v); sum += i ? 2 * v : v; }
233
+ for (let i = 0; i <= R; i++) w[i] /= sum;
234
+ const offs = [0], ws = [w[0]];
235
+ for (let i = 1; i <= R; i += 2) { const a = w[i], b = i + 1 <= R ? w[i + 1] : 0, s = a + b; offs.push(s > 0 ? (i * a + (i + 1) * b) / s : i); ws.push(s); }
236
+ while (offs.length < 16) { offs.push(0); ws.push(0); }
237
+ return { offs: new Float32Array(offs.slice(0, 16)), ws: new Float32Array(ws.slice(0, 16)), n: Math.min(16, Math.ceil(R / 2) + 1) };
238
+ }
239
+ const TAPS_FROST = gaussTaps(30 * DPR / 4); // backdrop-filter: blur(30px)
240
+ const TAPS_LENS = gaussTaps(22 * DPR / 4);
241
+ const TAPS_BLOOM = gaussTaps(40 * DPR / 4);
242
+
243
+ // ------------------------------------------------------------------ pass helpers
244
+ function use(prog, target) {
245
+ gl.useProgram(prog.p);
246
+ if (target) { gl.bindFramebuffer(gl.FRAMEBUFFER, target.fb); gl.viewport(0, 0, target.w, target.h); }
247
+ else { gl.bindFramebuffer(gl.FRAMEBUFFER, null); gl.viewport(0, 0, RW, RH); }
248
+ const w = target ? target.w : RW, h = target ? target.h : RH;
249
+ if (prog.loc.uRes) gl.uniform2f(prog.loc.uRes, w, h);
250
+ if (prog.loc.uDPR) gl.uniform1f(prog.loc.uDPR, DPR * (target ? target.w / RW : 1));
251
+ return prog.loc;
252
+ }
253
+ function bindTex(unit, tex, loc) { gl.activeTexture(gl.TEXTURE0 + unit); gl.bindTexture(gl.TEXTURE_2D, tex); gl.uniform1i(loc, unit); }
254
+ function draw() { gl.bindVertexArray(vao); gl.drawArrays(gl.TRIANGLES, 0, 3); }
255
+ function blurInto(src, dst, taps, thresh = 0) {
256
+ // src full-res → dst quarter (blurred)
257
+ let L = use(P.down, T.q2); bindTex(0, src.tex, L.uSrc); gl.uniform2f(L.uSrcTexel, 1 / src.w, 1 / src.h); gl.uniform1f(L.uThresh, thresh); draw();
258
+ L = use(P.blur, T.q1); bindTex(0, T.q2.tex, L.uSrc); gl.uniform2f(L.uDir, 1, 0); gl.uniform1fv(L.uOff, taps.offs); gl.uniform1fv(L.uW, taps.ws); gl.uniform1i(L.uN, taps.n); draw();
259
+ L = use(P.blur, dst); bindTex(0, T.q1.tex, L.uSrc); gl.uniform2f(L.uDir, 0, 1); gl.uniform1fv(L.uOff, taps.offs); gl.uniform1fv(L.uW, taps.ws); gl.uniform1i(L.uN, taps.n); draw();
260
+ }
261
+
262
+ // ------------------------------------------------------------------ 2D UI drawing helpers
263
+ function rr(g, x, y, w, h, r) { r = Math.min(r, w / 2, h / 2); g.beginPath(); g.moveTo(x + r, y); g.arcTo(x + w, y, x + w, y + h, r); g.arcTo(x + w, y + h, x, y + h, r); g.arcTo(x, y + h, x, y, r); g.arcTo(x, y, x + w, y, r); g.closePath(); }
264
+ function font(g, w, s, fam = FONT_UI) { g.font = `${w} ${s}px ${fam}`; }
265
+ function riseA(t, t0, dur = 0.75) { const k = inv(t0, t0 + dur, t); return { a: eSine(k), dy: 8 * (1 - eOutCubic(k)) }; }
266
+ const WHITE = (a) => `rgba(255,255,255,${a})`;
267
+
268
+ function orbIcon(g, x, y, r, t) { // small aurora orb avatar
269
+ const gr = g.createRadialGradient(x - r * 0.35, y - r * 0.4, r * 0.1, x, y, r);
270
+ gr.addColorStop(0, '#E9E4FF'); gr.addColorStop(0.35, '#9A7CFF'); gr.addColorStop(0.75, '#3F55F0'); gr.addColorStop(1, '#35C8EE');
271
+ g.fillStyle = gr; g.beginPath(); g.arc(x, y, r, 0, TAU); g.fill();
272
+ g.strokeStyle = WHITE(0.5); g.lineWidth = 1; g.beginPath(); g.arc(x, y, r - 0.5, 0, TAU); g.stroke();
273
+ }
274
+
275
+ // ------------------------------------------------------------------ cards
276
+ const S = 2; // content texture scale
277
+ function makeCard(def) {
278
+ const cv = document.createElement('canvas'); cv.width = def.w * S; cv.height = def.h * S;
279
+ return { ...def, cv, g: cv.getContext('2d'), tex: canvasTex(true) };
280
+ }
281
+
282
+ // A — conversation
283
+ const REPLY = V5 ? UI.reply : ['Slow breakfast at ten.', 'The lake path at four,', 'when the light turns gold.', 'I’ve cleared the rest.'];
284
+ function drawChat(c, t) {
285
+ const g = c.g; g.setTransform(S, 0, 0, S, 0, 0); g.clearRect(0, 0, c.w, c.h);
286
+ g.textBaseline = 'alphabetic';
287
+ const P0 = 38;
288
+ // header
289
+ const hA = riseA(t, cues.cardA_in + 0.35, 0.9);
290
+ g.globalAlpha = hA.a;
291
+ orbIcon(g, P0 + 16, P0 + 16 + hA.dy, 16, t);
292
+ font(g, 600, 20); g.fillStyle = WHITE(0.95); g.letterSpacing = '0px'; g.fillText(V5 ? UI.name : 'Aurora', P0 + 44, P0 + 23 + hA.dy);
293
+ const thinking = inv(cues.typing - 0.1, cues.typing + 0.15, t) * (1 - inv(cues.reply[0] - 0.05, cues.reply[0] + 0.35, t));
294
+ font(g, 400, 16); g.fillStyle = WHITE(0.5); g.textAlign = 'right';
295
+ g.globalAlpha = hA.a * (1 - thinking); g.fillText(V5 ? UI.now : 'now', c.w - P0, P0 + 22 + hA.dy);
296
+ g.globalAlpha = hA.a * thinking; g.fillText(V5 ? UI.thinking : 'thinking', c.w - P0, P0 + 22 + hA.dy);
297
+ g.textAlign = 'left'; g.globalAlpha = 1;
298
+ // user bubble (right aligned)
299
+ const ub = riseA(t, cues.user_bubble - 0.25, 0.7); // r1s2: in medias res - the bubble is already landing on frame 0 (pop at 0.03)
300
+ if (ub.a > 0) {
301
+ font(g, 400, 21); const txt = V5 ? UI.ask : 'Plan a slow Sunday for me.'; const tw = g.measureText(txt).width;
302
+ const bw = tw + 44, bh = 50, bx = c.w - P0 - bw, by = 100 + ub.dy;
303
+ g.globalAlpha = ub.a;
304
+ rr(g, bx, by, bw, bh, 25); g.fillStyle = WHITE(0.16); g.fill(); g.strokeStyle = WHITE(0.22); g.lineWidth = 1; g.stroke();
305
+ g.fillStyle = WHITE(0.96); g.fillText(txt, bx + 22, by + 32);
306
+ g.globalAlpha = 1;
307
+ }
308
+ // typing indicator
309
+ const ty = inv(cues.typing, cues.typing + 0.3, t) * (1 - inv(cues.reply[0] - 0.1, cues.reply[0] + 0.2, t));
310
+ if (ty > 0.001) {
311
+ g.globalAlpha = eSine(ty);
312
+ rr(g, P0, 178, 76, 40, 20); g.fillStyle = WHITE(0.12); g.fill();
313
+ for (let i = 0; i < 3; i++) { const ph = 0.5 + 0.5 * Math.sin(t * 7.5 - i * 0.9); g.fillStyle = WHITE(0.35 + 0.5 * ph); g.beginPath(); g.arc(P0 + 22 + i * 16, 198 - 2 * ph, 4, 0, TAU); g.fill(); }
314
+ g.globalAlpha = 1;
315
+ }
316
+ // reply: streamed word by word, each word opacity + 8px rise
317
+ font(g, 400, 25); g.letterSpacing = '-0.2px';
318
+ const LH = 40, Y0 = 212;
319
+ REPLY.forEach((line, li) => {
320
+ const t0 = cues.reply[Math.min(li, cues.reply.length - 1)] + (li >= cues.reply.length ? 0.375 : 0);
321
+ const words = V5 ? [...line] : line.split(' '); let x = P0; // v5: Chinese streams glyph by glyph
322
+ words.forEach((wd, wi) => {
323
+ const r = riseA(t, t0 + wi * (V5 ? 0.032 : 0.06), 0.7);
324
+ const ww = g.measureText(V5 ? wd : wd + ' ').width;
325
+ if (r.a > 0) { g.globalAlpha = r.a; g.fillStyle = li === 2 ? WHITE(1) : WHITE(0.9); g.fillText(wd, x, Y0 + li * LH + r.dy); }
326
+ x += ww;
327
+ });
328
+ });
329
+ g.globalAlpha = 1; g.letterSpacing = '0px';
330
+ // action chips
331
+ font(g, 500, 16);
332
+ let cx = P0 + 48; // r2: clear of card C's rim
333
+ (V5 ? UI.chips : ['Add to calendar', 'Remind me at 3:30']).forEach((txt, i) => {
334
+ const r = riseA(t, cues.chips + i * 0.12, 0.75);
335
+ const tw = g.measureText(txt).width, bw = tw + 36;
336
+ if (r.a > 0) {
337
+ g.globalAlpha = r.a;
338
+ rr(g, cx, c.h - P0 - 40 + r.dy, bw, 40, 20); g.fillStyle = WHITE(i === 0 ? 0.2 : 0.1); g.fill(); g.strokeStyle = WHITE(0.24); g.lineWidth = 1; g.stroke();
339
+ g.fillStyle = WHITE(0.92); g.fillText(txt, cx + 18, c.h - P0 - 14 + r.dy);
340
+ }
341
+ cx += bw + 12;
342
+ });
343
+ g.globalAlpha = 1;
344
+ }
345
+
346
+ // B — voice
347
+ function envAt(t) {
348
+ if (ENV && ENV.rate) { const i = t * ENV.rate; const a = Math.floor(i), f = i - a; const v = ENV.v; const x0 = v[clamp(a, 0, v.length - 1)], x1 = v[clamp(a + 1, 0, v.length - 1)]; return lerp(x0, x1, f); }
349
+ return 0.45 + 0.25 * Math.sin(t * 5.1) * Math.sin(t * 1.7 + 1) + 0.15 * Math.sin(t * 11.3);
350
+ }
351
+ function drawVoice(c, t) {
352
+ const g = c.g; g.setTransform(S, 0, 0, S, 0, 0); g.clearRect(0, 0, c.w, c.h);
353
+ const P0 = 30;
354
+ const hA = riseA(t, cues.cardB_in + 0.35, 0.9);
355
+ g.globalAlpha = hA.a;
356
+ // r2: label + timer (small), the live orb is now the waveform's source
357
+ font(g, 500, 19); g.fillStyle = WHITE(0.95); g.fillText(V5 ? UI.listening : 'Listening', P0, P0 + 18 + hA.dy);
358
+ const secs = Math.max(0, Math.floor(t - cues.voice_on + 3));
359
+ const lw = g.measureText(V5 ? UI.listening : 'Listening').width; font(g, 400, 17); g.fillStyle = WHITE(0.5);
360
+ g.fillText(`0:${String(secs).padStart(2, '0')}`, P0 + lw + 12, P0 + 18 + hA.dy);
361
+ g.globalAlpha = 1;
362
+ // waveform radiating from the orb (newest at the orb), driven by the soundtrack envelope.
363
+ // The orb sits ON card A's left edge (B-local x ~ 180): half crisp outside, half blooming through A's frost.
364
+ const OX = q('orbx', 178), cy = 122, maxH = 80, bw = 6, step = 11.5;
365
+ const on = seg(t, cues.voice_on, cues.voice_on + 0.8);
366
+ const bar = (x, dist, a) => {
367
+ const tt = t - dist * 0.0042;
368
+ const e = clamp(envAt(Math.max(0, tt)));
369
+ const shape = Math.exp(-Math.pow(dist / 150, 2)) * 0.8 + 0.2;
370
+ const jitter = 0.72 + 0.28 * Math.sin(x * 0.37 + tt * 3.1);
371
+ const h = Math.max(5, maxH * on * (0.1 + 0.9 * Math.pow(e, 0.75) * shape * jitter));
372
+ const gr = g.createLinearGradient(0, cy - h / 2, 0, cy + h / 2);
373
+ gr.addColorStop(0, `rgba(170,238,255,${a})`); gr.addColorStop(0.5, `rgba(255,255,255,${a})`); gr.addColorStop(1, `rgba(196,176,255,${a})`);
374
+ g.fillStyle = gr; rr(g, x - bw / 2, cy - h / 2, bw, h, bw / 2); g.fill();
375
+ };
376
+ for (let x = OX - 34, i = 0; x > P0; x -= step, i++) bar(x, OX - x, 0.5 + 0.45 * Math.exp(-i / 6));
377
+ for (let x = OX + 34; x < c.w - P0; x += step) bar(x, x - OX, 0.95); // the bars under A: chunky, 95 % white
378
+ const e0 = clamp(envAt(t)), pr = 20 * (0.92 + 0.14 * e0) * (0.3 + 0.7 * on);
379
+ const halo = g.createRadialGradient(OX, cy, 0, OX, cy, pr * 4.6);
380
+ halo.addColorStop(0, `rgba(120,228,255,${0.95 * on})`); halo.addColorStop(0.28, `rgba(67,211,242,${0.64 * on})`); halo.addColorStop(0.6, `rgba(67,211,242,${0.26 * on})`); halo.addColorStop(1, 'rgba(67,211,242,0)');
381
+ g.fillStyle = halo; g.beginPath(); g.arc(OX, cy, pr * 4.6, 0, TAU); g.fill();
382
+ const core = g.createRadialGradient(OX - pr * 0.3, cy - pr * 0.35, 0, OX, cy, pr);
383
+ core.addColorStop(0, '#FFFFFF'); core.addColorStop(0.35, '#BFF4FF'); core.addColorStop(1, '#43D3F2');
384
+ g.globalAlpha = on; g.fillStyle = core; g.beginPath(); g.arc(OX, cy, pr, 0, TAU); g.fill(); g.globalAlpha = 1;
385
+ // caption: short enough to stay left of A
386
+ const cap = riseA(t, cues.voice_on + 0.9, 0.8);
387
+ g.globalAlpha = cap.a * 0.6; font(g, 400, 15); g.fillStyle = WHITE(1); g.fillText(V5 ? UI.caption : '“…keep tonight free”', P0, c.h - P0 + 2 + cap.dy);
388
+ g.globalAlpha = 1;
389
+ }
390
+
391
+ // C — modes (the Focus toggle knob becomes the Liquid Glass lens)
392
+ const TOG = { x: 440 - 32 - 80, y: 92 - 22.5, w: 80, h: 45 }; // r2: 1.25x (glass knob ~70 px at the lean-in) // card-local, top-left origin
393
+ const KNOB_R = 18.75;
394
+ function toggleP(t) { return eInOutCubic(inv(cues.toggle_on, cues.toggle_on + 0.42, t)); }
395
+ function knobLocal(t) { // centre of the knob, card-local, relative to card centre
396
+ const p = toggleP(t);
397
+ const x = TOG.x + TOG.h / 2 + p * (TOG.w - TOG.h);
398
+ return [x - 220, TOG.y + TOG.h / 2 - 125];
399
+ }
400
+ function knobVis(t) { // white knob opacity & scale in the canvas
401
+ const press = seg(t, cues.knob_press - 0.12, cues.knob_press + 0.1);
402
+ const glass = seg(t, cues.knob_glass - 0.07, cues.knob_glass + 0.08);
403
+ const back = seg(t, 5.5, 5.85);
404
+ return { a: (HIDE_DOT ? 0 : 1 - glass) + glass * back, s: 1 + 0.14 * press * (1 - back), dotA: 1 - glass };
405
+ }
406
+ function drawModes(c, t) {
407
+ const g = c.g; g.setTransform(S, 0, 0, S, 0, 0); g.clearRect(0, 0, c.w, c.h);
408
+ const P0 = 32;
409
+ const hA = riseA(t, cues.cardC_in + 0.35, 0.9);
410
+ g.globalAlpha = hA.a;
411
+ // row 1: Focus
412
+ const y1 = 92 + hA.dy;
413
+ // moon icon
414
+ g.save(); g.translate(P0 + 14, y1); g.fillStyle = WHITE(0.92);
415
+ g.beginPath(); g.arc(0, 0, 12, 0, TAU); g.arc(6, -5, 10, 0, TAU, true); g.fill('evenodd'); g.restore();
416
+ font(g, 500, 21); g.fillStyle = WHITE(0.95); g.fillText(V5 ? UI.focus : 'Focus', P0 + 42, y1 - 2);
417
+ font(g, 400, 15); g.fillStyle = WHITE(0.5); g.fillText(V5 ? UI.focusSub : 'Only family can reach you', P0 + 42, y1 + 20);
418
+ // toggle
419
+ const p = toggleP(t);
420
+ const tx = TOG.x, ty = TOG.y + hA.dy;
421
+ rr(g, tx, ty, TOG.w, TOG.h, TOG.h / 2); g.fillStyle = WHITE(0.16); g.fill();
422
+ if (p > 0) {
423
+ g.save(); g.globalAlpha = hA.a * p;
424
+ const gr = g.createLinearGradient(tx, 0, tx + TOG.w, 0); gr.addColorStop(0, '#5B7CFF'); gr.addColorStop(1, '#43D3F2');
425
+ rr(g, tx, ty, TOG.w, TOG.h, TOG.h / 2); g.fillStyle = gr; g.fill(); g.restore(); g.globalAlpha = hA.a;
426
+ }
427
+ rr(g, tx + 0.5, ty + 0.5, TOG.w - 1, TOG.h - 1, TOG.h / 2); g.strokeStyle = WHITE(0.22); g.lineWidth = 1; g.stroke();
428
+ const kv = knobVis(t);
429
+ if (kv.a > 0.002) {
430
+ const [kx, ky] = knobLocal(t);
431
+ const stretch = 1 + 0.22 * Math.sin(Math.PI * inv(cues.toggle_on, cues.toggle_on + 0.42, t)); // knob elongates mid-slide
432
+ g.save(); g.globalAlpha = hA.a * kv.a; g.translate(kx + 220, ky + 125 + hA.dy); g.scale(stretch * kv.s, kv.s);
433
+ g.shadowColor = 'rgba(10,5,40,0.35)'; g.shadowBlur = 8; g.shadowOffsetY = 2;
434
+ g.fillStyle = '#FFFFFF'; g.beginPath(); g.arc(0, 0, KNOB_R, 0, TAU); g.fill(); g.restore(); g.globalAlpha = hA.a;
435
+ }
436
+ // divider
437
+ g.fillStyle = WHITE(0.1); g.fillRect(P0, 139 + hA.dy, c.w - 2 * P0, 1);
438
+ // row 2: Glow slider
439
+ const y2 = 190 + hA.dy;
440
+ g.save(); g.translate(P0 + 14, y2 - 6); g.strokeStyle = WHITE(0.92); g.lineWidth = 2; g.lineCap = 'round';
441
+ g.beginPath(); g.arc(0, 0, 6, 0, TAU); g.stroke();
442
+ for (let i = 0; i < 8; i++) { const a = i * TAU / 8; g.beginPath(); g.moveTo(Math.cos(a) * 10, Math.sin(a) * 10); g.lineTo(Math.cos(a) * 13, Math.sin(a) * 13); g.stroke(); }
443
+ g.restore();
444
+ font(g, 500, 21); g.fillStyle = WHITE(0.95); g.fillText(V5 ? UI.glow : 'Glow', P0 + 42, y2);
445
+ const sx0 = 190, sx1 = c.w - P0, sy = y2 - 7;
446
+ const v = lerp(0.3, 0.74, eInOutCubic(inv(cues.slider[0], cues.slider[1], t)));
447
+ rr(g, sx0, sy - 2, sx1 - sx0, 4, 2); g.fillStyle = WHITE(0.18); g.fill();
448
+ const gr2 = g.createLinearGradient(sx0, 0, sx1, 0); gr2.addColorStop(0, '#7A5CFF'); gr2.addColorStop(1, '#43D3F2');
449
+ rr(g, sx0, sy - 2, (sx1 - sx0) * v, 4, 2); g.fillStyle = gr2; g.fill();
450
+ g.save(); g.shadowColor = 'rgba(10,5,40,0.35)'; g.shadowBlur = 6; g.shadowOffsetY = 1;
451
+ g.fillStyle = '#fff'; g.beginPath(); g.arc(sx0 + (sx1 - sx0) * v, sy, 10, 0, TAU); g.fill(); g.restore();
452
+ font(g, 400, 15); g.fillStyle = WHITE(0.5); g.textAlign = 'right'; g.fillText(`${Math.round(v * 100)}%`, sx1, y2 + 22); g.textAlign = 'left';
453
+ g.globalAlpha = 1;
454
+ }
455
+
456
+ const exitA = hermite([{ t: 6.92, v: [0], m: [0] }, { t: cues.a_hold[0], v: [0.22], m: [0.08] }, { t: cues.a_hold[1], v: [0.27], m: [0.25] },
457
+ { t: 7.98, v: [0.52], m: [2.0] }, { t: 8.4, v: [1.0], m: [0] }]);
458
+ const CARDS = [
459
+ makeCard({ id: 'A', w: 660, h: 470, r: 42, base: [1090, 500, 0], rot: [2, 6.5, 0], tin: cues.cardA_in, tout: 6.92, pdur: 1.5, exitFn: (t) => exitA(t)[0], fade: [cues.a_clear - 0.03, cues.a_clear + 0.45], dfT: [7.8, 8.2], cA: [6.9, 7.35, 1.0], drift: [920, -30, -560], prot: [2, -16], gd: 0.0, draw: drawChat, ph: 0.0 }),
460
+ makeCard({ id: 'B', w: 400, h: 200, r: 38, base: [q('bx', 752), 292, 230], rot: [4, -10, 0], tin: cues.cardB_in, tout: cues.part[0], pdur: 1.65, drift: [-640, -400, -520], prot: [-8, 14], gd: 0.22, draw: drawVoice, ph: 1.7 }),
461
+ makeCard({ id: 'C', w: 440, h: 250, r: 40, base: [652, 800, -120], rot: [-4, -9, 0], tin: cues.cardC_in, tout: cues.part[1], pdur: 1.65, drift: [-440, 330, -460], prot: [10, 14], gd: 0.14, draw: drawModes, ph: 3.1 }),
462
+ ];
463
+
464
+ function cardState(c, t, lens) {
465
+ const k = inv(c.tin, c.tin + 1.9, t);
466
+ const e = eOutQuint(k);
467
+ // exit: the glass parts like a curtain - each pane slides out along its own axis and toward the lens/camera
468
+ const kp = inv(c.tout, c.tout + c.pdur, t);
469
+ const eo = c.exitFn ? c.exitFn(t) : eInOutCubic(kp);
470
+ // arrival: from deep z, below, tilted back; damped overshoot on the tilt (follow-through)
471
+ const tau = Math.max(0, t - c.tin);
472
+ const tilt = 20 * Math.exp(-3.2 * tau) * Math.cos(3.6 * tau);
473
+ const fl = [Math.sin(TAU * t / 5.4 + c.ph) * 5, Math.sin(TAU * t / 6.3 + c.ph * 1.3) * 0.9, Math.sin(TAU * t / 7.7 + c.ph * 0.7) * 1.1];
474
+ let x = c.base[0] + c.drift[0] * eo;
475
+ let y = c.base[1] + 70 * (1 - e) + fl[0] + c.drift[1] * eo;
476
+ let z = c.base[2] + 560 * (1 - e) + c.drift[2] * eo;
477
+ let rx = c.rot[0] + (k > 0 ? tilt : 20) + fl[1] + c.prot[0] * eo;
478
+ let ry = c.rot[1] + fl[2] + c.prot[1] * eo;
479
+ // slow orbit of the whole cluster (true parallax + changing perspective on the glass)
480
+ const yaw = lerp(8, 0, eOutCubic(inv(-0.8, 5.6, t))) - 2.5 * eSine(inv(5.6, 10.5, t));
481
+ const pitch = lerp(-4, 0, eOutCubic(inv(-0.8, 5.6, t)));
482
+ {
483
+ const cy = Math.cos(yaw * D2R), sy = Math.sin(yaw * D2R), cp = Math.cos(pitch * D2R), sp = Math.sin(pitch * D2R);
484
+ let dx = x - 960, dy = y - 540, dz = z - 80;
485
+ let nx = cy * dx + sy * dz, nz = -sy * dx + cy * dz;
486
+ let ny = cp * dy - sp * nz; nz = sp * dy + cp * nz;
487
+ x = 960 + nx; y = 540 + ny; z = 80 + nz; ry += yaw; rx -= pitch;
488
+ }
489
+ // the lens "has weight": cards lean slightly away from it as it passes
490
+ if (lens && lens.on > 0) {
491
+ const dx = (lens.c[0] - x) / (c.w * 0.5), dy = (lens.c[1] - y) / (c.h * 0.5);
492
+ const infl = Math.exp(-(dx * dx + dy * dy) * 0.5) * lens.on * seg(t, 5.5, 6.0);
493
+ ry += -2.2 * dx * infl; rx += 2.2 * dy * infl;
494
+ }
495
+ const fd = c.fade || [c.tout + c.pdur * 0.82, c.tout + c.pdur];
496
+ const op = eSine(inv(c.tin, c.tin + 0.8, t)) * (1 - seg(t, fd[0], fd[1]));
497
+ const ca = c.cA || [c.tout - 0.05, c.tout + 0.7, 0.75];
498
+ const contentA = 1 - ca[2] * eSine(inv(ca[0], ca[1], t));
499
+ const dfk = c.dfT ? eSine(inv(c.dfT[0], c.dfT[1], t)) : eSine(inv(c.tout, c.tout + c.pdur * 0.7, t)); // defocus leads the travel (the pane leaves the focal plane first)
500
+ const lod = 3.2 * (1 - eOutCubic(inv(c.tin, c.tin + 1.1, t))) + 3.2 * dfk;
501
+ // catch-light: the glass catches the key light as it tilts into place, and again when the drop's light wave passes
502
+ let glint = [0, 0];
503
+ const g1 = inv(c.tin + 0.3, c.tin + 1.7, t), g2 = inv(cues.drop + 0.05 + c.gd, cues.drop + 1.05 + c.gd, t);
504
+ if (g1 > 0 && g1 < 1) glint = [lerp(-1.9, 1.9, eSine(g1)), 0.11 * Math.sin(Math.PI * g1)];
505
+ else if (g2 > 0 && g2 < 1) glint = [lerp(-1.9, 1.9, eSine(g2)), 0.085 * Math.sin(Math.PI * g2)];
506
+ const defocus = 0.5 + 16 * dfk; // px: the pane passes through the focal plane
507
+ const frost = 1;
508
+ const R = rotM(rx, ry, 0);
509
+ const edge = c.id === 'A' ? 1 + 0.25 * seg(t, 0.0, 0.4) * (1 - seg(t, 1.3, 2.1)) : 1; // the hook: bevel catch-light
510
+ // r2 hook event: a catch-light runs round A's top-right bevel radius over 0.05-0.5 s (s: local px along the rim)
511
+ const kc = inv(0.05, 0.5, t);
512
+ const catchL = c.id === 'A' && kc > 0 && kc < 1 ? [lerp(-90, c.r * 1.5708 + 80, eSine(kc)), 1.0 * Math.sin(Math.PI * kc)] : [0, 0];
513
+ return { C: [x, y, z], ...R, op, contentA, lod, frost, defocus, part: eo, glint, edge, catchL, sheen: -0.7 + 1.2 * inv(-1, 10, t) + (rx - c.rot[0]) * 0.07 + (ry - c.rot[1]) * 0.05 };
514
+ }
515
+
516
+ // projection of a card-local point (relative to the card centre, y down) -> [x, y, scale]
517
+ function cardLocalScreen(ci, t, lx, ly) {
518
+ const st = cardState(CARDS[ci], t, null);
519
+ const Pw = [0, 1, 2].map((i) => st.C[i] + st.U[i] * lx + st.V[i] * ly);
520
+ return project(Pw, camO(camera(t)));
521
+ }
522
+ // knob screen position (projection of card C's knob) — lens birth point
523
+ function knobScreen(t) { const [lx, ly] = knobLocal(t); return cardLocalScreen(2, t, lx, ly); }
524
+
525
+ // ------------------------------------------------------------------ wordmark metrics (the lens becomes its "o")
526
+ const WORD = 'Aurora';
527
+ const wordCv = document.createElement('canvas'); wordCv.width = RW; wordCv.height = RH;
528
+ const wg = wordCv.getContext('2d');
529
+ const wordTex = canvasTex(false);
530
+ // v5.1: the four title glyphs go to their own mask canvas (premultiplied white, g = wave catch); FS_GLYPH turns them to glass
531
+ const glyphCv = V5 ? document.createElement('canvas') : null, gg = V5 ? glyphCv.getContext('2d') : null, glyphTex = V5 ? canvasTex(false) : null;
532
+ if (V5) { glyphCv.width = RW; glyphCv.height = RH; }
533
+ const WORD_SIZE = 178, WORD_BASE = 596, WORD_TRACK = 4;
534
+ wg.font = `200 ${WORD_SIZE}px ${FONT_WORD}`; wg.letterSpacing = `${WORD_TRACK}px`;
535
+ const wordW = wg.measureText(WORD).width - WORD_TRACK;
536
+ const WX0 = 960 - wordW / 2;
537
+ // optical kerning: close the open A-u diagonal, give the (rounder, larger) glass "o" room before "ra"; recentred
538
+ const KERN = [8, 0, 0, 0, 6, 6];
539
+ const LETTER_X = [...WORD].map((_, i) => WX0 + wg.measureText(WORD.slice(0, i)).width + KERN[i] - (KERN[0] + KERN[5]) / 2);
540
+ wg.letterSpacing = '0px';
541
+ const oM = wg.measureText('o');
542
+ // v5: 「玻璃拟态。」 — HarmonyOS Sans SC Light, the four glyphs centred (the 句号 hangs, 标点悬挂); the glass ring is the 句号
543
+ const V5L = (() => {
544
+ if (!V5) return null;
545
+ wg.font = `${V5T.w} ${V5T.size}px ${FONT_CN}`;
546
+ const ch = [...V5T.title], adv = ch.map((c) => wg.measureText(c).width);
547
+ const total = adv.reduce((a, b) => a + b, 0) + V5T.track * (ch.length - 1);
548
+ const R = q('v5r', 30), gap = q('v5gap', 0.05) * V5T.size;
549
+ const shift = -q('v5shift', 0.5) * (gap + R); // optical centring: half the hanging 句号 pulls the lockup left
550
+ const x0 = 960 - total / 2 + shift;
551
+ const xs = []; let x = x0; ch.forEach((c, i) => { xs.push(x); x += adv[i] + V5T.track; });
552
+ const m = wg.measureText(V5T.title);
553
+ const ink = { top: V5T.base - m.actualBoundingBoxAscent, bot: V5T.base + m.actualBoundingBoxDescent, l: x0 - m.actualBoundingBoxLeft, r: x0 + m.actualBoundingBoxRight + (ch.length - 1) * V5T.track };
554
+ const cx = x0 + total + gap + R, cy = ink.bot - R - q('v5lift', 2);
555
+ wg.font = `200 ${WORD_SIZE}px ${FONT_WORD}`;
556
+ return { ch, adv, xs, ink, total, R, cx, cy };
557
+ })();
558
+ const O_CX = V5 ? V5L.cx : LETTER_X[3] + (oM.actualBoundingBoxRight - oM.actualBoundingBoxLeft) / 2 + (-oM.actualBoundingBoxLeft);
559
+ const O_CY = V5 ? V5L.cy : WORD_BASE - (oM.actualBoundingBoxAscent - oM.actualBoundingBoxDescent) / 2;
560
+ const O_R = V5 ? V5L.R : (oM.actualBoundingBoxAscent + oM.actualBoundingBoxDescent) / 2 + 1.5;
561
+ // letters ripple outward from the glass "o"
562
+ // r2: when the 9.0 refraction wave (r = 700 * outSine(k), 0.9 s) reaches each letter's centre
563
+ const LETTER_CATCH = [...WORD].map((ch, i) => { const w = wg.measureText(ch).width; const d = Math.hypot(LETTER_X[i] + w / 2 - O_CX, WORD_BASE - 60 - O_CY);
564
+ return cues.wave + 0.9 * Math.asin(Math.min(1, d / 700)) * 2 / Math.PI; });
565
+ const LETTER_T = [0.3, 0.15, 0.0, null, 0.02, 0.17].map((d) => d === null ? null : cues.word + d);
566
+ // v5: the four glyphs ripple outward from the centre; each catches the 9.0 wave as it reaches it from the 句号
567
+ const V5_T = V5 ? [0.13, 0.0, 0.02, 0.15].map((d) => cues.word + d) : null;
568
+ const V5_RISE = 0.95, V5_SWEEP = [8.0, 8.8]; // (internal time) glyph rise; specular sweep once pane A has cleared
569
+ const V5_CATCH = V5 ? V5L.xs.map((x, i) => { const d = Math.hypot(x + V5L.adv[i] / 2 - O_CX, (V5L.ink.top + V5L.ink.bot) / 2 - O_CY);
570
+ return cues.wave + 0.9 * Math.asin(Math.min(1, d / 700)) * 2 / Math.PI; }) : null;
571
+
572
+ // ------------------------------------------------------------------ lens choreography
573
+ const KN_T = cues.bead;
574
+ const SETTLE = cues.orb_settle;
575
+ // 1) the bead rises out of the toggle (card C local offset from the knob)
576
+ // first 0.3 s through clear aurora (right, off the chip), then up into the reply
577
+ const beadRise = hermite([{ t: KN_T, v: [0, 0], m: [150, -6] }, { t: 5.1, v: [q('b1x', 42), q('b1y', -5)] }, { t: cues.snap, v: [q('b2x', 100), q('b2y', -12)] }, { t: 6.0, v: [170, -190] }]);
578
+ // 2) ...and is caught by the reply: it glides along "when the light turns gold." (card A local, from the card centre)
579
+ const G0 = [q('g0x', -150), q('g0y', 36)], G1 = [q('g1x', -40), q('g1y', 40)];
580
+ const glideA = hermite([{ t: 5.3, v: [G0[0] - 70, G0[1] + 40] }, { t: 5.95, v: G0 }, { t: cues.lens_free, v: G1 }]);
581
+ function lensOnUI(t) {
582
+ if (t <= KN_T) { const k = knobScreen(t); return [k[0], k[1]]; }
583
+ const [kx, ky] = knobLocal(t), r = beadRise(t);
584
+ const pc = cardLocalScreen(2, t, kx + r[0], ky + r[1]);
585
+ const g = glideA(t), pa = cardLocalScreen(0, t, g[0], g[1]);
586
+ const w = eSine(inv(5.3, 5.95, t));
587
+ return [lerp(pc[0], pa[0], w), lerp(pc[1], pa[1], w)];
588
+ }
589
+ // 3) free flight: the glass leaves the parting UI and settles as the "o" of the wordmark (screen space, C1-continuous)
590
+ const T_FREE = cues.lens_free;
591
+ const P_FREE = lensOnUI(T_FREE);
592
+ const V_FREE = (() => { const a = lensOnUI(T_FREE - 0.01); return [(P_FREE[0] - a[0]) / 0.01, (P_FREE[1] - a[1]) / 0.01]; })();
593
+ const lensFree = hermite([
594
+ { t: T_FREE, v: P_FREE, m: V_FREE },
595
+ { t: SETTLE, v: [O_CX, O_CY], m: [0, 0] },
596
+ ]);
597
+ const lensHX = keysS([[5.95, 200], [6.45, 224], [T_FREE, 190], [7.7, 112], [SETTLE, O_R]], eSine); // r2: blooms toward the viewer
598
+ const lensHY = keysS([[5.95, 110], [6.45, 122], [T_FREE, 106], [7.7, 88], [SETTLE, O_R]], eSine);
599
+ const lensMag = keysS([[4.62, 1.0], [5.1, 1.14], [6.0, 1.36], [7.0, 1.32], [7.6, 1.26], [SETTLE, 1.2]], eSine);
600
+ function lensCentreRaw(t) { return t < T_FREE ? lensOnUI(t) : lensFree(t); }
601
+ // damped-spring squash & stretch driven by velocity (integrated from a fixed start → pure in t)
602
+ function lensStretch(t) {
603
+ const t0 = 4.9, dt = 1 / 240; let sx = 0, sy = 0, vx = 0, vy = 0;
604
+ const w = TAU * 2.0, z = 0.3;
605
+ const n = Math.max(0, Math.floor((t - t0) / dt));
606
+ let prev = lensCentreRaw(t0);
607
+ for (let i = 1; i <= n; i++) {
608
+ const tt = t0 + i * dt; const cur = lensCentreRaw(tt);
609
+ const tvx = (cur[0] - prev[0]) / dt * 0.00016, tvy = (cur[1] - prev[1]) / dt * 0.00016; prev = cur;
610
+ const ax = -w * w * (sx - tvx) - 2 * z * w * vx, ay = -w * w * (sy - tvy) - 2 * z * w * vy;
611
+ vx += ax * dt; vy += ay * dt; sx += vx * dt; sy += vy * dt;
612
+ }
613
+ return [sx, sy];
614
+ }
615
+ function lensState(t) {
616
+ const on = seg(t, cues.knob_glass - 0.08, cues.knob_glass + 0.08);
617
+ if (on <= 0) return { on: 0 };
618
+ const kn = knobScreen(t);
619
+ const kr = KNOB_R * kn[2];
620
+ const c0 = lensCentreRaw(t), ws = wordScale(t);
621
+ const c = [960 + (c0[0] - 960) * ws, WC_Y + (c0[1] - WC_Y) * ws];
622
+ // anticipation: the glass knob swells 8 % and sinks 4 px (6 frames) before the bead pulls out
623
+ const ant = seg(t, cues.bead - 0.2, cues.bead) * (1 - seg(t, cues.bead, cues.bead + 0.25));
624
+ c[1] += 4 * kn[2] * ant;
625
+ const press = 1.14 * (1 + 0.08 * seg(t, cues.bead - 0.2, cues.bead));
626
+ let hx, hy;
627
+ if (t < KN_T) { // the knob itself has turned to glass, 'held' with a soft wobble
628
+ const wob = Math.sin(TAU * 3.0 * (t - cues.knob_glass)) * Math.exp(-4 * Math.max(0, t - cues.knob_glass)) * 0.07;
629
+ hx = kr * press * (1 + wob); hy = kr * press * (1 - wob);
630
+ } else if (t < cues.snap) { // a bead of glass pulls away from the knob (0.65 s neck)
631
+ const k = eSine(inv(KN_T, cues.snap, t)); hx = hy = kr * press * lerp(1, 2.0, k);
632
+ } else if (t < 5.95) { // snap: a 3-frame overshoot wobble, then it swells into the capsule lens
633
+ const k = eSine(inv(cues.snap, 5.95, t)); const r0 = kr * press * 2.0;
634
+ const dt = t - cues.snap, wob = Math.sin(TAU * 7.5 * dt) * Math.exp(-14 * dt) * 0.12;
635
+ hx = lerp(r0, 200, k) * (1 - wob); hy = lerp(r0, 110, k) * (1 + wob);
636
+ } else { hx = lensHX(t); hy = lensHY(t); }
637
+ // squash & stretch
638
+ const [sx, sy] = lensStretch(t);
639
+ const amt = clamp(Math.hypot(sx, sy) * 0.75, 0, 0.1);
640
+ const ang = Math.atan2(sy, sx);
641
+ const a = 1 + amt, b = 1 / Math.sqrt(1 + amt * 1.4);
642
+ const ca = Math.cos(ang), sa = Math.sin(ang);
643
+ const i11 = ca * ca / a + sa * sa / b, i12 = ca * sa * (1 / a - 1 / b), i22 = sa * sa / a + ca * ca / b;
644
+ // breathing at rest
645
+ const br = 1 + 0.018 * Math.sin(TAU * (t - SETTLE - 0.35) / 2.2) * seg(t, SETTLE + 0.2, SETTLE + 0.9);
646
+ hx *= br * ws; hy *= br * ws;
647
+ // bead: the part of the knob that stays in the track (metaball neck while separating)
648
+ const sep = seg(t, KN_T, cues.snap);
649
+ const beadR = t < 5.9 ? kr * lerp(1.14, 1.0, sep) : 0;
650
+ const beadA = 1 - seg(t, 5.5, 5.85);
651
+ const beadK = t < 5.9 ? q('neck', 40) * kn[2] * (1 - seg(t, cues.snap - 0.16, cues.snap + 0.01)) + 0.6 : 0;
652
+ const lift = seg(t, KN_T, 5.6) * (1 - 0.9 * seg(t, 7.6, SETTLE));
653
+ const mag = lensMag(t);
654
+ let bezel = Math.min(Math.min(hx, hy) * 0.55, q('bzmax', 60));
655
+ // the drop opens into a ring: an annulus the weight of a Sora stroke (iris opening from the centre)
656
+ const rk = eSine(inv(cues.ring_open[0], cues.ring_open[1], t));
657
+ const RW_END = q('ringw', V5 ? 14 : 13) * ws; // v5.1: a heavier 句号 ring to sit with the Black glass glyphs
658
+ const ringW = rk > 0 ? lerp(Math.min(hx, hy) * 1.1, RW_END, rk) : 0;
659
+ if (ringW > 0) bezel = Math.min(bezel, ringW * 0.5);
660
+ // the light turns gold inside the glass (and a trace of it stays in the counter of the "o")
661
+ // r2s3: sharp attack - ~50 % of the gold within 3 frames of the drop, then the slow build to the 6.45 peak
662
+ const warm = Math.max(0.5 * seg(t, cues.gold[0] - 0.01, cues.gold[0] + 0.09, eOutCubic), seg(t, cues.gold[0], cues.gold[1])) * (1 - 0.88 * seg(t, cues.gold[1], 7.0));
663
+ const cw = 0.12 * seg(t, 7.9, 8.5) + 0.06 * Math.sin(Math.PI * inv(cues.final_note, cues.final_note + 0.8, t)); // breathes 12 -> 18 % on the E7
664
+ const kg = inv(cues.ring_glint[0], cues.ring_glint[1], t);
665
+ let rgl = [lerp(-2.5, 0.9, eOutSine(kg)), kg > 0 && kg < 1 ? 1.0 * Math.sin(Math.PI * kg) : 0];
666
+ // r2s3: on the drop a specular whips round the bezel (6.0 -> 6.2) and the rim flashes (attack 2 frames, decay ~8)
667
+ const kd = inv(cues.drop - 0.015, cues.drop + 0.2, t);
668
+ if (kd > 0 && kd < 1) rgl = [lerp(-2.3, 2.9, eOutCubic(kd)), 1.1 * Math.pow(Math.sin(Math.PI * Math.pow(kd, 0.55)), 1.5)];
669
+ const flash = seg(t, cues.drop - 0.02, cues.drop + 0.04, eOutSine) * (1 - seg(t, cues.drop + 0.05, cues.drop + 0.32));
670
+ const kw = inv(cues.wave, cues.wave + 0.9, t);
671
+ const wk = kw > 0 && kw < 1 ? seg(kw, 0, 0.06) : 0; // r2: a refraction wave - bends each letter ~2.5 px, light on its crest
672
+ const wave = [700 * eOutSine(kw), 0.11 * Math.pow(1 - kw, 0.8) * wk, 6.5 * Math.pow(1 - kw, 0.45) * wk];
673
+ return { on, c, hx, hy, r: Math.min(hx, hy), M: [i11, i12, i12, i22], bead: [kn[0], kn[1], beadR], beadK, beadA, lift, mag, bezel, ringW, warm, cw, rgl, wave, flash };
674
+ }
675
+
676
+ // the end card keeps breathing: a very slow push on the lockup (the glass "o" rides the same transform)
677
+ const WC_Y = V5 ? 560 : 590;
678
+ function wordScale(t) { return 1 + 0.022 * eSine(inv(7.2, 10.4, t)); }
679
+ function drawWord(t) {
680
+ const g = wg; g.setTransform(DPR, 0, 0, DPR, 0, 0); g.clearRect(0, 0, W, H);
681
+ const ws = wordScale(t); g.setTransform(DPR * ws, 0, 0, DPR * ws, DPR * 960 * (1 - ws), DPR * WC_Y * (1 - ws));
682
+ if (t < cues.word - 0.05) return false;
683
+ g.textBaseline = 'alphabetic';
684
+ if (V5) { gg.setTransform(DPR, 0, 0, DPR, 0, 0); gg.clearRect(0, 0, W, H); gg.setTransform(DPR * ws, 0, 0, DPR * ws, DPR * 960 * (1 - ws), DPR * WC_Y * (1 - ws));
685
+ drawTitleV5(g, t); return true; }
686
+ g.font = `200 ${WORD_SIZE}px ${FONT_WORD}`; g.letterSpacing = '0px';
687
+ for (let i = 0; i < WORD.length; i++) {
688
+ if (LETTER_T[i] === null) continue; // the "o" is the glass lens
689
+ const r = riseA(t, LETTER_T[i], 0.95);
690
+ if (r.a <= 0) continue;
691
+ g.globalAlpha = r.a; g.fillStyle = '#FFFFFF';
692
+ const ck = Math.exp(-Math.pow((t - LETTER_CATCH[i]) / 0.055, 2));
693
+ if (ck > 0.01) { g.shadowColor = `rgba(255,246,236,${0.9 * ck})`; g.shadowBlur = 22 * DPR; }
694
+ g.fillText(WORD[i], LETTER_X[i], WORD_BASE + r.dy);
695
+ if (ck > 0.01) { g.globalCompositeOperation = 'lighter'; g.globalAlpha = r.a * 0.35 * ck; g.fillText(WORD[i], LETTER_X[i], WORD_BASE + r.dy);
696
+ g.globalCompositeOperation = 'source-over'; g.shadowBlur = 0; g.shadowColor = 'rgba(0,0,0,0)'; }
697
+ }
698
+ // tagline: English | Chinese
699
+ const tg = riseA(t, cues.tagline, 0.95);
700
+ if (tg.a > 0) {
701
+ g.globalAlpha = tg.a;
702
+ const y = 684 + tg.dy;
703
+ g.font = `300 29px ${FONT_WORD}`; g.letterSpacing = '0.5px';
704
+ const en = 'Think in light.'; const ew = g.measureText(en).width;
705
+ // CJK line set in full cells: the native full-width comma keeps its own em (standard 全角 setting)
706
+ g.font = `300 28px ${FONT_CN}`; g.letterSpacing = '0px';
707
+ const TR = 2.5, adv = (c) => g.measureText(c).width;
708
+ const cn = []; let px = 0;
709
+ for (const ch of '思考,自有光') { cn.push([ch, px]); px += adv(ch) + TR; }
710
+ const cw = px - TR;
711
+ const gap = 28, total = ew + gap * 2 + 1 + cw, x0 = 960 - total / 2;
712
+ g.font = `300 29px ${FONT_WORD}`; g.letterSpacing = '0.5px'; g.fillStyle = WHITE(0.88); g.fillText(en, x0, y);
713
+ g.fillStyle = WHITE(0.32); g.fillRect(Math.round(x0 + ew + gap), y - 23, 1, 27);
714
+ g.font = `300 28px ${FONT_CN}`; g.letterSpacing = '0px'; g.fillStyle = WHITE(0.88);
715
+ const cx0 = x0 + ew + gap * 2 + 1; for (const [c, dx] of cn) g.fillText(c, cx0 + dx, y - 1);
716
+ }
717
+ g.globalAlpha = 1; g.letterSpacing = '0px';
718
+ return true;
719
+ }
720
+
721
+ // v5 end card: 「玻璃拟态。」 big (the glass ring is the 句号, drawn by the lens pass) + 弥散渐变 | AURORA · GLASS
722
+ // v5.1: the glyphs are drawn as a MASK into glyphCv (FS_GLYPH makes them frosted glass); line 2 stays flat white in wordCv
723
+ let GLYPHS_ON = false;
724
+ function drawTitleV5(g, t) {
725
+ GLYPHS_ON = false;
726
+ if (!HIDE_TITLE) {
727
+ gg.font = `${V5T.w} ${V5T.size}px ${FONT_CN}`; gg.letterSpacing = '0px'; gg.textBaseline = 'alphabetic';
728
+ V5L.ch.forEach((c, i) => {
729
+ const r = riseA(t, V5_T[i], V5_RISE);
730
+ if (r.a <= 0) return;
731
+ GLYPHS_ON = true;
732
+ const ck = Math.exp(-Math.pow((t - V5_CATCH[i]) / 0.055, 2));
733
+ gg.globalAlpha = r.a; gg.fillStyle = `rgb(255,${Math.round(255 * ck)},255)`;
734
+ gg.fillText(c, V5L.xs[i], V5T.base + r.dy * 1.6);
735
+ });
736
+ gg.globalAlpha = 1;
737
+ }
738
+ const tg = riseA(t, cues.a_clear - 0.13, 0.6); // v5.1: 弥散渐变 rises as pane A clears (internal 8.02 = source 7.52), full by source ~8.17
739
+ if (tg.a > 0) {
740
+ g.globalAlpha = tg.a;
741
+ const y = V5T.subBase + tg.dy;
742
+ const fs = `${V5T.subW} ${V5T.subSize}px ${FONT_CN}`, fe = `${V5T.subW} ${V5T.enSize}px ${FONT_CN}`;
743
+ g.font = fs; g.letterSpacing = '4px';
744
+ const cw = g.measureText(V5T.sub).width - 4;
745
+ g.font = fe; g.letterSpacing = `${V5T.enTrack}px`;
746
+ const ew = g.measureText(V5T.en).width - V5T.enTrack;
747
+ const gap = 30, total = cw + gap * 2 + 2 + ew, x0 = 960 - total / 2;
748
+ // v5.2: line 2 brighter (0.94/0.4/0.84 -> 0.97/0.55/0.95) and set on a soft deep-indigo shadow, so it reads on the pool
749
+ if (V52) { g.shadowColor = `rgba(6,4,36,${q('l2sh', 0.55)})`; g.shadowBlur = 12 * DPR; g.shadowOffsetY = 2 * DPR; }
750
+ g.font = fs; g.letterSpacing = '4px'; g.fillStyle = WHITE(V52 ? 0.97 : 0.94); g.fillText(V5T.sub, x0, y);
751
+ g.fillStyle = WHITE(V52 ? 0.55 : 0.4); g.fillRect(Math.round(x0 + cw + gap), y - 42, 2, 46);
752
+ g.font = fe; g.letterSpacing = `${V5T.enTrack}px`; g.fillStyle = WHITE(V52 ? 0.95 : 0.84); g.fillText(V5T.en, x0 + cw + gap * 2 + 2, y - 7);
753
+ if (V52) { g.shadowColor = 'rgba(0,0,0,0)'; g.shadowBlur = 0; g.shadowOffsetY = 0; }
754
+ }
755
+ g.globalAlpha = 1; g.letterSpacing = '0px';
756
+ }
757
+ // v5.1: the glass glyph pass (src -> dst). Bevel = narrow full-res blur of the mask; dome + shadow = wide quarter-res blur.
758
+ const TAPS_GN = V5 ? gaussTaps(4.5 * DPR) : null, TAPS_GW = V5 ? gaussTaps(12 * DPR / 4) : null, TAPS_GF = V5 ? gaussTaps(18 * DPR / 4) : null;
759
+ function glassGlyphs(t, src, dst) {
760
+ upload(glyphTex, glyphCv, false);
761
+ let L = use(P.blur, T.g1); bindTex(0, glyphTex, L.uSrc); gl.uniform2f(L.uDir, 1, 0); gl.uniform1fv(L.uOff, TAPS_GN.offs); gl.uniform1fv(L.uW, TAPS_GN.ws); gl.uniform1i(L.uN, TAPS_GN.n); draw();
762
+ L = use(P.blur, T.g2); bindTex(0, T.g1.tex, L.uSrc); gl.uniform2f(L.uDir, 0, 1); gl.uniform1fv(L.uOff, TAPS_GN.offs); gl.uniform1fv(L.uW, TAPS_GN.ws); gl.uniform1i(L.uN, TAPS_GN.n); draw();
763
+ blurInto({ tex: glyphTex, w: RW, h: RH }, T.qL, TAPS_GW);
764
+ blurInto(src, T.qB, TAPS_GF);
765
+ L = use(P.glyph, dst);
766
+ bindTex(0, src.tex, L.uScene); bindTex(1, T.qB.tex, L.uFrost); bindTex(2, glyphTex, L.uMask); bindTex(3, T.g2.tex, L.uMN); bindTex(4, T.qL.tex, L.uMW);
767
+ gl.uniform2f(L.uLight, -0.55, -0.83); gl.uniform1f(L.uRefr, q('grefr', 30)); gl.uniform3f(L.uShadow, 5, 11, q('gshadow', 0.34)); gl.uniform1f(L.uLift, q('glift', V52 ? 0.15 : 0.115));
768
+ // v5.2 (jury r6: the glass was ~1.7:1 on lavender): a deep-indigo pool behind the lockup + a dark halo, a brighter body and
769
+ // rim. The pool fades in with the glyph rise (internal 6.8 -> 8.0 = source ~6.72 -> 7.55, mostly under pane A's frost).
770
+ const ws = wordScale(t), pk = V52 ? q('gpool', 0.85) * seg(t, cues.word - 0.1, cues.word + 1.1) : 0;
771
+ gl.uniform4f(L.uPool, 960 + (985 - 960) * ws, WC_Y + (548 - WC_Y) * ws, q('poolw', 520) * ws, q('poolh', 190) * ws);
772
+ gl.uniform3f(L.uPoolK, pk, 170 * ws, q('poolf', 240));
773
+ gl.uniform3f(L.uHalo, q('ghalo', V52 ? 0.34 : 0), q('gbody', V52 ? 1.16 : 1.08), q('grim', V52 ? 1.35 : 1)); gl.uniform1f(L.uSat, q('gsat', V52 ? 1.42 : 1.3));
774
+ // the glass catches the light once it is clear of the panes: a specular sweep left -> right
775
+ const ks = inv(V5_SWEEP[0], V5_SWEEP[1], t);
776
+ gl.uniform3f(L.uSweep, lerp(-620, 620, eInOutCubic(ks)), 70, ks > 0 && ks < 1 ? 0.42 * Math.sin(Math.PI * ks) : 0);
777
+ draw();
778
+ }
779
+
780
+ // ------------------------------------------------------------------ global light & ripple
781
+ // r2s3: the drop is a light HIT - half the bloom lands in ~2 frames on the 6.01 onset, the rest builds to 6.3
782
+ const gainK0 = keysS([[0, 0.78], [1.3, 1.0], [5.8, 1.0], [5.985, 1.0], [6.055, 1.085], [6.3, 1.17], [7.4, 1.0], [8.3, 1.04], [8.95, 1.03], [9.3, 1.08], [10, 1.06]], eSine);
783
+ // the Glow slider really turns the light up (3.95-4.85)
784
+ const gainK = (t) => gainK0(t) + 0.08 * eInOutCubic(inv(cues.slider[0], cues.slider[1], t));
785
+ const RIP_C = lensCentreRaw(cues.drop);
786
+ function ripple(t) { const k = inv(cues.drop - 0.05, cues.drop + 1.9, t); return [RIP_C[0], RIP_C[1], 60 + 1500 * eOutSine(k), k > 0 ? 1.6 * Math.pow(1 - k, 1.5) * seg(k, 0, 0.035) : 0]; } // r2s3: sharp attack (68 ms)
787
+
788
+ // ------------------------------------------------------------------ render
789
+ window.DEMO = { width: W, height: H, fps: FPS, duration: 10, motionBlur: { samples: 1, shutter: 0.5 } };
790
+
791
+ // reel v5.1 time warp (v5 only): source time t -> internal choreography time u. Identity up to the 6.0 drop; the act-3 build
792
+ // (parting, lens flight, title reveal) then plays ~1.3x so the title is fully readable by ~7.6 (internal 8.05); the hold runs
793
+ // ~0.8x to 10.0. C1-smooth (Hermite), monotone. linkAt uses the same warp.
794
+ const TW = V5 ? (() => {
795
+ const h = hermite([{ t: 6.0, v: [6.0], m: [1] }, { t: 7.6, v: [q('tw76', 8.1)], m: [0.9] }, { t: 10.0, v: [10.0], m: [0.75] }]);
796
+ return (t) => (t <= 6.0 || t >= 10.0 ? t : h(t)[0]);
797
+ })() : (t) => t;
798
+ window.__tw = TW;
799
+ const renderU = async (t) => {
800
+ const frame = Math.round(t * FPS * 4);
801
+ const cam = camera(t); const O = camO(cam);
802
+ const lens = lensState(t);
803
+ const states = CARDS.map((c) => ({ c, s: cardState(c, t, lens) }));
804
+
805
+ // ---- aurora
806
+ let L = use(P.aurora, T.sceneA);
807
+ gl.uniform1f(L.uT, t);
808
+ const blobs = new Float32Array(4 * NB), blobE = new Float32Array(2 * NB), cols = new Float32Array(3 * NB);
809
+ const gain = gainK(t);
810
+ BLOBS.forEach((b, i) => {
811
+ const p = b.path(t);
812
+ // gentle parallax of the light field against the camera
813
+ blobs.set([p[0] - cam.x * 0.35, p[1] - cam.y * 0.35, b.rx, b.i], i * 4); blobE.set([b.ry, b.ang * D2R + 0.08 * Math.sin(t * 0.3 + i)], i * 2);
814
+ cols.set(hex(b.col), i * 3);
815
+ });
816
+ gl.uniform4fv(L.uBlob, blobs); gl.uniform2fv(L.uBlobE, blobE); gl.uniform3fv(L.uBlobC, cols);
817
+ gl.uniform4fv(L.uRipple, ripple(t));
818
+ gl.uniform3fv(L.uBase, hex(PAL.base)); gl.uniform1f(L.uGain, gain); gl.uniform1f(L.uWarp, 150);
819
+ gl.uniform3fv(L.uCamO, O); gl.uniform1f(L.uF, F);
820
+ const SC = [], SU = [], SV = [], SN = [], SH = [];
821
+ states.forEach(({ c, s }) => { SC.push(...s.C); SU.push(...s.U); SV.push(...s.V); SN.push(...s.N); SH.push(c.w / 2, c.h / 2, c.r, NOUI ? 0 : s.op * (1 - s.part)); });
822
+ gl.uniform3fv(L.uSC, SC); gl.uniform3fv(L.uSU, SU); gl.uniform3fv(L.uSV, SV); gl.uniform3fv(L.uSN, SN); gl.uniform4fv(L.uSH, SH);
823
+ gl.uniform1f(L.uMotes, Q.has('nomotes') ? 0 : 1);
824
+ draw();
825
+
826
+ // ---- cards, back to front, each frosting what is already behind it
827
+ let cur = T.sceneA;
828
+ // ---- wordmark + tagline: they live BEHIND the glass, so the parting panes frost them first
829
+ if (!NOUI && drawWord(t)) {
830
+ if (V5 && GLYPHS_ON) { glassGlyphs(t, cur, T.sceneB); cur = T.sceneB; } // v5.1: frosted-glass letters
831
+ upload(wordTex, wordCv, false);
832
+ L = use(P.over, cur); gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
833
+ bindTex(0, wordTex, L.uTex); gl.uniform1f(L.uA, 1); draw(); gl.disable(gl.BLEND);
834
+ }
835
+ const order = states.filter(({ s }) => s.op > 0.001 && !NOUI).sort((a, b) => (b.s.C[2] - a.s.C[2]));
836
+ for (const { c, s } of order) {
837
+ c.draw(c, t); upload(c.tex, c.cv, true);
838
+ const thin = c.id === 'A' ? seg(t, 6.95, 7.4) * (1 - seg(t, 7.9, 8.2)) : 0;
839
+ blurInto(cur, T.qB, thin > 0 ? gaussTaps(lerp(30, 9, thin) * DPR / 4) : TAPS_FROST);
840
+ L = use(P.card, cur);
841
+ gl.enable(gl.BLEND); gl.blendFunc(gl.ONE, gl.ONE_MINUS_SRC_ALPHA);
842
+ gl.uniform3fv(L.uCamO, O); gl.uniform1f(L.uF, F);
843
+ gl.uniform3fv(L.uC, s.C); gl.uniform3fv(L.uU, s.U); gl.uniform3fv(L.uV, s.V); gl.uniform3fv(L.uN, s.N);
844
+ gl.uniform2f(L.uHalf, c.w / 2, c.h / 2); gl.uniform1f(L.uRad, c.r);
845
+ gl.uniform1f(L.uOpacity, s.op); gl.uniform1f(L.uDefocus, s.defocus); gl.uniform1f(L.uContentA, s.contentA); gl.uniform1f(L.uLod, s.lod - 0.35); gl.uniform1f(L.uFrost, s.frost);
846
+ gl.uniform2f(L.uLight, -0.55, -0.83); gl.uniform1f(L.uSheen, s.sheen); gl.uniform2f(L.uGlint, s.glint[0], s.glint[1]); gl.uniform1f(L.uEdge, s.edge); gl.uniform2f(L.uCatch, s.catchL[0], s.catchL[1]);
847
+ bindTex(0, T.qB.tex, L.uBlurTex); bindTex(1, c.tex, L.uContent);
848
+ draw();
849
+ gl.disable(gl.BLEND);
850
+ }
851
+
852
+ // ---- Liquid Glass lens
853
+ blurInto(cur, T.qL, TAPS_LENS);
854
+ L = use(P.lens, T.lens);
855
+ bindTex(0, cur.tex, L.uScene); bindTex(1, T.qL.tex, L.uBlur);
856
+ gl.uniform1f(L.uOn, NOUI ? 0 : (lens.on || 0));
857
+ if (lens.on > 0) {
858
+ gl.uniform2f(L.uLC, lens.c[0], lens.c[1]); gl.uniform2f(L.uLH, lens.hx, lens.hy); gl.uniform1f(L.uLR, lens.r);
859
+ gl.uniformMatrix2fv(L.uLM, false, new Float32Array(lens.M));
860
+ gl.uniform3f(L.uBead, lens.bead[0], lens.bead[1], lens.bead[2]); gl.uniform1f(L.uBeadK, lens.beadK); gl.uniform1f(L.uBeadA, lens.beadA);
861
+ gl.uniform1f(L.uBezel, lens.bezel); gl.uniform1f(L.uDepth, lens.bezel * 0.9);
862
+ gl.uniform1f(L.uIor, 1.5); gl.uniform1f(L.uDisp, q('disp', 0.25)); gl.uniform1f(L.uMag, lens.mag);
863
+ gl.uniform1f(L.uRefrSign, +(Q.get('rsign') || -1)); gl.uniform1f(L.uMaxDisp, lens.bezel * +(Q.get('maxd') || 0.85)); gl.uniform1f(L.uRimGlow, lerp(1, V52 ? 2.1 : 1.6, seg(t, 7.6, SETTLE))); gl.uniform1f(L.uRing, +(Q.get('ring') || (V52 ? 0.4 : 0.2)) * seg(t, 7.8, SETTLE + 0.2) + 0.9 * lens.flash); // v5.2: the 句号 ring's lit edge a touch brighter on the deeper pool
864
+ if (L.uHide) gl.uniform1f(L.uHide, HIDE_DOT ? 1 : 0);
865
+ gl.uniform1f(L.uRingW, lens.ringW); gl.uniform1f(L.uWarm, lens.warm); gl.uniform1f(L.uCW, lens.cw);
866
+ gl.uniform2f(L.uRGl, lens.rgl[0], lens.rgl[1]); gl.uniform3f(L.uWave, lens.wave[0], lens.wave[1], lens.wave[2]); gl.uniform1f(L.uCaustic, 0.22 * lens.lift);
867
+ gl.uniform1f(L.uLift, lens.lift); gl.uniform1f(L.uGlint, t < 8 ? lerp(-1.8, 1.8, inv(5.5, 6.7, t)) : lerp(-1.8, 1.8, inv(8.7, 9.9, t)));
868
+ gl.uniform3f(L.uLight3, -0.5, -0.78, 0.62); gl.uniform1f(L.uFrostRim, q('frim', 0.1)); gl.uniform1f(L.uFres, q('fres', 0.25)); gl.uniform1f(L.uBzExp, q('bzexp', 2.5));
869
+ }
870
+ draw();
871
+
872
+ // ---- bloom + final
873
+ blurInto(T.lens, T.qB, TAPS_BLOOM, 0.62);
874
+ L = use(P.final, null);
875
+ bindTex(0, T.lens.tex, L.uSrc); bindTex(1, T.qB.tex, L.uBloom); bindTex(2, grainTex, L.uGrain);
876
+ gl.uniform1f(L.uBloomAmt, 0.22); gl.uniform1f(L.uGrainAmt, +(Q.get('grain') || 0.14)); gl.uniform1f(L.uFrame, frame); gl.uniform1f(L.uVig, 0.18);
877
+ gl.uniform1f(L.uFade, 1);
878
+ draw();
879
+ gl.finish();
880
+ };
881
+ window.renderAt = V5 ? async (t) => renderU(TW(t)) : renderU;
882
+
883
+ // ------------------------------------------------------------------ reel v5: link elements (pure in t, 1920x1080 frame px)
884
+ if (V5) {
885
+ const r1 = (v) => Math.round(v * 10) / 10;
886
+ const toFrame = (x, y, ws) => [960 + (x - 960) * ws, WC_Y + (y - WC_Y) * ws];
887
+ function cardRect(ci, t, lens, id, note) {
888
+ const c = CARDS[ci], st = cardState(c, t, lens);
889
+ if (st.op <= 0.01) return null;
890
+ const O = camO(camera(t)), hw = c.w / 2, hh = c.h / 2;
891
+ const P = [[-hw, -hh], [hw, -hh], [hw, hh], [-hw, hh]].map(([lx, ly]) => project([0, 1, 2].map((i) => st.C[i] + st.U[i] * lx + st.V[i] * ly), O));
892
+ const xs = P.map((p) => p[0]), ys = P.map((p) => p[1]);
893
+ const x0 = Math.min(...xs), x1 = Math.max(...xs), y0 = Math.min(...ys), y1 = Math.max(...ys);
894
+ const rot = Math.atan2(P[1][1] - P[0][1], P[1][0] - P[0][0]) / D2R;
895
+ return { id, type: 'rect', x: r1((x0 + x1) / 2), y: r1((y0 + y1) / 2), w: r1(x1 - x0), h: r1(y1 - y0), rot: r1(rot), a: Math.round(st.op * 1000) / 1000,
896
+ look: 'glass', fill: '#8E93F5', stroke: '#FFFFFF', sw: 1, corners: P.map((p) => [r1(p[0]), r1(p[1])]), note };
897
+ }
898
+ // the visible outline of the glass is the shader's outerSDF: capsule smooth-min'ed with the bead (metaball). While the bead
899
+ // is attached (4.62-5.9) the smin inflates the shape by up to beadK/4, so the radius is measured on the same SDF (FS_LENS outerSDF).
900
+ function outerSDFjs(L, px, py) {
901
+ const dx = px - L.c[0], dy = py - L.c[1];
902
+ const qx = L.M[0] * dx + L.M[2] * dy, qy = L.M[1] * dx + L.M[3] * dy;
903
+ const rr = Math.min(L.r, L.hx, L.hy), ax = Math.abs(qx) - L.hx + rr, ay = Math.abs(qy) - L.hy + rr;
904
+ let d = Math.hypot(Math.max(ax, 0), Math.max(ay, 0)) + Math.min(Math.max(ax, ay), 0) - rr;
905
+ if (L.bead[2] > 0) {
906
+ const d2 = Math.hypot(px - L.bead[0], py - L.bead[1]) - L.bead[2];
907
+ if (L.beadK > 0.5) { const h = Math.max(L.beadK - Math.abs(d - d2), 0) / L.beadK; d = Math.min(d, d2) - h * h * L.beadK * 0.25; } else d = Math.min(d, d2);
908
+ }
909
+ return d;
910
+ }
911
+ function visR(L) {
912
+ const bx = L.c[0] - L.bead[0], by = L.c[1] - L.bead[1], bl = Math.hypot(bx, by);
913
+ const a0 = bl > 1 ? Math.atan2(by, bx) : -Math.PI / 2;
914
+ const rs = [0, Math.PI / 2, -Math.PI / 2].map((da) => {
915
+ const ux = Math.cos(a0 + da), uy = Math.sin(a0 + da); let lo = 0, hi = 4 * Math.max(L.hx, L.hy) + L.beadK;
916
+ for (let i = 0; i < 40; i++) { const m = (lo + hi) / 2; if (outerSDFjs(L, L.c[0] + ux * m, L.c[1] + uy * m) < 0) lo = m; else hi = m; }
917
+ return lo;
918
+ });
919
+ return rs.reduce((a, b) => a + b, 0) / rs.length;
920
+ }
921
+ window.linkAt = (tS) => {
922
+ const t = TW(tS), out = [];
923
+ const lens = lensState(t);
924
+ const kv = knobVis(t);
925
+ // --- the dot: Focus-toggle knob (flat white) -> glass knob 4.62 -> bead 4.8-5.45 -> capsule lens 5.95-7.75 -> ring 句号 8.3-10
926
+ const hA = riseA(t, cues.cardC_in + 0.35, 0.9), knobA = cardState(CARDS[2], t, lens).op * hA.a;
927
+ if (t < cues.knob_glass && knobA > 0.005) {
928
+ // (the white knob cross-fades into the glass knob 4.55-4.70; the pair is always fully visible, so a = the knob's own presence)
929
+ const [kx, ky] = knobLocal(t), kn = cardLocalScreen(2, t, kx, ky + hA.dy);
930
+ const stretch = 1 + 0.22 * Math.sin(Math.PI * inv(cues.toggle_on, cues.toggle_on + 0.42, t));
931
+ const r = KNOB_R * kv.s * kn[2];
932
+ out.push({ id: 'dot', type: 'circle', x: r1(kn[0]), y: r1(kn[1]), r: r1(r), rx: r1(r * stretch), a: Math.round(knobA * 1000) / 1000, look: 'flat',
933
+ fill: '#FFFFFF', stroke: '#FFFFFF', sw: 0, note: 'Focus toggle knob: flat white disc with a soft drop shadow, on a frosted card' });
934
+ } else if (lens.on > 0) {
935
+ const ring = lens.ringW > 0, capsule = Math.abs(lens.hx - lens.hy) > 0.12 * Math.min(lens.hx, lens.hy);
936
+ const a = t < 4.8 ? Math.min(1, lens.on + kv.dotA * knobA) : lens.on;
937
+ const rv = lens.bead[2] > 0 ? visR(lens) : Math.min(lens.hx, lens.hy);
938
+ const e = { id: 'dot', type: 'circle', x: r1(lens.c[0]), y: r1(lens.c[1]), r: r1(rv), a: Math.round(a * 1000) / 1000, look: 'glass',
939
+ fill: '#9AA8FF', stroke: '#FFFFFF', sw: ring ? r1(lens.ringW) : 1.5, grad: ['#C9D2FF', '#6F63F0'] };
940
+ if (capsule) Object.assign(e, { shape: 'capsule', w: r1(2 * lens.hx), h: r1(2 * lens.hy) });
941
+ e.note = t < KN_T ? 'glass knob (the toggle knob has turned to clear glass, rim light)'
942
+ : t < cues.snap ? 'glass bead pulling out of the toggle (metaball neck)'
943
+ : !ring ? (capsule ? 'Liquid-Glass capsule lens (magnifier, rim light); r = end-cap radius, w/h = full size' : 'glass bead swelling into the lens')
944
+ : lens.ringW > 1.2 * O_R ? 'lens irising open into a ring' : 'clear glass ring (annulus, sw = glass stroke) = the 句号 of 玻璃拟态。';
945
+ out.push(e);
946
+ }
947
+ // --- the title block and its second line
948
+ if (t >= cues.word - 0.05) {
949
+ const ws = wordScale(t), ra = V5_T.map((t0) => riseA(t, t0, 0.95).a), a = Math.min(...ra);
950
+ if (!HIDE_TITLE && Math.max(...ra) > 0) {
951
+ const [xa, ya] = toFrame(V5L.ink.l, V5L.ink.top, ws), [xb, yb] = toFrame(V5L.ink.r, V5L.ink.bot, ws);
952
+ out.push({ id: 'title', type: 'rect', x: r1((xa + xb) / 2), y: r1((ya + yb) / 2), w: r1(xb - xa), h: r1(yb - ya), rot: 0, a: Math.round(a * 1000) / 1000, look: 'flat',
953
+ fill: '#FFFFFF', note: '「玻璃拟态」 frosted-glass glyphs (HarmonyOS Sans SC Black 216 px), ink bbox; behind pane A until ~7.55 (source)' });
954
+ }
955
+ }
956
+ // --- the frosted panes: A (conversation) and C (Focus/Glow controls)
957
+ const A = cardRect(0, t, lens, 'cardA', 'frosted glass pane A (conversation); w/h = screen bbox of the projected card, corners = exact quad');
958
+ const Cc = cardRect(2, t, lens, 'cardC', 'frosted glass pane C (Focus toggle + Glow slider)');
959
+ if (A) out.push(A); if (Cc) out.push(Cc);
960
+ return out;
961
+ };
962
+ // v5.2 debug (tests/v5_contrast.mjs): the glyph mask and line-2 canvases of the last renderAt, for glyph-vs-surround luma
963
+ window.__v5dbg = { mask: () => glyphCv.toDataURL(), word: () => wordCv.toDataURL() };
964
+ }
965
+
966
+ await window.renderAt(0);
967
+ window.__ready = true;