diff --git a/.gitattributes b/.gitattributes index c90a6677615d3fc27cd3b6f78d85d7721856bb38..171b382f1bc3629936be001de89eb6a90843d99c 100644 --- a/.gitattributes +++ b/.gitattributes @@ -160,3 +160,8 @@ wolf-coming/web/public/film/live/06-fly.jpg filter=lfs diff=lfs merge=lfs -text wolf-coming/web/public/film/qa/sheet_landscape.jpg filter=lfs diff=lfs merge=lfs -text wolf-coming/web/public/film/qa/sheet_portrait.jpg filter=lfs diff=lfs merge=lfs -text wolf-coming/web/public/film/voice.mp3 filter=lfs diff=lfs merge=lfs -text +wolf-coming/video/film/fonts/mashanzheng.woff2 filter=lfs diff=lfs merge=lfs -text +wolf-coming/video/film/fonts/wenkai.woff2 filter=lfs diff=lfs merge=lfs -text +wolf-coming/video/film/voice.mp3 filter=lfs diff=lfs merge=lfs -text +wolf-coming/video/wolf/voice.mp3 filter=lfs diff=lfs merge=lfs -text +wolf-coming/web/public/wolf/voice.mp3 filter=lfs diff=lfs merge=lfs -text diff --git a/wolf-coming/README.md b/wolf-coming/README.md index 9166285f4c3c02ed1e3b6fb5171210f024284698..e499f45aab8880eb5f65d91f85f31adce350c162 100644 --- a/wolf-coming/README.md +++ b/wolf-coming/README.md @@ -60,12 +60,16 @@ - 每帧只由 t 决定;`vibe det` 校验。 ## 4. 步骤 -1. 写本 README → 推送 HF -2. 分镜 + 多角色配音 → 时间轴 → 推送 -3. 引擎多片化(《丑小鸭》画面逐像素不变)+ 本片角色与 14 场 → 接触表 / 静帧 / det → 推送 -4. 配乐音效混音 + 网页 → 推送 +1. ✅ 写本 README → 推送 HF +2. ✅ 分镜 + 多角色配音 → 时间轴 → 推送 +3. ✅ 引擎多片化(《丑小鸭》画面逐像素不变)+ 本片角色与 14 场 → 接触表 / 静帧 / det → 推送 +4. ✅ 配乐音效混音 + 网页 → 推送 5. 封面 + 四平台文案 + 实况图 → 推送 6. 全片渲染 + `vibe qa` → 修复 → 部署 `/wolf-coming/` → 最终推送 ## 5. 进度日志 - **2026-10-18** 写下本计划。沿用上一部确认过的风格与交付要求。 +- **2026-10-18** 分镜 14 场(`video/storyboard.json`),23 句台词三种声音:旁白晓晓、放羊娃云夏、农民云健。`scripts/tts_multi.py` 逐句合成 → 按角色统一响度 → 拼接人声轨 + `timeline.json`(与 vibe 同格式,另含 lines/who),全片 137.9 秒。 +- **2026-10-18** 引擎多片化:`web/src/film/` 只放共用引擎(宣纸 / 毛笔 / 合成器 / 混音器 / 导出 / 封面 / 实况图),每部片子在 `web/src/films/<片名>/` 提供时间轴、场景、配乐、文案。改造后《丑小鸭》11 个时间点逐帧 MD5 与改造前完全一致;音轨差异 ≤1 LSB(与同一份旧代码两次渲染之间的差异相同,来自浏览器混响多线程)。 +- **2026-10-18** 本片角色:放羊娃(IK 骨架:站、走、躺、喊、笑、怕、坐抱小羊;冲天辫、红褂、表情系统)、农民 ×4(头巾 / 草帽 / 发髻,锄地、扛锄奔跑、叉腰、指责、摆手)、羊(卷毛、吃草、奔逃)、狼(潜行、奔跑、龇牙、叼羊、树丛里的绿眼睛);景物:梯田、茅草屋炊烟、山石皴法、大树、灌木、乌云;「狼来了」朱红大字从嘴边喊出、第三次被风吹散成墨点。 +- **2026-10-18** 配乐音效:调皮主题(拨弦跳音 + 木鱼)、田园笛、狼来时低音颤奏 + 心跳 + 不协和重音、羊群奔逃的急促颤奏、夕阳下的笛声哀歌;音效含合成羊叫、山谷回声(把放羊娃的喊声再放三遍、越来越远)、锄头落地(与画面锄地动作逐拍对齐)、脚步、喘气、雷声、狼嚎、低吼、羊群奔逃、蟋蟀。响度 −14.0 LUFS。 diff --git a/wolf-coming/scripts/build_bundle.sh b/wolf-coming/scripts/build_bundle.sh new file mode 100644 index 0000000000000000000000000000000000000000..3c12e45738d51a4d88d778271f93d38eb8a0fbb5 --- /dev/null +++ b/wolf-coming/scripts/build_bundle.sh @@ -0,0 +1,3 @@ +#!/bin/sh +# 打包无头渲染入口(两个 vibe 项目共用同一份 bundle;狼来了的 index.html 自动带 ?film=wolf) +cd /workspaces/frontend && ./node_modules/.bin/esbuild src/film/standalone.ts --bundle --format=iife --outfile=/workspaces/ugly-duckling/video/film.bundle.js --loader:.json=json --log-level=error && cp /workspaces/ugly-duckling/video/film.bundle.js /workspaces/wolf-coming/video/film.bundle.js diff --git a/wolf-coming/scripts/tts_multi.py b/wolf-coming/scripts/tts_multi.py new file mode 100644 index 0000000000000000000000000000000000000000..301fef849f871de3bef77b2567643c562be94dcd --- /dev/null +++ b/wolf-coming/scripts/tts_multi.py @@ -0,0 +1,61 @@ +#!/usr/bin/env python3 +"""多角色配音 → timeline.json(与 vibe-voice tts.py 同格式,另含 lines/who)+ audio/voice.wav + out/subs.srt +每个场景的台词按顺序合成(edge-tts,不同角色不同音色),句间留气口;场景时长 = max(min_dur, 前导 + 台词 + 气口 + 尾巴)。""" +import asyncio, json, re, subprocess, sys +from pathlib import Path +import edge_tts +proj = Path(sys.argv[1]).resolve(); sb = json.loads((proj / 'storyboard.json').read_text(encoding='utf-8')) +V = sb['voices']; fps = sb['format']['fps']; LEAD, TAIL, GAP, GAP2 = 0.5, 0.9, 0.22, 0.38 +ad = proj / 'audio'; ad.mkdir(exist_ok=True); (proj / 'out').mkdir(exist_ok=True) +def dur(p): return float(subprocess.run(['ffprobe', '-v', 'error', '-show_entries', 'format=duration', '-of', 'csv=p=0', str(p)], capture_output=True, text=True).stdout.strip()) +def loud(p): + e = subprocess.run(['ffmpeg', '-nostats', '-i', str(p), '-filter_complex', 'ebur128', '-f', 'null', '-'], capture_output=True, text=True).stderr + m = re.findall(r'I:\s+(-?[\d.]+) LUFS', e); return float(m[-1]) if m else -20.0 +TARGET = {'n': -17.0, 'boy': -16.0, 'farmer': -16.5} # 各角色台词统一响度(喊叫稍响) +def split(text, maxlen=16): + parts = [x.strip() for x in re.split(r'(?<=[,。!?;:])', text) if x.strip()] + out = [] + for p in parts: + p = p.rstrip(',。;:') + while len(p) > maxlen: out.append(p[:maxlen]); p = p[maxlen:] + if p: out.append(p) + return out +async def say(text, v, path): + for k in range(3): + try: await edge_tts.Communicate(text, v['voice'], rate=v['rate'], pitch=v['pitch']).save(str(path)); return + except Exception as e: + if k == 2: raise +t = 0.0; scenes = []; clips = []; srt = [] +for i, sc in enumerate(sb['scenes']): + start = t; cur = start + LEAD; lines = []; prev = None + for j, ln in enumerate(sc['lines']): + f = ad / f"v_{i+1:02}_{j+1}_{ln['who']}.mp3" + if not f.exists(): asyncio.run(say(ln['text'], V[ln['who']], f)) + d = dur(f) + if prev is not None: cur += GAP2 if prev != ln['who'] else GAP + subs = split(ln['text']); tot = sum(len(s) for s in subs); x = cur; sub_items = [] + for s in subs: + dd = d * len(s) / tot; sub_items.append({'t0': round(x, 3), 't1': round(x + dd, 3), 'text': s, 'who': ln['who']}); x += dd + lines.append({'who': ln['who'], 'text': ln['text'], 't0': round(cur, 3), 'dur': round(d, 3), 'subs': sub_items}) + clips.append((f, cur, loud(f), ln['who'])); cur += d; prev = ln['who'] + dd = max(sc.get('min_dur', 2), cur + TAIL - start); dd = round(dd * fps) / fps + subs_all = [s for l in lines for s in l['subs']] + scenes.append({'id': sc['id'], 'start': round(start, 4), 'dur': dd, 'voice_start': lines[0]['t0'], 'voice_dur': round(cur - lines[0]['t0'], 3), 'lines': lines, 'subs': subs_all}) + print(f"{sc['id']:8} {start:7.2f}s +{dd:5.2f}s " + ' / '.join(f"{l['who']}:{l['text'][:10]}" for l in lines)) + t = start + dd +total = round(t * fps) / fps +tl = {'title': sb['title'], 'style': sb['style'], 'w': sb['format']['w'], 'h': sb['format']['h'], 'fps': fps, 'total': total, 'frames': int(round(total * fps)), 'voice': {k: v['voice'] for k, v in V.items()}, 'scenes': scenes} +(proj / 'timeline.json').write_text(json.dumps(tl, ensure_ascii=False, indent=1), encoding='utf-8') +# 拼接人声轨:每句按时间放置 +args = ['ffmpeg', '-loglevel', 'error', '-y'] +for c in clips: args += ['-i', str(c[0])] +fl = ''.join(f"[{k}:a]aresample=48000,volume={TARGET[who] - L:.2f}dB,adelay={int(st*1000)}|{int(st*1000)}[a{k}];" for k, (_, st, L, who) in enumerate(clips)) +fl += ''.join(f"[a{k}]" for k in range(len(clips))) + f"amix=inputs={len(clips)}:normalize=0:dropout_transition=0,apad,atrim=0:{total}[out]" +subprocess.run(args + ['-filter_complex', fl, '-map', '[out]', '-ac', '2', str(ad / 'voice.wav')], check=True) +def ts(x): ms = int(round(x * 1000)); return f"{ms//3600000:02}:{ms//60000%60:02}:{ms//1000%60:02},{ms%1000:03}" +n = 0 +for s in scenes: + for c in s['subs']: + n += 1; srt.append(f"{n}\n{ts(c['t0'])} --> {ts(c['t1'])}\n{c['text']}\n") +(proj / 'out/subs.srt').write_text('\n'.join(srt), encoding='utf-8') +print(f'总时长 {total:.2f}s,{len(clips)} 句') diff --git a/wolf-coming/video/audio/v_01_1_n.mp3 b/wolf-coming/video/audio/v_01_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ab4543a1311a0a7d8bfc58080d75902e52abbf5f Binary files /dev/null and b/wolf-coming/video/audio/v_01_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_02_1_n.mp3 b/wolf-coming/video/audio/v_02_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..08dde484384be3de0385810ec9c2e03ef4661594 Binary files /dev/null and b/wolf-coming/video/audio/v_02_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_03_1_n.mp3 b/wolf-coming/video/audio/v_03_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..e29c6a0dc2497469e8a7f60d3cd57592874875c0 Binary files /dev/null and b/wolf-coming/video/audio/v_03_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_04_1_n.mp3 b/wolf-coming/video/audio/v_04_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..fa906a2ccc7123b86653cf7f1c3566171faeb0d0 Binary files /dev/null and b/wolf-coming/video/audio/v_04_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_04_2_boy.mp3 b/wolf-coming/video/audio/v_04_2_boy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..b93a23c78980c964ced4b40c1389796555ce31ec Binary files /dev/null and b/wolf-coming/video/audio/v_04_2_boy.mp3 differ diff --git a/wolf-coming/video/audio/v_05_1_n.mp3 b/wolf-coming/video/audio/v_05_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1ced9ccaee1c335c79db88e1286ea141c6afdc19 Binary files /dev/null and b/wolf-coming/video/audio/v_05_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_05_2_farmer.mp3 b/wolf-coming/video/audio/v_05_2_farmer.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..adc100297d44e9157f6775febaef2a47dd62c436 Binary files /dev/null and b/wolf-coming/video/audio/v_05_2_farmer.mp3 differ diff --git a/wolf-coming/video/audio/v_06_1_boy.mp3 b/wolf-coming/video/audio/v_06_1_boy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7270188239a8d081d5188e8742f14416acd3e243 Binary files /dev/null and b/wolf-coming/video/audio/v_06_1_boy.mp3 differ diff --git a/wolf-coming/video/audio/v_06_2_n.mp3 b/wolf-coming/video/audio/v_06_2_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..93d16637fb814a4f9bbc87d93e03c3fbbe76e110 Binary files /dev/null and b/wolf-coming/video/audio/v_06_2_n.mp3 differ diff --git a/wolf-coming/video/audio/v_07_1_n.mp3 b/wolf-coming/video/audio/v_07_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..4dba54eff32bc5577906f94d9ca75255bd34319c Binary files /dev/null and b/wolf-coming/video/audio/v_07_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_07_2_boy.mp3 b/wolf-coming/video/audio/v_07_2_boy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d4f735bad861713aed15b9b7e2ca1ec47d03d966 Binary files /dev/null and b/wolf-coming/video/audio/v_07_2_boy.mp3 differ diff --git a/wolf-coming/video/audio/v_07_3_n.mp3 b/wolf-coming/video/audio/v_07_3_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..5e1b17d2909ae65dedcd61f0d7935fab14e06741 Binary files /dev/null and b/wolf-coming/video/audio/v_07_3_n.mp3 differ diff --git a/wolf-coming/video/audio/v_08_1_boy.mp3 b/wolf-coming/video/audio/v_08_1_boy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a8eeddd8bc656c78b0b8b9adfd6c11740e351ca5 Binary files /dev/null and b/wolf-coming/video/audio/v_08_1_boy.mp3 differ diff --git a/wolf-coming/video/audio/v_08_2_farmer.mp3 b/wolf-coming/video/audio/v_08_2_farmer.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..1b24e93dbef8149398f1e6d01754451f3986ea1d Binary files /dev/null and b/wolf-coming/video/audio/v_08_2_farmer.mp3 differ diff --git a/wolf-coming/video/audio/v_09_1_n.mp3 b/wolf-coming/video/audio/v_09_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..d7602454bc6008a26e5a2e1c0d677406ce35a2d7 Binary files /dev/null and b/wolf-coming/video/audio/v_09_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_10_1_n.mp3 b/wolf-coming/video/audio/v_10_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..6475ff758867e64ae00e42b9a47376597e7a5f09 Binary files /dev/null and b/wolf-coming/video/audio/v_10_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_10_2_boy.mp3 b/wolf-coming/video/audio/v_10_2_boy.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..ac4d35f509b65bae6c711bfc90058c0259a43158 Binary files /dev/null and b/wolf-coming/video/audio/v_10_2_boy.mp3 differ diff --git a/wolf-coming/video/audio/v_11_1_n.mp3 b/wolf-coming/video/audio/v_11_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..a867e7fe89350bbfb9eb8c43f47ffaa9e7a767e9 Binary files /dev/null and b/wolf-coming/video/audio/v_11_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_11_2_farmer.mp3 b/wolf-coming/video/audio/v_11_2_farmer.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7e808b07d71c5d1f41673f5e54913a8bb554e9a1 Binary files /dev/null and b/wolf-coming/video/audio/v_11_2_farmer.mp3 differ diff --git a/wolf-coming/video/audio/v_11_3_n.mp3 b/wolf-coming/video/audio/v_11_3_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..754ae0b4a2baeb88fe5b8cc27dbac2a4e8e43b86 Binary files /dev/null and b/wolf-coming/video/audio/v_11_3_n.mp3 differ diff --git a/wolf-coming/video/audio/v_12_1_n.mp3 b/wolf-coming/video/audio/v_12_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..288dd971ad580d8002cc87eefb33dde8db2e7a45 Binary files /dev/null and b/wolf-coming/video/audio/v_12_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_13_1_n.mp3 b/wolf-coming/video/audio/v_13_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..c1f0694fc9749de63370ee1c32fab1fd0f1e723c Binary files /dev/null and b/wolf-coming/video/audio/v_13_1_n.mp3 differ diff --git a/wolf-coming/video/audio/v_14_1_n.mp3 b/wolf-coming/video/audio/v_14_1_n.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..aad8f876b7129ceb36124af0e1a34da68085432d Binary files /dev/null and b/wolf-coming/video/audio/v_14_1_n.mp3 differ diff --git a/wolf-coming/video/film.bundle.js b/wolf-coming/video/film.bundle.js new file mode 100644 index 0000000000000000000000000000000000000000..3f8d504e1dce823fbe43f6e4267101f0ca3e5ba9 --- /dev/null +++ b/wolf-coming/video/film.bundle.js @@ -0,0 +1,7235 @@ +"use strict"; +(() => { + var __defProp = Object.defineProperty; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + + // src/film/core.ts + var core_exports = {}; + __export(core_exports, { + FONT_BRUSH: () => FONT_BRUSH, + FONT_KAI: () => FONT_KAI, + INK: () => INK, + PAPER: () => PAPER, + SEAL: () => SEAL, + backOut: () => backOut, + blobLoop: () => blobLoop, + blot: () => blot, + brush: () => brush, + clamp: () => clamp, + ctx2d: () => ctx2d, + easeIn: () => easeIn, + easeInOut: () => easeInOut, + easeOut: () => easeOut, + hash: () => hash, + inv: () => inv, + lerp: () => lerp, + loadFonts: () => loadFonts, + makeCanvas: () => makeCanvas, + mixHex: () => mixHex, + mountains: () => mountains, + noise1: () => noise1, + nz: () => nz, + outline: () => outline, + paper: () => paper, + rgba: () => rgba, + rng: () => rng, + seal: () => seal, + smooth: () => smooth, + spline: () => spline, + spring: () => spring, + stroke: () => stroke, + subPath: () => subPath, + tracePath: () => tracePath, + wash: () => wash + }); + function rng(seed) { + let a = seed >>> 0 || 1; + return () => { + a = a + 1831565813 >>> 0; + let t = a; + t = Math.imul(t ^ t >>> 15, t | 1); + t ^= t + Math.imul(t ^ t >>> 7, t | 61); + return ((t ^ t >>> 14) >>> 0) / 4294967296; + }; + } + function hash(n) { + const s = Math.sin(n * 127.1 + 311.7) * 43758.5453; + return s - Math.floor(s); + } + function noise1(x, seed = 0) { + const i = Math.floor(x), f = x - i; + const a = hash(i + seed * 101.3), b = hash(i + 1 + seed * 101.3); + const u = f * f * (3 - 2 * f); + return a + (b - a) * u; + } + var nz = (x, seed = 0) => noise1(x, seed) * 2 - 1; + var clamp = (x, a = 0, b = 1) => Math.max(a, Math.min(b, x)); + var lerp = (a, b, t) => a + (b - a) * t; + var inv = (a, b, x) => clamp((x - a) / (b - a)); + var smooth = (x) => { + x = clamp(x); + return x * x * (3 - 2 * x); + }; + var easeOut = (x) => 1 - Math.pow(1 - clamp(x), 3); + var easeIn = (x) => Math.pow(clamp(x), 3); + var easeInOut = (x) => { + x = clamp(x); + return x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; + }; + var backOut = (x) => { + x = clamp(x); + const c1 = 1.70158, c3 = c1 + 1; + return 1 + c3 * Math.pow(x - 1, 3) + c1 * Math.pow(x - 1, 2); + }; + var spring = (x) => { + x = Math.max(0, x); + return 1 - Math.exp(-6 * x) * Math.cos(11 * x); + }; + var INK = "#1d1a17"; + var PAPER = "#f6efdf"; + var SEAL = "#b8322a"; + function rgba(hex, a) { + const n = parseInt(hex.slice(1), 16); + return `rgba(${n >> 16 & 255},${n >> 8 & 255},${n & 255},${a})`; + } + function mixHex(a, b, t) { + const x = parseInt(a.slice(1), 16), y = parseInt(b.slice(1), 16); + const c2 = (s) => Math.round(lerp(x >> s & 255, y >> s & 255, clamp(t))); + return "#" + (1 << 24 | c2(16) << 16 | c2(8) << 8 | c2(0)).toString(16).slice(1); + } + function makeCanvas(w, h) { + if (typeof OffscreenCanvas !== "undefined") return new OffscreenCanvas(w, h); + const c2 = document.createElement("canvas"); + c2.width = w; + c2.height = h; + return c2; + } + function ctx2d(c2) { + return c2.getContext("2d"); + } + var paperCache = /* @__PURE__ */ new Map(); + function paper(W2, H2) { + const key = W2 + "x" + H2; + const hit = paperCache.get(key); + if (hit) return hit; + const c2 = makeCanvas(W2, H2), g2 = ctx2d(c2), r = rng(20261017); + g2.fillStyle = PAPER; + g2.fillRect(0, 0, W2, H2); + const u = Math.min(W2, H2) / 1080; + for (let i = 0; i < 46; i++) { + const x = r() * W2, y = r() * H2, rad = (120 + r() * 420) * u; + const gr = g2.createRadialGradient(x, y, 0, x, y, rad); + const dark = r() < 0.55; + gr.addColorStop(0, dark ? "rgba(170,140,90,0.028)" : "rgba(255,252,240,0.08)"); + gr.addColorStop(1, "rgba(0,0,0,0)"); + g2.fillStyle = gr; + g2.fillRect(x - rad, y - rad, rad * 2, rad * 2); + } + g2.strokeStyle = "rgba(150,125,85,0.035)"; + g2.lineWidth = Math.max(1, u); + for (let y = 0; y < H2; y += 9 * u) { + g2.beginPath(); + g2.moveTo(0, y + r() * 2); + g2.lineTo(W2, y + r() * 2); + g2.stroke(); + } + const nf = Math.round(W2 * H2 / 2600); + for (let i = 0; i < nf; i++) { + const x = r() * W2, y = r() * H2, len = (6 + r() * 26) * u, ang = r() * Math.PI * 2; + g2.strokeStyle = r() < 0.7 ? `rgba(150,120,75,${0.05 + r() * 0.08})` : `rgba(255,252,240,${0.2 + r() * 0.25})`; + g2.lineWidth = (0.4 + r() * 0.9) * u; + g2.beginPath(); + g2.moveTo(x, y); + g2.quadraticCurveTo(x + Math.cos(ang + 0.6) * len * 0.5, y + Math.sin(ang + 0.6) * len * 0.5, x + Math.cos(ang) * len, y + Math.sin(ang) * len); + g2.stroke(); + } + try { + const id = g2.getImageData(0, 0, W2, H2), d = id.data; + let s = 12345; + for (let i = 0; i < d.length; i += 4) { + s = s * 1103515245 + 12345 & 2147483647; + const n = (s >> 8) % 15 - 7; + d[i] += n; + d[i + 1] += n; + d[i + 2] += n; + } + g2.putImageData(id, 0, 0); + } catch { + } + const vg = g2.createRadialGradient(W2 / 2, H2 / 2, Math.min(W2, H2) * 0.35, W2 / 2, H2 / 2, Math.hypot(W2, H2) * 0.62); + vg.addColorStop(0, "rgba(0,0,0,0)"); + vg.addColorStop(1, "rgba(120,90,50,0.13)"); + g2.fillStyle = vg; + g2.fillRect(0, 0, W2, H2); + paperCache.set(key, c2); + if (paperCache.size > 4) paperCache.delete(paperCache.keys().next().value); + return c2; + } + function spline(pts, seg = 8, closed = false) { + const out = []; + const n = pts.length; + if (n < 3) return pts.slice(); + const get = (i) => closed ? pts[(i + n) % n] : pts[Math.max(0, Math.min(n - 1, i))]; + const last = closed ? n : n - 1; + for (let i = 0; i < last; i++) { + const p0 = get(i - 1), p1 = get(i), p2 = get(i + 1), p3 = get(i + 2); + for (let j = 0; j < seg; j++) { + const t = j / seg, t2 = t * t, t3 = t2 * t; + out.push([ + 0.5 * (2 * p1[0] + (-p0[0] + p2[0]) * t + (2 * p0[0] - 5 * p1[0] + 4 * p2[0] - p3[0]) * t2 + (-p0[0] + 3 * p1[0] - 3 * p2[0] + p3[0]) * t3), + 0.5 * (2 * p1[1] + (-p0[1] + p2[1]) * t + (2 * p0[1] - 5 * p1[1] + 4 * p2[1] - p3[1]) * t2 + (-p0[1] + 3 * p1[1] - 3 * p2[1] + p3[1]) * t3) + ]); + } + } + if (!closed) out.push(pts[n - 1]); + return out; + } + function subPath(pts, a, b) { + if (pts.length < 2) return pts; + const L = [0]; + for (let i = 1; i < pts.length; i++) L.push(L[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1])); + const tot = L[L.length - 1], A = a * tot, B2 = b * tot, out = []; + const at3 = (d) => { + for (let i = 1; i < L.length; i++) if (L[i] >= d) { + const f = (d - L[i - 1]) / Math.max(1e-6, L[i] - L[i - 1]); + return [lerp(pts[i - 1][0], pts[i][0], f), lerp(pts[i - 1][1], pts[i][1], f)]; + } + return pts[pts.length - 1]; + }; + out.push(at3(A)); + for (let i = 0; i < pts.length; i++) if (L[i] > A && L[i] < B2) out.push(pts[i]); + out.push(at3(B2)); + return out; + } + function tracePath(g2, pts, close = false) { + g2.beginPath(); + g2.moveTo(pts[0][0], pts[0][1]); + for (let i = 1; i < pts.length; i++) g2.lineTo(pts[i][0], pts[i][1]); + if (close) g2.closePath(); + } + function brush(g2, pts0, w, o = {}) { + const grow = o.grow ?? 1; + if (grow <= 0 || pts0.length < 2) return; + const pts = grow < 1 ? subPath(pts0, 0, grow) : pts0; + const n = pts.length; + const L = [0]; + for (let i = 1; i < n; i++) L.push(L[i - 1] + Math.hypot(pts[i][0] - pts[i - 1][0], pts[i][1] - pts[i - 1][1])); + const tot = Math.max(1e-6, L[n - 1]); + const tin = o.taperIn ?? 0.18, tout = o.taperOut ?? 0.35, jit = o.jitter ?? 0.18, seed = o.seed ?? 1; + const left = [], right = []; + for (let i = 0; i < n; i++) { + const s = L[i] / tot; + const a = pts[Math.max(0, i - 1)], b = pts[Math.min(n - 1, i + 1)]; + let dx = b[0] - a[0], dy = b[1] - a[1]; + const d = Math.hypot(dx, dy) || 1; + dx /= d; + dy /= d; + let prof = Math.min(1, 0.35 + 0.65 * smooth(s / tin)) * Math.min(1, Math.pow(clamp((1 - s) / tout), 0.7) * 0.92 + 0.08); + if (o.press) prof *= 1 + o.press * Math.exp(-s * 9); + const ww = w / 2 * prof * (1 + jit * nz(s * 7 + seed * 3.1, seed)); + left.push([pts[i][0] - dy * ww, pts[i][1] + dx * ww]); + right.push([pts[i][0] + dy * ww, pts[i][1] - dx * ww]); + } + g2.fillStyle = rgba(o.color ?? INK, o.alpha ?? 0.92); + g2.beginPath(); + g2.moveTo(left[0][0], left[0][1]); + for (let i = 1; i < n; i++) g2.lineTo(left[i][0], left[i][1]); + for (let i = n - 1; i >= 0; i--) g2.lineTo(right[i][0], right[i][1]); + g2.closePath(); + g2.fill(); + if ((o.alpha ?? 0.92) > 0.5 && w > 3) { + g2.fillStyle = rgba(o.color ?? INK, 0.12); + g2.beginPath(); + g2.arc(pts[0][0], pts[0][1], w / 2 * 0.55, 0, Math.PI * 2); + g2.fill(); + } + if (o.dry && o.dry > 0) { + g2.strokeStyle = rgba(PAPER, 0.75 * o.dry); + const lines = 4; + for (let k = 0; k < lines; k++) { + const off = (k + 0.5) / lines - 0.5; + g2.lineWidth = Math.max(0.6, w * 0.07); + g2.beginPath(); + let started = false; + for (let i = Math.floor(n * (0.45 + 0.1 * hash(seed + k))); i < n; i++) { + const sx = lerp(left[i][0], right[i][0], 0.5 + off * 0.8), sy = lerp(left[i][1], right[i][1], 0.5 + off * 0.8); + if (hash(i * 0.37 + k * 13 + seed) < 0.18) { + started = false; + continue; + } + if (!started) { + g2.moveTo(sx, sy); + started = true; + } else g2.lineTo(sx, sy); + } + g2.stroke(); + } + } + } + function stroke(g2, ctrl, w, o = {}) { + brush(g2, spline(ctrl, 10), w, o); + } + function outline(g2, loop, w, o = {}) { + const pts = spline(loop, 8, true); + pts.push(pts[0]); + const br = o.breaks ?? [0, 0.36, 0.7, 1]; + for (let i = 0; i < br.length - 1; i++) { + const a = br[i] + 0.012, b = br[i + 1] - 0.012; + if (b > a) brush(g2, subPath(pts, a, b), w, { taperIn: 0.12, taperOut: 0.25, ...o, seed: (o.seed ?? 1) + i * 7 }); + } + } + function wash(g2, loop, color, alpha = 0.6, o = {}) { + const pts = spline(loop, 8, o.closed ?? true); + g2.save(); + g2.translate(o.dx ?? 0, o.dy ?? 0); + tracePath(g2, pts, true); + g2.fillStyle = rgba(color, alpha); + g2.fill(); + if ((o.edge ?? 1) > 0) { + g2.strokeStyle = rgba(color, Math.min(1, alpha * 0.5 * (o.edge ?? 1))); + g2.lineWidth = o.lw ?? 2.2; + g2.lineJoin = "round"; + g2.stroke(); + } + g2.restore(); + } + function blot(g2, x, y, r, color, a, sx = 1, sy = 1) { + if (r <= 0 || a <= 0) return; + g2.save(); + g2.translate(x, y); + g2.scale(sx, sy); + const gr = g2.createRadialGradient(0, 0, 0, 0, 0, r); + gr.addColorStop(0, rgba(color, a)); + gr.addColorStop(0.55, rgba(color, a * 0.6)); + gr.addColorStop(1, rgba(color, 0)); + g2.fillStyle = gr; + g2.fillRect(-r, -r, r * 2, r * 2); + g2.restore(); + } + function blobLoop(cx, cy, r, seed, rough = 0.18, n = 28) { + const out = []; + for (let i = 0; i < n; i++) { + const a = i / n * Math.PI * 2; + const rr = r * (1 + rough * nz(i * 0.7 + seed, seed) + rough * 0.5 * nz(i * 2.3 + seed * 3, seed + 1)); + out.push([cx + Math.cos(a) * rr, cy + Math.sin(a) * rr]); + } + return out; + } + function mountains(g2, W2, baseY, height, color, alpha, seed, o = {}) { + const f = o.freq ?? 2.2; + const pts = []; + const steps2 = 60; + for (let i = 0; i <= steps2; i++) { + const x = i / steps2 * W2; + const v = 0.55 * noise1(i / steps2 * f + seed, seed) + 0.3 * noise1(i / steps2 * f * 2.7 + seed * 2, seed + 1) + 0.15 * noise1(i / steps2 * f * 7 + seed * 5, seed + 2); + pts.push([x, baseY - height * Math.pow(v, 1.4) * 1.6]); + } + const top = Math.min(...pts.map((p) => p[1])); + const gr = g2.createLinearGradient(0, top, 0, baseY); + gr.addColorStop(0, rgba(color, alpha)); + gr.addColorStop(0.6, rgba(color, alpha * 0.55)); + gr.addColorStop(1, rgba(color, 0)); + g2.fillStyle = gr; + g2.beginPath(); + g2.moveTo(0, baseY + 2); + for (const p of pts) g2.lineTo(p[0], p[1]); + g2.lineTo(W2, baseY + 2); + g2.closePath(); + g2.fill(); + if ((o.soft ?? 0) < 1) { + const r = rng(seed * 991); + for (let k = 0; k < 6; k++) { + const a = r() * 0.85, b = a + 0.06 + r() * 0.12; + brush(g2, subPath(pts, a, Math.min(1, b)), 2.5 + r() * 3, { color, alpha: alpha * 0.9, seed: seed + k, jitter: 0.4, dry: 0.4 }); + } + } + } + function seal(g2, x, y, size, text, o = {}) { + const a = o.alpha ?? 1; + if (a <= 0) return; + g2.save(); + g2.translate(x, y); + const s = size; + g2.globalAlpha = a; + const loop = [[-s / 2, -s / 2], [s / 2, -s / 2], [s / 2, s / 2], [-s / 2, s / 2]]; + g2.fillStyle = SEAL; + g2.beginPath(); + if (o.round) g2.arc(0, 0, s / 2, 0, Math.PI * 2); + else { + g2.moveTo(loop[0][0], loop[0][1]); + for (const p of loop.slice(1)) g2.lineTo(p[0], p[1]); + g2.closePath(); + } + g2.fill(); + g2.fillStyle = PAPER; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + const cols = text.length; + const rows = Math.max(...text.map((c2) => c2.length)); + const cell = s * 0.84 / Math.max(cols, rows); + g2.font = `${cell * 0.95}px ${o.font ?? FONT_BRUSH}`; + for (let c2 = 0; c2 < cols; c2++) { + const col = text[c2]; + for (let r2 = 0; r2 < col.length; r2++) { + const cx = (cols - 1) / 2 * cell - c2 * cell; + const cy = (r2 - (col.length - 1) / 2) * cell * (rows / col.length); + g2.fillText(col[r2], cx, cy + cell * 0.04); + } + } + const r = rng(Math.round(x * 7 + y * 13)); + g2.fillStyle = PAPER; + for (let i = 0; i < 26; i++) { + g2.globalAlpha = a * (0.25 + r() * 0.5); + g2.beginPath(); + g2.arc((r() - 0.5) * s, (r() - 0.5) * s, r() * s * 0.025 + 0.5, 0, Math.PI * 2); + g2.fill(); + } + g2.restore(); + } + var FONT_KAI = '"WenKaiFilm", "Kaiti SC", "STKaiti", "KaiTi", serif'; + var FONT_BRUSH = '"MaShanZhengFilm", "WenKaiFilm", "Kaiti SC", "STKaiti", "KaiTi", serif'; + var fontsPromise = null; + function loadFonts(base) { + if (fontsPromise) return fontsPromise; + if (typeof FontFace === "undefined") return fontsPromise = Promise.resolve(); + const list = [["WenKaiFilm", base + "film/fonts/wenkai.woff2"], ["MaShanZhengFilm", base + "film/fonts/mashanzheng.woff2"]]; + fontsPromise = Promise.all(list.map(async ([fam, url]) => { + try { + const f = new FontFace(fam, `url(${url})`); + await f.load(); + document.fonts.add(f); + } catch (e) { + console.warn("font load failed", fam, e); + } + })).then(() => void 0); + return fontsPromise; + } + + // src/film/film.ts + var FILM = null; + var TL = { total: 1, fps: 30, scenes: [] }; + var TOTAL = 1; + var SCENES = {}; + function setFilm(f) { + if (FILM === f) return; + FILM = f; + TL = f.timeline; + TOTAL = f.timeline.total; + SCENES = f.scenes; + bgCache.clear(); + } + var bgCache = /* @__PURE__ */ new Map(); + function cachedBg(id, W2, H2, draw) { + const k = `${id}:${W2}x${H2}`; + let c2 = bgCache.get(k); + if (c2) { + bgCache.delete(k); + bgCache.set(k, c2); + } + if (!c2) { + c2 = makeCanvas(W2, H2); + const g2 = ctx2d(c2); + draw(g2, W2, H2); + bgCache.set(k, c2); + if (bgCache.size > 8) bgCache.delete(bgCache.keys().next().value); + } + return c2; + } + function sceneIndexAt(T) { + const s = TL.scenes; + for (let i = s.length - 1; i >= 0; i--) if (T >= s[i].start) return i; + return 0; + } + function drawScene(g2, W2, H2, i, T) { + const tl3 = TL.scenes[i]; + const def = SCENES[tl3.id]; + const u = Math.min(W2, H2) / 1080; + const portrait = H2 > W2 * 1.05; + const t = T - tl3.start; + const sc = { g: g2, W: W2, H: H2, u, t, D: tl3.dur, T, portrait, gy: H2 * (portrait ? 0.62 : 0.6), X: (f) => W2 / 2 + f * Math.min(W2 / 2, H2 * 0.85) }; + g2.drawImage(paper(W2, H2), 0, 0); + g2.save(); + const cam3 = def.cam ? def.cam(sc) : { z: 1, x: W2 / 2, y: H2 / 2, dx: 0, dy: 0 }; + const z = Math.max(1, cam3.z); + const cx = Math.min(Math.max(cam3.x, W2 / (2 * z)), W2 - W2 / (2 * z)); + const cy = Math.min(Math.max(cam3.y, H2 / (2 * z)), H2 - H2 / (2 * z)); + g2.translate(W2 / 2 + (cam3.dx ?? 0), H2 / 2 + (cam3.dy ?? 0)); + g2.scale(z, z); + g2.translate(-cx, -cy); + if (def.bg) g2.drawImage(cachedBg(tl3.id, W2, H2, (bg, w, h) => def.bg({ ...sc, g: bg, W: w, H: h })), 0, 0); + def.draw(sc); + g2.restore(); + if (def.overlay) def.overlay(sc); + } + var TR = 1.1; + function renderFrame(g2, W2, H2, T, opts = {}) { + T = clamp(T, 0, TOTAL - 1e-4); + const i = sceneIndexAt(T); + const tl3 = TL.scenes[i]; + const lt = T - tl3.start; + g2.save(); + g2.setTransform(1, 0, 0, 1, 0, 0); + g2.globalAlpha = 1; + g2.globalCompositeOperation = "source-over"; + if (i > 0 && lt < TR) { + drawScene(g2, W2, H2, i - 1, T); + const p = easeInOut(lt / TR); + const R = Math.hypot(W2, H2) * 0.62 * p / 0.8 + 1; + const def = SCENES[tl3.id]; + const fx = (def.enterAt?.[0] ?? 0.5) * W2, fy = (def.enterAt?.[1] ?? 0.5) * H2; + const loop = spline(blobLoop(fx, fy, R, i * 17 + 3, 0.16, 26), 4, true); + g2.save(); + tracePath(g2, loop, true); + g2.clip(); + drawScene(g2, W2, H2, i, T); + g2.restore(); + g2.save(); + tracePath(g2, loop, true); + g2.strokeStyle = rgba(INK, 0.16 * (1 - p)); + g2.lineWidth = Math.min(W2, H2) * 0.02 * (1 - p) + 1; + g2.stroke(); + g2.strokeStyle = rgba("#7a6a50", 0.12 * (1 - p)); + g2.lineWidth = Math.min(W2, H2) * 0.05 * (1 - p) + 1; + g2.stroke(); + g2.restore(); + } else { + drawScene(g2, W2, H2, i, T); + } + if (opts.subs !== false) drawSubs(g2, W2, H2, T); + if (opts.fadeEnd !== false) { + const f = smooth(inv(TOTAL - 1.2, TOTAL - 0.1, T)); + if (f > 0) { + g2.globalAlpha = f; + g2.drawImage(paper(W2, H2), 0, 0); + g2.globalAlpha = 1; + } + } + g2.restore(); + } + function currentSub(T) { + const i = sceneIndexAt(T); + const subs = TL.scenes[i].subs ?? []; + for (let k = 0; k < subs.length; k++) { + const s = subs[k]; + const end = k + 1 < subs.length ? subs[k + 1].t0 : s.t1 + 0.35; + if (T >= s.t0 - 0.05 && T < end) { + const a = Math.min(smooth((T - s.t0 + 0.05) / 0.18), smooth((end - T) / 0.15)); + return { text: s.text, a, who: s.who }; + } + } + return null; + } + function drawSubs(g2, W2, H2, T) { + const s = currentSub(T); + if (!s || s.a <= 0) return; + if (SCENES[TL.scenes[sceneIndexAt(T)].id]?.noSubs) return; + const portrait = H2 > W2 * 1.05; + const u = Math.min(W2, H2) / 1080; + const fs = (portrait ? 58 : 50) * u; + const y = portrait ? H2 * 0.785 : H2 - 86 * u; + g2.save(); + g2.globalAlpha = s.a; + g2.font = `${fs}px ${FONT_KAI}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + const w = g2.measureText(s.text).width; + const rx = w / 2 + fs * 2.2, ry = fs * 0.95; + g2.save(); + g2.translate(W2 / 2, y); + g2.scale(rx / ry, 1); + const gr = g2.createRadialGradient(0, 0, 0, 0, 0, ry); + gr.addColorStop(0, rgba(PAPER, 0.95)); + gr.addColorStop(0.72, rgba(PAPER, 0.9)); + gr.addColorStop(1, rgba(PAPER, 0)); + g2.fillStyle = gr; + g2.fillRect(-ry, -ry, ry * 2, ry * 2); + g2.restore(); + g2.lineJoin = "round"; + g2.strokeStyle = rgba(PAPER, 0.96); + g2.lineWidth = fs * 0.2; + g2.strokeText(s.text, W2 / 2, y); + g2.fillStyle = rgba(s.who && FILM.subColors?.[s.who] || INK, 0.97); + g2.fillText(s.text, W2 / 2, y); + g2.restore(); + } + + // src/film/types.ts + var sceneAt = (tl3, id, dt) => (tl3.scenes.find((s) => s.id === id)?.start ?? 0) + dt; + + // src/films/duckling/timeline.json + var timeline_default = { + title: "\u4E11\u5C0F\u9E2D", + style: "ink-wash textbook illustration", + w: 1920, + h: 1080, + fps: 30, + total: 156.3, + frames: 4689, + voice: "zh-CN-XiaoxiaoNeural", + scenes: [ + { + id: "title", + start: 0, + dur: 6.5, + voice_start: 0.5, + voice_dur: 2.832, + subs: [ + { + t0: 0.5, + t1: 2.199, + text: "\u5B89\u5F92\u751F\u7AE5\u8BDD" + }, + { + t0: 2.199, + t1: 3.332, + text: "\u4E11\u5C0F\u9E2D" + } + ], + cue: { + type: "title" + }, + sfx: [ + { + at: 0.3, + type: "drop" + }, + { + at: 4.6, + type: "seal" + } + ] + }, + { + id: "summer", + start: 6.5, + dur: 11.566666666666666, + voice_start: 7, + voice_dur: 10.176, + subs: [ + { + t0: 7, + t1: 8.42, + text: "\u590F\u5929\u7684\u4E61\u4E0B" + }, + { + t0: 8.42, + t1: 9.603, + text: "\u9EA6\u5B50\u91D1\u9EC4" + }, + { + t0: 9.603, + t1: 12.68, + text: "\u6C60\u5858\u8FB9\u7684\u82A6\u82C7\u957F\u5F97\u6BD4\u4EBA\u8FD8\u9AD8" + }, + { + t0: 12.68, + t1: 14.573, + text: "\u9E2D\u5988\u5988\u5C31\u5728\u8FD9\u91CC" + }, + { + t0: 14.573, + t1: 17.176, + text: "\u5B89\u5B89\u9759\u9759\u5730\u5B75\u7740\u5979\u7684\u86CB" + } + ], + cue: { + type: "summer" + }, + sfx: [ + { + at: 0.2, + type: "breeze" + } + ] + }, + { + id: "hatch", + start: 18.0667, + dur: 11.566666666666666, + voice_start: 18.5667, + voice_dur: 10.176, + subs: [ + { + t0: 18.567, + t1: 19.26, + text: "\u5494\u5693" + }, + { + t0: 19.26, + t1: 19.954, + text: "\u5494\u5693\uFF01" + }, + { + t0: 19.954, + t1: 22.267, + text: "\u86CB\u58F3\u4E00\u4E2A\u63A5\u4E00\u4E2A\u88C2\u5F00" + }, + { + t0: 22.267, + t1: 25.042, + text: "\u6BDB\u8338\u8338\u7684\u5C0F\u9EC4\u9E2D\u94BB\u4E86\u51FA\u6765" + }, + { + t0: 25.042, + t1: 26.43, + text: "\u5527\u5527\u5730\u53EB\u7740" + }, + { + t0: 26.43, + t1: 28.743, + text: "\u597D\u5947\u5730\u770B\u7740\u8FD9\u4E2A\u4E16\u754C" + } + ], + cue: { + type: "hatch" + }, + sfx: [ + { + at: 0.5, + type: "crack" + }, + { + at: 1.2, + type: "crack" + }, + { + at: 2.2, + type: "peep" + } + ] + }, + { + id: "bigegg", + start: 29.6333, + dur: 11.8, + voice_start: 30.1333, + voice_dur: 10.416, + subs: [ + { + t0: 30.133, + t1: 32.217, + text: "\u53EA\u5269\u6700\u5927\u7684\u90A3\u9897\u86CB" + }, + { + t0: 32.217, + t1: 33.374, + text: "\u8FC7\u4E86\u597D\u4E45" + }, + { + t0: 33.374, + t1: 34.763, + text: "\u624D\u6162\u6162\u88C2\u5F00" + }, + { + t0: 34.763, + t1: 36.614, + text: "\u94BB\u51FA\u6765\u7684\u5C0F\u5BB6\u4F19" + }, + { + t0: 36.614, + t1: 37.772, + text: "\u53C8\u5927\u53C8\u7070" + }, + { + t0: 37.772, + t1: 40.549, + text: "\u4E00\u70B9\u4E5F\u4E0D\u50CF\u4ED6\u7684\u54E5\u54E5\u59D0\u59D0" + } + ], + cue: { + type: "bigegg" + }, + sfx: [ + { + at: 2.6, + type: "bigcrack" + } + ] + }, + { + id: "bullied", + start: 41.4333, + dur: 10.366666666666667, + voice_start: 41.9333, + voice_dur: 8.952, + subs: [ + { + t0: 41.933, + t1: 44.111, + text: "\u5927\u5BB6\u90FD\u53EB\u4ED6\u4E11\u5C0F\u9E2D" + }, + { + t0: 44.111, + t1: 46.046, + text: "\u54E5\u54E5\u59D0\u59D0\u7B11\u8BDD\u4ED6" + }, + { + t0: 46.046, + t1: 47.74, + text: "\u516C\u9E21\u8FFD\u7740\u5544\u4ED6" + }, + { + t0: 47.74, + t1: 50.885, + text: "\u8FDE\u5582\u9E2D\u7684\u5C0F\u59D1\u5A18\u4E5F\u5ACC\u4ED6\u788D\u4E8B" + } + ], + cue: { + type: "bullied" + }, + sfx: [ + { + at: 0.8, + type: "quack" + }, + { + at: 3.2, + type: "flap" + } + ] + }, + { + id: "leave", + start: 51.8, + dur: 7.533333333333333, + voice_start: 52.3, + voice_dur: 6.144, + subs: [ + { + t0: 52.3, + t1: 54.348, + text: "\u4E11\u5C0F\u9E2D\u96BE\u8FC7\u6781\u4E86" + }, + { + t0: 54.348, + t1: 55.116, + text: "\u4E00\u5929" + }, + { + t0: 55.116, + t1: 57.164, + text: "\u4ED6\u6084\u6084\u94BB\u8FC7\u7BF1\u7B06" + }, + { + t0: 57.164, + t1: 58.444, + text: "\u79BB\u5F00\u4E86\u5BB6" + } + ], + cue: { + type: "leave" + }, + sfx: [ + { + at: 0.3, + type: "wind" + } + ] + }, + { + id: "marsh", + start: 59.3333, + dur: 13.666666666666666, + voice_start: 59.8333, + voice_dur: 12.264, + subs: [ + { + t0: 59.833, + t1: 61.921, + text: "\u4ED6\u6765\u5230\u4E00\u7247\u6CBC\u6CFD" + }, + { + t0: 61.921, + t1: 64.269, + text: "\u91CE\u9E2D\u4EEC\u7B11\u4ED6\u957F\u5F97\u602A" + }, + { + t0: 64.269, + t1: 65.052, + text: "\u5FFD\u7136" + }, + { + t0: 65.052, + t1: 65.574, + text: "\u7830\uFF01" + }, + { + t0: 65.574, + t1: 66.096, + text: "\u7830\uFF01" + }, + { + t0: 66.096, + t1: 68.183, + text: "\u730E\u4EBA\u7684\u67AA\u58F0\u54CD\u8D77" + }, + { + t0: 68.183, + t1: 70.271, + text: "\u4ED6\u5413\u5F97\u8EB2\u8FDB\u82A6\u82C7" + }, + { + t0: 70.271, + t1: 72.097, + text: "\u4E00\u52A8\u4E5F\u4E0D\u6562\u52A8" + } + ], + cue: { + type: "marsh" + }, + sfx: [ + { + at: 3.4, + type: "shot" + }, + { + at: 3.9, + type: "shot" + }, + { + at: 4.1, + type: "scatter" + } + ] + }, + { + id: "autumn", + start: 73, + dur: 15.8, + voice_start: 73.5, + voice_dur: 14.4, + subs: [ + { + t0: 73.5, + t1: 74.7, + text: "\u79CB\u5929\u5230\u4E86" + }, + { + t0: 74.7, + t1: 76.38, + text: "\u6811\u53F6\u53D8\u9EC4\u53D8\u7EA2" + }, + { + t0: 76.38, + t1: 77.58, + text: "\u4E00\u5929\u508D\u665A" + }, + { + t0: 77.58, + t1: 80.7, + text: "\u4E00\u7FA4\u6D01\u767D\u7684\u5927\u9E1F\u4ECE\u5929\u4E0A\u98DE\u8FC7" + }, + { + t0: 80.7, + t1: 82.14, + text: "\u8116\u5B50\u957F\u957F\u7684" + }, + { + t0: 82.14, + t1: 83.82, + text: "\u7FC5\u8180\u53C8\u5927\u53C8\u7F8E" + }, + { + t0: 83.82, + t1: 85.74, + text: "\u4E11\u5C0F\u9E2D\u671B\u7740\u4ED6\u4EEC" + }, + { + t0: 85.74, + t1: 87.9, + text: "\u5FC3\u91CC\u8BF4\u4E0D\u51FA\u7684\u5411\u5F80" + } + ], + cue: { + type: "autumn" + }, + sfx: [ + { + at: 2.4, + type: "call" + } + ] + }, + { + id: "winter", + start: 88.8, + dur: 13.433333333333334, + voice_start: 89.3, + voice_dur: 12.024, + subs: [ + { + t0: 89.3, + t1: 90.502, + text: "\u51AC\u5929\u6765\u4E86" + }, + { + t0: 90.502, + t1: 92.186, + text: "\u96EA\u82B1\u7EB7\u7EB7\u626C\u626C" + }, + { + t0: 92.186, + t1: 93.629, + text: "\u6E56\u9762\u7ED3\u4E86\u51B0" + }, + { + t0: 93.629, + t1: 96.033, + text: "\u4E11\u5C0F\u9E2D\u51BB\u5F97\u7F29\u6210\u4E00\u56E2" + }, + { + t0: 96.033, + t1: 96.755, + text: "\u53EF\u662F" + }, + { + t0: 96.755, + t1: 97.957, + text: "\u4ED6\u54AC\u7740\u7259" + }, + { + t0: 97.957, + t1: 101.324, + text: "\u71AC\u8FC7\u4E86\u4E00\u4E2A\u53C8\u4E00\u4E2A\u5BD2\u51B7\u7684\u591C\u665A" + } + ], + cue: { + type: "winter" + }, + sfx: [ + { + at: 0.2, + type: "wind" + }, + { + at: 3, + type: "ice" + } + ] + }, + { + id: "spring", + start: 102.2333, + dur: 12.366666666666667, + voice_start: 102.7333, + voice_dur: 10.968, + subs: [ + { + t0: 102.733, + t1: 104.519, + text: "\u6625\u5929\u7EC8\u4E8E\u6765\u4E86\uFF01" + }, + { + t0: 104.519, + t1: 105.794, + text: "\u67F3\u6811\u53D1\u82BD" + }, + { + t0: 105.794, + t1: 107.07, + text: "\u6843\u82B1\u76DB\u5F00" + }, + { + t0: 107.07, + t1: 109.365, + text: "\u4E11\u5C0F\u9E2D\u6247\u4E86\u6247\u7FC5\u8180" + }, + { + t0: 109.365, + t1: 109.875, + text: "\u54A6\uFF1F" + }, + { + t0: 109.875, + t1: 112.171, + text: "\u7FC5\u8180\u53D8\u5F97\u8FD9\u4E48\u6709\u529B" + }, + { + t0: 112.171, + t1: 113.701, + text: "\u4ED6\u98DE\u8D77\u6765\u4E86\uFF01" + } + ], + cue: { + type: "spring" + }, + sfx: [ + { + at: 0.2, + type: "chime" + }, + { + at: 4.8, + type: "rise" + } + ] + }, + { + id: "reflection", + start: 114.6, + dur: 13.2, + voice_start: 115.1, + voice_dur: 11.784, + subs: [ + { + t0: 115.1, + t1: 116.686, + text: "\u4ED6\u843D\u5230\u6E56\u9762\u4E0A" + }, + { + t0: 116.686, + t1: 118.952, + text: "\u51E0\u53EA\u767D\u5929\u9E45\u5411\u4ED6\u6E38\u6765" + }, + { + t0: 118.952, + t1: 120.086, + text: "\u4ED6\u4F4E\u4E0B\u5934" + }, + { + t0: 120.086, + t1: 121.898, + text: "\u770B\u89C1\u6C34\u91CC\u7684\u5012\u5F71" + }, + { + t0: 121.898, + t1: 124.391, + text: "\u90A3\u4E0D\u662F\u53C8\u4E11\u53C8\u7070\u7684\u9E2D\u5B50" + }, + { + t0: 124.391, + t1: 126.884, + text: "\u800C\u662F\u4E00\u53EA\u7F8E\u4E3D\u7684\u767D\u5929\u9E45\uFF01" + } + ], + cue: { + type: "reflection" + }, + sfx: [ + { + at: 0.6, + type: "splash" + }, + { + at: 5.6, + type: "reveal" + } + ] + }, + { + id: "accepted", + start: 127.8, + dur: 11.5, + voice_start: 128.3, + voice_dur: 10.104, + subs: [ + { + t0: 128.3, + t1: 129.945, + text: "\u5929\u9E45\u4EEC\u56F4\u7740\u4ED6" + }, + { + t0: 129.945, + t1: 132.53, + text: "\u7528\u5634\u8F7B\u8F7B\u68B3\u7406\u4ED6\u7684\u7FBD\u6BDB" + }, + { + t0: 132.53, + t1: 135.114, + text: "\u5CB8\u8FB9\u7684\u5B69\u5B50\u4EEC\u62CD\u7740\u624B\u558A" + }, + { + t0: 135.114, + t1: 135.819, + text: "\u5FEB\u770B" + }, + { + t0: 135.819, + t1: 138.404, + text: "\u65B0\u6765\u7684\u90A3\u53EA\u5929\u9E45\u6700\u6F02\u4EAE\uFF01" + } + ], + cue: { + type: "accepted" + }, + sfx: [ + { + at: 3.6, + type: "cheer" + } + ] + }, + { + id: "fly", + start: 139.3, + dur: 7, + voice_start: 139.8, + voice_dur: 5.472, + subs: [ + { + t0: 139.8, + t1: 141.852, + text: "\u4ED6\u5F20\u5F00\u96EA\u767D\u7684\u7FC5\u8180" + }, + { + t0: 141.852, + t1: 143.448, + text: "\u548C\u4F19\u4F34\u4EEC\u4E00\u8D77" + }, + { + t0: 143.448, + t1: 145.272, + text: "\u98DE\u5411\u9AD8\u9AD8\u7684\u84DD\u5929" + } + ], + cue: { + type: "fly" + }, + sfx: [ + { + at: 0.4, + type: "flap" + }, + { + at: 1, + type: "rise" + } + ] + }, + { + id: "ending", + start: 146.3, + dur: 10, + voice_start: 146.8, + voice_dur: 8.376, + subs: [ + { + t0: 146.8, + t1: 148.429, + text: "\u53EA\u8981\u5FC3\u91CC\u6709\u68A6" + }, + { + t0: 148.429, + t1: 149.592, + text: "\u4E0D\u6015\u98CE\u96EA" + }, + { + t0: 149.592, + t1: 150.988, + text: "\u4E5F\u4E0D\u6015\u5632\u7B11" + }, + { + t0: 150.988, + t1: 152.151, + text: "\u603B\u6709\u4E00\u5929" + }, + { + t0: 152.151, + t1: 155.176, + text: "\u4F60\u4E5F\u4F1A\u5C55\u5F00\u5C5E\u4E8E\u81EA\u5DF1\u7684\u7FC5\u8180" + } + ], + cue: { + type: "ending" + }, + sfx: [ + { + at: 6.2, + type: "seal" + } + ] + } + ] + }; + + // src/films/duckling/live.json + var live_default = [ + { id: "01-egg", scene: "bigegg", from: 2.2, dur: 3, cover: 2.4, caption: "\u6700\u5927\u7684\u86CB", index: "\u5176\u4E00", xhs: "\u6700\u540E\u4E00\u9897\u86CB\u6700\u5927\uFF0C\u597D\u4E45\u624D\u88C2\u5F00\uFF0C\u94BB\u51FA\u6765\u4E00\u53EA\u53C8\u5927\u53C8\u7070\u7684\u5C0F\u5BB6\u4F19\u3002" }, + { id: "02-laugh", scene: "bullied", from: 3, dur: 3, cover: 1, caption: "\u4E11\u5C0F\u9E2D", index: "\u5176\u4E8C", xhs: "\u54E5\u54E5\u59D0\u59D0\u7B11\u8BDD\u4ED6\uFF0C\u516C\u9E21\u8FFD\u7740\u5544\u4ED6\uFF0C\u5927\u5BB6\u90FD\u53EB\u4ED6\u300C\u4E11\u5C0F\u9E2D\u300D\u3002" }, + { id: "03-swans", scene: "autumn", from: 4.6, dur: 3, cover: 1.6, caption: "\u4EF0\u671B", index: "\u5176\u4E09", xhs: "\u79CB\u5929\u508D\u665A\uFF0C\u4E00\u7FA4\u6D01\u767D\u7684\u5927\u9E1F\u98DE\u8FC7\uFF0C\u4ED6\u671B\u7740\u5929\u7A7A\uFF0C\u5FC3\u91CC\u8BF4\u4E0D\u51FA\u7684\u5411\u5F80\u3002" }, + { id: "04-snow", scene: "winter", from: 6.4, dur: 3, cover: 1.6, caption: "\u5BD2\u51AC", index: "\u5176\u56DB", xhs: "\u96EA\u591C\u91CC\u6E56\u9762\u7ED3\u4E86\u51B0\uFF0C\u4ED6\u7F29\u6210\u4E00\u56E2\uFF0C\u71AC\u8FC7\u4E86\u4E00\u4E2A\u53C8\u4E00\u4E2A\u5BD2\u51B7\u7684\u591C\u665A\u3002" }, + { id: "05-reflect", scene: "reflection", from: 5.4, dur: 3, cover: 2.4, caption: "\u5012\u5F71", index: "\u5176\u4E94", xhs: "\u4ED6\u4F4E\u4E0B\u5934\uFF0C\u770B\u89C1\u6C34\u91CC\u7684\u5012\u5F71\u2014\u2014\u90A3\u662F\u4E00\u53EA\u7F8E\u4E3D\u7684\u767D\u5929\u9E45\u3002" }, + { id: "06-fly", scene: "fly", from: 2.6, dur: 3, cover: 1.6, caption: "\u5C55\u7FC5", index: "\u5176\u516D", xhs: "\u539F\u6765\uFF0C\u4ED6\u672C\u6765\u5C31\u662F\u4E00\u53EA\u5929\u9E45\u3002\u5F20\u5F00\u7FC5\u8180\uFF0C\u98DE\u5411\u9AD8\u9AD8\u7684\u84DD\u5929\u3002" } + ]; + + // src/films/duckling/qa.json + var qa_default = { + status: "done", + generated: "2026-10-17", + meta: { + w: 1080, + h: 1920, + fps: 30, + dur: 156.3, + audio: true + }, + verdict: "PASS", + results: [ + { + level: "PASS", + check: "\u7AD6\u5C4F \u5206\u8FA8\u7387", + msg: "1080\xD71920\uFF08\u65F6\u95F4\u8F74 1080\xD71920\uFF09" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u5E27\u7387", + msg: "30.0 fps" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u65F6\u957F", + msg: "156.30s\uFF08\u65F6\u95F4\u8F74 156.30s\uFF09" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u97F3\u8F68", + msg: "\u6709" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u54CD\u5EA6", + msg: "-14.0 LUFS\uFF08\u76EE\u6807 \u221214\uFF09" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u771F\u5CF0\u503C", + msg: "-1.4 dBTP\uFF08\u5E94 \u2264 \u22121\uFF09" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u9ED1\u573A", + msg: "\u65E0" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u51BB\u7ED3\u753B\u9762", + msg: "\u65E0" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@title", + msg: "OCR \u8BFB\u51FA 16 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@summer", + msg: "OCR \u8BFB\u51FA 37 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 88%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@hatch", + msg: "OCR \u8BFB\u51FA 31 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@bigegg", + msg: "OCR \u8BFB\u51FA 33 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@bullied", + msg: "OCR \u8BFB\u51FA 35 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@leave", + msg: "OCR \u8BFB\u51FA 21 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@marsh", + msg: "OCR \u8BFB\u51FA 14 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 86%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@autumn", + msg: "OCR \u8BFB\u51FA 20 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@winter", + msg: "OCR \u8BFB\u51FA 40 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 91%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@spring", + msg: "OCR \u8BFB\u51FA 22 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "INFO", + check: "\u7AD6\u5C4F \u6587\u5B57@reflection", + msg: "OCR \u8BFB\u51FA 47 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 53%\uFF08\u68C0\u67E5\u7A97\u53E3\u540C\u65F6\u8BA1\u5165\u4E86\u4E0A\u4E00\u53E5\u5B57\u5E55\uFF1B\u5F53\u524D\u5B57\u5E55\u300C\u800C\u662F\u4E00\u53EA\u7F8E\u4E3D\u7684\u767D\u5929\u9E45\u300D\u5DF2\u88AB\u8BFB\u51FA\uFF09" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@accepted", + msg: "OCR \u8BFB\u51FA 33 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@fly", + msg: "OCR \u8BFB\u51FA 11 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u7AD6\u5C4F \u6587\u5B57@ending", + msg: "OCR \u8BFB\u51FA 28 \u5B57\uFF1B\u5B57\u5E55\u547D\u4E2D 100%" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5206\u8FA8\u7387", + msg: "1920\xD71080\uFF08\u65F6\u95F4\u8F74 1920\xD71080\uFF09" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5E27\u7387", + msg: "30.0 fps" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u65F6\u957F", + msg: "156.30s\uFF08\u65F6\u95F4\u8F74 156.30s\uFF09" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u97F3\u8F68", + msg: "\u6709" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u54CD\u5EA6", + msg: "-14.0 LUFS\uFF08\u76EE\u6807 \u221214\uFF09" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u771F\u5CF0\u503C", + msg: "-1.4 dBTP\uFF08\u5E94 \u2264 \u22121\uFF09" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u9ED1\u573A", + msg: "\u65E0" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u51BB\u7ED3\u753B\u9762", + msg: "\u65E0" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@summer", + msg: "\u300C\u5B89\u5B89\u9759\u9759\u5730\u5B75\u7740\u5979\u7684\u86CB\u300DOCR \u547D\u4E2D 88%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.5:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@hatch", + msg: "\u300C\u597D\u5947\u5730\u770B\u7740\u8FD9\u4E2A\u4E16\u754C\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.4:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@bigegg", + msg: "\u300C\u4E00\u70B9\u4E5F\u4E0D\u50CF\u4ED6\u7684\u54E5\u54E5\u59D0\u59D0\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.7:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@bullied", + msg: "\u300C\u8FDE\u5582\u9E2D\u7684\u5C0F\u59D1\u5A18\u4E5F\u5ACC\u4ED6\u788D\u4E8B\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 13.2:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@leave", + msg: "\u300C\u79BB\u5F00\u4E86\u5BB6\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 3.5:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@marsh", + msg: "\u300C\u4ED6\u5413\u5F97\u8EB2\u8FDB\u82A6\u82C7\u300DOCR \u547D\u4E2D 0%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 11.6:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@autumn", + msg: "\u300C\u4E11\u5C0F\u9E2D\u671B\u7740\u4ED6\u4EEC\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 11.2:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@winter", + msg: "\u300C\u71AC\u8FC7\u4E86\u4E00\u4E2A\u53C8\u4E00\u4E2A\u5BD2\u51B7\u7684\u591C\u665A\u300DOCR \u547D\u4E2D 91%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.9:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@spring", + msg: "\u300C\u7FC5\u8180\u53D8\u5F97\u8FD9\u4E48\u6709\u529B\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.2:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@reflection", + msg: "\u300C\u90A3\u4E0D\u662F\u53C8\u4E11\u53C8\u7070\u7684\u9E2D\u5B50\u800C\u662F\u4E00\u53EA\u7F8E\u4E3D\u7684\u767D\u5929\u9E45\u300DOCR \u547D\u4E2D 53%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.1:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@accepted", + msg: "\u300C\u65B0\u6765\u7684\u90A3\u53EA\u5929\u9E45\u6700\u6F02\u4EAE\u300DOCR \u547D\u4E2D 90%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 12.9:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@fly", + msg: "\u300C\u98DE\u5411\u9AD8\u9AD8\u7684\u84DD\u5929\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 10.2:1" + }, + { + level: "PASS", + check: "\u6A2A\u5C4F \u5B57\u5E55@ending", + msg: "\u300C\u4F60\u4E5F\u4F1A\u5C55\u5F00\u5C5E\u4E8E\u81EA\u5DF1\u7684\u7FC5\u8180\u300DOCR \u547D\u4E2D 100%\uFF1B\u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6 13.1:1" + }, + { + level: "INFO", + check: "\u8BF4\u660E", + msg: "\u6A2A\u5C4F\u5B57\u5E55\u4F4D\u4E8E\u753B\u9762\u5E95\u90E8 ~92%\uFF0Cvibe qa \u7684 OCR \u53EA\u626B 60\u201384% \u9AD8\u5EA6\u5E26\uFF0C\u6545\u6A2A\u5C4F\u6539\u7528\u300C\u5B57\u5E55\u5E26 OCR + \u58A8\u8272/\u7EB8\u5E26\u5BF9\u6BD4\u5EA6\uFF08\u22657:1\uFF09\u300D\u8865\u5145\u68C0\u67E5\u3002" + }, + { + level: "PASS", + check: "\u786E\u5B9A\u6027", + msg: "vibe det\uFF1A\u540C\u4E00\u5E27\u4E71\u5E8F\u91CD\u6E32\u9010\u50CF\u7D20\u4E00\u81F4" + }, + { + level: "PASS", + check: "\u5B9E\u51B5\u56FE", + msg: "6 \u7EC4 JPG+MOV\uFF1Acontent identifier \u4E00\u81F4\u3001still-image-time \u8BFB\u56DE\u6B63\u786E\u3001MOV \u5B8C\u6574\u89E3\u7801\u3001\u54C8\u5E0C\u6821\u9A8C\u901A\u8FC7\uFF08PhotoKit \u672C\u673A\u52A0\u8F7D\uFF1ALinux \u73AF\u5883\u4E0D\u53EF\u7528\uFF09" + } + ] + }; + + // src/film/actors.ts + var actors_exports = {}; + __export(actors_exports, { + bird: () => bird, + blossom: () => blossom, + child: () => child, + cloud: () => cloud, + egg: () => egg, + farBird: () => farBird, + fence: () => fence, + grass: () => grass, + leaf: () => leaf, + lotusLeaf: () => lotusLeaf, + nest: () => nest, + reed: () => reed, + reflection: () => reflection, + rooster: () => rooster, + smoke: () => smoke, + soundMarks: () => soundMarks, + sun: () => sun, + swimRipples: () => swimRipples, + tear: () => tear, + tree: () => tree, + water: () => water, + willow: () => willow + }); + var DUCK_BODY = [[0.42, -0.04], [0.3, -0.3], [0.02, -0.39], [-0.3, -0.31], [-0.56, -0.24], [-0.5, 0.02], [-0.4, 0.22], [-0.1, 0.33], [0.22, 0.3], [0.41, 0.15]]; + var SWAN_BODY = [[0.47, -0.02], [0.32, -0.17], [0.02, -0.24], [-0.36, -0.22], [-0.66, -0.36], [-0.6, -0.08], [-0.44, 0.12], [-0.1, 0.22], [0.26, 0.2], [0.46, 0.1]]; + var lp = (a, b, t) => [lerp(a[0], b[0], t), lerp(a[1], b[1], t)]; + var add = (a, b) => [a[0] + b[0], a[1] + b[1]]; + var WING = [[0, 0], [0.32, -0.09], [0.7, -0.07], [1, 0.04], [0.93, 0.16], [0.84, 0.2], [0.76, 0.27], [0.64, 0.31], [0.5, 0.36], [0.3, 0.4], [0.06, 0.34]]; + function drawWing(g2, root, span, e, col, iw, seed, alpha, back = false) { + const S = [-0.16 * span, -e * span]; + const chord = span * (0.42 + 0.18 * Math.abs(e)); + const T = [-chord, -0.12 * chord * Math.sign(e || 1)]; + const map = (p) => [root[0] + S[0] * p[0] + T[0] * p[1], root[1] + S[1] * p[0] + T[1] * p[1]]; + const pts = WING.map(map); + wash(g2, pts, back ? mixHex(col, "#9a948a", 0.3) : col, alpha * 0.94, { edge: 0.6, lw: iw }); + const lead = spline(WING.slice(0, 4).map(map), 8); + brush(g2, lead, iw * 1.15, { seed, taperIn: 0.1, taperOut: 0.5, alpha: 0.9 * alpha }); + for (let i = 3; i < 10; i++) { + const a = map(WING[i]), b = map([WING[i][0] - 0.2, WING[i][1] - 0.16]); + brush(g2, [a, lp(a, b, 0.5), b], iw * 0.55, { seed: seed + i, taperIn: 0.05, taperOut: 0.7, alpha: 0.55 * alpha }); + } + brush(g2, spline(WING.slice(3).map(map), 6), iw * 0.7, { seed: seed + 3, alpha: 0.6 * alpha, dry: 0.4 }); + } + function neckCurve(nb, H2, sw) { + const dx = H2[0] - nb[0], dy = H2[1] - nb[1]; + const ang = Math.atan2(-dy, Math.abs(dx) + 1e-6); + let c2; + if (ang > 0.35) { + const k = smooth((ang - 0.35) / 0.9); + c2 = [nb[0] + dx * lerp(0.35, 0.02, k), H2[1] + Math.abs(dy) * lerp(0.15, 0.32, k)]; + } else if (ang > -0.15) { + c2 = [nb[0] + dx * 0.5, nb[1] + dy * 0.5 - 0.04 - 0.04 * sw]; + } else { + c2 = [nb[0] + dx * 0.45, Math.min(nb[1], H2[1]) - lerp(0.08, 0.3, sw)]; + } + const out = []; + for (let i = 0; i <= 18; i++) { + const t = i / 18, u = 1 - t; + out.push([u * u * nb[0] + 2 * u * t * c2[0] + t * t * H2[0], u * u * nb[1] + 2 * u * t * c2[1] + t * t * H2[1]]); + } + return out; + } + function bird(g2, o) { + const s = o.s, sw = clamp(o.sw ?? 0), face = o.face ?? 1, t = o.t, seed = o.seed ?? 1; + const alpha = o.alpha ?? 1; + if (alpha <= 1e-3) return; + const col = o.body ?? "#e9c45a"; + const headCol = o.head ?? col; + const beakCol = o.beak ?? "#e0862c"; + const inkW = Math.max(1.2, s * 0.028 * (o.ink ?? 1)); + const walk = o.walk ?? 0; + const ph = t * walk * Math.PI * 2; + const legLen = lerp(0.2, 0.26, sw); + const bob = walk ? Math.abs(Math.sin(ph)) * 0.035 : 8e-3 * Math.sin(t * 2.1 + seed); + const shiver = (o.shiver ?? 0) * 0.012 * Math.sin(t * 47 + seed); + const bodyRy = lerp(0.33, 0.22, sw); + const by = o.swim ? -0.02 : -(legLen + bodyRy) - bob; + g2.save(); + g2.globalAlpha = alpha; + g2.translate(o.x + shiver * s, o.y); + g2.scale(face * s, s); + g2.rotate(o.tilt ?? 0); + const iw = inkW / s; + const sq = o.squash ?? 0; + g2.translate(0, by); + g2.scale(1 + sq * 0.5, 1 - sq); + const body = DUCK_BODY.map((p, i) => lp(p, SWAN_BODY[i], sw)); + const nb = lp([0.24, -0.26], [0.36, -0.12], sw); + let H2 = lp([0.3, -0.56], [0.5, -1.12], sw); + const up = o.neckUp ?? 0; + if (up > 0) H2 = add(H2, [-0.06 * up, -lerp(0.08, 0.22, sw) * up]); + if (up < 0) H2 = lp(H2, lp([0.48, -0.34], [0.92, -0.06], sw), -up); + const tuck = o.tuck ?? 0; + if (tuck > 0) H2 = lp(H2, [-0.06, lerp(-0.42, -0.3, sw)], tuck); + H2 = add(H2, [0.012 * nz(t * 0.9, seed), 0.012 * nz(t * 1.1, seed + 3)]); + const hr = lerp(0.22, 0.105, sw); + if (!o.swim && o.legs !== false) { + for (let k = 0; k < 2; k++) { + const sgn = k === 0 ? 1 : -1; + const swing = walk ? Math.sin(ph + (k ? Math.PI : 0)) * 0.35 : 0; + const hip = [0.02 + 0.08 * sgn, bodyRy - 0.04]; + const foot2 = [hip[0] + Math.sin(swing) * legLen, hip[1] + Math.cos(swing) * legLen + bob - (walk && Math.sin(ph + (k ? Math.PI : 0)) > 0 ? 0.03 : 0)]; + brush(g2, [hip, lp(hip, foot2, 0.5), foot2], iw * 1.3, { color: "#c8731f", alpha: 0.95, taperOut: 0.1, seed: seed + k }); + g2.fillStyle = rgba("#e08a2c", 0.9); + g2.beginPath(); + g2.moveTo(foot2[0] - 0.04, foot2[1]); + g2.lineTo(foot2[0] + 0.11, foot2[1] + 0.012); + g2.lineTo(foot2[0] + 0.02, foot2[1] - 0.035); + g2.closePath(); + g2.fill(); + } + } + const wingOpen = o.wing ?? 0; + const flap = o.flap ?? 0; + const span = lerp(0.75, 1.55, sw); + const shoulder = [0.08, -bodyRy * 0.7]; + const wingE = wingOpen * (o.flap === void 0 ? 0.85 : Math.sin(flap)); + if (wingOpen > 0.01 && wingE > -0.2) { + drawWing(g2, add(shoulder, [0.07, -0.03]), span * 0.95, Math.min(1, wingE * 0.92 + 0.08), col, iw, seed + 50, 1, true); + } + const neckPts = neckCurve(nb, H2, sw); + const nw = lerp(0.3, 0.1, sw); + const neckSide = (sgn, k = 1) => neckPts.map((p, i) => { + const a = neckPts[Math.max(0, i - 1)], b = neckPts[Math.min(neckPts.length - 1, i + 1)]; + let dx = b[0] - a[0], dy = b[1] - a[1]; + const d = Math.hypot(dx, dy) || 1; + dx /= d; + dy /= d; + const f = i / (neckPts.length - 1); + const w = nw * 0.5 * k * lerp(1.35, 0.9, smooth(f)); + return [p[0] - dy * w * sgn, p[1] + dx * w * sgn]; + }); + { + const L = neckSide(1), R = neckSide(-1); + g2.fillStyle = rgba(headCol === col ? col : mixHex(col, headCol, 0.5), 0.96); + g2.beginPath(); + g2.moveTo(L[0][0], L[0][1]); + for (const p of L.slice(1)) g2.lineTo(p[0], p[1]); + for (const p of R.slice().reverse()) g2.lineTo(p[0], p[1]); + g2.closePath(); + g2.fill(); + g2.beginPath(); + g2.arc(nb[0], nb[1], nw * 0.68, 0, Math.PI * 2); + g2.fill(); + } + wash(g2, body, col, 0.94, { dx: 0.012, dy: 0.01, edge: 1.2, lw: iw }); + g2.save(); + tracePath(g2, spline(body, 8, true), true); + g2.clip(); + blot(g2, -0.1, bodyRy * 0.8, 0.5, mixHex(col, "#5a4a38", 0.5), 0.22, 1.2, 0.6); + if (o.glow) blot(g2, 0.05, -0.1, 0.7, "#fffdf4", 0.5 * o.glow); + g2.restore(); + g2.fillStyle = rgba(headCol, 0.96); + g2.beginPath(); + g2.arc(H2[0], H2[1], hr, 0, Math.PI * 2); + g2.fill(); + if (wingOpen > 0.01) { + drawWing(g2, shoulder, span, wingE, col, iw, seed + 60, 1); + } else { + const wl = [[0.18, -0.12], [-0.05, -lerp(0.2, 0.16, sw)], [-0.38, -lerp(0.12, 0.2, sw)], [-lerp(0.42, 0.6, sw), -lerp(0.02, 0.22, sw)], [-0.2, lerp(0.12, 0.08, sw)], [0.1, 0.06]]; + wash(g2, wl, mixHex(col, "#6b6255", 0.12), 0.55, { edge: 0.8, lw: iw }); + const wlp = spline(wl, 8, true); + brush(g2, subPath([...wlp, wlp[0]], 0.05, 0.62), iw * 0.85, { seed: seed + 9, alpha: 0.75, taperIn: 0.2 }); + for (let i = 0; i < 3; i++) { + const a = [-0.12 - i * 0.1, -0.02 + i * 0.01], b = [-0.3 - i * 0.1, -0.1 - i * 0.02 * (1 - sw) - sw * 0.05]; + brush(g2, [a, lp(a, b, 0.5), b], iw * 0.55, { seed: seed + 20 + i, alpha: 0.5 }); + } + } + outline(g2, body, iw * 1.2, { seed, alpha: 0.88, breaks: [0.02, 0.33, 0.62, 0.98] }); + if (sw > 0.15) { + const a = 0.85 * smooth(sw * 2); + brush(g2, subPath(neckSide(1), 0.1, 0.97), iw * 0.95, { seed: seed + 31, alpha: a, taperIn: 0.15, taperOut: 0.2 }); + brush(g2, subPath(neckSide(-1), 0.18, 0.92), iw * 0.8, { seed: seed + 32, alpha: a * 0.75, taperIn: 0.2, taperOut: 0.3 }); + } + const hl = []; + for (let i = 0; i < 12; i++) { + const a = i / 12 * Math.PI * 2; + hl.push([H2[0] + Math.cos(a) * hr, H2[1] + Math.sin(a) * hr]); + } + outline(g2, hl, iw * 1.05, { seed: seed + 40, alpha: 0.85, breaks: [0.55, 0.95, 1.4].map((v) => v % 1).sort() }); + const fl = o.fluff ?? (sw < 0.3 ? 0.4 : 0); + if (fl > 0) { + const r = rng(seed * 77); + const n = Math.round(6 + fl * 10); + for (let i = 0; i < n; i++) { + const u = 0.05 + r() * 0.5; + const bp = spline(body, 6, true); + const idx = Math.floor(u * bp.length) % bp.length; + const p = bp[idx]; + const dir = Math.atan2(p[1] + 0.05, p[0]) + (r() - 0.5) * 0.8; + const L = (0.04 + r() * 0.06) * (0.6 + fl); + brush(g2, [p, [p[0] + Math.cos(dir) * L * 0.6, p[1] + Math.sin(dir) * L * 0.6], [p[0] + Math.cos(dir) * L, p[1] + Math.sin(dir) * L]], iw * 0.6, { seed: seed + 100 + i, alpha: 0.7, taperIn: 0.05 }); + } + for (let i = 0; i < 2 + Math.round(fl * 2); i++) { + const a = -Math.PI / 2 + (i - 1) * 0.35; + const p = [H2[0] + Math.cos(a) * hr, H2[1] + Math.sin(a) * hr]; + const q2 = [p[0] + Math.cos(a - 0.4) * 0.08 * (0.6 + fl), p[1] + Math.sin(a - 0.4) * 0.09 * (0.6 + fl)]; + brush(g2, [p, lp(p, q2, 0.5), q2], iw * 0.7, { seed: seed + 200 + i, alpha: 0.8, taperIn: 0.05 }); + } + } + if (o.snow && o.snow > 0) { + const sl = blobLoop(-0.1, -bodyRy - 0.02 + 0.06 * (1 - o.snow), 0.26 * o.snow + 0.02, seed + 5, 0.25, 18).map((p) => [p[0] * 1.5, p[1] * 0.55 - bodyRy * 0.45]); + wash(g2, sl, "#ffffff", 0.92, { edge: 0.2, lw: iw }); + } + if (tuck < 0.85) { + const look = o.look ?? 0; + let bdir = lerp(0.12, 0.32, sw) + look; + if (up > 0) bdir -= 0.35 * up; + if (up < 0) bdir += 0.9 * -up; + const bl = lerp(0.2, 0.2, sw), bwid = lerp(0.085, 0.05, sw); + const bx = H2[0] + Math.cos(bdir) * hr * 0.85, byy = H2[1] + Math.sin(bdir) * hr * 0.85; + const m = (o.mouth ?? 0) * 0.35; + g2.save(); + g2.translate(bx, byy); + g2.rotate(bdir); + g2.fillStyle = rgba(beakCol, 0.97); + g2.save(); + g2.rotate(-m * 0.6); + g2.beginPath(); + g2.moveTo(-0.02, -bwid); + g2.quadraticCurveTo(bl * 0.6, -bwid * 1.1, bl, -bwid * 0.05); + g2.lineTo(-0.02, bwid * 0.2); + g2.closePath(); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.8); + g2.lineWidth = iw * 0.7; + g2.stroke(); + if (sw > 0.6) { + g2.fillStyle = rgba(INK, 0.9 * smooth((sw - 0.6) * 3)); + g2.beginPath(); + g2.ellipse(0, -bwid * 0.45, 0.035, bwid * 0.7, 0, 0, Math.PI * 2); + g2.fill(); + } + g2.restore(); + g2.save(); + g2.rotate(m); + g2.fillStyle = rgba(mixHex(beakCol, "#8a4a1a", 0.2), 0.97); + g2.beginPath(); + g2.moveTo(-0.02, bwid * 0.15); + g2.quadraticCurveTo(bl * 0.55, bwid * 0.9, bl * 0.9, bwid * 0.1); + g2.lineTo(-0.02, bwid * 0.8); + g2.closePath(); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.7); + g2.lineWidth = iw * 0.6; + g2.stroke(); + g2.restore(); + g2.restore(); + const ex = H2[0] + Math.cos(bdir - 0.95) * hr * 0.42, ey = H2[1] + Math.sin(bdir - 0.95) * hr * 0.42; + const er = lerp(0.045, 0.022, sw); + const blink = !o.noBlink && (t + seed * 0.37) % 3.4 < 0.13; + if (blink || tuck > 0.5) { + g2.strokeStyle = rgba(INK, 0.9); + g2.lineWidth = iw * 0.9; + g2.beginPath(); + g2.arc(ex, ey - er * 0.3, er, 0.2, Math.PI - 0.2); + g2.stroke(); + } else { + g2.fillStyle = rgba(INK, 0.95); + g2.beginPath(); + g2.arc(ex, ey, er, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = rgba(PAPER, 0.95); + g2.beginPath(); + g2.arc(ex + er * 0.3, ey - er * 0.35, er * 0.33, 0, Math.PI * 2); + g2.fill(); + } + } + g2.restore(); + } + function swimRipples(g2, x, y, s, t, seed = 1, alpha = 0.6) { + for (let k = 0; k < 3; k++) { + const ph = (t * 0.6 + k / 3 + hash(seed)) % 1; + const w = s * (0.55 + ph * 0.9); + g2.strokeStyle = rgba(INK, alpha * (1 - ph) * 0.7); + g2.lineWidth = Math.max(1, s * 0.012); + g2.beginPath(); + g2.ellipse(x, y + s * 0.01, w, w * 0.08, 0, Math.PI * 0.05, Math.PI * 0.95); + g2.stroke(); + } + brush(g2, [[x - s * 0.62, y], [x, y + s * 0.015], [x + s * 0.62, y]], s * 0.02, { seed, alpha: alpha * 0.8, jitter: 0.5 }); + } + function reflection(g2, draw, box, waterY, t, alpha, tint = "#5d7180") { + const w = Math.ceil(box.w), h = Math.ceil(box.h); + if (w < 2 || h < 2) return; + const off = typeof OffscreenCanvas !== "undefined" ? new OffscreenCanvas(w, h) : Object.assign(document.createElement("canvas"), { width: w, height: h }); + const c2 = off.getContext("2d"); + c2.translate(-box.x, -box.y); + draw(c2); + c2.setTransform(1, 0, 0, 1, 0, 0); + c2.globalCompositeOperation = "source-atop"; + c2.fillStyle = rgba(tint, 0.18); + c2.fillRect(0, 0, w, h); + const strips = Math.max(8, Math.round(h / 6)); + const sh = h / strips; + g2.save(); + for (let i = 0; i < strips; i++) { + const sy = box.y + box.h - (i + 1) * sh; + const dy = waterY + (waterY - (box.y + box.h)) + i * sh; + const wob = Math.sin(t * 2.2 + i * 0.55) * (1.5 + i * 0.25); + g2.globalAlpha = alpha * (1 - i / strips) * 0.9; + g2.save(); + g2.translate(0, dy + sh); + g2.scale(1, -1); + g2.drawImage(off, 0, sy - box.y, w, sh + 1, box.x + wob, 0, w, sh + 1); + g2.restore(); + } + g2.restore(); + } + function rooster(g2, x, y, s, t, o = {}) { + const face = o.face ?? 1, seed = o.seed ?? 3, iw = Math.max(1.2, s * 0.028); + g2.save(); + g2.translate(x, y); + g2.scale(face * s, s); + const w = iw / s; + const peck = o.peck ?? 0; + for (const k of [-1, 1]) brush(g2, [[0.02 * k, -0.28], [0.04 * k, -0.14], [0.02 * k + 0.04, 0]], w * 1.4, { color: "#b8892a", alpha: 0.95, seed: seed + k }); + const tailCols = ["#2e3d33", "#3f5a43", "#1f2a24"]; + for (let i = 0; i < 5; i++) { + const a = -1.9 - i * 0.22 + 0.05 * Math.sin(t * 3 + i); + const p0 = [-0.32, -0.52]; + const p2 = [p0[0] + Math.cos(a) * 0.62, p0[1] + Math.sin(a) * 0.5 - 0.1]; + const p1 = [lerp(p0[0], p2[0], 0.5) - 0.18, lerp(p0[1], p2[1], 0.5) - 0.2]; + brush(g2, spline([p0, p1, p2], 8), w * (3.5 - i * 0.3), { color: tailCols[i % 3], alpha: 0.85, seed: seed + 10 + i, taperOut: 0.8 }); + } + const body = [[0.3, -0.55], [0.12, -0.42], [-0.22, -0.4], [-0.38, -0.55], [-0.32, -0.28], [-0.1, -0.22], [0.2, -0.28], [0.34, -0.42]]; + wash(g2, body, "#c8783a", 0.9, { lw: w }); + const wing = [[0.12, -0.44], [-0.2, -0.46], [-0.3, -0.36], [-0.05, -0.3]]; + wash(g2, wing, "#8a4a22", 0.7, { lw: w }); + if (o.flap) { + const a = -1.2 - 0.6 * Math.sin(o.flap); + stroke(g2, [[0, -0.45], [Math.cos(a) * 0.25, -0.45 + Math.sin(a) * 0.25], [Math.cos(a) * 0.45 - 0.1, -0.45 + Math.sin(a) * 0.4]], w * 5, { color: "#7a3e1c", alpha: 0.8, seed: seed + 7 }); + } + outline(g2, body, w * 1.2, { seed, alpha: 0.85 }); + const hx = 0.36 + peck * 0.22, hy = -0.72 + peck * 0.32; + stroke(g2, [[0.26, -0.5], [0.33 + peck * 0.1, -0.62 + peck * 0.15], [hx, hy]], w * 5.5, { color: "#d9a04a", alpha: 0.95, seed: seed + 5, taperOut: 0 }); + g2.fillStyle = rgba("#d9a04a", 0.97); + g2.beginPath(); + g2.arc(hx, hy, 0.085, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = rgba("#c0362c", 0.95); + for (let i = 0; i < 3; i++) { + g2.beginPath(); + g2.arc(hx - 0.05 + i * 0.045, hy - 0.08 - (i === 1 ? 0.02 : 0), 0.035, 0, Math.PI * 2); + g2.fill(); + } + g2.beginPath(); + g2.ellipse(hx + 0.04, hy + 0.08, 0.025, 0.045, 0, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = rgba("#e3a22f", 1); + g2.beginPath(); + g2.moveTo(hx + 0.07, hy - 0.02); + g2.lineTo(hx + 0.17, hy + 0.01); + g2.lineTo(hx + 0.07, hy + 0.035); + g2.closePath(); + g2.fill(); + g2.fillStyle = INK; + g2.beginPath(); + g2.arc(hx + 0.025, hy - 0.015, 0.016, 0, Math.PI * 2); + g2.fill(); + g2.restore(); + } + function child(g2, x, y, s, t, o = {}) { + const face = o.face ?? 1, seed = o.seed ?? 5, iw = Math.max(1.2, s * 0.02); + const dress = o.dress ?? "#c0504a"; + g2.save(); + g2.translate(x, y); + g2.scale(face * s, s); + const w = iw / s; + const bob = 0.01 * Math.sin(t * 3 + seed); + for (const k of [-1, 1]) brush(g2, [[0.05 * k, -0.42], [0.06 * k, -0.2], [0.06 * k, -0.02]], w * 5, { color: "#3b3a3f", alpha: 0.85, seed: seed + k, taperOut: 0 }); + g2.fillStyle = rgba(INK, 0.9); + for (const k of [-1, 1]) { + g2.beginPath(); + g2.ellipse(0.06 * k + 0.03, -0.01, 0.06, 0.025, 0, 0, Math.PI * 2); + g2.fill(); + } + const dl = [[0, -0.88 + bob], [0.14, -0.82 + bob], [0.19, -0.4], [-0.19, -0.4], [-0.14, -0.82 + bob]]; + wash(g2, dl, dress, 0.85, { lw: w }); + outline(g2, dl, w * 1.2, { seed, alpha: 0.8 }); + const pose = o.pose ?? "stand"; + const sh = [0.12, -0.8 + bob], sh2 = [-0.12, -0.8 + bob]; + let hand1 = [0.2, -0.5], hand2 = [-0.2, -0.5]; + if (pose === "clap") { + const c2 = Math.abs(Math.sin(t * 9 + seed)); + hand1 = [0.1 + c2 * 0.08, -1.02]; + hand2 = [-0.02 - c2 * 0.08 + 0.12, -1.02]; + } + if (pose === "shoo") { + const c2 = Math.sin(t * 6 + seed); + hand1 = [0.32, -0.75 + c2 * 0.06]; + hand2 = [-0.18, -0.52]; + } + if (pose === "basket") { + hand1 = [0.22, -0.56]; + hand2 = [-0.2, -0.52]; + } + for (const [a, b] of [[sh, hand1], [sh2, hand2]]) { + brush(g2, [a, [lerp(a[0], b[0], 0.5) + 0.03, lerp(a[1], b[1], 0.5)], b], w * 4.5, { color: dress, alpha: 0.9, seed: seed + 3, taperOut: 0 }); + g2.fillStyle = rgba("#f0cfa8", 1); + g2.beginPath(); + g2.arc(b[0], b[1], 0.035, 0, Math.PI * 2); + g2.fill(); + } + if (pose === "basket") { + g2.strokeStyle = rgba("#8a6a3a", 0.9); + g2.lineWidth = w * 2; + g2.beginPath(); + g2.arc(0.22, -0.48, 0.09, Math.PI, 0); + g2.stroke(); + wash(g2, [[0.12, -0.48], [0.32, -0.48], [0.29, -0.36], [0.15, -0.36]], "#b08850", 0.9, { lw: w }); + } + const hy = -1 + bob; + g2.fillStyle = rgba("#f3d7b5", 1); + g2.beginPath(); + g2.arc(0.02, hy, 0.13, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = rgba(INK, 0.92); + g2.beginPath(); + g2.arc(0, hy - 0.035, 0.135, Math.PI * 1.02, Math.PI * 2); + g2.fill(); + if (o.girl !== false) { + g2.beginPath(); + g2.arc(-0.11, hy - 0.12, 0.055, 0, Math.PI * 2); + g2.fill(); + g2.beginPath(); + g2.arc(0.13, hy - 0.12, 0.055, 0, Math.PI * 2); + g2.fill(); + } else { + g2.beginPath(); + g2.arc(0, hy - 0.13, 0.04, 0, Math.PI * 2); + g2.fill(); + } + g2.beginPath(); + g2.arc(0.08, hy + 0.01, 0.014, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = rgba("#e07a6a", 0.45); + g2.beginPath(); + g2.arc(0.06, hy + 0.06, 0.03, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.8); + g2.lineWidth = w * 1; + g2.beginPath(); + if (pose === "shoo") { + g2.moveTo(0.06, hy + 0.08); + g2.lineTo(0.12, hy + 0.075); + } else g2.arc(0.1, hy + 0.06, 0.03, 0.2, Math.PI - 0.4); + g2.stroke(); + g2.restore(); + } + function reed(g2, x, y, h, t, seed, o = {}) { + const r = rng(seed); + const wind = o.wind ?? 1; + const sway = (0.05 + 0.04 * r()) * Math.sin(t * (0.9 + r() * 0.5) + seed) * wind + 0.03 * wind; + const tip = [x + h * sway * 1.4, y - h]; + const mid = [x + h * sway * 0.4, y - h * 0.55]; + const col = o.color ?? "#3b4a35", a = o.alpha ?? 0.8; + brush(g2, spline([[x, y], mid, tip], 8), Math.max(1.5, h * 0.012), { color: col, alpha: a, seed, taperIn: 0.02, taperOut: 0.6 }); + for (let i = 0; i < 2; i++) { + const k = 0.25 + i * 0.25 + r() * 0.1; + const base = [lerp(x, tip[0], k * 0.6), y - h * k]; + const dir = r() < 0.5 ? -1 : 1; + const end = [base[0] + dir * h * (0.18 + r() * 0.12) + h * sway * 0.5, base[1] - h * (0.12 + r() * 0.15)]; + brush(g2, spline([base, [lerp(base[0], end[0], 0.5), base[1] - h * 0.14], end], 8), Math.max(2, h * 0.03), { color: col, alpha: a * 0.85, seed: seed + i * 5, taperIn: 0.05, taperOut: 0.85 }); + } + if (o.head !== false && r() < 0.65) { + const hp = [lerp(mid[0], tip[0], 0.75), lerp(mid[1], tip[1], 0.75)]; + g2.save(); + g2.translate(hp[0], hp[1]); + g2.rotate(sway * 1.5); + g2.fillStyle = rgba("#5e4630", a); + g2.beginPath(); + g2.ellipse(0, 0, h * 0.018, h * 0.07, 0, 0, Math.PI * 2); + g2.fill(); + g2.restore(); + } + } + function grass(g2, x, y, h, t, seed, color = "#4f6a3e", alpha = 0.7, wind = 1) { + const r = rng(seed); + for (let i = 0; i < 5; i++) { + const dx = (r() - 0.5) * h * 0.5; + const bend = (r() - 0.5) * 0.6 + 0.12 * Math.sin(t * 1.5 + seed + i) * wind + 0.15 * wind; + const hh = h * (0.5 + r() * 0.6); + brush(g2, spline([[x + dx, y], [x + dx + bend * hh * 0.3, y - hh * 0.5], [x + dx + bend * hh, y - hh]], 6), Math.max(1.5, h * 0.06), { color, alpha, seed: seed + i, taperIn: 0.02, taperOut: 0.9 }); + } + } + function lotusLeaf(g2, x, y, r, seed, color = "#6f9a62", alpha = 0.75) { + const loop = blobLoop(0, 0, r, seed, 0.06, 20).map((p) => [x + p[0], y + p[1] * 0.38]); + wash(g2, loop, color, alpha); + outline(g2, loop, Math.max(1.2, r * 0.03), { seed, alpha: 0.55, breaks: [0.1, 0.45, 0.9] }); + for (let i = 0; i < 6; i++) { + const a = i / 6 * Math.PI * 2 + seed; + brush(g2, [[x, y], [x + Math.cos(a) * r * 0.45, y + Math.sin(a) * r * 0.17], [x + Math.cos(a) * r * 0.85, y + Math.sin(a) * r * 0.32]], Math.max(1, r * 0.02), { color: "#3e5a35", alpha: 0.4, seed: seed + i }); + } + } + function egg(g2, x, y, r, o = {}) { + const seed = o.seed ?? 1; + const col = o.color ?? "#f4ecd6"; + const crack = o.crack ?? 0, open = o.open ?? 0; + g2.save(); + g2.translate(x, y); + g2.rotate(o.wobble ?? 0); + const loop = []; + for (let i = 0; i < 18; i++) { + const a = i / 18 * Math.PI * 2; + const k = Math.sin(a) < 0 ? 1.28 : 1; + loop.push([Math.cos(a) * r * 0.82, Math.sin(a) * r * k - r * 0.95]); + } + if (open < 0.02) { + wash(g2, loop, col, 0.95); + blot(g2, -r * 0.25, -r * 1.25, r * 0.4, "#ffffff", 0.5); + outline(g2, loop, Math.max(1.2, r * 0.07), { seed, alpha: 0.8 }); + } else { + const low = loop.filter((p) => p[1] > -r * 1.05); + const lowL = [...low.sort((a, b) => Math.atan2(a[1] + r, a[0]) - Math.atan2(b[1] + r, b[0]))]; + const zig = []; + for (let i = 0; i <= 6; i++) zig.push([lerp(r * 0.82, -r * 0.82, i / 6), -r * 1.05 + (i % 2 ? -r * 0.16 : r * 0.05)]); + const shell = [...lowL, ...zig]; + g2.fillStyle = rgba(col, 0.95); + tracePath(g2, shell, true); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.8); + g2.lineWidth = Math.max(1.2, r * 0.06); + tracePath(g2, shell, true); + g2.stroke(); + const lift = open; + g2.save(); + g2.translate(r * 0.3 * lift, -r * 1.2 - r * 1.4 * lift); + g2.rotate(0.9 * lift); + const cap = [...zig.slice().reverse().map((p) => [p[0], p[1] + r * 1.05]), [-r * 0.6, -r * 0.6], [0, -r * 0.95], [r * 0.6, -r * 0.6]]; + g2.globalAlpha *= clamp(1.4 - lift); + g2.fillStyle = rgba(col, 0.95); + tracePath(g2, cap, true); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.8); + tracePath(g2, cap, true); + g2.stroke(); + g2.restore(); + } + if (crack > 0 && open < 0.02) { + const pts = []; + const n = 7; + for (let i = 0; i <= n; i++) pts.push([lerp(-r * 0.75, r * 0.75, i / n), -r * 1 + (i % 2 ? -r * 0.14 : r * 0.08) + r * 0.04 * nz(i, seed)]); + brush(g2, pts, Math.max(1.2, r * 0.06), { seed: seed + 3, alpha: 0.9, grow: crack, taperIn: 0.05, taperOut: 0.1 }); + } + g2.restore(); + } + function nest(g2, x, y, w, seed) { + const r = rng(seed); + blot(g2, x, y - w * 0.05, w * 0.6, "#7a5a34", 0.35, 1, 0.35); + for (let i = 0; i < 26; i++) { + const a0 = r() * Math.PI, a1 = a0 + 0.6 + r() * 0.8; + const rr = w * (0.42 + r() * 0.12); + const p = (a) => [x + Math.cos(a) * rr, y - w * 0.04 + Math.sin(a) * rr * 0.28]; + brush(g2, [p(a0), p((a0 + a1) / 2), p(a1)], Math.max(1.2, w * 0.012), { color: i % 3 ? "#6b4a26" : "#a07a40", alpha: 0.75, seed: seed + i, dry: 0.3 }); + } + } + function fence(g2, x0, x1, y, h, seed, gapAt = -1, gapW = 0) { + const r = rng(seed); + const step = h * 0.42; + for (let x = x0; x < x1; x += step) { + if (gapAt >= 0 && Math.abs(x - gapAt) < gapW / 2) continue; + const hh = h * (0.9 + r() * 0.2), lean = (r() - 0.5) * 0.06; + brush(g2, [[x, y], [x + lean * hh * 0.5, y - hh * 0.5], [x + lean * hh, y - hh]], h * 0.07, { color: "#5a4630", alpha: 0.85, seed: seed + x, taperIn: 0.05, taperOut: 0.2, dry: 0.5 }); + } + for (const k of [0.35, 0.7]) { + const yy = y - h * k; + if (gapAt >= 0) { + brush(g2, [[x0, yy], [(x0 + gapAt - gapW / 2) / 2, yy + 3], [gapAt - gapW / 2, yy]], h * 0.045, { color: "#5a4630", alpha: 0.8, seed: seed + k * 9, dry: 0.5 }); + brush(g2, [[gapAt + gapW / 2, yy], [(x1 + gapAt + gapW / 2) / 2, yy - 2], [x1, yy]], h * 0.045, { color: "#5a4630", alpha: 0.8, seed: seed + k * 11, dry: 0.5 }); + } else brush(g2, [[x0, yy], [(x0 + x1) / 2, yy + 4], [x1, yy]], h * 0.045, { color: "#5a4630", alpha: 0.8, seed: seed + k * 9, dry: 0.5 }); + } + } + function tree(g2, x, y, h, seed, o = {}) { + const r = rng(seed); + const col = o.color ?? "#2a2420"; + const t = o.t ?? 0; + const grow = (x2, y2, ang, len, w, d) => { + const sway = 0.015 * Math.sin(t * 0.8 + d + seed) * (4 - d); + const a = ang + sway; + const ex = x2 + Math.cos(a) * len, ey = y2 + Math.sin(a) * len; + const mx = (x2 + ex) / 2 + (r() - 0.5) * len * 0.25, my = (y2 + ey) / 2 + (r() - 0.5) * len * 0.15; + brush(g2, spline([[x2, y2], [mx, my], [ex, ey]], 8), w, { color: col, alpha: 0.9, seed: seed + d * 13 + len, taperIn: 0.02, taperOut: d >= (o.depth ?? 4) ? 0.9 : 0.25, dry: 0.35, jitter: 0.3 }); + if (d >= (o.depth ?? 4)) { + o.leaves?.(ex, ey, len); + return; + } + const kids = d < 2 ? 2 : 2 + (r() < 0.4 ? 1 : 0); + for (let i = 0; i < kids; i++) grow(ex, ey, a + (r() - 0.5) * 1.3 + (i - (kids - 1) / 2) * 0.35, len * (0.62 + r() * 0.15), w * 0.62, d + 1); + }; + grow(x, y, -Math.PI / 2 + (o.lean ?? 0), h * 0.38, h * 0.06, 1); + } + function willow(g2, x, y, h, t, seed, green = "#7fa36b", bud = 1) { + const r = rng(seed); + stroke(g2, [[x, y], [x - h * 0.04, y - h * 0.4], [x + h * 0.03, y - h * 0.75], [x + h * 0.1, y - h * 0.95]], h * 0.07, { color: "#3a3027", alpha: 0.9, seed, dry: 0.5, jitter: 0.35 }); + const top = [x + h * 0.08, y - h * 0.93]; + for (let i = 0; i < 16; i++) { + const a = -Math.PI / 2 + (r() - 0.5) * 2.6; + const sx = top[0] + Math.cos(a) * h * (0.15 + r() * 0.25), sy = top[1] + Math.sin(a) * h * 0.12; + const len = h * (0.45 + r() * 0.45); + const sway = Math.sin(t * 1.1 + i * 0.7 + seed) * h * 0.035; + const pts = spline([[top[0], top[1]], [sx, sy - h * 0.05], [sx + sway * 0.4 + (sx - top[0]) * 0.2, sy + len * 0.45], [sx + sway, sy + len]], 6); + brush(g2, pts, Math.max(1, h * 6e-3), { color: "#4a4030", alpha: 0.6, seed: seed + i, taperIn: 0.05, taperOut: 0.6 }); + if (bud > 0) { + for (let k = 6; k < pts.length; k += 2) { + const p = pts[k]; + g2.save(); + g2.translate(p[0], p[1]); + g2.rotate(0.6 * (k % 4 ? 1 : -1)); + g2.fillStyle = rgba(green, 0.55 * bud); + g2.beginPath(); + g2.ellipse(0, 0, h * 6e-3 + h * 8e-3 * bud, h * 0.018 * bud + 1, 0, 0, Math.PI * 2); + g2.fill(); + g2.restore(); + } + } + } + } + function farBird(g2, x, y, s, ph, color = INK, alpha = 0.85) { + const k = Math.sin(ph) * 0.6; + brush(g2, spline([[x - s, y - s * 0.3 * k - s * 0.1], [x - s * 0.45, y - s * 0.45 * (0.4 + k)], [x, y]], 6), Math.max(1.2, s * 0.16), { color, alpha, taperIn: 0.6, taperOut: 0.1, seed: 3 }); + brush(g2, spline([[x, y], [x + s * 0.45, y - s * 0.45 * (0.4 + k)], [x + s, y - s * 0.3 * k - s * 0.1]], 6), Math.max(1.2, s * 0.16), { color, alpha, taperIn: 0.1, taperOut: 0.6, seed: 4 }); + } + function sun(g2, x, y, r, color = "#c8463a", alpha = 0.85) { + blot(g2, x, y, r * 2.4, color, alpha * 0.12); + const loop = blobLoop(x, y, r, 7, 0.025, 24); + wash(g2, loop, color, alpha, { edge: 0.6 }); + } + function cloud(g2, x, y, w, seed, alpha = 0.35, color = "#8d8a84") { + const r = rng(seed); + for (let i = 0; i < 5; i++) blot(g2, x + (r() - 0.5) * w, y + (r() - 0.5) * w * 0.12, w * (0.18 + r() * 0.16), color, alpha * 0.5, 1.6, 0.55); + brush(g2, spline([[x - w * 0.5, y + w * 0.06], [x - w * 0.2, y - w * 0.02], [x + w * 0.15, y + w * 0.03], [x + w * 0.5, y - w * 0.02]], 6), Math.max(1.2, w * 8e-3), { color: "#6b665e", alpha: alpha * 0.9, seed, dry: 0.4 }); + } + function smoke(g2, x, y, r, a, seed) { + for (let i = 0; i < 6; i++) { + const ang = hash(seed + i) * Math.PI * 2; + blot(g2, x + Math.cos(ang) * r * 0.5, y + Math.sin(ang) * r * 0.35, r * (0.55 + hash(seed + i * 3) * 0.4), "#6d6a66", a * 0.55); + } + } + function water(g2, x0, x1, y, h, t, seed, o = {}) { + const r = rng(seed); + const n = o.lines ?? 14; + for (let i = 0; i < n; i++) { + const yy = y + Math.pow(r(), 1.3) * h; + const len = (x1 - x0) * (0.04 + r() * 0.12) * (0.6 + (yy - y) / h); + const xx = x0 + (r() * (x1 - x0) + t * (o.speed ?? 6) * (0.5 + r())) % (x1 - x0 + len) - len * 0.5; + brush(g2, [[xx, yy], [xx + len * 0.5, yy + 1.5 * Math.sin(t + i)], [xx + len, yy]], Math.max(1, 1.2 + (yy - y) / h * 2.5), { color: o.color ?? "#3d4a52", alpha: (o.alpha ?? 0.35) * (0.5 + 0.5 * (yy - y) / h), seed: seed + i, jitter: 0.5 }); + } + } + function blossom(g2, x, y, r, seed, color = "#e48a96", alpha = 0.8) { + for (let i = 0; i < 5; i++) { + const a = i / 5 * Math.PI * 2 + seed; + blot(g2, x + Math.cos(a) * r * 0.55, y + Math.sin(a) * r * 0.55, r * 0.6, color, alpha); + } + g2.fillStyle = rgba("#b8323a", alpha); + g2.beginPath(); + g2.arc(x, y, r * 0.18, 0, Math.PI * 2); + g2.fill(); + } + function leaf(g2, x, y, s, rot2, color, alpha = 0.85) { + g2.save(); + g2.translate(x, y); + g2.rotate(rot2); + g2.fillStyle = rgba(color, alpha); + g2.beginPath(); + g2.moveTo(-s, 0); + g2.quadraticCurveTo(0, -s * 0.7, s, 0); + g2.quadraticCurveTo(0, s * 0.7, -s, 0); + g2.fill(); + g2.strokeStyle = rgba(INK, alpha * 0.5); + g2.lineWidth = Math.max(0.8, s * 0.08); + g2.beginPath(); + g2.moveTo(-s, 0); + g2.lineTo(s * 0.9, 0); + g2.stroke(); + g2.restore(); + } + function soundMarks(g2, x, y, s, a, seed = 1, dir = 1) { + if (a <= 0) return; + for (let i = 0; i < 3; i++) { + const ang = -0.7 + i * 0.45; + const p0 = [x + dir * Math.cos(ang) * s * 0.5, y + Math.sin(ang) * s * 0.5]; + const p1 = [x + dir * Math.cos(ang) * s, y + Math.sin(ang) * s]; + brush(g2, [p0, lp(p0, p1, 0.5), p1], Math.max(1.2, s * 0.08), { alpha: 0.75 * a, seed: seed + i, taperIn: 0.3, taperOut: 0.4 }); + } + } + function tear(g2, x, y, s, a) { + if (a <= 0) return; + g2.fillStyle = rgba("#7fa4c4", 0.8 * a); + g2.beginPath(); + g2.moveTo(x, y - s); + g2.quadraticCurveTo(x + s * 0.7, y + s * 0.2, x, y + s * 0.6); + g2.quadraticCurveTo(x - s * 0.7, y + s * 0.2, x, y - s); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.6 * a); + g2.lineWidth = Math.max(1, s * 0.12); + g2.stroke(); + } + + // src/films/duckling/cues.ts + var CUE = { + title: { drop: 0.32, write: 1.1, seal: 4.55 }, + hatch: { first: 0.4, gap: 0.75 }, + bigegg: { crackFrom: 3, open: 3.8, surprise: 5.2, question: 6.6, walk: 8.4 }, + bullied: { laugh1: [0.6, 4.6], laugh2: [8.6, 10.4], roosterIn: 4, pecks: [4.9, 5.35, 5.8, 6.25], girlIn: 6, shoo: 7 }, + leave: { walk: 2.6 }, + marsh: { laugh: [2.4, 5], shots: [5.75, 6.3], scatter: 6.4, hide: 6.8 }, + autumn: { swans: 4, lookUp: 4.4, walk: 11 }, + winter: { ice: 3.6 }, + spring: { buds: 2.2, bloom: 3.5, spread: 4.8, flapHard: 7.6, lift: 9.5, question: 7.1 }, + reflection: { land: 0.75, bow: 4.2, mirror: 5.4, reveal: 9.7, proud: 10.6 }, + accepted: { cheer: 4.6, heart: 7.4 }, + ending: { end: 8.5 } + }; + + // src/films/duckling/scenes.ts + var UGLY = "#8d8a84"; + var UGLY_BEAK = "#9c7f5c"; + var DUCKY = "#ecc75a"; + var WHITE = "#fbf8f0"; + var SWAN_BEAK = "#d9822b"; + var ks = (s) => s.u * (s.portrait ? 0.95 : 1); + var cam = (s, z, x, y, extra = {}) => ({ z: z * (s.portrait ? 1.25 : 1), x, y, ...extra }); + function sky(g2, W2, y1, top, a0, bottom, a1) { + const gr = g2.createLinearGradient(0, 0, 0, y1); + gr.addColorStop(0, rgba(top, a0)); + gr.addColorStop(1, rgba(bottom, a1)); + g2.fillStyle = gr; + g2.fillRect(0, 0, W2, y1); + } + function band(g2, W2, y0, y1, color, a, seed, rough = 18) { + const pts = []; + for (let i = 0; i <= 24; i++) pts.push([i / 24 * W2, y0 + nz(i * 0.6, seed) * rough]); + const gr = g2.createLinearGradient(0, y0 - rough, 0, y1); + gr.addColorStop(0, rgba(color, a)); + gr.addColorStop(1, rgba(color, a * 0.45)); + g2.fillStyle = gr; + g2.beginPath(); + g2.moveTo(0, y1); + for (const p of pts) g2.lineTo(p[0], p[1]); + g2.lineTo(W2, y1); + g2.closePath(); + g2.fill(); + brush(g2, pts, 2.2, { color, alpha: Math.min(1, a * 1.3), seed, jitter: 0.6, dry: 0.5 }); + } + function strokesH(g2, W2, y0, y1, n, color, a, seed, u) { + const r = rng(seed); + for (let i = 0; i < n; i++) { + const y = lerp(y0, y1, r()), x = r() * W2, len = (40 + r() * 160) * u * (0.5 + (y - y0) / Math.max(1, y1 - y0)); + brush(g2, [[x, y], [x + len / 2, y + (r() - 0.5) * 4 * u], [x + len, y]], (1.5 + r() * 3) * u, { color, alpha: a * (0.5 + r() * 0.5), seed: seed + i, dry: 0.6, jitter: 0.5 }); + } + } + function particles(n, seed, fn) { + const r = rng(seed); + for (let i = 0; i < n; i++) fn(r, i); + } + function exclaim(g2, x, y, s, a, seed = 1) { + if (a <= 0) return; + brush(g2, [[x, y - s], [x + s * 0.04, y - s * 0.6], [x + s * 0.02, y - s * 0.25]], s * 0.22, { alpha: 0.9 * a, seed, taperIn: 0.1, taperOut: 0.7 }); + g2.fillStyle = rgba(INK, 0.9 * a); + g2.beginPath(); + g2.arc(x + s * 0.02, y, s * 0.09, 0, Math.PI * 2); + g2.fill(); + } + function question(g2, x, y, s, a) { + if (a <= 0) return; + g2.save(); + g2.globalAlpha = a; + g2.fillStyle = INK; + g2.font = `${s}px ${FONT_BRUSH}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + g2.fillText("\uFF1F", x, y); + g2.restore(); + } + function writeText(g2, text, x, y, fs, t0, per, t, o = {}) { + const chars = [...text]; + const gap = o.gap ?? 1.08; + g2.save(); + g2.font = `${fs}px ${o.font ?? FONT_BRUSH}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + chars.forEach((ch, i) => { + const off = (i - (chars.length - 1) / 2) * fs * gap; + const cx = o.vertical ? x : x + off, cy = o.vertical ? y + off : y; + const p = inv(t0 + i * per, t0 + i * per + per * 1.2, t); + if (p <= 0) return; + g2.save(); + g2.beginPath(); + g2.rect(cx - fs * 0.7, cy - fs * 0.7, fs * 1.4, fs * 1.4 * easeOut(p)); + g2.clip(); + if (o.bleed !== 0) { + g2.shadowColor = rgba(INK, 0.35); + g2.shadowBlur = fs * 0.06; + } + g2.fillStyle = o.color ?? INK; + g2.fillText(ch, cx, cy); + g2.restore(); + }); + g2.restore(); + } + function swanBird(x, y, s, t, extra = {}) { + return { x, y, s, t, sw: 1, body: WHITE, beak: SWAN_BEAK, swim: true, ...extra }; + } + function ripplesRing(g2, x, y, r, a, color = INK) { + if (a <= 0) return; + g2.strokeStyle = rgba(color, a); + g2.lineWidth = Math.max(1, r * 0.012); + g2.beginPath(); + g2.ellipse(x, y, r, r * 0.16, 0, 0, Math.PI * 2); + g2.stroke(); + } + function swimBird(g2, o, rip = true) { + g2.save(); + g2.beginPath(); + g2.rect(o.x - o.s * 3, o.y - o.s * 4, o.s * 6, o.s * 4 + o.s * 0.06); + g2.clip(); + bird(g2, o); + g2.restore(); + if (rip) swimRipples(g2, o.x, o.y + o.s * 0.06, o.s, o.t, o.seed ?? 1, 0.5 * (o.alpha ?? 1)); + } + function fallingThings(g2, W2, H2, t, n, seed, u, draw, speed = 70) { + particles(n, seed, (r, i) => { + const x0 = r() * W2 * 1.2 - W2 * 0.1, y0 = r() * H2, sp = (0.6 + r() * 0.8) * speed * u, drift = (20 + r() * 50) * u; + const y = (y0 + t * sp) % (H2 + 60 * u) - 30 * u; + const x = x0 + Math.sin(t * (0.8 + r()) + i) * drift + t * 12 * u; + draw((x % (W2 + 40 * u) + W2 + 40 * u) % (W2 + 40 * u) - 20 * u, y, t * (1 + r() * 2) + i, r, i); + }); + } + function nestPos(s) { + return [s.X(0.08), s.gy + 70 * s.u]; + } + function bgNestSide(s) { + const { g: g2, W: W2, H: H2, u, gy } = s; + sky(g2, W2, gy, "#a9c3c9", 0.16, "#ffffff", 0); + mountains(g2, W2, gy - 90 * u, 120 * u, "#8c9a96", 0.25, 11, { soft: 1 }); + for (let i = 0; i < 26; i++) { + const x = i / 26 * W2 + hash(i) * 40 * u; + reed(g2, x, gy - 20 * u, (120 + hash(i * 3) * 110) * u, 0, 300 + i, { color: "#7d8a76", alpha: 0.35, head: true, wind: 0.3 }); + } + band(g2, W2, gy - 30 * u, H2, "#a8b878", 0.32, 4); + strokesH(g2, W2, gy, H2, 40, "#6f8a52", 0.35, 9, u); + const [nx, ny] = nestPos(s); + blot(g2, nx, ny + 30 * u, 380 * u, "#7a8a50", 0.18, 1.3, 0.35); + } + var SCENES2 = { + title: { + label: "\u7247\u5934", + draw(s) { + const { g: g2, W: W2, H: H2, u, t, portrait } = s; + const cx = W2 / 2, cy = portrait ? H2 * 0.37 : H2 * 0.43; + if (t < 0.32) { + const y = lerp(-30 * u, cy, easeIn(t / 0.32)); + g2.fillStyle = rgba(INK, 0.95); + g2.beginPath(); + g2.ellipse(cx, y, 9 * u, 15 * u, 0, 0, Math.PI * 2); + g2.fill(); + } + const p = easeOut(inv(0.3, 2.4, t)); + if (p > 0) { + const R = (portrait ? 420 : 350) * u * p; + wash(g2, blobLoop(cx, cy, R, 5, 0.05, 32), "#a39c90", 0.14 + 0.18 * (1 - p), { edge: 1.4 }); + blot(g2, cx, cy, R * 0.85, "#8a8276", 0.12 * p); + particles(10, 77, (r, i) => { + const a2 = r() * Math.PI * 2, d = (R * 0.9 + 60 * u * r()) * easeOut(inv(0.3, 0.8, t)); + const rr = (3 + r() * 8) * u; + g2.fillStyle = rgba(INK, 0.6 * (1 - 0.6 * p)); + g2.beginPath(); + g2.arc(cx + Math.cos(a2) * d, cy + Math.sin(a2) * d * 0.9, rr, 0, Math.PI * 2); + g2.fill(); + void i; + }); + } + const a = smooth(inv(0.8, 2.6, t)); + if (a > 0) { + g2.save(); + g2.globalAlpha = a; + const by = portrait ? H2 * 0.9 : H2 * 0.95; + lotusLeaf(g2, W2 * 0.1, by, 150 * u, 3); + lotusLeaf(g2, W2 * 0.24, by + 30 * u, 110 * u, 5, "#7fa070"); + for (let i = 0; i < 9; i++) reed(g2, W2 * (0.8 + i * 0.025), by + 20 * u, (260 + hash(i) * 200) * u * (portrait ? 1.2 : 1), t, 40 + i); + g2.restore(); + } + const fs = (portrait ? 230 : 210) * u; + writeText(g2, "\u4E11\u5C0F\u9E2D", cx, cy, fs, 1.1, 0.45, t, { vertical: portrait }); + const sa = smooth(inv(2.9, 3.6, t)); + if (sa > 0) { + g2.save(); + g2.globalAlpha = sa; + g2.font = `${46 * u}px ${FONT_KAI}`; + g2.fillStyle = rgba(INK, 0.85); + g2.textAlign = "center"; + g2.textBaseline = "middle"; + const sy = portrait ? cy + fs * 1.95 : cy + fs * 0.95; + g2.fillText("\u5B89 \u5F92 \u751F \u7AE5 \u8BDD", cx, sy); + brush(g2, [[cx - 170 * u, sy + 38 * u], [cx, sy + 44 * u], [cx + 170 * u, sy + 36 * u]], 4 * u, { alpha: 0.5 * sa, seed: 8, dry: 0.6 }); + g2.restore(); + } + const sp = inv(4.55, 4.85, t); + if (sp > 0) { + const sc = lerp(1.5, 1, easeOut(sp)); + const sx = portrait ? cx + fs * 0.78 : cx + fs * 1.82, sy = portrait ? cy + fs * 1.15 : cy + fs * 0.32; + g2.save(); + g2.translate(sx, sy); + g2.scale(sc, sc); + g2.rotate(-0.04); + seal(g2, 0, 0, 92 * u, ["\u7AE5\u8BDD"], { alpha: Math.min(1, sp * 2) }); + g2.restore(); + } + const wy = portrait ? H2 * 0.74 : H2 * 0.86; + const wp = inv(2, 6.6, t); + if (wp > 0) { + const x0 = lerp(-120 * u, W2 * 0.62, wp); + bird(g2, { x: x0, y: wy, s: 70 * u, t, walk: 2.4, body: DUCKY, seed: 3 }); + bird(g2, { x: x0 - 120 * u, y: wy + 6 * u, s: 92 * u, t: t + 0.2, walk: 2, body: UGLY, beak: UGLY_BEAK, fluff: 1, sw: 0.1, seed: 9, neckUp: -0.2 }); + } + }, + enterAt: [0.5, 0.45] + }, + summer: { + label: "\u590F\u65E5\u4E61\u6751", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X, portrait } = s; + sky(g2, W2, gy, "#9fbccb", 0.2, "#ffffff", 0); + sun(g2, X(portrait ? -0.55 : -0.7), H2 * (portrait ? 0.13 : 0.17), 58 * u); + mountains(g2, W2, gy - 110 * u, 150 * u, "#7d8a8c", 0.32, 3); + mountains(g2, W2, gy - 40 * u, 110 * u, "#56645c", 0.42, 7); + band(g2, W2, gy - 70 * u, gy + 40 * u, "#d9b45a", 0.42, 21, 14 * u); + particles(260, 5, (r) => { + const x = r() * W2, y = gy - 60 * u + r() * 90 * u, h = (14 + r() * 26) * u; + brush(g2, [[x, y + h], [x + 2 * u, y + h * 0.5], [x + 4 * u, y]], 2.4 * u, { color: r() < 0.5 ? "#b88a2e" : "#d8a940", alpha: 0.55, seed: Math.floor(x), taperIn: 0.05 }); + }); + band(g2, W2, gy + 35 * u, H2, "#9fb07a", 0.3, 31); + const px = X(-0.15), py = gy + 190 * u, rx = Math.min(W2 * 0.45, 720 * u), ry = 120 * u; + wash(g2, blobLoop(px, py, 1, 4, 0.04, 26).map(([x, y]) => [px + (x - px) * rx, py + (y - py) * ry]), "#94adb5", 0.4, { edge: 1.5 }); + strokesH(g2, W2 * 0.9, py - ry * 0.6, py + ry * 0.7, 18, "#4f6670", 0.35, 41, u); + lotusLeaf(g2, px - rx * 0.6, py + 30 * u, 70 * u, 1); + lotusLeaf(g2, px - rx * 0.35, py + 70 * u, 55 * u, 2); + lotusLeaf(g2, px + rx * 0.1, py + 10 * u, 60 * u, 3); + blossom(g2, px - rx * 0.48, py - 30 * u, 22 * u, 4, "#e7a0b0"); + tree(g2, X(portrait ? -0.95 : -0.88), gy + 40 * u, 560 * u, 17, { + color: "#3a3128", + depth: 4, + leaves: (x, y, l) => { + blot(g2, x, y, l * 1.4, "#5f8a4c", 0.32); + blot(g2, x + l * 0.4, y - l * 0.3, l * 0.9, "#86a85e", 0.3); + } + }); + }, + draw(s) { + const { g: g2, W: W2, u, t, gy, X } = s; + const k = ks(s); + cloud(g2, X(-0.2) + t * 14 * u, gy - 430 * u, 420 * u, 2, 0.3); + cloud(g2, X(0.5) + t * 9 * u, gy - 520 * u, 300 * u, 5, 0.25); + const nx = X(0.55), ny = gy + 95 * u; + for (let i = 0; i < 12; i++) reed(g2, nx - 220 * u + i * 42 * u + hash(i) * 20 * u, ny - 10 * u + hash(i + 3) * 30 * u, (260 + hash(i * 7) * 180) * u, t, 70 + i); + nest(g2, nx, ny + 10 * u, 270 * k, 3); + egg(g2, nx + 95 * k, ny + 4 * u, 30 * k, { seed: 2 }); + bird(g2, { x: nx, y: ny - 8 * u, s: 230 * k, t, sw: 0.34, body: "#f7f2e6", swim: true, face: -1, seed: 5, neckUp: 0.15 + 0.15 * Math.sin(t * 0.8), look: 0.15 * Math.sin(t * 0.5), fluff: 0 }); + egg(g2, nx - 100 * k, ny + 12 * u, 28 * k, { seed: 4 }); + for (let i = 0; i < 5; i++) reed(g2, nx - 160 * u + i * 85 * u, ny + 60 * u, (150 + hash(i * 11) * 90) * u, t, 90 + i, { alpha: 0.85 }); + const dx = X(0.05) + 280 * u * Math.sin(t * 0.55), dy = gy - 140 * u + 60 * u * Math.sin(t * 1.3); + g2.save(); + g2.translate(dx, dy); + g2.rotate(0.15 * Math.sin(t)); + brush(g2, [[-28 * u, 0], [0, 0], [26 * u, 1 * u]], 6 * u, { color: "#b8322a", alpha: 0.9, taperIn: 0.05, taperOut: 0.9 }); + g2.fillStyle = rgba("#8aa0a8", 0.35); + for (const sgn of [-1, 1]) for (const off of [0, 9 * u]) { + g2.beginPath(); + g2.ellipse(2 * u - off, sgn * (13 * u + 3 * u * Math.sin(t * 40)), 7 * u, 16 * u, sgn * 0.3, 0, Math.PI * 2); + g2.fill(); + } + g2.restore(); + for (let b = 0; b < 2; b++) { + const bx = X(-0.5 + b * 0.35) + 120 * u * Math.sin(t * 0.7 + b * 2), by = gy - 60 * u + 50 * u * Math.sin(t * 1.1 + b); + const f = Math.abs(Math.sin(t * 9 + b)); + g2.fillStyle = rgba(b ? "#e0a040" : "#f1f0ea", 0.85); + for (const sgn of [-1, 1]) { + g2.beginPath(); + g2.ellipse(bx + sgn * 9 * u * f, by, 9 * u * f + 1, 12 * u, sgn * 0.4, 0, Math.PI * 2); + g2.fill(); + } + g2.strokeStyle = rgba(INK, 0.6); + g2.lineWidth = 1.5 * u; + for (const sgn of [-1, 1]) { + g2.beginPath(); + g2.ellipse(bx + sgn * 9 * u * f, by, 9 * u * f + 1, 12 * u, sgn * 0.4, 0, Math.PI * 2); + g2.stroke(); + } + } + void W2; + }, + cam(s) { + const p = easeInOut(inv(0, s.D, s.t)); + return cam(s, lerp(1, 1.4, p), lerp(s.W / 2, s.X(0.52), p), lerp(s.H / 2, s.gy + 20 * s.u, p)); + }, + enterAt: [0.5, 0.4] + }, + hatch: { + label: "\u7834\u58F3", + bg: bgNestSide, + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks(s); + const [nx, ny] = nestPos(s); + bird(g2, { x: X(-0.6), y: ny + 60 * u, s: 270 * k, t, sw: 0.34, body: "#f7f2e6", face: 1, seed: 5, neckUp: -0.25 + 0.1 * Math.sin(t * 0.7), fluff: 0 }); + nest(g2, nx, ny + 10 * u, 470 * k, 8); + const eggs = [[-150, -6], [-70, 8], [110, 10], [180, -6]]; + egg(g2, nx + 20 * k, ny - 2 * u, 62 * k, { color: "#e6e3d6", seed: 99, wobble: t > s.D - 2.5 ? 0.04 * Math.sin(t * 13) : 0 }); + const targets = [[X(-0.38), ny + 150 * u], [X(-0.2), ny + 175 * u], [X(0.32), ny + 165 * u], [X(0.5), ny + 140 * u]]; + eggs.forEach(([ex, ey], i) => { + const x = nx + ex * k, y = ny + ey * u; + const tc = 0.4 + i * 0.75; + const crack = inv(tc, tc + 0.35, t); + const open = easeOut(inv(tc + 0.55, tc + 1.05, t)); + egg(g2, x, y, 40 * k, { crack, open, seed: 10 + i, wobble: crack > 0 && open === 0 ? 0.08 * Math.sin(t * 30) : 0 }); + if (open > 0) { + const em = spring(inv(tc + 0.55, tc + 1.3, t)); + const hop = inv(tc + 2, tc + 3, t); + const [tx, ty] = targets[i]; + const bx = lerp(x, tx, easeInOut(hop)), by = lerp(y + 10 * u, ty, easeInOut(hop)) - Math.sin(Math.PI * hop) * 70 * u; + const peep = (t - tc) % 2.3; + bird(g2, { + x: bx, + y: by + (1 - em) * 40 * u, + s: 88 * k * lerp(0.6, 1, em), + t, + body: DUCKY, + seed: 20 + i, + face: i < 2 ? -1 : 1, + walk: hop > 0 && hop < 1 ? 3 : 0, + mouth: peep > 1 && peep < 1.25 ? 1 : 0, + squash: (1 - em) * 0.2, + neckUp: 0.15, + legs: hop > 0 + }); + if (hop >= 1 && peep > 1 && peep < 1.5) soundMarks(g2, bx + (i < 2 ? -1 : 1) * 40 * u, by - 95 * u, 40 * u, 1 - (peep - 1) / 0.5, i, i < 2 ? -1 : 1); + } + }); + }, + cam(s) { + const [nx, ny] = nestPos(s); + return cam(s, lerp(1.3, 1.42, easeInOut(s.t / s.D)), nx - 20 * s.u, ny + 30 * s.u); + }, + enterAt: [0.55, 0.65] + }, + bigegg: { + label: "\u6700\u5927\u7684\u86CB", + bg: bgNestSide, + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks(s); + const [nx, ny] = nestPos(s); + const C = CUE.bigegg; + const surprise = smooth(inv(C.surprise, C.surprise + 0.3, t)) * (1 - smooth(inv(C.surprise + 2.2, C.surprise + 3.2, t))); + bird(g2, { x: X(-0.62), y: ny + 60 * u, s: 270 * k, t, sw: 0.34, body: "#f7f2e6", face: 1, seed: 5, neckUp: lerp(-0.2, 0.75, surprise), fluff: 0 }); + nest(g2, nx, ny + 10 * u, 470 * k, 8); + [[-150, -6], [-70, 8], [110, 10], [180, -6]].forEach(([ex2, ey2], i) => egg(g2, nx + ex2 * k, ny + ey2 * u, 40 * k, { open: 1, seed: 10 + i })); + const amp = 0.12 * smooth(inv(0.3, C.crackFrom, t)); + const wob = Math.sin(t * 14) * amp; + const ex = nx + 20 * k, ey = ny - 2 * u; + const crack = inv(C.crackFrom, C.open - 0.05, t), open = easeOut(inv(C.open, C.open + 0.5, t)); + egg(g2, ex, ey - (open ? 0 : Math.abs(Math.sin(t * 7)) * 8 * u * amp * 6), 62 * k, { color: "#e6e3d6", crack, open, seed: 99, wobble: open ? 0 : wob }); + const back = inv(C.walk, C.walk + 2.6, t); + if (open > 0) { + const em = spring(inv(C.open + 0.1, C.open + 1, t)); + const ux = lerp(ex, X(-0.02), easeInOut(back)), uy = lerp(ey + 8 * u, ny + 175 * u, easeInOut(back)) - Math.sin(Math.PI * clamp(back * 3)) * 40 * u; + bird(g2, { + x: ux, + y: uy + (1 - em) * 60 * u, + s: 132 * k * lerp(0.7, 1, em), + t, + sw: 0.12, + body: UGLY, + beak: UGLY_BEAK, + fluff: 1.1, + seed: 7, + face: -1, + squash: (1 - em) * 0.25, + legs: back > 0, + walk: back > 0 && back < 1 ? 1.6 : 0, + tilt: t > C.question && t < C.walk ? 0.18 * Math.sin((t - C.question) * 2) : 0, + neckUp: t > C.question && t < C.walk ? 0.2 : 0 + }); + question(g2, ux + 10 * u, uy - 230 * u * k, 90 * u, smooth(inv(C.question, C.question + 0.3, t)) * (1 - smooth(inv(C.walk - 0.4, C.walk - 0.1, t)))); + } + for (let i = 0; i < 4; i++) { + const recoil = surprise * 0.3 + easeInOut(back) * 0.5; + const x = X(-0.42) + i * 70 * u - recoil * 60 * u; + const y = ny + 150 * u + i % 2 * 22 * u; + bird(g2, { x, y, s: 86 * k, t: t + i * 0.3, body: DUCKY, seed: 30 + i, face: 1, neckUp: 0.25 - surprise * 0.2, tilt: -0.2 * surprise, walk: back > 0 && back < 1 ? 1.2 : 0 }); + exclaim(g2, x + 10 * u, y - 150 * u, 46 * u, smooth(inv(C.surprise, C.surprise + 0.15, t)) * (1 - smooth(inv(C.surprise + 1.4, C.surprise + 1.8, t))), i); + } + }, + cam(s) { + const [nx, ny] = nestPos(s); + const zin = easeInOut(inv(0, CUE.bigegg.open + 0.6, s.t)), zout = easeInOut(inv(CUE.bigegg.walk - 0.4, CUE.bigegg.walk + 2.4, s.t)); + return cam(s, lerp(lerp(1.35, 1.9, zin), 1.35, zout), lerp(nx + 20 * s.u, s.X(-0.18), zout), ny + lerp(-30, 70, zout) * s.u); + }, + enterAt: [0.55, 0.62] + }, + bullied: { + label: "\u88AB\u5632\u7B11", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#c9c2b0", 0.18, "#ffffff", 0); + const rx = X(0.78), ry = gy - 360 * u; + wash(g2, [[rx - 260 * u, ry + 40 * u], [rx + 400 * u, ry - 40 * u], [rx + 420 * u, ry + 70 * u], [rx - 240 * u, ry + 120 * u]], "#4a4a4f", 0.6); + for (let i = 0; i < 12; i++) brush(g2, [[rx - 240 * u + i * 55 * u, ry + 115 * u - i * 6 * u], [rx - 250 * u + i * 55 * u, ry + 40 * u - i * 6 * u]], 4 * u, { alpha: 0.5, seed: i }); + wash(g2, [[rx - 200 * u, ry + 115 * u], [rx + 400 * u, ry + 65 * u], [rx + 400 * u, gy - 10 * u], [rx - 200 * u, gy - 10 * u]], "#b3a48a", 0.4); + const hx = X(-0.78), hy = gy - 10 * u; + wash(g2, [[hx - 150 * u, hy], [hx - 110 * u, hy - 150 * u], [hx, hy - 210 * u], [hx + 110 * u, hy - 150 * u], [hx + 150 * u, hy]], "#d6b060", 0.55); + particles(60, 12, (r) => { + const a = r() * Math.PI - Math.PI, rr = r() * 140 * u; + const x = hx + Math.cos(a) * rr, y = hy - 20 * u + Math.sin(a) * rr; + brush(g2, [[x, y], [x + 18 * u * (r() - 0.5), y - 25 * u]], 2 * u, { color: "#8a6a2a", alpha: 0.6 }); + }); + band(g2, W2, gy - 20 * u, H2, "#c9b48a", 0.32, 51); + fence(g2, -20 * u, W2 + 20 * u, gy + 10 * u, 160 * u, 5); + strokesH(g2, W2, gy + 20 * u, H2, 34, "#8a7450", 0.3, 13, u); + }, + draw(s) { + const { g: g2, u, t, gy, X } = s; + const k = ks(s); + const C = CUE.bullied; + const laugh = t > C.laugh1[0] && t < C.laugh1[1] || t > C.laugh2[0] && t < C.laugh2[1]; + const ga = inv(C.girlIn, C.girlIn + 0.9, t); + if (ga > 0) child(g2, lerp(X(-1.15), X(-0.78), easeOut(ga)), gy + 200 * u, 360 * k, t, { face: 1, pose: t > C.shoo ? "shoo" : "basket", dress: "#b8483e", seed: 4 }); + for (let i = 0; i < 3; i++) { + const x = X(-0.52) + i * 85 * u, y = gy + 150 * u + i % 2 * 20 * u; + const b = laugh ? Math.abs(Math.sin(t * 9 + i)) : 0; + bird(g2, { x, y, s: 92 * k, t: t + i, body: DUCKY, seed: 40 + i, face: 1, mouth: laugh ? 0.4 + 0.6 * b : 0, squash: 0.06 * b, tilt: laugh ? -0.15 : 0, neckUp: laugh ? 0.3 : 0 }); + if (laugh) soundMarks(g2, x + 50 * u, y - 120 * u, 36 * u, 0.8, i); + } + const ra = inv(C.roosterIn, C.roosterIn + 0.6, t); + const pecks = C.pecks; + let peck = 0, pushed = 0; + for (const p of pecks) { + const q2 = inv(p - 0.12, p + 0.12, t); + peck = Math.max(peck, Math.sin(Math.PI * q2)); + if (t > p) pushed += 1; + } + const ux = X(0.1) - pushed * 30 * u; + const cower = t > C.shoo ? 0.2 : 0.08; + bird(g2, { x: ux, y: gy + 170 * u, s: 140 * k, t, sw: 0.16, body: UGLY, beak: UGLY_BEAK, fluff: 1.1, seed: 7, face: t > C.girlIn ? -1 : 1, neckUp: -0.45, shiver: t > C.pecks[0] ? 1 : 0.4, squash: cower }); + tear(g2, ux + (t > C.girlIn ? -1 : 1) * 40 * u, gy + 60 * u + t * 30 % 30 * u, 9 * u, smooth(inv(1.2, 1.5, t)) * (1 - smooth(inv(3.4, 3.7, t)))); + tear(g2, ux + (t > C.girlIn ? -1 : 1) * 40 * u, gy + 60 * u + t * 30 % 30 * u, 9 * u, smooth(inv(7.6, 7.9, t)) * (1 - smooth(inv(9.6, 10, t)))); + if (ra > 0) { + const rx = lerp(X(1.3), X(0.42), easeOut(ra)) - (t > C.pecks[3] + 0.3 ? (t - C.pecks[3] - 0.3) * 20 * u : 0); + rooster(g2, rx, gy + 175 * u, 240 * k, t, { face: -1, peck, flap: t > C.roosterIn + 0.6 && t < C.pecks[3] + 0.5 ? t * 18 : 0 }); + } + pecks.forEach((p, j) => { + const q2 = t - p; + if (q2 < 0 || q2 > 2) return; + for (let m = 0; m < 3; m++) { + const x = ux + 30 * u + (m - 1) * 30 * u + Math.sin(q2 * 4 + m) * 20 * u; + const y = gy + 40 * u - 80 * u * easeOut(q2 * 2) + q2 * q2 * 40 * u; + leaf(g2, x, y, 10 * u, q2 * 3 + m + j, "#9a968e", 0.8 * (1 - q2 / 2)); + } + }); + }, + cam(s) { + return cam(s, lerp(1.28, 1.36, easeInOut(s.t / s.D)), s.X(-0.08), s.gy + 110 * s.u); + }, + enterAt: [0.3, 0.6] + }, + leave: { + label: "\u79BB\u5BB6", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#b9a48a", 0.2, "#ffffff", 0); + mountains(g2, W2, gy - 70 * u, 140 * u, "#8a8478", 0.3, 23); + mountains(g2, W2, gy - 20 * u, 90 * u, "#6a665e", 0.35, 29); + band(g2, W2, gy - 20 * u, H2, "#b8ad8a", 0.28, 61); + const path = [[X(-0.75), H2 + 20 * u], [X(-0.42), gy + 170 * u], [X(-0.05), gy + 100 * u], [X(0.35), gy + 30 * u], [X(0.68), gy - 25 * u]]; + const pts = spline(path, 10); + for (let i = 0; i < pts.length - 1; i++) { + const f = i / pts.length; + brush(g2, [pts[i], pts[i + 1]], lerp(150, 16, f) * u, { color: "#d8c49a", alpha: 0.55, taperIn: 0, taperOut: 0, jitter: 0.1, seed: i }); + } + tree(g2, X(0.8), gy - 10 * u, 380 * u, 33, { depth: 4, leaves: (x, y, l) => blot(g2, x, y, l * 0.8, "#8a7a50", 0.25) }); + fence(g2, -20 * u, X(-0.12), gy + 120 * u, 170 * u, 8, X(-0.42), 120 * u); + strokesH(g2, W2, gy + 30 * u, H2, 30, "#7a6a48", 0.28, 15, u); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X, D: D2 } = s; + const p = Math.pow(inv(CUE.leave.walk, D2 + 1.2, t), 1.15); + const path = spline([[X(-0.62), gy + 200 * u], [X(-0.42), gy + 175 * u], [X(-0.05), gy + 100 * u], [X(0.35), gy + 32 * u], [X(0.62), gy - 20 * u]], 12); + const idx = Math.min(path.length - 1, Math.floor(p * (path.length - 1))); + const [bx, by] = path[idx]; + const sc = lerp(150, 52, p) * ks(s); + bird(g2, { x: bx, y: by, s: sc, t, sw: 0.18, body: UGLY, beak: UGLY_BEAK, fluff: 1, seed: 7, face: 1, walk: t > CUE.leave.walk ? 1.7 : 0, neckUp: -0.35, shiver: t < CUE.leave.walk ? 0.4 : 0 }); + tear(g2, bx + 45 * u * (sc / 150), by - 95 * u * (sc / 150) + t * 25 % 25 * u, 9 * u, smooth(inv(0.3, 0.6, t)) * (1 - smooth(inv(2.2, 2.6, t)))); + for (let i = 0; i < 14; i++) grass(g2, i / 14 * W2 + hash(i) * 50 * u, H2 - hash(i * 3) * 120 * u, 70 * u, t, 200 + i, "#5d6a44", 0.6, 2); + fallingThings(g2, W2, H2, t, 14, 8, u, (x, y, rot2, r) => leaf(g2, x, y * 0.8, (8 + r() * 6) * u, rot2, r() < 0.5 ? "#b08a4a" : "#8a7a50", 0.75), 50); + }, + cam(s) { + return cam(s, lerp(1.08, 1.16, s.t / s.D), s.W / 2, s.gy + 60 * s.u); + }, + enterAt: [0.25, 0.7] + }, + marsh: { + label: "\u6CBC\u6CFD", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy } = s; + sky(g2, W2, gy, "#7e878a", 0.25, "#ffffff", 0.02); + mountains(g2, W2, gy - 50 * u, 100 * u, "#6e7672", 0.3, 37, { soft: 1 }); + for (let i = 0; i < 46; i++) reed(g2, i / 46 * W2 + hash(i) * 30 * u, gy - 10 * u, (90 + hash(i * 3) * 90) * u, 0, 500 + i, { color: "#5d6658", alpha: 0.4, wind: 0.2 }); + band(g2, W2, gy - 15 * u, H2, "#8fa3a6", 0.32, 71, 6 * u); + particles(8, 19, (r) => blot(g2, r() * W2, gy + r() * (H2 - gy), (80 + r() * 160) * u, "#5d6a62", 0.12, 2.2, 0.4)); + strokesH(g2, W2, gy + 20 * u, H2, 36, "#3f4c52", 0.35, 17, u); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X } = s; + const k = ks(s); + const M = CUE.marsh; + water(g2, 0, W2, gy + 10 * u, H2 - gy, t, 3, { alpha: 0.3, lines: 12 }); + for (let i = 0; i < 12; i++) reed(g2, X(0.62) + i * 38 * u, gy + 40 * u + hash(i) * 30 * u, (280 + hash(i * 5) * 160) * u, t, 120 + i, { wind: t > M.shots[0] ? 1 + 2 * Math.exp(-(t - M.shots[0]) * 2) : 1 }); + for (const [ts, fx, fy] of [[M.shots[0], 0.86, -130], [M.shots[1], 0.72, -170]]) { + const q2 = t - ts; + if (q2 > 0 && q2 < 0.09) { + g2.fillStyle = rgba("#e0a040", 0.9); + g2.beginPath(); + g2.arc(X(fx), gy + fy * u, 26 * u, 0, Math.PI * 2); + g2.fill(); + } + if (q2 > 0) smoke(g2, X(fx) - q2 * 30 * u, gy + fy * u - q2 * 25 * u, (30 + 110 * easeOut(q2 / 2)) * u, 0.6 * Math.max(0, 1 - q2 / 4), Math.floor(ts * 10)); + } + for (let i = 0; i < 3; i++) { + const takeoff = easeIn(inv(M.scatter + i * 0.12, M.scatter + 2.1 + i * 0.12, t)); + const bx = X(0.2 + i * 0.16) + takeoff * 380 * u, by = gy + (110 + i * 25) * u - takeoff * (gy + 300 * u); + const laugh = t > M.laugh[0] && t < M.laugh[1]; + const o = { x: bx, y: by, s: 140 * k, t: t + i, sw: 0.32, body: "#9b7b55", head: "#3d6b4a", beak: "#d8b03a", seed: 60 + i, face: takeoff > 0 ? 1 : -1, mouth: laugh ? 0.5 + 0.5 * Math.abs(Math.sin(t * 9 + i)) : 0, fluff: 0 }; + if (takeoff > 0) bird(g2, { ...o, legs: false, wing: 1, flap: t * 16 + i, neckUp: -0.7 }); + else { + swimBird(g2, o); + if (laugh) soundMarks(g2, bx - 70 * u, by - 150 * u, 34 * u, 0.8, i, -1); + } + } + particles(7, 33, (r, i) => { + const q2 = t - M.scatter + 0.1 - r() * 0.4; + if (q2 <= 0) return; + farBird(g2, X(0.8) - q2 * (120 + r() * 120) * u, gy - 120 * u - q2 * (150 + r() * 120) * u, (18 + r() * 12) * u, t * 14 + i); + }); + const hide = easeInOut(inv(M.hide, M.hide + 1.4, t)); + const startle = Math.exp(-Math.max(0, t - M.shots[0]) * 5) * (t > M.shots[0] ? 1 : 0); + const ux = lerp(X(-0.08), X(-0.55), hide), uy = gy + 175 * u; + swimBird(g2, { x: ux, y: uy, s: 150 * k, t, sw: 0.24, body: UGLY, beak: UGLY_BEAK, fluff: 1, seed: 7, face: hide > 0 && hide < 1 ? -1 : 1, neckUp: lerp(0, -0.6, hide), tuck: 0.3 * hide, shiver: t > M.shots[0] ? 1 : 0, squash: startle * 0.2 }); + exclaim(g2, ux + 20 * u, uy - 220 * u, 50 * u, startle > 0.05 ? 1 : 0, 3); + for (let i = 0; i < 9; i++) reed(g2, X(-0.78) + i * 40 * u + hash(i * 9) * 20 * u, H2 + 20 * u, (420 + hash(i * 13) * 220) * u, t, 150 + i, { color: "#2f3b2c", alpha: 0.85, wind: t > M.shots[0] ? 1 + 1.5 * Math.exp(-(t - M.shots[0])) : 1 }); + }, + cam(s) { + const { t, u } = s; + let dx = 0; + for (const ts of CUE.marsh.shots) { + const q2 = t - ts; + if (q2 > 0) dx += Math.sin(q2 * 90) * 10 * u * Math.exp(-q2 * 9); + } + const p = easeInOut(inv(9.2, s.D, t)); + return cam(s, lerp(1.15, 1.55, p), lerp(s.X(0.1), s.X(-0.52), p), s.gy + lerp(90, 120, p) * u, { dx, dy: dx * 0.5 }); + }, + enterAt: [0.5, 0.6] + }, + autumn: { + label: "\u79CB\u5929\u7684\u5927\u9E1F", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#e3a868", 0.12, "#e8b878", 0.38); + sun(g2, X(0.3), gy - 150 * u, 72 * u, "#c8463a", 0.85); + mountains(g2, W2, gy - 40 * u, 170 * u, "#8a7a6a", 0.32, 41); + mountains(g2, W2, gy, 100 * u, "#5e5248", 0.45, 47); + band(g2, W2, gy, gy + 190 * u, "#b8a990", 0.32, 81, 4 * u); + particles(9, 3, (r) => { + const y = gy + 20 * u + r() * 120 * u, w = (80 - (y - gy) / u * 0.3) * u; + brush(g2, [[X(0.3) - w, y], [X(0.3), y + 2 * u], [X(0.3) + w, y]], 5 * u, { color: "#c8463a", alpha: 0.35, dry: 0.6, seed: Math.floor(y) }); + }); + band(g2, W2, gy + 185 * u, H2, "#b8925a", 0.32, 83); + strokesH(g2, W2, gy + 200 * u, H2, 30, "#7a5a30", 0.3, 19, u); + tree(g2, X(-0.88), gy + 260 * u, 640 * u, 9, { + depth: 5, + color: "#3a2a20", + leaves: (x, y, l) => { + blot(g2, x, y, l * 1.3, "#c0582e", 0.35); + blot(g2, x + l * 0.5, y - l * 0.2, l * 0.9, "#e0a040", 0.3); + } + }); + for (let i = 0; i < 10; i++) reed(g2, X(0.7) + i * 30 * u, gy + 200 * u, (180 + hash(i) * 120) * u, 0, 600 + i, { color: "#8a6a3a", alpha: 0.7 }); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X, portrait } = s; + const k = ks(s); + fallingThings(g2, W2, H2, t, 22, 12, u, (x, y, rot2, r) => leaf(g2, x * 0.55, y, (9 + r() * 7) * u, rot2, ["#c0582e", "#d98a3a", "#b8402a", "#e0a040"][Math.floor(r() * 4)], 0.85), 55); + const sp = (W2 + 1400 * u) / 11.5; + let lead = 0; + for (let i = 0; i < 5; i++) { + const x = W2 + 160 * u + i * 170 * u - (t - CUE.autumn.swans) * sp; + const y = H2 * (portrait ? 0.2 : 0.17) + i * (portrait ? 70 : 52) * u + 10 * u * Math.sin(t * 1.3 + i); + if (i === 0) lead = x; + if (x < -300 * u || x > W2 + 300 * u) continue; + bird(g2, { x, y, s: 105 * k, t, sw: 1, body: WHITE, beak: SWAN_BEAK, legs: false, wing: 1, flap: t * 4.2 + i * 0.9, face: -1, neckUp: -0.85, seed: 80 + i }); + } + const lookUp = smooth(inv(CUE.autumn.lookUp, CUE.autumn.lookUp + 1, t)); + const turn = lead < X(0.42); + const walkP = inv(CUE.autumn.walk, CUE.autumn.walk + 2.2, t); + const ux = X(0.42) - easeInOut(walkP) * 110 * u; + bird(g2, { x: ux, y: gy + 230 * u, s: 168 * k, t, sw: 0.42, body: UGLY, beak: UGLY_BEAK, fluff: 0.7, seed: 7, face: turn ? -1 : 1, neckUp: lookUp * 0.95, walk: walkP > 0 && walkP < 1 ? 1.4 : 0 }); + }, + cam(s) { + return cam(s, lerp(1.04, 1.12, easeInOut(s.t / s.D)), lerp(s.X(0.15), s.X(0.05), easeInOut(s.t / s.D)), s.H * 0.48); + }, + enterAt: [0.8, 0.3] + }, + winter: { + label: "\u5BD2\u51AC", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#5f6a74", 0.35, "#c9d0d4", 0.1); + mountains(g2, W2, gy - 30 * u, 160 * u, "#5a6268", 0.3, 53); + particles(10, 5, (r) => blot(g2, r() * W2, gy - 120 * u - r() * 60 * u, (50 + r() * 60) * u, "#ffffff", 0.45, 2, 0.5)); + band(g2, W2, gy - 10 * u, gy + 300 * u, "#c9d4d8", 0.5, 91, 5 * u); + particles(10, 77, (r, i) => { + const x = r() * W2, y = gy + 20 * u + r() * 250 * u; + brush(g2, [[x, y], [x + (60 + r() * 120) * u, y + (r() - 0.5) * 20 * u]], 1.6 * u, { color: "#6a7a86", alpha: 0.35, seed: i }); + }); + band(g2, W2, gy + 290 * u, H2, "#ffffff", 0.6, 93, 14 * u); + for (const [fx, h, sd] of [[-0.86, 520, 3], [0.88, 460, 7]]) { + tree(g2, X(fx), gy + 40 * u, h * u, sd, { depth: 5, color: "#2a2622", leaves: (x, y, l) => blot(g2, x, y - 3 * u, l * 0.35, "#ffffff", 0.85, 1.6, 0.6) }); + } + for (let i = 0; i < 12; i++) reed(g2, X(-0.5) + i * 26 * u, gy + 290 * u, (140 + hash(i) * 90) * u, 0, 700 + i, { color: "#6a6458", alpha: 0.65, wind: 0.6 }); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X } = s; + const k = ks(s); + const night = 0.34 * smooth(inv(4, 6.5, t)) - 0.24 * smooth(inv(8.2, 9, t)) + 0.24 * smooth(inv(9.6, 10.6, t)) - 0.3 * smooth(inv(11.8, 13.2, t)); + if (night > 0) { + g2.fillStyle = rgba("#26303c", night); + g2.fillRect(-W2, -H2, W2 * 3, H2 * 3); + blot(g2, X(0.6), H2 * 0.16, 70 * u, "#fffbe8", night * 2.2); + g2.fillStyle = rgba("#fffbe8", night * 2.4); + g2.beginPath(); + g2.arc(X(0.6), H2 * 0.16, 30 * u, 0, Math.PI * 2); + g2.fill(); + } + const ux = X(-0.04), uy = gy + 150 * u; + for (let i = 0; i < 6; i++) { + const a = i / 6 * Math.PI * 2 + 0.4, L = (150 + hash(i) * 160) * u; + const pts = [[ux + Math.cos(a) * 60 * u, uy + 10 * u + Math.sin(a) * 15 * u]]; + for (let j = 1; j < 5; j++) pts.push([pts[0][0] + Math.cos(a + nz(j, i) * 0.4) * L * j / 4, pts[0][1] + Math.sin(a + nz(j, i) * 0.4) * L * j / 4 * 0.25]); + brush(g2, pts, 2.2 * u, { color: "#3e4c58", alpha: 0.75, grow: easeOut(inv(CUE.winter.ice + i * 0.05, CUE.winter.ice + 0.7 + i * 0.05, t)), taperIn: 0.05, seed: i }); + } + bird(g2, { x: ux, y: uy, s: 175 * k, t, sw: 0.5, body: UGLY, beak: UGLY_BEAK, fluff: 0.8, seed: 7, face: 1, swim: true, tuck: 1, shiver: 1, snow: smooth(inv(0, s.D, t)), noBlink: true }); + for (let i = 0; i < 4; i++) { + const q2 = (t * 0.45 + i * 0.27) % 1; + const y = H2 * (0.2 + 0.18 * i); + brush(g2, [[lerp(-300, W2 + 300, q2) * 1, y], [lerp(-300, W2 + 300, q2) + 180 * u, y + 6 * u], [lerp(-300, W2 + 300, q2) + 360 * u, y]], 3 * u, { color: "#d8dde0", alpha: 0.5, dry: 0.9, seed: i }); + } + fallingThings(g2, W2, H2, t, 170, 31, u, (x, y, _r, r) => { + const rr = (2 + r() * 5) * u; + g2.fillStyle = rgba("#ffffff", 0.92); + g2.beginPath(); + g2.arc(x, y, rr, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba("#8a96a0", 0.35); + g2.lineWidth = 0.8 * u; + g2.stroke(); + }, 95); + }, + cam(s) { + return cam(s, lerp(1.2, 1.5, easeInOut(s.t / s.D)), s.X(-0.04), s.gy + 70 * s.u); + }, + enterAt: [0.5, 0.2] + }, + spring: { + label: "\u6625\u56DE", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#bcd3c8", 0.2, "#ffffff", 0); + sun(g2, X(0.5), H2 * 0.15, 50 * u, "#d65a40", 0.7); + mountains(g2, W2, gy - 40 * u, 150 * u, "#86a87a", 0.35, 61); + mountains(g2, W2, gy, 90 * u, "#5e8a50", 0.4, 67); + band(g2, W2, gy - 5 * u, H2, "#a9c48a", 0.35, 101); + const pts = spline([[X(-1.1), gy + 120 * u], [X(-0.3), gy + 60 * u], [X(0.3), gy + 160 * u], [X(1.1), gy + 90 * u]], 12); + brush(g2, pts, 70 * u, { color: "#9fc0c8", alpha: 0.55, taperIn: 0, taperOut: 0, jitter: 0.2 }); + brush(g2, pts.map(([x, y]) => [x, y - 10 * u]), 6 * u, { color: "#5e7a86", alpha: 0.3, dry: 0.7, taperIn: 0, taperOut: 0 }); + strokesH(g2, W2, gy + 200 * u, H2, 26, "#5e8a50", 0.3, 23, u); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X } = s; + const k = ks(s); + const C = CUE.spring; + willow(g2, X(-0.9), gy + 220 * u, 620 * u, t, 3, "#8fb86b", 0.15 + 0.85 * smooth(inv(C.buds, C.buds + 1.4, t))); + const bloom = smooth(inv(C.bloom, C.bloom + 1.3, t)); + tree(g2, X(0.86), gy + 220 * u, 520 * u, 21, { depth: 4, color: "#3a2c26", t, leaves: (x, y, l) => { + if (bloom > 0) { + blossom(g2, x, y, l * 0.28 * bloom, Math.floor(x), "#ec9aa8", 0.75); + blossom(g2, x + l * 0.3, y - l * 0.2, l * 0.2 * bloom, Math.floor(y), "#f4b8c0", 0.7); + } else blot(g2, x, y, l * 0.12, "#b86a74", 0.5); + } }); + for (let i = 0; i < 2; i++) farBird(g2, X(-0.2 + i * 0.4) + Math.sin(t * 0.5 + i) * 100 * u, H2 * 0.25 + i * 40 * u, 20 * u, t * 9 + i, "#3a3a3a"); + const spread = easeOut(inv(C.spread, C.spread + 0.9, t)); + const lift = easeIn(inv(C.lift, C.lift + 4.2, t)); + const fx = X(-0.12) + lift * W2 * 0.55, fy = gy + 170 * u - lift * (gy + 260 * u); + const flapRate = t < C.flapHard ? 2.6 : t < C.lift ? lerp(3, 6, inv(C.flapHard, C.lift, t)) : 4.6; + bird(g2, { + x: fx, + y: fy, + s: 205 * k, + t, + sw: 0.8, + body: "#b5b1a9", + beak: "#b88a5c", + fluff: 0.2, + seed: 7, + face: 1, + wing: spread, + flap: t * flapRate * 1 + (t > 3.4 ? Math.sin(t) : 0), + legs: lift < 0.05, + neckUp: lift > 0 ? -0.6 * smooth(lift * 4) : lerp(0, 0.3, spread), + look: t < C.spread ? 0.2 * Math.sin(t * 1.5) : 0 + }); + question(g2, fx + 30 * u, fy - 330 * u * k, 80 * u, smooth(inv(C.question, C.question + 0.25, t)) * (1 - smooth(inv(C.flapHard + 0.2, C.flapHard + 0.5, t)))); + if (t > C.lift && t < C.lift + 1.2) particles(8, 99, (r, i) => { + const q2 = t - C.lift; + leaf(g2, X(-0.12) + (r() - 0.5) * 200 * u, gy + 180 * u - q2 * 80 * u * r(), 8 * u, q2 * 4 + i, "#9fbf7a", 0.8 * (1 - q2 / 1.2)); + }); + fallingThings(g2, W2, H2, t, 26, 41, u, (x, y, rot2, r) => leaf(g2, x, y, (6 + r() * 5) * u, rot2, r() < 0.5 ? "#f0a8b4" : "#f6c8cf", 0.85), 45); + }, + cam(s) { + const lift = easeInOut(inv(CUE.spring.lift - 0.3, CUE.spring.lift + 3.5, s.t)); + return cam(s, lerp(1.3, 1.1, lift), lerp(s.X(-0.1), s.W / 2, lift), lerp(s.gy + 60 * s.u, s.H * 0.3, lift)); + }, + enterAt: [0.5, 0.5] + }, + reflection: { + label: "\u5012\u5F71", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#c9d8d8", 0.18, "#ffffff", 0); + mountains(g2, W2, gy - 60 * u, 120 * u, "#8fa69a", 0.28, 71, { soft: 1 }); + for (let i = 0; i < 7; i++) { + const x = X(-1 + i * 0.33) + hash(i) * 60 * u; + willow(g2, x, gy - 20 * u, (200 + hash(i * 3) * 80) * u, 0, 40 + i, "#8fb86b", 1); + } + band(g2, W2, gy - 25 * u, H2, "#a9bcc0", 0.32, 111, 3 * u); + strokesH(g2, W2, gy, H2, 30, "#5a6e78", 0.28, 29, u); + lotusLeaf(g2, X(-0.9), H2 - 90 * u, 120 * u, 7); + lotusLeaf(g2, X(-0.72), H2 - 30 * u, 90 * u, 8); + lotusLeaf(g2, X(0.92), H2 - 60 * u, 110 * u, 9); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X } = s; + const k = ks(s); + water(g2, 0, W2, gy, H2 - gy, t, 7, { alpha: 0.22, lines: 10, speed: 3 }); + const px = X(-0.16), py = gy + 175 * u, ps = 200 * k; + const R = CUE.reflection; + const reveal = smooth(inv(R.reveal, R.reveal + 1.6, t)); + const bow = smooth(inv(R.bow, R.bow + 1, t)) * (1 - smooth(inv(R.proud - 0.4, R.proud + 0.8, t))); + const proud = smooth(inv(R.proud, R.proud + 1.2, t)); + const land = inv(0, R.land, t); + const body = mixHex("#aaa69e", WHITE, reveal); + const o = { x: px, y: py, s: ps, t, sw: 1, body, beak: mixHex("#b88a5c", SWAN_BEAK, reveal), seed: 7, face: 1, neckUp: -0.95 * bow + 0.25 * proud, glow: reveal * (1 - smooth(inv(R.reveal + 2, R.reveal + 4, t))), noBlink: bow > 0.5 }; + const refl = (c2) => bird(c2, { ...o, body: WHITE, beak: SWAN_BEAK, swim: true, glow: 0 }); + if (land >= 1) { + const mirror = smooth(inv(R.mirror, R.mirror + 1.2, t)); + reflection(g2, refl, { x: px - ps * 1.3, y: py - ps * 1.4, w: ps * 2.8, h: ps * 1.46 }, py + ps * 0.06, t, lerp(0.3, 0.95, mirror), "#7a90a0"); + if (mirror > 0 && mirror < 1) blot(g2, px + 60 * u, py + 140 * u, 220 * u, "#fff4d8", 0.35 * Math.sin(Math.PI * mirror)); + if (reveal > 0 && reveal < 1) blot(g2, px + 30 * u, py - 60 * u, 320 * u, "#fff4d8", 0.55 * Math.sin(Math.PI * reveal)); + for (let r = 0; r < 3; r++) { + const q2 = inv(R.reveal + r * 0.5, R.reveal + 2.4 + r * 0.5, t); + if (q2 > 0 && q2 < 1) ripplesRing(g2, px + 70 * u, py + 60 * u, 60 * u + q2 * 420 * u, 0.4 * (1 - q2), "#9a7a3a"); + } + } + for (let i = 0; i < 3; i++) { + const q2 = easeOut(inv(1.2 + i * 0.4, 5.2 + i * 0.4, t)); + const sx = lerp(X(1.3 + i * 0.2), X(0.3 + i * 0.24), q2), sy = gy + (110 + i * 45) * u; + const greet = smooth(inv(R.proud + 0.4 + i * 0.2, R.proud + 1.2 + i * 0.2, t)); + swimBird(g2, swanBird(sx, sy, (150 - i * 12) * k, t + i, { face: -1, seed: 90 + i, neckUp: -0.55 * greet + 0.08 * Math.sin(t + i) })); + } + if (land < 1) { + const lx = lerp(X(-0.65), px, easeOut(land)), ly = lerp(H2 * 0.12, py, easeInOut(land)); + bird(g2, { ...o, x: lx, y: ly, legs: false, wing: 1 - land * 0.3, flap: t * 9, swim: true, neckUp: -0.5 }); + } else { + const settle = inv(R.land, R.land + 0.85, t); + swimBird(g2, { ...o, swim: true, wing: (1 - easeOut(settle)) * 0.7, flap: t * 6 }); + const q2 = t - R.land; + if (q2 < 1) particles(14, 5, (r) => { + const a = -Math.PI * r(), v = (100 + r() * 180) * u; + const x = px + Math.cos(a) * v * q2 * 1.6, y = py + Math.sin(a) * v * q2 * 1.4 + 300 * u * q2 * q2; + g2.fillStyle = rgba("#ffffff", 0.9 * (1 - q2)); + g2.beginPath(); + g2.arc(x, y, (3 + r() * 5) * u, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba("#6a7a86", 0.5 * (1 - q2)); + g2.lineWidth = u; + g2.stroke(); + }); + } + particles(10, 61, (r, i) => { + const x = (r() * W2 + t * 14 * u) % W2, y = gy + 40 * u + r() * (H2 - gy - 80 * u); + leaf(g2, x, y, 6 * u, t * 0.3 + i, "#f0a8b4", 0.75); + }); + }, + cam(s) { + const { t, u, gy } = s; + const zin = easeInOut(inv(CUE.reflection.bow - 0.2, CUE.reflection.mirror + 1.2, t)), zout = easeInOut(inv(CUE.reflection.reveal - 0.6, CUE.reflection.reveal + 2.4, t)); + return cam(s, lerp(lerp(1.25, 1.65, zin), 1.3, zout), lerp(s.X(-0.05), s.X(-0.1), zin), gy + lerp(lerp(60, 230, zin), 110, zout) * u); + }, + enterAt: [0.3, 0.3] + }, + accepted: { + label: "\u63A5\u7EB3", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#b8d0d8", 0.2, "#ffffff", 0); + mountains(g2, W2, gy - 50 * u, 110 * u, "#88a294", 0.28, 81, { soft: 1 }); + band(g2, W2, gy - 20 * u, H2, "#a3bfc6", 0.34, 121, 3 * u); + strokesH(g2, W2, gy, H2, 30, "#4f6670", 0.28, 33, u); + const bank = [[-50 * u, gy - 60 * u], [X(-0.55), gy - 20 * u], [X(-0.38), gy + 120 * u], [X(-0.5), gy + 260 * u], [-50 * u, gy + 300 * u]]; + wash(g2, bank, "#9fb878", 0.6); + for (let i = 0; i < 10; i++) grass(g2, X(-0.95) + i * 50 * u, gy + 100 * u + hash(i) * 120 * u, 50 * u, 0, 300 + i, "#4f6a3e", 0.6, 0.3); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, gy, X } = s; + const k = ks(s); + cloud(g2, X(0.3) + t * 10 * u, H2 * 0.14, 380 * u, 9, 0.22); + willow(g2, X(-0.88), gy + 60 * u, 560 * u, t, 8, "#8fb86b", 1); + const cheer = t > CUE.accepted.cheer; + [[-0.82, "#c0504a", true], [-0.66, "#4a6a9a", false], [-0.52, "#d0a040", true]].forEach(([fx, col, girl], i) => { + child(g2, X(fx), gy + 110 * u + i * 12 * u, 270 * k, t + i, { face: 1, dress: col, girl, pose: cheer ? i === 1 ? "shoo" : "clap" : "stand", seed: 10 + i }); + if (cheer) soundMarks(g2, X(fx) + 50 * u, gy - 200 * u * k, 40 * u, 0.8 * (1 - smooth(inv(10, 11, t))), i); + }); + const hx = X(0.62), hy = gy + 70 * u; + const heart = smooth(inv(CUE.accepted.heart, CUE.accepted.heart + 2, t)); + swimBird(g2, swanBird(hx - 70 * u, hy, 115 * k, t, { face: 1, seed: 95, neckUp: -0.55 * heart })); + swimBird(g2, swanBird(hx + 70 * u, hy, 115 * k, t + 1, { face: -1, seed: 96, neckUp: -0.55 * heart })); + const px = X(0.12), py = gy + 210 * u; + const nuzzle = (ph) => Math.max(0, Math.sin(t * 1.6 + ph)) * smooth(inv(0.5, 1.5, t)); + swimBird(g2, swanBird(px - 190 * u, py - 30 * u, 165 * k, t + 2, { face: 1, seed: 91, neckUp: -0.7 * nuzzle(0) })); + swimBird(g2, swanBird(px, py, 200 * k, t, { face: 1, seed: 7, neckUp: 0.2 + 0.1 * Math.sin(t), glow: 0.2 })); + swimBird(g2, swanBird(px + 220 * u, py - 20 * u, 165 * k, t + 3, { face: -1, seed: 92, neckUp: -0.7 * nuzzle(2) })); + particles(12, 71, (r, i) => { + const x = (r() * W2 + t * 18 * u) % W2, y = gy + 30 * u + r() * (H2 - gy - 60 * u); + leaf(g2, x, y, 6 * u, t * 0.4 + i, "#f0a8b4", 0.75); + }); + }, + cam(s) { + return cam(s, lerp(1.32, 1.18, easeInOut(s.t / s.D)), s.X(-0.12), s.gy + 110 * s.u); + }, + enterAt: [0.2, 0.5] + }, + fly: { + label: "\u9AD8\u98DE", + bg(s) { + const { g: g2, W: W2, H: H2, u, X, portrait } = s; + sky(g2, W2, H2, "#a8c4d4", 0.32, "#f2ead8", 0); + sun(g2, X(0.62), H2 * (portrait ? 0.14 : 0.18), 60 * u, "#d65a40", 0.75); + mountains(g2, W2, H2 * 0.92, 300 * u, "#7d8f8a", 0.35, 91); + mountains(g2, W2, H2 * 1.02, 260 * u, "#4f5d58", 0.5, 97); + }, + draw(s) { + const { g: g2, W: W2, H: H2, u, t, X, portrait } = s; + const k = ks(s); + particles(7, 21, (r, i) => { + const y = H2 * (0.15 + r() * 0.6), w = (260 + r() * 360) * u, sp2 = (90 + r() * 160) * u; + const x = ((r() * (W2 + w * 2) - t * sp2) % (W2 + w * 2) + W2 + w * 2) % (W2 + w * 2) - w; + cloud(g2, x, y, w, 30 + i, 0.25 + r() * 0.15); + }); + const enter = easeOut(inv(0, 2.2, t)); + const cx = lerp(-W2 * 0.4, X(portrait ? 0.05 : 0.1), enter) + Math.sin(t * 0.9) * 20 * u; + const cy = H2 * (portrait ? 0.42 : 0.44) + Math.sin(t * 1.2) * 18 * u; + const sp = portrait ? 0.62 : 1; + const flock2 = [[-250, 110, 0.78], [-470, 210, 0.66], [-230, -120, 0.74], [-440, -210, 0.6]]; + flock2.forEach(([dx, dy, sc], i) => bird(g2, { x: cx + dx * u * sp, y: cy + dy * u * sp, s: 230 * k * sc, t, sw: 1, body: WHITE, beak: SWAN_BEAK, legs: false, wing: 1, flap: t * 3.3 + i * 0.8, neckUp: -0.85, seed: 80 + i })); + bird(g2, { x: cx, y: cy, s: 240 * k, t, sw: 1, body: WHITE, beak: SWAN_BEAK, legs: false, wing: 1, flap: t * 3.2, neckUp: -0.85, seed: 7, glow: 0.3 }); + }, + cam(s) { + return { z: lerp(1, 1.08, s.t / s.D), x: s.W / 2, y: s.H / 2 }; + }, + enterAt: [0.1, 0.5] + }, + ending: { + label: "\u5BC4\u8BED", + bg(s) { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky(g2, W2, gy, "#e8a070", 0.3, "#f0c090", 0.35); + sun(g2, X(0.05), gy - 130 * u, 115 * u, "#c8463a", 0.9); + mountains(g2, W2, gy - 20 * u, 200 * u, "#6a5048", 0.42, 101); + mountains(g2, W2, gy + 10 * u, 120 * u, "#3a2c28", 0.6, 107); + band(g2, W2, gy + 5 * u, H2, "#d8a878", 0.3, 131, 3 * u); + particles(14, 9, (r) => { + const y = gy + 20 * u + r() * 200 * u, w = (130 - (y - gy) / u * 0.4) * u; + brush(g2, [[X(0.05) - w, y], [X(0.05), y + 2 * u], [X(0.05) + w, y]], 6 * u, { color: "#c8463a", alpha: 0.35, dry: 0.6, seed: Math.floor(y) }); + }); + for (let i = 0; i < 8; i++) reed(g2, X(-0.98) + i * 34 * u, H2 + 10 * u, (300 + hash(i) * 200) * u, 0, 800 + i, { color: "#2a2220", alpha: 0.85 }); + for (let i = 0; i < 6; i++) reed(g2, X(0.86) + i * 30 * u, H2 + 10 * u, (240 + hash(i + 9) * 160) * u, 0, 820 + i, { color: "#2a2220", alpha: 0.85 }); + }, + draw(s) { + const { g: g2, H: H2, u, t, gy, X, portrait } = s; + for (let i = 0; i < 5; i++) { + const q2 = inv(0, 10, t); + const x0 = X(-0.75 + i * 0.07), y0 = H2 * (0.3 + 0.03 * i); + const tx = X(0.05) + i * 12 * u, ty = gy - 160 * u - i * 8 * u; + const x = lerp(x0, tx, q2 * 0.85), y = lerp(y0, ty, q2 * 0.85); + farBird(g2, x, y, lerp(28, 9, q2) * u, t * 7 + i * 1.3, "#2a2220", 0.9); + } + const fs = (portrait ? 150 : 130) * u; + const vx = portrait ? X(0.62) : X(0.72), vy = portrait ? H2 * 0.3 : H2 * 0.36; + writeText(g2, "\u76F8\u4FE1\u81EA\u5DF1", vx, vy, fs, 1.4, 0.55, t, { vertical: true, gap: 1.02 }); + const E = CUE.ending; + const ea = smooth(inv(E.end - 0.3, E.end + 0.3, t)); + if (ea > 0) { + g2.save(); + g2.globalAlpha = ea; + g2.font = `${40 * u}px ${FONT_KAI}`; + g2.fillStyle = rgba(INK, 0.85); + g2.textAlign = "center"; + g2.save(); + g2.translate(vx - fs * 0.95, vy - fs * 1.45); + [..."\u4E11\u5C0F\u9E2D \xB7 \u5B8C"].forEach((c2, i) => g2.fillText(c2, 0, i * 46 * u)); + g2.restore(); + g2.restore(); + } + const sp = inv(E.end + 0.1, E.end + 0.35, t); + if (sp > 0) { + g2.save(); + g2.translate(vx - fs * 0.95, vy + fs * 1.2); + g2.scale(lerp(1.5, 1, easeOut(sp)), lerp(1.5, 1, easeOut(sp))); + seal(g2, 0, 0, 80 * u, ["\u7AE5\u8BDD"], { alpha: Math.min(1, sp * 2) }); + g2.restore(); + } + }, + cam(s) { + return { z: lerp(1, 1.05, s.t / s.D), x: s.W / 2, y: s.gy }; + }, + enterAt: [0.5, 0.4] + } + }; + + // src/film/audio.ts + var SR = 44100; + var midi = (m) => 440 * Math.pow(2, (m - 69) / 12); + var N = { E2: 40, G2: 43, A2: 45, B2: 47, D3: 50, E3: 52, G3: 55, A3: 57, B3: 59, D4: 62, E4: 64, G4: 67, A4: 69, B4: 71, D5: 74, E5: 76, G5: 79, A5: 81, B5: 83, D6: 86, E6: 88 }; + var sceneStart = (id) => TL.scenes.find((s) => s.id === id)?.start ?? 0; + var sceneDur = (id) => TL.scenes.find((s) => s.id === id)?.dur ?? 0; + var Mixer = class { + ctx; + music; + sfx; + voice; + rev; + revIn; + master; + noise; + ksCache = /* @__PURE__ */ new Map(); + r = rng(4242); + q = []; + /** 人声轨(供「山谷回声」等效果使用;在 score/effects 之前由 buildSoundtrack 设置) */ + voiceBuf = null; + /** 在 t 时刻真正创建节点(离线渲染按需建节点) */ + at(t, fn) { + this.q.push({ t, fn }); + } + /** 把人声 [t0, t0+dur) 这一段当回声再放几遍:越来越远、越来越暗 */ + echo(t0, dur, delay = 0.42, gain = 0.3, taps = 3) { + this.at(t0 + delay, () => { + if (!this.voiceBuf) return; + for (let k = 1; k <= taps; k++) { + const s = this.ctx.createBufferSource(); + s.buffer = this.voiceBuf; + const f = this.ctx.createBiquadFilter(); + f.type = "lowpass"; + f.frequency.value = 2800 / k; + const g2 = this.ctx.createGain(); + g2.gain.value = gain * Math.pow(0.5, k - 1); + s.connect(f); + f.connect(g2); + this.out(g2, this.sfx, k % 2 ? -0.45 : 0.45, 0.6); + s.start(t0 + delay * k + 1 / 30, t0, dur); + } + }); + } + constructor(ctx) { + this.ctx = ctx; + const self = this; + for (const k of ["flute", "pad", "bell", "block", "noiseHit", "tone", "wind", "quack", "swanCall"]) { + const orig = self[k].bind(this); + self[k] = (...a) => { + this.q.push({ t: a[0], fn: () => orig(...a) }); + }; + } + this.master = ctx.createGain(); + this.master.gain.value = 0.9; + const comp = ctx.createDynamicsCompressor(); + comp.threshold.value = -10; + comp.knee.value = 6; + comp.ratio.value = 6; + comp.attack.value = 4e-3; + comp.release.value = 0.2; + this.master.connect(comp); + comp.connect(ctx.destination); + this.music = ctx.createGain(); + this.music.connect(this.master); + this.sfx = ctx.createGain(); + this.sfx.gain.value = 0.9; + this.sfx.connect(this.master); + this.voice = ctx.createGain(); + this.voice.gain.value = 1; + this.voice.connect(this.master); + this.rev = ctx.createConvolver(); + this.rev.buffer = this.impulse(2.3, 2.4); + this.revIn = ctx.createGain(); + this.revIn.gain.value = 0.55; + this.revIn.connect(this.rev); + this.rev.connect(this.master); + this.noise = ctx.createBuffer(1, SR * 2, SR); + const d = this.noise.getChannelData(0), nr = rng(99); + for (let i = 0; i < d.length; i++) d[i] = nr() * 2 - 1; + } + impulse(sec, decay) { + const b = this.ctx.createBuffer(2, Math.floor(SR * sec), SR), r = rng(7); + for (let c2 = 0; c2 < 2; c2++) { + const d = b.getChannelData(c2); + for (let i = 0; i < d.length; i++) d[i] = (r() * 2 - 1) * Math.pow(1 - i / d.length, decay) * (i < 400 ? i / 400 : 1); + } + return b; + } + out(node, bus, pan = 0, send = 0.25) { + const p = this.ctx.createStereoPanner(); + p.pan.value = pan; + node.connect(p); + p.connect(bus); + if (send > 0) { + const s = this.ctx.createGain(); + s.gain.value = send; + p.connect(s); + s.connect(this.revIn); + } + } + // ---------- 乐器 ---------- + ks(freq) { + const key = Math.round(freq * 10); + let b = this.ksCache.get(key); + if (b) return b; + const len = Math.floor(SR * 3), per = Math.max(2, Math.round(SR / freq)); + b = this.ctx.createBuffer(1, len, SR); + const d = b.getChannelData(0), r = rng(key); + const buf = new Float32Array(per); + for (let i = 0; i < per; i++) buf[i] = (r() * 2 - 1) * (1 - i / per * 0.5); + let idx = 0, last = 0; + const damp = freq > 600 ? 0.994 : 0.997; + for (let i = 0; i < len; i++) { + const cur = buf[idx]; + const nxt = buf[(idx + 1) % per]; + const v = damp * 0.5 * (cur + nxt); + buf[idx] = v; + d[i] = cur * 0.8 + last * 0.2; + last = cur; + idx = (idx + 1) % per; + } + this.ksCache.set(key, b); + return b; + } + // 拨弦直接在 JS 里叠加到采样缓冲(比每个音符建节点快一个数量级) + pl = []; + pluck(t, m, vel = 0.5, pan = 0, o = {}) { + if (t < 0 || t > TOTAL + 2) return; + if (!this.pl.length) { + const n2 = this.ctx.length; + this.pl = [new Float32Array(n2), new Float32Array(n2)]; + } + const src = this.ks(midi(m)).getChannelData(0); + const dur = o.dur ?? 2.6; + const i0 = Math.floor(t * SR), n = Math.min(src.length - 2, Math.floor((dur + 0.6) * SR)); + const gl = vel * Math.cos((pan + 1) * Math.PI / 4), gr = vel * Math.sin((pan + 1) * Math.PI / 4); + const relStart = dur * 0.7 * SR, tau = dur * 0.15 * SR; + const a = Math.exp(-2 * Math.PI * (2400 + (o.bright ?? 0.5) * 4e3) / SR); + const L = this.pl[0], R = this.pl[1]; + let pos = 0, lp3 = 0; + const bendTo = o.bend ? Math.pow(2, o.bend / 12) : 1; + const b0 = 0.12 * SR, b1 = 0.32 * SR; + for (let k = 0; k < n; k++) { + const idx = i0 + k; + if (idx >= L.length) break; + const rate = o.bend ? k < b0 ? 1 : k > b1 ? bendTo : 1 + (bendTo - 1) * (k - b0) / (b1 - b0) : 1; + const p = Math.floor(pos), f = pos - p; + if (p + 1 >= src.length) break; + const x = src[p] * (1 - f) + src[p + 1] * f; + pos += rate; + lp3 = x + a * (lp3 - x); + const env = k < relStart ? 1 : Math.exp(-(k - relStart) / tau); + const v = lp3 * env; + L[idx] += v * gl; + R[idx] += v * gr; + } + } + flushPlucks() { + if (!this.pl.length) return; + const b = this.ctx.createBuffer(2, this.ctx.length, SR); + b.copyToChannel(this.pl[0], 0); + b.copyToChannel(this.pl[1], 1); + const s = this.ctx.createBufferSource(); + s.buffer = b; + const g2 = this.ctx.createGain(); + g2.gain.value = 1; + s.connect(g2); + this.out(g2, this.music, 0, 0.3); + s.start(0); + this.pl = []; + } + flute(t, m, dur, vel = 0.22, pan = 0.1) { + const c2 = this.ctx, fr = midi(m); + const o1 = c2.createOscillator(); + o1.type = "sine"; + o1.frequency.value = fr; + const o2 = c2.createOscillator(); + o2.type = "triangle"; + o2.frequency.value = fr * 2; + const lfo = c2.createOscillator(); + lfo.frequency.value = 5.3; + const lg = c2.createGain(); + lg.gain.setValueAtTime(0, t); + lg.gain.linearRampToValueAtTime(fr * 7e-3, t + Math.min(dur, 0.6)); + lfo.connect(lg); + lg.connect(o1.frequency); + lg.connect(o2.frequency); + const g2 = c2.createGain(); + g2.gain.value = 0.12; + const g3 = c2.createGain(); + g3.gain.setValueAtTime(0, t); + g3.gain.linearRampToValueAtTime(vel, t + 0.07); + g3.gain.setValueAtTime(vel * 0.85, t + Math.max(0.08, dur - 0.12)); + g3.gain.linearRampToValueAtTime(0, t + dur + 0.18); + const n = c2.createBufferSource(); + n.buffer = this.noise; + n.loop = true; + const bp = c2.createBiquadFilter(); + bp.type = "bandpass"; + bp.frequency.value = fr * 2.2; + bp.Q.value = 1.2; + const ng = c2.createGain(); + ng.gain.setValueAtTime(0, t); + ng.gain.linearRampToValueAtTime(vel * 0.16, t + 0.05); + ng.gain.linearRampToValueAtTime(vel * 0.04, t + 0.25); + ng.gain.linearRampToValueAtTime(0, t + dur + 0.1); + n.connect(bp); + bp.connect(ng); + this.out(ng, this.music, pan, 0.4); + o1.connect(g3); + o2.connect(g2); + g2.connect(g3); + this.out(g3, this.music, pan, 0.45); + for (const o of [o1, o2, lfo]) { + o.start(t); + o.stop(t + dur + 0.3); + } + n.start(t, t * 0.37 % 1.5); + n.stop(t + dur + 0.2); + } + pad(t0, t1, notes, vel = 0.06, cutoff = 900) { + const c2 = this.ctx; + const f = c2.createBiquadFilter(); + f.type = "lowpass"; + f.frequency.value = cutoff; + const g2 = c2.createGain(); + const att = Math.min(1.6, (t1 - t0) / 3); + g2.gain.setValueAtTime(0, t0); + g2.gain.linearRampToValueAtTime(vel, t0 + att); + g2.gain.setValueAtTime(vel, Math.max(t0 + att, t1 - att)); + g2.gain.linearRampToValueAtTime(0, t1); + f.connect(g2); + this.out(g2, this.music, 0, 0.5); + for (const m of notes) for (const det of [-6, 6]) { + const o = c2.createOscillator(); + o.type = "triangle"; + o.frequency.value = midi(m); + o.detune.value = det; + o.connect(f); + o.start(t0); + o.stop(t1 + 0.1); + } + } + bell(t, m, vel = 0.15, pan = 0, bus) { + const c2 = this.ctx, fr = midi(m); + const g2 = c2.createGain(); + g2.gain.value = 1; + [[1, 1, 2.5], [2.76, 0.4, 1.2], [5.4, 0.22, 0.6], [8.93, 0.1, 0.35]].forEach(([k, a, d]) => { + const o = c2.createOscillator(); + o.type = "sine"; + o.frequency.value = fr * k; + const og = c2.createGain(); + og.gain.setValueAtTime(0, t); + og.gain.linearRampToValueAtTime(vel * a, t + 5e-3); + og.gain.exponentialRampToValueAtTime(1e-4, t + d); + o.connect(og); + og.connect(g2); + o.start(t); + o.stop(t + d + 0.05); + }); + this.out(g2, bus ?? this.music, pan, 0.5); + } + block(t, vel = 0.2, fr = 950, pan = 0) { + const c2 = this.ctx; + const o = c2.createOscillator(); + o.type = "sine"; + o.frequency.setValueAtTime(fr, t); + o.frequency.exponentialRampToValueAtTime(fr * 0.6, t + 0.05); + const g2 = c2.createGain(); + g2.gain.setValueAtTime(vel, t); + g2.gain.exponentialRampToValueAtTime(1e-4, t + 0.09); + o.connect(g2); + this.out(g2, this.music, pan, 0.15); + o.start(t); + o.stop(t + 0.12); + } + gliss(t, notes, step = 0.06, vel = 0.3, pan = 0) { + notes.forEach((m, i) => this.pluck(t + i * step, m, vel * (0.7 + 0.3 * i / notes.length), pan + (i / notes.length - 0.5) * 0.6, { dur: 2.2 })); + } + melody(t, beat, seq, vel = 0.22, oct = 0, inst = "flute") { + let x = t; + for (const [n, d] of seq) { + if (n !== "-") { + if (inst === "flute") this.flute(x, N[n] + oct, d * beat * 0.96, vel); + else this.pluck(x, N[n] + oct, vel, 0.15, { bend: d >= 2 ? this.r() < 0.5 ? 0 : 0 : 0 }); + } + x += d * beat; + } + return x; + } + // ---------- 音效 ---------- + noiseHit(t, dur, type, f0, f1, vel, pan = 0, q2 = 1, send = 0.2, bus) { + const c2 = this.ctx; + const s = c2.createBufferSource(); + s.buffer = this.noise; + s.loop = true; + const f = c2.createBiquadFilter(); + f.type = type; + f.Q.value = q2; + f.frequency.setValueAtTime(f0, t); + f.frequency.exponentialRampToValueAtTime(Math.max(20, f1), t + dur); + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + Math.min(0.01, dur * 0.2)); + g2.gain.exponentialRampToValueAtTime(1e-4, t + dur); + s.connect(f); + f.connect(g2); + this.out(g2, bus ?? this.sfx, pan, send); + s.start(t, t * 0.731 % 1.8); + s.stop(t + dur + 0.05); + } + tone(t, f0, f1, dur, vel, type = "sine", pan = 0, send = 0.2) { + const c2 = this.ctx; + const o = c2.createOscillator(); + o.type = type; + o.frequency.setValueAtTime(f0, t); + o.frequency.exponentialRampToValueAtTime(Math.max(20, f1), t + dur); + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + 8e-3); + g2.gain.exponentialRampToValueAtTime(1e-4, t + dur); + o.connect(g2); + this.out(g2, this.sfx, pan, send); + o.start(t); + o.stop(t + dur + 0.05); + } + wind(t, dur, vel, pan = 0) { + const c2 = this.ctx; + const s = c2.createBufferSource(); + s.buffer = this.noise; + s.loop = true; + const f = c2.createBiquadFilter(); + f.type = "bandpass"; + f.Q.value = 0.8; + const lfo = c2.createOscillator(); + lfo.frequency.value = 0.23; + const lg = c2.createGain(); + lg.gain.value = 260; + f.frequency.value = 520; + lfo.connect(lg); + lg.connect(f.frequency); + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + Math.min(1.5, dur / 3)); + g2.gain.setValueAtTime(vel, t + dur - Math.min(1.5, dur / 3)); + g2.gain.linearRampToValueAtTime(0, t + dur); + s.connect(f); + f.connect(g2); + this.out(g2, this.sfx, pan, 0.1); + s.start(t); + s.stop(t + dur + 0.1); + lfo.start(t); + lfo.stop(t + dur + 0.1); + } + chirp(t, vel = 0.06, pan = 0) { + const base = 2600 + this.r() * 1600; + for (let i = 0; i < 2 + Math.floor(this.r() * 3); i++) this.tone(t + i * 0.09, base, base * (1.25 + this.r() * 0.2), 0.06, vel, "sine", pan, 0.3); + } + peep(t, vel = 0.12, pan = 0) { + this.tone(t, 2100, 2900, 0.09, vel, "triangle", pan, 0.15); + this.tone(t + 0.13, 2200, 3e3, 0.08, vel * 0.8, "triangle", pan, 0.15); + } + quack(t, vel = 0.14, pan = 0, pitch = 1) { + const c2 = this.ctx; + const o = c2.createOscillator(); + o.type = "sawtooth"; + o.frequency.setValueAtTime(330 * pitch, t); + o.frequency.linearRampToValueAtTime(250 * pitch, t + 0.16); + const f = c2.createBiquadFilter(); + f.type = "bandpass"; + f.frequency.value = 1150 * pitch; + f.Q.value = 3.2; + const f2 = c2.createBiquadFilter(); + f2.type = "peaking"; + f2.frequency.value = 2400; + f2.gain.value = 8; + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + 0.015); + g2.gain.setValueAtTime(vel, t + 0.11); + g2.gain.exponentialRampToValueAtTime(1e-4, t + 0.2); + o.connect(f); + f.connect(f2); + f2.connect(g2); + this.out(g2, this.sfx, pan, 0.15); + o.start(t); + o.stop(t + 0.25); + } + crack(t, vel = 0.25, pan = 0, big = false) { + const n = big ? 7 : 3; + for (let i = 0; i < n; i++) this.noiseHit(t + i * (0.018 + this.r() * 0.03), 0.03 + this.r() * 0.03, "highpass", 2500 + this.r() * 2500, 1800, vel * (0.6 + this.r() * 0.5), pan, 0.7, 0.1); + if (big) this.tone(t, 160, 70, 0.25, vel * 0.9, "sine", pan, 0.2); + } + flap(t, n, rate, vel = 0.12, pan = 0) { + for (let i = 0; i < n; i++) this.noiseHit(t + i / rate, 0.12, "bandpass", 900, 300, vel * (0.8 + this.r() * 0.3), pan, 0.9, 0.1); + } + thump(t, vel = 0.4, f = 95) { + this.tone(t, f, f * 0.55, 0.22, vel, "sine", 0, 0.15); + this.noiseHit(t, 0.05, "lowpass", 1200, 300, vel * 0.5, 0, 0.7, 0.05); + } + shot(t, pan = 0.5) { + this.noiseHit(t, 0.5, "lowpass", 6e3, 200, 0.85, pan, 0.5, 0.6); + this.tone(t, 120, 40, 0.5, 0.6, "sine", pan, 0.6); + this.noiseHit(t + 0.05, 1.6, "lowpass", 900, 120, 0.18, pan, 0.6, 0.9); + } + swanCall(t, vel = 0.08, pan = -0.3) { + const c2 = this.ctx; + for (const [dt, f] of [[0, 520], [0.32, 620]]) { + const o = c2.createOscillator(); + o.type = "sawtooth"; + o.frequency.setValueAtTime(f, t + dt); + o.frequency.linearRampToValueAtTime(f * 0.94, t + dt + 0.28); + const bp = c2.createBiquadFilter(); + bp.type = "bandpass"; + bp.frequency.value = 1300; + bp.Q.value = 4; + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t + dt); + g2.gain.linearRampToValueAtTime(vel, t + dt + 0.04); + g2.gain.exponentialRampToValueAtTime(1e-4, t + dt + 0.3); + o.connect(bp); + bp.connect(g2); + this.out(g2, this.sfx, pan, 0.8); + o.start(t + dt); + o.stop(t + dt + 0.35); + } + } + splash(t, vel = 0.35) { + this.noiseHit(t, 0.6, "lowpass", 3e3, 400, vel, 0, 0.6, 0.3); + for (let i = 0; i < 6; i++) this.tone(t + 0.08 + this.r() * 0.4, 900 + this.r() * 900, 1500 + this.r() * 900, 0.05, vel * 0.2, "sine", this.r() - 0.5, 0.3); + } + heart(t) { + this.tone(t, 70, 50, 0.16, 0.28, "sine", 0, 0.05); + this.tone(t + 0.2, 64, 48, 0.14, 0.2, "sine", 0, 0.05); + } + clap(t, vel = 0.08, pan = -0.5) { + this.noiseHit(t, 0.06, "bandpass", 1600 + this.r() * 600, 900, vel, pan, 1.5, 0.2); + } + swish(t, vel = 0.08) { + this.noiseHit(t, 0.35, "bandpass", 2500, 900, vel, 0, 1, 0.25); + } + drop(t) { + this.tone(t, 700, 1500, 0.12, 0.3, "sine", 0, 0.5); + this.noiseHit(t, 0.08, "highpass", 3e3, 2e3, 0.1, 0, 0.7, 0.4); + } + lap(t0, t1, vel = 0.05) { + for (let t = t0; t < t1; t += 0.6 + this.r() * 0.9) this.noiseHit(t, 0.5, "bandpass", 700 + this.r() * 300, 300, vel, this.r() * 1.2 - 0.6, 1.5, 0.3); + } + }; + var cache = /* @__PURE__ */ new Map(); + function buildSoundtrack(voiceUrl, onStage) { + const film = FILM; + const hit = cache.get(film.id); + if (hit) return hit; + const cached = (async () => { + const len = Math.ceil(SR * (TOTAL + 0.3)); + const ctx = new OfflineAudioContext(2, len, SR); + onStage?.("\u52A0\u8F7D\u65C1\u767D\u2026"); + let voiceBuf = null; + try { + const ab = await (await fetch(voiceUrl)).arrayBuffer(); + voiceBuf = await ctx.decodeAudioData(ab); + } catch (e) { + console.warn("voice load failed", e); + } + onStage?.("\u5408\u6210\u914D\u4E50\u4E0E\u97F3\u6548\u2026"); + const m = new Mixer(ctx); + m.voiceBuf = voiceBuf; + const mg = m.music.gain; + mg.setValueAtTime(0.9, 0); + for (const s of TL.scenes) { + const spans = s.lines?.length ? s.lines.map((l) => [l.t0, l.t0 + l.dur]) : s.voice_start && s.voice_dur ? [[s.voice_start, s.voice_start + s.voice_dur]] : []; + for (const [a, b] of spans) { + mg.setTargetAtTime(0.42, Math.max(0, a - 0.25), 0.08); + mg.setTargetAtTime(0.9, b + 0.1, 0.25); + } + } + film.score(m); + film.effects(m); + m.flushPlucks(); + m.q.sort((a, b) => a.t - b.t); + let qi = 0; + const until = (tt) => { + while (qi < m.q.length && m.q[qi].t < tt) { + m.q[qi].fn(); + qi++; + } + }; + until(1.5); + for (let k = 1; k < TOTAL + 0.3; k++) { + const tt = k; + ctx.suspend(tt).then(() => { + until(tt + 1.5); + ctx.resume(); + }); + } + if (voiceBuf) { + const v = ctx.createBufferSource(); + v.buffer = voiceBuf; + const vg = ctx.createGain(); + vg.gain.value = 1.15; + const hp = ctx.createBiquadFilter(); + hp.type = "highpass"; + hp.frequency.value = 80; + v.connect(hp); + hp.connect(vg); + vg.connect(m.voice); + v.start(1 / 30); + } + const out = await ctx.startRendering(); + onStage?.("\u54CD\u5EA6\u6807\u51C6\u5316\uFF08\u221214 LUFS\uFF09\u2026"); + normalize(out, -14, 0.84); + onStage?.("\u5B8C\u6210"); + return out; + })(); + cache.set(film.id, cached); + cached.catch(() => cache.delete(film.id)); + return cached; + } + function toWav(b) { + const ch = b.numberOfChannels, len = b.length, sr = b.sampleRate; + const buf = new ArrayBuffer(44 + len * ch * 2), v = new DataView(buf); + const w = (o2, s) => { + for (let i = 0; i < s.length; i++) v.setUint8(o2 + i, s.charCodeAt(i)); + }; + w(0, "RIFF"); + v.setUint32(4, 36 + len * ch * 2, true); + w(8, "WAVE"); + w(12, "fmt "); + v.setUint32(16, 16, true); + v.setUint16(20, 1, true); + v.setUint16(22, ch, true); + v.setUint32(24, sr, true); + v.setUint32(28, sr * ch * 2, true); + v.setUint16(32, ch * 2, true); + v.setUint16(34, 16, true); + w(36, "data"); + v.setUint32(40, len * ch * 2, true); + const data = Array.from({ length: ch }, (_, c2) => b.getChannelData(c2)); + let o = 44; + for (let i = 0; i < len; i++) for (let c2 = 0; c2 < ch; c2++) { + const s = Math.max(-1, Math.min(1, data[c2][i])); + v.setInt16(o, s < 0 ? s * 32768 : s * 32767, true); + o += 2; + } + return buf; + } + function loudness(b) { + const fs = b.sampleRate; + const biquad = (x, b0, b1, b2, a1, a2) => { + const y = new Float32Array(x.length); + let x1 = 0, x2 = 0, y1 = 0, y2 = 0; + for (let i = 0; i < x.length; i++) { + const v = b0 * x[i] + b1 * x1 + b2 * x2 - a1 * y1 - a2 * y2; + x2 = x1; + x1 = x[i]; + y2 = y1; + y1 = v; + y[i] = v; + } + return y; + }; + let K = Math.tan(Math.PI * 1681.974450955533 / fs), Q = 0.7071752369554196; + const Vh = Math.pow(10, 3.999843853973347 / 20), Vb = Math.pow(Vh, 0.4996667741545416); + let a0 = 1 + K / Q + K * K; + const s1 = [(Vh + Vb * K / Q + K * K) / a0, 2 * (K * K - Vh) / a0, (Vh - Vb * K / Q + K * K) / a0, 2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0]; + K = Math.tan(Math.PI * 38.13547087602444 / fs); + Q = 0.5003270373238773; + a0 = 1 + K / Q + K * K; + const s2 = [1, -2, 1, 2 * (K * K - 1) / a0, (1 - K / Q + K * K) / a0]; + const chans = Array.from({ length: b.numberOfChannels }, (_, c2) => { + const y = biquad(b.getChannelData(c2), s1[0], s1[1], s1[2], s1[3], s1[4]); + return biquad(y, s2[0], s2[1], s2[2], s2[3], s2[4]); + }); + const blk = Math.round(0.4 * fs), hop = Math.round(0.1 * fs), z = []; + for (let st = 0; st + blk <= b.length; st += hop) { + let sum = 0; + for (const y of chans) { + let e = 0; + for (let i = st; i < st + blk; i++) e += y[i] * y[i]; + sum += e / blk; + } + z.push(sum); + } + const L = (e) => -0.691 + 10 * Math.log10(e + 1e-12); + const abs = z.filter((e) => L(e) > -70); + if (!abs.length) return -70; + const rel = L(abs.reduce((a, e) => a + e, 0) / abs.length) - 10; + const gated = abs.filter((e) => L(e) > rel); + return L(gated.reduce((a, e) => a + e, 0) / Math.max(1, gated.length)); + } + function normalize(b, target = -14, ceiling = 0.84) { + const L = loudness(b); + const g2 = Math.pow(10, (target - L) / 20); + const n = b.length, ch = Array.from({ length: b.numberOfChannels }, (_, c2) => b.getChannelData(c2)); + const want = new Float32Array(n); + for (let i = 0; i < n; i++) { + let m = 0; + for (const d of ch) { + d[i] *= g2; + const a = Math.abs(d[i]); + if (a > m) m = a; + } + want[i] = m > ceiling ? ceiling / m : 1; + } + const att = 1 / (4e-3 * b.sampleRate), rel = 1 / (0.12 * b.sampleRate); + for (let i = n - 2; i >= 0; i--) want[i] = Math.min(want[i], want[i + 1] + att); + let cur = 1; + for (let i = 0; i < n; i++) { + cur = Math.min(want[i], cur + rel); + for (const d of ch) d[i] *= cur; + } + return { before: L, gain: g2 }; + } + + // src/films/duckling/music.ts + function score(m) { + const B2 = 60 / 72; + const S = sceneStart, D2 = sceneDur; + const r = rng(808); + const arp2 = (t0, t1, chords, step, vel, pattern = [0, 1, 2, 1]) => { + let i = 0; + for (let t2 = t0; t2 < t1 - 0.05; t2 += step, i++) { + const ch = chords[Math.floor((t2 - t0) / (step * 8)) % chords.length]; + m.pluck(t2, ch[pattern[i % pattern.length] % ch.length], vel * (i % 4 === 0 ? 1 : 0.7), i % 2 ? 0.25 : -0.25, { dur: 1.8 }); + } + }; + const G2 = [N.G3, N.D4, N.B4], Em2 = [N.E3, N.B3, N.G4], Dm = [N.D3, N.A3, N.D4], Am2 = [N.A2, N.E3, N.A3], Gh2 = [N.G4, N.B4, N.D5]; + let t = S("title"); + m.gliss(t + 0.35, [N.G3, N.A3, N.B3, N.D4, N.E4, N.G4, N.A4, N.B4, N.D5], 0.07, 0.28); + m.pad(t + 0.5, S("summer") + 1, [N.G2, N.D3], 0.05); + m.melody(t + 2.2, B2, [["D5", 1], ["E5", 0.5], ["G5", 0.5], ["E5", 1], ["D5", 1.5]], 0.17); + t = S("summer"); + m.pad(t, S("hatch") + 0.5, [N.G2, N.D3, N.B3], 0.045); + arp2(t + 0.3, S("hatch"), [G2, Em2, G2, Dm], B2 / 2, 0.2); + m.melody(t + 1.2, B2, [["D5", 1], ["E5", 0.5], ["G5", 0.5], ["E5", 1], ["D5", 1], ["B4", 1], ["D5", 0.5], ["B4", 0.5], ["A4", 2], ["G4", 1], ["A4", 0.5], ["B4", 0.5], ["D5", 1], ["E5", 0.5], ["D5", 0.5], ["B4", 1], ["A4", 0.5], ["G4", 0.5], ["G4", 2]], 0.13); + t = S("hatch"); + for (let i = 0; i < Math.floor(D2("hatch") / B2); i++) { + if (i % 2 === 0) m.block(t + i * B2, 0.07, 1100, -0.3); + m.pluck(t + i * B2 + B2 / 2, [N.G4, N.B4, N.D5, N.E5][i % 4], 0.13, 0.3, { dur: 0.5, bright: 0.9 }); + } + m.pad(t, S("bigegg"), [N.G2, N.D3], 0.035); + t = S("bigegg"); + const BE = CUE.bigegg; + m.pad(t, t + BE.open, [N.E2, N.B2], 0.05, 500); + for (let x = t + 0.6; x < t + BE.open - 0.05; x += 0.09) m.pluck(x, N.E3, 0.06 + 0.1 * (x - t) / BE.open, 0, { dur: 0.3, send: 0.1 }); + m.pluck(t + BE.surprise, N.D4, 0.3, 0, { bend: -2 }); + m.pluck(t + BE.surprise + 0.25, N.A3, 0.3, 0, { bend: -2 }); + m.pluck(t + BE.question + 0.05, N.B4, 0.22, 0.2, { bend: 2 }); + arp2(t + BE.walk, S("bullied"), [Em2, G2], B2 / 2, 0.12); + t = S("bullied"); + m.pad(t, S("leave") + 0.5, [N.E2, N.B2, N.G3], 0.05, 700); + for (let i = 0; i < 12; i++) m.pluck(t + 0.3 + i * B2, [N.E4, N.D4, N.B3, N.A3, N.G3, N.A3, N.B3, N.E3][i % 8], 0.16, -0.2, { dur: 2, bend: i % 4 === 3 ? -1 : 0 }); + t = S("leave"); + m.pad(t, S("marsh") + 0.3, [N.E2, N.B2], 0.045, 600); + m.melody(t + 0.4, B2 * 1.1, [["E5", 1.5], ["D5", 0.5], ["B4", 1], ["A4", 1], ["G4", 1], ["A4", 0.5], ["B4", 0.5], ["E4", 2]], 0.13); + t = S("marsh"); + for (let x = t + 0.2; x < t + CUE.marsh.shots[0] - 0.02; x += 0.125) m.pluck(x, Math.floor((x - t) / 0.5) % 2 ? N.B2 : N.E2, 0.17, 0, { dur: 0.25, send: 0.05 }); + m.pad(t + CUE.marsh.hide, S("autumn") + 0.5, [N.E2], 0.04, 400); + for (let x = t + CUE.marsh.hide + 0.6; x < t + 13; x += 0.95) m.heart(x); + t = S("autumn"); + m.pad(t, S("winter") + 0.5, [N.E2, N.B2, N.G3], 0.04, 800); + arp2(t + 0.3, S("winter"), [Em2, Am2, G2, Em2], B2 * 0.75, 0.13); + m.bell(t + CUE.autumn.swans + 0.6, N.B5, 0.07, 0.4); + m.bell(t + CUE.autumn.swans + 3, N.E6, 0.06, -0.3); + m.melody(t + CUE.autumn.swans + 1.4, B2, [["B4", 1.5], ["D5", 0.5], ["E5", 1], ["G5", 1], ["E5", 2], ["D5", 1], ["B4", 1], ["A4", 2]], 0.11, 0); + t = S("winter"); + m.pad(t, S("spring") + 0.5, [N.E2, N.B2], 0.035, 350); + for (let i = 0; i < 6; i++) m.bell(t + 1 + i * 2.1, [N.E5, N.B4, N.D5, N.A4, N.B4, N.E5][i], 0.05, i % 2 ? 0.4 : -0.4); + t = S("spring"); + m.bell(t + 0.15, N.G5, 0.1, 0); + m.bell(t + 0.4, N.D6, 0.07, 0.3); + m.pad(t, S("reflection") + 0.5, [N.G2, N.D3, N.B3], 0.045); + const SP = CUE.spring; + arp2(t + 0.5, t + SP.flapHard, [G2, Em2], B2 / 2, 0.15); + arp2(t + SP.flapHard, S("reflection"), [G2, Dm, Em2, G2], B2 / 4, 0.14, [0, 1, 2, 1, 2, 1]); + m.gliss(t + SP.lift - 0.05, [N.G3, N.A3, N.B3, N.D4, N.E4, N.G4, N.A4, N.B4, N.D5, N.E5, N.G5], 0.05, 0.25); + m.melody(t + SP.lift + 0.4, B2, [["D5", 1], ["E5", 0.5], ["G5", 0.5], ["A5", 1], ["G5", 1], ["E5", 1], ["G5", 0.5], ["E5", 0.5], ["D5", 2]], 0.13); + t = S("reflection"); + const RF = CUE.reflection; + m.pad(t, t + RF.reveal, [N.E2, N.B2, N.E3], 0.04, 600); + for (let i = 0; i < 10; i++) m.pluck(t + 0.8 + i * B2 * 1.05, [N.B3, N.D4, N.E4, N.D4, N.B3, N.A3, N.B3, N.D4, N.E4, N.G4][i], 0.12, 0, { dur: 2 }); + m.bell(t + RF.mirror, N.E5, 0.06, 0); + m.bell(t + RF.mirror + 0.3, N.B5, 0.04, 0.3); + m.gliss(t + RF.reveal - 0.05, [N.G3, N.B3, N.D4, N.G4, N.B4, N.D5, N.G5, N.B5, N.D6], 0.06, 0.3); + [N.G5, N.B5, N.D6, N.G5].forEach((x, i) => m.bell(t + RF.reveal + i * 0.18, x, 0.08, i % 2 ? 0.3 : -0.3)); + m.pad(t + RF.reveal, S("accepted") + 0.5, [N.G2, N.D3, N.B3, N.D4], 0.05, 1200); + m.melody(t + RF.reveal + 1.2, B2, [["G5", 1.5], ["E5", 0.5], ["D5", 1], ["B4", 1], ["D5", 2]], 0.14); + t = S("accepted"); + arp2(t + 0.2, S("fly"), [G2, Em2, Dm, G2], B2 / 2, 0.15); + m.pad(t, S("fly") + 0.3, [N.G2, N.D3], 0.04); + m.melody(t + 0.8, B2, [["G4", 1], ["A4", 0.5], ["B4", 0.5], ["D5", 1], ["E5", 0.5], ["D5", 0.5], ["B4", 1], ["A4", 0.5], ["G4", 0.5], ["G4", 2]], 0.12); + t = S("fly"); + m.pad(t, S("ending") + 1, [N.G2, N.D3, N.G3, N.B3], 0.06, 1600); + arp2(t, S("ending"), [Gh2, G2, Em2, Gh2], B2 / 4, 0.16, [0, 1, 2, 1, 2, 1, 0, 2]); + for (let i = 0; i < Math.floor(D2("fly") / B2); i++) m.block(t + i * B2, 0.08, 800, 0); + m.melody(t + 0.2, B2, [["D5", 1], ["E5", 0.5], ["G5", 0.5], ["A5", 1], ["G5", 1], ["E5", 1], ["G5", 0.5], ["A5", 0.5], ["B5", 2]], 0.15, 0); + t = S("ending"); + m.pad(t, TOTAL, [N.G2, N.D3, N.B3], 0.045); + const EN = CUE.ending.end; + arp2(t + 0.2, t + EN - 0.2, [G2, Em2, G2, Dm], B2 * 0.75, 0.12); + m.gliss(t + EN, [N.G3, N.B3, N.D4, N.G4, N.B4, N.D5, N.G5], 0.08, 0.22); + m.bell(t + EN + 0.1, N.G5, 0.08); + m.bell(t + EN + 0.35, N.D6, 0.05, 0.3); + m.pluck(t + EN + 0.6, N.G3, 0.2, 0, { dur: 2 }); + m.pluck(t + EN + 0.6, N.D4, 0.15, 0, { dur: 2 }); + m.pluck(t + EN + 0.6, N.G4, 0.12, 0, { dur: 2 }); + void r; + } + function effects(m) { + const S = sceneStart, D2 = sceneDur; + const r = m.r; + let t = S("title"); + const TT = CUE.title; + m.drop(t + TT.drop); + m.swish(t + TT.write); + m.swish(t + TT.write + 0.45); + m.swish(t + TT.write + 0.9); + m.thump(t + TT.seal + 0.01, 0.45); + for (let i = 0; i < 6; i++) m.peep(t + 2.6 + i * 0.65, 0.04, 0.3); + t = S("summer"); + m.wind(t, D2("summer"), 0.025); + for (let x = t + 0.6; x < t + D2("summer"); x += 1.1 + r() * 1.6) m.chirp(x, 0.035, r() * 1.4 - 0.7); + t = S("hatch"); + for (let k = 0; k < 4; k++) { + const tc = t + 0.4 + k * 0.75; + m.crack(tc + 0.05, 0.22, -0.3 + k * 0.2); + m.crack(tc + 0.25, 0.18, -0.3 + k * 0.2); + m.noiseHit(tc + 0.55, 0.08, "bandpass", 1800, 900, 0.18, -0.3 + k * 0.2, 2, 0.1); + m.peep(tc + 0.75, 0.1, -0.3 + k * 0.2); + for (let p = 1; p < 5; p++) { + const pt = tc + 1 + 2.3 * (p - 1); + if (pt - t < D2("hatch")) m.peep(pt + 0.05, 0.07, -0.3 + k * 0.2); + } + m.noiseHit(tc + 3, 0.05, "lowpass", 600, 200, 0.12, -0.3 + k * 0.2, 0.7, 0.05); + } + t = S("bigegg"); + const BE = CUE.bigegg; + for (let x = t + 0.5; x < t + BE.crackFrom; x += Math.max(0.12, 0.5 - (x - t) * 0.12)) m.block(x, 0.05 + (x - t) * 0.02, 420, 0); + m.crack(t + BE.crackFrom + 0.05, 0.2); + m.crack(t + BE.crackFrom + 0.4, 0.22); + m.crack(t + BE.open, 0.35, 0, true); + m.noiseHit(t + BE.surprise, 0.25, "bandpass", 2400, 1200, 0.06, -0.4, 1.2, 0.2); + m.peep(t + BE.surprise + 0.1, 0.08, -0.4); + m.peep(t + BE.surprise + 0.2, 0.08, -0.5); + for (let x = t + BE.walk + 0.1; x < t + BE.walk + 2.6; x += 0.6) m.noiseHit(x, 0.05, "lowpass", 600, 200, 0.1, 0, 0.7, 0.05); + t = S("bullied"); + const BU = CUE.bullied; + for (let x = t + BU.laugh1[0] + 0.1; x < t + BU.laugh1[1] - 0.2; x += 0.35 + r() * 0.3) m.quack(x, 0.09, -0.5 + r() * 0.3, 1.25 + r() * 0.2); + for (let x = t + BU.laugh2[0] + 0.1; x < t + BU.laugh2[1] - 0.2; x += 0.4 + r() * 0.35) m.quack(x, 0.07, -0.5 + r() * 0.3, 1.25 + r() * 0.2); + m.flap(t + BU.roosterIn, 6, 9, 0.12, 0.5); + m.tone(t + BU.roosterIn + 0.2, 600, 900, 0.25, 0.08, "sawtooth", 0.5, 0.3); + m.tone(t + BU.roosterIn + 0.45, 900, 500, 0.5, 0.07, "sawtooth", 0.5, 0.4); + for (const p of BU.pecks) { + m.block(t + p, 0.25, 1500, 0.2); + m.flap(t + p - 0.15, 2, 10, 0.08, 0.3); + } + m.swish(t + BU.shoo, 0.07); + t = S("leave"); + m.wind(t, D2("leave") + 1, 0.07, -0.2); + for (let x = t + CUE.leave.walk + 0.2; x < t + D2("leave"); x += 0.62) m.noiseHit(x, 0.04, "lowpass", 500, 200, 0.05, 0, 0.7, 0.05); + t = S("marsh"); + m.lap(t, t + D2("marsh"), 0.035); + const MA = CUE.marsh; + for (let x = t + MA.laugh[0] + 0.1; x < t + MA.laugh[1] - 0.2; x += 0.3 + r() * 0.3) m.quack(x, 0.1, 0.4 + r() * 0.3, 0.95 + r() * 0.15); + m.shot(t + MA.shots[0], 0.6); + m.shot(t + MA.shots[1], 0.45); + m.flap(t + MA.scatter, 14, 12, 0.13, 0.4); + for (let i = 0; i < 6; i++) m.quack(t + MA.scatter + 0.05 + i * 0.12, 0.08, 0.5, 1.1); + m.splash(t + MA.hide + 0.2, 0.12); + t = S("autumn"); + m.wind(t, D2("autumn"), 0.03, 0.2); + const AU = CUE.autumn.swans; + m.swanCall(t + AU + 0.8, 0.06, 0.5); + m.swanCall(t + AU + 3, 0.07, 0.1); + m.swanCall(t + AU + 5.6, 0.05, -0.5); + m.flap(t + AU + 1.5, 12, 1.4, 0.04, 0.2); + t = S("winter"); + m.wind(t, D2("winter") + 0.8, 0.12, 0); + m.wind(t + 2, D2("winter") - 2, 0.06, 0.6); + m.crack(t + CUE.winter.ice, 0.2, 0, true); + m.noiseHit(t + CUE.winter.ice + 0.05, 0.9, "bandpass", 300, 120, 0.12, 0, 4, 0.5); + t = S("spring"); + for (let x = t + 0.4; x < t + D2("spring"); x += 0.7 + r() * 1.1) m.chirp(x, 0.04, r() * 1.4 - 0.7); + const SP = CUE.spring; + m.flap(t + SP.spread, 2, 2, 0.1, 0); + m.flap(t + SP.spread + 0.9, Math.floor((SP.flapHard - SP.spread - 0.9) * 0.83), 0.83, 0.08, 0); + m.flap(t + SP.flapHard, Math.floor((SP.lift - SP.flapHard) * 1.6), 1.6, 0.13, 0); + m.flap(t + SP.lift, 6, 1.46, 0.1, 0.2); + m.noiseHit(t + SP.lift - 0.1, 1.2, "bandpass", 400, 2400, 0.12, 0, 1, 0.4); + m.pluck(t + SP.question, N.B4, 0.2, 0, { bend: 2 }); + t = S("reflection"); + const RF = CUE.reflection; + m.flap(t, 5, 7, 0.1, -0.2); + m.splash(t + RF.land, 0.3); + m.lap(t + 1.2, t + D2("reflection"), 0.03); + m.bell(t + RF.reveal, 91, 0.06, 0, m.sfx); + t = S("accepted"); + m.lap(t, t + D2("accepted"), 0.03); + const CH = CUE.accepted.cheer; + for (let x = t + CH + 0.1; x < t + CH + 4.5; x += 0.11 + r() * 0.08) m.clap(x, 0.06 * (1 - (x - t - CH) / 5), -0.55 + r() * 0.2); + for (let i = 0; i < 4; i++) m.chirp(t + CH + 0.4 + i * 1.3, 0.03, 0.4); + t = S("fly"); + m.wind(t, D2("fly") + 1, 0.07, 0); + m.flap(t + 0.3, Math.floor(D2("fly") * 3.2 / 2 / Math.PI * 2), 3.2 / Math.PI, 0.09, 0); + m.noiseHit(t + 0.9, 1.6, "bandpass", 300, 2e3, 0.1, 0, 1, 0.5); + t = S("ending"); + m.lap(t, t + D2("ending"), 0.025); + m.thump(t + CUE.ending.end + 0.12, 0.4); + } + + // src/films/duckling/content.ts + var PLATFORMS = [ + { + id: "xhs", + name: "\u5C0F\u7EA2\u4E66", + titleMax: 20, + tagMax: 10, + color: "#ff2442", + titles: ["\u6C34\u58A8\u7248\u4E11\u5C0F\u9E2D\uFF5C\u50CF\u8BED\u6587\u8BFE\u672C\u91CC\u7684\u63D2\u56FE", "\u4E00\u7B14\u4E00\u753B\u753B\u51FA\u7684\u6C34\u58A8\u4E11\u5C0F\u9E2D", "\u7761\u524D\u6545\u4E8B\uFF5C\u4E11\u5C0F\u9E2D\u53D8\u767D\u5929\u9E45"], + desc: `\u8FD8\u8BB0\u5F97\u5C0F\u5B66\u8BED\u6587\u4E66\u91CC\u7684\u300A\u4E11\u5C0F\u9E2D\u300B\u5417\uFF1F +\u8FD9\u6B21\u628A\u5B83\u753B\u6210\u4E86\u4E00\u90E8\u4F1A\u52A8\u7684\u6C34\u58A8\u63D2\u56FE\uFF1A\u5BA3\u7EB8\u5E95\u3001\u6BDB\u7B14\u52FE\u7EBF\u3001\u6DE1\u5F69\u6655\u67D3\uFF0C\u8FDE\u5B57\u5E55\u90FD\u662F\u6977\u4E66\u3002 + +\u{1F95A} \u6700\u5927\u7684\u90A3\u9897\u86CB\uFF0C\u94BB\u51FA\u4E00\u53EA\u53C8\u5927\u53C8\u7070\u7684\u5C0F\u5BB6\u4F19 +\u{1F413} \u88AB\u7B11\u8BDD\u3001\u88AB\u6B3A\u8D1F\uFF0C\u4ED6\u6084\u6084\u79BB\u5F00\u4E86\u5BB6 +\u2744\uFE0F \u71AC\u8FC7\u6700\u51B7\u7684\u51AC\u5929 +\u{1F9A2} \u6625\u5929\u6765\u4E86\uFF0C\u6C34\u91CC\u7684\u5012\u5F71\u662F\u4E00\u53EA\u767D\u5929\u9E45 + +\u9001\u7ED9\u6BCF\u4E00\u4E2A\u89C9\u5F97\u81EA\u5DF1\u300C\u4E0D\u4E00\u6837\u300D\u7684\u5C0F\u670B\u53CB\uFF0C\u4E5F\u9001\u7ED9\u5C0F\u65F6\u5019\u7684\u81EA\u5DF1\uFF1A +\u4E0D\u6015\u98CE\u96EA\uFF0C\u4E5F\u4E0D\u6015\u5632\u7B11\uFF0C\u603B\u6709\u4E00\u5929\u4F60\u4E5F\u4F1A\u5C55\u5F00\u5C5E\u4E8E\u81EA\u5DF1\u7684\u7FC5\u8180\u3002 + +\u9002\u5408\u5F53\u7761\u524D\u6545\u4E8B\uFF0C\u548C\u5B69\u5B50\u4E00\u8D77\u770B \u{1F440}`, + tags: ["\u4E11\u5C0F\u9E2D", "\u5B89\u5F92\u751F\u7AE5\u8BDD", "\u6C34\u58A8\u52A8\u753B", "\u7761\u524D\u6545\u4E8B", "\u513F\u7AE5\u6545\u4E8B", "\u56FD\u98CE\u63D2\u753B", "\u624B\u7ED8\u52A8\u753B", "\u4EB2\u5B50\u9605\u8BFB", "\u5C0F\u5B66\u8BED\u6587", "\u7AE5\u8BDD\u6545\u4E8B"], + tip: "\u6807\u9898 \u226420 \u5B57\uFF0C\u8BDD\u9898 \u226410 \u4E2A\uFF1B\u8BDD\u9898\u7C98\u8D34\u540E\u5728 App \u91CC\u70B9\u9009\u4E00\u4E0B\u5373\u53EF\u53D8\u6210\u84DD\u8272\u8BDD\u9898\u3002" + }, + { + id: "dy", + name: "\u6296\u97F3", + titleMax: 30, + tagMax: 5, + color: "#111111", + titles: ["\u7528\u4EE3\u7801\u4E00\u7B14\u4E00\u753B\uFF0C\u753B\u4E86\u4E00\u90E8\u6C34\u58A8\u7248\u300A\u4E11\u5C0F\u9E2D\u300B", "\u50CF\u5C0F\u65F6\u5019\u8BED\u6587\u4E66\u63D2\u56FE\u7684\u4E11\u5C0F\u9E2D\uFF0C\u4F60\u8FD8\u8BB0\u5F97\u5417", "\u4E11\u5C0F\u9E2D\u53D8\u6210\u767D\u5929\u9E45\uFF0C\u8FD9\u6B21\u662F\u6C34\u58A8\u624B\u7ED8\u7248"], + desc: `\u5BA3\u7EB8\u3001\u6BDB\u7B14\u3001\u6DE1\u5F69\u3001\u6731\u7EA2\u5370\u7AE0\u2014\u2014\u628A\u5B89\u5F92\u751F\u7684\u300A\u4E11\u5C0F\u9E2D\u300B\u753B\u6210\u4E86\u4F1A\u52A8\u7684\u8BFE\u672C\u63D2\u56FE\u3002 +\u4ECE\u7834\u58F3\u3001\u88AB\u5632\u7B11\u3001\u71AC\u8FC7\u5BD2\u51AC\uFF0C\u5230\u5728\u6C34\u91CC\u770B\u89C1\u81EA\u5DF1\u7684\u5012\u5F71\uFF1A\u539F\u6765\u4ED6\u672C\u6765\u5C31\u662F\u4E00\u53EA\u5929\u9E45\u3002 +\u76F8\u4FE1\u81EA\u5DF1\uFF0C\u4F60\u4E5F\u4F1A\u5C55\u5F00\u5C5E\u4E8E\u81EA\u5DF1\u7684\u7FC5\u8180\u3002`, + tags: ["\u4E11\u5C0F\u9E2D", "\u6C34\u58A8\u52A8\u753B", "\u513F\u7AE5\u6545\u4E8B", "\u5B89\u5F92\u751F\u7AE5\u8BDD", "\u56FD\u98CE\u52A8\u753B"], + tip: "\u6807\u9898 \u226430 \u5B57\uFF0C\u8BDD\u9898 \u22645 \u4E2A\u3002" + }, + { + id: "sph", + name: "\u5FAE\u4FE1\u89C6\u9891\u53F7", + titleMax: 16, + tagMax: null, + color: "#07c160", + titles: ["\u6C34\u58A8\u7248\u300A\u4E11\u5C0F\u9E2D\u300B", "\u4E11\u5C0F\u9E2D\u53D8\u767D\u5929\u9E45\xB7\u6C34\u58A8\u7248", "\u76F8\u4FE1\u81EA\u5DF1\uFF0C\u4F60\u4E5F\u4F1A\u98DE"], + desc: `\u4E00\u90E8\u50CF\u8BED\u6587\u8BFE\u672C\u63D2\u56FE\u7684\u6C34\u58A8\u52A8\u753B\u3002 +\u6700\u5927\u7684\u90A3\u9897\u86CB\u91CC\uFF0C\u94BB\u51FA\u4E86\u53C8\u5927\u53C8\u7070\u7684\u4E11\u5C0F\u9E2D\uFF1B\u4ED6\u88AB\u5632\u7B11\u3001\u79BB\u5F00\u5BB6\u3001\u71AC\u8FC7\u5BD2\u51AC\uFF0C\u7EC8\u4E8E\u5728\u6625\u5929\u7684\u6E56\u6C34\u91CC\u770B\u89C1\u2014\u2014\u81EA\u5DF1\u662F\u4E00\u53EA\u767D\u5929\u9E45\u3002 +\u8F6C\u7ED9\u5BB6\u91CC\u7684\u5C0F\u670B\u53CB\uFF0C\u5F53\u4F5C\u4ECA\u665A\u7684\u7761\u524D\u6545\u4E8B\u5427\u3002`, + tags: ["\u4E11\u5C0F\u9E2D", "\u5B89\u5F92\u751F\u7AE5\u8BDD", "\u6C34\u58A8\u52A8\u753B", "\u513F\u7AE5\u52A8\u753B", "\u7761\u524D\u6545\u4E8B", "\u7AE5\u8BDD\u6545\u4E8B", "\u56FD\u98CE", "\u624B\u7ED8", "\u4EB2\u5B50", "\u8BED\u6587\u8BFE\u672C", "\u6210\u957F", "\u76F8\u4FE1\u81EA\u5DF1"], + tip: "\u6807\u9898 \u226416 \u5B57\uFF0C\u8BDD\u9898\u6570\u91CF\u4E0D\u9650\u3002" + }, + { + id: "bili", + name: "\u54D4\u54E9\u54D4\u54E9", + titleMax: 80, + tagMax: 10, + color: "#fb7299", + titles: ["\u3010\u6C34\u58A8\u624B\u7ED8\u3011\u5B89\u5F92\u751F\u7AE5\u8BDD\u300A\u4E11\u5C0F\u9E2D\u300B\uFF5C\u5C0F\u5B66\u8BED\u6587\u8BFE\u672C\u63D2\u56FE\u98CE\u513F\u7AE5\u52A8\u753B\uFF0C\u5168\u7A0B\u4EE3\u7801\u9010\u5E27\u7ED8\u5236\uFF0B\u539F\u521B\u56FD\u98CE\u914D\u4E50", "\u3010\u513F\u7AE5\u52A8\u753B\u3011\u6C34\u58A8\u7248\u300A\u4E11\u5C0F\u9E2D\u53D8\u6210\u767D\u5929\u9E45\u300B\uFF1A\u4E00\u90E8\u50CF\u8BFE\u672C\u63D2\u56FE\u7684\u56FD\u98CE\u7AE5\u8BDD\u77ED\u7247"], + desc: `\u4E00\u90E8\u7EA6 2 \u5206\u534A\u7684\u6C34\u58A8\u624B\u7ED8\u98CE\u513F\u7AE5\u52A8\u753B\uFF0C\u6539\u7F16\u81EA\u5B89\u5F92\u751F\u7AE5\u8BDD\u300A\u4E11\u5C0F\u9E2D\u300B\uFF081843\uFF09\u3002 + +\u753B\u9762\uFF1A\u5168\u90E8\u7531\u4EE3\u7801\u5728\u6D4F\u89C8\u5668\u91CC\u9010\u5E27\u7ED8\u5236\u2014\u2014\u5BA3\u7EB8\u7EB9\u7406\u3001\u53D8\u5BBD\u6BDB\u7B14\u7B14\u89E6\u3001\u98DE\u767D\u3001\u6DE1\u5F69\u6655\u67D3\u3001\u6731\u7802\u5370\u7AE0\uFF0C14 \u4E2A\u573A\u666F\u6A2A\u7AD6\u5C4F\u5404\u81EA\u91CD\u65B0\u6784\u56FE\u3002 +\u58F0\u97F3\uFF1A\u4E2D\u6587\u65C1\u767D\uFF1B\u539F\u521B\u4E94\u58F0\u97F3\u9636\u914D\u4E50\uFF08\u53E4\u7B5D\u62E8\u5F26\uFF0B\u7AF9\u7B1B\uFF0B\u94FA\u5E95\uFF0C\u968F\u56DB\u5B63\u6362\u60C5\u7EEA\uFF09\uFF1B\u7834\u58F3\u3001\u9E2D\u53EB\u3001\u67AA\u58F0\u3001\u51B0\u88C2\u3001\u632F\u7FC5\u7B49\u97F3\u6548\u5168\u90E8\u7A0B\u5E8F\u5408\u6210\u3002 + +\u7AE0\u8282\uFF1A +00:00 \u7247\u5934 +00:06 \u590F\u65E5\u4E61\u6751 +00:18 \u7834\u58F3 +00:29 \u6700\u5927\u7684\u86CB +00:41 \u88AB\u5632\u7B11 +00:51 \u79BB\u5BB6 +00:59 \u6CBC\u6CFD +01:13 \u79CB\u5929\u7684\u5927\u9E1F +01:28 \u5BD2\u51AC +01:42 \u6625\u56DE +01:54 \u5012\u5F71 +02:07 \u63A5\u7EB3 +02:19 \u9AD8\u98DE +02:26 \u5BC4\u8BED`, + tags: ["\u4E11\u5C0F\u9E2D", "\u5B89\u5F92\u751F\u7AE5\u8BDD", "\u6C34\u58A8\u52A8\u753B", "\u513F\u7AE5\u52A8\u753B", "\u56FD\u98CE", "\u624B\u7ED8\u52A8\u753B", "\u7761\u524D\u6545\u4E8B", "\u539F\u521B\u914D\u4E50", "\u4EE3\u7801\u827A\u672F", "\u7AE5\u8BDD"], + tip: "\u6807\u9898 \u226480 \u5B57\uFF0C\u6807\u7B7E \u226410 \u4E2A\uFF1B\u7AE0\u8282\u65F6\u95F4\u53EF\u76F4\u63A5\u7C98\u8FDB\u7B80\u4ECB\u3002" + } + ]; + var PHOTO_POSTS = [ + { + id: "xhs", + name: "\u5C0F\u7EA2\u4E66\u56FE\u6587\uFF08\u5B9E\u51B5\u56FE\uFF09", + titleMax: 20, + tagMax: 10, + titles: ["\u4E11\u5C0F\u9E2D\u7684\u516D\u4E2A\u77AC\u95F4\uFF5C\u6C34\u58A8\u5B9E\u51B5\u56FE", "\u4F1A\u52A8\u7684\u8BED\u6587\u8BFE\u672C\u63D2\u56FE\uFF5C\u4E11\u5C0F\u9E2D"], + body: `\u628A\u300A\u4E11\u5C0F\u9E2D\u300B\u753B\u6210\u4E86 6 \u5F20\u4F1A\u52A8\u7684\u6C34\u58A8\u63D2\u56FE\uFF0C\u957F\u6309\u5C31\u80FD\u770B\u5B83\u52A8\u8D77\u6765 \u2728 + +\u5176\u4E00 \xB7 \u6700\u5927\u7684\u86CB\uFF1A\u597D\u4E45\u624D\u88C2\u5F00\uFF0C\u94BB\u51FA\u4E00\u53EA\u53C8\u5927\u53C8\u7070\u7684\u5C0F\u5BB6\u4F19 +\u5176\u4E8C \xB7 \u4E11\u5C0F\u9E2D\uFF1A\u54E5\u54E5\u59D0\u59D0\u7B11\u8BDD\u4ED6\uFF0C\u516C\u9E21\u8FFD\u7740\u5544\u4ED6 +\u5176\u4E09 \xB7 \u4EF0\u671B\uFF1A\u79CB\u5929\u508D\u665A\uFF0C\u4E00\u7FA4\u6D01\u767D\u7684\u5927\u9E1F\u4ECE\u5929\u4E0A\u98DE\u8FC7 +\u5176\u56DB \xB7 \u5BD2\u51AC\uFF1A\u6E56\u9762\u7ED3\u51B0\uFF0C\u4ED6\u7F29\u6210\u4E00\u56E2\u71AC\u8FC7\u4E00\u4E2A\u4E2A\u5BD2\u591C +\u5176\u4E94 \xB7 \u5012\u5F71\uFF1A\u4F4E\u5934\u4E00\u770B\uFF0C\u6C34\u91CC\u662F\u4E00\u53EA\u767D\u5929\u9E45 +\u5176\u516D \xB7 \u5C55\u7FC5\uFF1A\u539F\u6765\uFF0C\u4ED6\u672C\u6765\u5C31\u662F\u4E00\u53EA\u5929\u9E45 + +\u6BCF\u4E00\u7B14\u90FD\u662F\u4EE3\u7801\u753B\u51FA\u6765\u7684\uFF1A\u5BA3\u7EB8\u3001\u6BDB\u7B14\u3001\u6DE1\u5F69\u3001\u6731\u7EA2\u5370\u7AE0\u3002 +\u4F60\u5C0F\u65F6\u5019\u6700\u559C\u6B22\u8BFE\u672C\u91CC\u7684\u54EA\u4E00\u7BC7\uFF1F`, + tags: ["\u4E11\u5C0F\u9E2D", "\u5B9E\u51B5\u56FE", "\u6C34\u58A8\u63D2\u753B", "\u5B89\u5F92\u751F\u7AE5\u8BDD", "\u56FD\u98CE\u63D2\u753B", "\u624B\u7ED8", "\u5C0F\u5B66\u8BED\u6587", "\u7AE5\u8BDD\u6545\u4E8B", "\u6CBB\u6108\u7CFB", "\u52A8\u6001\u58C1\u7EB8"], + tip: "\u53D1\u5E03\u65F6\u9009\u62E9\u300C\u5B9E\u51B5\u56FE\u300D\u4E0A\u4F20 6 \u7EC4 JPG+MOV\uFF1B\u6807\u9898 \u226420 \u5B57\uFF0C\u8BDD\u9898 \u226410 \u4E2A\u3002" + }, + { + id: "dy", + name: "\u6296\u97F3\u56FE\u6587\uFF08\u5B9E\u51B5\u56FE\uFF09", + titleMax: 30, + tagMax: 5, + titles: ["\u6C34\u58A8\u5B9E\u51B5\u56FE\uFF1A\u4E11\u5C0F\u9E2D\u53D8\u6210\u767D\u5929\u9E45\u7684\u516D\u4E2A\u77AC\u95F4", "\u4F1A\u52A8\u7684\u8BFE\u672C\u63D2\u56FE\uFF0C\u4E11\u5C0F\u9E2D\u957F\u5927\u4E86"], + body: `6 \u5F20\u4F1A\u52A8\u7684\u6C34\u58A8\u63D2\u56FE\uFF0C\u8BB2\u5B8C\u4E00\u4E2A\u300A\u4E11\u5C0F\u9E2D\u300B\u3002 +\u7834\u58F3 \u2192 \u88AB\u5632\u7B11 \u2192 \u4EF0\u671B\u5929\u9E45 \u2192 \u71AC\u8FC7\u5BD2\u51AC \u2192 \u6C34\u4E2D\u5012\u5F71 \u2192 \u5C55\u7FC5\u9AD8\u98DE\u3002 +\u4E0D\u6015\u98CE\u96EA\uFF0C\u4E5F\u4E0D\u6015\u5632\u7B11\uFF0C\u603B\u6709\u4E00\u5929\uFF0C\u4F60\u4E5F\u4F1A\u5C55\u5F00\u5C5E\u4E8E\u81EA\u5DF1\u7684\u7FC5\u8180\u3002`, + tags: ["\u4E11\u5C0F\u9E2D", "\u5B9E\u51B5\u56FE", "\u6C34\u58A8\u63D2\u753B", "\u5B89\u5F92\u751F\u7AE5\u8BDD", "\u56FD\u98CE"], + tip: "\u56FE\u6587\u6A21\u5F0F\u4E0A\u4F20\u5B9E\u51B5\u56FE\uFF1B\u6807\u9898 \u226430 \u5B57\uFF0C\u8BDD\u9898 \u22645 \u4E2A\u3002" + } + ]; + + // src/films/duckling/index.ts + var tl = timeline_default; + var at = (id, dt) => sceneAt(tl, id, dt); + var duckling = { + id: "duckling", + title: "\u4E11\u5C0F\u9E2D", + tagline: "\u53D8\u6210\u767D\u5929\u9E45", + intro: "\u5B89\u5F92\u751F\u7AE5\u8BDD \xB7 \u6C34\u58A8\u624B\u7ED8\u513F\u7AE5\u52A8\u753B\u3002\u5BA3\u7EB8\u3001\u6BDB\u7B14\u3001\u6DE1\u5F69\u3001\u6731\u7EA2\u5370\u7AE0\uFF0C\u50CF\u5C0F\u5B66\u8BED\u6587\u8BFE\u672C\u91CC\u7684\u63D2\u56FE\u90A3\u6837\uFF0C\u4E00\u7B14\u4E00\u753B\u5728\u4F60\u7684\u6D4F\u89C8\u5668\u91CC\u5B9E\u65F6\u753B\u51FA\u6765\uFF1B\u65C1\u767D\u3001\u539F\u521B\u56FD\u98CE\u914D\u4E50\u4E0E\u97F3\u6548\u540C\u6B65\u64AD\u653E\u3002", + credit: "\u6545\u4E8B\u6539\u7F16\u81EA\u5B89\u5F92\u751F\u300A\u4E11\u5C0F\u9E2D\u300B\uFF081843\uFF0C\u516C\u6709\u9886\u57DF\uFF09", + assets: "film/", + slug: "ugly-duckling", + timeline: tl, + scenes: SCENES2, + seal: ["\u7AE5\u8BDD"], + series: "\u5B89\u5F92\u751F\u7AE5\u8BDD \xB7 \u4E11\u5C0F\u9E2D", + score, + effects, + covers: [ + { id: "reflect", name: "\u5012\u5F71\u63ED\u6653", t: at("reflection", 7.6), title: "\u4E11\u5C0F\u9E2D", sub: "\u539F\u6765\uFF0C\u4ED6\u662F\u4E00\u53EA\u767D\u5929\u9E45", side: "left", vertical: true }, + { id: "hatch", name: "\u7834\u58F3\u800C\u51FA", t: at("bigegg", 4.4), title: "\u4E11\u5C0F\u9E2D", sub: "\u5B89\u5F92\u751F\u7AE5\u8BDD \xB7 \u6C34\u58A8\u624B\u7ED8\u52A8\u753B", side: "right", vertical: true }, + { id: "fly", name: "\u5C55\u7FC5\u9AD8\u98DE", t: at("fly", 4.2), title: "\u76F8\u4FE1\u81EA\u5DF1", sub: "\u4F60\u4E5F\u4F1A\u5C55\u5F00\u5C5E\u4E8E\u81EA\u5DF1\u7684\u7FC5\u8180", side: "right", vertical: true }, + { id: "autumn", name: "\u79CB\u65E5\u4EF0\u671B", t: at("autumn", 6.6), title: "\u4E11\u5C0F\u9E2D", sub: "\u50CF\u8BED\u6587\u8BFE\u672C\u91CC\u7684\u63D2\u56FE", side: "left", vertical: true } + ], + live: live_default, + platforms: PLATFORMS, + photoPosts: PHOTO_POSTS, + qa: qa_default, + making: [ + ["\u753B\u9762", "\u5168\u90E8\u7531\u4EE3\u7801\u9010\u5E27\u7ED8\u5236\uFF1A\u7A0B\u5E8F\u751F\u6210\u5BA3\u7EB8\u7EB9\u7406\uFF0C\u53D8\u5BBD\u6BDB\u7B14\u7B14\u89E6\u5E26\u8D77\u7B14\u3001\u6536\u7B14\u548C\u98DE\u767D\uFF0C\u6DE1\u5F69\u6655\u67D3\u6709\u6C34\u5F69\u79EF\u8FB9\uFF0C\u518D\u76D6\u6731\u7802\u5370\u7AE0\u3002\u540C\u4E00\u53EA\u300C\u9E1F\u300D\u53EF\u4EE5\u4ECE\u5C0F\u9EC4\u9E2D\u4E00\u8DEF\u957F\u6210\u5929\u9E45\u3002\u6BCF\u5E27\u53EA\u7531\u65F6\u95F4\u51B3\u5B9A\uFF0C\u53EF\u9010\u5E27\u590D\u73B0\u3002"], + ["\u58F0\u97F3", "\u4E2D\u6587\u795E\u7ECF\u7F51\u7EDC\u8BED\u97F3\u65C1\u767D\uFF1B\u539F\u521B\u4E94\u58F0\u97F3\u9636\u914D\u4E50\uFF08\u53E4\u7B5D\u62E8\u5F26\u3001\u7AF9\u7B1B\u3001\u94FA\u5E95\u3001\u94C3\uFF09\uFF0C\u968F\u56DB\u5B63\u6362\u60C5\u7EEA\uFF1B\u7834\u58F3\u3001\u9E2D\u53EB\u3001\u67AA\u58F0\u3001\u51B0\u88C2\u3001\u632F\u7FC5\u3001\u62CD\u624B\u7B49\u97F3\u6548\u90FD\u662F\u7A0B\u5E8F\u5408\u6210\u3002\u4EBA\u58F0\u51FA\u73B0\u65F6\u914D\u4E50\u81EA\u52A8\u538B\u4F4E\uFF0C\u6574\u4F53\u54CD\u5EA6 \u221214 LUFS\u3002"], + ["\u6D41\u7A0B", "\u6309 vibe-video \u6D41\u7A0B\uFF1A\u5206\u955C storyboard \u2192 \u914D\u97F3\u751F\u6210\u65F6\u95F4\u8F74 \u2192 \u63A5\u89E6\u8868 / \u9759\u5E27\u68C0\u67E5 \u2192 \u786E\u5B9A\u6027\u81EA\u68C0 \u2192 \u5168\u7247\u6E32\u67D3 \u2192 \u6DF7\u97F3 \u2192 vibe qa \u8D28\u68C0 \u2192 \u5C01\u9762\u4E0E\u53D1\u5E03\u5305\uFF1B\u5B9E\u51B5\u56FE\u6309 livecanvas \u6D41\u7A0B\u914D\u5BF9\u5C01\u88C5\u3002"] + ] + }; + + // src/films/wolf/timeline.json + var timeline_default2 = { + title: "\u72FC\u6765\u4E86", + style: "ink-wash textbook illustration", + w: 1920, + h: 1080, + fps: 30, + total: 137.86666666666667, + frames: 4136, + voice: { + n: "zh-CN-XiaoxiaoNeural", + boy: "zh-CN-YunxiaNeural", + farmer: "zh-CN-YunjianNeural" + }, + scenes: [ + { + id: "title", + start: 0, + dur: 6.5, + voice_start: 0.5, + voice_dur: 2.568, + lines: [ + { + who: "n", + text: "\u4F0A\u7D22\u5BD3\u8A00\uFF0C\u72FC\u6765\u4E86\u3002", + t0: 0.5, + dur: 2.568, + subs: [ + { + t0: 0.5, + t1: 1.967, + text: "\u4F0A\u7D22\u5BD3\u8A00", + who: "n" + }, + { + t0: 1.967, + t1: 3.068, + text: "\u72FC\u6765\u4E86", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 0.5, + t1: 1.967, + text: "\u4F0A\u7D22\u5BD3\u8A00", + who: "n" + }, + { + t0: 1.967, + t1: 3.068, + text: "\u72FC\u6765\u4E86", + who: "n" + } + ] + }, + { + id: "village", + start: 6.5, + dur: 10.566666666666666, + voice_start: 7, + voice_dur: 9.168, + lines: [ + { + who: "n", + text: "\u5C71\u811A\u4E0B\u6709\u4E2A\u5C0F\u6751\u5B50\u3002\u5927\u4EBA\u4EEC\u6BCF\u5929\u5728\u7530\u91CC\u5E72\u6D3B\uFF0C\u6709\u4E2A\u653E\u7F8A\u7684\u5B69\u5B50\uFF0C\u5929\u5929\u8D76\u7740\u7F8A\u7FA4\u4E0A\u5C71\u5403\u8349\u3002", + t0: 7, + dur: 9.168, + subs: [ + { + t0: 7, + t1: 9.096, + text: "\u5C71\u811A\u4E0B\u6709\u4E2A\u5C0F\u6751\u5B50", + who: "n" + }, + { + t0: 9.096, + t1: 11.715, + text: "\u5927\u4EBA\u4EEC\u6BCF\u5929\u5728\u7530\u91CC\u5E72\u6D3B", + who: "n" + }, + { + t0: 11.715, + t1: 13.549, + text: "\u6709\u4E2A\u653E\u7F8A\u7684\u5B69\u5B50", + who: "n" + }, + { + t0: 13.549, + t1: 16.168, + text: "\u5929\u5929\u8D76\u7740\u7F8A\u7FA4\u4E0A\u5C71\u5403\u8349", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 7, + t1: 9.096, + text: "\u5C71\u811A\u4E0B\u6709\u4E2A\u5C0F\u6751\u5B50", + who: "n" + }, + { + t0: 9.096, + t1: 11.715, + text: "\u5927\u4EBA\u4EEC\u6BCF\u5929\u5728\u7530\u91CC\u5E72\u6D3B", + who: "n" + }, + { + t0: 11.715, + t1: 13.549, + text: "\u6709\u4E2A\u653E\u7F8A\u7684\u5B69\u5B50", + who: "n" + }, + { + t0: 13.549, + t1: 16.168, + text: "\u5929\u5929\u8D76\u7740\u7F8A\u7FA4\u4E0A\u5C71\u5403\u8349", + who: "n" + } + ] + }, + { + id: "bored", + start: 17.0667, + dur: 12.833333333333334, + voice_start: 17.567, + voice_dur: 11.424, + lines: [ + { + who: "n", + text: "\u5C71\u5761\u4E0A\uFF0C\u7F8A\u513F\u6162\u60A0\u60A0\u5730\u5403\u7740\u8349\u3002\u653E\u7F8A\u5A03\u8EBA\u5728\u5927\u6811\u4E0B\uFF0C\u5634\u91CC\u53FC\u7740\u4E00\u6839\u8349\uFF0C\u89C9\u5F97\u65E0\u804A\u6781\u4E86\u3002\u5FFD\u7136\uFF0C\u4ED6\u7684\u773C\u73E0\u4E00\u8F6C\u3002", + t0: 17.567, + dur: 11.424, + subs: [ + { + t0: 17.567, + t1: 18.403, + text: "\u5C71\u5761\u4E0A", + who: "n" + }, + { + t0: 18.403, + t1: 20.91, + text: "\u7F8A\u513F\u6162\u60A0\u60A0\u5730\u5403\u7740\u8349", + who: "n" + }, + { + t0: 20.91, + t1: 23.139, + text: "\u653E\u7F8A\u5A03\u8EBA\u5728\u5927\u6811\u4E0B", + who: "n" + }, + { + t0: 23.139, + t1: 25.09, + text: "\u5634\u91CC\u53FC\u7740\u4E00\u6839\u8349", + who: "n" + }, + { + t0: 25.09, + t1: 26.762, + text: "\u89C9\u5F97\u65E0\u804A\u6781\u4E86", + who: "n" + }, + { + t0: 26.762, + t1: 27.319, + text: "\u5FFD\u7136", + who: "n" + }, + { + t0: 27.319, + t1: 28.991, + text: "\u4ED6\u7684\u773C\u73E0\u4E00\u8F6C", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 17.567, + t1: 18.403, + text: "\u5C71\u5761\u4E0A", + who: "n" + }, + { + t0: 18.403, + t1: 20.91, + text: "\u7F8A\u513F\u6162\u60A0\u60A0\u5730\u5403\u7740\u8349", + who: "n" + }, + { + t0: 20.91, + t1: 23.139, + text: "\u653E\u7F8A\u5A03\u8EBA\u5728\u5927\u6811\u4E0B", + who: "n" + }, + { + t0: 23.139, + t1: 25.09, + text: "\u5634\u91CC\u53FC\u7740\u4E00\u6839\u8349", + who: "n" + }, + { + t0: 25.09, + t1: 26.762, + text: "\u89C9\u5F97\u65E0\u804A\u6781\u4E86", + who: "n" + }, + { + t0: 26.762, + t1: 27.319, + text: "\u5FFD\u7136", + who: "n" + }, + { + t0: 27.319, + t1: 28.991, + text: "\u4ED6\u7684\u773C\u73E0\u4E00\u8F6C", + who: "n" + } + ] + }, + { + id: "shout1", + start: 29.9, + dur: 9.3, + voice_start: 30.4, + voice_dur: 7.892, + lines: [ + { + who: "n", + text: "\u4ED6\u8DF3\u4E0A\u5927\u77F3\u5934\uFF0C\u62E2\u7740\u5634\u5927\u558A\uFF1A", + t0: 30.4, + dur: 3.36, + subs: [ + { + t0: 30.4, + t1: 32.233, + text: "\u4ED6\u8DF3\u4E0A\u5927\u77F3\u5934", + who: "n" + }, + { + t0: 32.233, + t1: 33.76, + text: "\u62E2\u7740\u5634\u5927\u558A", + who: "n" + } + ] + }, + { + who: "boy", + text: "\u72FC\u6765\u4E86\uFF01\u72FC\u6765\u4E86\uFF01\u5FEB\u6765\u5440\uFF01", + t0: 34.14, + dur: 4.152, + subs: [ + { + t0: 34.14, + t1: 35.524, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 35.524, + t1: 36.908, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 36.908, + t1: 38.292, + text: "\u5FEB\u6765\u5440\uFF01", + who: "boy" + } + ] + } + ], + subs: [ + { + t0: 30.4, + t1: 32.233, + text: "\u4ED6\u8DF3\u4E0A\u5927\u77F3\u5934", + who: "n" + }, + { + t0: 32.233, + t1: 33.76, + text: "\u62E2\u7740\u5634\u5927\u558A", + who: "n" + }, + { + t0: 34.14, + t1: 35.524, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 35.524, + t1: 36.908, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 36.908, + t1: 38.292, + text: "\u5FEB\u6765\u5440\uFF01", + who: "boy" + } + ] + }, + { + id: "run1", + start: 39.2, + dur: 11.8, + voice_start: 39.7, + voice_dur: 10.412, + lines: [ + { + who: "n", + text: "\u5C71\u4E0B\u7684\u519C\u6C11\u542C\u89C1\u4E86\uFF0C\u625B\u8D77\u9504\u5934\uFF0C\u62FF\u8D77\u6241\u62C5\uFF0C\u6025\u6025\u5FD9\u5FD9\u5F80\u5C71\u4E0A\u8DD1\u3002", + t0: 39.7, + dur: 6.768, + subs: [ + { + t0: 39.7, + t1: 41.956, + text: "\u5C71\u4E0B\u7684\u519C\u6C11\u542C\u89C1\u4E86", + who: "n" + }, + { + t0: 41.956, + t1: 43.084, + text: "\u625B\u8D77\u9504\u5934", + who: "n" + }, + { + t0: 43.084, + t1: 44.212, + text: "\u62FF\u8D77\u6241\u62C5", + who: "n" + }, + { + t0: 44.212, + t1: 46.468, + text: "\u6025\u6025\u5FD9\u5FD9\u5F80\u5C71\u4E0A\u8DD1", + who: "n" + } + ] + }, + { + who: "farmer", + text: "\u72FC\u5728\u54EA\u513F\uFF1F\u72FC\u5728\u54EA\u513F\uFF1F", + t0: 46.848, + dur: 3.264, + subs: [ + { + t0: 46.848, + t1: 48.48, + text: "\u72FC\u5728\u54EA\u513F\uFF1F", + who: "farmer" + }, + { + t0: 48.48, + t1: 50.112, + text: "\u72FC\u5728\u54EA\u513F\uFF1F", + who: "farmer" + } + ] + } + ], + subs: [ + { + t0: 39.7, + t1: 41.956, + text: "\u5C71\u4E0B\u7684\u519C\u6C11\u542C\u89C1\u4E86", + who: "n" + }, + { + t0: 41.956, + t1: 43.084, + text: "\u625B\u8D77\u9504\u5934", + who: "n" + }, + { + t0: 43.084, + t1: 44.212, + text: "\u62FF\u8D77\u6241\u62C5", + who: "n" + }, + { + t0: 44.212, + t1: 46.468, + text: "\u6025\u6025\u5FD9\u5FD9\u5F80\u5C71\u4E0A\u8DD1", + who: "n" + }, + { + t0: 46.848, + t1: 48.48, + text: "\u72FC\u5728\u54EA\u513F\uFF1F", + who: "farmer" + }, + { + t0: 48.48, + t1: 50.112, + text: "\u72FC\u5728\u54EA\u513F\uFF1F", + who: "farmer" + } + ] + }, + { + id: "laugh1", + start: 51, + dur: 9.4, + voice_start: 51.5, + voice_dur: 7.988, + lines: [ + { + who: "boy", + text: "\u54C8\u54C8\u54C8\uFF0C\u54EA\u6709\u4EC0\u4E48\u72FC\uFF0C\u6211\u662F\u9017\u4F60\u4EEC\u73A9\u7684\uFF01", + t0: 51.5, + dur: 3.816, + subs: [ + { + t0: 51.5, + t1: 52.215, + text: "\u54C8\u54C8\u54C8", + who: "boy" + }, + { + t0: 52.215, + t1: 53.408, + text: "\u54EA\u6709\u4EC0\u4E48\u72FC", + who: "boy" + }, + { + t0: 53.408, + t1: 55.316, + text: "\u6211\u662F\u9017\u4F60\u4EEC\u73A9\u7684\uFF01", + who: "boy" + } + ] + }, + { + who: "n", + text: "\u519C\u6C11\u4EEC\u6C14\u5F97\u76F4\u6447\u5934\uFF0C\u53EA\u597D\u4E0B\u5C71\u53BB\u4E86\u3002", + t0: 55.696, + dur: 3.792, + subs: [ + { + t0: 55.696, + t1: 57.863, + text: "\u519C\u6C11\u4EEC\u6C14\u5F97\u76F4\u6447\u5934", + who: "n" + }, + { + t0: 57.863, + t1: 59.488, + text: "\u53EA\u597D\u4E0B\u5C71\u53BB\u4E86", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 51.5, + t1: 52.215, + text: "\u54C8\u54C8\u54C8", + who: "boy" + }, + { + t0: 52.215, + t1: 53.408, + text: "\u54EA\u6709\u4EC0\u4E48\u72FC", + who: "boy" + }, + { + t0: 53.408, + t1: 55.316, + text: "\u6211\u662F\u9017\u4F60\u4EEC\u73A9\u7684\uFF01", + who: "boy" + }, + { + t0: 55.696, + t1: 57.863, + text: "\u519C\u6C11\u4EEC\u6C14\u5F97\u76F4\u6447\u5934", + who: "n" + }, + { + t0: 57.863, + t1: 59.488, + text: "\u53EA\u597D\u4E0B\u5C71\u53BB\u4E86", + who: "n" + } + ] + }, + { + id: "shout2", + start: 60.4, + dur: 10.9, + voice_start: 60.9, + voice_dur: 9.496, + lines: [ + { + who: "n", + text: "\u8FC7\u4E86\u51E0\u5929\uFF0C\u653E\u7F8A\u5A03\u53C8\u558A\u8D77\u6765\uFF1A", + t0: 60.9, + dur: 3.216, + subs: [ + { + t0: 60.9, + t1: 62.069, + text: "\u8FC7\u4E86\u51E0\u5929", + who: "n" + }, + { + t0: 62.069, + t1: 64.116, + text: "\u653E\u7F8A\u5A03\u53C8\u558A\u8D77\u6765", + who: "n" + } + ] + }, + { + who: "boy", + text: "\u72FC\u6765\u4E86\uFF01\u72FC\u6765\u4E86\uFF01", + t0: 64.496, + dur: 2.904, + subs: [ + { + t0: 64.496, + t1: 65.948, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 65.948, + t1: 67.4, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + } + ] + }, + { + who: "n", + text: "\u519C\u6C11\u4EEC\u53C8\u4E00\u6B21\u8DD1\u4E0A\u4E86\u5C71\u3002", + t0: 67.78, + dur: 2.616, + subs: [ + { + t0: 67.78, + t1: 70.396, + text: "\u519C\u6C11\u4EEC\u53C8\u4E00\u6B21\u8DD1\u4E0A\u4E86\u5C71", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 60.9, + t1: 62.069, + text: "\u8FC7\u4E86\u51E0\u5929", + who: "n" + }, + { + t0: 62.069, + t1: 64.116, + text: "\u653E\u7F8A\u5A03\u53C8\u558A\u8D77\u6765", + who: "n" + }, + { + t0: 64.496, + t1: 65.948, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 65.948, + t1: 67.4, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 67.78, + t1: 70.396, + text: "\u519C\u6C11\u4EEC\u53C8\u4E00\u6B21\u8DD1\u4E0A\u4E86\u5C71", + who: "n" + } + ] + }, + { + id: "warn", + start: 71.3, + dur: 8, + voice_start: 71.8, + voice_dur: 6.596, + lines: [ + { + who: "boy", + text: "\u54C8\u54C8\uFF0C\u53C8\u4E0A\u5F53\u5566\uFF01", + t0: 71.8, + dur: 2.352, + subs: [ + { + t0: 71.8, + t1: 72.472, + text: "\u54C8\u54C8", + who: "boy" + }, + { + t0: 72.472, + t1: 74.152, + text: "\u53C8\u4E0A\u5F53\u5566\uFF01", + who: "boy" + } + ] + }, + { + who: "farmer", + text: "\u4F60\u518D\u8BF4\u8C0E\uFF0C\u4E0B\u6B21\u6211\u4EEC\u5C31\u4E0D\u4FE1\u4F60\u4E86\uFF01", + t0: 74.532, + dur: 3.864, + subs: [ + { + t0: 74.532, + t1: 75.636, + text: "\u4F60\u518D\u8BF4\u8C0E", + who: "farmer" + }, + { + t0: 75.636, + t1: 78.396, + text: "\u4E0B\u6B21\u6211\u4EEC\u5C31\u4E0D\u4FE1\u4F60\u4E86\uFF01", + who: "farmer" + } + ] + } + ], + subs: [ + { + t0: 71.8, + t1: 72.472, + text: "\u54C8\u54C8", + who: "boy" + }, + { + t0: 72.472, + t1: 74.152, + text: "\u53C8\u4E0A\u5F53\u5566\uFF01", + who: "boy" + }, + { + t0: 74.532, + t1: 75.636, + text: "\u4F60\u518D\u8BF4\u8C0E", + who: "farmer" + }, + { + t0: 75.636, + t1: 78.396, + text: "\u4E0B\u6B21\u6211\u4EEC\u5C31\u4E0D\u4FE1\u4F60\u4E86\uFF01", + who: "farmer" + } + ] + }, + { + id: "wolf", + start: 79.3, + dur: 12, + voice_start: 79.8, + voice_dur: 10.584, + lines: [ + { + who: "n", + text: "\u53C8\u8FC7\u4E86\u4E9B\u65E5\u5B50\uFF0C\u4E4C\u4E91\u538B\u7740\u5C71\u5934\u3002\u6811\u4E1B\u91CC\uFF0C\u4E00\u53CC\u7EFF\u83B9\u83B9\u7684\u773C\u775B\uFF0C\u6162\u6162\u9760\u8FD1\u4E86\u7F8A\u7FA4\u3002\u72FC\uFF0C\u771F\u7684\u6765\u4E86\uFF01", + t0: 79.8, + dur: 10.584, + subs: [ + { + t0: 79.8, + t1: 81.564, + text: "\u53C8\u8FC7\u4E86\u4E9B\u65E5\u5B50", + who: "n" + }, + { + t0: 81.564, + t1: 83.328, + text: "\u4E4C\u4E91\u538B\u7740\u5C71\u5934", + who: "n" + }, + { + t0: 83.328, + t1: 84.21, + text: "\u6811\u4E1B\u91CC", + who: "n" + }, + { + t0: 84.21, + t1: 86.562, + text: "\u4E00\u53CC\u7EFF\u83B9\u83B9\u7684\u773C\u775B", + who: "n" + }, + { + t0: 86.562, + t1: 88.62, + text: "\u6162\u6162\u9760\u8FD1\u4E86\u7F8A\u7FA4", + who: "n" + }, + { + t0: 88.62, + t1: 88.914, + text: "\u72FC", + who: "n" + }, + { + t0: 88.914, + t1: 90.384, + text: "\u771F\u7684\u6765\u4E86\uFF01", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 79.8, + t1: 81.564, + text: "\u53C8\u8FC7\u4E86\u4E9B\u65E5\u5B50", + who: "n" + }, + { + t0: 81.564, + t1: 83.328, + text: "\u4E4C\u4E91\u538B\u7740\u5C71\u5934", + who: "n" + }, + { + t0: 83.328, + t1: 84.21, + text: "\u6811\u4E1B\u91CC", + who: "n" + }, + { + t0: 84.21, + t1: 86.562, + text: "\u4E00\u53CC\u7EFF\u83B9\u83B9\u7684\u773C\u775B", + who: "n" + }, + { + t0: 86.562, + t1: 88.62, + text: "\u6162\u6162\u9760\u8FD1\u4E86\u7F8A\u7FA4", + who: "n" + }, + { + t0: 88.62, + t1: 88.914, + text: "\u72FC", + who: "n" + }, + { + t0: 88.914, + t1: 90.384, + text: "\u771F\u7684\u6765\u4E86\uFF01", + who: "n" + } + ] + }, + { + id: "cry", + start: 91.3, + dur: 9.4, + voice_start: 91.8, + voice_dur: 7.988, + lines: [ + { + who: "n", + text: "\u653E\u7F8A\u5A03\u5413\u574F\u4E86\uFF0C\u62FC\u547D\u5730\u558A\uFF1A", + t0: 91.8, + dur: 3.192, + subs: [ + { + t0: 91.8, + t1: 93.715, + text: "\u653E\u7F8A\u5A03\u5413\u574F\u4E86", + who: "n" + }, + { + t0: 93.715, + t1: 94.992, + text: "\u62FC\u547D\u5730\u558A", + who: "n" + } + ] + }, + { + who: "boy", + text: "\u72FC\u6765\u4E86\uFF01\u72FC\u771F\u7684\u6765\u4E86\uFF01\u6551\u547D\u554A\uFF01", + t0: 95.372, + dur: 4.416, + subs: [ + { + t0: 95.372, + t1: 96.634, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 96.634, + t1: 98.526, + text: "\u72FC\u771F\u7684\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 98.526, + t1: 99.788, + text: "\u6551\u547D\u554A\uFF01", + who: "boy" + } + ] + } + ], + subs: [ + { + t0: 91.8, + t1: 93.715, + text: "\u653E\u7F8A\u5A03\u5413\u574F\u4E86", + who: "n" + }, + { + t0: 93.715, + t1: 94.992, + text: "\u62FC\u547D\u5730\u558A", + who: "n" + }, + { + t0: 95.372, + t1: 96.634, + text: "\u72FC\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 96.634, + t1: 98.526, + text: "\u72FC\u771F\u7684\u6765\u4E86\uFF01", + who: "boy" + }, + { + t0: 98.526, + t1: 99.788, + text: "\u6551\u547D\u554A\uFF01", + who: "boy" + } + ] + }, + { + id: "noone", + start: 100.7, + dur: 11.166666666666666, + voice_start: 101.2, + voice_dur: 9.76, + lines: [ + { + who: "n", + text: "\u5C71\u4E0B\u7684\u519C\u6C11\u542C\u89C1\u4E86\uFF0C\u90FD\u4EE5\u4E3A\u4ED6\u53C8\u5728\u8BF4\u8C0E\u3002", + t0: 101.2, + dur: 4.296, + subs: [ + { + t0: 101.2, + t1: 103.348, + text: "\u5C71\u4E0B\u7684\u519C\u6C11\u542C\u89C1\u4E86", + who: "n" + }, + { + t0: 103.348, + t1: 105.496, + text: "\u90FD\u4EE5\u4E3A\u4ED6\u53C8\u5728\u8BF4\u8C0E", + who: "n" + } + ] + }, + { + who: "farmer", + text: "\u54FC\uFF0C\u53C8\u6765\u9A97\u4EBA\u3002", + t0: 105.876, + dur: 2.664, + subs: [ + { + t0: 105.876, + t1: 106.409, + text: "\u54FC", + who: "farmer" + }, + { + t0: 106.409, + t1: 108.54, + text: "\u53C8\u6765\u9A97\u4EBA", + who: "farmer" + } + ] + }, + { + who: "n", + text: "\u8C01\u4E5F\u6CA1\u6709\u4E0A\u5C71\u3002", + t0: 108.92, + dur: 2.04, + subs: [ + { + t0: 108.92, + t1: 110.96, + text: "\u8C01\u4E5F\u6CA1\u6709\u4E0A\u5C71", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 101.2, + t1: 103.348, + text: "\u5C71\u4E0B\u7684\u519C\u6C11\u542C\u89C1\u4E86", + who: "n" + }, + { + t0: 103.348, + t1: 105.496, + text: "\u90FD\u4EE5\u4E3A\u4ED6\u53C8\u5728\u8BF4\u8C0E", + who: "n" + }, + { + t0: 105.876, + t1: 106.409, + text: "\u54FC", + who: "farmer" + }, + { + t0: 106.409, + t1: 108.54, + text: "\u53C8\u6765\u9A97\u4EBA", + who: "farmer" + }, + { + t0: 108.92, + t1: 110.96, + text: "\u8C01\u4E5F\u6CA1\u6709\u4E0A\u5C71", + who: "n" + } + ] + }, + { + id: "chaos", + start: 111.8667, + dur: 8, + voice_start: 112.367, + voice_dur: 5.976, + lines: [ + { + who: "n", + text: "\u72FC\u51B2\u8FDB\u7F8A\u7FA4\uFF0C\u7F8A\u513F\u5413\u5F97\u56DB\u5904\u4E71\u8DD1\uFF0C\u597D\u51E0\u53EA\u7F8A\u88AB\u72FC\u53FC\u8D70\u4E86\u3002", + t0: 112.367, + dur: 5.976, + subs: [ + { + t0: 112.367, + t1: 113.725, + text: "\u72FC\u51B2\u8FDB\u7F8A\u7FA4", + who: "n" + }, + { + t0: 113.725, + t1: 115.898, + text: "\u7F8A\u513F\u5413\u5F97\u56DB\u5904\u4E71\u8DD1", + who: "n" + }, + { + t0: 115.898, + t1: 118.343, + text: "\u597D\u51E0\u53EA\u7F8A\u88AB\u72FC\u53FC\u8D70\u4E86", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 112.367, + t1: 113.725, + text: "\u72FC\u51B2\u8FDB\u7F8A\u7FA4", + who: "n" + }, + { + t0: 113.725, + t1: 115.898, + text: "\u7F8A\u513F\u5413\u5F97\u56DB\u5904\u4E71\u8DD1", + who: "n" + }, + { + t0: 115.898, + t1: 118.343, + text: "\u597D\u51E0\u53EA\u7F8A\u88AB\u72FC\u53FC\u8D70\u4E86", + who: "n" + } + ] + }, + { + id: "regret", + start: 119.8667, + dur: 8, + voice_start: 120.367, + voice_dur: 5.328, + lines: [ + { + who: "n", + text: "\u592A\u9633\u843D\u5C71\u4E86\uFF0C\u653E\u7F8A\u5A03\u62B1\u7740\u5269\u4E0B\u7684\u5C0F\u7F8A\uFF0C\u4F24\u5FC3\u5730\u54ED\u4E86\u3002", + t0: 120.367, + dur: 5.328, + subs: [ + { + t0: 120.367, + t1: 121.699, + text: "\u592A\u9633\u843D\u5C71\u4E86", + who: "n" + }, + { + t0: 121.699, + t1: 124.363, + text: "\u653E\u7F8A\u5A03\u62B1\u7740\u5269\u4E0B\u7684\u5C0F\u7F8A", + who: "n" + }, + { + t0: 124.363, + t1: 125.695, + text: "\u4F24\u5FC3\u5730\u54ED\u4E86", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 120.367, + t1: 121.699, + text: "\u592A\u9633\u843D\u5C71\u4E86", + who: "n" + }, + { + t0: 121.699, + t1: 124.363, + text: "\u653E\u7F8A\u5A03\u62B1\u7740\u5269\u4E0B\u7684\u5C0F\u7F8A", + who: "n" + }, + { + t0: 124.363, + t1: 125.695, + text: "\u4F24\u5FC3\u5730\u54ED\u4E86", + who: "n" + } + ] + }, + { + id: "moral", + start: 127.8667, + dur: 10, + voice_start: 128.367, + voice_dur: 7.224, + lines: [ + { + who: "n", + text: "\u8BF4\u8C0E\u7684\u4EBA\uFF0C\u5C31\u7B97\u8BF4\u4E86\u771F\u8BDD\uFF0C\u4E5F\u6CA1\u6709\u4EBA\u518D\u76F8\u4FE1\u4ED6\u3002\u505A\u4E00\u4E2A\u8BDA\u5B9E\u7684\u5B69\u5B50\u5427\u3002", + t0: 128.367, + dur: 7.224, + subs: [ + { + t0: 128.367, + t1: 129.437, + text: "\u8BF4\u8C0E\u7684\u4EBA", + who: "n" + }, + { + t0: 129.437, + t1: 131.042, + text: "\u5C31\u7B97\u8BF4\u4E86\u771F\u8BDD", + who: "n" + }, + { + t0: 131.042, + t1: 133.183, + text: "\u4E5F\u6CA1\u6709\u4EBA\u518D\u76F8\u4FE1\u4ED6", + who: "n" + }, + { + t0: 133.183, + t1: 135.591, + text: "\u505A\u4E00\u4E2A\u8BDA\u5B9E\u7684\u5B69\u5B50\u5427", + who: "n" + } + ] + } + ], + subs: [ + { + t0: 128.367, + t1: 129.437, + text: "\u8BF4\u8C0E\u7684\u4EBA", + who: "n" + }, + { + t0: 129.437, + t1: 131.042, + text: "\u5C31\u7B97\u8BF4\u4E86\u771F\u8BDD", + who: "n" + }, + { + t0: 131.042, + t1: 133.183, + text: "\u4E5F\u6CA1\u6709\u4EBA\u518D\u76F8\u4FE1\u4ED6", + who: "n" + }, + { + t0: 133.183, + t1: 135.591, + text: "\u505A\u4E00\u4E2A\u8BDA\u5B9E\u7684\u5B69\u5B50\u5427", + who: "n" + } + ] + } + ] + }; + + // src/films/wolf/live.json + var live_default2 = []; + + // src/films/wolf/qa.json + var qa_default2 = { status: "pending", results: [] }; + + // src/films/wolf/actors.ts + var actors_exports2 = {}; + __export(actors_exports2, { + POSE: () => POSE, + bigTree: () => bigTree, + bush: () => bush, + chimneySmoke: () => chimneySmoke, + cottage: () => cottage, + eyesInDark: () => eyesInDark, + flowers: () => flowers, + person: () => person, + rock: () => rock, + sheep: () => sheep, + shoutText: () => shoutText, + shoutWaves: () => shoutWaves, + stormCloud: () => stormCloud, + terraces: () => terraces, + wolf: () => wolf + }); + var lp2 = (a, b, t) => [lerp(a[0], b[0], t), lerp(a[1], b[1], t)]; + var add2 = (a, b) => [a[0] + b[0], a[1] + b[1]]; + var rot = (p, c2, a) => { + const dx = p[0] - c2[0], dy = p[1] - c2[1], co = Math.cos(a), si = Math.sin(a); + return [c2[0] + dx * co - dy * si, c2[1] + dx * si + dy * co]; + }; + function ik(a, c2, l1, l2, bend) { + let dx = c2[0] - a[0], dy = c2[1] - a[1]; + let d = Math.hypot(dx, dy); + if (d > l1 + l2 - 1e-4) { + const k = (l1 + l2 - 1e-4) / d; + dx *= k; + dy *= k; + d = l1 + l2 - 1e-4; + c2 = [a[0] + dx, a[1] + dy]; + } + if (d < 1e-4) return [[a[0] + l1, a[1]], c2]; + const cosA = clamp((l1 * l1 + d * d - l2 * l2) / (2 * l1 * d), -1, 1); + const ang = Math.atan2(dy, dx) + bend * Math.acos(cosA); + return [[a[0] + Math.cos(ang) * l1, a[1] + Math.sin(ang) * l1], c2]; + } + function limb(g2, pts, w, col, iw, seed, endCol, split = 0) { + brush(g2, pts, w + iw * 2.2, { color: INK, alpha: 0.82, taperIn: 0.02, taperOut: 0.04, jitter: 0.12, seed }); + g2.lineCap = "round"; + g2.lineJoin = "round"; + g2.strokeStyle = col; + g2.lineWidth = w; + g2.beginPath(); + g2.moveTo(pts[0][0], pts[0][1]); + for (const p of pts.slice(1)) g2.lineTo(p[0], p[1]); + g2.stroke(); + if (endCol && split > 0) { + const a = pts[pts.length - 2], b = pts[pts.length - 1], m = lp2(a, b, split); + g2.strokeStyle = endCol; + g2.lineWidth = w * 0.9; + g2.beginPath(); + g2.moveTo(m[0], m[1]); + g2.lineTo(b[0], b[1]); + g2.stroke(); + } + } + var SKIN = "#efcfa8"; + var PROP = { + boy: { hip: -0.4, sh: -0.68, head: -0.85, hr: 0.13, ua: 0.15, fa: 0.15, th: 0.2, sn: 0.2, wTop: 0.095, wBot: 0.11, limb: 0.055 }, + man: { hip: -0.48, sh: -0.79, head: -0.91, hr: 0.088, ua: 0.17, fa: 0.165, th: 0.245, sn: 0.235, wTop: 0.11, wBot: 0.105, limb: 0.05 }, + woman: { hip: -0.47, sh: -0.77, head: -0.89, hr: 0.085, ua: 0.16, fa: 0.16, th: 0.24, sn: 0.23, wTop: 0.095, wBot: 0.115, limb: 0.045 } + }; + function person(g2, o) { + const s = o.s, face = o.face ?? 1, seed = o.seed ?? 1, t = o.t; + const P0 = PROP[o.kind]; + const iw = Math.max(1.1, s * 0.012) / s; + const alpha = o.alpha ?? 1; + if (alpha <= 0) return; + g2.save(); + g2.globalAlpha = alpha; + g2.translate(o.x + (o.shiver ?? 0) * s * 6e-3 * Math.sin(t * 53 + seed), o.y); + g2.scale(face * s, s); + const bob = o.bob ?? 0; + const hip = [0, (o.hipY ?? P0.hip) + bob]; + const lean = o.lean ?? 0; + const sh = rot([0, P0.sh + bob], hip, lean); + const neck = rot([8e-3, P0.sh - 0.035 + bob], hip, lean); + const head = rot([0.022, P0.head + bob], hip, lean); + const jacket = o.jacket ?? (o.kind === "boy" ? "#c0473a" : "#5d7385"); + const pants = o.pants ?? (o.kind === "boy" ? "#3e4a5c" : "#3a4048"); + const L = P0.limb; + const drawTool = () => { + const tl3 = o.tool; + if (!tl3) return; + if (tl3.kind === "hoe" || tl3.kind === "pole" || tl3.kind === "stick") { + brush(g2, [tl3.a, lp2(tl3.a, tl3.b, 0.5), tl3.b], 0.022, { color: "#5a4026", alpha: 0.95, taperIn: 0, taperOut: 0, jitter: 0.08, seed: seed + 70 }); + g2.strokeStyle = rgba("#a07a48", 0.9); + g2.lineWidth = 0.012; + g2.beginPath(); + g2.moveTo(tl3.a[0], tl3.a[1]); + g2.lineTo(tl3.b[0], tl3.b[1]); + g2.stroke(); + if (tl3.kind === "hoe") { + const dx = tl3.b[0] - tl3.a[0], dy = tl3.b[1] - tl3.a[1], d = Math.hypot(dx, dy) || 1; + const ux = dx / d, uy = dy / d, nx = -uy, ny = ux; + const b = tl3.b; + const q2 = [[b[0] - nx * 0.01, b[1] - ny * 0.01], [b[0] + nx * 0.13 - ux * 0.02, b[1] + ny * 0.13 - uy * 0.02], [b[0] + nx * 0.14 + ux * 0.05, b[1] + ny * 0.14 + uy * 0.05], [b[0] + ux * 0.03, b[1] + uy * 0.03]]; + g2.fillStyle = rgba("#4a4c50", 0.95); + g2.beginPath(); + g2.moveTo(q2[0][0], q2[0][1]); + for (const p of q2.slice(1)) g2.lineTo(p[0], p[1]); + g2.closePath(); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.8); + g2.lineWidth = iw; + g2.stroke(); + } + if (tl3.kind === "pole") { + for (const e of [tl3.a, tl3.b]) { + g2.strokeStyle = rgba("#5a4026", 0.8); + g2.lineWidth = 6e-3; + g2.beginPath(); + g2.moveTo(e[0], e[1]); + g2.lineTo(e[0], e[1] + 0.06); + g2.stroke(); + } + } + } + if (tl3.kind === "whip") { + brush(g2, [tl3.a, lp2(tl3.a, tl3.b, 0.5), tl3.b], 0.014, { color: "#5a4026", alpha: 0.95, taperIn: 0, taperOut: 0.3, seed: seed + 71 }); + const sw = tl3.swing ?? Math.sin(t * 3); + const end = [tl3.b[0] + 0.12 + 0.1 * sw, tl3.b[1] + 0.2 - 0.08 * sw]; + brush(g2, spline([tl3.b, [tl3.b[0] + 0.14, tl3.b[1] - 0.02 + 0.06 * sw], end], 8), 6e-3, { color: INK, alpha: 0.8, taperIn: 0, taperOut: 0.6, seed: seed + 72 }); + } + }; + const hipL = add2(hip, [-0.03, 0.01]), hipR = add2(hip, [0.03, 0.01]); + const shL = add2(sh, [-0.05, 0.02]), shR = add2(sh, [0.05, 0.02]); + const legBend = 1; + const [kneeL] = ik(hipL, o.fl, P0.th, P0.sn, -legBend); + const [kneeR] = ik(hipR, o.fr, P0.th, P0.sn, -legBend); + const [elL] = ik(shL, o.hl, P0.ua, P0.fa, 1); + const [elR] = ik(shR, o.hr, P0.ua, P0.fa, 1); + const backCol = (c2) => mixHex(c2, "#3a3530", 0.18); + const legCol = o.kind === "boy" ? SKIN : pants; + limb(g2, [hipL, kneeL, o.fl], L * 1.05, backCol(o.kind === "boy" ? pants : pants), iw, seed + 1, o.kind === "boy" ? backCol(SKIN) : void 0, o.kind === "boy" ? 0 : 0); + if (o.kind === "boy") limb(g2, [kneeL, o.fl], L * 0.95, backCol(SKIN), iw, seed + 2); + foot(g2, o.fl, iw, o.kind); + limb(g2, [shL, elL, o.hl], L * 0.95, backCol(jacket), iw, seed + 3, backCol(SKIN), o.kind === "boy" ? 0.05 : 0.55); + hand(g2, o.hl, L, iw); + drawTool(); + const torso = [ + rot([-P0.wTop, P0.sh + 0.02 + bob], hip, lean), + rot([P0.wTop * 0.9, P0.sh + 0.02 + bob], hip, lean), + rot([P0.wBot, hip[1] + 0.03], hip, lean), + rot([-P0.wBot * 0.95, hip[1] + 0.03], hip, lean) + ]; + const pantsTop = [rot([-P0.wBot * 0.95, hip[1] - 0.01], hip, lean), rot([P0.wBot, hip[1] - 0.01], hip, lean), add2(hipR, [0.03, o.kind === "boy" ? 0.09 : 0.06]), add2(hipL, [-0.04, o.kind === "boy" ? 0.09 : 0.06])]; + wash(g2, pantsTop, pants, 0.95, { lw: iw }); + if (o.kind === "boy") { + limb(g2, [hipR, kneeR], L * 1.1, pants, iw, seed + 4); + limb(g2, [kneeR, o.fr], L, SKIN, iw, seed + 5); + } else limb(g2, [hipR, kneeR, o.fr], L * 1.08, legCol, iw, seed + 4); + foot(g2, o.fr, iw, o.kind); + wash(g2, torso, jacket, 0.96, { lw: iw }); + outline(g2, torso, iw * 1.2, { seed: seed + 9, alpha: 0.8, breaks: [0.05, 0.45, 0.95] }); + if (o.kind === "boy") { + const c0 = rot([-0.04, P0.sh + 0.02 + bob], hip, lean), c1 = rot([0.05, P0.sh + 0.02 + bob], hip, lean), c2 = rot([5e-3, P0.sh + 0.09 + bob], hip, lean); + g2.fillStyle = rgba("#f4ead4", 0.95); + g2.beginPath(); + g2.moveTo(c0[0], c0[1]); + g2.lineTo(c1[0], c1[1]); + g2.lineTo(c2[0], c2[1]); + g2.closePath(); + g2.fill(); + } else { + const b0 = rot([-P0.wBot, hip[1] + 0], hip, lean), b1 = rot([P0.wBot, hip[1] + 0], hip, lean); + brush(g2, [b0, lp2(b0, b1, 0.5), b1], 0.025, { color: "#2a2622", alpha: 0.8, taperIn: 0, taperOut: 0, seed: seed + 10 }); + const k0 = rot([0.02, P0.sh + 0.03 + bob], hip, lean), k1 = rot([0.035, hip[1] + 0.02], hip, lean); + brush(g2, [k0, lp2(k0, k1, 0.5), k1], iw * 0.9, { alpha: 0.6, seed: seed + 11 }); + } + g2.strokeStyle = SKIN; + g2.lineWidth = o.kind === "boy" ? 0.05 : 0.045; + g2.lineCap = "round"; + g2.beginPath(); + g2.moveTo(neck[0], neck[1]); + g2.lineTo(lp2(neck, head, 0.6)[0], lp2(neck, head, 0.6)[1]); + g2.stroke(); + drawHead(g2, head, P0.hr, o, iw, seed); + limb(g2, [shR, elR, o.hr], L, jacket, iw, seed + 12, SKIN, o.kind === "boy" ? 0.05 : 0.55); + hand(g2, o.hr, L, iw); + g2.restore(); + } + function hand(g2, p, L, iw) { + g2.fillStyle = SKIN; + g2.beginPath(); + g2.arc(p[0], p[1], L * 0.62, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.75); + g2.lineWidth = iw; + g2.stroke(); + } + function foot(g2, p, iw, kind) { + const w = kind === "boy" ? 0.05 : 0.055; + g2.fillStyle = rgba(kind === "boy" ? "#8a6a3a" : "#2e2a26", 0.95); + g2.beginPath(); + g2.ellipse(p[0] + w * 0.4, p[1] - 8e-3, w, 0.018, 0, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.6); + g2.lineWidth = iw; + g2.stroke(); + } + function drawHead(g2, c2, r, o, iw, seed) { + const t = o.t; + g2.save(); + g2.translate(c2[0], c2[1]); + g2.rotate(o.tilt ?? 0); + const kind = o.kind; + g2.fillStyle = SKIN; + g2.beginPath(); + g2.ellipse(0, 0, r, r * 1.05, 0, 0, Math.PI * 2); + g2.fill(); + const fl = []; + for (let i = 0; i < 14; i++) { + const a = i / 14 * Math.PI * 2; + fl.push([Math.cos(a) * r, Math.sin(a) * r * 1.05]); + } + outline(g2, fl, iw * 1.1, { seed: seed + 20, alpha: 0.8, breaks: [0.08, 0.5, 0.95] }); + g2.fillStyle = rgba("#1f1c1a", 0.95); + g2.beginPath(); + if (kind === "boy") { + g2.ellipse(-r * 0.12, -r * 0.3, r * 0.98, r * 0.78, -0.15, Math.PI * 0.95, Math.PI * 2.08); + g2.fill(); + brush(g2, spline([[-r * 0.2, -r * 0.95], [-r * 0.1, -r * 1.45], [r * 0.25, -r * 1.7]], 6), r * 0.32, { color: "#1f1c1a", alpha: 0.95, taperIn: 0.1, taperOut: 0.8, seed: seed + 21 }); + g2.fillStyle = rgba("#c0473a", 0.95); + g2.beginPath(); + g2.arc(-r * 0.16, -r * 1.02, r * 0.14, 0, Math.PI * 2); + g2.fill(); + } else { + g2.ellipse(-r * 0.15, -r * 0.35, r * 0.95, r * 0.72, -0.1, Math.PI * 0.92, Math.PI * 2.05); + g2.fill(); + if (kind === "woman" || o.hat === "bun") { + g2.beginPath(); + g2.arc(-r * 0.85, -r * 0.45, r * 0.38, 0, Math.PI * 2); + g2.fill(); + } + } + g2.fillStyle = SKIN; + g2.beginPath(); + g2.ellipse(-r * 0.2, r * 0.05, r * 0.16, r * 0.22, 0, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.6); + g2.lineWidth = iw * 0.8; + g2.beginPath(); + g2.arc(-r * 0.2, r * 0.05, r * 0.14, -1.2, 1.6); + g2.stroke(); + const e = o.expr ?? {}; + const ex = r * 0.5, ey = -r * 0.08; + const blink = (t + seed * 0.71) % 3.7 < 0.12; + const look = (o.look ?? 0) * r * 0.08; + g2.fillStyle = INK; + g2.strokeStyle = INK; + g2.lineWidth = iw * 1.2; + g2.lineCap = "round"; + const eye = e.eye ?? "open"; + if (blink && eye !== "closed" && eye !== "happy") { + g2.beginPath(); + g2.moveTo(ex - r * 0.1, ey); + g2.lineTo(ex + r * 0.1, ey); + g2.stroke(); + } else if (eye === "closed" || eye === "happy") { + g2.beginPath(); + g2.arc(ex, ey + r * 0.05, r * 0.11, Math.PI * 1.1, Math.PI * 1.9); + g2.stroke(); + } else if (eye === "sad") { + g2.beginPath(); + g2.arc(ex, ey - r * 0.02, r * 0.1, 0.2, Math.PI - 0.2); + g2.stroke(); + } else if (eye === "wide") { + g2.fillStyle = "#fbf7ee"; + g2.beginPath(); + g2.ellipse(ex, ey, r * 0.14, r * 0.17, 0, 0, Math.PI * 2); + g2.fill(); + g2.stroke(); + g2.fillStyle = INK; + g2.beginPath(); + g2.arc(ex + r * 0.03 + look, ey, r * 0.065, 0, Math.PI * 2); + g2.fill(); + } else { + g2.fillStyle = INK; + g2.beginPath(); + g2.ellipse(ex + (eye === "sly" ? r * 0.06 : 0) + look, ey, r * 0.065, r * 0.085, 0, 0, Math.PI * 2); + g2.fill(); + if (eye === "sly") { + g2.beginPath(); + g2.moveTo(ex - r * 0.13, ey - r * 0.07); + g2.lineTo(ex + r * 0.12, ey - r * 0.1); + g2.stroke(); + } + } + const brow = e.brow ?? "none"; + if (brow !== "none") { + g2.lineWidth = iw * 1.4; + g2.beginPath(); + if (brow === "angry") { + g2.moveTo(ex - r * 0.14, ey - r * 0.32); + g2.lineTo(ex + r * 0.16, ey - r * 0.2); + } else if (brow === "worried") { + g2.moveTo(ex - r * 0.14, ey - r * 0.2); + g2.lineTo(ex + r * 0.15, ey - r * 0.32); + } else { + g2.moveTo(ex - r * 0.14, ey - r * 0.36); + g2.quadraticCurveTo(ex, ey - r * 0.44, ex + r * 0.15, ey - r * 0.36); + } + g2.stroke(); + } + g2.lineWidth = iw; + g2.beginPath(); + g2.moveTo(r * 0.92, -r * 0.02); + g2.quadraticCurveTo(r * 1.06, r * 0.12, r * 0.9, r * 0.17); + g2.stroke(); + if (kind !== "man") { + g2.fillStyle = rgba("#e07a6a", 0.35); + g2.beginPath(); + g2.arc(r * 0.45, r * 0.32, r * 0.16, 0, Math.PI * 2); + g2.fill(); + } + const m = e.mouth ?? "smile"; + const mx = r * 0.68, my = r * 0.5; + g2.fillStyle = rgba("#7a2a22", 0.95); + g2.strokeStyle = INK; + g2.lineWidth = iw * 1.1; + g2.beginPath(); + if (m === "smile") { + g2.arc(mx - r * 0.05, my - r * 0.12, r * 0.16, 0.35, Math.PI - 0.6); + g2.stroke(); + } else if (m === "line") { + g2.moveTo(mx - r * 0.15, my); + g2.lineTo(mx + r * 0.08, my - r * 0.02); + g2.stroke(); + } else if (m === "frown") { + g2.arc(mx - r * 0.05, my + r * 0.1, r * 0.14, Math.PI + 0.5, Math.PI * 2 - 0.4); + g2.stroke(); + } else if (m === "chew") { + const k = Math.abs(Math.sin(t * 6)); + g2.ellipse(mx, my, r * 0.1, r * (0.03 + 0.05 * k), 0, 0, Math.PI * 2); + g2.fill(); + g2.stroke(); + } else if (m === "open" || m === "shout") { + const k = m === "shout" ? 0.22 : 0.12; + g2.ellipse(mx + r * 0.02, my, r * 0.12, r * k, 0, 0, Math.PI * 2); + g2.fill(); + g2.stroke(); + } else if (m === "laugh") { + const k = 0.6 + 0.4 * Math.abs(Math.sin(t * 9)); + g2.moveTo(mx - r * 0.2, my - r * 0.06); + g2.quadraticCurveTo(mx, my + r * 0.36 * k, mx + r * 0.2, my - r * 0.08); + g2.closePath(); + g2.fill(); + g2.stroke(); + } else if (m === "cry") { + const k = 0.8 + 0.2 * Math.sin(t * 7); + g2.moveTo(mx - r * 0.18, my + r * 0.1); + g2.quadraticCurveTo(mx, my - r * 0.14 * k, mx + r * 0.16, my + r * 0.12); + g2.quadraticCurveTo(mx, my + r * 0.02, mx - r * 0.18, my + r * 0.1); + g2.fill(); + g2.stroke(); + } + if (o.hat === "straw") { + g2.fillStyle = rgba("#d8b45a", 0.95); + g2.beginPath(); + g2.moveTo(-r * 1.6, -r * 0.45); + g2.quadraticCurveTo(0, -r * 1.85, r * 1.6, -r * 0.45); + g2.quadraticCurveTo(0, -r * 0.7, -r * 1.6, -r * 0.45); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.7); + g2.lineWidth = iw; + g2.stroke(); + for (let i = -3; i <= 3; i++) { + g2.beginPath(); + g2.moveTo(i * r * 0.18, -r * 1.4); + g2.lineTo(i * r * 0.5, -r * 0.62); + g2.strokeStyle = rgba("#8a6a2a", 0.5); + g2.stroke(); + } + } else if (o.hat === "scarf") { + g2.fillStyle = rgba("#f2ece0", 0.97); + g2.beginPath(); + g2.ellipse(-r * 0.05, -r * 0.55, r * 1.04, r * 0.5, 0, Math.PI * 0.98, Math.PI * 2.02); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.6); + g2.lineWidth = iw; + g2.stroke(); + g2.beginPath(); + g2.moveTo(-r * 0.95, -r * 0.5); + g2.quadraticCurveTo(-r * 1.3, -r * 0.3, -r * 1.25, r * 0.1); + g2.lineWidth = iw * 2.5; + g2.strokeStyle = rgba("#e8e0d0", 0.95); + g2.stroke(); + } + if (o.tears && o.tears > 0) { + for (let i = 0; i < 2; i++) { + const q2 = (t * 1.4 + i * 0.5) % 1; + g2.fillStyle = rgba("#7fa4c4", 0.85 * o.tears); + g2.beginPath(); + g2.ellipse(ex + r * 0.05, ey + r * 0.2 + q2 * r * 1.3, r * 0.06, r * 0.1, 0, 0, Math.PI * 2); + g2.fill(); + } + } + if (o.sweat && o.sweat > 0) { + g2.fillStyle = rgba("#9cc0d8", 0.9 * o.sweat); + g2.beginPath(); + g2.ellipse(-r * 0.6, -r * 0.7 + t % 0.8 * r * 0.5, r * 0.08, r * 0.13, 0.3, 0, Math.PI * 2); + g2.fill(); + } + g2.restore(); + } + var POSE = { + stand: (k = "boy") => k === "boy" ? { hl: [-0.05, -0.39], hr: [0.07, -0.4], fl: [-0.04, 0], fr: [0.05, 0] } : { hl: [-0.06, -0.46], hr: [0.07, -0.47], fl: [-0.05, 0], fr: [0.06, 0] }, + walk: (ph, k = "boy", run = 0) => { + const st = (0.09 + 0.07 * run) * (k === "boy" ? 1 : 1.15), lift = 0.04 + 0.06 * run; + const s1 = Math.sin(ph), s2 = Math.sin(ph + Math.PI); + const armY = k === "boy" ? -0.42 : -0.5; + return { + fl: [st * s1, -Math.max(0, Math.cos(ph)) * lift], + fr: [st * s2, -Math.max(0, Math.cos(ph + Math.PI)) * lift], + hl: [-st * 0.8 * s1 + 0.02, armY + run * 0.08], + hr: [-st * 0.8 * s2 + 0.04 + run * 0.06, armY + run * 0.1], + bob: -Math.abs(Math.sin(ph)) * 0.015 * (1 + run), + lean: 0.06 + 0.16 * run + }; + } + }; + function sheep(g2, o) { + const s = o.s, face = o.face ?? 1, seed = o.seed ?? 1, t = o.t; + const run = o.run ?? 0, walk = o.walk ?? 0; + const ph = t * (run > 0 ? 11 : walk > 0 ? 5 : 0) + seed; + const bounce = run > 0 ? Math.abs(Math.sin(ph)) * 0.05 * run : walk > 0 ? Math.abs(Math.sin(ph)) * 0.012 : 4e-3 * Math.sin(t * 2 + seed); + const iw = Math.max(1, s * 0.016) / s; + g2.save(); + g2.globalAlpha = o.alpha ?? 1; + g2.translate(o.x, o.y); + g2.scale(face * s, s); + const by = -0.36 - bounce; + const legs = [[-0.2, 0], [-0.12, Math.PI], [0.14, Math.PI * 0.5], [0.22, Math.PI * 1.5]]; + legs.forEach(([lx, off], i) => { + const sw = run > 0 ? Math.sin(ph + off) * 0.1 * run : walk > 0 ? Math.sin(ph + off) * 0.04 : 0; + const top = [lx, by + 0.1], foot2 = [lx + sw, -0 - (run > 0 ? Math.max(0, Math.cos(ph + off)) * 0.04 : 0)]; + brush(g2, [top, lp2(top, foot2, 0.5), foot2], 0.045, { color: i < 2 ? "#4a4440" : "#2e2a27", alpha: 0.92, taperIn: 0, taperOut: 0.1, seed: seed + i }); + }); + const rx = 0.33, ry = o.lamb ? 0.21 : 0.2; + const bumps = []; + const nB = 13; + for (let i = 0; i < nB; i++) { + const a = i / nB * Math.PI * 2; + bumps.push([Math.cos(a) * rx, by + Math.sin(a) * ry]); + } + g2.fillStyle = rgba("#fbf7ee", 0.98); + g2.beginPath(); + g2.ellipse(0, by, rx * 1.02, ry * 1.04, 0, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.7); + g2.lineWidth = iw * 1.1; + for (let i = 0; i < nB; i++) { + const a = i / nB * Math.PI * 2, b = bumps[i]; + const rr = 0.075 + 0.015 * hash(i + seed); + g2.fillStyle = rgba("#fbf7ee", 0.98); + g2.beginPath(); + g2.arc(b[0], b[1], rr, a - 1.3, a + 1.3); + g2.fill(); + g2.stroke(); + } + blot(g2, -0.02, by + 0.1, 0.3, "#8a8578", 0.22, 1.2, 0.5); + for (let i = 0; i < 5; i++) { + const cx = -0.18 + i * 0.09, cy = by - 0.04 + i % 2 * 0.06; + g2.strokeStyle = rgba("#9a958a", 0.6); + g2.lineWidth = iw; + g2.beginPath(); + g2.arc(cx, cy, 0.025, 0.5, 4.5); + g2.stroke(); + } + g2.fillStyle = rgba("#fbf7ee", 1); + g2.beginPath(); + g2.arc(-rx - 0.02, by - 0.04, 0.05, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.6); + g2.stroke(); + const graze = clamp(o.graze ?? 0), look = o.look ?? 0; + const chew = graze > 0.5 ? Math.sin(t * 7 + seed) * 0.01 : 0; + const hx = lerp(0.36, 0.38, graze) + run * 0.05, hy = lerp(by - 0.1 - look * 0.06, -0.08 + chew, graze); + const ang = lerp(0.35, 1.25, graze) - run * 0.2 - look * 0.35; + g2.save(); + g2.translate(hx, hy); + g2.rotate(ang); + g2.fillStyle = rgba("#fbf7ee", 1); + g2.beginPath(); + g2.arc(-0.03, -0.07, 0.06, 0, Math.PI * 2); + g2.fill(); + const fc = o.lamb ? "#5a4c44" : "#2e2a27"; + g2.fillStyle = rgba(fc, 0.96); + g2.beginPath(); + g2.ellipse(0.04, 0, 0.11, 0.07, 0.15, 0, Math.PI * 2); + g2.fill(); + g2.beginPath(); + g2.ellipse(-0.05, 0.02, 0.06, 0.022, -0.6 - 0.3 * (o.scared ?? 0), 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = "#f6efdf"; + g2.beginPath(); + g2.arc(0.04, -0.02, o.scared ? 0.022 : 0.016, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = INK; + g2.beginPath(); + g2.arc(0.045, -0.02, 8e-3, 0, Math.PI * 2); + g2.fill(); + if ((o.baa ?? 0) > 0) { + g2.fillStyle = rgba("#8a3a32", 0.95); + g2.beginPath(); + g2.ellipse(0.13, 0.03, 0.02, 0.018 * o.baa, 0, 0, Math.PI * 2); + g2.fill(); + } + g2.restore(); + g2.restore(); + } + function wolf(g2, o) { + const s = o.s, face = o.face ?? 1, seed = o.seed ?? 3, t = o.t; + const mode = o.mode ?? "stand"; + const iw = Math.max(1.1, s * 0.012) / s; + g2.save(); + g2.globalAlpha = o.alpha ?? 1; + g2.translate(o.x, o.y); + g2.scale(face * s, s); + const run = mode === "run", sneak = mode === "sneak"; + const ph = t * (run ? 12 : sneak ? 3 : 0) + seed; + const low = sneak ? 0.07 : 0; + const bounce = run ? Math.abs(Math.sin(ph)) * 0.04 : 0; + const by = -0.4 + low - bounce; + const stretch = run ? 1.12 : 1; + const fur = "#6d6f73", dark = "#3f4145", belly = "#b9b6ad"; + const tw = run ? -0.1 : sneak ? 0.12 : 0.05 + 0.03 * Math.sin(t * 2); + wash(g2, [[-0.32 * stretch, by - 0.04], [-0.55 * stretch, by + 0.02 - tw], [-0.68 * stretch, by + 0.12 - tw], [-0.6 * stretch, by + 0.16 - tw], [-0.4 * stretch, by + 0.08]], fur, 0.92, { lw: iw }); + brush(g2, spline([[-0.32 * stretch, by - 0.04], [-0.55 * stretch, by + 0.02 - tw], [-0.68 * stretch, by + 0.12 - tw]], 6), iw * 2, { color: dark, alpha: 0.8, dry: 0.7, seed: seed + 1 }); + const legs = [[-0.22, 0, false], [0.2, Math.PI, false], [-0.17, Math.PI * 0.6, true], [0.26, Math.PI * 1.6, true]]; + for (const [lx0, off, front] of legs) { + if (front) continue; + drawLeg(lx0, off, dark); + } + function drawLeg(lx0, off, col) { + const lx = lx0 * stretch; + const sw = run ? Math.sin(ph + off) * 0.16 : sneak ? Math.sin(ph + off) * 0.05 : 0; + const top = [lx, by + 0.08], knee = [lx + sw * 0.5 + (lx < 0 ? -0.04 : 0.02), by + 0.22 - low * 0.5], foot2 = [lx + sw, -(run ? Math.max(0, Math.cos(ph + off)) * 0.06 : 0)]; + brush(g2, [top, knee, foot2], 0.055, { color: col, alpha: 0.95, taperIn: 0, taperOut: 0.35, seed: seed + lx0 * 10 }); + } + const body = [[0.3 * stretch, by - 0.1], [0.05, by - 0.14], [-0.25 * stretch, by - 0.12], [-0.36 * stretch, by - 0.02], [-0.28 * stretch, by + 0.12], [0, by + 0.12], [0.26 * stretch, by + 0.1], [0.36 * stretch, by + 0]]; + wash(g2, body, fur, 0.95, { lw: iw }); + blot(g2, 0, by + 0.06, 0.3, belly, 0.35, 1.3, 0.3); + outline(g2, body, iw * 1.3, { seed: seed + 2, alpha: 0.85, breaks: [0.02, 0.4, 0.7, 0.98] }); + for (let i = 0; i < 6; i++) { + const x0 = (-0.3 + i * 0.11) * stretch; + brush(g2, [[x0, by - 0.13], [x0 + 0.05, by - 0.17 - 0.02 * hash(i + seed)], [x0 + 0.1, by - 0.12]], 0.025, { color: dark, alpha: 0.75, dry: 0.8, seed: seed + 10 + i, taperIn: 0.1 }); + } + for (const [lx0, off, front] of legs) if (front) drawLeg(lx0, off, fur); + const howl2 = mode === "howl", snarl = mode === "snarl"; + const hx = 0.4 * stretch, hy = by - 0.12 + low * 0.6 + (run ? 0.04 : 0); + const ha = howl2 ? -0.8 : sneak ? 0.25 : run ? 0.1 : -0.05; + g2.save(); + g2.translate(hx, hy); + g2.rotate(ha); + wash(g2, [[-0.14, 0.12], [-0.06, -0.07], [0.06, -0.06], [0.08, 0.08], [-0.02, 0.16]], fur, 0.95, { lw: iw }); + const head = [[-0.06, -0.08], [0.06, -0.09], [0.24, -0.03], [0.26, 0.01], [0.2, 0.04], [0.06, 0.07], [-0.06, 0.05]]; + wash(g2, head, fur, 0.97, { lw: iw }); + outline(g2, head, iw * 1.2, { seed: seed + 3, alpha: 0.9, breaks: [0.05, 0.55, 0.95] }); + for (const [ex, k] of [[-0.03, 0.85], [0.03, 1]]) { + g2.fillStyle = rgba(k < 1 ? dark : fur, 0.97); + g2.beginPath(); + g2.moveTo(ex - 0.03, -0.07); + g2.lineTo(ex + 0.01, -0.2 * k); + g2.lineTo(ex + 0.05, -0.08); + g2.closePath(); + g2.fill(); + g2.strokeStyle = rgba(INK, 0.8); + g2.lineWidth = iw; + g2.stroke(); + } + g2.fillStyle = INK; + g2.beginPath(); + g2.arc(0.255, -0.015, 0.018, 0, Math.PI * 2); + g2.fill(); + const open = howl2 ? 0.07 : snarl ? 0.05 + 0.01 * Math.sin(t * 20) : run ? 0.03 : 0; + if (open > 0) { + g2.fillStyle = rgba("#5a1e1a", 0.95); + g2.beginPath(); + g2.moveTo(0.06, 0.04); + g2.lineTo(0.24, 0.02); + g2.lineTo(0.2, 0.02 + open); + g2.lineTo(0.06, 0.05); + g2.closePath(); + g2.fill(); + if (snarl || run) { + g2.fillStyle = "#f6efdf"; + for (let i = 0; i < 3; i++) { + const x = 0.12 + i * 0.035; + g2.beginPath(); + g2.moveTo(x, 0.025); + g2.lineTo(x + 0.01, 0.045); + g2.lineTo(x + 0.02, 0.025); + g2.fill(); + } + } + } else { + g2.strokeStyle = INK; + g2.lineWidth = iw; + g2.beginPath(); + g2.moveTo(0.08, 0.045); + g2.lineTo(0.22, 0.03); + g2.stroke(); + } + const glow = o.glow ?? (sneak ? 0.6 : 0.3); + blot(g2, 0.08, -0.035, 0.05, "#c8f05a", 0.6 * glow); + g2.fillStyle = "#c8e050"; + g2.beginPath(); + g2.ellipse(0.08, -0.035, 0.025, 0.014, -0.25, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = INK; + g2.beginPath(); + g2.ellipse(0.085, -0.035, 6e-3, 0.012, 0, 0, Math.PI * 2); + g2.fill(); + g2.strokeStyle = INK; + g2.lineWidth = iw * 1.3; + g2.beginPath(); + g2.moveTo(0.04, -0.06); + g2.lineTo(0.12, -0.045); + g2.stroke(); + if (o.carry) { + g2.save(); + g2.translate(0.16, 0.16); + g2.rotate(-0.2); + sheep(g2, { x: 0, y: 0, s: 0.42, t, face: -1, lamb: true, scared: 1, seed: 9 }); + g2.restore(); + } + g2.restore(); + g2.restore(); + } + function eyesInDark(g2, x, y, s, t, a) { + if (a <= 0) return; + const blink = (t + 0.3) % 2.6 < 0.12 ? 0.15 : 1; + for (const dx of [-0.5, 0.5]) { + blot(g2, x + dx * s, y, s * 0.9, "#c8f05a", 0.35 * a); + g2.fillStyle = rgba("#d8f070", a); + g2.beginPath(); + g2.ellipse(x + dx * s, y, s * 0.32, s * 0.16 * blink, dx * 0.4, 0, Math.PI * 2); + g2.fill(); + g2.fillStyle = rgba(INK, a); + g2.beginPath(); + g2.ellipse(x + dx * s, y, s * 0.06, s * 0.14 * blink, 0, 0, Math.PI * 2); + g2.fill(); + } + } + function cottage(g2, x, y, s, seed) { + const r = rng(seed); + const wall = [[x - s * 0.5, y], [x - s * 0.5, y - s * 0.45], [x + s * 0.5, y - s * 0.45], [x + s * 0.5, y]]; + wash(g2, wall, "#d9cba8", 0.92); + outline(g2, wall, Math.max(1.2, s * 0.012), { seed, alpha: 0.75 }); + wash(g2, [[x - s * 0.1, y], [x - s * 0.1, y - s * 0.3], [x + s * 0.08, y - s * 0.3], [x + s * 0.08, y]], "#4a3a2a", 0.85); + wash(g2, [[x + s * 0.22, y - s * 0.2], [x + s * 0.22, y - s * 0.33], [x + s * 0.38, y - s * 0.33], [x + s * 0.38, y - s * 0.2]], "#3a3530", 0.8); + const roof = [[x - s * 0.66, y - s * 0.4], [x - s * 0.3, y - s * 0.82], [x + s * 0.3, y - s * 0.84], [x + s * 0.66, y - s * 0.4]]; + wash(g2, roof, "#b8955a", 0.92); + for (let i = 0; i < 26; i++) { + const u = r(), px = lerp(x - s * 0.62, x + s * 0.62, u), top = y - s * 0.8 + Math.abs(u - 0.5) * s * 0.7; + brush(g2, [[px - s * 0.02, top], [px + s * 0.01, lerp(top, y - s * 0.42, 0.6)], [px + s * 0.02, y - s * 0.4]], Math.max(1, s * 0.01), { color: "#7a5a2a", alpha: 0.55, seed: seed + i, taperIn: 0.1, dry: 0.4 }); + } + brush(g2, [[x - s * 0.68, y - s * 0.39], [x, y - s * 0.43], [x + s * 0.68, y - s * 0.39]], Math.max(1.5, s * 0.025), { color: "#6a4a22", alpha: 0.8, seed: seed + 50, dry: 0.5 }); + } + function chimneySmoke(g2, x, y, s, t, seed, a = 0.4) { + for (let i = 0; i < 6; i++) { + const q2 = (t * 0.18 + i / 6 + hash(seed)) % 1; + blot(g2, x + Math.sin(q2 * 5 + i) * s * 0.15 + q2 * s * 0.4, y - q2 * s * 1.6, s * (0.08 + q2 * 0.25), "#9a958c", a * (1 - q2)); + } + } + function terraces(g2, x0, y0, x1, y1, rows, seed) { + const r = rng(seed); + for (let i = 0; i < rows; i++) { + const ya = lerp(y0, y1, i / rows), yb = lerp(y0, y1, (i + 1) / rows); + const pts = [], pts2 = []; + for (let k = 0; k <= 12; k++) { + const u = k / 12, x = lerp(x0, x1, u); + pts.push([x, ya + Math.sin(u * 3 + i + seed) * (yb - ya) * 0.25]); + pts2.push([x, yb + Math.sin(u * 3 + i + 1 + seed) * (yb - ya) * 0.25]); + } + const col = i % 2 ? "#a9b86a" : "#c9b56a"; + wash(g2, [...pts, ...pts2.reverse()], col, 0.42 + 0.1 * r(), { closed: true }); + brush(g2, pts, Math.max(1.2, (yb - ya) * 0.06), { color: "#5a5a32", alpha: 0.55, seed: seed + i, dry: 0.5, jitter: 0.4 }); + for (let k = 0; k < 14; k++) { + const x = lerp(x0, x1, r()), y = lerp(ya, yb, 0.3 + 0.5 * r()); + brush(g2, [[x, y], [x + 1, y - (yb - ya) * 0.18]], Math.max(1, (yb - ya) * 0.04), { color: "#4f6a3e", alpha: 0.6, seed: seed + k * 7 }); + } + } + } + function rock(g2, x, y, w, h, seed) { + const loop = [[x - w * 0.5, y], [x - w * 0.46, y - h * 0.55], [x - w * 0.2, y - h * 0.95], [x + w * 0.15, y - h], [x + w * 0.45, y - h * 0.6], [x + w * 0.52, y]]; + wash(g2, loop, "#8a8a82", 0.85); + outline(g2, loop, Math.max(1.5, w * 0.02), { seed, alpha: 0.85, breaks: [0, 0.5, 0.86] }); + const r = rng(seed); + for (let i = 0; i < 8; i++) { + const px = x + (r() - 0.5) * w * 0.7, py = y - h * (0.2 + r() * 0.6); + brush(g2, [[px, py], [px + w * 0.08, py + h * 0.08], [px + w * 0.12, py + h * 0.2]], Math.max(1.2, w * 0.018), { color: "#3a3a36", alpha: 0.6, dry: 0.7, seed: seed + i }); + } + blot(g2, x - w * 0.15, y - h * 0.7, w * 0.25, "#f2ecd8", 0.35); + } + function bigTree(g2, x, y, h, t, seed, o = {}) { + const col = o.color ?? "#5f8a4c"; + brush(g2, spline([[x, y], [x - h * 0.03, y - h * 0.3], [x + h * 0.02, y - h * 0.55]], 8), h * 0.08, { color: "#3a3027", alpha: 0.92, seed, dry: 0.5, jitter: 0.3, taperIn: 0 }); + brush(g2, spline([[x, y - h * 0.4], [x + h * 0.12, y - h * 0.52], [x + h * 0.22, y - h * 0.6]], 6), h * 0.035, { color: "#3a3027", alpha: 0.9, seed: seed + 1 }); + brush(g2, spline([[x, y - h * 0.45], [x - h * 0.14, y - h * 0.56], [x - h * 0.22, y - h * 0.62]], 6), h * 0.03, { color: "#3a3027", alpha: 0.9, seed: seed + 2 }); + const r = rng(seed); + for (let i = 0; i < 14; i++) { + const a = r() * Math.PI * 2, d = r() * h * 0.32; + const cx = x + Math.cos(a) * d * 1.2 + Math.sin(t * 0.8 + i) * h * 4e-3, cy = y - h * 0.72 + Math.sin(a) * d * 0.6; + blot(g2, cx, cy, h * (0.12 + r() * 0.08), i % 3 ? col : mixHex(col, "#2a3a22", 0.45), 0.42 + 0.1 * (o.dark ?? 0)); + } + for (let i = 0; i < 40; i++) { + const a = r() * Math.PI * 2, d = r() * h * 0.38; + g2.fillStyle = rgba("#2f4a28", 0.5); + g2.beginPath(); + g2.arc(x + Math.cos(a) * d * 1.2, y - h * 0.72 + Math.sin(a) * d * 0.6, h * 8e-3, 0, Math.PI * 2); + g2.fill(); + } + } + function bush(g2, x, y, w, seed, dark = 0) { + const r = rng(seed); + for (let i = 0; i < 7; i++) blot(g2, x + (r() - 0.5) * w, y - r() * w * 0.35, w * (0.22 + r() * 0.15), mixHex("#4f6a3e", "#1e2a1c", dark), 0.55 + 0.25 * dark, 1.3, 0.8); + for (let i = 0; i < 10; i++) { + const px = x + (r() - 0.5) * w * 1.1, py = y - r() * w * 0.5; + brush(g2, [[px, py], [px + w * 0.05 * (r() - 0.5), py - w * 0.12]], Math.max(1, w * 0.02), { color: "#1e2a1c", alpha: 0.6, seed: seed + i }); + } + } + function stormCloud(g2, x, y, w, a, seed) { + if (a <= 0) return; + const r = rng(seed); + for (let i = 0; i < 8; i++) blot(g2, x + (r() - 0.5) * w, y + (r() - 0.5) * w * 0.15, w * (0.2 + r() * 0.15), "#3e4248", a * 0.55, 1.5, 0.6); + brush(g2, spline([[x - w * 0.5, y + w * 0.08], [x - w * 0.15, y + w * 0.12], [x + w * 0.2, y + w * 0.09], [x + w * 0.5, y + w * 0.13]], 6), Math.max(1.5, w * 0.01), { color: "#2a2c30", alpha: a * 0.7, dry: 0.6, seed }); + } + function flowers(g2, x0, x1, y0, y1, n, seed, a = 0.8) { + const r = rng(seed); + for (let i = 0; i < n; i++) { + const x = lerp(x0, x1, r()), y = lerp(y0, y1, r()), c2 = ["#e8d06a", "#f2f0e6", "#d88aa0"][i % 3]; + g2.fillStyle = rgba(c2, a); + g2.beginPath(); + g2.arc(x, y, 2 + r() * 3, 0, Math.PI * 2); + g2.fill(); + } + } + function shoutText(g2, text, x0, y0, dirx, size, t, o = {}) { + if (t <= 0) return; + const chars = [...text], per = o.per ?? 0.16, col = o.color ?? "#b8322a", sc = o.scatter ?? 0, seed = o.seed ?? 1; + chars.forEach((ch, i) => { + const q2 = t - i * per; + if (q2 <= 0) return; + const p = smooth(q2 / 0.5); + const x = x0 + dirx * (size * 0.45 + i * size * 0.98 * p + q2 * size * 0.25), y = y0 - size * 0.1 * Math.sin(i * 1.3) - q2 * size * 0.08; + const k = lerp(0.35, 1, p) * (1 + 0.04 * Math.sin(q2 * 30) * (1 - p)); + let a = Math.min(1, q2 * 5) * (o.fade ?? 1); + g2.save(); + g2.translate(x, y); + g2.rotate(-0.08 * dirx + 0.05 * Math.sin(i + q2 * 3)); + if (sc > 0) { + const s2 = smooth(sc), r = rng(seed * 31 + i); + a *= 1 - s2 * 0.9; + for (let qd = 0; qd < 4; qd++) { + const ox = (qd % 2 ? 1 : -1) * size * 0.5, oy = (qd < 2 ? -1 : 1) * size * 0.5; + g2.save(); + g2.beginPath(); + g2.rect(qd % 2 ? 0 : -size, qd < 2 ? -size : 0, size, size); + g2.clip(); + g2.translate(ox * s2 * 0.8 + s2 * size * 1.5 * dirx * (0.5 + r()), oy * s2 * 0.6 - s2 * size * 0.6 * r()); + g2.rotate(s2 * (r() - 0.5) * 1.5); + g2.globalAlpha *= a; + g2.fillStyle = col; + g2.font = `${size * k}px ${FONT_BRUSH}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + g2.fillText(ch, 0, 0); + g2.restore(); + } + for (let d = 0; d < 6; d++) { + const r2 = rng(seed + i * 7 + d); + g2.fillStyle = rgba(col, 0.6 * s2 * (1 - s2)); + g2.beginPath(); + g2.arc(dirx * s2 * size * (1 + r2() * 2), (r2() - 0.5) * size * 1.5 * s2, size * 0.04 * (1 + r2()), 0, Math.PI * 2); + g2.fill(); + } + } else { + g2.globalAlpha *= a; + g2.font = `${size * k}px ${FONT_BRUSH}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + g2.shadowColor = rgba(col, 0.35); + g2.shadowBlur = size * 0.06; + g2.lineJoin = "round"; + g2.strokeStyle = rgba(PAPER, 0.7); + g2.lineWidth = size * 0.08; + g2.strokeText(ch, 0, 0); + g2.fillStyle = col; + g2.fillText(ch, 0, 0); + } + g2.restore(); + }); + } + function shoutWaves(g2, x, y, s, t, dirx, a = 1) { + for (let k = 0; k < 3; k++) { + const q2 = (t * 1.6 + k / 3) % 1; + g2.strokeStyle = rgba(INK, a * 0.6 * (1 - q2)); + g2.lineWidth = Math.max(1.2, s * 0.03); + g2.beginPath(); + g2.arc(x, y, s * (0.3 + q2 * 1.4), dirx > 0 ? -0.6 : Math.PI - 0.6, dirx > 0 ? 0.6 : Math.PI + 0.6); + g2.stroke(); + } + } + + // src/films/wolf/cues.ts + var CUE2 = { + title: { drop: 0.32, write: 1.1, seal: 4.55 }, + village: { boyIn: 4.8 }, + bored: { chew: 6.1, yawn: 8, idea: 9.8, sly: 10.3 }, + shout1: { hop: 0.6, cup: 2.4, shouts: [4.2, 5.6, 7] }, + run1: { hear: 0.6, grab: 2.8, run: 4.6, arrive: 7.3 }, + laugh1: { laugh: 0.5, angry: 4.6, leave: 6.6 }, + shout2: { days: 0.5, shouts: [4.1, 5.5], run: 7.2 }, + warn: { laugh: 0.5, point: 3.1 }, + wolf: { dark: 0.3, bush: 4, eyes: 4.9, creep: 7.3, reveal: 9.3 }, + cry: { scared: 0.5, toRock: 2.3, shouts: [4.1, 5.3, 7.2] }, + noone: { look: 0.6, shake: 2.6, wave: 5.2, work: 8 }, + chaos: { dash: 0.4, scatter: 1.8, grab: 4 }, + regret: { hug: 1.8, cry: 4.4 }, + moral: { write: 4.9, end: 8.2 } + }; + + // src/films/wolf/scenes.ts + var ks2 = (s) => s.u * (s.portrait ? 0.95 : 1); + var cam2 = (s, z, x, y, extra = {}) => ({ z: z * (s.portrait ? 1.22 : 1), x, y, ...extra }); + function sky2(g2, W2, y1, top, a0, bottom, a1) { + const gr = g2.createLinearGradient(0, 0, 0, y1); + gr.addColorStop(0, rgba(top, a0)); + gr.addColorStop(1, rgba(bottom, a1)); + g2.fillStyle = gr; + g2.fillRect(0, 0, W2, y1); + } + function band2(g2, W2, y0, y1, color, a, seed, rough = 18) { + const pts = []; + for (let i = 0; i <= 24; i++) pts.push([i / 24 * W2, y0 + nz(i * 0.6, seed) * rough]); + const gr = g2.createLinearGradient(0, y0 - rough, 0, y1); + gr.addColorStop(0, rgba(color, a)); + gr.addColorStop(1, rgba(color, a * 0.5)); + g2.fillStyle = gr; + g2.beginPath(); + g2.moveTo(0, y1); + for (const p of pts) g2.lineTo(p[0], p[1]); + g2.lineTo(W2, y1); + g2.closePath(); + g2.fill(); + brush(g2, pts, 2.2, { color, alpha: Math.min(1, a * 1.3), seed, jitter: 0.6, dry: 0.5 }); + } + function strokesH2(g2, W2, y0, y1, n, color, a, seed, u) { + const r = rng(seed); + for (let i = 0; i < n; i++) { + const y = lerp(y0, y1, r()), x = r() * W2, len = (30 + r() * 120) * u; + brush(g2, [[x, y], [x + len / 2, y + (r() - 0.5) * 4 * u], [x + len, y]], (1.5 + r() * 2.5) * u, { color, alpha: a * (0.5 + r() * 0.5), seed: seed + i, dry: 0.6, jitter: 0.5 }); + } + } + function particles2(n, seed, fn) { + const r = rng(seed); + for (let i = 0; i < n; i++) fn(r, i); + } + function writeText2(g2, text, x, y, fs, t0, per, t, o = {}) { + const chars = [...text], gap = o.gap ?? 1.08; + g2.save(); + g2.font = `${fs}px ${FONT_BRUSH}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + chars.forEach((ch, i) => { + const off = (i - (chars.length - 1) / 2) * fs * gap; + const cx = o.vertical ? x : x + off, cy = o.vertical ? y + off : y; + const p = inv(t0 + i * per, t0 + i * per + per * 1.2, t); + if (p <= 0) return; + g2.save(); + g2.beginPath(); + g2.rect(cx - fs * 0.7, cy - fs * 0.7, fs * 1.4, fs * 1.4 * easeOut(p)); + g2.clip(); + g2.shadowColor = rgba(INK, 0.35); + g2.shadowBlur = fs * 0.06; + g2.fillStyle = o.color ?? INK; + g2.fillText(ch, cx, cy); + g2.restore(); + }); + g2.restore(); + } + function marks(g2, x, y, s, a, txt, color = INK) { + if (a <= 0) return; + g2.save(); + g2.globalAlpha = a; + g2.fillStyle = color; + g2.font = `${s}px ${FONT_BRUSH}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + g2.fillText(txt, x, y); + g2.restore(); + } + function sparkle(g2, x, y, s, a) { + if (a <= 0) return; + for (let i = 0; i < 4; i++) { + const ang = i * Math.PI / 2 + 0.3; + brush(g2, [[x + Math.cos(ang) * s * 0.3, y + Math.sin(ang) * s * 0.3], [x + Math.cos(ang) * s, y + Math.sin(ang) * s]], s * 0.12, { alpha: a * 0.85, taperIn: 0.1, taperOut: 0.6, seed: i }); + } + } + function hoePose(ph, kind) { + const st = 0.5 + 0.5 * Math.sin(ph); + const th = lerp(-2.05, 0.95, Math.pow(st, 1.6)); + const piv = [0.06, -0.6]; + const d = [Math.cos(th), Math.sin(th)]; + const a = [piv[0] - d[0] * 0.12, piv[1] - d[1] * 0.12], b = [piv[0] + d[0] * 0.78, piv[1] + d[1] * 0.78]; + return { hl: [piv[0] - d[0] * 0.08, piv[1] - d[1] * 0.08], hr: [piv[0] + d[0] * 0.14, piv[1] + d[1] * 0.14], fl: [-0.1, 0], fr: [0.12, 0], lean: 0.08 + 0.18 * st, tool: { kind: "hoe", a, b } }; + void kind; + } + function carryPose(ph, kind, run, tool = "hoe") { + const w = POSE.walk(ph, kind, run); + const a = tool === "hoe" ? [-0.3, -0.62] : [-0.42, -0.77], b = tool === "hoe" ? [0.42, -0.98] : [0.42, -0.8]; + return { ...w, hr: [0.1, -0.84], tool: { kind: tool, a, b } }; + } + function standPose(kind, mode = "still", t = 0) { + const base = POSE.stand(kind); + if (mode === "hips") return { ...base, hl: [-0.12, -0.5], hr: [0.13, -0.5] }; + if (mode === "point") return { ...base, hr: [0.34, -0.8 + 0.02 * Math.sin(t * 8)], hl: [-0.12, -0.5], lean: 0.08 }; + if (mode === "wave") return { ...base, hr: [0.22 + 0.06 * Math.sin(t * 9), -0.86], hl: [-0.06, -0.46] }; + if (mode === "shade") return { ...base, hr: [0.1, -0.93] }; + return base; + } + var FARMERS = [ + { kind: "man", hat: "scarf", jacket: "#5d7385", pants: "#3a4048", tool: "hoe" }, + { kind: "man", hat: "straw", jacket: "#6b5a48", pants: "#3e3a36", tool: "pole" }, + { kind: "woman", hat: "bun", jacket: "#8a5a62", pants: "#3a3a44", tool: "hoe" }, + { kind: "man", hat: "none", jacket: "#4f6a5a", pants: "#2e3438", tool: "hoe" } + ]; + function farmer(g2, i, x, y, s, t, pose, extra = {}) { + const f = FARMERS[i % FARMERS.length]; + person(g2, { x, y, s, t, kind: f.kind, hat: f.hat, jacket: f.jacket, pants: f.pants, seed: 10 + i * 7, ...POSE.stand(f.kind), ...pose, ...extra }); + } + function boy(g2, x, y, s, t, pose, extra = {}) { + person(g2, { x, y, s, t, kind: "boy", seed: 3, ...POSE.stand("boy"), ...pose, ...extra }); + } + var SHOUT = { hl: [0.16, -0.8], hr: [0.15, -0.78], fl: [-0.08, 0], fr: [0.1, 0], lean: 0.12 }; + var LAUGH = (t) => ({ hl: [0.05, -0.5], hr: [0.08, -0.52], fl: [-0.06, 0], fr: [0.08, 0], lean: -0.14 - 0.05 * Math.abs(Math.sin(t * 9)), bob: -0.01 * Math.abs(Math.sin(t * 9)), expr: { eye: "happy", mouth: "laugh" } }); + var SCARED = (t) => ({ hl: [-0.14, -1 + 0.02 * Math.sin(t * 20)], hr: [0.2, -0.98], fl: [-0.09, 0], fr: [0.09, 0], lean: -0.05, shiver: 1, expr: { eye: "wide", mouth: "shout", brow: "worried" } }); + var SIT = { hipY: -0.13, fl: [0.22, 0], fr: [0.26, -0.01], hl: [0.2, -0.34], hr: [0.22, -0.32], lean: 0.2 }; + var hillY = (s, x) => s.gy + 30 * s.u + 110 * s.u * Math.pow((x - s.W * 0.48) / (s.W * 0.75), 2); + function bgHill(mood, o = {}) { + return (s) => { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + const seed = o.seed ?? 1; + if (mood === "day") sky2(g2, W2, gy, "#9fbccb", 0.22, "#ffffff", 0); + if (mood === "storm") sky2(g2, W2, gy + 40 * u, "#3e464e", 0.45, "#8a9094", 0.2); + if (mood === "sunset") { + sky2(g2, W2, gy, "#e08a50", 0.35, "#f0c890", 0.45); + sun(g2, X(-0.35), gy - 60 * u, 110 * u, "#c8463a", 0.9); + } + if (mood === "dusk") sky2(g2, W2, gy, "#4a5068", 0.4, "#d8a878", 0.35); + const mc = mood === "storm" ? "#4a5258" : mood === "sunset" ? "#7a5048" : mood === "dusk" ? "#3a3a48" : "#7d8a8c"; + mountains(g2, W2, gy - 40 * u, 170 * u, mc, mood === "day" ? 0.3 : 0.45, 3 + seed); + const vx = X(-0.8), vy = gy + 10 * u; + for (let i = 0; i < 4; i++) cottage(g2, vx + i * 46 * u - i % 2 * 10 * u, vy + i % 2 * 14 * u, 40 * u, 30 + i); + const pts = []; + for (let i = 0; i <= 30; i++) { + const x = i / 30 * W2; + pts.push([x, hillY(s, x) + nz(i * 0.7, seed) * 6 * u]); + } + const gc = mood === "storm" ? "#6a7a5a" : mood === "sunset" ? "#8a8a50" : mood === "dusk" ? "#4a5a48" : "#8fae6a"; + const gr = g2.createLinearGradient(0, gy, 0, H2); + gr.addColorStop(0, rgba(gc, 0.6)); + gr.addColorStop(1, rgba(mixHex(gc, "#4a5a32", 0.4), 0.5)); + g2.fillStyle = gr; + g2.beginPath(); + g2.moveTo(0, H2); + for (const p of pts) g2.lineTo(p[0], p[1]); + g2.lineTo(W2, H2); + g2.closePath(); + g2.fill(); + brush(g2, pts, 3 * u, { color: "#3e5a2e", alpha: 0.7, seed, dry: 0.5, jitter: 0.5 }); + strokesH2(g2, W2, gy + 80 * u, H2, 30, "#4f6a3e", 0.35, seed + 9, u); + if (mood === "day") flowers(g2, 0, W2, gy + 90 * u, H2 - 20 * u, 40, seed + 4); + if (o.tree !== false) bigTree(g2, X(0.62), hillY(s, X(0.62)) + 10 * u, 520 * u, 0, 17, { color: mood === "storm" ? "#4a6a3e" : mood === "sunset" ? "#6a7040" : "#5f8a4c" }); + if (o.rock !== false) rock(g2, X(0.05), hillY(s, X(0.05)) + 40 * u, 190 * u, 110 * u, 5); + }; + } + var rockTop = (s) => [s.X(0.05), hillY(s, s.X(0.05)) + 40 * s.u - 105 * s.u]; + function bgVillage(storm = false) { + return (s) => { + const { g: g2, W: W2, H: H2, u, gy, X } = s; + sky2(g2, W2, gy, storm ? "#4e565e" : "#a9c3c9", storm ? 0.4 : 0.2, "#ffffff", 0); + mountains(g2, W2, gy - 70 * u, 200 * u, storm ? "#4a5258" : "#7d8a8c", 0.35, 41); + mountains(g2, W2, gy - 20 * u, 120 * u, storm ? "#3a4248" : "#5e6c62", 0.4, 47); + band2(g2, W2, gy - 10 * u, H2, storm ? "#7a8a6a" : "#a8b878", 0.35, 21); + terraces(g2, X(-0.1), gy + 30 * u, W2 + 20 * u, H2, 6, 9); + for (const [fx, dy, sz, sd] of [[-0.88, 40, 150, 1], [-0.62, 70, 130, 2], [-0.92, 160, 170, 3]]) cottage(g2, X(fx), gy + dy * u, sz * u, sd); + const path = spline([[X(-0.55), H2 + 20 * u], [X(-0.35), gy + 120 * u], [X(-0.2), gy + 10 * u], [X(0.1), gy - 60 * u]], 10); + for (let i = 0; i < path.length - 1; i++) brush(g2, [path[i], path[i + 1]], lerp(90, 14, i / path.length) * u, { color: "#d8c49a", alpha: 0.55, taperIn: 0, taperOut: 0, jitter: 0.1, seed: i }); + if (storm) { + g2.fillStyle = rgba("#2a3038", 0.18); + g2.fillRect(0, 0, W2, H2); + } + }; + } + var fieldSpot = (s, i) => [s.X(0.05 + i * 0.26), s.gy + (120 + i % 2 * 70) * s.u]; + function bgSlope(s) { + const { g: g2, W: W2, H: H2, u, X } = s; + sky2(g2, W2, H2 * 0.5, "#9fbccb", 0.22, "#ffffff", 0); + mountains(g2, W2, H2 * 0.42, 200 * u, "#7d8a8c", 0.3, 61); + const slope = [[-20 * u, H2 * 0.62], [X(-0.3), H2 * 0.5], [X(0.3), H2 * 0.32], [X(0.75), H2 * 0.24], [W2 + 20 * u, H2 * 0.26], [W2 + 20 * u, H2 + 20 * u], [-20 * u, H2 + 20 * u]]; + wash(g2, slope, "#8fae6a", 0.55); + brush(g2, spline(slope.slice(0, 5), 10), 3 * u, { color: "#3e5a2e", alpha: 0.7, seed: 3, dry: 0.5 }); + terraces(g2, -20 * u, H2 * 0.74, X(-0.1), H2 + 20 * u, 4, 13); + for (const [fx, fy, sz, sd] of [[-0.9, 0.72, 120, 1], [-0.7, 0.8, 100, 2]]) cottage(g2, X(fx), H2 * fy, sz * u, sd); + const path = slopePath(s); + for (let i = 0; i < path.length - 1; i++) brush(g2, [path[i], path[i + 1]], 34 * u, { color: "#d8c49a", alpha: 0.6, taperIn: 0, taperOut: 0, jitter: 0.1, seed: i }); + strokesH2(g2, W2, H2 * 0.4, H2, 34, "#4f6a3e", 0.3, 7, u); + bigTree(g2, X(0.88), H2 * 0.26, 360 * u, 0, 21); + rock(g2, X(0.7), H2 * 0.27, 120 * u, 70 * u, 8); + } + var slopePath = (s) => spline([[s.X(-0.85), s.H * 0.92], [s.X(-0.45), s.H * 0.68], [s.X(-0.05), s.H * 0.55], [s.X(0.3), s.H * 0.4], [s.X(0.6), s.H * 0.3]], 12); + var along = (pts, p) => { + const f = clamp(p) * (pts.length - 1), i = Math.min(pts.length - 2, Math.floor(f)), k = f - i; + return [lerp(pts[i][0], pts[i + 1][0], k), lerp(pts[i][1], pts[i + 1][1], k)]; + }; + var FLOCK = [ + { f: -0.55, dy: 60, s: 1, face: 1, seed: 1 }, + { f: -0.32, dy: 120, s: 1.1, face: -1, seed: 2 }, + { f: -0.15, dy: 40, s: 0.9, face: 1, seed: 3 }, + { f: 0.28, dy: 140, s: 1.15, face: -1, seed: 4 }, + { f: 0.45, dy: 70, s: 0.95, face: 1, seed: 5 }, + { f: 0.82, dy: 110, s: 1.05, face: -1, seed: 6 }, + { f: -0.75, dy: 150, s: 0.7, face: 1, seed: 7 } + ]; + function flock(s, o = () => ({})) { + const { g: g2, u, t, X } = s; + const k = ks2(s); + FLOCK.forEach((sp, i) => { + const e = o(sp, i); + const x = X(sp.f) + (e.dx ?? 0), y = hillY(s, X(sp.f)) + sp.dy * u + (e.dy ?? 0); + const graze = e.graze ?? (0.5 + 0.5 * Math.sin(t * 0.5 + sp.seed * 2) > 0.35 ? 1 : 0.2); + sheep(g2, { x, y, s: 150 * k * sp.s, t, face: e.face ?? sp.face, seed: sp.seed, graze, look: e.look, run: e.run, alpha: e.alpha, baa: e.baa, scared: e.scared }); + }); + } + var SCENES3 = { + title: { + label: "\u7247\u5934", + draw(s) { + const { g: g2, W: W2, H: H2, u, t, portrait } = s; + const C = CUE2.title; + const cx = W2 / 2, cy = portrait ? H2 * 0.37 : H2 * 0.42; + if (t < C.drop) { + g2.fillStyle = rgba(INK, 0.95); + g2.beginPath(); + g2.ellipse(cx, lerp(-30 * u, cy, easeIn(t / C.drop)), 9 * u, 15 * u, 0, 0, Math.PI * 2); + g2.fill(); + } + const p = easeOut(inv(C.drop, 2.4, t)); + if (p > 0) { + const R = (portrait ? 420 : 350) * u * p; + wash(g2, blobLoop(cx, cy, R, 9, 0.06, 32), "#8a9a7a", 0.14 + 0.16 * (1 - p), { edge: 1.4 }); + particles2(10, 71, (r) => { + const a2 = r() * Math.PI * 2, d = (R * 0.9 + 60 * u * r()) * easeOut(inv(C.drop, 0.8, t)); + g2.fillStyle = rgba(INK, 0.6 * (1 - 0.6 * p)); + g2.beginPath(); + g2.arc(cx + Math.cos(a2) * d, cy + Math.sin(a2) * d * 0.9, (3 + r() * 8) * u, 0, Math.PI * 2); + g2.fill(); + }); + } + const a = smooth(inv(0.8, 2.6, t)); + if (a > 0) { + g2.save(); + g2.globalAlpha = a; + const by = portrait ? H2 * 0.82 : H2 * 0.88; + mountains(g2, W2, by - 40 * u, 120 * u, "#7d8a8c", 0.3, 5, { soft: 1 }); + band2(g2, W2, by, H2, "#8fae6a", 0.45, 3, 10 * u); + bush(g2, W2 * 0.86, by + 10 * u, 160 * u, 4, 0.7); + g2.restore(); + eyesInDark(g2, W2 * 0.86, by - 20 * u, 14 * u, t, smooth(inv(3.6, 4.2, t)) * a); + } + const fs = (portrait ? 230 : 210) * u; + writeText2(g2, "\u72FC\u6765\u4E86", cx, cy, fs, C.write, 0.45, t, { vertical: portrait }); + const sa = smooth(inv(2.9, 3.6, t)); + if (sa > 0) { + g2.save(); + g2.globalAlpha = sa; + g2.font = `${46 * u}px ${FONT_KAI}`; + g2.fillStyle = rgba(INK, 0.85); + g2.textAlign = "center"; + g2.textBaseline = "middle"; + const sy = portrait ? cy + fs * 1.95 : cy + fs * 0.95; + g2.fillText("\u4F0A \u7D22 \u5BD3 \u8A00", cx, sy); + brush(g2, [[cx - 150 * u, sy + 38 * u], [cx, sy + 44 * u], [cx + 150 * u, sy + 36 * u]], 4 * u, { alpha: 0.5 * sa, seed: 8, dry: 0.6 }); + g2.restore(); + } + const sp = inv(C.seal, C.seal + 0.3, t); + if (sp > 0) { + const sc = lerp(1.5, 1, easeOut(sp)), sx = portrait ? cx + fs * 0.78 : cx + fs * 1.82, sy = portrait ? cy + fs * 1.15 : cy + fs * 0.32; + g2.save(); + g2.translate(sx, sy); + g2.scale(sc, sc); + g2.rotate(-0.04); + seal(g2, 0, 0, 92 * u, ["\u5BD3\u8A00"], { alpha: Math.min(1, sp * 2) }); + g2.restore(); + } + const wy = portrait ? H2 * 0.8 : H2 * 0.92; + const wp = inv(1.8, 6.6, t); + if (wp > 0) { + const x0 = lerp(-150 * u, W2 * 0.6, wp); + for (let i = 0; i < 3; i++) sheep(g2, { x: x0 + 150 * u - i * 120 * u, y: wy + i % 2 * 8 * u, s: 85 * u, t, walk: 1, seed: i + 1, graze: 0 }); + boy(g2, x0 - 260 * u, wy, 170 * u, t, POSE.walk(t * 6, "boy"), { tool: { kind: "whip", a: [0.07, -0.42], b: [0.28, -0.72] }, expr: { mouth: "smile" } }); + } + }, + enterAt: [0.5, 0.45] + }, + village: { + label: "\u5C71\u6751", + bg: bgVillage(false), + draw(s) { + const { g: g2, u, t, gy, X, H: H2 } = s; + const k = ks2(s); + for (const [fx, dy, sz] of [[-0.88, 40, 150], [-0.62, 70, 130]]) chimneySmoke(g2, X(fx) + sz * 0.3 * u, gy + dy * u - sz * 0.8 * u, 60 * u, t, fx * 10); + for (let i = 0; i < 3; i++) { + const [x, y] = fieldSpot(s, i); + farmer(g2, i, x, y, 330 * k, t, hoePose(t * 2.2 + i * 1.7, FARMERS[i].kind), { face: i === 1 ? -1 : 1 }); + } + const p = easeInOut(inv(CUE2.village.boyIn - 1.2, s.D + 1, t)); + const path = spline([[X(-0.6), H2 + 60 * u], [X(-0.4), gy + 160 * u], [X(-0.22), gy + 40 * u], [X(0.05), gy - 40 * u]], 10); + for (let i = 0; i < 4; i++) { + const q2 = clamp(p * 1.05 - 0.02 + i * 0.05); + if (q2 <= 0) continue; + const [x, y] = along(path, q2); + sheep(g2, { x, y, s: 130 * k * (1 - q2 * 0.5), t, walk: 1, seed: i + 2, graze: 0 }); + } + const qb = clamp(p * 1.05 - 0.06); + if (qb > 0) { + const [x, y] = along(path, qb); + boy(g2, x, y, 250 * k * (1 - qb * 0.5), t, POSE.walk(t * 6, "boy"), { tool: { kind: "whip", a: [0.07, -0.42], b: [0.28, -0.72] }, expr: { mouth: "smile" } }); + } + }, + cam(s) { + const p = easeInOut(inv(CUE2.village.boyIn - 0.5, s.D, s.t)); + return cam2(s, lerp(1.08, 1.35, p), lerp(s.W / 2, s.X(-0.35), p), lerp(s.gy + 60 * s.u, s.gy + 120 * s.u, p)); + }, + enterAt: [0.5, 0.5] + }, + bored: { + label: "\u5C71\u5761\u4E0A", + bg: bgHill("day", { rock: false }), + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks2(s), C = CUE2.bored; + cloud(g2, X(-0.3) + t * 12 * u, s.gy - 420 * u, 380 * u, 3, 0.25); + flock(s); + const bx = X(0.42), by = hillY(s, X(0.42)) + 50 * u; + const yawn = smooth(inv(C.yawn, C.yawn + 0.4, t)) * (1 - smooth(inv(C.yawn + 1.3, C.yawn + 1.7, t))); + const idea = t > C.idea; + g2.save(); + g2.translate(bx, by); + g2.rotate(-Math.PI / 2); + boy(g2, 0, 0, 300 * k, t, { + hl: yawn > 0.3 ? [-0.06, -1.22] : [-0.05, -0.96], + hr: yawn > 0.3 ? [0.04, -1.25] : [0.01, -0.98], + fl: [0, 0], + fr: [0.02, -0.2], + lean: 0, + expr: { eye: idea ? t > C.sly ? "sly" : "wide" : yawn > 0.3 ? "closed" : "open", mouth: yawn > 0.3 ? "shout" : t > C.chew && !idea ? "chew" : idea ? "smile" : "line", brow: idea ? "up" : "none" }, + look: t > C.sly ? 1 : 0 + }); + g2.restore(); + if (t > C.chew - 0.3 && yawn < 0.3) { + const mx = bx - 300 * k * 0.8, my = by - 300 * k * 0.2; + brush(g2, [[mx, my], [mx + 40 * u, my - 30 * u + 6 * u * Math.sin(t * 6)], [mx + 80 * u, my - 70 * u + 10 * u * Math.sin(t * 6)]], 4 * u, { color: "#5f8a3c", alpha: 0.9, taperIn: 0, taperOut: 0.7, seed: 4 }); + } + sparkle(g2, bx - 300 * k * 0.98, by - 300 * k * 0.42, 40 * u, smooth(inv(C.idea, C.idea + 0.2, t)) * (1 - smooth(inv(C.idea + 1.2, C.idea + 1.6, t)))); + if (yawn > 0.3) marks(g2, bx - 300 * k * 1.05, by - 300 * k * 0.5, 50 * u, yawn, "\uFF5E"); + }, + cam(s) { + const C = CUE2.bored, bx = s.X(0.42), by = hillY(s, s.X(0.42)); + const p1 = easeInOut(inv(C.yawn - 4.5, C.yawn - 2, s.t)), p2 = easeInOut(inv(C.idea - 0.5, C.idea + 0.8, s.t)); + return cam2(s, lerp(lerp(1.05, 1.5, p1), 2.2, p2), lerp(lerp(s.W / 2, bx - 80 * s.u, p1), bx - 230 * s.u, p2), lerp(lerp(s.gy + 50 * s.u, by, p1), by - 30 * s.u, p2)); + }, + enterAt: [0.5, 0.5] + }, + shout1: { + label: "\u7B2C\u4E00\u6B21\u558A", + bg: bgHill("day"), + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks2(s), C = CUE2.shout1; + flock(s, (sp) => t > C.shouts[0] + 0.1 ? { graze: 0, look: 0.6, scared: 1, face: sp.f > 0.05 ? -1 : 1 } : {}); + const [rx, ry] = rockTop(s); + const hop = inv(C.hop, C.hop + 0.7, t); + const x = lerp(X(-0.2), rx, easeInOut(hop)), y = lerp(hillY(s, X(-0.2)) + 30 * u, ry, easeInOut(hop)) - Math.sin(Math.PI * hop) * 90 * u; + const cupping = t > C.cup; + const shouting = C.shouts.some((st) => t > st && t < st + 1.1); + boy(g2, x, y, 300 * k, t, cupping ? SHOUT : hop > 0 && hop < 1 ? { hl: [-0.12, -0.7], hr: [0.15, -0.72], fl: [-0.04, -0.06], fr: [0.08, -0.08] } : POSE.stand("boy"), { + face: -1, + expr: { eye: shouting ? "closed" : "sly", mouth: shouting ? "shout" : "smile", brow: shouting ? "up" : "none" }, + tool: cupping ? void 0 : { kind: "whip", a: [0.07, -0.42], b: [0.28, -0.72] } + }); + if (cupping) { + const mx = x - 300 * k * 0.25, my = y - 300 * k * 0.8; + if (shouting) shoutWaves(g2, mx, my, 120 * u, t, -1, 1); + C.shouts.forEach((st, i) => shoutText(g2, i === 2 ? "\u5FEB\u6765\u5440\uFF01" : "\u72FC\u6765\u4E86\uFF01", mx - 40 * u, my - 140 * u - i * 40 * u, -1, 130 * u, t - st, { fade: 1 - smooth(inv(st + 2.6, st + 3.4, t)), seed: i })); + } + }, + cam(s) { + const [rx, ry] = rockTop(s), p = easeInOut(inv(3.8, 5, s.t)); + return cam2(s, lerp(1.6, 1.12, p), lerp(rx, s.X(-0.25), p), lerp(ry - 80 * s.u, s.gy - 20 * s.u, p)); + }, + enterAt: [0.3, 0.6] + }, + run1: { + label: "\u519C\u6C11\u4E0A\u5C71", + bg: bgSlope, + draw(s) { + const { g: g2, u, t, X, H: H2 } = s; + const k = ks2(s), C = CUE2.run1; + const path = slopePath(s); + boy(g2, X(0.7), H2 * 0.27 - 66 * u, 140 * k, t, { hl: [0.05, -0.5], hr: [0.08, -0.52] }, { face: -1, expr: { eye: "happy", mouth: "smile" } }); + for (let i = 0; i < 4; i++) { + const f = FARMERS[i]; + const p = easeInOut(inv(C.run + i * 0.15, C.arrive + i * 0.2, t)) * (0.92 - i * 0.07); + const start = along(path, 0.04 + i * 0.03); + const [x, y] = p > 0 ? along(path, 0.04 + i * 0.03 + p) : start; + const fx = x + (p > 0 ? 0 : (i - 1.5) * 90 * u), fy = y + i % 2 * 20 * u; + let pose; + if (t < C.grab) pose = t > C.hear + i * 0.2 ? { ...standPose(f.kind, "shade"), tilt: -0.25 } : hoePose(t * 2.2 + i, f.kind); + else if (p <= 0) pose = carryPose(0, f.kind, 0, f.tool); + else if (t < C.arrive + i * 0.2) pose = carryPose(t * 12 + i, f.kind, 1, f.tool); + else pose = { ...standPose(f.kind, "shade") }; + const arrived = t > C.arrive + i * 0.2; + const lookFace = arrived ? Math.sin(t * 2.5 + i * 1.3) > 0 ? 1 : -1 : 1; + farmer(g2, i, fx, fy, 300 * k, t, pose, { face: lookFace, expr: { eye: "wide", mouth: t > C.grab ? "open" : "line", brow: arrived ? "worried" : "up" }, sweat: arrived ? 1 : 0, tilt: arrived ? 0 : -0.1 }); + if (arrived) marks(g2, fx + 10 * u, fy - 300 * k * 1.12, 70 * u, smooth(inv(C.arrive + 0.3 + i * 0.2, C.arrive + 0.6 + i * 0.2, t)), "\uFF1F"); + } + }, + cam(s) { + const C = CUE2.run1, path = slopePath(s); + const p = easeInOut(inv(C.run, C.arrive + 0.6, s.t)) * 0.85; + const [x, y] = along(path, 0.05 + p); + return cam2(s, 1.55, x + 80 * s.u, y - 90 * s.u); + }, + enterAt: [0.2, 0.8] + }, + laugh1: { + label: "\u54C8\u54C8\u5927\u7B11", + bg: bgHill("day"), + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks2(s), C = CUE2.laugh1; + flock(s); + const [rx, ry] = rockTop(s); + const laughing = t > C.laugh && t < C.angry + 0.8; + boy(g2, rx, ry, 300 * k, t, laughing ? LAUGH(t) : { ...POSE.stand("boy"), expr: { eye: "happy", mouth: "smile" } }, { face: -1 }); + if (laughing) soundMarks(g2, rx - 120 * u, ry - 330 * k, 50 * u, 0.8, 3, -1); + for (let i = 0; i < 4; i++) { + const f = FARMERS[i]; + const leave = easeIn(inv(C.leave + i * 0.25, C.leave + 3 + i * 0.25, t)); + const x = X(-0.85 + i * 0.17) - leave * 700 * u, y = hillY(s, X(-0.85 + i * 0.17)) + (30 + i % 2 * 40) * u; + const angry = t > C.angry; + const pose = leave > 0 ? carryPose(t * 7 + i, f.kind, 0, f.tool) : angry ? standPose(f.kind, "hips") : { ...standPose(f.kind, "shade") }; + farmer(g2, i, x, y, 320 * k, t, pose, { + face: leave > 0 ? -1 : 1, + tilt: angry && leave <= 0 ? 0.12 * Math.sin(t * 7 + i) : 0, + sweat: angry ? 0 : 1, + expr: { eye: "open", mouth: angry ? "frown" : "open", brow: angry ? "angry" : "worried" } + }); + } + }, + cam(s) { + const [rx] = rockTop(s); + return cam2(s, lerp(1.3, 1.2, s.t / s.D), lerp(rx, s.X(-0.35), easeInOut(inv(CUE2.laugh1.angry - 0.5, CUE2.laugh1.angry + 1, s.t))), s.gy + 30 * s.u); + }, + enterAt: [0.5, 0.4] + }, + shout2: { + label: "\u53C8\u558A\u72FC\u6765\u4E86", + bg: bgHill("day"), + draw(s) { + const { g: g2, W: W2, H: H2, u, t, X } = s; + const k = ks2(s), C = CUE2.shout2; + const dp = inv(0, 3, t); + if (dp < 1) { + const night = Math.max(0, Math.sin(dp * Math.PI * 2 - Math.PI / 2) * 0.5 + 0.5) * 0.3 * (1 - dp); + g2.fillStyle = rgba("#2a3040", night); + g2.fillRect(-W2, -H2, W2 * 3, H2 * 3); + const sa = dp * 2 % 1; + sun(g2, lerp(X(-1), X(1), sa), s.gy - Math.sin(sa * Math.PI) * 360 * u, 50 * u, "#d65a40", 0.8 * (1 - dp)); + } + flock(s, (sp) => t > C.shouts[0] ? { graze: 0, look: 0.5, face: sp.f > 0.05 ? -1 : 1 } : {}); + const [rx, ry] = rockTop(s); + const cupping = t > C.shouts[0] - 0.4; + const shouting = C.shouts.some((st) => t > st && t < st + 1.1); + boy(g2, rx, ry, 300 * k, t, cupping ? SHOUT : POSE.stand("boy"), { face: -1, expr: { eye: shouting ? "closed" : "sly", mouth: shouting ? "shout" : "smile" } }); + if (cupping) { + const mx = rx - 300 * k * 0.25, my = ry - 300 * k * 0.8; + if (shouting) shoutWaves(g2, mx, my, 120 * u, t, -1, 1); + C.shouts.forEach((st, i) => shoutText(g2, "\u72FC\u6765\u4E86\uFF01", mx - 40 * u, my - 140 * u - i * 40 * u, -1, 120 * u, t - st, { fade: 1 - smooth(inv(st + 2.2, st + 3, t)), seed: i + 5 })); + } + for (let i = 0; i < 4; i++) { + const f = FARMERS[i]; + const p = easeOut(inv(C.run + i * 0.2, C.run + 2.6 + i * 0.2, t)); + if (p <= 0) continue; + const x = lerp(X(-1.25) - i * 90 * u, X(-0.75 + i * 0.13), p), y = hillY(s, X(-0.75 + i * 0.13)) + (40 + i % 2 * 40) * u + (1 - p) * 120 * u; + farmer(g2, i, x, y, 320 * k, t, p < 1 ? carryPose(t * 12 + i, f.kind, 1, f.tool) : standPose(f.kind, "shade"), { expr: { eye: "wide", mouth: "open", brow: "worried" }, sweat: 1 }); + } + }, + cam(s) { + const [rx, ry] = rockTop(s); + const p = easeInOut(inv(CUE2.shout2.run - 0.4, CUE2.shout2.run + 1.2, s.t)); + return cam2(s, lerp(1.45, 1.05, p), lerp(rx - 60 * s.u, s.W / 2 - 100 * s.u, p), lerp(ry - 60 * s.u, s.gy, p)); + }, + enterAt: [0.5, 0.3] + }, + warn: { + label: "\u8B66\u544A", + bg: bgHill("day"), + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks2(s), C = CUE2.warn; + flock(s); + const [rx, ry] = rockTop(s); + boy(g2, rx, ry, 300 * k, t, t < C.point + 0.4 ? LAUGH(t) : { ...POSE.stand("boy"), lean: 0.05 }, { face: -1, expr: t < C.point + 0.4 ? { eye: "happy", mouth: "laugh" } : { eye: "open", mouth: "line", brow: "worried" } }); + for (let i = 0; i < 4; i++) { + const f = FARMERS[i]; + const lead = i === 0; + const step = lead ? easeOut(inv(C.point - 0.3, C.point + 0.4, t)) : 0; + const x = X(-0.82 + i * 0.16) + step * 140 * u, y = hillY(s, X(-0.82 + i * 0.16)) + (30 + i % 2 * 40) * u; + const pose = lead && t > C.point ? standPose(f.kind, "point", t) : standPose(f.kind, "hips"); + farmer(g2, i, x, y, 330 * k, t, pose, { expr: { eye: "open", mouth: lead && t > C.point ? "open" : "frown", brow: "angry" }, tilt: lead && t > C.point ? -0.05 : 0 }); + if (lead && t > C.point) marks(g2, x + 60 * u, y - 330 * k * 1.15, 90 * u, smooth(inv(C.point, C.point + 0.2, t)), "\uFF01", "#b8322a"); + } + }, + cam(s) { + const [rx, ry] = rockTop(s); + const p = easeInOut(inv(CUE2.warn.point - 0.4, CUE2.warn.point + 0.6, s.t)); + return cam2(s, lerp(1.6, 1.45, p), lerp(rx - 40 * s.u, s.X(-0.62), p), lerp(ry - 50 * s.u, s.gy - 10 * s.u, p)); + }, + enterAt: [0.3, 0.5] + }, + wolf: { + label: "\u72FC\u771F\u7684\u6765\u4E86", + bg: bgHill("storm", { tree: false }), + draw(s) { + const { g: g2, W: W2, H: H2, u, t, X } = s; + const k = ks2(s), C = CUE2.wolf; + for (let i = 0; i < 4; i++) stormCloud(g2, X(-1.2 + i * 0.7) + t * 30 * u, s.gy - (380 + i % 2 * 90) * u, 520 * u, smooth(inv(C.dark, C.dark + 3, t)), 40 + i); + for (let i = 0; i < 18; i++) grass(g2, i / 18 * W2 + hash(i) * 40 * u, H2 - hash(i * 3) * 160 * u, 70 * u, t, 100 + i, "#3e5a2e", 0.6, 2.5); + flock(s, () => ({ graze: t > C.creep ? 0 : 0.6 + 0.4 * Math.sin(t * 2), look: t > C.creep ? 0.4 : 0, scared: t > C.creep ? 1 : 0 })); + const bx = X(0.78), by = hillY(s, X(0.78)) + 80 * u; + const creep = easeInOut(inv(C.creep, C.reveal, t)); + if (t > C.creep - 0.2) { + const mode = t > C.reveal ? "snarl" : "sneak"; + wolf(g2, { x: bx - creep * 300 * u, y: by + 10 * u, s: 330 * k, t, face: -1, mode, glow: 0.8 }); + } + bush(g2, bx + 40 * u, by + 30 * u, 260 * u, 11, 0.85); + bush(g2, bx + 180 * u, by + 10 * u, 200 * u, 12, 0.9); + eyesInDark(g2, bx + 20 * u, by - 70 * u, 18 * u, t, smooth(inv(C.eyes, C.eyes + 0.6, t)) * (1 - smooth(inv(C.creep, C.creep + 0.3, t)))); + const fl = t > C.reveal && t < C.reveal + 0.18 ? 0.6 : t > C.reveal + 0.28 && t < C.reveal + 0.36 ? 0.35 : 0; + if (fl > 0) { + g2.fillStyle = rgba("#f6f4ee", fl); + g2.fillRect(-W2, -H2, W2 * 3, H2 * 3); + } + }, + cam(s) { + const C = CUE2.wolf, bx = s.X(0.78), by = hillY(s, s.X(0.78)); + const p1 = easeInOut(inv(C.bush - 0.6, C.eyes, s.t)), p2 = easeInOut(inv(C.reveal - 0.3, C.reveal + 0.5, s.t)); + const sh = s.t > C.reveal ? Math.sin(s.t * 70) * 6 * s.u * Math.exp(-(s.t - C.reveal) * 4) : 0; + return cam2(s, lerp(lerp(1.05, 1.8, p1), 2.1, p2), lerp(lerp(s.W / 2, bx + 20 * s.u, p1), bx - 230 * s.u, p2), lerp(lerp(s.gy - 80 * s.u, by - 40 * s.u, p1), by - 40 * s.u, p2), { dx: sh }); + }, + enterAt: [0.5, 0.2] + }, + cry: { + label: "\u547C\u6551", + bg: bgHill("storm"), + draw(s) { + const { g: g2, W: W2, H: H2, u, t, X } = s; + const k = ks2(s), C = CUE2.cry; + for (let i = 0; i < 4; i++) stormCloud(g2, X(-1.2 + i * 0.7) + t * 40 * u, s.gy - (380 + i % 2 * 90) * u, 520 * u, 1, 50 + i); + for (let i = 0; i < 5; i++) { + const q2 = (t * 0.6 + i * 0.21) % 1, y2 = H2 * (0.15 + 0.12 * i); + brush(g2, [[lerp(W2 * 1.2, -W2 * 0.3, q2), y2], [lerp(W2 * 1.2, -W2 * 0.3, q2) - 220 * u, y2 + 8 * u]], 3 * u, { color: "#cfd3d4", alpha: 0.5, dry: 0.9, seed: i }); + } + flock(s, () => ({ graze: 0, look: 0.4, scared: 1 })); + wolf(g2, { x: X(1.05) - t * 25 * u, y: hillY(s, X(0.95)) + 60 * u, s: 230 * k, t, face: -1, mode: "sneak", glow: 0.9 }); + const [rx, ry] = rockTop(s); + const run = easeInOut(inv(C.toRock, C.toRock + 1, t)); + const x = lerp(X(0.4), rx, run), y = lerp(hillY(s, X(0.4)) + 40 * u, ry, run) - Math.sin(Math.PI * run) * 70 * u; + const shouting = C.shouts.some((st) => t > st && t < st + 1.2); + const pose = t < C.toRock ? SCARED(t) : run < 1 ? POSE.walk(t * 14, "boy", 1) : shouting ? { ...SHOUT, shiver: 0.6 } : SCARED(t); + boy(g2, x, y, 300 * k, t, pose, { face: -1, expr: { eye: "wide", mouth: "shout", brow: "worried" }, tears: t > C.shouts[1] ? 1 : 0, shiver: 0.6 }); + if (run >= 1) { + const mx = rx - 300 * k * 0.25, my = ry - 300 * k * 0.8; + if (shouting) shoutWaves(g2, mx, my, 120 * u, t, -1, 0.8); + C.shouts.forEach((st, i) => shoutText(g2, i === 2 ? "\u6551\u547D\u554A\uFF01" : "\u72FC\u6765\u4E86\uFF01", mx - 40 * u, my - 140 * u - i * 50 * u, -1, 130 * u, t - st, { scatter: inv(st + 0.9, st + 2.2, t), seed: i + 11 })); + } + }, + cam(s) { + const [rx, ry] = rockTop(s); + return cam2(s, lerp(1.35, 1.25, s.t / s.D), lerp(s.X(0.3), rx - 120 * s.u, easeInOut(inv(1.8, 3.6, s.t))), ry - 60 * s.u); + }, + enterAt: [0.4, 0.5] + }, + noone: { + label: "\u6CA1\u4EBA\u6765", + bg: bgVillage(true), + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks2(s), C = CUE2.noone; + shoutText(g2, "\u72FC\u6765\u4E86", X(0.85), s.gy - 300 * u, -1, 60 * u, t - 0.2, { scatter: inv(1.4, 3.2, t), color: "#b8322a", seed: 21 }); + for (let i = 0; i < 3; i++) { + const [x, y] = fieldSpot(s, i); + const look = t > C.look + i * 0.15 && t < C.work; + const lead = i === 0; + let pose = hoePose(t * 2.2 + i * 1.7, FARMERS[i].kind); + if (look) pose = { ...POSE.stand(FARMERS[i].kind), hl: [0.1, -0.62], hr: [0.16, -0.66], tool: { kind: "hoe", a: [0.05, -0.05], b: [0.18, -0.95] } }; + if (lead && t > C.wave && t < C.work) pose = { ...standPose(FARMERS[i].kind, "wave", t), tool: { kind: "hoe", a: [-0.12, -0.05], b: [-0.05, -0.95] } }; + const shake = t > C.shake && t < C.wave ? 0.14 * Math.sin(t * 8 + i) : 0; + farmer(g2, i, x, y, 330 * k, t, pose, { + face: t > C.work ? i === 1 ? 1 : -1 : i === 1 ? -1 : 1, + tilt: look ? -0.18 + shake : 0, + expr: { eye: "open", mouth: lead && t > C.wave && t < C.work ? "open" : look ? "frown" : "line", brow: t > C.shake ? "angry" : "up" } + }); + } + }, + cam(s) { + const C = CUE2.noone, [lx, ly] = fieldSpot(s, 0); + const p = easeInOut(inv(C.wave - 0.6, C.wave + 0.4, s.t)) * (1 - easeInOut(inv(C.work - 0.3, C.work + 0.9, s.t))); + return cam2(s, lerp(1.3, 1.9, p), lerp(s.X(0.25), lx + 40 * s.u, p), lerp(s.gy + 20 * s.u, ly - 180 * s.u, p)); + }, + enterAt: [0.8, 0.2] + }, + chaos: { + label: "\u7F8A\u7FA4\u56DB\u6563", + bg: bgHill("storm", { rock: true }), + draw(s) { + const { g: g2, W: W2, H: H2, u, t, X } = s; + const k = ks2(s), C = CUE2.chaos; + for (let i = 0; i < 3; i++) stormCloud(g2, X(-1 + i * 0.8) + t * 30 * u, s.gy - 400 * u, 520 * u, 1, 60 + i); + const [rx, ry] = rockTop(s); + boy(g2, rx, ry, 300 * k, t, SCARED(t), { face: 1, tears: 1 }); + const wx = lerp(X(1.2), X(0.35), easeOut(inv(C.dash, C.scatter, t))) + easeIn(inv(C.grab, C.grab + 2.2, t)) * 900 * u; + const wy = hillY(s, X(0.4)) + 90 * u; + FLOCK.forEach((sp, i) => { + const x0 = X(sp.f), y0 = hillY(s, x0) + sp.dy * u; + const run = inv(C.scatter + i * 0.06, C.scatter + 0.3 + i * 0.06, t); + const dir = x0 < X(0.35) ? -1 : 1, q2 = Math.max(0, t - C.scatter - i * 0.06); + if (i === 3 && t > C.grab) return; + sheep(g2, { x: x0 + dir * q2 * (260 + hash(i) * 200) * u, y: y0 + (hash(i + 3) - 0.5) * q2 * 60 * u, s: 150 * k * sp.s, t, face: run > 0 ? dir : sp.face, run: run > 0 ? 1 : 0, scared: run > 0 ? 1 : 0, baa: run > 0 ? Math.abs(Math.sin(t * 6 + i)) : 0, seed: sp.seed, graze: run > 0 ? 0 : 1 }); + if (run > 0 && q2 < 1.2) blot(g2, x0 + dir * q2 * 200 * u, y0 + 10 * u, 60 * u, "#a89a78", 0.3 * (1 - q2 / 1.2)); + }); + wolf(g2, { x: wx, y: wy, s: 340 * k, t, face: t > C.grab ? 1 : -1, mode: t < C.grab - 0.4 ? "run" : t < C.grab ? "snarl" : "run", carry: t > C.grab }); + for (let i = 0; i < 12; i++) grass(g2, i / 12 * W2, H2 - hash(i) * 100 * u, 60 * u, t, 200 + i, "#3e5a2e", 0.6, 2.5); + particles2(6, 9, (r, i) => { + const q2 = t - C.scatter; + if (q2 > 0 && q2 < 2.5) leaf(g2, X(0.35) + (r() - 0.5) * 400 * u + q2 * 100 * u, wy - q2 * 120 * u * r(), 10 * u, q2 * 4 + i, "#f2ece0", 0.8 * (1 - q2 / 2.5)); + }); + }, + cam(s) { + const sh = s.t > CUE2.chaos.scatter && s.t < CUE2.chaos.scatter + 1 ? Math.sin(s.t * 60) * 5 * s.u : 0; + return cam2(s, 1.15, s.X(0.15), s.gy + 20 * s.u, { dx: sh }); + }, + enterAt: [0.8, 0.5] + }, + regret: { + label: "\u540E\u6094", + bg: bgHill("sunset", { rock: false }), + draw(s) { + const { g: g2, u, t, X } = s; + const k = ks2(s), C = CUE2.regret; + for (let i = 0; i < 3; i++) farBird(g2, X(-0.6 + i * 0.12) + t * 20 * u, s.gy - (330 + i * 30) * u, 18 * u, t * 7 + i, "#2a2220", 0.8); + sheep(g2, { x: X(-0.4), y: hillY(s, X(-0.4)) + 80 * u, s: 140 * k, t, graze: 1, seed: 2 }); + sheep(g2, { x: X(0.45), y: hillY(s, X(0.45)) + 120 * u, s: 130 * k, t, face: -1, graze: 0.3, seed: 5 }); + const bx = X(0.02), by = hillY(s, X(0.02)) + 100 * u; + const hug = smooth(inv(C.hug - 0.4, C.hug + 0.4, t)); + const crying = t > C.cry; + boy(g2, bx, by, 300 * k, t, SIT, { expr: { eye: crying ? "sad" : "open", mouth: crying ? "cry" : "frown", brow: "worried" }, tears: crying ? 1 : 0, tilt: 0.12 * hug, shiver: crying ? 0.4 : 0 }); + sheep(g2, { x: bx + 300 * k * 0.2, y: by - 300 * k * 0.06, s: 100 * k, t, lamb: true, face: -1, graze: 0, look: 0.3, seed: 9, alpha: 0.4 + 0.6 * hug }); + if (crying) { + tear(g2, bx + 300 * k * 0.2, by - 300 * k * 0.6 + t * 40 % 50 * u, 8 * u, 1); + } + }, + cam(s) { + const bx = s.X(0.02), by = hillY(s, bx); + return cam2(s, lerp(1.15, 1.6, easeInOut(s.t / s.D)), bx + 20 * s.u, by - 40 * s.u); + }, + enterAt: [0.3, 0.3] + }, + moral: { + label: "\u5BD3\u610F", + bg: bgHill("dusk", { rock: false, tree: true }), + draw(s) { + const { g: g2, H: H2, u, t, X, portrait } = s; + const C = CUE2.moral; + for (let i = 0; i < 4; i++) blot(g2, X(-0.8) + i * 46 * u + 10 * u, s.gy - 2 * u + i % 2 * 14 * u, 14 * u, "#ffd890", 0.7 * smooth(inv(0.5 + i * 0.3, 1.5 + i * 0.3, t))); + const p = inv(0, s.D, t); + const x = lerp(X(0.5), X(-0.3), p), y = hillY(s, lerp(X(0.5), X(-0.3), p)) + 60 * u; + boy(g2, x, y, 170 * u, t, POSE.walk(t * 5, "boy"), { face: -1, alpha: 0.85, expr: { mouth: "line", eye: "sad" } }); + sheep(g2, { x: x - 110 * u, y: y + 4 * u, s: 70 * u, t, walk: 1, face: -1, lamb: true, seed: 9 }); + shoutText(g2, "\u72FC\u6765\u4E86", X(-0.2), H2 * 0.25, 1, 80 * u, t - 0.6, { scatter: inv(2, 4.6, t), seed: 31, color: "#9a3a32" }); + const fs = (portrait ? 200 : 180) * u; + const vx = portrait ? X(0.6) : X(0.68), vy = portrait ? H2 * 0.28 : H2 * 0.36; + writeText2(g2, "\u8BDA\u5B9E", vx, vy, fs, C.write, 0.55, t, { vertical: true, gap: 1.02 }); + const ea = smooth(inv(C.end - 0.3, C.end + 0.3, t)); + if (ea > 0) { + g2.save(); + g2.globalAlpha = ea; + g2.font = `${40 * u}px ${FONT_KAI}`; + g2.fillStyle = rgba(INK, 0.85); + g2.textAlign = "center"; + g2.save(); + g2.translate(vx - fs * 0.95, vy - fs * 0.9); + [..."\u72FC\u6765\u4E86 \xB7 \u5B8C"].forEach((c2, i) => g2.fillText(c2, 0, i * 46 * u)); + g2.restore(); + g2.restore(); + } + const sp = inv(C.end + 0.1, C.end + 0.35, t); + if (sp > 0) { + g2.save(); + g2.translate(vx - fs * 0.95, vy + fs * 0.9); + g2.scale(lerp(1.5, 1, easeOut(sp)), lerp(1.5, 1, easeOut(sp))); + seal(g2, 0, 0, 80 * u, ["\u5BD3\u8A00"], { alpha: Math.min(1, sp * 2) }); + g2.restore(); + } + }, + cam(s) { + return cam2(s, lerp(1, 1.06, s.t / s.D), s.W / 2, s.H / 2); + }, + enterAt: [0.5, 0.4] + } + }; + + // src/films/wolf/music.ts + var B = 60 / 96; + var G = [N.G3, N.D4, N.B4]; + var Em = [N.E3, N.B3, N.G4]; + var D = [N.D3, N.A3, N.D4]; + var Am = [N.A2, N.E3, N.A3]; + var Gh = [N.G4, N.B4, N.D5]; + function arp(m, t0, t1, chords, step, vel, pattern = [0, 1, 2, 1]) { + let i = 0; + for (let t = t0; t < t1 - 0.05; t += step, i++) { + const ch = chords[Math.floor((t - t0) / (step * 8)) % chords.length]; + m.pluck(t, ch[pattern[i % pattern.length] % ch.length], vel * (i % 4 === 0 ? 1 : 0.7), i % 2 ? 0.25 : -0.25, { dur: 1.6 }); + } + } + var MISCHIEF = [["G4", 0.5], ["B4", 0.5], ["D5", 0.5], ["B4", 0.5], ["A4", 0.5], ["G4", 0.5], ["E4", 1]]; + var PASTORAL = [["D5", 1], ["E5", 0.5], ["D5", 0.5], ["B4", 1], ["A4", 1], ["G4", 1], ["A4", 0.5], ["B4", 0.5], ["D5", 2]]; + var LAMENT = [["E5", 1.5], ["D5", 0.5], ["B4", 1], ["A4", 1], ["G4", 1.5], ["A4", 0.5], ["E4", 2]]; + function score2(m) { + const S = sceneStart, Dd = sceneDur; + let t = S("title"); + m.gliss(t + 0.35, [N.G3, N.A3, N.B3, N.D4, N.E4, N.G4, N.A4, N.B4], 0.07, 0.26); + m.pad(t + 0.5, S("village") + 1, [N.G2, N.D3], 0.045); + m.melody(t + 2, B, MISCHIEF, 0.15, 0, "pluck"); + for (let i = 0; i < 6; i++) m.block(t + 2 + i * B, 0.06, 1e3, 0.2); + t = S("village"); + m.pad(t, S("bored") + 0.5, [N.G2, N.D3, N.B3], 0.04); + arp(m, t + 0.3, S("bored"), [G, Em, G, D], B / 2, 0.16); + m.melody(t + 0.8, B * 1.2, PASTORAL, 0.12); + t = S("bored"); + m.pad(t, S("shout1") + 0.3, [N.G2, N.D3], 0.04); + for (let i = 0; i < Math.floor(CUE2.bored.idea / (B * 2)); i++) m.pluck(t + 0.4 + i * B * 2, [N.G3, N.D4, N.B3, N.D4][i % 4], 0.13, 0, { dur: 2.4 }); + m.tone(t + CUE2.bored.yawn, 520, 240, 1.1, 0.035, "triangle", 0, 0.3); + m.bell(t + CUE2.bored.idea, N.D6, 0.08, 0.2); + m.bell(t + CUE2.bored.idea + 0.12, N.G6 ?? 91, 0.06, 0.2); + for (let i = 0; i < 6; i++) m.pluck(t + CUE2.bored.sly + i * B / 2, [N.E4, N.G4, N.E4, N.B4, N.A4, N.G4][i], 0.14, 0.2, { dur: 0.4, bright: 0.9 }); + t = S("shout1"); + m.pad(t, S("run1"), [N.G2, N.D3], 0.035); + for (let i = 0; i < 4; i++) m.block(t + 0.6 + i * 0.12, 0.05, 1300, 0); + for (const st of CUE2.shout1.shouts) { + m.pluck(t + st, N.G2, 0.3, 0, { dur: 1.2 }); + m.pluck(t + st, N.D3, 0.25, 0, { dur: 1.2 }); + } + t = S("run1"); + m.pad(t, S("laugh1"), [N.D3, N.A3], 0.035); + for (let x = t + CUE2.run1.run; x < t + CUE2.run1.arrive + 0.6; x += B / 2) { + m.block(x, 0.07, 900, 0); + m.pluck(x + B / 4, [N.D4, N.E4, N.G4, N.A4, N.B4, N.D5][Math.floor((x - t) / (B / 2)) % 6], 0.12, 0.2, { dur: 0.3, bright: 0.9 }); + } + m.pluck(t + CUE2.run1.arrive + 0.3, N.B4, 0.2, 0, { bend: 2 }); + m.pluck(t + CUE2.run1.arrive + 1.4, N.A4, 0.18, 0, { bend: 2 }); + t = S("laugh1"); + arp(m, t + 0.2, t + CUE2.laugh1.angry, [G, Gh], B / 2, 0.13, [0, 2, 1, 2]); + const wah = [N.D4, N.D4 - 1, N.D4 - 2, N.D4 - 3]; + wah.forEach((nn, i) => m.tone(t + CUE2.laugh1.angry + i * 0.42, 440 * Math.pow(2, (nn - 69) / 12), 440 * Math.pow(2, (nn - 69.4) / 12), i === 3 ? 0.9 : 0.36, 0.05, "sawtooth", 0, 0.3)); + m.pad(t + CUE2.laugh1.angry, S("shout2") + 0.4, [N.E2, N.B2], 0.035, 600); + t = S("shout2"); + m.melody(t + 0.4, B, MISCHIEF, 0.13, 0, "pluck"); + for (const st of CUE2.shout2.shouts) { + m.pluck(t + st, N.G2, 0.28, 0, { dur: 1.2 }); + m.pluck(t + st, N.D3, 0.22, 0, { dur: 1.2 }); + } + for (let x = t + CUE2.shout2.run; x < S("warn"); x += B / 2) m.block(x, 0.06, 900, 0); + t = S("warn"); + arp(m, t + 0.2, t + CUE2.warn.point, [G, Gh], B / 2, 0.12, [0, 2, 1, 2]); + m.pluck(t + CUE2.warn.point, N.E2, 0.35, 0, { dur: 2.5 }); + m.pluck(t + CUE2.warn.point, N.B2, 0.25, 0, { dur: 2.5 }); + m.pad(t + CUE2.warn.point, S("wolf") + 1, [N.E2, N.B2], 0.045, 500); + t = S("wolf"); + m.pad(t, S("cry") + 0.5, [N.E2, N.B2, N.E3], 0.05, 420); + for (let x = t + 1; x < t + CUE2.wolf.reveal; x += 0.11) m.pluck(x, Math.floor((x - t) / 0.88) % 2 ? N.B2 : N.E2 - 12 + 12, 0.04 + 0.14 * (x - t) / CUE2.wolf.reveal, 0, { dur: 0.25, send: 0.05 }); + for (let x = t + CUE2.wolf.eyes; x < t + CUE2.wolf.reveal; x += 0.8) m.heart(x); + [N.E3, N.E3 + 1, N.B3, N.E4 + 1].forEach((nn) => m.pluck(t + CUE2.wolf.reveal, nn, 0.22, 0, { dur: 2.5 })); + t = S("cry"); + for (let x = t + 0.2; x < S("noone"); x += 0.12) m.pluck(x, Math.floor((x - t) / 0.48) % 2 ? N.E3 : N.B2, 0.1, 0, { dur: 0.2, send: 0.05 }); + m.flute(t + CUE2.cry.shouts[0] + 0.1, N.E5, 0.9, 0.08); + m.flute(t + CUE2.cry.shouts[2] + 0.1, N.G5, 1.1, 0.08); + t = S("noone"); + m.pad(t, S("chaos") + 0.4, [N.E2, N.B2], 0.04, 500); + for (let i = 0; i < 7; i++) m.pluck(t + 0.5 + i * B * 2.2, [N.B3, N.A3, N.G3, N.E3, N.G3, N.A3, N.E3][i], 0.13, -0.1, { dur: 2.2 }); + t = S("chaos"); + m.pad(t, S("regret") + 0.6, [N.E2, N.B2, N.E3 + 1], 0.05, 700); + for (let x = t + 0.3; x < S("regret"); x += 0.09) m.pluck(x, [N.E3, N.G3, N.E3, N.B3][Math.floor((x - t) / 0.09) % 4], 0.11, x * 7 % 1 - 0.5, { dur: 0.18, send: 0.05 }); + for (let i = 0; i < 8; i++) m.block(t + CUE2.chaos.scatter + i * 0.3, 0.12, 500, 0); + t = S("regret"); + m.pad(t, S("moral") + 0.6, [N.E2, N.B2, N.G3], 0.04, 700); + arp(m, t + 0.3, S("moral"), [Em, Am, Em, D], B, 0.1); + m.melody(t + 1, B * 1.25, LAMENT, 0.12); + t = S("moral"); + m.pad(t, TOTAL, [N.G2, N.D3, N.B3], 0.045); + arp(m, t + 0.2, t + CUE2.moral.end, [G, Em, G, D], B * 0.75, 0.12); + m.melody(t + CUE2.moral.write, B, [["D5", 1], ["B4", 1], ["A4", 1], ["G4", 2]], 0.12); + m.gliss(t + CUE2.moral.end, [N.G3, N.B3, N.D4, N.G4, N.B4, N.D5, N.G5], 0.08, 0.2); + m.bell(t + CUE2.moral.end + 0.1, N.G5, 0.08); + m.bell(t + CUE2.moral.end + 0.35, N.D6, 0.05, 0.3); + void Dd; + } + function baa(m, t, vel = 0.08, pan = 0, pitch = 1, len = 0.6) { + m.at(t, () => { + const c2 = m.ctx; + const o = c2.createOscillator(); + o.type = "sawtooth"; + o.frequency.setValueAtTime(330 * pitch, t); + o.frequency.linearRampToValueAtTime(290 * pitch, t + len); + const am = c2.createOscillator(); + am.frequency.value = 22; + const amg = c2.createGain(); + amg.gain.value = 0.45; + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + 0.05); + g2.gain.setValueAtTime(vel, t + len * 0.7); + g2.gain.linearRampToValueAtTime(0, t + len); + const trem = c2.createGain(); + trem.gain.value = 0.6; + am.connect(amg); + amg.connect(trem.gain); + const f = c2.createBiquadFilter(); + f.type = "bandpass"; + f.frequency.value = 950 * pitch; + f.Q.value = 2.5; + const f2 = c2.createBiquadFilter(); + f2.type = "peaking"; + f2.frequency.value = 2500; + f2.gain.value = 6; + o.connect(trem); + trem.connect(f); + f.connect(f2); + f2.connect(g2); + m.out(g2, m.sfx, pan, 0.25); + o.start(t); + o.stop(t + len + 0.05); + am.start(t); + am.stop(t + len + 0.05); + }); + } + function growl(m, t, dur = 1.4, vel = 0.12) { + m.at(t, () => { + const c2 = m.ctx; + const o = c2.createOscillator(); + o.type = "sawtooth"; + o.frequency.setValueAtTime(78, t); + o.frequency.linearRampToValueAtTime(95, t + dur * 0.6); + o.frequency.linearRampToValueAtTime(70, t + dur); + const n = c2.createBufferSource(); + n.buffer = m.noise; + n.loop = true; + const am = c2.createOscillator(); + am.frequency.value = 17; + const amg = c2.createGain(); + amg.gain.value = 0.5; + const mix = c2.createGain(); + mix.gain.value = 0.5; + am.connect(amg); + amg.connect(mix.gain); + const lp3 = c2.createBiquadFilter(); + lp3.type = "lowpass"; + lp3.frequency.value = 650; + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + 0.25); + g2.gain.setValueAtTime(vel, t + dur - 0.3); + g2.gain.linearRampToValueAtTime(0, t + dur); + o.connect(mix); + n.connect(mix); + mix.connect(lp3); + lp3.connect(g2); + m.out(g2, m.sfx, 0.3, 0.2); + o.start(t); + o.stop(t + dur + 0.05); + n.start(t, t * 0.37 % 1.5); + n.stop(t + dur + 0.05); + am.start(t); + am.stop(t + dur + 0.05); + }); + } + function howl(m, t, vel = 0.09) { + m.at(t, () => { + const c2 = m.ctx, dur = 2.4; + const o = c2.createOscillator(); + o.type = "triangle"; + o.frequency.setValueAtTime(330, t); + o.frequency.linearRampToValueAtTime(600, t + 0.6); + o.frequency.setValueAtTime(600, t + 1.4); + o.frequency.linearRampToValueAtTime(420, t + dur); + const lfo = c2.createOscillator(); + lfo.frequency.value = 5.5; + const lg = c2.createGain(); + lg.gain.value = 8; + lfo.connect(lg); + lg.connect(o.frequency); + const bp = c2.createBiquadFilter(); + bp.type = "bandpass"; + bp.frequency.value = 900; + bp.Q.value = 0.8; + const g2 = c2.createGain(); + g2.gain.setValueAtTime(0, t); + g2.gain.linearRampToValueAtTime(vel, t + 0.4); + g2.gain.setValueAtTime(vel, t + 1.6); + g2.gain.linearRampToValueAtTime(0, t + dur); + o.connect(bp); + bp.connect(g2); + m.out(g2, m.sfx, 0.4, 0.9); + o.start(t); + o.stop(t + dur + 0.05); + lfo.start(t); + lfo.stop(t + dur + 0.05); + }); + } + function thunder(m, t, vel = 0.3) { + m.noiseHit(t, 0.25, "lowpass", 3e3, 300, vel, 0, 0.6, 0.5); + for (let i = 0; i < 5; i++) m.noiseHit(t + 0.15 + i * 0.32, 0.9, "lowpass", 400, 60, vel * 0.6 * (1 - i * 0.15), i % 2 ? 0.3 : -0.3, 0.7, 0.8); + } + var whip = (m, t, pan = 0) => { + m.noiseHit(t, 0.04, "highpass", 5e3, 2500, 0.25, pan, 0.7, 0.2); + m.tone(t, 2400, 700, 0.05, 0.05, "triangle", pan, 0.2); + }; + var thud = (m, t, vel = 0.12, pan = 0) => { + m.tone(t, 110, 60, 0.12, vel, "sine", pan, 0.1); + m.noiseHit(t, 0.06, "lowpass", 900, 200, vel * 0.7, pan, 0.7, 0.05); + }; + var steps = (m, t0, t1, rate, vel = 0.06, pan = 0) => { + for (let x = t0; x < t1; x += 1 / rate) m.noiseHit(x, 0.05, "lowpass", 700, 180, vel, pan, 0.7, 0.05); + }; + var crickets = (m, t0, t1, r) => { + for (let x = t0; x < t1; x += 0.6 + r() * 0.8) for (let k = 0; k < 3; k++) m.tone(x + k * 0.05, 4600, 4500, 0.035, 0.012, "sine", r() - 0.5, 0.3); + }; + function echoLines(m, sceneId, gain = 0.3) { + const sc = TL.scenes.find((s) => s.id === sceneId); + for (const l of sc?.lines ?? []) if (l.who === "boy" && /狼|救命|快来/.test(l.text)) m.echo(l.t0, l.dur, 0.45, gain, 3); + } + function hoeHits(m, t0, t1, rate, offset, vel = 0.06, pan = 0) { + for (let n = 0; n < 200; n++) { + const x = (Math.PI / 2 + n * Math.PI * 2 - offset) / rate; + if (x < 0) continue; + const tt = t0 + x; + if (tt > t1) break; + thud(m, tt, vel, pan); + } + } + function effects2(m) { + const S = sceneStart, Dd = sceneDur; + const r = rng(77); + let t = S("title"); + m.drop(t + CUE2.title.drop); + m.swish(t + CUE2.title.write); + m.swish(t + CUE2.title.write + 0.45); + m.swish(t + CUE2.title.write + 0.9); + m.thump(t + CUE2.title.seal + 0.01, 0.45); + baa(m, t + 2.6, 0.06, 0.2); + baa(m, t + 4, 0.05, -0.1, 1.15); + whip(m, t + 3.3); + t = S("village"); + for (let x = t + 0.5; x < t + Dd("village"); x += 1.2 + r() * 1.5) m.chirp(x, 0.03, r() * 1.4 - 0.7); + for (let i = 0; i < 3; i++) hoeHits(m, t, t + Dd("village"), 2.2, i * 1.7 + 2.2 * 0, 0.05, -0.2 + i * 0.25); + baa(m, t + 5.6, 0.07, -0.3); + baa(m, t + 7.8, 0.06, -0.2, 1.1); + whip(m, t + 6.4, -0.3); + t = S("bored"); + m.wind(t, Dd("bored"), 0.03); + baa(m, t + 1.8, 0.06, 0.4); + baa(m, t + 4.6, 0.05, -0.4, 1.12); + baa(m, t + 7.2, 0.05, 0.3, 0.95); + t = S("shout1"); + m.noiseHit(t + CUE2.shout1.hop, 0.3, "bandpass", 600, 1800, 0.06, 0, 1, 0.2); + thud(m, t + CUE2.shout1.hop + 0.7, 0.12); + echoLines(m, "shout1"); + baa(m, t + CUE2.shout1.shouts[0] + 0.4, 0.07, 0.3, 1.1); + baa(m, t + CUE2.shout1.shouts[1] + 0.3, 0.06, -0.3, 1.2); + t = S("run1"); + for (let i = 0; i < 3; i++) hoeHits(m, t, t + CUE2.run1.hear, 2.2, i, 0.05, -0.3 + i * 0.3); + m.noiseHit(t + CUE2.run1.grab, 0.08, "bandpass", 1200, 600, 0.08, 0, 1, 0.1); + steps(m, t + CUE2.run1.run, t + CUE2.run1.arrive + 0.6, 7, 0.07, -0.1); + steps(m, t + CUE2.run1.run + 0.07, t + CUE2.run1.arrive + 0.8, 6.3, 0.05, 0.2); + for (let i = 0; i < 4; i++) m.noiseHit(t + CUE2.run1.arrive + 0.2 + i * 0.4, 0.25, "bandpass", 900, 500, 0.03, 0, 1.2, 0.1); + t = S("laugh1"); + steps(m, t + CUE2.laugh1.leave, t + Dd("laugh1") + 0.5, 3, 0.05, -0.3); + t = S("shout2"); + echoLines(m, "shout2", 0.27); + steps(m, t + CUE2.shout2.run, t + Dd("shout2"), 7, 0.07, -0.3); + t = S("warn"); + m.thump(t + CUE2.warn.point, 0.25, 80); + t = S("wolf"); + thunder(m, t + 0.4, 0.18); + m.wind(t, Dd("wolf") + 0.5, 0.09, 0.2); + m.noiseHit(t + CUE2.wolf.bush, 0.8, "bandpass", 2500, 1500, 0.05, 0.4, 0.8, 0.2); + growl(m, t + CUE2.wolf.creep, 1.6, 0.1); + thunder(m, t + CUE2.wolf.reveal, 0.35); + howl(m, t + CUE2.wolf.reveal + 0.4, 0.08); + t = S("cry"); + m.wind(t, Dd("cry") + 0.5, 0.12, -0.2); + echoLines(m, "cry", 0.12); + growl(m, t + 5.5, 1.2, 0.06); + t = S("noone"); + m.wind(t, Dd("noone"), 0.05, 0.3); + for (let i = 0; i < 3; i++) { + hoeHits(m, t, t + CUE2.noone.look, 2.2, i * 1.7, 0.05, -0.2 + i * 0.25); + hoeHits(m, t + CUE2.noone.work, t + Dd("noone"), 2.2, i * 1.7 + CUE2.noone.work * 2.2, 0.05, -0.2 + i * 0.25); + } + t = S("chaos"); + growl(m, t + 0.2, 0.9, 0.12); + for (let x = t + CUE2.chaos.scatter; x < t + CUE2.chaos.scatter + 3; x += 0.05 + r() * 0.07) m.noiseHit(x, 0.05, "lowpass", 800, 200, 0.05 + r() * 0.04, r() * 1.6 - 0.8, 0.7, 0.05); + for (let i = 0; i < 9; i++) baa(m, t + CUE2.chaos.scatter + 0.1 + i * 0.3 + r() * 0.15, 0.06, r() * 1.4 - 0.7, 1 + r() * 0.3, 0.45); + m.noiseHit(t + CUE2.chaos.grab, 0.2, "lowpass", 1500, 300, 0.12, 0.4, 0.7, 0.2); + baa(m, t + CUE2.chaos.grab + 0.1, 0.07, 0.5, 1.35, 0.8); + t = S("regret"); + crickets(m, t + 0.5, t + Dd("regret"), r); + baa(m, t + CUE2.regret.hug + 0.6, 0.05, 0.1, 1.3, 0.7); + for (let i = 0; i < 3; i++) m.tone(t + 1.2 + i * 1.7, 520, 380, 0.25, 0.02, "sawtooth", -0.5, 0.6); + t = S("moral"); + crickets(m, t, t + Dd("moral"), r); + m.thump(t + CUE2.moral.end + 0.12, 0.4); + void TOTAL; + } + + // src/films/wolf/content.ts + var PLATFORMS2 = []; + var PHOTO_POSTS2 = []; + var COVERS = (tl3) => { + void tl3; + return []; + }; + + // src/films/wolf/index.ts + var tl2 = timeline_default2; + var wolf2 = { + id: "wolf", + title: "\u72FC\u6765\u4E86", + tagline: "\u653E\u7F8A\u7684\u5B69\u5B50", + intro: "\u4F0A\u7D22\u5BD3\u8A00 \xB7 \u6C34\u58A8\u624B\u7ED8\u513F\u7AE5\u52A8\u753B\u3002\u5C71\u6751\u3001\u68AF\u7530\u3001\u7F8A\u7FA4\u548C\u4E00\u4E2A\u7231\u5F00\u73A9\u7B11\u7684\u653E\u7F8A\u5A03\u2014\u2014\u5BA3\u7EB8\u3001\u6BDB\u7B14\u3001\u6DE1\u5F69\u3001\u6731\u7EA2\u5370\u7AE0\uFF0C\u50CF\u8BED\u6587\u8BFE\u672C\u91CC\u7684\u63D2\u56FE\u4E00\u6837\u5728\u6D4F\u89C8\u5668\u91CC\u5B9E\u65F6\u753B\u51FA\u6765\uFF1B\u65C1\u767D\u3001\u653E\u7F8A\u5A03\u548C\u519C\u6C11\u5404\u6709\u58F0\u97F3\uFF0C\u914D\u4E50\u4E0E\u97F3\u6548\u540C\u6B65\u64AD\u653E\u3002", + credit: "\u6545\u4E8B\u6539\u7F16\u81EA\u300A\u4F0A\u7D22\u5BD3\u8A00 \xB7 \u72FC\u6765\u4E86\u300B\uFF08\u516C\u6709\u9886\u57DF\uFF09", + assets: "wolf/", + slug: "wolf-coming", + timeline: tl2, + scenes: SCENES3, + seal: ["\u5BD3\u8A00"], + series: "\u4F0A\u7D22\u5BD3\u8A00 \xB7 \u72FC\u6765\u4E86", + subColors: { boy: "#9a2f22", farmer: "#2f4a5a" }, + score: score2, + effects: effects2, + covers: COVERS(tl2), + live: live_default2, + platforms: PLATFORMS2, + photoPosts: PHOTO_POSTS2, + qa: qa_default2, + making: [ + ["\u753B\u9762", "\u4E0E\u300A\u4E11\u5C0F\u9E2D\u300B\u5171\u7528\u4E00\u5957\u4EE3\u7801\u6C34\u58A8\u5F15\u64CE\uFF08\u5BA3\u7EB8\u3001\u53D8\u5BBD\u6BDB\u7B14\u3001\u98DE\u767D\u3001\u6DE1\u5F69\u3001\u5370\u7AE0\uFF09\uFF0C\u672C\u7247\u65B0\u753B\u4E86\u653E\u7F8A\u5A03\u3001\u519C\u6C11\u3001\u7F8A\u7FA4\u3001\u72FC\u3001\u68AF\u7530\u548C\u8349\u5C4B\u3002\u300C\u72FC\u6765\u4E86\u300D\u4E09\u4E2A\u6731\u7EA2\u5927\u5B57\u4ECE\u753B\u9762\u91CC\u558A\u51FA\u6765\uFF0C\u7B2C\u4E09\u6B21\u558A\u65F6\u88AB\u98CE\u5439\u6563\u2014\u2014\u6CA1\u4EBA\u4FE1\u4E86\u3002"], + ["\u58F0\u97F3", "\u4E09\u4E2A\u58F0\u97F3\uFF1A\u65C1\u767D\uFF08\u6653\u6653\uFF09\u3001\u653E\u7F8A\u5A03\uFF08\u4E91\u590F\u7AE5\u58F0\uFF09\u3001\u519C\u6C11\uFF08\u4E91\u5065\uFF09\u9010\u53E5\u5408\u6210\u540E\u6309\u65F6\u95F4\u8F74\u62FC\u63A5\u3002\u539F\u521B\u914D\u4E50\u7531\u8C03\u76AE\u7684\u7AF9\u7B1B\u6728\u9C7C\u3001\u7D27\u5F20\u7684\u4F4E\u97F3\u98A4\u594F\u3001\u60CB\u60DC\u7684\u53E4\u7B5D\u7EC4\u6210\uFF1B\u7F8A\u53EB\u3001\u558A\u58F0\u56DE\u58F0\u3001\u811A\u6B65\u3001\u72FC\u568E\u3001\u7F8A\u7FA4\u5954\u9003\u7B49\u97F3\u6548\u5168\u90E8\u7A0B\u5E8F\u5408\u6210\uFF0C\u6574\u4F53 \u221214 LUFS\u3002"], + ["\u6D41\u7A0B", "\u6309 vibe-video \u6D41\u7A0B\uFF1A\u5206\u955C \u2192 \u591A\u89D2\u8272\u914D\u97F3\u751F\u6210\u65F6\u95F4\u8F74 \u2192 \u63A5\u89E6\u8868 / \u9759\u5E27 \u2192 \u786E\u5B9A\u6027\u81EA\u68C0 \u2192 \u5168\u7247\u6E32\u67D3 \u2192 \u6DF7\u97F3 \u2192 vibe qa\uFF1B\u5B9E\u51B5\u56FE\u6309 livecanvas \u6D41\u7A0B\u914D\u5BF9\u5C01\u88C5\u3002"] + ] + }; + + // src/films/index.ts + var FILMS = [duckling, wolf2]; + + // src/film/overlays.ts + var at2 = (id, dt) => (TL.scenes.find((s) => s.id === id)?.start ?? 0) + dt; + function titleBlock(g2, W2, H2, title, sub, side, o = {}) { + const u = Math.min(W2, H2) / 1080; + const chars = [...title]; + const fs = Math.min((o.big ?? 190) * u, H2 * 0.62 / chars.length); + const x = side === "left" ? W2 * 0.13 + fs * 0.1 : W2 * 0.87 - fs * 0.1; + const y0 = H2 * 0.1 + fs * 0.6; + const colH = chars.length * fs * 1.02; + blot(g2, x, y0 + colH / 2 - fs * 0.5, Math.max(fs * 1.6, colH * 0.75), PAPER, 0.82, 0.75, 1); + g2.save(); + g2.textAlign = "center"; + g2.textBaseline = "middle"; + g2.font = `${fs}px ${FONT_BRUSH}`; + g2.shadowColor = rgba(INK, 0.3); + g2.shadowBlur = fs * 0.05; + g2.lineJoin = "round"; + g2.strokeStyle = rgba(PAPER, 0.9); + g2.lineWidth = fs * 0.08; + chars.forEach((c2, i) => { + g2.strokeText(c2, x, y0 + i * fs * 1.02); + }); + g2.fillStyle = INK; + chars.forEach((c2, i) => g2.fillText(c2, x, y0 + i * fs * 1.02)); + g2.restore(); + const sfs = Math.max(26 * u, fs * 0.2); + const sx = side === "left" ? x + fs * 0.85 : x - fs * 0.85; + g2.save(); + g2.font = `${sfs}px ${FONT_KAI}`; + g2.textAlign = "center"; + g2.textBaseline = "middle"; + g2.lineJoin = "round"; + g2.strokeStyle = rgba(PAPER, 0.92); + g2.lineWidth = sfs * 0.22; + const sc = [...sub.replace(/\s/g, "")]; + const sy0 = y0 + fs * 0.1; + sc.forEach((c2, i) => { + const cy = sy0 + i * sfs * 1.12; + const ch = c2 === "\uFF0C" ? "\uFE10" : c2 === "\xB7" ? "\u30FB" : c2; + g2.strokeText(ch, sx, cy); + }); + g2.fillStyle = rgba(INK, 0.88); + sc.forEach((c2, i) => { + const cy = sy0 + i * sfs * 1.12; + const ch = c2 === "\uFF0C" ? "\uFE10" : c2 === "\xB7" ? "\u30FB" : c2; + g2.fillText(ch, sx, cy); + }); + g2.restore(); + const sy = y0 + colH - fs * 0.15; + seal(g2, x + (side === "left" ? -fs * 0.05 : fs * 0.05), sy + fs * 0.1, fs * 0.42, o.seal ?? FILM.seal); + if (o.index) { + g2.save(); + g2.font = `${sfs * 1.1}px ${FONT_BRUSH}`; + g2.fillStyle = rgba("#b8322a", 0.9); + g2.textAlign = "center"; + g2.fillText(o.index, sx, sy0 - sfs * 1.6); + g2.restore(); + } + } + var liveStart = (l) => at2(l.scene, l.from); + function drawLive(g2, W2, H2, l, lt) { + renderFrame(g2, W2, H2, liveStart(l) + lt, { subs: false, fadeEnd: false }); + titleBlock(g2, W2, H2, l.caption, FILM.series, "right", { big: 120, index: l.index }); + } + + // src/film/standalone.ts + var q = new URLSearchParams(location.search); + setFilm(FILMS.find((f) => f.id === (q.get("film") || "duckling"))); + var W = Number(q.get("w")) || innerWidth; + var H = Number(q.get("h")) || innerHeight; + var c = document.createElement("canvas"); + c.width = W; + c.height = H; + c.style.cssText = `position:fixed;left:0;top:0;width:${W}px;height:${H}px`; + document.body.style.margin = "0"; + document.body.appendChild(c); + var g = c.getContext("2d"); + window.__total = TOTAL; + window.__opts = { subs: q.get("subs") !== "0" }; + window.renderAt = (t) => renderFrame(g, W, H, t, { subs: window.__opts.subs }); + loadFonts(q.get("base") || "./").then(() => { + window.renderAt(0); + window.__ready = true; + }); + window.renderAudio = async () => { + const t0 = performance.now(); + const b = await buildSoundtrack((q.get("base") || "./") + FILM.assets + "voice.mp3", (s2) => console.log("stage", s2, Math.round(performance.now() - t0))); + const bytes = new Uint8Array(toWav(b)); + let s = ""; + for (let i = 0; i < bytes.length; i += 32768) s += String.fromCharCode(...bytes.subarray(i, i + 32768)); + return btoa(s); + }; + window.__dbg = { ...actors_exports, ...core_exports, W: actors_exports2 }; + window.__g = g; + window.__W = W; + window.__H = H; + window.__live = FILM.live; + window.renderLive = (id, lt) => { + const l = FILM.live.find((x) => x.id === id); + if (l) drawLive(g, W, H, l, lt); + }; +})(); diff --git a/wolf-coming/video/film/fonts/chars.txt b/wolf-coming/video/film/fonts/chars.txt new file mode 100644 index 0000000000000000000000000000000000000000..5348265823253b4387b6741f8393f709b944c985 --- /dev/null +++ b/wolf-coming/video/film/fonts/chars.txt @@ -0,0 +1 @@ + !#%+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz·—‘’“”…、。《》「」【】・一三上下不与丑世丛东丝两个中为主丽举久么乎乐也乡书乱了事二于云五些交享亮亲人仅今介从他代以们仰任伙会传伴位低住体何作你例侧便俏保信倒候值停傍像儿充入全公六共关其具内再写冠冬冰冲决况冷冻准几出击函分则创到制前剩副力加动勾匀包化区半单印即压原又叉友发取变叠口古句另只叫可台右叶号叽各合同名后向吓吗吧呆告命和咔咦咬哀哈响哔哥哩哪唧啄喂善喊喙喜喳嘲嘴器噪嚓四回团围固国图圆圈在地场均坐块垂垛域堆塘填境增墨壁声壳处备复夏外多夜大天太失头夹奇奏她好妈始姐姑娘嫌子字存季孤学孩孵它安完官定实宣宫家宽寂寄密寒寸对导封小尖尚就尺尼尾层屋屏展属山岸峰工左差己已布希帘带帧幅幕干平并序应底度廓延建开式引张弦弯弹归当录形彩影往径很徒得循微心快念忽态怕性怪总息悄悬情惊愈意感慢成截所扇手才扑打扫扬把抖折报拉拍拖拢拨择持按挖振换掉排掠接控推描提插揭撇播擎支收改放政故效敢数整文斑断新旁无日时明春是昼显晓晕晚景晰暂暖暗曲更替最有朋望朝期木未本术朱机权村条来极构果枝枪染柔查柳标树校样核根格桃案梅梦梳检楷模横檐欠次欢欺款歌正此步段每比毛气水池治沼沿法波泥泪泽洁流测浏浓浮消淡深混清渐渡温渲游湖湿溅源溪滴漂漫潮灰点烟烧然照熬片版牙物状猎率环现理瓣生用田由电画畅界痕登白的皮皴益盖盛目直相省看真眼着睡瞬瞻短码砂砰破确碍示神离秋种秒积程空窗窝立竖章童竹笆笑笔笛符等筝签简篇篱簧粒粘系素紧红纤约级纳纷纸纹线组终经绒结绘给络绪续维缓编缘缩网置美群羽翅翻翼而肉背能脊脑脖脚脸腿膀臂自致色艺节芦花芽苇苹范茸草荷落蓝藏蛋蜓蜻蝴蝶融行衣补表被裁裂装西要见观规视览觉角解触计让记讲证试话语说请读课调负败质贴资走起路跳身躲轨转轮软轴轻载输辨边过近还这进远连迟述追送适选逐递通遮那部都配采里重野量金钟钻铃铺错键镜长门间阅阴阵阶阻附限除随难雁雪雾需霞露静面音页顶顿预领颈频颗题颜颤风飘飞马驳验高髻鱼鸟鸡鸣鸭鹅鹜麦黄黑齐️︐!()+,:;?|🐓👀🥚🦢 \ No newline at end of file diff --git a/wolf-coming/video/film/fonts/mashanzheng.woff2 b/wolf-coming/video/film/fonts/mashanzheng.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..336f627eea468eaf298942d59a3c615369298a25 --- /dev/null +++ b/wolf-coming/video/film/fonts/mashanzheng.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:fe7ce130e02290a7dff93743e8a7492fb7c41bdbe9d6b338945144e018735476 +size 420940 diff --git a/wolf-coming/video/film/fonts/wenkai.woff2 b/wolf-coming/video/film/fonts/wenkai.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..d3eee58aa0e17d95557ba0ac444a80499e4a0fef --- /dev/null +++ b/wolf-coming/video/film/fonts/wenkai.woff2 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:14374c15ca95593a0fc159be8f69f4435a3e7c4206b1817fd7bfba9495222f02 +size 240976 diff --git a/wolf-coming/video/film/voice.mp3 b/wolf-coming/video/film/voice.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..8d5e99314cdf32dfd2585db67f744a59ce1d15d0 --- /dev/null +++ b/wolf-coming/video/film/voice.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:50046e8a3fb27e30e64cb0ca975c630a567ff84a3e6a217076501607745250fe +size 1655162 diff --git a/wolf-coming/video/index.html b/wolf-coming/video/index.html new file mode 100644 index 0000000000000000000000000000000000000000..d30ab8bcad2d591aa3ca5ddbb09778f6c6b885c9 --- /dev/null +++ b/wolf-coming/video/index.html @@ -0,0 +1 @@ +狼来了 · render diff --git a/wolf-coming/video/out/subs.srt b/wolf-coming/video/out/subs.srt new file mode 100644 index 0000000000000000000000000000000000000000..43d8ea5dfbaddc6eaa8b069fedfa860f3f4cd649 --- /dev/null +++ b/wolf-coming/video/out/subs.srt @@ -0,0 +1,259 @@ +1 +00:00:00,500 --> 00:00:01,967 +伊索寓言 + +2 +00:00:01,967 --> 00:00:03,068 +狼来了 + +3 +00:00:07,000 --> 00:00:09,096 +山脚下有个小村子 + +4 +00:00:09,096 --> 00:00:11,715 +大人们每天在田里干活 + +5 +00:00:11,715 --> 00:00:13,549 +有个放羊的孩子 + +6 +00:00:13,549 --> 00:00:16,168 +天天赶着羊群上山吃草 + +7 +00:00:17,567 --> 00:00:18,403 +山坡上 + +8 +00:00:18,403 --> 00:00:20,910 +羊儿慢悠悠地吃着草 + +9 +00:00:20,910 --> 00:00:23,139 +放羊娃躺在大树下 + +10 +00:00:23,139 --> 00:00:25,090 +嘴里叼着一根草 + +11 +00:00:25,090 --> 00:00:26,762 +觉得无聊极了 + +12 +00:00:26,762 --> 00:00:27,319 +忽然 + +13 +00:00:27,319 --> 00:00:28,991 +他的眼珠一转 + +14 +00:00:30,400 --> 00:00:32,233 +他跳上大石头 + +15 +00:00:32,233 --> 00:00:33,760 +拢着嘴大喊 + +16 +00:00:34,140 --> 00:00:35,524 +狼来了! + +17 +00:00:35,524 --> 00:00:36,908 +狼来了! + +18 +00:00:36,908 --> 00:00:38,292 +快来呀! + +19 +00:00:39,700 --> 00:00:41,956 +山下的农民听见了 + +20 +00:00:41,956 --> 00:00:43,084 +扛起锄头 + +21 +00:00:43,084 --> 00:00:44,212 +拿起扁担 + +22 +00:00:44,212 --> 00:00:46,468 +急急忙忙往山上跑 + +23 +00:00:46,848 --> 00:00:48,480 +狼在哪儿? + +24 +00:00:48,480 --> 00:00:50,112 +狼在哪儿? + +25 +00:00:51,500 --> 00:00:52,215 +哈哈哈 + +26 +00:00:52,215 --> 00:00:53,408 +哪有什么狼 + +27 +00:00:53,408 --> 00:00:55,316 +我是逗你们玩的! + +28 +00:00:55,696 --> 00:00:57,863 +农民们气得直摇头 + +29 +00:00:57,863 --> 00:00:59,488 +只好下山去了 + +30 +00:01:00,900 --> 00:01:02,069 +过了几天 + +31 +00:01:02,069 --> 00:01:04,116 +放羊娃又喊起来 + +32 +00:01:04,496 --> 00:01:05,948 +狼来了! + +33 +00:01:05,948 --> 00:01:07,400 +狼来了! + +34 +00:01:07,780 --> 00:01:10,396 +农民们又一次跑上了山 + +35 +00:01:11,800 --> 00:01:12,472 +哈哈 + +36 +00:01:12,472 --> 00:01:14,152 +又上当啦! + +37 +00:01:14,532 --> 00:01:15,636 +你再说谎 + +38 +00:01:15,636 --> 00:01:18,396 +下次我们就不信你了! + +39 +00:01:19,800 --> 00:01:21,564 +又过了些日子 + +40 +00:01:21,564 --> 00:01:23,328 +乌云压着山头 + +41 +00:01:23,328 --> 00:01:24,210 +树丛里 + +42 +00:01:24,210 --> 00:01:26,562 +一双绿莹莹的眼睛 + +43 +00:01:26,562 --> 00:01:28,620 +慢慢靠近了羊群 + +44 +00:01:28,620 --> 00:01:28,914 +狼 + +45 +00:01:28,914 --> 00:01:30,384 +真的来了! + +46 +00:01:31,800 --> 00:01:33,715 +放羊娃吓坏了 + +47 +00:01:33,715 --> 00:01:34,992 +拼命地喊 + +48 +00:01:35,372 --> 00:01:36,634 +狼来了! + +49 +00:01:36,634 --> 00:01:38,526 +狼真的来了! + +50 +00:01:38,526 --> 00:01:39,788 +救命啊! + +51 +00:01:41,200 --> 00:01:43,348 +山下的农民听见了 + +52 +00:01:43,348 --> 00:01:45,496 +都以为他又在说谎 + +53 +00:01:45,876 --> 00:01:46,409 +哼 + +54 +00:01:46,409 --> 00:01:48,540 +又来骗人 + +55 +00:01:48,920 --> 00:01:50,960 +谁也没有上山 + +56 +00:01:52,367 --> 00:01:53,725 +狼冲进羊群 + +57 +00:01:53,725 --> 00:01:55,898 +羊儿吓得四处乱跑 + +58 +00:01:55,898 --> 00:01:58,343 +好几只羊被狼叼走了 + +59 +00:02:00,367 --> 00:02:01,699 +太阳落山了 + +60 +00:02:01,699 --> 00:02:04,363 +放羊娃抱着剩下的小羊 + +61 +00:02:04,363 --> 00:02:05,695 +伤心地哭了 + +62 +00:02:08,367 --> 00:02:09,437 +说谎的人 + +63 +00:02:09,437 --> 00:02:11,042 +就算说了真话 + +64 +00:02:11,042 --> 00:02:13,183 +也没有人再相信他 + +65 +00:02:13,183 --> 00:02:15,591 +做一个诚实的孩子吧 diff --git a/wolf-coming/video/storyboard.json b/wolf-coming/video/storyboard.json new file mode 100644 index 0000000000000000000000000000000000000000..ad37193a4ed1591ddc9e1b157140517bed35bcb7 --- /dev/null +++ b/wolf-coming/video/storyboard.json @@ -0,0 +1,30 @@ +{ + "title": "狼来了", + "format": { "w": 1920, "h": 1080, "fps": 30 }, + "style": "ink-wash textbook illustration", + "voices": { + "n": { "voice": "zh-CN-XiaoxiaoNeural", "rate": "-6%", "pitch": "+0Hz", "name": "旁白" }, + "boy": { "voice": "zh-CN-YunxiaNeural", "rate": "+4%", "pitch": "+6Hz", "name": "放羊娃" }, + "farmer": { "voice": "zh-CN-YunjianNeural", "rate": "-2%", "pitch": "-2Hz", "name": "农民" } + }, + "sources": [ + "Aesop's Fables, \"The Boy Who Cried Wolf\" (Perry Index 210) — public domain", + "https://en.wikipedia.org/wiki/The_Boy_Who_Cried_Wolf" + ], + "scenes": [ + { "id": "title", "min_dur": 6.5, "lines": [{ "who": "n", "text": "伊索寓言,狼来了。" }] }, + { "id": "village", "min_dur": 8, "lines": [{ "who": "n", "text": "山脚下有个小村子。大人们每天在田里干活,有个放羊的孩子,天天赶着羊群上山吃草。" }] }, + { "id": "bored", "min_dur": 8, "lines": [{ "who": "n", "text": "山坡上,羊儿慢悠悠地吃着草。放羊娃躺在大树下,嘴里叼着一根草,觉得无聊极了。忽然,他的眼珠一转。" }] }, + { "id": "shout1", "min_dur": 6, "lines": [{ "who": "n", "text": "他跳上大石头,拢着嘴大喊:" }, { "who": "boy", "text": "狼来了!狼来了!快来呀!" }] }, + { "id": "run1", "min_dur": 7, "lines": [{ "who": "n", "text": "山下的农民听见了,扛起锄头,拿起扁担,急急忙忙往山上跑。" }, { "who": "farmer", "text": "狼在哪儿?狼在哪儿?" }] }, + { "id": "laugh1", "min_dur": 7, "lines": [{ "who": "boy", "text": "哈哈哈,哪有什么狼,我是逗你们玩的!" }, { "who": "n", "text": "农民们气得直摇头,只好下山去了。" }] }, + { "id": "shout2", "min_dur": 7, "lines": [{ "who": "n", "text": "过了几天,放羊娃又喊起来:" }, { "who": "boy", "text": "狼来了!狼来了!" }, { "who": "n", "text": "农民们又一次跑上了山。" }] }, + { "id": "warn", "min_dur": 7, "lines": [{ "who": "boy", "text": "哈哈,又上当啦!" }, { "who": "farmer", "text": "你再说谎,下次我们就不信你了!" }] }, + { "id": "wolf", "min_dur": 9, "lines": [{ "who": "n", "text": "又过了些日子,乌云压着山头。树丛里,一双绿莹莹的眼睛,慢慢靠近了羊群。狼,真的来了!" }] }, + { "id": "cry", "min_dur": 7, "lines": [{ "who": "n", "text": "放羊娃吓坏了,拼命地喊:" }, { "who": "boy", "text": "狼来了!狼真的来了!救命啊!" }] }, + { "id": "noone", "min_dur": 7, "lines": [{ "who": "n", "text": "山下的农民听见了,都以为他又在说谎。" }, { "who": "farmer", "text": "哼,又来骗人。" }, { "who": "n", "text": "谁也没有上山。" }] }, + { "id": "chaos", "min_dur": 8, "lines": [{ "who": "n", "text": "狼冲进羊群,羊儿吓得四处乱跑,好几只羊被狼叼走了。" }] }, + { "id": "regret", "min_dur": 8, "lines": [{ "who": "n", "text": "太阳落山了,放羊娃抱着剩下的小羊,伤心地哭了。" }] }, + { "id": "moral", "min_dur": 10, "lines": [{ "who": "n", "text": "说谎的人,就算说了真话,也没有人再相信他。做一个诚实的孩子吧。" }] } + ] +} diff --git a/wolf-coming/video/timeline.json b/wolf-coming/video/timeline.json new file mode 100644 index 0000000000000000000000000000000000000000..72322ba7d41309afd3797f05a030c0ba6ff542dd --- /dev/null +++ b/wolf-coming/video/timeline.json @@ -0,0 +1,1134 @@ +{ + "title": "狼来了", + "style": "ink-wash textbook illustration", + "w": 1920, + "h": 1080, + "fps": 30, + "total": 137.86666666666667, + "frames": 4136, + "voice": { + "n": "zh-CN-XiaoxiaoNeural", + "boy": "zh-CN-YunxiaNeural", + "farmer": "zh-CN-YunjianNeural" + }, + "scenes": [ + { + "id": "title", + "start": 0.0, + "dur": 6.5, + "voice_start": 0.5, + "voice_dur": 2.568, + "lines": [ + { + "who": "n", + "text": "伊索寓言,狼来了。", + "t0": 0.5, + "dur": 2.568, + "subs": [ + { + "t0": 0.5, + "t1": 1.967, + "text": "伊索寓言", + "who": "n" + }, + { + "t0": 1.967, + "t1": 3.068, + "text": "狼来了", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 0.5, + "t1": 1.967, + "text": "伊索寓言", + "who": "n" + }, + { + "t0": 1.967, + "t1": 3.068, + "text": "狼来了", + "who": "n" + } + ] + }, + { + "id": "village", + "start": 6.5, + "dur": 10.566666666666666, + "voice_start": 7.0, + "voice_dur": 9.168, + "lines": [ + { + "who": "n", + "text": "山脚下有个小村子。大人们每天在田里干活,有个放羊的孩子,天天赶着羊群上山吃草。", + "t0": 7.0, + "dur": 9.168, + "subs": [ + { + "t0": 7.0, + "t1": 9.096, + "text": "山脚下有个小村子", + "who": "n" + }, + { + "t0": 9.096, + "t1": 11.715, + "text": "大人们每天在田里干活", + "who": "n" + }, + { + "t0": 11.715, + "t1": 13.549, + "text": "有个放羊的孩子", + "who": "n" + }, + { + "t0": 13.549, + "t1": 16.168, + "text": "天天赶着羊群上山吃草", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 7.0, + "t1": 9.096, + "text": "山脚下有个小村子", + "who": "n" + }, + { + "t0": 9.096, + "t1": 11.715, + "text": "大人们每天在田里干活", + "who": "n" + }, + { + "t0": 11.715, + "t1": 13.549, + "text": "有个放羊的孩子", + "who": "n" + }, + { + "t0": 13.549, + "t1": 16.168, + "text": "天天赶着羊群上山吃草", + "who": "n" + } + ] + }, + { + "id": "bored", + "start": 17.0667, + "dur": 12.833333333333334, + "voice_start": 17.567, + "voice_dur": 11.424, + "lines": [ + { + "who": "n", + "text": "山坡上,羊儿慢悠悠地吃着草。放羊娃躺在大树下,嘴里叼着一根草,觉得无聊极了。忽然,他的眼珠一转。", + "t0": 17.567, + "dur": 11.424, + "subs": [ + { + "t0": 17.567, + "t1": 18.403, + "text": "山坡上", + "who": "n" + }, + { + "t0": 18.403, + "t1": 20.91, + "text": "羊儿慢悠悠地吃着草", + "who": "n" + }, + { + "t0": 20.91, + "t1": 23.139, + "text": "放羊娃躺在大树下", + "who": "n" + }, + { + "t0": 23.139, + "t1": 25.09, + "text": "嘴里叼着一根草", + "who": "n" + }, + { + "t0": 25.09, + "t1": 26.762, + "text": "觉得无聊极了", + "who": "n" + }, + { + "t0": 26.762, + "t1": 27.319, + "text": "忽然", + "who": "n" + }, + { + "t0": 27.319, + "t1": 28.991, + "text": "他的眼珠一转", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 17.567, + "t1": 18.403, + "text": "山坡上", + "who": "n" + }, + { + "t0": 18.403, + "t1": 20.91, + "text": "羊儿慢悠悠地吃着草", + "who": "n" + }, + { + "t0": 20.91, + "t1": 23.139, + "text": "放羊娃躺在大树下", + "who": "n" + }, + { + "t0": 23.139, + "t1": 25.09, + "text": "嘴里叼着一根草", + "who": "n" + }, + { + "t0": 25.09, + "t1": 26.762, + "text": "觉得无聊极了", + "who": "n" + }, + { + "t0": 26.762, + "t1": 27.319, + "text": "忽然", + "who": "n" + }, + { + "t0": 27.319, + "t1": 28.991, + "text": "他的眼珠一转", + "who": "n" + } + ] + }, + { + "id": "shout1", + "start": 29.9, + "dur": 9.3, + "voice_start": 30.4, + "voice_dur": 7.892, + "lines": [ + { + "who": "n", + "text": "他跳上大石头,拢着嘴大喊:", + "t0": 30.4, + "dur": 3.36, + "subs": [ + { + "t0": 30.4, + "t1": 32.233, + "text": "他跳上大石头", + "who": "n" + }, + { + "t0": 32.233, + "t1": 33.76, + "text": "拢着嘴大喊", + "who": "n" + } + ] + }, + { + "who": "boy", + "text": "狼来了!狼来了!快来呀!", + "t0": 34.14, + "dur": 4.152, + "subs": [ + { + "t0": 34.14, + "t1": 35.524, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 35.524, + "t1": 36.908, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 36.908, + "t1": 38.292, + "text": "快来呀!", + "who": "boy" + } + ] + } + ], + "subs": [ + { + "t0": 30.4, + "t1": 32.233, + "text": "他跳上大石头", + "who": "n" + }, + { + "t0": 32.233, + "t1": 33.76, + "text": "拢着嘴大喊", + "who": "n" + }, + { + "t0": 34.14, + "t1": 35.524, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 35.524, + "t1": 36.908, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 36.908, + "t1": 38.292, + "text": "快来呀!", + "who": "boy" + } + ] + }, + { + "id": "run1", + "start": 39.2, + "dur": 11.8, + "voice_start": 39.7, + "voice_dur": 10.412, + "lines": [ + { + "who": "n", + "text": "山下的农民听见了,扛起锄头,拿起扁担,急急忙忙往山上跑。", + "t0": 39.7, + "dur": 6.768, + "subs": [ + { + "t0": 39.7, + "t1": 41.956, + "text": "山下的农民听见了", + "who": "n" + }, + { + "t0": 41.956, + "t1": 43.084, + "text": "扛起锄头", + "who": "n" + }, + { + "t0": 43.084, + "t1": 44.212, + "text": "拿起扁担", + "who": "n" + }, + { + "t0": 44.212, + "t1": 46.468, + "text": "急急忙忙往山上跑", + "who": "n" + } + ] + }, + { + "who": "farmer", + "text": "狼在哪儿?狼在哪儿?", + "t0": 46.848, + "dur": 3.264, + "subs": [ + { + "t0": 46.848, + "t1": 48.48, + "text": "狼在哪儿?", + "who": "farmer" + }, + { + "t0": 48.48, + "t1": 50.112, + "text": "狼在哪儿?", + "who": "farmer" + } + ] + } + ], + "subs": [ + { + "t0": 39.7, + "t1": 41.956, + "text": "山下的农民听见了", + "who": "n" + }, + { + "t0": 41.956, + "t1": 43.084, + "text": "扛起锄头", + "who": "n" + }, + { + "t0": 43.084, + "t1": 44.212, + "text": "拿起扁担", + "who": "n" + }, + { + "t0": 44.212, + "t1": 46.468, + "text": "急急忙忙往山上跑", + "who": "n" + }, + { + "t0": 46.848, + "t1": 48.48, + "text": "狼在哪儿?", + "who": "farmer" + }, + { + "t0": 48.48, + "t1": 50.112, + "text": "狼在哪儿?", + "who": "farmer" + } + ] + }, + { + "id": "laugh1", + "start": 51.0, + "dur": 9.4, + "voice_start": 51.5, + "voice_dur": 7.988, + "lines": [ + { + "who": "boy", + "text": "哈哈哈,哪有什么狼,我是逗你们玩的!", + "t0": 51.5, + "dur": 3.816, + "subs": [ + { + "t0": 51.5, + "t1": 52.215, + "text": "哈哈哈", + "who": "boy" + }, + { + "t0": 52.215, + "t1": 53.408, + "text": "哪有什么狼", + "who": "boy" + }, + { + "t0": 53.408, + "t1": 55.316, + "text": "我是逗你们玩的!", + "who": "boy" + } + ] + }, + { + "who": "n", + "text": "农民们气得直摇头,只好下山去了。", + "t0": 55.696, + "dur": 3.792, + "subs": [ + { + "t0": 55.696, + "t1": 57.863, + "text": "农民们气得直摇头", + "who": "n" + }, + { + "t0": 57.863, + "t1": 59.488, + "text": "只好下山去了", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 51.5, + "t1": 52.215, + "text": "哈哈哈", + "who": "boy" + }, + { + "t0": 52.215, + "t1": 53.408, + "text": "哪有什么狼", + "who": "boy" + }, + { + "t0": 53.408, + "t1": 55.316, + "text": "我是逗你们玩的!", + "who": "boy" + }, + { + "t0": 55.696, + "t1": 57.863, + "text": "农民们气得直摇头", + "who": "n" + }, + { + "t0": 57.863, + "t1": 59.488, + "text": "只好下山去了", + "who": "n" + } + ] + }, + { + "id": "shout2", + "start": 60.4, + "dur": 10.9, + "voice_start": 60.9, + "voice_dur": 9.496, + "lines": [ + { + "who": "n", + "text": "过了几天,放羊娃又喊起来:", + "t0": 60.9, + "dur": 3.216, + "subs": [ + { + "t0": 60.9, + "t1": 62.069, + "text": "过了几天", + "who": "n" + }, + { + "t0": 62.069, + "t1": 64.116, + "text": "放羊娃又喊起来", + "who": "n" + } + ] + }, + { + "who": "boy", + "text": "狼来了!狼来了!", + "t0": 64.496, + "dur": 2.904, + "subs": [ + { + "t0": 64.496, + "t1": 65.948, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 65.948, + "t1": 67.4, + "text": "狼来了!", + "who": "boy" + } + ] + }, + { + "who": "n", + "text": "农民们又一次跑上了山。", + "t0": 67.78, + "dur": 2.616, + "subs": [ + { + "t0": 67.78, + "t1": 70.396, + "text": "农民们又一次跑上了山", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 60.9, + "t1": 62.069, + "text": "过了几天", + "who": "n" + }, + { + "t0": 62.069, + "t1": 64.116, + "text": "放羊娃又喊起来", + "who": "n" + }, + { + "t0": 64.496, + "t1": 65.948, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 65.948, + "t1": 67.4, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 67.78, + "t1": 70.396, + "text": "农民们又一次跑上了山", + "who": "n" + } + ] + }, + { + "id": "warn", + "start": 71.3, + "dur": 8.0, + "voice_start": 71.8, + "voice_dur": 6.596, + "lines": [ + { + "who": "boy", + "text": "哈哈,又上当啦!", + "t0": 71.8, + "dur": 2.352, + "subs": [ + { + "t0": 71.8, + "t1": 72.472, + "text": "哈哈", + "who": "boy" + }, + { + "t0": 72.472, + "t1": 74.152, + "text": "又上当啦!", + "who": "boy" + } + ] + }, + { + "who": "farmer", + "text": "你再说谎,下次我们就不信你了!", + "t0": 74.532, + "dur": 3.864, + "subs": [ + { + "t0": 74.532, + "t1": 75.636, + "text": "你再说谎", + "who": "farmer" + }, + { + "t0": 75.636, + "t1": 78.396, + "text": "下次我们就不信你了!", + "who": "farmer" + } + ] + } + ], + "subs": [ + { + "t0": 71.8, + "t1": 72.472, + "text": "哈哈", + "who": "boy" + }, + { + "t0": 72.472, + "t1": 74.152, + "text": "又上当啦!", + "who": "boy" + }, + { + "t0": 74.532, + "t1": 75.636, + "text": "你再说谎", + "who": "farmer" + }, + { + "t0": 75.636, + "t1": 78.396, + "text": "下次我们就不信你了!", + "who": "farmer" + } + ] + }, + { + "id": "wolf", + "start": 79.3, + "dur": 12.0, + "voice_start": 79.8, + "voice_dur": 10.584, + "lines": [ + { + "who": "n", + "text": "又过了些日子,乌云压着山头。树丛里,一双绿莹莹的眼睛,慢慢靠近了羊群。狼,真的来了!", + "t0": 79.8, + "dur": 10.584, + "subs": [ + { + "t0": 79.8, + "t1": 81.564, + "text": "又过了些日子", + "who": "n" + }, + { + "t0": 81.564, + "t1": 83.328, + "text": "乌云压着山头", + "who": "n" + }, + { + "t0": 83.328, + "t1": 84.21, + "text": "树丛里", + "who": "n" + }, + { + "t0": 84.21, + "t1": 86.562, + "text": "一双绿莹莹的眼睛", + "who": "n" + }, + { + "t0": 86.562, + "t1": 88.62, + "text": "慢慢靠近了羊群", + "who": "n" + }, + { + "t0": 88.62, + "t1": 88.914, + "text": "狼", + "who": "n" + }, + { + "t0": 88.914, + "t1": 90.384, + "text": "真的来了!", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 79.8, + "t1": 81.564, + "text": "又过了些日子", + "who": "n" + }, + { + "t0": 81.564, + "t1": 83.328, + "text": "乌云压着山头", + "who": "n" + }, + { + "t0": 83.328, + "t1": 84.21, + "text": "树丛里", + "who": "n" + }, + { + "t0": 84.21, + "t1": 86.562, + "text": "一双绿莹莹的眼睛", + "who": "n" + }, + { + "t0": 86.562, + "t1": 88.62, + "text": "慢慢靠近了羊群", + "who": "n" + }, + { + "t0": 88.62, + "t1": 88.914, + "text": "狼", + "who": "n" + }, + { + "t0": 88.914, + "t1": 90.384, + "text": "真的来了!", + "who": "n" + } + ] + }, + { + "id": "cry", + "start": 91.3, + "dur": 9.4, + "voice_start": 91.8, + "voice_dur": 7.988, + "lines": [ + { + "who": "n", + "text": "放羊娃吓坏了,拼命地喊:", + "t0": 91.8, + "dur": 3.192, + "subs": [ + { + "t0": 91.8, + "t1": 93.715, + "text": "放羊娃吓坏了", + "who": "n" + }, + { + "t0": 93.715, + "t1": 94.992, + "text": "拼命地喊", + "who": "n" + } + ] + }, + { + "who": "boy", + "text": "狼来了!狼真的来了!救命啊!", + "t0": 95.372, + "dur": 4.416, + "subs": [ + { + "t0": 95.372, + "t1": 96.634, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 96.634, + "t1": 98.526, + "text": "狼真的来了!", + "who": "boy" + }, + { + "t0": 98.526, + "t1": 99.788, + "text": "救命啊!", + "who": "boy" + } + ] + } + ], + "subs": [ + { + "t0": 91.8, + "t1": 93.715, + "text": "放羊娃吓坏了", + "who": "n" + }, + { + "t0": 93.715, + "t1": 94.992, + "text": "拼命地喊", + "who": "n" + }, + { + "t0": 95.372, + "t1": 96.634, + "text": "狼来了!", + "who": "boy" + }, + { + "t0": 96.634, + "t1": 98.526, + "text": "狼真的来了!", + "who": "boy" + }, + { + "t0": 98.526, + "t1": 99.788, + "text": "救命啊!", + "who": "boy" + } + ] + }, + { + "id": "noone", + "start": 100.7, + "dur": 11.166666666666666, + "voice_start": 101.2, + "voice_dur": 9.76, + "lines": [ + { + "who": "n", + "text": "山下的农民听见了,都以为他又在说谎。", + "t0": 101.2, + "dur": 4.296, + "subs": [ + { + "t0": 101.2, + "t1": 103.348, + "text": "山下的农民听见了", + "who": "n" + }, + { + "t0": 103.348, + "t1": 105.496, + "text": "都以为他又在说谎", + "who": "n" + } + ] + }, + { + "who": "farmer", + "text": "哼,又来骗人。", + "t0": 105.876, + "dur": 2.664, + "subs": [ + { + "t0": 105.876, + "t1": 106.409, + "text": "哼", + "who": "farmer" + }, + { + "t0": 106.409, + "t1": 108.54, + "text": "又来骗人", + "who": "farmer" + } + ] + }, + { + "who": "n", + "text": "谁也没有上山。", + "t0": 108.92, + "dur": 2.04, + "subs": [ + { + "t0": 108.92, + "t1": 110.96, + "text": "谁也没有上山", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 101.2, + "t1": 103.348, + "text": "山下的农民听见了", + "who": "n" + }, + { + "t0": 103.348, + "t1": 105.496, + "text": "都以为他又在说谎", + "who": "n" + }, + { + "t0": 105.876, + "t1": 106.409, + "text": "哼", + "who": "farmer" + }, + { + "t0": 106.409, + "t1": 108.54, + "text": "又来骗人", + "who": "farmer" + }, + { + "t0": 108.92, + "t1": 110.96, + "text": "谁也没有上山", + "who": "n" + } + ] + }, + { + "id": "chaos", + "start": 111.8667, + "dur": 8.0, + "voice_start": 112.367, + "voice_dur": 5.976, + "lines": [ + { + "who": "n", + "text": "狼冲进羊群,羊儿吓得四处乱跑,好几只羊被狼叼走了。", + "t0": 112.367, + "dur": 5.976, + "subs": [ + { + "t0": 112.367, + "t1": 113.725, + "text": "狼冲进羊群", + "who": "n" + }, + { + "t0": 113.725, + "t1": 115.898, + "text": "羊儿吓得四处乱跑", + "who": "n" + }, + { + "t0": 115.898, + "t1": 118.343, + "text": "好几只羊被狼叼走了", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 112.367, + "t1": 113.725, + "text": "狼冲进羊群", + "who": "n" + }, + { + "t0": 113.725, + "t1": 115.898, + "text": "羊儿吓得四处乱跑", + "who": "n" + }, + { + "t0": 115.898, + "t1": 118.343, + "text": "好几只羊被狼叼走了", + "who": "n" + } + ] + }, + { + "id": "regret", + "start": 119.8667, + "dur": 8.0, + "voice_start": 120.367, + "voice_dur": 5.328, + "lines": [ + { + "who": "n", + "text": "太阳落山了,放羊娃抱着剩下的小羊,伤心地哭了。", + "t0": 120.367, + "dur": 5.328, + "subs": [ + { + "t0": 120.367, + "t1": 121.699, + "text": "太阳落山了", + "who": "n" + }, + { + "t0": 121.699, + "t1": 124.363, + "text": "放羊娃抱着剩下的小羊", + "who": "n" + }, + { + "t0": 124.363, + "t1": 125.695, + "text": "伤心地哭了", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 120.367, + "t1": 121.699, + "text": "太阳落山了", + "who": "n" + }, + { + "t0": 121.699, + "t1": 124.363, + "text": "放羊娃抱着剩下的小羊", + "who": "n" + }, + { + "t0": 124.363, + "t1": 125.695, + "text": "伤心地哭了", + "who": "n" + } + ] + }, + { + "id": "moral", + "start": 127.8667, + "dur": 10.0, + "voice_start": 128.367, + "voice_dur": 7.224, + "lines": [ + { + "who": "n", + "text": "说谎的人,就算说了真话,也没有人再相信他。做一个诚实的孩子吧。", + "t0": 128.367, + "dur": 7.224, + "subs": [ + { + "t0": 128.367, + "t1": 129.437, + "text": "说谎的人", + "who": "n" + }, + { + "t0": 129.437, + "t1": 131.042, + "text": "就算说了真话", + "who": "n" + }, + { + "t0": 131.042, + "t1": 133.183, + "text": "也没有人再相信他", + "who": "n" + }, + { + "t0": 133.183, + "t1": 135.591, + "text": "做一个诚实的孩子吧", + "who": "n" + } + ] + } + ], + "subs": [ + { + "t0": 128.367, + "t1": 129.437, + "text": "说谎的人", + "who": "n" + }, + { + "t0": 129.437, + "t1": 131.042, + "text": "就算说了真话", + "who": "n" + }, + { + "t0": 131.042, + "t1": 133.183, + "text": "也没有人再相信他", + "who": "n" + }, + { + "t0": 133.183, + "t1": 135.591, + "text": "做一个诚实的孩子吧", + "who": "n" + } + ] + } + ] +} \ No newline at end of file diff --git a/wolf-coming/video/wolf/voice.mp3 b/wolf-coming/video/wolf/voice.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7da9c682a8da2c523b61eb98157aa6fb97422c35 --- /dev/null +++ b/wolf-coming/video/wolf/voice.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d161c8ce43a4b1d0cc2927f83ec8a118133e873443bc70dc060f425d584e483 +size 1655162 diff --git a/wolf-coming/web/public/film/fonts/chars.txt b/wolf-coming/web/public/film/fonts/chars.txt index 5348265823253b4387b6741f8393f709b944c985..25108ddfc2e9164285bf3d55066c59569a0ce3da 100644 --- a/wolf-coming/web/public/film/fonts/chars.txt +++ b/wolf-coming/web/public/film/fonts/chars.txt @@ -1 +1 @@ - !#%+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz·—‘’“”…、。《》「」【】・一三上下不与丑世丛东丝两个中为主丽举久么乎乐也乡书乱了事二于云五些交享亮亲人仅今介从他代以们仰任伙会传伴位低住体何作你例侧便俏保信倒候值停傍像儿充入全公六共关其具内再写冠冬冰冲决况冷冻准几出击函分则创到制前剩副力加动勾匀包化区半单印即压原又叉友发取变叠口古句另只叫可台右叶号叽各合同名后向吓吗吧呆告命和咔咦咬哀哈响哔哥哩哪唧啄喂善喊喙喜喳嘲嘴器噪嚓四回团围固国图圆圈在地场均坐块垂垛域堆塘填境增墨壁声壳处备复夏外多夜大天太失头夹奇奏她好妈始姐姑娘嫌子字存季孤学孩孵它安完官定实宣宫家宽寂寄密寒寸对导封小尖尚就尺尼尾层屋屏展属山岸峰工左差己已布希帘带帧幅幕干平并序应底度廓延建开式引张弦弯弹归当录形彩影往径很徒得循微心快念忽态怕性怪总息悄悬情惊愈意感慢成截所扇手才扑打扫扬把抖折报拉拍拖拢拨择持按挖振换掉排掠接控推描提插揭撇播擎支收改放政故效敢数整文斑断新旁无日时明春是昼显晓晕晚景晰暂暖暗曲更替最有朋望朝期木未本术朱机权村条来极构果枝枪染柔查柳标树校样核根格桃案梅梦梳检楷模横檐欠次欢欺款歌正此步段每比毛气水池治沼沿法波泥泪泽洁流测浏浓浮消淡深混清渐渡温渲游湖湿溅源溪滴漂漫潮灰点烟烧然照熬片版牙物状猎率环现理瓣生用田由电画畅界痕登白的皮皴益盖盛目直相省看真眼着睡瞬瞻短码砂砰破确碍示神离秋种秒积程空窗窝立竖章童竹笆笑笔笛符等筝签简篇篱簧粒粘系素紧红纤约级纳纷纸纹线组终经绒结绘给络绪续维缓编缘缩网置美群羽翅翻翼而肉背能脊脑脖脚脸腿膀臂自致色艺节芦花芽苇苹范茸草荷落蓝藏蛋蜓蜻蝴蝶融行衣补表被裁裂装西要见观规视览觉角解触计让记讲证试话语说请读课调负败质贴资走起路跳身躲轨转轮软轴轻载输辨边过近还这进远连迟述追送适选逐递通遮那部都配采里重野量金钟钻铃铺错键镜长门间阅阴阵阶阻附限除随难雁雪雾需霞露静面音页顶顿预领颈频颗题颜颤风飘飞马驳验高髻鱼鸟鸡鸣鸭鹅鹜麦黄黑齐️︐!()+,:;?|🐓👀🥚🦢 \ No newline at end of file + !#%+,-./0123456789:;?@ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz·—‘’“”…、。《》「」【】・一三上下不与丑专世丛东丝两个中为主丽举久么义之乌乎乐也乡书乱了事二于云互五些交享亮亲人什仅今介从他代以们仰任伊伙会传伤伴伸位低住体何余作你使例供侧便促俏保信倒候值做停健傍像儿充先光入全公六共关其具内再写农冠冬冰冲决况冷冻准几出击函分列则创到制刻前剩剪副力加动勾匀包化区升半单印即却卷压原去又叉及友双发取变叠口古句另只叫可台右叶号叼叽吃各合同名后向吓吗否吧听吹吻呀呆呈告呼命和咔咦咬哀哈响哔哥哩哪哭哼唧啄啊啦喂善喊喘喙喜喳嗓嘲嘴器噪嚎嚓四回团园围固国图圆圈在地场址均坏坐块坡垂型垛域堆塘塞填境墙增墨壁声壳处备复夏夕外多夜大天太失头夹奇奏奔套她好如妈始姐姑姿娃娘嫌子字存季孤学孩孵它安完官定实宣宫家宽寂寄密寒寓寸对导封小尔尖尚就尺尼尾层屋屏展属山岸峰工左差己已巾布希帘带帧帽幅幕干平并序库应底度廓延建开式引张弦弧弯弹强归当录形彩影往径很徒得循微心忙快念忽态怕急性怪总息悄悔悠悬情惊惋惜愈意感慢憋懒成我或截所扁扇手才扑打扛扫扬把抖折报抱担拉拍拖拢拨择拱拼拿持挂按挖振换掉排掠探接控推描提插握揭摇撇播擎支收改放政故效救敢散数整文斑斜断新方旁无日时明易春是昼显晓晕晚景晰暂暖暗曲更替最有朋望朝期木未末本术朱机权村条来极构果枝枪柄染柔查柳标树校样核根格桃案梅梦梯梳检楷模横檐欠次欢欺款歌正此步段每比毛民气水求汗池沙没治沼沿法波泥泪泽洁洋活流测浏浓浮消润淡深混清渐渡温渲游湖湿溅源溪滑滴漂漫潮灯灰点烟烧然照熬爱片版牙物状独狼猎率玩环现珠理瓣生用田由电画畅界略疏痕登白的皮皴益盖盛目直相省眉看真眼着睛睡瞬瞻短石码砂砰破确碍碎示神离秋种秒秧积移稀程稍空窗窝立竖章童竹笆笑笔笛符第等筐筝签简算篇篱簧类粒粗粘系素索紧红纤约级纳纷纸纹线组细终经绑绒结绘给络绪续维绿缓编缘缩网置署羊美群羽翅翻翼而耳聊肉肢肤背胸能脊脑脖脚脸腮腰腿膀膝臂自致色艺节芦花芽苇苗苹范茅茸草荷莹落蓝藏蛋蜓蜻蝴蝶融行衣补表袖被裁裂装裤襟西要见观规视览觉角解触言警计认让记讲设证词试诚话语说请读课谁调谎谷贝负败质贴资走赶起越跑路跳身躯躲躺轨转轮软轴轻载输辨辫边迁过近还这进远连迟述追送适逃选逐递逗通逼遍道遮那部都配采里重野量金钟钻铃铺链锄错键镜长门闪间阅阳阴阵阶阻附限除随难雁雪雷雾需霞露静靠面音页顶顿预领颈频颗题颜额颤风飘飞马驳验骗骨高髻鱼鸟鸡鸣鸦鸭鹅鹜麦黄黑默鼻齐️︐!()+,:;?|~🐑🐓🐺👀📢😂🥚🦢 \ No newline at end of file diff --git a/wolf-coming/web/public/film/fonts/mashanzheng.woff2 b/wolf-coming/web/public/film/fonts/mashanzheng.woff2 index c6ebf28b26ed73e01389a2ba5bcee1e199f70a24..336f627eea468eaf298942d59a3c615369298a25 100644 --- a/wolf-coming/web/public/film/fonts/mashanzheng.woff2 +++ b/wolf-coming/web/public/film/fonts/mashanzheng.woff2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e009f88eccbb75b93ece3214992da9dbab3c96e3e003d1078a88d753a001b630 -size 343228 +oid sha256:fe7ce130e02290a7dff93743e8a7492fb7c41bdbe9d6b338945144e018735476 +size 420940 diff --git a/wolf-coming/web/public/film/fonts/wenkai.woff2 b/wolf-coming/web/public/film/fonts/wenkai.woff2 index a9a12a80e05800bdfaaf761ad87db25d19e2e1cf..d3eee58aa0e17d95557ba0ac444a80499e4a0fef 100644 --- a/wolf-coming/web/public/film/fonts/wenkai.woff2 +++ b/wolf-coming/web/public/film/fonts/wenkai.woff2 @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c673c2d90b5d3e48ff76694316aec488e221d610b6269440d795d093207566fe -size 200844 +oid sha256:14374c15ca95593a0fc159be8f69f4435a3e7c4206b1817fd7bfba9495222f02 +size 240976 diff --git a/wolf-coming/web/public/wolf/voice.mp3 b/wolf-coming/web/public/wolf/voice.mp3 new file mode 100644 index 0000000000000000000000000000000000000000..7da9c682a8da2c523b61eb98157aa6fb97422c35 --- /dev/null +++ b/wolf-coming/web/public/wolf/voice.mp3 @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0d161c8ce43a4b1d0cc2927f83ec8a118133e873443bc70dc060f425d584e483 +size 1655162 diff --git a/wolf-coming/web/src/App.tsx b/wolf-coming/web/src/App.tsx index fa5c4c8b78c6a6d5ea9f9c1de454e944ae96002c..9cae8caba0c8ce7c5db62a7bfed28fa2df40f344 100644 --- a/wolf-coming/web/src/App.tsx +++ b/wolf-coming/web/src/App.tsx @@ -5,7 +5,12 @@ import Covers from '@/components/Covers' import PublishCopy from '@/components/PublishCopy' import LivePhotos from '@/components/LivePhotos' import Making from '@/components/Making' -import { TOTAL } from '@/film/film' +import { TOTAL, FILM, setFilm } from '@/film/film' +import { FILMS, pickFilm } from '@/films' + +const PICK = pickFilm() +setFilm(PICK.film) +if (typeof window !== 'undefined' && !PICK.locked) window.addEventListener('hashchange', () => location.reload()) const isPhone = () => typeof window !== 'undefined' && window.innerWidth < 768 && window.innerHeight > window.innerWidth @@ -29,8 +34,13 @@ export default function App() {
- 鸭 - 丑小鸭 + {[...FILM.title].pop()} + {FILM.title} + + {FILMS.filter((f) => f.id !== FILM.id).map((f) => ( + 系列 · 看《{f.title}》 + ))} + @@ -40,14 +50,12 @@ export default function App() {
-

丑小鸭变成白天鹅

-

- 安徒生童话 · 水墨手绘儿童动画。宣纸、毛笔、淡彩、朱红印章,像小学语文课本里的插图那样,一笔一画在你的浏览器里实时画出来;旁白、原创国风配乐与音效同步播放。 -

+

{FILM.title}{FILM.tagline}

+

{FILM.intro}

{Math.floor(TOTAL / 60)}′{Math.round(TOTAL % 60)}″
片长
-
14
场景
+
{FILM.timeline.scenes.length}
场景
2
画幅
@@ -60,7 +68,7 @@ export default function App() { -
+
@@ -68,7 +76,7 @@ export default function App() {
-
+
@@ -77,7 +85,7 @@ export default function App() {
- 故事改编自安徒生《丑小鸭》(1843,公有领域)· 画面、配乐、音效均由代码原创生成 · 字体:霞鹜文楷、马善政毛笔楷书(SIL OFL) + {FILM.credit} · 画面、配乐、音效均由代码原创生成 · 字体:霞鹜文楷、马善政毛笔楷书(SIL OFL)
) diff --git a/wolf-coming/web/src/components/Covers.tsx b/wolf-coming/web/src/components/Covers.tsx index 87275d73a3d7e3769ef17f27e41c690b0dce7a97..61207ada8eaf6a2c065da1d343895f95ab32c4c0 100644 --- a/wolf-coming/web/src/components/Covers.tsx +++ b/wolf-coming/web/src/components/Covers.tsx @@ -1,12 +1,14 @@ import { useEffect, useState } from 'react' import { Check, Download, Loader2, PackageOpen } from 'lucide-react' import { zipSync } from 'fflate' -import { COVER_DESIGNS, COVER_RATIOS, drawCover } from '@/film/overlays' +import { COVER_RATIOS, drawCover } from '@/film/overlays' +import { FILM } from '@/film/film' +import type { CoverDesign } from '@/film/types' import { loadFonts } from '@/film/core' const BASE = import.meta.env.BASE_URL type Ratio = (typeof COVER_RATIOS)[number] -type Design = (typeof COVER_DESIGNS)[number] +type Design = CoverDesign type Item = { key: string; ratio: string; design: string; name: string; url: string; w: number; h: number } const THUMB = 420 // 预览图长边;下载时才按原尺寸重新绘制(省内存,手机也流畅) @@ -20,8 +22,8 @@ async function renderCover(ratio: Ratio, d: Design, scale = 1): Promise { c.width = c.height = 1 return blob } -const fullName = (r: Ratio, d: Design) => `丑小鸭封面_${r.id.replace(':', 'x')}_${d.name}_${r.w}x${r.h}.jpg` -const find = (key: string) => { const [r, d] = key.split('|'); return { r: COVER_RATIOS.find((x) => x.id === r)!, d: COVER_DESIGNS.find((x) => x.id === d)! } } +const fullName = (r: Ratio, d: Design) => `${FILM.title}封面_${r.id.replace(':', 'x')}_${d.name}_${r.w}x${r.h}.jpg` +const find = (key: string) => { const [r, d] = key.split('|'); return { r: COVER_RATIOS.find((x) => x.id === r)!, d: FILM.covers.find((x) => x.id === d)! } } export default function Covers() { const [items, setItems] = useState([]) @@ -33,7 +35,7 @@ export default function Covers() { let dead = false ;(async () => { await loadFonts(BASE) - for (const r of COVER_RATIOS) for (const d of COVER_DESIGNS) { + for (const r of COVER_RATIOS) for (const d of FILM.covers) { if (dead) return await new Promise((res) => setTimeout(res, 30)) // 让出主线程 const blob = await renderCover(r, d, THUMB / Math.max(r.w, r.h)) @@ -44,7 +46,7 @@ export default function Covers() { return () => { dead = true } }, []) - const total = COVER_RATIOS.length * COVER_DESIGNS.length + const total = COVER_RATIOS.length * FILM.covers.length const shown = items.filter((i) => tab === '全部' || i.ratio === tab) const toggle = (k: string) => setSel((s) => { const n = new Set(s); if (n.has(k)) n.delete(k); else n.add(k); return n }) const [busyKey, setBusyKey] = useState(null) @@ -55,7 +57,7 @@ export default function Covers() { const files: Record = {} for (const it of items.filter((i) => sel.has(i.key))) { const { r, d } = find(it.key); files[it.name] = new Uint8Array(await (await renderCover(r, d)).arrayBuffer()); await new Promise((res) => setTimeout(res, 10)) } const out = zipSync(files, { level: 0 }) - save(new Blob([out as Uint8Array], { type: 'application/zip' }), `丑小鸭封面_${sel.size}张.zip`) + save(new Blob([out as Uint8Array], { type: 'application/zip' }), `${FILM.title}封面_${sel.size}张.zip`) setZipping(false) } @@ -84,7 +86,7 @@ export default function Covers() { {on && }
- {it.ratio}{COVER_DESIGNS.find((d) => d.id === it.design)?.name} · {it.w}×{it.h} + {it.ratio}{FILM.covers.find((d) => d.id === it.design)?.name} · {it.w}×{it.h}
diff --git a/wolf-coming/web/src/components/ExportPanel.tsx b/wolf-coming/web/src/components/ExportPanel.tsx index 282863b27a82df34206c3726406d09811bc7b016..887d0527c70e36597633613765cdaa43ce38826e 100644 --- a/wolf-coming/web/src/components/ExportPanel.tsx +++ b/wolf-coming/web/src/components/ExportPanel.tsx @@ -1,7 +1,7 @@ import { useRef, useState } from 'react' import { Download, Film, Loader2, X, Smartphone, Monitor } from 'lucide-react' import { Output, Mp4OutputFormat, BufferTarget, CanvasSource, AudioBufferSource, QUALITY_HIGH, getFirstEncodableVideoCodec, getFirstEncodableAudioCodec } from 'mediabunny' -import { renderFrame, TOTAL } from '@/film/film' +import { renderFrame, TOTAL, FILM } from '@/film/film' import { buildSoundtrack } from '@/film/audio' import { loadFonts } from '@/film/core' @@ -30,7 +30,7 @@ export default function ExportPanel({ defaultAspect }: { defaultAspect: Aspect } try { setMsg('准备字体与音轨…') await loadFonts(BASE) - const full = await buildSoundtrack(`${BASE}film/voice.mp3`) + const full = await buildSoundtrack(`${BASE}${FILM.assets}voice.mp3`) // ?exportSeconds=N 仅用于自动化测试(只导出前 N 秒) const lim = Number(new URLSearchParams(location.search).get('exportSeconds')) || TOTAL const N = Math.round(Math.min(TOTAL, lim) * fps) @@ -68,7 +68,7 @@ export default function ExportPanel({ defaultAspect }: { defaultAspect: Aspect } await output.finalize() const buf = (output.target as BufferTarget).buffer! const blob = new Blob([buf], { type: 'video/mp4' }) - const name = `丑小鸭_${aspect === '16:9' ? '横屏' : '竖屏'}_${W}x${H}_${fps}fps.mp4` + const name = `${FILM.title}_${aspect === '16:9' ? '横屏' : '竖屏'}_${W}x${H}_${fps}fps.mp4` setFile({ url: URL.createObjectURL(blob), name, size: blob.size }) setProg(1) setMsg(`完成:${vcodec.toUpperCase()} 视频${acodec ? ` + ${acodec.toUpperCase()} 音频` : '(此浏览器不支持音频编码,导出为无声视频)'}`) diff --git a/wolf-coming/web/src/components/LivePhotos.tsx b/wolf-coming/web/src/components/LivePhotos.tsx index 0b23dc133171cb23d6b3d3945ef0d4a9dfaa5791..b22c265266dde8d37cb71cef028b93ee65989545 100644 --- a/wolf-coming/web/src/components/LivePhotos.tsx +++ b/wolf-coming/web/src/components/LivePhotos.tsx @@ -1,9 +1,9 @@ import { Download, PackageOpen, Apple } from 'lucide-react' -import { LIVE } from '@/film/overlays' +import { FILM } from '@/film/film' import { PhotoCopy } from './PublishCopy' const BASE = import.meta.env.BASE_URL -const L = (f: string) => `${BASE}film/live/${f}` +const L = (f: string) => `${BASE}${FILM.assets}live/${f}` export default function LivePhotos() { return ( @@ -11,13 +11,13 @@ export default function LivePhotos() {
- 6 组苹果实况照片(每组 JPG + MOV,3:4 · 1080×1440 · 3 秒 · 30fps),按 livecanvas 流程写入 Live Photo 配对信息(共享 content identifier + still-image-time),另附 .pvt 资源包。 + {FILM.live.length} 组苹果实况照片(每组 JPG + MOV,3:4 · 1080×1440 · 3 秒 · 30fps),按 livecanvas 流程写入 Live Photo 配对信息(共享 content identifier + still-image-time),另附 .pvt 资源包。 在 Mac 上解压后,把同名的 JPG 和 MOV 一起拖进「照片」即合成为实况图,再同步到手机发小红书 / 抖音图文。
- 下载实况图压缩包 + 下载实况图压缩包
- {LIVE.map((l) => ( + {FILM.live.map((l) => (