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.
- package/LICENSE +4154 -0
- package/README.md +49 -11
- package/lib/audio-media.js +150 -0
- package/lib/audio-service.js +66 -0
- package/lib/canvas-format.js +134 -0
- package/lib/client.js +5090 -184
- package/lib/comfyui-default-workflow.mjs +1825 -0
- package/lib/comfyui-workflow.mjs +426 -0
- package/lib/document-commands.js +12 -0
- package/lib/document-core.js +29 -0
- package/lib/drawing-runner.js +6 -4
- package/lib/drawing-tasks.js +26 -0
- package/lib/ffmpeg-manager.js +1056 -0
- package/lib/imagegen.mjs +596 -0
- package/lib/index.js +1762 -129
- package/lib/media.js +26 -13
- package/lib/mgstyle-core.js +75 -0
- package/lib/motion/apply.js +181 -0
- package/lib/motion/asset-pack-lock.js +10 -0
- package/lib/motion/asset-pack.js +335 -0
- package/lib/motion/assets.js +291 -0
- package/lib/motion/audio.js +103 -0
- package/lib/motion/audiolevel.js +63 -0
- package/lib/motion/audiomix.js +144 -0
- package/lib/motion/beats.js +338 -0
- package/lib/motion/builtin-assets.js +62 -0
- package/lib/motion/canvas-spec.js +442 -0
- package/lib/motion/capture.js +95 -0
- package/lib/motion/cards.js +80 -0
- package/lib/motion/collect.js +449 -0
- package/lib/motion/delivery.js +140 -0
- package/lib/motion/edge-speech.py +90 -0
- package/lib/motion/entry.jsx +73 -0
- package/lib/motion/export.js +200 -0
- package/lib/motion/ffmpeg.js +43 -0
- package/lib/motion/fonts.js +71 -0
- package/lib/motion/jobs.js +117 -0
- package/lib/motion/json.js +4 -0
- package/lib/motion/media-catalog.js +59 -0
- package/lib/motion/mgstyles/BRIEF-TEMPLATE.md +63 -0
- package/lib/motion/mgstyles/briefs/01-flat-vector.md +104 -0
- package/lib/motion/mgstyles/briefs/02-line-art.md +97 -0
- package/lib/motion/mgstyles/briefs/03-isometric.md +108 -0
- package/lib/motion/mgstyles/briefs/05-cel-boil.md +145 -0
- package/lib/motion/mgstyles/briefs/06-collage.md +149 -0
- package/lib/motion/mgstyles/briefs/07-liquid.md +106 -0
- package/lib/motion/mgstyles/briefs/08-morph.md +111 -0
- package/lib/motion/mgstyles/briefs/09-bauhaus.md +150 -0
- package/lib/motion/mgstyles/briefs/10-synthwave.md +151 -0
- package/lib/motion/mgstyles/briefs/12-aurora-glass.md +147 -0
- package/lib/motion/mgstyles/briefs/18-hanazi.md +140 -0
- package/lib/motion/mgstyles/briefs/19-paperclip.md +156 -0
- package/lib/motion/mgstyles/briefs/20-pixel.md +118 -0
- package/lib/motion/mgstyles/briefs/22-hud.md +118 -0
- package/lib/motion/mgstyles/catalog.d/01-flat-vector.json +34 -0
- package/lib/motion/mgstyles/catalog.d/02-line-art.json +34 -0
- package/lib/motion/mgstyles/catalog.d/03-isometric.json +35 -0
- package/lib/motion/mgstyles/catalog.d/05-cel-boil.json +28 -0
- package/lib/motion/mgstyles/catalog.d/06-collage.json +28 -0
- package/lib/motion/mgstyles/catalog.d/07-liquid.json +28 -0
- package/lib/motion/mgstyles/catalog.d/08-morph.json +28 -0
- package/lib/motion/mgstyles/catalog.d/09-bauhaus.json +28 -0
- package/lib/motion/mgstyles/catalog.d/10-synthwave.json +28 -0
- package/lib/motion/mgstyles/catalog.d/12-aurora-glass.json +28 -0
- package/lib/motion/mgstyles/catalog.d/18-hanazi.json +28 -0
- package/lib/motion/mgstyles/catalog.d/19-paperclip.json +28 -0
- package/lib/motion/mgstyles/catalog.d/20-pixel.json +28 -0
- package/lib/motion/mgstyles/catalog.d/22-hud.json +28 -0
- package/lib/motion/mgstyles/catalog.json +1356 -0
- package/lib/motion/mgstyles/upstream/README.md +147 -0
- package/lib/motion/mgstyles/upstream/README.zh-CN.md +147 -0
- package/lib/motion/mgstyles/upstream/demos/01-flat-vector/cues.json +1 -0
- package/lib/motion/mgstyles/upstream/demos/01-flat-vector/index.html +712 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/cues.json +331 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/film.js +676 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/index.html +18 -0
- package/lib/motion/mgstyles/upstream/demos/02-line-art/tools/dump_timing.mjs +26 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/cues.json +109 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/index.html +25 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/anim.js +82 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/main.js +219 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/night5.js +180 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/render.js +202 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title.js +167 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/title5.js +294 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/ui.js +141 -0
- package/lib/motion/mgstyles/upstream/demos/03-isometric/js/world.js +1399 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/cues.json +157 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/index.html +34 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/engine.js +326 -0
- package/lib/motion/mgstyles/upstream/demos/05-cel-boil/js/scene.js +2036 -0
- package/lib/motion/mgstyles/upstream/demos/06-collage/assets/gen/layout.json +157 -0
- package/lib/motion/mgstyles/upstream/demos/06-collage/cues.json +80 -0
- package/lib/motion/mgstyles/upstream/demos/06-collage/index.html +889 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/cues.json +100 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/index.html +108 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/engine.js +153 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/links.js +64 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/lookdev.js +46 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/scene.js +963 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/shaders.js +647 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/textsdf.js +114 -0
- package/lib/motion/mgstyles/upstream/demos/07-liquid/js/type.js +50 -0
- package/lib/motion/mgstyles/upstream/demos/08-morph/cues.json +131 -0
- package/lib/motion/mgstyles/upstream/demos/08-morph/glyphs.json +1 -0
- package/lib/motion/mgstyles/upstream/demos/08-morph/index.html +901 -0
- package/lib/motion/mgstyles/upstream/demos/09-bauhaus/cues.json +270 -0
- package/lib/motion/mgstyles/upstream/demos/09-bauhaus/film.js +697 -0
- package/lib/motion/mgstyles/upstream/demos/09-bauhaus/index.html +12 -0
- package/lib/motion/mgstyles/upstream/demos/10-synthwave/cues.json +44 -0
- package/lib/motion/mgstyles/upstream/demos/10-synthwave/index.html +1028 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/cues.json +67 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/index.html +16 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/main.js +967 -0
- package/lib/motion/mgstyles/upstream/demos/12-aurora-glass/shaders.js +468 -0
- package/lib/motion/mgstyles/upstream/demos/18-hanazi/cues.json +117 -0
- package/lib/motion/mgstyles/upstream/demos/18-hanazi/index.html +541 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/countries-110m.json +1 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/assets/stickers/meta.json +34 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/cues.json +285 -0
- package/lib/motion/mgstyles/upstream/demos/19-paperclip/index.html +599 -0
- package/lib/motion/mgstyles/upstream/demos/20-pixel/cues.json +42 -0
- package/lib/motion/mgstyles/upstream/demos/20-pixel/film.js +914 -0
- package/lib/motion/mgstyles/upstream/demos/20-pixel/index.html +18 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/cues.json +108 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/index.html +125 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/holo.js +275 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/hud.js +855 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/post.js +150 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/util.js +65 -0
- package/lib/motion/mgstyles/upstream/demos/22-hud/js/v5.js +142 -0
- package/lib/motion/mgstyles/upstream/harness/render.mjs +319 -0
- package/lib/motion/mgstyles/upstream/manifest.json +269 -0
- package/lib/motion/mgstyles/upstream/prompts/01-flat-vector.md +98 -0
- package/lib/motion/mgstyles/upstream/prompts/02-line-art.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/03-isometric.md +97 -0
- package/lib/motion/mgstyles/upstream/prompts/05-cel-boil.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/06-collage.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/07-liquid.md +98 -0
- package/lib/motion/mgstyles/upstream/prompts/08-morph.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/09-bauhaus.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/10-synthwave.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/12-aurora-glass.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/18-hanazi.md +95 -0
- package/lib/motion/mgstyles/upstream/prompts/19-paperclip.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/20-pixel.md +96 -0
- package/lib/motion/mgstyles/upstream/prompts/22-hud.md +95 -0
- package/lib/motion/mgstyles/upstream/rubric.md +51 -0
- package/lib/motion/mgstyles/upstream/template.md +36 -0
- package/lib/motion/pixelqa.js +198 -0
- package/lib/motion/preview.js +97 -0
- package/lib/motion/project.js +395 -0
- package/lib/motion/qa.js +290 -0
- package/lib/motion/reference-media.jsx +10 -0
- package/lib/motion/render-settings.js +112 -0
- package/lib/motion/render.js +161 -0
- package/lib/motion/runtime/package-lock.json +3058 -0
- package/lib/motion/runtime/package.json +16 -0
- package/lib/motion/scene-kit.js +183 -0
- package/lib/motion/sfx.js +232 -0
- package/lib/motion/shotcraft/ClipCard.tsx +204 -0
- package/lib/motion/shotcraft/catalog.json +2461 -0
- package/lib/motion/shotcraft/upstream/SKILL.md +286 -0
- package/lib/motion/shotcraft/upstream/assets/lib/Caption.tsx +54 -0
- package/lib/motion/shotcraft/upstream/assets/lib/ClipCard.tsx +200 -0
- package/lib/motion/shotcraft/upstream/assets/lib/DigitRoll.tsx +45 -0
- package/lib/motion/shotcraft/upstream/assets/lib/FlashCut.tsx +20 -0
- package/lib/motion/shotcraft/upstream/assets/lib/FlatPanel.tsx +65 -0
- package/lib/motion/shotcraft/upstream/assets/lib/PageCam.tsx +151 -0
- package/lib/motion/shotcraft/upstream/assets/lib/VerticalTicker.tsx +129 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/camera.tsx +75 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/motion.ts +40 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/rand.ts +11 -0
- package/lib/motion/shotcraft/upstream/assets/lib/helpers/shake.ts +10 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/__pycache__/sfx-source.cpython-313.pyc +0 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/build-readme-cover.mjs +49 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/capture-template.mjs +210 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/sfx-source.py +423 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/smoke-render-demos.py +170 -0
- package/lib/motion/shotcraft/upstream/assets/scripts/test_sfx_source.py +157 -0
- package/lib/motion/shotcraft/upstream/demos/README.md +120 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Brand.tsx +112 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Fixtures.tsx +647 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Look.tsx +316 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Motion.tsx +82 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/PageCam2D.tsx +115 -0
- package/lib/motion/shotcraft/upstream/demos/_fixtures/Polish.tsx +243 -0
- package/lib/motion/shotcraft/upstream/demos/_textures/live-layout.json +389 -0
- package/lib/motion/shotcraft/upstream/demos/camera/basic-3d-scene/Basic3DScene.tsx +250 -0
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashImpactReal.tsx +305 -0
- package/lib/motion/shotcraft/upstream/demos/camera/crash-zoom-punch/CrashZoomReal.tsx +246 -0
- package/lib/motion/shotcraft/upstream/demos/camera/cursor-flyover/CursorFlyover.tsx +390 -0
- package/lib/motion/shotcraft/upstream/demos/camera/depth-layer-moves/MultiplaneReal.tsx +138 -0
- package/lib/motion/shotcraft/upstream/demos/camera/graze-face-tour/GrazeFaceTour.tsx +542 -0
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/OverheadTabletopDrop.tsx +315 -0
- package/lib/motion/shotcraft/upstream/demos/camera/overhead-camera-moves/TiltReveal.tsx +299 -0
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/DroneDiveLanding.tsx +173 -0
- package/lib/motion/shotcraft/upstream/demos/camera/space-camera-moves/ExplodedView.tsx +348 -0
- package/lib/motion/shotcraft/upstream/demos/camera/steep-tilt-glide/SteepTiltGlide.tsx +366 -0
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/BulletTimeFreezeOrbit.tsx +279 -0
- package/lib/motion/shotcraft/upstream/demos/camera/tension-camera-moves/PullBackIsolation.tsx +184 -0
- package/lib/motion/shotcraft/upstream/demos/camera/terminal-3d/Terminal3D.tsx +390 -0
- package/lib/motion/shotcraft/upstream/demos/data/before-after-slider-scrub/BeforeAfterSliderScrub.tsx +155 -0
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/AxisRescaleShockV2.tsx +303 -0
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/OscilloscopeStreamV2.tsx +290 -0
- package/lib/motion/shotcraft/upstream/demos/data/chart-live-moves/UnitDotSwarmRegroupV2.tsx +278 -0
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/NeedleSweepSelftest.tsx +307 -0
- package/lib/motion/shotcraft/upstream/demos/data/gauge-readout-moves/TapeScrollFixedPointer.tsx +215 -0
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/BrakeReticleLock.tsx +299 -0
- package/lib/motion/shotcraft/upstream/demos/data/scroll-brake-moves/ChangelogScrollBrake.tsx +232 -0
- package/lib/motion/shotcraft/upstream/demos/data/timeline-travel/TimelineTravel.tsx +290 -0
- package/lib/motion/shotcraft/upstream/demos/effects/assemble-then-type-flyin/AssembleThenTypeFlyin.tsx +273 -0
- package/lib/motion/shotcraft/upstream/demos/effects/aurora-bloom-bg-flip/AuroraBloomBgFlip.tsx +167 -0
- package/lib/motion/shotcraft/upstream/demos/effects/brand-frame-snap/BrandFrameSnap.tsx +536 -0
- package/lib/motion/shotcraft/upstream/demos/effects/fui-hud-moves/ReticleLockOn.tsx +164 -0
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/FlylineArc.tsx +175 -0
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/GlowOrbAmbient.tsx +197 -0
- package/lib/motion/shotcraft/upstream/demos/effects/glow-flyline-moves/OrbFlylineRelay.tsx +241 -0
- package/lib/motion/shotcraft/upstream/demos/effects/icon-performance-moves/AttentionBounce.tsx +279 -0
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/AnimeImpact.tsx +230 -0
- package/lib/motion/shotcraft/upstream/demos/effects/impact-feedback/HitCounter.tsx +345 -0
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SheenSweepRetry.tsx +275 -0
- package/lib/motion/shotcraft/upstream/demos/effects/light-play-moves/SpotlightSweepReveal.tsx +229 -0
- package/lib/motion/shotcraft/upstream/demos/effects/radial-ripple-phone-chips/RadialRipplePhoneChips.tsx +312 -0
- package/lib/motion/shotcraft/upstream/demos/effects/riso-print-hits/RisoMisregistrationHit.tsx +222 -0
- package/lib/motion/shotcraft/upstream/demos/effects/scan-bracket-sweep/ScanBracketSweep.tsx +295 -0
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-annotate-focus/ScanlineAnnotateFocus.tsx +329 -0
- package/lib/motion/shotcraft/upstream/demos/effects/scanline-assemble-flyin/ScanlineAssembleFlyin.tsx +315 -0
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ImpactBurstKit.tsx +264 -0
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/KanadaPerspectiveSnap.tsx +260 -0
- package/lib/motion/shotcraft/upstream/demos/effects/slam-entrance-moves/ScoreSlam.tsx +263 -0
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/CornerSpotlightReveal.tsx +123 -0
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/GlowWakeSleepPanel.tsx +187 -0
- package/lib/motion/shotcraft/upstream/demos/effects/spotlight-sweep-moves/SlideSpotlightPan.tsx +175 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/autolayout-gap-dial/AutolayoutGapDial.tsx +305 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/DiagramCascadeBuild.tsx +350 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/canvas-materialize-moves/PanelToCanvasMaterialize.tsx +352 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/chip-grid-single-select-blackout/ChipGridSingleSelectBlackout.tsx +218 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/collab-cursor-moves/CursorCastEnsemble.tsx +297 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/glass-pill-dictation-typing/GlassPillDictationTyping.tsx +157 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/hashtag-to-pill-materialize/HashtagToPillMaterialize.tsx +218 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/picker-carousel-feature-cycle/PickerCarouselFeatureCycle.tsx +202 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/segmented-thumb-hero/SegmentedThumbHero.tsx +265 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/PaletteThemeRipple.tsx +457 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/theme-switch-moves/ThemeSweepToggle.tsx +446 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/type-and-filter/TypeAndFilter.tsx +249 -0
- package/lib/motion/shotcraft/upstream/demos/interaction/voice-waveform-live/VoiceWaveformLive.tsx +278 -0
- package/lib/motion/shotcraft/upstream/demos/opening/crane-rise-reveal/CraneRiseReveal.tsx +332 -0
- package/lib/motion/shotcraft/upstream/demos/opening/dataviz-landscape-open/DatavizLandscapeOpen.tsx +358 -0
- package/lib/motion/shotcraft/upstream/demos/opening/fracture/Fracture.tsx +201 -0
- package/lib/motion/shotcraft/upstream/demos/opening/icon-field-colorize/IconFieldColorize.tsx +200 -0
- package/lib/motion/shotcraft/upstream/demos/opening/letterspace-materialize/LetterspaceMaterialize.tsx +208 -0
- package/lib/motion/shotcraft/upstream/demos/opening/magician-card-flourish/MagicianCardFlourish.tsx +341 -0
- package/lib/motion/shotcraft/upstream/demos/opening/orbit-ring-title-open/OrbitRingTitleOpen.tsx +389 -0
- package/lib/motion/shotcraft/upstream/demos/opening/spotlight-hero-card/SpotlightHeroCard.tsx +293 -0
- package/lib/motion/shotcraft/upstream/demos/opening/stroke-segment-build/StrokeSegmentBuild.tsx +183 -0
- package/lib/motion/shotcraft/upstream/demos/opening/text-as-mask/TextAsMask.tsx +245 -0
- package/lib/motion/shotcraft/upstream/demos/outro/grain-dissolve/GrainDissolve.tsx +222 -0
- package/lib/motion/shotcraft/upstream/demos/outro/logo-shrink-wordmark-lockup/LogoShrinkWordmarkLockup.tsx +223 -0
- package/lib/motion/shotcraft/upstream/demos/outro/neon-triple-marquee/NeonTripleMarquee.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/outro/outro-group-photo-launch/OutroGroupPhotoLaunch.tsx +343 -0
- package/lib/motion/shotcraft/upstream/demos/outro/ui-strip-away-outro/UiStripAwayOutro.tsx +357 -0
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/IconFlipBloomLogo.tsx +187 -0
- package/lib/motion/shotcraft/upstream/demos/outro/ui-to-brand-morph/InputMorphsIntoLogo.tsx +333 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-cut-moves/PaparazziFlash.tsx +274 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/beat-step-list-theme-cycle/BeatStepListThemeCycle.tsx +270 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/panel-grid-moves/ComicPanelSplit.tsx +430 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/quad-split-parallel-scenes/QuadSplitParallelScenes.tsx +252 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/rhythm-interrupt-moves/JumpCutPunchIn.tsx +106 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/sakuga-timing-shift/SakugaTimingShift.tsx +288 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/speed-ramp-freeze/FreezeAnnotateReal.tsx +362 -0
- package/lib/motion/shotcraft/upstream/demos/rhythm/trailer-grammar-moves/TrailerBumper.tsx +168 -0
- package/lib/motion/shotcraft/upstream/demos/transition/bottom-push-stack-wipe/BottomPushStackWipe.tsx +310 -0
- package/lib/motion/shotcraft/upstream/demos/transition/card-flip-reveal/CardFlipReveal.tsx +261 -0
- package/lib/motion/shotcraft/upstream/demos/transition/card-flock-tumble/CardFlockTumble.tsx +459 -0
- package/lib/motion/shotcraft/upstream/demos/transition/circle-match-iris/CircleMatchIris.tsx +294 -0
- package/lib/motion/shotcraft/upstream/demos/transition/gradient-transition/GradientTransition.tsx +161 -0
- package/lib/motion/shotcraft/upstream/demos/transition/line-carry-transition/LineCarryTransition.tsx +248 -0
- package/lib/motion/shotcraft/upstream/demos/transition/mosaic-reframe/MosaicReframe.tsx +359 -0
- package/lib/motion/shotcraft/upstream/demos/transition/page-turn-transitions/BarnDoorSplit.tsx +227 -0
- package/lib/motion/shotcraft/upstream/demos/transition/print-texture-transitions/InkBleedReveal.tsx +162 -0
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/PortalWipeV2.tsx +248 -0
- package/lib/motion/shotcraft/upstream/demos/transition/shot-transitions/WhipBrakeReal.tsx +186 -0
- package/lib/motion/shotcraft/upstream/demos/transition/tear-streak-transitions/GlitchDisplace.tsx +355 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/InvisibleCut.tsx +272 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/LightLeakBurn.tsx +262 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-hidden-cut/VersusSlam.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/transition/transition-travel/LetterformZoom.tsx +285 -0
- package/lib/motion/shotcraft/upstream/demos/transition/white-flash-logo-simplify-cut/WhiteFlashLogoSimplifyCut.tsx +205 -0
- package/lib/motion/shotcraft/upstream/demos/transition/wipe-transitions/BlindsSlice.tsx +178 -0
- package/lib/motion/shotcraft/upstream/demos/typography/blur-slide/BlurSlide.tsx +147 -0
- package/lib/motion/shotcraft/upstream/demos/typography/brace-expand/BraceExpand.tsx +143 -0
- package/lib/motion/shotcraft/upstream/demos/typography/brand-ink-open/BrandInkOpen.tsx +134 -0
- package/lib/motion/shotcraft/upstream/demos/typography/countdown-arc-scatter/CountdownArcScatter.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/typography/flying-words/FlyingWords.tsx +198 -0
- package/lib/motion/shotcraft/upstream/demos/typography/glitch-cycle/GlitchCycle.tsx +221 -0
- package/lib/motion/shotcraft/upstream/demos/typography/gradient-word-sweep/GradientWordSweep.tsx +312 -0
- package/lib/motion/shotcraft/upstream/demos/typography/lead-word-zoom-assemble/LeadWordZoomAssemble.tsx +261 -0
- package/lib/motion/shotcraft/upstream/demos/typography/marker-underline-title/MarkerUnderlineTitle.tsx +121 -0
- package/lib/motion/shotcraft/upstream/demos/typography/outline-word-fill/OutlineWordFill.tsx +153 -0
- package/lib/motion/shotcraft/upstream/demos/typography/paper-title-card/PaperTitleCard.tsx +179 -0
- package/lib/motion/shotcraft/upstream/demos/typography/pill-chip-slot-cycle-handled/PillChipSlotCycleHandled.tsx +140 -0
- package/lib/motion/shotcraft/upstream/demos/typography/pill-slot-cycle/PillSlotCycle.tsx +309 -0
- package/lib/motion/shotcraft/upstream/demos/typography/text-column-converge/TextColumnConverge.tsx +191 -0
- package/lib/motion/shotcraft/upstream/demos/typography/title-demote-to-label/TitleDemoteToLabel.tsx +292 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/LetterformDriftAssembly.tsx +135 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/SplitTextStagger.tsx +124 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-assembly-moves/TextOnPath.tsx +255 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-entrance-moves/LetterDropPhysics.tsx +173 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/FontWeightPump.tsx +225 -0
- package/lib/motion/shotcraft/upstream/demos/typography/type-rhythm-sync/KaraokeFillSync.tsx +141 -0
- package/lib/motion/shotcraft/upstream/demos/typography/typewriter-moves/TypewriterErrorRetype.tsx +137 -0
- package/lib/motion/shotcraft/upstream/demos/typography/vertical-word-roll-blur-cycle/VerticalWordRollBlurCycle.tsx +147 -0
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-filmstrip/WordRelayFilmstrip.tsx +409 -0
- package/lib/motion/shotcraft/upstream/demos/typography/word-relay-geometry/WordRelayGeometry.tsx +280 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/avatar-bracket-carousel/AvatarBracketCarousel.tsx +271 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/bezier-source-converge-merge/BezierSourceConvergeMerge.tsx +385 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/card-stack/CardStack.tsx +312 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/carousel-3d/Carousel3D.tsx +303 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/clipcard-looping/ClipCardLooping.tsx +77 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/cloner-depth-echo/ClonerDepthEcho.tsx +311 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/deck-deal-flyin/DeckDealFlyin.tsx +307 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/doc-park-left-pill-deal/DocParkLeftPillDeal.tsx +349 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/document-typewriter-reveal/DocumentTypewriterReveal.tsx +255 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/draw-svg-trace/DrawSvgTrace.tsx +244 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/element-body-moves/ContactShadowLift.tsx +217 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/integration-hub-map/IntegrationHubMap.tsx +652 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-reveal/ListReveal.tsx +153 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/list-stack-press/ListStackPress.tsx +238 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-forerun/NeonFrameForerun.tsx +324 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/neon-frame-orbit-drop/NeonFrameForerunOrbit.tsx +354 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/PageWaterfallWall.tsx +101 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/page-waterfall-wall/VerticalTicker.tsx +129 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/product-card-progressive-assemble/ProductCardProgressiveAssemble.tsx +297 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/radial-wave/RadialWave.tsx +57 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/research-card-stack-scroll/ResearchCardStackScroll.tsx +155 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/row-embed/RowEmbed.tsx +175 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/value-stagger-gradient/ValueStaggerGradient.tsx +153 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/BentoLightUp.tsx +146 -0
- package/lib/motion/shotcraft/upstream/demos/ui-entrance/wall-reveal-moves/WireframeDrawOn.tsx +226 -0
- package/lib/motion/shotcraft/upstream/gallery/api/library.json +1 -0
- package/lib/motion/shotcraft/upstream/manifest.json +2715 -0
- package/lib/motion/shotcraft/upstream/references/aesthetic-rules.md +193 -0
- package/lib/motion/shotcraft/upstream/references/final-review.md +121 -0
- package/lib/motion/shotcraft/upstream/references/guided-free-creation.md +199 -0
- package/lib/motion/shotcraft/upstream/references/jianying-export.md +183 -0
- package/lib/motion/shotcraft/upstream/references/music-beat-sync.md +257 -0
- package/lib/motion/shotcraft/upstream/references/pipeline.md +382 -0
- package/lib/motion/shotcraft/upstream/references/publish-upload-post.md +111 -0
- package/lib/motion/shotcraft/upstream/references/sequences/promo-energy-arc.md +50 -0
- package/lib/motion/shotcraft/upstream/references/sfx-sourcing.md +95 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/basic-3d-scene.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/crash-zoom-punch.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/cursor-flyover.md +39 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/depth-layer-moves.md +39 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/graze-face-tour.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/overhead-camera-moves.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/space-camera-moves.md +46 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/steep-tilt-glide.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/tension-camera-moves.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/camera/terminal-3d.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/before-after-slider-scrub.md +48 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/chart-live-moves.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/gauge-readout-moves.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/scroll-brake-moves.md +46 -0
- package/lib/motion/shotcraft/upstream/references/shots/data/timeline-travel.md +44 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/assemble-then-type-flyin.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/aurora-bloom-bg-flip.md +54 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/brand-frame-snap.md +58 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/fui-hud-moves.md +35 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/glow-flyline-moves.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/icon-performance-moves.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/impact-feedback.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/light-play-moves.md +46 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/radial-ripple-phone-chips.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/riso-print-hits.md +49 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/scan-bracket-sweep.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-annotate-focus.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/scanline-assemble-flyin.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/slam-entrance-moves.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/effects/spotlight-sweep-moves.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/autolayout-gap-dial.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/canvas-materialize-moves.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/chip-grid-single-select-blackout.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/collab-cursor-moves.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/glass-pill-dictation-typing.md +53 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/hashtag-to-pill-materialize.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/picker-carousel-feature-cycle.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/segmented-thumb-hero.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/theme-switch-moves.md +47 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/type-and-filter.md +37 -0
- package/lib/motion/shotcraft/upstream/references/shots/interaction/voice-waveform-live.md +54 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/brand-ink-open.md +36 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/crane-rise-reveal.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/dataviz-landscape-open.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/fracture.md +40 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/icon-field-colorize.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/letterspace-materialize.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/magician-card-flourish.md +73 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/orbit-ring-title-open.md +87 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/spotlight-hero-card.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/stroke-segment-build.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/opening/text-as-mask.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/grain-dissolve.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/logo-shrink-wordmark-lockup.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/neon-triple-marquee.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/outro-group-photo-launch.md +39 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-strip-away-outro.md +61 -0
- package/lib/motion/shotcraft/upstream/references/shots/outro/ui-to-brand-morph.md +52 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-cut-moves.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/beat-step-list-theme-cycle.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/panel-grid-moves.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/quad-split-parallel-scenes.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/rhythm-interrupt-moves.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/sakuga-timing-shift.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/speed-ramp-freeze.md +44 -0
- package/lib/motion/shotcraft/upstream/references/shots/rhythm/trailer-grammar-moves.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/bottom-push-stack-wipe.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flip-reveal.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/card-flock-tumble.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/circle-match-iris.md +58 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/gradient-transition.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/line-carry-transition.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/mosaic-reframe.md +49 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/page-turn-transitions.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/print-texture-transitions.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/shot-transitions.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/tear-streak-transitions.md +48 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-hidden-cut.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/transition-travel.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/white-flash-logo-simplify-cut.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/transition/wipe-transitions.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/blur-slide.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/brace-expand.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/countdown-arc-scatter.md +65 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/document-typewriter-reveal.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/flying-words.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/glitch-cycle.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/gradient-word-sweep.md +54 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/lead-word-zoom-assemble.md +91 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/marker-underline-title.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/outline-word-fill.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/paper-title-card.md +38 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-chip-slot-cycle-handled.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/pill-slot-cycle.md +58 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/text-column-converge.md +61 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/title-demote-to-label.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-assembly-moves.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-entrance-moves.md +41 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/type-rhythm-sync.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/typewriter-moves.md +42 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/vertical-word-roll-blur-cycle.md +63 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-filmstrip.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/typography/word-relay-geometry.md +75 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/avatar-bracket-carousel.md +59 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/bezier-source-converge-merge.md +67 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/card-stack.md +60 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/carousel-3d.md +66 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/cloner-depth-echo.md +40 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/deck-deal-flyin.md +45 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/doc-park-left-pill-deal.md +62 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/draw-svg-trace.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/element-body-moves.md +37 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/integration-hub-map.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-reveal.md +50 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/list-stack-press.md +43 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-forerun.md +55 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/neon-frame-orbit-drop.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/page-waterfall-wall.md +51 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/product-card-progressive-assemble.md +56 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/radial-wave.md +49 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/research-card-stack-scroll.md +57 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/row-embed.md +36 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/value-stagger-gradient.md +64 -0
- package/lib/motion/shotcraft/upstream/references/shots/ui-entrance/wall-reveal-moves.md +47 -0
- package/lib/motion/shotcraft/upstream/references/sound-design.md +170 -0
- package/lib/motion/shotcraft/upstream/references/workbench.md +157 -0
- package/lib/motion/shotcraft/upstream/template/TEMPLATE.md +103 -0
- package/lib/motion/shotcraft/upstream/template/THEMES.md +44 -0
- package/lib/motion/shotcraft/upstream/template/package.json +20 -0
- package/lib/motion/shotcraft/upstream/template/remotion.config.ts +6 -0
- package/lib/motion/shotcraft/upstream/template/src/Root.tsx +17 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/Caption.tsx +65 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/DigitRoll.tsx +48 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/FlashCut.tsx +24 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/Main.tsx +192 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/PaperTitleCard.tsx +111 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/PageCam.tsx +150 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneDetail.tsx +158 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneFlyIn.tsx +468 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOpen.tsx +552 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneOutroLive.tsx +411 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/ScenePapers.tsx +200 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live/SceneWbr.tsx +324 -0
- package/lib/motion/shotcraft/upstream/template/src/aifl/live-layout.json +389 -0
- package/lib/motion/shotcraft/upstream/template/src/index.ts +4 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/ThemedFilm.tsx +7 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/manifest.tsx +68 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.json +1 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/palette-assets.ts +24 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/palettes.json +74 -0
- package/lib/motion/shotcraft/upstream/template/src/themes/visual-theme.tsx +91 -0
- package/lib/motion/shotcraft/upstream/template/src/workbench.ts +213 -0
- package/lib/motion/shotcraft/upstream/template/tsconfig.json +15 -0
- package/lib/motion/spec.js +113 -0
- package/lib/motion/style-engine.js +153 -0
- package/lib/motion/style-frames.js +271 -0
- package/lib/motion/sync.js +214 -0
- package/lib/motion/theme.js +121 -0
- package/lib/motion/video-runtime.js +192 -0
- package/lib/motion/voice.js +417 -0
- package/lib/motion-agent-files.js +82 -0
- package/lib/motion-agent-protocol.js +66 -0
- package/lib/motion-agent-tools.js +196 -0
- package/lib/motion-agent.js +171 -0
- package/lib/motion-capture.js +93 -0
- package/lib/motion-core.js +238 -0
- package/lib/motion-film-review.js +71 -0
- package/lib/motion-library.js +210 -0
- package/lib/motion-planner.js +254 -0
- package/lib/motion-project.js +87 -0
- package/lib/motion-render-worker.js +153 -0
- package/lib/motion-session.js +32 -0
- package/lib/motion-source.js +49 -0
- package/lib/motion-tasks.js +452 -0
- package/lib/motion-vision.js +44 -0
- package/lib/mxfile.js +27 -7
- package/lib/routing/node-runtime.js +14 -0
- package/lib/routing/router.js +211 -0
- package/lib/routing/vendor/README.md +96 -0
- package/lib/routing/vendor/libavoid-routing.js +753 -0
- package/lib/routing/vendor/libavoid.min.js +59 -0
- package/lib/shotcard-core.js +39 -0
- package/lib/shotcard-reference.js +56 -0
- package/lib/style-kernel.js +37 -3
- package/lib/task-patches.js +26 -2
- package/lib/video-export.js +49 -22
- 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;
|