dsh-drawai 0.3.3 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +4142 -0
- package/README.md +43 -10
- package/lib/audio-media.js +147 -0
- package/lib/audio-service.js +66 -0
- package/lib/canvas-format.js +134 -0
- package/lib/client.js +5283 -223
- 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-tasks.js +26 -0
- package/lib/imagegen.mjs +596 -0
- package/lib/index.js +1758 -129
- package/lib/media.js +26 -13
- package/lib/mgstyle-core.js +75 -0
- package/lib/motion/apply.js +180 -0
- package/lib/motion/asset-pack-lock.js +10 -0
- package/lib/motion/asset-pack.js +335 -0
- package/lib/motion/assets.js +290 -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 +337 -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 +447 -0
- package/lib/motion/delivery.js +112 -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 +42 -0
- package/lib/motion/fonts.js +71 -0
- package/lib/motion/jobs.js +113 -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 +95 -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 +159 -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 +268 -0
- package/lib/motion/sync.js +214 -0
- package/lib/motion/theme.js +121 -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 +92 -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 +80 -0
- package/lib/motion-render-worker.js +145 -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 +43 -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 +17 -2
- package/package.json +29 -4
|
@@ -0,0 +1,889 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html><head><meta charset="utf-8">
|
|
3
|
+
<link rel="stylesheet" href="/assets/fonts/fonts.css">
|
|
4
|
+
<style>html,body{margin:0;background:#16130F;overflow:hidden}canvas{display:block}</style>
|
|
5
|
+
</head><body><canvas id="c" width="1920" height="1080"></canvas>
|
|
6
|
+
<script type="module">
|
|
7
|
+
// 06-collage — 「脑洞大开」THE MIND IS A COLLAGE. Canvas2D; every frame is a pure function of t.
|
|
8
|
+
// Motion is quantised to 12 fps steps (resting pieces boil at 6 fps) with seeded hand-placement jitter.
|
|
9
|
+
// Round 1: giant-hand hook, 3-pose puppet jaw, lid launch + hinge slap (brass brad, scalp band), POP burst,
|
|
10
|
+
// speech scraps, stepped rostrum camera, per-item spill actions, decluttered hero, page PEEL with kraft verso.
|
|
11
|
+
const W = 1920, H = 1080, D = '/demos/06-collage/';
|
|
12
|
+
window.DEMO = { width: W, height: H, fps: 30, duration: 10, motionBlur: { samples: 1, shutter: 0.5 } };
|
|
13
|
+
// reel v5 (?v5=1): the style name 拼·贴·剪·贴 is the title, window.linkAt(t) reports the link elements, ?nolink=dot hides the dot.
|
|
14
|
+
const QS = new URLSearchParams(location.search), V5 = QS.get('v5') === '1';
|
|
15
|
+
const NOLINK = new Set(V5 ? (QS.get('nolink') || '').split(',').map(s => s.trim()).filter(Boolean) : []);
|
|
16
|
+
const cv = document.getElementById('c'), X = cv.getContext('2d');
|
|
17
|
+
const C = await (await fetch(D + 'cues.json')).json();
|
|
18
|
+
const LAY = await (await fetch(D + 'assets/gen/layout.json')).json();
|
|
19
|
+
const load = src => new Promise((ok, no) => { const i = new Image(); i.onload = () => ok(i); i.onerror = () => no(src); i.src = src; });
|
|
20
|
+
const I = {};
|
|
21
|
+
const GEN = ['p_lid', 'p_jaw', 'p_face', 'space_wedge', 'earth', 'bird_hawk',
|
|
22
|
+
'rose_purple', 'rose_centi', 'bfly_a', 'bfly_b', 'rocket', 'flame0', 'flame1', 'news_a', 'news_b', 'manicule'];
|
|
23
|
+
await Promise.all([
|
|
24
|
+
...GEN.map(async n => I[n] = await load(D + 'assets/gen/' + n + '.png')),
|
|
25
|
+
(async () => I.kraft = await load(D + 'assets/gen/page_kraft.jpg'))(),
|
|
26
|
+
(async () => I.red = await load(D + 'assets/gen/page_red.jpg'))(),
|
|
27
|
+
(async () => I.grain = await load('/assets/textures/grain/grain_film35_2048.png'))(),
|
|
28
|
+
(async () => I.dust1 = await load('/assets/textures/dust/dust_01_3840x2160.png'))(),
|
|
29
|
+
(async () => I.dust2 = await load('/assets/textures/dust/dust_03_3840x2160.png'))(),
|
|
30
|
+
(async () => I.tapeW = await load('/assets/textures/washi/washi_solid_14.png'))(),
|
|
31
|
+
(async () => I.tapeK = await load('/assets/textures/washi/washi_solid_12.png'))(),
|
|
32
|
+
]);
|
|
33
|
+
await Promise.all(Object.values(I).map(i => i.decode()));
|
|
34
|
+
for (const f of ['400 100px "Anton"', '400 100px "Bebas Neue"', '700 100px "Alimama ShuHeiTi"', '900 100px "Noto Serif SC"',
|
|
35
|
+
'700 100px "Noto Sans SC"', '500 30px "IBM Plex Mono"', '600 30px "IBM Plex Mono"'])
|
|
36
|
+
await document.fonts.load(f, '脑洞大开季刊沿虚线剪开随刊附赠剪刀一把咳NOGGINAHEMBANG!0123456789' + (V5 ? '拼贴剪·COLLAGECUT-OUTQUARTERLY—¥' : ''));
|
|
37
|
+
await document.fonts.ready;
|
|
38
|
+
|
|
39
|
+
// ------------------------------------------------------------------ helpers
|
|
40
|
+
const PI = Math.PI, DEG = PI / 180;
|
|
41
|
+
const clamp = (x, a = 0, b = 1) => Math.min(b, Math.max(a, x));
|
|
42
|
+
const lerp = (a, b, u) => a + (b - a) * u;
|
|
43
|
+
const eOut = x => 1 - Math.pow(1 - clamp(x), 3);
|
|
44
|
+
const eIO = x => (x = clamp(x), x < .5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);
|
|
45
|
+
function hash(a, b = 0) { let h = (a * 374761393 + b * 668265263 + 1013904223) | 0; h = Math.imul(h ^ (h >>> 13), 1274126177); h ^= h >>> 16; return (h >>> 0) / 4294967296; }
|
|
46
|
+
const K = t => Math.floor(t * 12 + 1e-4); // 12 fps step index
|
|
47
|
+
const Q = t => K(t) / 12; // quantised time
|
|
48
|
+
const Q6 = t => Math.floor(t * 6 + 1e-4) / 6; // 6 fps (rostrum) time
|
|
49
|
+
const ks = (t, t0) => K(t) - Math.round(t0 * 12); // steps since cue
|
|
50
|
+
function jit(id, t, amp = 2, fps = 12) { // seeded hand-placement jitter (px, px, rad)
|
|
51
|
+
const k = Math.floor(t * fps + 1e-4);
|
|
52
|
+
return [(hash(id, k) * 2 - 1) * amp, (hash(id + 71, k) * 2 - 1) * amp, (hash(id + 913, k) * 2 - 1) * amp * 0.2 * DEG];
|
|
53
|
+
}
|
|
54
|
+
// slap: a piece drops from "camera" onto the page, hitting exactly on t0 (squash on impact, shadow collapses)
|
|
55
|
+
function slap(t, t0, pre = 3) {
|
|
56
|
+
const k = ks(t, t0);
|
|
57
|
+
if (k < -pre) return null;
|
|
58
|
+
if (k < 0) { const u = -k / pre; return { s: 1 + 0.38 * u * u + 0.05 * u, lift: u, k }; }
|
|
59
|
+
if (k === 0) return { s: 0.972, lift: 0, k, sq: 1 };
|
|
60
|
+
if (k === 1) return { s: 1.01, lift: 0.03, k };
|
|
61
|
+
return { s: 1, lift: 0, k };
|
|
62
|
+
}
|
|
63
|
+
function shadow(c, lift = 0, a = 0.36) {
|
|
64
|
+
c.shadowColor = `rgba(34,20,8,${a + 0.08 * Math.min(lift, 1)})`;
|
|
65
|
+
c.shadowBlur = 5 + lift * 34; c.shadowOffsetX = 3 + lift * 24; c.shadowOffsetY = 5 + lift * 34;
|
|
66
|
+
}
|
|
67
|
+
function noShadow(c) { c.shadowColor = 'transparent'; c.shadowBlur = 0; c.shadowOffsetX = 0; c.shadowOffsetY = 0; }
|
|
68
|
+
function piece(c, img, x, y, o = {}) {
|
|
69
|
+
const { rot = 0, s = 1, sx = 1, sy = 1, ax = .5, ay = .5, lift = 0, id = 1, t = 0, amp = 2, fps = 12, alpha = 1, flip = false, sh = true, skx = 0 } = o;
|
|
70
|
+
const [jx, jy, jr] = amp ? jit(id, t, amp, fps) : [0, 0, 0];
|
|
71
|
+
c.save(); c.translate(x + jx, y + jy); c.rotate(rot + jr); if (skx) c.transform(1, 0, skx, 1, 0, 0); c.scale(s * sx * (flip ? -1 : 1), s * sy);
|
|
72
|
+
if (sh) shadow(c, lift); c.globalAlpha = alpha;
|
|
73
|
+
c.drawImage(img, -img.width * ax, -img.height * ay);
|
|
74
|
+
c.restore();
|
|
75
|
+
}
|
|
76
|
+
const boil = (moving) => moving ? { amp: 2, fps: 12 } : { amp: 1.1, fps: 6 };
|
|
77
|
+
const off = (w, h) => { const c = document.createElement('canvas'); c.width = w; c.height = h; return c; };
|
|
78
|
+
function rimmed(img, r, col = '#F4EFE4') { // paper-white scissor rim: dilate the alpha by r px
|
|
79
|
+
const p = Math.ceil(r) + 2, c = off(img.width + 2 * p, img.height + 2 * p), x = c.getContext('2d');
|
|
80
|
+
for (let i = 0; i < 28; i++) { const a = i / 28 * 2 * PI; x.drawImage(img, p + Math.cos(a) * r, p + Math.sin(a) * r); }
|
|
81
|
+
x.globalCompositeOperation = 'source-in'; x.fillStyle = col; x.fillRect(0, 0, c.width, c.height);
|
|
82
|
+
x.globalCompositeOperation = 'source-over'; x.drawImage(img, p, p); c.pad = p; return c;
|
|
83
|
+
}
|
|
84
|
+
I.maniR = rimmed(I.manicule, 3.5);
|
|
85
|
+
const MT = [(LAY.manicule.tip[0] + I.maniR.pad) / I.maniR.width, (LAY.manicule.tip[1] + I.maniR.pad) / I.maniR.height];
|
|
86
|
+
const LIDR = rimmed(I.p_lid, 3.5);
|
|
87
|
+
const PAT = { red: X.createPattern(I.red, 'repeat'), news: X.createPattern(I.news_a, 'repeat'), kraft: X.createPattern(I.kraft, 'repeat') };
|
|
88
|
+
function cutQuad(c, w, h, seed, j = 5) {
|
|
89
|
+
const p = [[-w / 2, -h / 2], [w / 2, -h / 2], [w / 2, h / 2], [-w / 2, h / 2]];
|
|
90
|
+
c.beginPath();
|
|
91
|
+
p.forEach(([x, y], i) => { const dx = (hash(seed, i) * 2 - 1) * j, dy = (hash(seed + 5, i) * 2 - 1) * j; i ? c.lineTo(x + dx, y + dy) : c.moveTo(x + dx, y + dy); });
|
|
92
|
+
c.closePath();
|
|
93
|
+
}
|
|
94
|
+
function tornStrip(c, w, h, seed, rough = 10) {
|
|
95
|
+
c.beginPath(); c.moveTo(-w / 2, -h / 2);
|
|
96
|
+
for (let i = 1; i <= 24; i++) c.lineTo(-w / 2 + w * i / 24, -h / 2 + (hash(seed, i) - .5) * 3);
|
|
97
|
+
for (let i = 1; i <= 10; i++) c.lineTo(w / 2 + (hash(seed + 1, i) - .5) * rough * 2, -h / 2 + h * i / 10);
|
|
98
|
+
for (let i = 1; i <= 24; i++) c.lineTo(w / 2 - w * i / 24, h / 2 + (hash(seed + 2, i) - .5) * 3);
|
|
99
|
+
for (let i = 1; i < 10; i++) c.lineTo(-w / 2 + (hash(seed + 3, i) - .5) * rough * 2, h / 2 - h * i / 10);
|
|
100
|
+
c.closePath();
|
|
101
|
+
}
|
|
102
|
+
// paper fibre / dust flecks puffing out of an impact for 2 steps (k = 0, 1). pts: [x, y, dirx, diry]
|
|
103
|
+
function flecks(c, pts, k, seed, n = 6) {
|
|
104
|
+
if (k < 0 || k > 1) return;
|
|
105
|
+
c.save(); noShadow(c); c.lineCap = 'round';
|
|
106
|
+
for (let i = 0; i < n; i++) {
|
|
107
|
+
const [px, py, dx, dy] = pts[i % pts.length];
|
|
108
|
+
const a = Math.atan2(dy, dx) + (hash(seed, i) - .5) * 1.1, d = (12 + hash(seed + 1, i) * 22) * (k + 1);
|
|
109
|
+
const x = px + Math.cos(a) * d, y = py + Math.sin(a) * d;
|
|
110
|
+
c.globalAlpha = k ? 0.45 : 0.92;
|
|
111
|
+
if (i % 3 === 2) { c.fillStyle = 'rgba(30,20,10,.85)'; c.beginPath(); c.arc(x, y, 2 + hash(seed + 2, i) * 2.5, 0, 2 * PI); c.fill(); }
|
|
112
|
+
else {
|
|
113
|
+
const L = 10 + hash(seed + 3, i) * 14, b = a + (hash(seed + 4, i) - .5) * 2.2;
|
|
114
|
+
c.strokeStyle = '#F3EDDF'; c.lineWidth = 2.6;
|
|
115
|
+
c.beginPath(); c.moveTo(x, y); c.quadraticCurveTo(x + Math.cos(b) * L * .6, y + Math.sin(b) * L * .6, x + Math.cos(a) * L, y + Math.sin(a) * L); c.stroke();
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
c.restore();
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ------------------------------------------------------------------ portrait rig (local coords = piece px)
|
|
122
|
+
const PL = LAY.portrait, PW = LAY.p_face.w, PH = LAY.p_face.h;
|
|
123
|
+
const OFF = [845 - PL.head_center[0], 554 - PL.head_center[1]]; // local -> main-page screen (at rest)
|
|
124
|
+
const CUT = PL.cut, JAWH = PL.jaw_hinge;
|
|
125
|
+
const cutY = x => { for (let i = 0; i < CUT.length - 1; i++) { const [x0, y0] = CUT[i], [x1, y1] = CUT[i + 1]; if (x <= x1) return lerp(y0, y1, clamp((x - x0) / (x1 - x0))); } return CUT[CUT.length - 1][1]; };
|
|
126
|
+
const WEDGE_BASE = [PL.head_center[0], LAY.space_wedge.anchor[1] + LAY.space_wedge.y0 - OFF[1]];
|
|
127
|
+
const alphaOf = img => { const c = off(PW, PH), x = c.getContext('2d'); x.drawImage(img, 0, 0); return x.getImageData(0, 0, PW, PH).data; };
|
|
128
|
+
const AL = alphaOf(I.p_lid), AF = alphaOf(I.p_face), AJ = alphaOf(I.p_jaw);
|
|
129
|
+
const ext = (A, dy) => { let a = 1e9, b = -1e9; for (let x = 0; x < PW; x++) { const y = Math.round(cutY(x) + dy); if (y >= 0 && y < PH && A[(y * PW + x) * 4 + 3] > 128) { a = Math.min(a, x); b = Math.max(b, x); } } return [a, b]; };
|
|
130
|
+
const LX = ext(AL, -5), FX = ext(AF, 5);
|
|
131
|
+
const HG = [LX[0] + 26, cutY(LX[0] + 26) - 7]; // lid hinge = brass brad at the temple
|
|
132
|
+
// jaw top edge (the scissor cut between moustache and beard), sampled every 4 px
|
|
133
|
+
const JEDGE = []; for (let x = 216; x <= 544; x += 4) { for (let y = 430; y < 700; y++) if (AJ[(y * PW + x) * 4 + 3] > 128) { JEDGE.push([x, y]); break; } }
|
|
134
|
+
const edgeAt = x => { let best = JEDGE[0]; for (const p of JEDGE) if (Math.abs(p[0] - x) < Math.abs(best[0] - x)) best = p; return best[1]; };
|
|
135
|
+
// jaw with a faceted white scissor rim on its upper cut
|
|
136
|
+
const cJaw = off(PW, PH); { const x = cJaw.getContext('2d'); const fac = JEDGE.filter((_, i) => i % 6 === 0).concat([JEDGE[JEDGE.length - 1]]);
|
|
137
|
+
x.lineJoin = 'miter'; x.strokeStyle = '#F4EFE4'; x.lineWidth = 11; x.beginPath(); fac.forEach(([px, py], i) => i ? x.lineTo(px, py - 1) : x.moveTo(px, py - 1)); x.stroke();
|
|
138
|
+
x.drawImage(I.p_jaw, 0, 0); }
|
|
139
|
+
// mouth cavity: jaw silhouette at rest, near-black with a throat glow, a teeth strip under the lip and a 10 px inner shadow
|
|
140
|
+
const cCav = off(PW, PH); { const x = cCav.getContext('2d'); x.drawImage(I.p_jaw, 0, 0); x.globalCompositeOperation = 'source-in'; x.fillStyle = '#0E0B08'; x.fillRect(0, 0, PW, PH);
|
|
141
|
+
x.globalCompositeOperation = 'source-atop';
|
|
142
|
+
const g = x.createRadialGradient(390, 575, 8, 390, 575, 120); g.addColorStop(0, 'rgba(96,26,20,.85)'); g.addColorStop(1, 'rgba(96,26,20,0)'); x.fillStyle = g; x.fillRect(0, 0, PW, PH);
|
|
143
|
+
x.beginPath(); for (let px = 276; px <= 474; px += 6) x.lineTo(px, edgeAt(px) + 2); for (let px = 474; px >= 276; px -= 6) x.lineTo(px, edgeAt(px) + 17); x.closePath(); x.fillStyle = '#E4DAC4'; x.fill();
|
|
144
|
+
x.strokeStyle = 'rgba(14,11,8,.75)'; x.lineWidth = 2.2; for (let px = 290; px < 474; px += 21) { x.beginPath(); x.moveTo(px, edgeAt(px) + 4); x.lineTo(px + 1, edgeAt(px) + 17); x.stroke(); }
|
|
145
|
+
x.shadowColor = 'rgba(0,0,0,.9)'; x.shadowBlur = 10; x.strokeStyle = 'rgba(8,6,4,.9)'; x.lineWidth = 7;
|
|
146
|
+
x.beginPath(); JEDGE.forEach(([px, py], i) => i ? x.lineTo(px, py - 3) : x.moveTo(px, py - 3)); x.stroke(); }
|
|
147
|
+
const cLid = off(PW, PH), cFace = off(PW, PH);
|
|
148
|
+
const xLid = cLid.getContext('2d'), xFace = cFace.getContext('2d');
|
|
149
|
+
const cutPath = (cx, x0, x1, dy0, dy1) => { cx.beginPath(); for (let x = x0; x <= x1; x += 8) cx.lineTo(x, cutY(x) + dy0); for (let x = x1; x >= x0; x -= 8) cx.lineTo(x, cutY(x) + dy1); cx.closePath(); };
|
|
150
|
+
// print dashed coupon line, the snipped gap and (after the POP) the skull-band cut edge onto lid & face
|
|
151
|
+
function bakeHead(stamp, snipX, band) {
|
|
152
|
+
for (const [cx, img, isLid] of [[xLid, I.p_lid, 1], [xFace, I.p_face, 0]]) {
|
|
153
|
+
cx.globalCompositeOperation = 'source-over'; cx.clearRect(0, 0, PW, PH); if (isLid) cx.drawImage(LIDR, -LIDR.pad, -LIDR.pad); else cx.drawImage(img, 0, 0);
|
|
154
|
+
if (!stamp) continue;
|
|
155
|
+
cx.globalCompositeOperation = 'source-atop';
|
|
156
|
+
cx.lineCap = 'butt'; cx.lineJoin = 'round';
|
|
157
|
+
cx.strokeStyle = `rgba(22,19,15,${0.82 * stamp})`; cx.lineWidth = 5; cx.setLineDash([17, 11]);
|
|
158
|
+
cx.beginPath(); CUT.forEach(([x, y], i) => i ? cx.lineTo(x, y) : cx.moveTo(x, y)); cx.stroke();
|
|
159
|
+
cx.setLineDash([]);
|
|
160
|
+
if (snipX > CUT[0][0]) {
|
|
161
|
+
cx.strokeStyle = 'rgba(18,12,8,.95)'; cx.lineWidth = 3.2;
|
|
162
|
+
cx.beginPath(); cx.moveTo(CUT[0][0], CUT[0][1]);
|
|
163
|
+
for (let x = CUT[0][0]; x <= snipX; x += 8) cx.lineTo(x, cutY(x));
|
|
164
|
+
cx.stroke();
|
|
165
|
+
}
|
|
166
|
+
if (band) { // cut edge of the skull: 18 px bone band + printed dashes + white scissor rim
|
|
167
|
+
const [x0, x1] = isLid ? LX : FX, s = isLid ? -1 : 1;
|
|
168
|
+
cutPath(cx, x0 - 20, x1 + 20, 0, 18 * s); cx.fillStyle = '#E3CFAE'; cx.fill();
|
|
169
|
+
cx.strokeStyle = 'rgba(150,118,80,.9)'; cx.lineWidth = 2; cx.beginPath(); for (let x = x0 - 20; x <= x1 + 20; x += 8) cx.lineTo(x, cutY(x) + 18 * s); cx.stroke();
|
|
170
|
+
cx.strokeStyle = 'rgba(22,19,15,.8)'; cx.lineWidth = 3.4; cx.setLineDash([14, 9]);
|
|
171
|
+
cx.beginPath(); for (let x = x0; x <= x1; x += 8) cx.lineTo(x, cutY(x) + 9 * s); cx.stroke(); cx.setLineDash([]);
|
|
172
|
+
cx.globalCompositeOperation = 'source-over';
|
|
173
|
+
cutPath(cx, x0 + 2, x1 - 2, 0, -5 * s); cx.fillStyle = '#F4EFE4'; cx.fill();
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
// spilled items: final positions in MAIN-PAGE screen coords, launched from the skull at their cue; each has its own action
|
|
178
|
+
const SPILL = [
|
|
179
|
+
{ n: 'rocket', t: C.rocket, F: [1300, 204], r: 0.62, mode: 'launch', id: 31, drift: [26, -20] },
|
|
180
|
+
{ n: 'earth', t: C.earth, F: [712, 168], r: -0.22, r0: 1.1, arc: -330, mode: 'lob', id: 32 },
|
|
181
|
+
{ n: 'bird_hawk', t: C.birds[0], F: [1560, 196], r: -0.10, r0: 0.35, mode: 'swoop', id: 33 },
|
|
182
|
+
{ n: 'rose_centi', t: C.roses, F: [668, 560], r: -0.34, r0: -0.05, mode: 'grow', ay: 0.93, id: 36 },
|
|
183
|
+
{ n: 'rose_purple', t: C.roses + 1 / 12, F: [1040, 560], r: 0.42, r0: 0.05, mode: 'grow', ay: 0.95, id: 37 },
|
|
184
|
+
{ n: 'bfly_a', t: C.butterflies, F: [1196, 404], r: 0.25, r0: -0.8, arc: -150, mode: 'flutter', id: 38 },
|
|
185
|
+
{ n: 'bfly_b', t: C.butterflies + 1 / 12, F: [196, 150], r: -0.3, r0: 0.6, arc: -170, mode: 'flutter', id: 39 },
|
|
186
|
+
];
|
|
187
|
+
const MOUTH = [PL.head_center[0], PL.cut[3][1] + 10];
|
|
188
|
+
const bez = (a, b, c, u) => (1 - u) * (1 - u) * a + 2 * (1 - u) * u * b + u * u * c;
|
|
189
|
+
function puff(c, x, y, r, seed, tl) { // cut-paper smoke puff
|
|
190
|
+
const [jx, jy] = jit(seed, tl, 1.2, 6);
|
|
191
|
+
c.save(); c.translate(x + jx, y + jy);
|
|
192
|
+
const lobes = [[0, 0, 1], [-.62, .18, .7], [.6, .22, .66], [.05, -.45, .62]];
|
|
193
|
+
c.beginPath(); for (const [ox, oy, rr] of lobes) { c.moveTo(ox * r + rr * r + 6, oy * r); c.arc(ox * r, oy * r, rr * r + 6, 0, 2 * PI); }
|
|
194
|
+
shadow(c, 0.15, 0.3); c.fillStyle = '#FBF7EE'; c.fill(); noShadow(c);
|
|
195
|
+
c.beginPath(); for (const [ox, oy, rr] of lobes) { c.moveTo(ox * r + rr * r, oy * r); c.arc(ox * r, oy * r, rr * r, 0, 2 * PI); }
|
|
196
|
+
c.fillStyle = '#DCD3C1'; c.fill();
|
|
197
|
+
c.restore();
|
|
198
|
+
}
|
|
199
|
+
function drawSpill(c, t, it, tl = t) {
|
|
200
|
+
const k = ks(t, it.t); if (k < 0) return;
|
|
201
|
+
const img = I[it.n], Fl = [it.F[0] - OFF[0], it.F[1] - OFF[1]], M = MOUTH;
|
|
202
|
+
let x, y, s, r, sx = 1, sy = 1, lift = 0.12, skx = 0, moving = false;
|
|
203
|
+
if (it.mode === 'grow') { // roses sprout out of the skull with a 1.08 overshoot
|
|
204
|
+
const g = [0.35, 0.72, 0.96, 1.08, 1.0][Math.min(k, 4)];
|
|
205
|
+
x = Fl[0]; y = Fl[1] + (1 - Math.min(g, 1)) * 250; s = g; r = lerp(it.r0, it.r, Math.min(1, k / 3)); lift = 0.1; moving = k < 4;
|
|
206
|
+
} else if (it.mode === 'launch') { // rocket: 4-step straight launch, flame substitution + paper smoke trail
|
|
207
|
+
const U = [0.08, 0.28, 0.6, 1], fl = 4, u = k < fl ? U[k] : 1;
|
|
208
|
+
x = lerp(M[0], Fl[0], u); y = lerp(M[1], Fl[1], u);
|
|
209
|
+
s = k < fl ? [0.5, 0.7, 0.9, 1][k] : (k === fl ? 1.06 : 1); r = k < fl ? 0.95 : it.r; lift = k < fl ? 0.9 - k * 0.15 : 0.12; moving = k <= fl;
|
|
210
|
+
if (k >= fl) { const a = Q(t) - it.t - fl / 12; x += it.drift[0] * a; y += it.drift[1] * a; }
|
|
211
|
+
const dx = Fl[0] - M[0], dy = Fl[1] - M[1], dl = Math.hypot(dx, dy);
|
|
212
|
+
for (let i = 0; i < 4; i++) { // puff i appears at step i, held 6 steps
|
|
213
|
+
const kk = k - i; if (kk < 0 || kk >= 6) continue;
|
|
214
|
+
const u2 = U[i], px = lerp(M[0], Fl[0], u2) - dx / dl * 150, py = lerp(M[1], Fl[1], u2) - dy / dl * 150;
|
|
215
|
+
puff(c, px, py, (22 + i * 6) * [0.6, 1, 1.08, 1.08, 1.05, 1][kk], 120 + i, tl);
|
|
216
|
+
}
|
|
217
|
+
} else {
|
|
218
|
+
const fl = 6, u = clamp(k / fl), e = eOut(u); moving = k <= fl;
|
|
219
|
+
let cx = (M[0] + Fl[0]) / 2, cy = Math.min(M[1], Fl[1]) + (it.arc || -200);
|
|
220
|
+
if (it.mode === 'swoop') { cx = Fl[0] + 60; cy = M[1] + 170; } // hawk dives low, then climbs
|
|
221
|
+
x = bez(M[0], cx, Fl[0], e); y = bez(M[1], cy, Fl[1], e);
|
|
222
|
+
if (it.mode === 'flutter' && k < fl) { const nx = -(Fl[1] - M[1]), ny = Fl[0] - M[0], nl = Math.hypot(nx, ny); const w = Math.sin(k * 2.3) * 46 * (1 - u); x += nx / nl * w; y += ny / nl * w; }
|
|
223
|
+
s = k < fl ? lerp(0.35, 1, e) : (k === fl ? 1.06 : 1); r = lerp(it.r0, it.r, e); lift = (1 - e) * 0.8 + 0.12;
|
|
224
|
+
if (it.mode === 'lob' && k >= fl) { r = it.r + (Math.floor(tl * 6 + 1e-4) % 2 ? 6 : -6) * DEG; if (k === fl) { sx = 1.04; sy = 0.95; } }
|
|
225
|
+
if (it.mode === 'swoop' && Math.floor(tl * 6 + 1e-4) % 2) { sy = 0.74; skx = -0.12; r += 3 * DEG; } // 2 wing poses @ 6 fps
|
|
226
|
+
if (it.mode === 'flutter') sx = k < fl ? [1, 0.35][K(tl) % 2] : [1, 0.58][Math.floor(tl * 6 + 1e-4) % 2];
|
|
227
|
+
}
|
|
228
|
+
if (it.n === 'rocket') { // flame flicker (replacement frames, 12 fps)
|
|
229
|
+
const f = I[K(tl) % 2 ? 'flame0' : 'flame1'];
|
|
230
|
+
c.save(); c.translate(x, y); c.rotate(r); c.scale(s, s);
|
|
231
|
+
piece(c, f, 0, img.height * 0.46, { ay: 0, id: it.id + 50, t: tl, amp: 1.5, sh: false, sy: k < 4 ? 1.35 : 1 });
|
|
232
|
+
c.restore();
|
|
233
|
+
}
|
|
234
|
+
piece(c, img, x, y, { rot: r, s, sx, sy, skx, ay: it.ay ?? .5, lift, id: it.id, t: tl, ...boil(moving) });
|
|
235
|
+
}
|
|
236
|
+
function brad(c, x, y) { // brass paper fastener (the puppet joint)
|
|
237
|
+
c.save(); shadow(c, 0.05, 0.5);
|
|
238
|
+
const g = c.createRadialGradient(x - 5, y - 6, 1, x, y, 16); g.addColorStop(0, '#FFE9A6'); g.addColorStop(0.35, '#D6A945'); g.addColorStop(1, '#7A5516');
|
|
239
|
+
c.beginPath(); c.arc(x, y, 15, 0, 2 * PI); c.fillStyle = g; c.fill(); noShadow(c);
|
|
240
|
+
c.lineWidth = 1.6; c.strokeStyle = 'rgba(60,38,8,.9)'; c.stroke();
|
|
241
|
+
c.beginPath(); c.arc(x, y, 9, 3.6, 5.2); c.strokeStyle = 'rgba(255,245,210,.8)'; c.lineWidth = 2; c.stroke();
|
|
242
|
+
c.restore();
|
|
243
|
+
}
|
|
244
|
+
// full head: wedge -> lid -> spill -> cavity -> face -> jaw -> brad (lid on top while it flies)
|
|
245
|
+
function drawHead(c, t, S) {
|
|
246
|
+
bakeHead(S.stamp, S.snipX, S.band);
|
|
247
|
+
c.save();
|
|
248
|
+
c.translate(S.x, S.y); c.rotate(S.rot); c.scale(S.s * (S.sqx || 1), S.s * (S.sqy || 1));
|
|
249
|
+
c.translate(-PL.head_center[0], -PL.head_center[1]);
|
|
250
|
+
const lift = S.lift || 0;
|
|
251
|
+
if (S.wedge) {
|
|
252
|
+
const [wx, wy, smear] = S.wedge;
|
|
253
|
+
const dw = (sx, sy, a) => { c.save(); c.globalAlpha = a; c.translate(WEDGE_BASE[0], WEDGE_BASE[1]); c.scale(sx, sy); c.translate(-WEDGE_BASE[0], -WEDGE_BASE[1]);
|
|
254
|
+
if (a === 1) shadow(c, 0.1, 0.3); c.drawImage(I.space_wedge, -OFF[0], LAY.space_wedge.y0 - OFF[1]); c.restore(); noShadow(c); };
|
|
255
|
+
dw(wx, wy, 1);
|
|
256
|
+
if (smear) { dw(wx * 0.97, wy * 0.78, 0.7); dw(wx * 0.93, wy * 0.52, 0.5); } // stop-motion smear (streak copies)
|
|
257
|
+
}
|
|
258
|
+
const L = S.lidT || { a: 0, dx: 0, dy: 0 };
|
|
259
|
+
const drawLid = () => { c.save(); c.translate(HG[0] + (L.dx || 0), HG[1] + (L.dy || 0)); c.rotate(L.a || 0); c.scale(L.sx || 1, L.sy || 1); c.translate(-HG[0], -HG[1]);
|
|
260
|
+
shadow(c, lift + (L.lift || 0)); c.drawImage(cLid, 0, 0); c.restore(); noShadow(c); };
|
|
261
|
+
if (!L.top) drawLid();
|
|
262
|
+
if (S.spill) for (const it of SPILL) if (!S.only || S.only.includes(it.n)) drawSpill(c, S.spillT, it, t);
|
|
263
|
+
noShadow(c);
|
|
264
|
+
c.drawImage(cCav, 0, 0);
|
|
265
|
+
shadow(c, lift); c.drawImage(cFace, 0, 0);
|
|
266
|
+
const j = S.jaw || 0; // 3 replacement poses: 0 closed / .5 half / 1 wide
|
|
267
|
+
c.save(); c.translate(JAWH[0], JAWH[1] + j * 92); c.rotate(j * 9.5 * DEG); c.translate(-JAWH[0], -JAWH[1]);
|
|
268
|
+
shadow(c, lift + j * 0.25); c.drawImage(j > 0 ? cJaw : I.p_jaw, 0, 0); c.restore(); noShadow(c);
|
|
269
|
+
if (L.top) drawLid();
|
|
270
|
+
brad(c, HG[0], HG[1]);
|
|
271
|
+
c.restore(); noShadow(c);
|
|
272
|
+
}
|
|
273
|
+
const toScreen = (lx, ly) => [lx + OFF[0], ly + OFF[1]];
|
|
274
|
+
const lidRest = (lx, ly, a = -95 * DEG) => { const dx = lx - HG[0], dy = ly - HG[1]; return toScreen(HG[0] + dx * Math.cos(a) - dy * Math.sin(a), HG[1] + dx * Math.sin(a) + dy * Math.cos(a)); };
|
|
275
|
+
|
|
276
|
+
// ------------------------------------------------------------------ scissors (two cut-paper blades on a pivot)
|
|
277
|
+
function scissors(c, x, y, rot, open, s = 1) {
|
|
278
|
+
c.save(); c.translate(x, y); c.rotate(rot); c.scale(s, s);
|
|
279
|
+
for (const pass of [0, 1]) for (const sgn of [-1, 1]) {
|
|
280
|
+
c.save(); c.rotate(sgn * open);
|
|
281
|
+
c.beginPath(); c.moveTo(-8, 0); c.lineTo(110, 0); c.lineTo(86, 5 * sgn); c.lineTo(30, 10 * sgn); c.lineTo(-8, 9 * sgn); c.closePath();
|
|
282
|
+
c.lineWidth = pass ? 0 : 11; c.strokeStyle = '#F2ECDF'; c.fillStyle = '#16130F'; c.lineJoin = 'miter';
|
|
283
|
+
if (!pass) { shadow(c, 0.25); c.stroke(); noShadow(c); } else c.fill();
|
|
284
|
+
c.beginPath(); c.ellipse(-50, 18 * -sgn, 22, 15, 0.25 * sgn, 0, 2 * PI);
|
|
285
|
+
c.lineWidth = pass ? 9 : 19; c.strokeStyle = pass ? (sgn > 0 ? '#D2261C' : '#16130F') : '#F2ECDF'; c.stroke();
|
|
286
|
+
c.beginPath(); c.moveTo(-4, 0); c.lineTo(-32, 11 * -sgn); c.lineWidth = pass ? 8 : 18; c.stroke();
|
|
287
|
+
c.restore();
|
|
288
|
+
}
|
|
289
|
+
c.beginPath(); c.arc(0, 0, 4, 0, 2 * PI); c.fillStyle = '#F2ECDF'; c.fill();
|
|
290
|
+
c.restore();
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// ------------------------------------------------------------------ type pieces
|
|
294
|
+
function chip(c, S, ch, font, size, bg, fg, w, h, seed, rot, x, y, t) {
|
|
295
|
+
const [jx, jy, jr] = jit(seed, t, S.lift > 0 ? 2 : 1.1, S.lift > 0 ? 12 : 6);
|
|
296
|
+
c.save(); c.translate(x + jx, y + jy); c.rotate(rot + jr); c.scale(S.s, S.s);
|
|
297
|
+
cutQuad(c, w + 10, h + 10, seed, 5); c.fillStyle = '#F2ECDF'; shadow(c, S.lift); c.fill(); noShadow(c);
|
|
298
|
+
cutQuad(c, w, h, seed + 9, 4); c.fillStyle = bg; c.fill();
|
|
299
|
+
if (bg === PAT.news) { c.fillStyle = 'rgba(236,229,214,.55)'; c.fill(); }
|
|
300
|
+
c.fillStyle = fg; c.font = `${font.w} ${size}px "${font.f}"`; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
301
|
+
c.fillText(ch, font.dx || 0, font.dy || 0);
|
|
302
|
+
c.restore();
|
|
303
|
+
}
|
|
304
|
+
function tape(c, img, x, y, w, rot, seed, t) {
|
|
305
|
+
const [jx, jy, jr] = jit(seed, t, 1, 6);
|
|
306
|
+
c.save(); c.translate(x + jx, y + jy); c.rotate(rot + jr); shadow(c, 0, 0.18);
|
|
307
|
+
c.globalAlpha = 0.92; c.drawImage(img, 0, 0, 700, 300, -w / 2, -26, w, 52); c.restore();
|
|
308
|
+
}
|
|
309
|
+
function monoText(c, s, x, y, size = 19, color = 'rgba(22,19,15,.82)', ls = 3, align = 'left') {
|
|
310
|
+
c.save(); c.font = `500 ${size}px "IBM Plex Mono"`; c.letterSpacing = ls + 'px'; c.fillStyle = color; c.textAlign = align; c.textBaseline = 'alphabetic';
|
|
311
|
+
c.fillText(s, x, y); c.restore();
|
|
312
|
+
}
|
|
313
|
+
// ransom-note speech scraps (the portrait "talks", act 1)
|
|
314
|
+
const SPEECH = [
|
|
315
|
+
{ t: C.jaw[0], txt: 'AHEM', f: '400 124px "Anton"', ls: 4, bg: 'news', fg: '#16130F', w: 336, h: 156, x: 1236, y: 612, r: -8, seed: 81, dy: 8 },
|
|
316
|
+
{ t: C.jaw[1], txt: '咳咳', f: '700 116px "Alimama ShuHeiTi"', ls: 2, bg: 'red', fg: '#F2ECDF', w: 296, h: 160, x: 1500, y: 458, r: 5, seed: 82, dy: 6 },
|
|
317
|
+
{ t: C.jaw[2], txt: '!', f: '400 204px "Bebas Neue"', ls: 0, bg: 'ink', fg: '#F2ECDF', w: 118, h: 206, x: 1730, y: 330, r: -3, seed: 83, dy: 12 },
|
|
318
|
+
];
|
|
319
|
+
function speechScrap(c, sp, i, t) {
|
|
320
|
+
const s = slap(t, sp.t, 3); if (!s) return;
|
|
321
|
+
const kt = ks(t, C.stamp_cut); if (kt >= 3) return; // torn away when the stamp hits
|
|
322
|
+
const O = kt < 0 ? [0, 0, 0, 0] : [[40 + i * 24, -36, 9, 0.7], [300 + i * 70, -80 + i * 50, 27, 1.3], [800, 40 + i * 90, 50, 1.8]][kt];
|
|
323
|
+
const [jx, jy, jr] = jit(sp.seed, t, s.lift || kt >= 0 ? 2 : 1.1, s.lift || kt >= 0 ? 12 : 6);
|
|
324
|
+
const bg = sp.bg === 'news' ? PAT.news : sp.bg === 'red' ? PAT.red : '#16130F';
|
|
325
|
+
c.save(); c.translate(sp.x + 34 * s.lift + O[0] + jx, sp.y - 40 * s.lift + O[1] + jy); c.rotate((sp.r + O[2]) * DEG + jr); c.scale(s.s, s.s);
|
|
326
|
+
const lift = Math.max(s.lift, O[3]);
|
|
327
|
+
// speech tail aimed at the mouth (computed in the scrap's local frame)
|
|
328
|
+
const [mx, my] = toScreen(...PL.mouth), ra = -(sp.r + O[2]) * DEG - jr, vx0 = mx - (sp.x + O[0]), vy0 = my - (sp.y + O[1]);
|
|
329
|
+
const vx = vx0 * Math.cos(ra) - vy0 * Math.sin(ra), vy = vx0 * Math.sin(ra) + vy0 * Math.cos(ra), vl = Math.hypot(vx, vy), ux = vx / vl, uy = vy / vl;
|
|
330
|
+
const tb = Math.min((sp.w / 2 - 16) / Math.abs(ux || 1e-6), (sp.h / 2 - 16) / Math.abs(uy || 1e-6)), bx0 = ux * tb, by0 = uy * tb, TL = sp.w < 150 ? 64 : 84;
|
|
331
|
+
const tail = (g) => { c.beginPath(); c.moveTo(bx0 - uy * (22 - g), by0 + ux * (22 - g)); c.lineTo(bx0 + ux * (TL - g * 0.6), by0 + uy * (TL - g * 0.6)); c.lineTo(bx0 + uy * (22 - g), by0 - ux * (22 - g)); c.closePath(); };
|
|
332
|
+
tail(0); c.lineJoin = 'miter'; c.lineWidth = 12; c.strokeStyle = '#F2ECDF'; shadow(c, lift); c.stroke(); noShadow(c); c.fillStyle = bg; c.fill();
|
|
333
|
+
cutQuad(c, sp.w + 12, sp.h + 12, sp.seed, 5); c.fillStyle = '#F2ECDF'; shadow(c, lift); c.fill(); noShadow(c);
|
|
334
|
+
cutQuad(c, sp.w, sp.h, sp.seed + 9, 4); c.fillStyle = bg; c.fill();
|
|
335
|
+
if (sp.bg === 'news') { c.fillStyle = 'rgba(236,229,214,.6)'; c.fill(); }
|
|
336
|
+
tail(5); c.fillStyle = bg; c.fill(); if (sp.bg === 'news') { c.fillStyle = 'rgba(236,229,214,.6)'; c.fill(); }
|
|
337
|
+
c.fillStyle = sp.fg; c.font = sp.f; c.letterSpacing = sp.ls + 'px'; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(sp.txt, 2, sp.dy);
|
|
338
|
+
c.restore();
|
|
339
|
+
}
|
|
340
|
+
// POP burst: newsprint confetti, halftone dots, riso stars and a BANG! ransom word on 12 fps parabolas
|
|
341
|
+
const SCRAPS = Array.from({ length: 13 }, (_, i) => {
|
|
342
|
+
const type = (i === 3 || i === 10) ? 'star' : (i % 3 === 1 ? 'dots' : 'news');
|
|
343
|
+
const a = type === 'bang' ? -32 * DEG : lerp(-168, -12, (i + hash(401, i) * 0.7) / 13.2) * DEG;
|
|
344
|
+
return { type, a, sp: type === 'bang' ? 1780 : 2150 + hash(402, i) * 850, sz: 40 + hash(403, i) * 50, spin: type === 'bang' ? 0.9 : (hash(404, i) - .5) * 16, r0: type === 'bang' ? -0.14 : hash(405, i) * 6,
|
|
345
|
+
seed: 410 + i, delay: type === 'bang' ? 0 : i % 3, ox: (hash(406, i) - .5) * 320 };
|
|
346
|
+
});
|
|
347
|
+
const BANG = [['B', '400 58px "Anton"', 'news', '#16130F'], ['A', '400 62px "Bebas Neue"', 'red', '#F2ECDF'], ['N', '900 52px "Noto Serif SC"', 'white', '#16130F'],
|
|
348
|
+
['G', '400 58px "Anton"', 'ink', '#F2ECDF'], ['!', '400 60px "Anton"', 'red', '#16130F']];
|
|
349
|
+
function drawScraps(c, t) {
|
|
350
|
+
const kb = ks(t, C.pop); if (kb < 0 || kb > 10) return;
|
|
351
|
+
const [ox, oy] = toScreen(PL.head_center[0], PL.cut[3][1]);
|
|
352
|
+
for (const s of SCRAPS) {
|
|
353
|
+
const k = kb - s.delay; if (k < 0) continue;
|
|
354
|
+
const tau = (k + 1) / 12;
|
|
355
|
+
const x = ox + s.ox * 0.4 + Math.cos(s.a) * s.sp * tau, y = oy + Math.sin(s.a) * s.sp * tau + 0.5 * 1800 * tau * tau;
|
|
356
|
+
if (x < -260 || x > W + 260 || y < -200 || y > H + 200) continue;
|
|
357
|
+
c.save(); c.translate(x, y); c.rotate(s.r0 + s.spin * tau); shadow(c, 0.7, 0.3);
|
|
358
|
+
const z = s.sz;
|
|
359
|
+
if (s.type === 'news') { cutQuad(c, z + 8, z * 0.66 + 8, s.seed, 4); c.fillStyle = '#F2ECDF'; c.fill(); noShadow(c); cutQuad(c, z, z * 0.66, s.seed + 3, 3); c.fillStyle = PAT.news; c.fill(); }
|
|
360
|
+
else if (s.type === 'dots') {
|
|
361
|
+
c.beginPath(); c.arc(0, 0, z / 2 + 4, 0, 2 * PI); c.fillStyle = '#F2ECDF'; c.fill(); noShadow(c);
|
|
362
|
+
c.beginPath(); c.arc(0, 0, z / 2, 0, 2 * PI); c.fillStyle = i2c(s.seed); c.fill(); c.save(); c.clip(); c.fillStyle = '#16130F';
|
|
363
|
+
for (let gx = -z / 2; gx <= z / 2; gx += 7) for (let gy = -z / 2; gy <= z / 2; gy += 7) { const rr = 3.2 * (1 - Math.hypot(gx + z * .3, gy + z * .3) / (z * 1.3)); if (rr > .3) { c.beginPath(); c.arc(gx, gy, rr, 0, 2 * PI); c.fill(); } }
|
|
364
|
+
c.restore();
|
|
365
|
+
} else if (s.type === 'star') {
|
|
366
|
+
c.beginPath(); for (let q = 0; q < 10; q++) { const rr = q % 2 ? z * 0.28 : z * 0.62, aa = q / 10 * 2 * PI - PI / 2; q ? c.lineTo(Math.cos(aa) * rr, Math.sin(aa) * rr) : c.moveTo(Math.cos(aa) * rr, Math.sin(aa) * rr); }
|
|
367
|
+
c.closePath(); c.lineJoin = 'miter'; c.lineWidth = 9; c.strokeStyle = '#F2ECDF'; c.stroke(); noShadow(c); c.fillStyle = PAT.red; c.fill();
|
|
368
|
+
} else { // BANG! ransom word
|
|
369
|
+
noShadow(c); c.scale(1.45, 1.45);
|
|
370
|
+
BANG.forEach(([ch, f, bg, fg], q) => {
|
|
371
|
+
c.save(); c.translate(-120 + q * 60, (q % 2 ? 6 : -6)); c.rotate((q % 2 ? 6 : -5) * DEG);
|
|
372
|
+
cutQuad(c, 60, 74, 470 + q, 3); c.fillStyle = '#F2ECDF'; shadow(c, 0.6); c.fill(); noShadow(c);
|
|
373
|
+
cutQuad(c, 52, 66, 480 + q, 3); c.fillStyle = bg === 'news' ? PAT.news : bg === 'red' ? PAT.red : bg === 'ink' ? '#16130F' : '#F2ECDF'; c.fill();
|
|
374
|
+
c.fillStyle = fg; c.font = f; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(ch, 0, 4); c.restore();
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
c.restore(); noShadow(c);
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
// BANG! ransom word: its own path over the burst (1000,420) -> (1500,170), 0.8 -> 1.25 -> 1.0, exits up-right by 4.5
|
|
381
|
+
const BANGP = [[1000, 430, 0.8, 0.9], [1262, 292, 1.25, 0.7], [1500, 172, 1.0, 0.1], [1528, 156, 1.0, 0.05], [1716, 40, 1.06, 0.8], [2010, -110, 1.1, 1.2]];
|
|
382
|
+
function drawBang(c, t) {
|
|
383
|
+
const k = ks(t, C.pop); if (k < 0 || k >= BANGP.length) return;
|
|
384
|
+
const [x, y, s, lift] = BANGP[k], [jx, jy, jr] = jit(466, t, 2, 12);
|
|
385
|
+
c.save(); c.translate(x + jx, y + jy); c.rotate(-0.14 + jr + (k >= 4 ? 0.12 * (k - 3) : 0)); c.scale(1.45 * s, 1.45 * s);
|
|
386
|
+
BANG.forEach(([ch, f, bg, fg], q) => {
|
|
387
|
+
c.save(); c.translate(-120 + q * 60, (q % 2 ? 6 : -6)); c.rotate((q % 2 ? 5 : -5) * DEG);
|
|
388
|
+
cutQuad(c, 60, 74, 470 + q, 3); c.fillStyle = '#F2ECDF'; shadow(c, lift); c.fill(); noShadow(c);
|
|
389
|
+
cutQuad(c, 52, 66, 480 + q, 3); c.fillStyle = bg === 'news' ? PAT.news : bg === 'red' ? PAT.red : bg === 'ink' ? '#16130F' : '#F2ECDF'; c.fill();
|
|
390
|
+
c.fillStyle = fg; c.font = f; c.textAlign = 'center'; c.textBaseline = 'middle'; c.fillText(ch, 0, 4); c.restore();
|
|
391
|
+
});
|
|
392
|
+
c.restore(); noShadow(c);
|
|
393
|
+
}
|
|
394
|
+
const i2c = seed => ['#E3CFAE', '#D2261C', '#F2ECDF'][seed % 3];
|
|
395
|
+
|
|
396
|
+
// ------------------------------------------------------------------ stepped rostrum camera
|
|
397
|
+
function cam(t) {
|
|
398
|
+
const q6 = Q6(t), kp = ks(t, C.pop);
|
|
399
|
+
let z = 1, px = 845, py = 560, cx = 0, cy = 0;
|
|
400
|
+
if (q6 >= C.jaw[0] - 1e-4) z = 1 + 0.045 * clamp((q6 - C.jaw[0]) / (C.stamp_cut - C.jaw[0]));
|
|
401
|
+
if (q6 >= C.stamp_cut - 1e-4) z = 1.045 + 0.015 * clamp((q6 - C.stamp_cut) / (C.gap[0] - C.stamp_cut));
|
|
402
|
+
if (kp >= 0) {
|
|
403
|
+
if (kp < 4) { z = [1.11, 1.075, 1.056, 1.05][kp]; cx = [12, -8, 3, 0][kp]; cy = [26, -14, 6, 0][kp]; }
|
|
404
|
+
else z = 1 + 0.05 * (1 - eOut((q6 - C.lid_land) / 1.0));
|
|
405
|
+
}
|
|
406
|
+
if (q6 >= 5.5 - 1e-4) { z = 1 + 0.022 * eIO((q6 - 5.5) / 2.0); px = 900; py = 540; }
|
|
407
|
+
return { z, px, py, cx, cy };
|
|
408
|
+
}
|
|
409
|
+
const applyCam = (c, k) => { c.translate(k.px + k.cx, k.py + k.cy); c.scale(k.z, k.z); c.translate(-k.px, -k.py); };
|
|
410
|
+
|
|
411
|
+
// ------------------------------------------------------------------ MAIN PAGE (kraft) — everything before the tear
|
|
412
|
+
function mainPage(c, t) {
|
|
413
|
+
const kp = ks(t, C.pop);
|
|
414
|
+
c.drawImage(I.kraft, 0, 0);
|
|
415
|
+
const kn0 = ks(t, C.slap_news);
|
|
416
|
+
if (kp < 0 && kn0 >= 2) monoText(c, V5 ? 'COLLAGE QUARTERLY — Nº06 — AUTUMN 2026' : 'NOGGIN QUARTERLY — Nº06 — AUTUMN 2026', 96, 70);
|
|
417
|
+
if (kn0 >= 2) monoText(c, 'PL. VI · CUT & PASTE ON KRAFT, 1860 / 2026', W - 96, H - 52, 17, 'rgba(22,19,15,.72)', 3, 'right');
|
|
418
|
+
const snb = slap(t, C.slap_news + 1 / 12, 2); // left newsprint scrap + kraft tape (held back until the 1.0 slap)
|
|
419
|
+
if (snb) { piece(c, I.news_b, 196 - 50 * snb.lift, 640 + 30 * snb.lift, { rot: -5.5 * DEG, s: snb.s, lift: snb.lift, id: 2, t, ...boil(snb.k < 2) });
|
|
420
|
+
if (snb.k >= 1) tape(c, I.tapeK, 214, 372, 200, -0.14, 3, t); }
|
|
421
|
+
// red constructivist disc behind the head
|
|
422
|
+
const sd = slap(t, C.slap_circle);
|
|
423
|
+
if (sd && !NOLINK.has('dot')) {
|
|
424
|
+
const [jx, jy] = jit(5, t, sd.lift ? 2 : 1, sd.lift ? 12 : 6);
|
|
425
|
+
c.save(); c.translate(845 + jx - 40 * sd.lift, 468 + jy - 20 * sd.lift); c.scale(sd.s, sd.s);
|
|
426
|
+
c.beginPath(); for (let i = 0; i < 44; i++) { const a = i / 44 * 2 * PI, r = 352 + (hash(9, i) - .5) * 5; i ? c.lineTo(Math.cos(a) * r, Math.sin(a) * r) : c.moveTo(r, 0); }
|
|
427
|
+
c.closePath(); shadow(c, sd.lift, 0.3); c.fillStyle = PAT.red; c.fill(); c.restore(); noShadow(c);
|
|
428
|
+
}
|
|
429
|
+
// torn newsprint (top right) + Nº06 chip (bottom right, clear of the hawk)
|
|
430
|
+
const sn = slap(t, C.slap_news);
|
|
431
|
+
if (sn) { piece(c, I.news_a, 1592 + 60 * sn.lift, 222 - 30 * sn.lift, { rot: 3.2 * DEG, s: sn.s, lift: sn.lift, id: 6, t, ...boil(sn.k < 2) }); if (sn.k >= 1) tape(c, I.tapeW, 1452, 60, 170, 0.55, 8, t); }
|
|
432
|
+
const sc = slap(t, C.chip06); // Nº06 chip slaps in at 2.417 as the speech scraps tear away
|
|
433
|
+
if (sc) {
|
|
434
|
+
const [jx, jy, jr] = jit(7, t, 1, 6);
|
|
435
|
+
c.save(); c.translate(1744 + jx + 30 * sc.lift, 906 + jy - 30 * sc.lift); c.rotate(-5 * DEG + jr); c.scale(sc.s, sc.s);
|
|
436
|
+
cutQuad(c, 186, 100, 7, 3); c.fillStyle = '#F2ECDF'; shadow(c, sc.lift); c.fill(); noShadow(c);
|
|
437
|
+
cutQuad(c, 176, 92, 17, 3); c.fillStyle = PAT.red; c.fill();
|
|
438
|
+
c.fillStyle = '#F2ECDF'; c.font = '400 70px "Anton"'; c.textAlign = 'center'; c.textBaseline = 'middle'; c.letterSpacing = '2px';
|
|
439
|
+
c.fillText('Nº06', 2, 4); c.restore();
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// ---- portrait: carried in by a giant manicule from the top right, slapped at 0.333
|
|
443
|
+
const k0 = ks(t, C.slap_portrait);
|
|
444
|
+
const IN = [[1100, -100, -6, 1.08, 1.6], [560, -50, -3.5, 1.05, 1.2], [200, -18, -1.5, 1.025, 0.8], [40, -4, -0.4, 1.008, 0.4]];
|
|
445
|
+
let S = { x: 845, y: 554, s: 1, rot: 0, lift: 0 };
|
|
446
|
+
if (k0 < 0) { const p = IN[clamp(4 + k0, 0, 3)]; S = { x: 845 + p[0], y: 554 + p[1], rot: p[2] * DEG, s: p[3], lift: p[4] }; }
|
|
447
|
+
else if (k0 === 0) { S.sqx = 1.02; S.sqy = 0.96; S.y += 14; }
|
|
448
|
+
else if (k0 === 1) { S.sqy = 1.01; }
|
|
449
|
+
// jaw: 3 replacement poses per flap (anticipation half, WIDE on the beat, half, closed) + head bob up on each open
|
|
450
|
+
let jaw = 0;
|
|
451
|
+
for (const h of C.jaw) { const k = ks(t, h); if (k >= -1 && k < 3) jaw = Math.max(jaw, [0.5, 1, 0.5, 0][k + 1]); }
|
|
452
|
+
if (jaw === 1) S.y -= 7;
|
|
453
|
+
S.rot += jaw * -1.6 * DEG;
|
|
454
|
+
// stamp + snip
|
|
455
|
+
const kst = ks(t, C.stamp_cut);
|
|
456
|
+
S.stamp = kst < 0 ? 0 : kst === 0 ? 1.15 : 1;
|
|
457
|
+
const snips = C.snips, SX = [CUT[1][0] + 14, 205, 315, 425, 535];
|
|
458
|
+
let snipX = -1e9, scis = null;
|
|
459
|
+
const ks0 = ks(t, snips[0]);
|
|
460
|
+
if (ks0 >= -1) {
|
|
461
|
+
const tq = Q(t); let i = snips.findIndex(v => tq < v - 1e-4); if (i < 0) i = snips.length;
|
|
462
|
+
const kk = ks(t, snips[Math.max(0, i - 1)]);
|
|
463
|
+
if (i === 0) scis = { x: SX[0] - 70, open: 0.42, lift: 0.6 };
|
|
464
|
+
else if (i <= snips.length) {
|
|
465
|
+
const lx = SX[i - 1], nx = i < snips.length ? SX[i] : 700;
|
|
466
|
+
scis = { x: kk === 0 ? lx : lerp(lx, nx, 0.45), open: kk === 0 ? 0.02 : 0.38, lift: 0 };
|
|
467
|
+
snipX = lx;
|
|
468
|
+
if (i === snips.length && kk >= 1) { const f = kk - 1; scis = { x: 620 + f * 150, open: 0.3, lift: 0.3 + f * 0.25, fly: f }; snipX = 640; }
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
S.snipX = snipX;
|
|
472
|
+
// lid: rattles, then POP — launches out of frame spinning, falls back and slaps onto its brad at 4.333
|
|
473
|
+
let L = { a: 0, dx: 0, dy: 0 };
|
|
474
|
+
const RAT = { [K(C.rattle[0])]: [-5, 5], [K(C.rattle[0]) + 1]: [-1, 1], [K(C.rattle[1])]: [-8.5, 8], [K(C.rattle[1]) + 1]: [-1.5, 2], [K(C.rattle[2])]: [-13, 12], [K(C.rattle[2]) + 1]: [-3, 3] };
|
|
475
|
+
if (RAT[K(t)]) L = { a: RAT[K(t)][0] * DEG, dx: 0, dy: -RAT[K(t)][1] };
|
|
476
|
+
const LIDF = [[-70, 10, -230, 1.3], [-200, -30, -660, 1.8], [-300, -70, -700, 1.8], [-385, -40, -300, 1.4]];
|
|
477
|
+
if (kp >= 0 && kp < 4) { const f = LIDF[kp]; L = { a: f[0] * DEG, dx: f[1], dy: f[2], lift: f[3], top: 1 }; }
|
|
478
|
+
else if (kp >= 4) { const a = [-95, -89, -97, -95][Math.min(kp - 4, 3)]; L = kp === 4 ? { a: a * DEG, sx: 1.04, sy: 0.93, lift: 0 } : { a: a * DEG, lift: 0.08 }; }
|
|
479
|
+
S.lidT = L; S.band = kp >= 0;
|
|
480
|
+
if (kp >= 0) S.wedge = [[0.94, 1.35, 1], [1.07, 1.1], [0.97, 0.96], [1.01, 1.02], [1, 1]][Math.min(kp, 4)];
|
|
481
|
+
if (kp >= 0 && kp < 3) { S.rot += [2.6, -1.8, 0.8][kp] * DEG; S.y += [16, -8, 3][kp]; }
|
|
482
|
+
S.spill = kp >= 0; S.spillT = t; S.jaw = jaw;
|
|
483
|
+
if (kp >= 0 && kp < 3) S.jaw = [1, 1, 0.5][kp]; // gasp as the lid pops
|
|
484
|
+
const jp = jit(1, t, (k0 < 3 || jaw || kp >= 0 && kp < 5) ? 2 : 1, (k0 < 3 || jaw) ? 12 : 6);
|
|
485
|
+
S.x += jp[0]; S.y += jp[1]; S.rot += jp[2];
|
|
486
|
+
drawHead(c, t, S);
|
|
487
|
+
flecks(c, [[540, 820, -1, .3], [548, 985, -1, .5], [1266, 840, 1, .3], [1258, 990, 1, .5], [700, 300, -.6, -1], [990, 300, .6, -1]], k0, 90, 6);
|
|
488
|
+
if (kp >= 4) { const a = lidRest(LX[1] - 30, cutY(LX[1] - 30) - 40), b = lidRest(PL.head_top[0], PL.head_top[1] + 30), d = lidRest(LX[0] + 120, 150);
|
|
489
|
+
flecks(c, [[a[0], a[1], 0, -1], [b[0], b[1], -1, -.2], [d[0], d[1], -1, .4]], kp - 4, 95, 6); }
|
|
490
|
+
// speech scraps (bottom-right quadrant, act 1)
|
|
491
|
+
SPEECH.forEach((sp, i) => speechScrap(c, sp, i, t));
|
|
492
|
+
// coupon slip with the red rubber stamp (right of the head); blown off by the POP
|
|
493
|
+
if (kst >= 0 && kp < 3) {
|
|
494
|
+
const O = kp < 0 ? [0, 0, 0, 0] : [[90, -110, 14, 0.9], [420, -430, 40, 1.5], [980, -820, 75, 1.8]][kp];
|
|
495
|
+
const sl = slap(t, C.stamp_cut, 1), [jx, jy, jr] = jit(19, t, kp >= 0 ? 2 : 1, kp >= 0 ? 12 : 6);
|
|
496
|
+
c.save(); c.translate(1387 + O[0] + jx, 470 + O[1] + jy); c.rotate((-3.5 + O[2]) * DEG + jr); c.scale(sl.s, sl.s);
|
|
497
|
+
cutQuad(c, 372, 104, 91, 4); c.fillStyle = '#EDE5D3'; shadow(c, Math.max(O[3], sl.lift)); c.fill(); noShadow(c);
|
|
498
|
+
c.globalAlpha = kst === 0 ? 1 : 0.9;
|
|
499
|
+
c.strokeStyle = '#C8261D'; c.lineWidth = 3; c.strokeRect(-165, -38, 330, 70);
|
|
500
|
+
c.fillStyle = '#C8261D'; c.font = '700 30px "Noto Sans SC"'; c.textBaseline = 'middle'; c.textAlign = 'left'; c.fillText('沿虚线剪开', -95, -12);
|
|
501
|
+
c.font = '400 26px "Bebas Neue"'; c.letterSpacing = '5px'; c.fillText('CUT ALONG THE LINE', -95, 18);
|
|
502
|
+
c.globalAlpha = 1; scissors(c, -130, -2, 0, 0.3, 0.3);
|
|
503
|
+
c.restore();
|
|
504
|
+
}
|
|
505
|
+
if (scis) {
|
|
506
|
+
const [sx, sy] = toScreen(scis.x, cutY(scis.x));
|
|
507
|
+
const jj = jit(12, t, 2, 12);
|
|
508
|
+
scissors(c, S.x - 845 + sx + jj[0] + (scis.fly || 0) * 40, S.y - 554 + sy + jj[1] - (scis.fly || 0) * 70 - 4, -0.05 + jj[2] - (scis.fly || 0) * 0.35, scis.open, 1.15 + (scis.lift || 0) * 0.3);
|
|
509
|
+
}
|
|
510
|
+
drawScraps(c, t); drawBang(c, t);
|
|
511
|
+
// manicule #1: a giant ☞ pushes the portrait in (tip on the right shoulder), springs up with an overshoot, exits top right
|
|
512
|
+
if (k0 < 4) { // the artist's giant ☞ (x1.8, white rim) leads: its fingertip drags the portrait in by the brass brad, pins it, springs off left
|
|
513
|
+
let mx, my, ml, ms, mr;
|
|
514
|
+
const dx = HG[0] - PL.head_center[0], dy = HG[1] - PL.head_center[1];
|
|
515
|
+
if (k0 < 0) { mx = S.x + S.s * (dx * Math.cos(S.rot) - dy * Math.sin(S.rot)); my = S.y + S.s * (dx * Math.sin(S.rot) + dy * Math.cos(S.rot)); ml = 0.3 + S.lift * 0.7; ms = 1; mr = S.rot; }
|
|
516
|
+
else { const O = [[0, 4, 0], [-34, -50, -5], [-470, -260, -12], [-1150, -560, -20]][k0]; [mx, my] = toScreen(...HG); mx += O[0]; my += O[1]; mr = O[2] * DEG; ml = [0.05, 0.9, 1.4, 1.8][k0]; ms = [1, 1.03, 1.07, 1.12][k0]; }
|
|
517
|
+
piece(c, I.maniR, mx, my, { ax: MT[0], ay: MT[1], rot: 0.36 + mr, s: 1.8 * ms, sx: k0 === 0 ? 1.03 : 1, sy: k0 === 0 ? 0.97 : 1, lift: ml, id: 13, t });
|
|
518
|
+
}
|
|
519
|
+
// constructivist band + red rule
|
|
520
|
+
const sb = slap(t, C.band, 2);
|
|
521
|
+
if (sb) {
|
|
522
|
+
const [jx, jy, jr] = jit(15, t, sb.k < 2 ? 2 : 1, sb.k < 2 ? 12 : 6);
|
|
523
|
+
const slide = sb.k < 0 ? (-sb.k) * 260 : 0;
|
|
524
|
+
c.save(); c.translate(900 + jx - slide, 842 + jy + slide * 0.18); c.rotate(-10 * DEG + jr); c.scale(sb.s, sb.s);
|
|
525
|
+
tornStrip(c, 2500, 154, 16, 14); shadow(c, sb.lift); c.fillStyle = '#16130F'; c.fill(); noShadow(c);
|
|
526
|
+
c.fillStyle = '#F2ECDF'; c.font = '400 124px "Anton"'; c.letterSpacing = '2.5px'; c.textBaseline = 'middle'; c.textAlign = 'left';
|
|
527
|
+
c.fillText('THE MIND IS A COLLAGE', -700, 6);
|
|
528
|
+
c.restore();
|
|
529
|
+
}
|
|
530
|
+
const sr = slap(t, C.band + 1 / 12, 2);
|
|
531
|
+
if (sr) {
|
|
532
|
+
const [jx, jy, jr] = jit(17, t, 1, 6);
|
|
533
|
+
c.save(); c.translate(760 + jx, 958 + jy); c.rotate(-10 * DEG + jr); c.scale(sr.s, sr.s);
|
|
534
|
+
tornStrip(c, 2300, 30, 18, 10); shadow(c, sr.lift); c.fillStyle = PAT.red; c.fill(); c.restore(); noShadow(c);
|
|
535
|
+
}
|
|
536
|
+
// HERO: 脑 洞 大 开 as ransom-note cut-paper chips (16ths), block scaled 1.12
|
|
537
|
+
const BC = [1601, 552], BS = 1.12, bx = x => BC[0] + (x - 1601.5) * BS, by = y => BC[1] + (y - 571.5) * BS;
|
|
538
|
+
const HT = V5 ? ['拼', '贴', '剪', '贴'] : ['脑', '洞', '大', '开'];
|
|
539
|
+
const CH = [
|
|
540
|
+
[HT[0], { f: 'Alimama ShuHeiTi', w: 700, dy: 7 }, 190, PAT.red, '#16130F', 1484, 468, -6.5, 21],
|
|
541
|
+
[HT[1], { f: 'Noto Serif SC', w: 900, dy: 4 }, 186, PAT.news, '#16130F', 1706, 452, 4.5, 22],
|
|
542
|
+
[HT[2], { f: 'Alimama ShuHeiTi', w: 700, dy: 7 }, 190, '#16130F', '#F2ECDF', 1496, 690, 3, 23],
|
|
543
|
+
[HT[3], { f: 'Noto Serif SC', w: 900, dy: 4 }, 186, '#F2ECDF', '#D2261C', 1720, 676, -4.5, 24],
|
|
544
|
+
];
|
|
545
|
+
CH.forEach((q, i) => {
|
|
546
|
+
const s = slap(t, C.hero[i], 2); if (!s) return;
|
|
547
|
+
chip(c, s, q[0], q[1], q[2] * BS, q[3], q[4], 212 * BS, 212 * BS, q[8], q[7] * DEG, bx(q[5]) + 30 * s.lift, by(q[6]) - 40 * s.lift, t);
|
|
548
|
+
});
|
|
549
|
+
if (ks(t, C.hero[3]) >= 2) tape(c, I.tapeW, bx(1604), by(344), 200, -0.2, 25, t);
|
|
550
|
+
// the artist's hand returns (6.0): an engraved ☞ slides in from the left edge, points at the brad / open skull, taps twice
|
|
551
|
+
const km = ks(t, C.manicules);
|
|
552
|
+
if (km >= -2 && K(t) < K(C.tear_hand)) {
|
|
553
|
+
const PATH = [[-470, 30], [-150, 10], [-26, 0], [0, 0]];
|
|
554
|
+
const p = PATH[clamp(km + 2, 0, 3)];
|
|
555
|
+
const poke = (km === 2 || km === 6) ? 18 : 0;
|
|
556
|
+
piece(c, I.maniR, 598 + p[0] + poke, 506 + p[1], { ax: MT[0], ay: MT[1], rot: -0.04, s: 0.78, lift: km < 1 ? 0.5 : 0, id: 14, t, ...boil(km < 1) });
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// ------------------------------------------------------------------ v5 END-CARD TITLE: the style name as the masthead
|
|
561
|
+
// 拼·贴·剪·贴 ransom chips (the hero's four chips, re-pasted in a row) slap on 16ths from 8.167, then a smaller
|
|
562
|
+
// COLLAGE · CUT-OUT torn strip and the mono line. Row ≈ x 97–1085 (≈ 51 % of the width), glyphs ≈ 185 px tall.
|
|
563
|
+
const V5E = { chips: [8.1666667, 8.3333333, 8.5, 8.6666667], strip: 8.8333333, mono: 8.9166667 };
|
|
564
|
+
const ECH = [ // ch, font, size px, bg, fg, w, h, x, y, rot°, seed
|
|
565
|
+
['拼', { f: 'Alimama ShuHeiTi', w: 700, dy: 9 }, 214, 'kraft', '#16130F', 250, 262, 222, 290, -5, 61],
|
|
566
|
+
['贴', { f: 'Noto Serif SC', w: 900, dy: 5 }, 208, 'news', '#16130F', 240, 252, 468, 318, 3.5, 62],
|
|
567
|
+
['剪', { f: 'Alimama ShuHeiTi', w: 700, dy: 9 }, 214, 'ink', '#F2ECDF', 254, 258, 714, 284, -2.5, 63],
|
|
568
|
+
['贴', { f: 'Noto Serif SC', w: 900, dy: 5 }, 210, 'cream', '#D2261C', 244, 266, 960, 312, 5, 64],
|
|
569
|
+
];
|
|
570
|
+
const bgOf = b => b === 'kraft' ? PAT.kraft : b === 'news' ? PAT.news : b === 'red' ? PAT.red : b === 'ink' ? '#16130F' : '#F2ECDF';
|
|
571
|
+
function endTitleV5(c, t) {
|
|
572
|
+
ECH.forEach((q, i) => {
|
|
573
|
+
const s = slap(t, V5E.chips[i], 2); if (!s) return;
|
|
574
|
+
chip(c, s, q[0], q[1], q[2], bgOf(q[3]), q[4], q[5], q[6], q[10], q[9] * DEG, q[7] + 30 * s.lift, q[8] - 40 * s.lift, t);
|
|
575
|
+
});
|
|
576
|
+
const sm = slap(t, V5E.strip, 2);
|
|
577
|
+
if (sm) {
|
|
578
|
+
const [jx, jy, jr] = jit(52, t, sm.k < 2 ? 2 : 1, sm.k < 2 ? 12 : 6);
|
|
579
|
+
c.save(); c.translate(478 + jx, 540 + jy); c.rotate(-2 * DEG + jr); c.scale(sm.s, sm.s);
|
|
580
|
+
tornStrip(c, 660, 112, 53, 12); shadow(c, sm.lift); c.fillStyle = '#EFE8DA'; c.fill(); noShadow(c);
|
|
581
|
+
c.fillStyle = '#16130F'; c.font = '400 80px "Anton"'; c.letterSpacing = '5px'; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
582
|
+
c.fillText('COLLAGE · CUT-OUT', 0, 6); c.restore();
|
|
583
|
+
}
|
|
584
|
+
const s3 = slap(t, V5E.mono, 1);
|
|
585
|
+
if (s3) {
|
|
586
|
+
monoText(c, 'Nº06 · THE MIND IS A COLLAGE · AUTUMN 2026', 156, 684, 22, '#16130F', 3);
|
|
587
|
+
c.save(); c.fillStyle = '#16130F'; c.fillRect(156, 708, 640, 4); c.restore();
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
// ------------------------------------------------------------------ END PAGE (red riso) — revealed by the tear
|
|
592
|
+
function endPage(c, t) {
|
|
593
|
+
c.drawImage(I.red, 0, 0);
|
|
594
|
+
const kj = ks(t, C.end_jaw), kl = ks(t, C.end_lid);
|
|
595
|
+
let jaw = kj >= 0 && kj < 4 ? [1, 1, 0.5, 0][kj] : 0;
|
|
596
|
+
if (kl === 0) jaw = 0.5; // gulp as the lid slams
|
|
597
|
+
const LA = kl < -2 ? [-95, 0] : kl === -2 ? [-101, 0.15] : kl === -1 ? [-46, 0.7] : kl === 0 ? [6, 0] : [0, 0];
|
|
598
|
+
const lidT = { a: LA[0] * DEG, dy: kl < -1 ? -4 : 0, lift: LA[1], sx: kl === 0 ? 1.03 : 1, sy: kl === 0 ? 0.94 : 1, top: kl === -1 ? 1 : 0 };
|
|
599
|
+
const jp = jit(51, t, kl >= 0 && kl < 2 ? 2 : 1.1, kl >= 0 && kl < 2 ? 12 : 6);
|
|
600
|
+
const RX = 1490, RY = 716, RS = 0.60; // deliberate ~120 px bleed off the right edge, clear of the NOGGIN strip
|
|
601
|
+
drawHead(c, t, { x: RX + jp[0], y: RY + jp[1] - (jaw === 1 ? 5 : 0) + (kl === 0 ? 4 : 0), s: RS, rot: 2 * DEG + jp[2] - jaw * 1.5 * DEG, lift: 0, lidT, wedge: [1, 1],
|
|
602
|
+
jaw, stamp: 1, snipX: 640, band: 1, spill: true, spillT: 9.9, only: ['rocket', 'earth', 'rose_purple', 'rose_centi', 'bfly_a'] });
|
|
603
|
+
if (kl >= 0 && kl < 2) { const hx = (x, y) => [RX + (x - PL.head_center[0]) * RS, RY + (y - PL.head_center[1]) * RS];
|
|
604
|
+
flecks(c, [[...hx(LX[0], PL.cut[1][1]), -1, -.3], [...hx(PL.head_top[0], PL.head_top[1] + 10), 0, -1], [...hx(LX[1], PL.cut[4][1]), 1, -.3]], kl, 98, 7); }
|
|
605
|
+
if (V5) endTitleV5(c, t);
|
|
606
|
+
else { // default: NOGGIN strip, 脑洞季刊, mono line
|
|
607
|
+
const sm = slap(t, C.end_masthead, 2);
|
|
608
|
+
if (sm) {
|
|
609
|
+
const [jx, jy, jr] = jit(52, t, sm.k < 2 ? 2 : 1, sm.k < 2 ? 12 : 6);
|
|
610
|
+
c.save(); c.translate(560 + jx, 300 + jy); c.rotate(-3 * DEG + jr); c.scale(sm.s, sm.s);
|
|
611
|
+
tornStrip(c, 900, 250, 53, 16); shadow(c, sm.lift); c.fillStyle = '#EFE8DA'; c.fill(); noShadow(c);
|
|
612
|
+
c.fillStyle = '#16130F'; c.font = '400 238px "Anton"'; c.letterSpacing = '6px'; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
613
|
+
c.fillText('NOGGIN', 0, 12); c.restore();
|
|
614
|
+
}
|
|
615
|
+
const s2 = slap(t, C.end_masthead + 2 / 12, 2);
|
|
616
|
+
if (s2) {
|
|
617
|
+
const [jx, jy, jr] = jit(54, t, 1, 6);
|
|
618
|
+
c.save(); c.translate(150 + jx, 560 + jy); c.rotate(jr); c.scale(s2.s, s2.s);
|
|
619
|
+
c.fillStyle = '#16130F'; c.font = '900 132px "Noto Serif SC"'; c.textBaseline = 'alphabetic'; c.letterSpacing = '10px';
|
|
620
|
+
c.fillText('脑洞季刊', 0, 0);
|
|
621
|
+
c.restore();
|
|
622
|
+
}
|
|
623
|
+
const s3 = slap(t, C.end_masthead + 4 / 12, 1);
|
|
624
|
+
if (s3) {
|
|
625
|
+
monoText(c, 'Nº06 · THE MIND IS A COLLAGE · AUTUMN 2026', 156, 650, 22, '#16130F', 3);
|
|
626
|
+
c.save(); c.fillStyle = '#16130F'; c.fillRect(156, 676, 640, 4); c.restore();
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
// price roundel sticker (lower left) + its little ✂ that snips once at 9.5
|
|
630
|
+
const sb = slap(t, C.end_button, 2);
|
|
631
|
+
if (sb && !NOLINK.has('dot')) {
|
|
632
|
+
const [jx, jy, jr] = jit(55, t, sb.k < 2 ? 2 : 1, sb.k < 2 ? 12 : 6);
|
|
633
|
+
c.save(); c.translate(380 + jx, 870 + jy); c.rotate(-8 * DEG + jr); c.scale(sb.s, sb.s);
|
|
634
|
+
c.beginPath(); for (let i = 0; i < 28; i++) { const a = i / 28 * 2 * PI, r = 118 + (hash(56, i) - .5) * 4; i ? c.lineTo(Math.cos(a) * r, Math.sin(a) * r) : c.moveTo(r, 0); }
|
|
635
|
+
c.closePath(); c.fillStyle = '#EFE8DA'; shadow(c, sb.lift); c.fill(); noShadow(c);
|
|
636
|
+
c.beginPath(); c.arc(0, 0, 106, 0, 2 * PI); c.fillStyle = '#16130F'; c.fill();
|
|
637
|
+
c.fillStyle = '#EFE8DA'; c.textAlign = 'center'; c.textBaseline = 'middle';
|
|
638
|
+
c.font = '700 34px "Noto Sans SC"'; c.letterSpacing = '4px'; c.fillText('随刊附赠', 2, -34);
|
|
639
|
+
c.fillText('剪刀一把', 2, 8);
|
|
640
|
+
c.font = '400 22px "Bebas Neue"'; c.letterSpacing = '3px'; c.fillText('SCISSORS INCLUDED', 0, 48);
|
|
641
|
+
c.restore();
|
|
642
|
+
const kn = ks(t, C.end_snip);
|
|
643
|
+
const open = kn === 0 ? 0.02 : kn === 1 ? 0.4 : 0.28, hop = kn === 0 ? -8 : kn === 1 ? -3 : 0;
|
|
644
|
+
scissors(c, 482, 764 + hop, -0.7 + (kn === 0 ? 0.08 : 0), open, 0.46);
|
|
645
|
+
flecks(c, [[520, 730, 1, -1], [530, 745, 1, 0]], kn, 97, 4);
|
|
646
|
+
}
|
|
647
|
+
monoText(c, V5 ? 'COLLAGE QUARTERLY — ¥19.60' : 'NOGGIN QUARTERLY — ¥19.60', 96, 70, 19, 'rgba(22,19,15,.8)');
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
// ------------------------------------------------------------------ tear transition: rip runs down, then each half PEELS off
|
|
651
|
+
const pageC = off(W, H), pageX = pageC.getContext('2d');
|
|
652
|
+
const TEAR = (() => { const pts = [], n = 90; for (let i = 0; i <= n; i++) { const u = i / n; let x = lerp(1190, 770, u), y = lerp(-60, H + 60, u);
|
|
653
|
+
x += Math.sin(u * 9.1 + 1.3) * 22 + Math.sin(u * 31 + 0.4) * 8 + (hash(77, i) - .5) * 14; pts.push([x, y]); } return pts; })();
|
|
654
|
+
function halfPath(c, side, grow = 0, seed = 0) {
|
|
655
|
+
c.beginPath(); const e = side < 0 ? -600 : W + 600;
|
|
656
|
+
c.moveTo(e, -600);
|
|
657
|
+
TEAR.forEach(([x, y], i) => c.lineTo(x - side * grow * (0.6 + hash(seed, i)), y));
|
|
658
|
+
c.lineTo(e, H + 600); c.closePath();
|
|
659
|
+
}
|
|
660
|
+
const frontC = off(W, H), frontX = frontC.getContext('2d'), flapC = off(W, H), flapX = flapC.getContext('2d');
|
|
661
|
+
const ripX = y => { for (let i = 0; i < TEAR.length - 1; i++) if (TEAR[i + 1][1] >= y) { const [x0, y0] = TEAR[i], [x1, y1] = TEAR[i + 1]; return lerp(x0, x1, (y - y0) / (y1 - y0)); } return TEAR[TEAR.length - 1][0]; };
|
|
662
|
+
// Each half curls over a CR = 100 px cylinder. d = distance past the fold (measured along n, from the fixed part towards the rip).
|
|
663
|
+
// Paper at d maps to screen distance R·sin(d/R) (recto quarter, then verso quarter), and beyond πR lies flat, verso up (πR − d).
|
|
664
|
+
// The fold starts at the top inner corner (step 0 = a small dog-ear triangle) and sweeps out; n is tilted (−10° left, 220° right).
|
|
665
|
+
// Per step: fold normal angle a (the dog-ear starts steep at the corner, then swings towards the rip's lean), paper peeled E, curl radius R.
|
|
666
|
+
const C0 = [ripX(0), 0];
|
|
667
|
+
const PEEL = {
|
|
668
|
+
'-1': { a: [-20, 6, 10, 12], E: [330, 640, 980, 1320], R: [70, 110, 130, 130], yank: [[0, 0], [-10, -22], [-60, -100], [-150, -240]] },
|
|
669
|
+
'1': { a: [220, 198, 185, 180], E: [300, 520, 700, 960], R: [70, 110, 130, 130], yank: [[0, 0], [8, 12], [26, 40], [60, 90]] } };
|
|
670
|
+
const fmap = (d, R) => d <= PI * R ? R * Math.sin(d / R) : PI * R - d;
|
|
671
|
+
function peelGeom(side, j) { const P = PEEL[side], a = P.a[j] * DEG, nx = Math.cos(a), ny = Math.sin(a);
|
|
672
|
+
return { nx, ny, d0: nx * C0[0] + ny * C0[1] - P.E[j], y: P.yank[j], R: P.R[j] }; }
|
|
673
|
+
function peelPoint(side, j, p) { const g = peelGeom(side, j), d = g.nx * p[0] + g.ny * p[1] - g.d0; if (d <= 0) return p.slice();
|
|
674
|
+
const sh = fmap(d, g.R) - d; return [p[0] + g.nx * sh + g.y[0], p[1] + g.ny * sh + g.y[1]]; }
|
|
675
|
+
function band(cx, g, da, db) { // source-space strip d0+da <= n·p <= d0+db
|
|
676
|
+
const L = 6000, ux = -g.ny, uy = g.nx, a = g.d0 + da, b = g.d0 + db;
|
|
677
|
+
cx.beginPath(); cx.moveTo(g.nx * a - ux * L, g.ny * a - uy * L); cx.lineTo(g.nx * a + ux * L, g.ny * a + uy * L);
|
|
678
|
+
cx.lineTo(g.nx * b + ux * L, g.ny * b + uy * L); cx.lineTo(g.nx * b - ux * L, g.ny * b - uy * L); cx.closePath();
|
|
679
|
+
}
|
|
680
|
+
function setMap(cx, g, A, B) { // p -> p + n·(A + (B−1)·d) + yank
|
|
681
|
+
const k = B - 1, o = A - k * g.d0, { nx, ny } = g;
|
|
682
|
+
cx.setTransform(1 + k * nx * nx, k * nx * ny, k * nx * ny, 1 + k * ny * ny, nx * o + g.y[0], ny * o + g.y[1]);
|
|
683
|
+
}
|
|
684
|
+
const shadeV = v => { // verso quarter: v = 1 at the crest (grazing, dark) -> 0 where it rolls flat
|
|
685
|
+
if (v > 0.8) return `rgba(22,10,4,${(0.35 * (v - 0.8) / 0.2).toFixed(3)})`;
|
|
686
|
+
if (v > 0.2) return `rgba(255,248,232,${(0.40 * clamp(1 - Math.abs(v - 0.52) / 0.34)).toFixed(3)})`;
|
|
687
|
+
return `rgba(22,10,4,${(0.14 * (0.2 - v) / 0.2).toFixed(3)})`;
|
|
688
|
+
};
|
|
689
|
+
function curlHalf(c, side, j) {
|
|
690
|
+
if (j >= 4) return; // fully peeled and gone (j = 3 is the roll leaving frame)
|
|
691
|
+
const seed = side > 0 ? 81 : 82;
|
|
692
|
+
const paint = (cx, verso) => {
|
|
693
|
+
halfPath(cx, side, 7, seed); cx.fillStyle = verso ? '#F1EADB' : '#EFE7D6'; cx.fill(); // torn fibre rim
|
|
694
|
+
halfPath(cx, side, 0); cx.clip();
|
|
695
|
+
if (!verso) { cx.drawImage(pageC, 0, 0); return; }
|
|
696
|
+
cx.fillStyle = '#C4A077'; cx.fillRect(-W, -H, 3 * W, 3 * H);
|
|
697
|
+
cx.globalCompositeOperation = 'multiply'; cx.globalAlpha = 0.45; cx.drawImage(I.kraft, 0, 0);
|
|
698
|
+
cx.globalCompositeOperation = 'source-over'; cx.globalAlpha = 0.08; cx.drawImage(pageC, 0, 0); cx.globalAlpha = 1; // mirrored print show-through
|
|
699
|
+
};
|
|
700
|
+
const g = j < 0 ? null : peelGeom(side, j);
|
|
701
|
+
frontX.setTransform(1, 0, 0, 1, 0, 0); frontX.clearRect(0, 0, W, H); frontX.save();
|
|
702
|
+
if (g) { band(frontX, g, -1e4, 0.6); frontX.clip(); }
|
|
703
|
+
paint(frontX, false); frontX.restore();
|
|
704
|
+
c.save(); c.shadowColor = 'rgba(34,20,8,.42)'; c.shadowBlur = 12; c.shadowOffsetX = 4; c.shadowOffsetY = 7; c.drawImage(frontC, 0, 0); c.restore();
|
|
705
|
+
if (!g) return;
|
|
706
|
+
const lift = clamp((j + 1) / 4), CR = g.R;
|
|
707
|
+
// throat: the roll's cast shadow on the revealed page, just beyond the crest
|
|
708
|
+
c.save(); halfPath(c, side, 0); c.clip(); band(c, g, CR - 4, CR + 120); c.clip();
|
|
709
|
+
const tg = c.createLinearGradient(g.nx * (g.d0 + CR), g.ny * (g.d0 + CR), g.nx * (g.d0 + CR + 120), g.ny * (g.d0 + CR + 120));
|
|
710
|
+
tg.addColorStop(0, 'rgba(30,12,4,.45)'); tg.addColorStop(0.3, 'rgba(30,12,4,.22)'); tg.addColorStop(1, 'rgba(30,12,4,0)'); c.fillStyle = tg; c.fillRect(0, 0, W, H); c.restore();
|
|
711
|
+
// the lifted paper: recto quarter (hidden under the roll except at the dog-ear tip), verso quarter (lit crest), flat verso flap
|
|
712
|
+
flapX.setTransform(1, 0, 0, 1, 0, 0); flapX.clearRect(0, 0, W, H);
|
|
713
|
+
const NS = 12, q = PI * CR / 2 / NS;
|
|
714
|
+
for (let pass = 0; pass < 2; pass++) for (let i = 0; i < NS; i++) {
|
|
715
|
+
const da = (pass * NS + i) * q, db = da + q, fa = fmap(da, CR), fb = fmap(db, CR), B = (fb - fa) / q, A = fa - B * da, v = Math.sin((da + db) / 2 / CR);
|
|
716
|
+
flapX.save(); setMap(flapX, g, A, B); band(flapX, g, da - 0.7, db + 0.7); flapX.clip(); paint(flapX, pass === 1);
|
|
717
|
+
flapX.fillStyle = pass ? shadeV(v) : `rgba(20,10,4,${(0.36 * v * v).toFixed(3)})`; flapX.fillRect(-W, -H, 3 * W, 3 * H);
|
|
718
|
+
flapX.restore();
|
|
719
|
+
}
|
|
720
|
+
flapX.save(); setMap(flapX, g, PI * CR, -1); band(flapX, g, PI * CR - 0.7, 1e4); flapX.clip(); paint(flapX, true);
|
|
721
|
+
const e0 = g.d0 + PI * CR, lg = flapX.createLinearGradient(g.nx * e0, g.ny * e0, g.nx * (e0 + 560), g.ny * (e0 + 560));
|
|
722
|
+
lg.addColorStop(0, 'rgba(255,246,226,.12)'); lg.addColorStop(0.28, 'rgba(255,246,226,0)'); lg.addColorStop(1, 'rgba(40,24,10,.34)');
|
|
723
|
+
flapX.fillStyle = lg; flapX.fillRect(-W, -H, 3 * W, 3 * H); flapX.restore();
|
|
724
|
+
flapX.setTransform(1, 0, 0, 1, 0, 0);
|
|
725
|
+
c.save(); c.shadowColor = 'rgba(34,20,8,.45)'; c.shadowBlur = lerp(14, 42, lift); c.shadowOffsetX = lerp(8, 34, lift); c.shadowOffsetY = lerp(12, 58, lift);
|
|
726
|
+
c.drawImage(flapC, 0, 0); c.restore();
|
|
727
|
+
}
|
|
728
|
+
// the artist's hand (the one that pointed at the brad) lifts at 7.417, pinches the top of the page at 7.5 (2 steps, 3% squash),
|
|
729
|
+
// and drags the left half's corner up-left as it curls away
|
|
730
|
+
function tearHand(c, t) {
|
|
731
|
+
const k = K(t) - K(C.tear_hand); if (k < 0 || k > 5) return;
|
|
732
|
+
const G = [ripX(70) - 30, 70];
|
|
733
|
+
let tip, s = 1.3, rot = 0.2, lift = 0, sx = 1, sy = 1;
|
|
734
|
+
if (k === 0) { tip = [lerp(598, G[0] - 90, 0.6), lerp(506, G[1] - 60, 0.6)]; s = 1.12; rot = 0.08; lift = 1.3; }
|
|
735
|
+
else if (k === 1) { tip = G; sx = 1.03; sy = 0.97; lift = 0.02; }
|
|
736
|
+
else if (k === 2) { tip = [G[0] - 8, G[1] - 10]; lift = 0.15; sx = 0.99; sy = 1.02; }
|
|
737
|
+
else { const j = k - 3; tip = peelPoint(-1, j, G); rot = 0.2 - j * 0.12; lift = 0.35 + j * 0.35; s = 1.3 + j * 0.05; }
|
|
738
|
+
piece(c, I.maniR, tip[0], tip[1], { ax: MT[0], ay: MT[1], rot, s, sx, sy, lift, id: 15, t });
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
// ------------------------------------------------------------------ finishing layers
|
|
742
|
+
const dustC = [I.dust1, I.dust2].map((img, n) => { const c = off(W, H), x = c.getContext('2d'); x.filter = 'invert(1)'; x.drawImage(img, 0, 0, W, H);
|
|
743
|
+
if (n === 1) x.clearRect(940, 400, 180, 210); // remove the fibre-loop "ghost ring" from dust_03
|
|
744
|
+
return c; });
|
|
745
|
+
function finish(c, t) {
|
|
746
|
+
const k = K(t);
|
|
747
|
+
c.save(); c.globalCompositeOperation = 'overlay'; c.globalAlpha = 0.26;
|
|
748
|
+
c.drawImage(I.grain, -Math.floor(hash(k, 1) * 120), -Math.floor(hash(k, 2) * 900)); c.restore();
|
|
749
|
+
c.save(); c.globalCompositeOperation = 'multiply'; c.globalAlpha = 0.35; c.drawImage(dustC[Math.floor(k / 2) % 2], (k % 3) * -3, 0, W + 6, H); c.restore();
|
|
750
|
+
const g = c.createRadialGradient(W / 2, H / 2, H * 0.45, W / 2, H / 2, H * 1.05);
|
|
751
|
+
g.addColorStop(0, 'rgba(40,20,5,0)'); g.addColorStop(1, 'rgba(40,20,5,.28)');
|
|
752
|
+
c.save(); c.globalCompositeOperation = 'multiply'; c.fillStyle = g; c.fillRect(0, 0, W, H); c.restore();
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// ------------------------------------------------------------------ frame
|
|
756
|
+
window.renderAt = async (t) => {
|
|
757
|
+
t = Math.min(Math.max(t, 0), 9.999);
|
|
758
|
+
X.setTransform(1, 0, 0, 1, 0, 0); noShadow(X); X.globalAlpha = 1; X.globalCompositeOperation = 'source-over';
|
|
759
|
+
const kt = ks(t, C.tear[0]); // tear starts at 7.5 (record scratch freezes the page)
|
|
760
|
+
if (kt < 0) {
|
|
761
|
+
X.save(); applyCam(X, cam(t)); mainPage(X, t); X.restore();
|
|
762
|
+
} else {
|
|
763
|
+
const kk = kt - 2; // 0 at 7.667: halves crack apart, then peel
|
|
764
|
+
X.drawImage(I.red, 0, 0);
|
|
765
|
+
X.save();
|
|
766
|
+
if (kk === 0 || kk === 1) X.translate(...[[6, -5], [-3, 3]][kk]); // 2-frame camera shake on the rip
|
|
767
|
+
endPage(X, t);
|
|
768
|
+
pageX.setTransform(1, 0, 0, 1, 0, 0);
|
|
769
|
+
pageX.save(); applyCam(pageX, cam(C.tear[0] - 1e-3)); mainPage(pageX, C.tear[0] - 1e-3); pageX.restore();
|
|
770
|
+
if (kt < 2) { // the rip runs down the page first (2 steps)
|
|
771
|
+
X.drawImage(pageC, 0, 0);
|
|
772
|
+
const n = Math.round(TEAR.length * [0.42, 0.8][kt]);
|
|
773
|
+
X.save(); X.lineJoin = 'round';
|
|
774
|
+
for (const [w, col] of [[11, 'rgba(30,18,8,.35)'], [6, '#EFE7D6']]) {
|
|
775
|
+
X.beginPath(); TEAR.slice(0, n).forEach(([x, y], j) => j ? X.lineTo(x + 2, y) : X.moveTo(x + 2, y));
|
|
776
|
+
X.lineWidth = w; X.strokeStyle = col; X.stroke();
|
|
777
|
+
}
|
|
778
|
+
X.restore();
|
|
779
|
+
} else if (kk < 6) {
|
|
780
|
+
curlHalf(X, -1, kk); curlHalf(X, 1, kk - 1);
|
|
781
|
+
if (kk < 2) flecks(X, TEAR.filter((_, i) => i % 9 === 4).map(([x, y], i) => [x, y, i % 2 ? 1 : -1, (hash(71, i) - .5)]), kk, 700, 10);
|
|
782
|
+
}
|
|
783
|
+
tearHand(X, t);
|
|
784
|
+
X.restore();
|
|
785
|
+
}
|
|
786
|
+
if (kt < 0) tearHand(X, t);
|
|
787
|
+
finish(X, t);
|
|
788
|
+
};
|
|
789
|
+
// ------------------------------------------------------------------ reel v5: link elements (pure functions of t)
|
|
790
|
+
// Everything below mirrors the drawing code above (same slap / jit / cam maths), in 1920×1080 output pixels.
|
|
791
|
+
if (V5) {
|
|
792
|
+
const camPt = (k, x, y) => [(x - k.px) * k.z + k.px + k.cx, (y - k.py) * k.z + k.py + k.cy];
|
|
793
|
+
const headMainS = t => { // = the portrait transform built in mainPage()
|
|
794
|
+
const kp = ks(t, C.pop), k0 = ks(t, C.slap_portrait);
|
|
795
|
+
const IN = [[1100, -100, -6, 1.08], [560, -50, -3.5, 1.05], [200, -18, -1.5, 1.025], [40, -4, -0.4, 1.008]];
|
|
796
|
+
let S = { x: 845, y: 554, s: 1, rot: 0, sqx: 1, sqy: 1 };
|
|
797
|
+
if (k0 < 0) { const p = IN[clamp(4 + k0, 0, 3)]; S = { x: 845 + p[0], y: 554 + p[1], rot: p[2] * DEG, s: p[3], sqx: 1, sqy: 1 }; }
|
|
798
|
+
else if (k0 === 0) { S.sqx = 1.02; S.sqy = 0.96; S.y += 14; }
|
|
799
|
+
else if (k0 === 1) S.sqy = 1.01;
|
|
800
|
+
let jaw = 0; for (const h of C.jaw) { const k = ks(t, h); if (k >= -1 && k < 3) jaw = Math.max(jaw, [0.5, 1, 0.5, 0][k + 1]); }
|
|
801
|
+
if (jaw === 1) S.y -= 7;
|
|
802
|
+
S.rot += jaw * -1.6 * DEG;
|
|
803
|
+
if (kp >= 0 && kp < 3) { S.rot += [2.6, -1.8, 0.8][kp] * DEG; S.y += [16, -8, 3][kp]; }
|
|
804
|
+
const jp = jit(1, t, (k0 < 3 || jaw || kp >= 0 && kp < 5) ? 2 : 1, (k0 < 3 || jaw) ? 12 : 6);
|
|
805
|
+
S.x += jp[0]; S.y += jp[1]; S.rot += jp[2];
|
|
806
|
+
return S;
|
|
807
|
+
};
|
|
808
|
+
const headEndS = t => { // = the reprise-head transform built in endPage()
|
|
809
|
+
const kj = ks(t, C.end_jaw), kl = ks(t, C.end_lid);
|
|
810
|
+
let jaw = kj >= 0 && kj < 4 ? [1, 1, 0.5, 0][kj] : 0; if (kl === 0) jaw = 0.5;
|
|
811
|
+
const jp = jit(51, t, kl >= 0 && kl < 2 ? 2 : 1.1, kl >= 0 && kl < 2 ? 12 : 6);
|
|
812
|
+
return { x: 1490 + jp[0], y: 716 + jp[1] - (jaw === 1 ? 5 : 0) + (kl === 0 ? 4 : 0), s: 0.6, rot: 2 * DEG + jp[2] - jaw * 1.5 * DEG, sqx: 1, sqy: 1 };
|
|
813
|
+
};
|
|
814
|
+
const headPt = (S, lx, ly) => { const ax = (lx - PL.head_center[0]) * S.s * S.sqx, ay = (ly - PL.head_center[1]) * S.s * S.sqy;
|
|
815
|
+
return [S.x + ax * Math.cos(S.rot) - ay * Math.sin(S.rot), S.y + ax * Math.sin(S.rot) + ay * Math.cos(S.rot)]; };
|
|
816
|
+
const earthLocal = (ts, tl) => { // = drawSpill() for the Earth ('lob'): local centre + scale
|
|
817
|
+
const it = SPILL[1], k = ks(ts, it.t); if (k < 0) return null;
|
|
818
|
+
const Fl = [it.F[0] - OFF[0], it.F[1] - OFF[1]], M = MOUTH, fl = 6, e = eOut(clamp(k / fl));
|
|
819
|
+
const cx = (M[0] + Fl[0]) / 2, cy = Math.min(M[1], Fl[1]) + it.arc;
|
|
820
|
+
const s = k < fl ? lerp(0.35, 1, e) : (k === fl ? 1.06 : 1), sx = k === fl ? 1.04 : 1, sy = k === fl ? 0.95 : 1;
|
|
821
|
+
const [jx, jy] = jit(it.id, tl, k <= fl ? 2 : 1.1, k <= fl ? 12 : 6);
|
|
822
|
+
return { x: bez(M[0], cx, Fl[0], e) + jx, y: bez(M[1], cy, Fl[1], e) + jy, s: s * (sx + sy) / 2, k };
|
|
823
|
+
};
|
|
824
|
+
const bbox = (els) => { let x0 = 1e9, y0 = 1e9, x1 = -1e9, y1 = -1e9;
|
|
825
|
+
for (const [x, y, w, h, r] of els) for (const [u, v] of [[-1, -1], [1, -1], [1, 1], [-1, 1]]) {
|
|
826
|
+
const px = x + (u * w / 2) * Math.cos(r) - (v * h / 2) * Math.sin(r), py = y + (u * w / 2) * Math.sin(r) + (v * h / 2) * Math.cos(r);
|
|
827
|
+
x0 = Math.min(x0, px); y0 = Math.min(y0, py); x1 = Math.max(x1, px); y1 = Math.max(y1, py); }
|
|
828
|
+
return { x: (x0 + x1) / 2, y: (y0 + y1) / 2, w: x1 - x0, h: y1 - y0 }; };
|
|
829
|
+
const R = v => Math.round(v * 10) / 10;
|
|
830
|
+
window.linkAt = (t) => {
|
|
831
|
+
t = Math.min(Math.max(t, 0), 9.999);
|
|
832
|
+
const out = [], kt = ks(t, C.tear[0]);
|
|
833
|
+
// ---- main page (kraft): live until the tear; frozen at 7.5 while the rip runs down; torn apart from 7.667
|
|
834
|
+
if (kt < 2) {
|
|
835
|
+
const tm = kt < 0 ? t : C.tear[0] - 1e-3, k = cam(tm), kp = ks(tm, C.pop);
|
|
836
|
+
const sd = slap(tm, C.slap_circle);
|
|
837
|
+
if (sd) {
|
|
838
|
+
const [jx, jy] = jit(5, tm, sd.lift ? 2 : 1, sd.lift ? 12 : 6), [x, y] = camPt(k, 845 + jx - 40 * sd.lift, 468 + jy - 20 * sd.lift);
|
|
839
|
+
out.push({ id: 'dot', type: 'circle', x: R(x), y: R(y), r: R(352 * sd.s * k.z), a: 1, look: 'paper', fill: '#B42620', stroke: '#B42620', sw: 0,
|
|
840
|
+
vis: kp >= 0 ? 0.11 : sd.k < 0 ? 0.6 : 0.4, z: 'behind the portrait (and after 4.0 behind the space wedge + band)', // vis = measured unoccluded fraction (v5 vs nolink=dot diff)
|
|
841
|
+
note: 'red riso paper disc (constructivist sun) behind the head; cut edge ±2.5 px, soft drop shadow (3,5) blur 5' });
|
|
842
|
+
}
|
|
843
|
+
const S = headMainS(tm), [bx, by] = camPt(k, ...headPt(S, HG[0], HG[1]));
|
|
844
|
+
out.push({ id: 'brad', type: 'circle', x: R(bx), y: R(by), r: R(15 * S.s * k.z), a: 1, look: 'sphere', fill: '#D6A945', stroke: '#3C2608', sw: 1.6,
|
|
845
|
+
grad: ['#FFE9A6', '#7A5516'], note: 'brass paper fastener = the lid hinge (the manicule presses it at 0–0.33 and points at it at 6.0)' });
|
|
846
|
+
const el = earthLocal(tm, tm);
|
|
847
|
+
if (el) { const [ex, ey] = camPt(k, ...headPt(S, el.x, el.y));
|
|
848
|
+
out.push({ id: 'earth', type: 'circle', x: R(ex), y: R(ey), r: R(118 * el.s * S.s * k.z), a: 1, look: 'halftone-photo', fill: '#3F6FA8', stroke: '#F4EFE4', sw: 4,
|
|
849
|
+
note: el.k < 6 ? 'Earth cut-out lobbed out of the open skull' : 'Earth cut-out at rest (halftone photo, white scissor rim, 4 small tabs to r 131)' }); }
|
|
850
|
+
const sb = slap(tm, C.band, 2);
|
|
851
|
+
if (sb) {
|
|
852
|
+
const [jx, jy, jr] = jit(15, tm, sb.k < 2 ? 2 : 1, sb.k < 2 ? 12 : 6), slide = sb.k < 0 ? (-sb.k) * 260 : 0;
|
|
853
|
+
const cx = 900 + jx - slide, cy = 842 + jy + slide * 0.18, a = -10 * DEG + jr, L = 1250 * sb.s;
|
|
854
|
+
let [x0, y0] = camPt(k, cx - Math.cos(a) * L, cy - Math.sin(a) * L), [x1, y1] = camPt(k, cx + Math.cos(a) * L, cy + Math.sin(a) * L);
|
|
855
|
+
const yx = x => y0 + (y1 - y0) * (x - x0) / (x1 - x0);
|
|
856
|
+
if (x0 < 0) { y0 = yx(0); x0 = 0; } if (x1 > W) { y1 = yx(W); x1 = W; }
|
|
857
|
+
out.push({ id: 'band', type: 'line', x0: R(x0), y0: R(y0), x1: R(x1), y1: R(y1), w: R(154 * sb.s * k.z), a: 1, fill: '#16130F',
|
|
858
|
+
note: 'black torn constructivist band "THE MIND IS A COLLAGE" (−10°), a 30 px red rule under it' });
|
|
859
|
+
}
|
|
860
|
+
const BC = [1601, 552], BS = 1.12, bxf = x => BC[0] + (x - 1601.5) * BS, byf = y => BC[1] + (y - 571.5) * BS;
|
|
861
|
+
const HP = [[1484, 468, -6.5, 21], [1706, 452, 4.5, 22], [1496, 690, 3, 23], [1720, 676, -4.5, 24]], ch = [];
|
|
862
|
+
HP.forEach((q, i) => { const s = slap(tm, C.hero[i], 2); if (!s || s.k < 0) return; const [jx, jy, jr] = jit(q[3], tm, 1.1, 6);
|
|
863
|
+
const [x, y] = camPt(k, bxf(q[0]) + 30 * s.lift + jx, byf(q[1]) - 40 * s.lift + jy); ch.push([x, y, (212 * BS + 10) * s.s * k.z, (212 * BS + 10) * s.s * k.z, q[2] * DEG + jr]); });
|
|
864
|
+
if (ch.length) { const b = bbox(ch); out.push({ id: 'title', type: 'rect', x: R(b.x), y: R(b.y), w: R(b.w), h: R(b.h), rot: 0, a: ch.length / 4,
|
|
865
|
+
text: '拼贴剪贴', note: 'hero: 拼·贴·剪·贴 ransom chips, 2×2 block (landed ' + ch.length + '/4)' }); }
|
|
866
|
+
}
|
|
867
|
+
// ---- end card (red riso), revealed by the tear (fully clear from 8.083)
|
|
868
|
+
if (t >= 8.0833) {
|
|
869
|
+
const S = headEndS(t), el = earthLocal(9.9, t), [ex, ey] = headPt(S, el.x, el.y);
|
|
870
|
+
out.push({ id: 'earth', type: 'circle', x: R(ex), y: R(ey), r: R(118 * S.s), a: 1, look: 'halftone-photo', fill: '#3F6FA8', stroke: '#F4EFE4', sw: 2.4,
|
|
871
|
+
note: 'Earth cut-out in the reprise head (end card)' });
|
|
872
|
+
const [bx, by] = headPt(S, HG[0], HG[1]);
|
|
873
|
+
out.push({ id: 'brad', type: 'circle', x: R(bx), y: R(by), r: R(15 * S.s), a: 1, look: 'sphere', fill: '#D6A945', stroke: '#3C2608', sw: 1,
|
|
874
|
+
grad: ['#FFE9A6', '#7A5516'], note: 'brass brad of the reprise head (lid snaps shut on it at 9.5)' });
|
|
875
|
+
const ch = [];
|
|
876
|
+
ECH.forEach((q, i) => { const s = slap(t, V5E.chips[i], 2); if (!s || s.k < 0) return; const [jx, jy, jr] = jit(q[10], t, s.lift > 0 ? 2 : 1.1, s.lift > 0 ? 12 : 6);
|
|
877
|
+
ch.push([q[7] + 30 * s.lift + jx, q[8] - 40 * s.lift + jy, (q[5] + 10) * s.s, (q[6] + 10) * s.s, q[9] * DEG + jr]); });
|
|
878
|
+
if (ch.length) { const b = bbox(ch); out.push({ id: 'title', type: 'rect', x: R(b.x), y: R(b.y), w: R(b.w), h: R(b.h), rot: 0, a: ch.length / 4,
|
|
879
|
+
text: '拼贴剪贴', note: 'end-card masthead: 拼·贴·剪·贴 ransom chips in a row (landed ' + ch.length + '/4)' }); }
|
|
880
|
+
const sb = slap(t, C.end_button, 2);
|
|
881
|
+
if (sb) { const [jx, jy] = jit(55, t, sb.k < 2 ? 2 : 1, sb.k < 2 ? 12 : 6);
|
|
882
|
+
out.unshift({ id: 'dot', type: 'circle', x: R(380 + jx), y: R(870 + jy), r: R(118 * sb.s), a: 1, look: 'sticker', fill: '#16130F', stroke: '#EFE8DA', sw: R(12 * sb.s),
|
|
883
|
+
vis: 1, z: 'top (unoccluded)', note: 'round price sticker 随刊附赠 剪刀一把: cream paper rim + black disc with cream text; its tiny ✂ (top right) snips at 9.583' }); }
|
|
884
|
+
}
|
|
885
|
+
return out;
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
window.__ready = true;
|
|
889
|
+
</script></body></html>
|