# -*- coding: utf-8 -*-
# 動画「ハコブくんの3D」（3Dを作る順番の7手順）の場面。2026-11-13 1版
PATTERN = 2   # 大文字型（4-4c）。2026-11-13 エイイチが決定（大文字型＋動きあり）
MOVE = True
# 背景の映像は Unity の場面の寄り（KIOXIA service_sim/unity/closeup/ 依頼162・166・171）と、病院の1日（hospital161 1便の通し）
# 出どころの札は使わない（全部エイイチの記録）
import os as _os, sys as _sys, json as _json, base64 as _b64
_here = _os.path.dirname(_os.path.abspath(__file__))
for _c in (_os.environ.get("P2M_BASE", ""), _os.path.join(_here, "..", "base"), _os.path.join(_here, "..", "_制作ベース")):
    if _c and _os.path.exists(_os.path.join(_c, "scene_kit.py")):
        _sys.path.insert(0, _c); break
from scene_kit import *
from scene_kit import _p2page
from motion_kit import use_motion

# --- 場面ごとの、文の始まりの秒（声の長さから）---
_idx = _json.load(open(_os.path.join(_here, 'tts/index.json'))) if _os.path.exists(_os.path.join(_here, 'tts/index.json')) else []
_LINES = {}
for _o in _idx:
    _d = _LINES.setdefault(_o['scene'], [])
    _t0 = (_d[-1][0] + _d[-1][1] + _d[-1][2]) if _d else 0.0
    _d.append((_t0, _o['dur'], _o['pause']))


def at(scene, k=0, f=0.0):
    """その場面の k 番目の文の、読み上げの f 割の所（秒）"""
    L = _LINES.get(scene)
    if not L:
        return 0.3
    k = min(k, len(L) - 1)
    return round(L[k][0] + L[k][1] * f, 2)


def scene_len(scene):
    L = _LINES.get(scene)
    return (L[-1][0] + L[-1][1] + L[-1][2]) if L else 4.0


# --- 出どころの札（左上）---
FUDA_CSS = """
.fuda{position:absolute;left:140px;top:96px;display:flex;align-items:center;gap:14px;font-size:30px;font-weight:700;color:#0D0D0D}
.fuda .k{padding:6px 18px;border-radius:999px;font-size:28px;font-weight:900}
.fuda .k.hap{background:#0D0D0D;color:#fff}
.fuda .k.news{background:#1A6C98;color:#fff}
.fuda .k.ken{border:3px solid #888888;color:#555}
.fuda .k.ei{background:#C4471F;color:#fff}
.fuda .k.clip{background:#0D0D0D;color:#fff}
.fuda .k.chosa{background:#5A5A5A;color:#fff}
"""
FUDA_NAME = {"hap": "会社の発表", "news": "ニュース", "ken": "論文や本", "ei": "エイイチの記録と見立て", "clip": "公開されている実物の動画", "chosa": "調査会社の推計"}


def fuda(kind, who):
    return f'<style>{FUDA_CSS}</style><div class="fuda"><span class="k {kind}">{FUDA_NAME[kind]}</span><span>{who}</span></div>'


# --- エイイチ（ドット絵）---
def _uri(p):
    return 'data:image/png;base64,' + _b64.b64encode(open(_os.path.join(_here, p), 'rb').read()).decode()


import ei_talk as _talk
EI = {k: (_talk.base_uri(k), _talk.base_uri(k, True)) for k in ('udegumi', 'unazuku', 'yubisasu', 'hirameku')}
# エイイチが話す文（narration.py の EI_LINES）→ (場面, 場面の中で何番目の文か, 文番号)
import importlib.util as _ilu
_sp = _ilu.spec_from_file_location('_narr_here', _os.path.join(_here, 'narration.py'))
_nm = _ilu.module_from_spec(_sp); _sp.loader.exec_module(_nm)
_EIL = getattr(_nm, 'EI_LINES', [])
_EI_SPEAK = {}
_cnt = {}
for _o in _idx:
    _k = _cnt.get(_o['scene'], 0); _cnt[_o['scene']] = _k + 1
    if _o['i'] in _EIL:
        _EI_SPEAK.setdefault(_o['scene'], []).append((_k, _o['i']))

EI_CSS = """
.eiw{position:absolute;right:10px;bottom:0px;width:420px;height:420px;overflow:hidden}
.eiw img{position:absolute;left:0;top:8px;width:420px;height:420px;image-rendering:pixelated}
.ein{position:absolute;right:40px;bottom:346px;width:300px;text-align:center;font-size:30px;font-weight:900;color:#0D0D0D}
.ein span{display:inline-block;background:#F5F5F0;border:3px solid #D6D6D1;border-radius:999px;padding:4px 20px}
"""


def _blinks(start, end):
    t, out = start, []
    while t < end - 0.4:
        out.append(round(t, 2)); t += 3.3
    return out


def ei_wipe(pose, scene, pop=None, name=None, poses=None):
    """右下の小窓。pop=秒 で、ぽんと出る。poses=[(秒, 絵)] で途中で絵を替える（指さしは大事な所だけ。2026-10-28 エイイチの指示）。
    話している間は口と目を声に合わせて動かし、話していない間はまばたきだけする。
    絵ごとにまとまり（div）を作り、替える時刻に前のまとまりを消して（out）、次を出す（fade）。絵の透明な所から前の絵が見えないように"""
    seq_p = sorted(poses or [(0, pose)])
    if seq_p[0][0] > 0:
        seq_p = [(0, pose)] + seq_p
    L = scene_len(scene) + 1
    segs = [(t, seq_p[k + 1][0] if k + 1 < len(seq_p) else L, p) for k, (t, p) in enumerate(seq_p)]
    spans, mouth, blinks = [], [], []
    for k, i in _EI_SPEAK.get(scene, []):
        t0 = at(scene, k, 0.0)
        sq, end = _talk.mouth_seq(i, t0)
        spans.append((t0, end)); mouth += sq
        blinks += [(b, 0.13) for b in _talk.blink_times(t0, end)]
    mouth.sort()
    blinks += [(b, 0.15) for b in _blinks((pop or 0) + 1.3, scene_len(scene)) if not any(a0 - 0.2 <= b <= a1 + 0.2 for a0, a1 in spans)]
    out = ''
    for s0, s1, p in segs:
        ev = []
        lv = ([l for (t, l) in mouth if t <= s0] or [1])[-1]
        if lv != 1 and p in _talk.MOUTH:
            ev.append((s0, f'<img src="{_talk.mouth_patches(p)[lv]}">'))
        for t, l in mouth:
            if s0 < t < s1 and p in _talk.MOUTH:
                ev.append((t, f'<img src="{_talk.mouth_patches(p)[l]}" data-a="fade" data-t="{t}" data-d="0.01">'))
        e = _talk.eye_patches(p) if p != 'unazuku' else None
        for b, d in blinks:
            if e and s0 <= b and b + d < s1:
                ev.append((b, f'<img src="{e[1]}" data-a="fade" data-t="{b}" data-d="0.01">'))
                ev.append((round(b + d, 2), f'<img src="{e[0]}" data-a="fade" data-t="{round(b + d, 2)}" data-d="0.01">'))
        ev.sort(key=lambda x: x[0])
        inner = f'<img src="{EI[p][0]}">' + ''.join(h for _, h in ev)
        if s0 > 0:
            inner = f'<div style="position:absolute;inset:0" data-a="fade" data-t="{s0}" data-d="0.01">{inner}</div>'
        if s1 < L:
            inner = f'<div style="position:absolute;inset:0" data-a="out" data-t="{s1}" data-d="0.01">{inner}</div>'
        out += inner
    a = f' data-a="pop" data-t="{pop}" data-d="0.35"' if pop is not None else ''
    nm = f'<div class="ein"{a}><span>{name}</span></div>' if name else ''
    return f'<style>{EI_CSS}</style>{nm}<div class="eiw"{a}>{out}</div>'


def add(page_html, *parts):
    return page_html.replace('</body>', ''.join(parts) + '</body>', 1)


S = {}
MOTION = {}
O = {}
VIDEO = {}




# --- 背景の映像の上にカード（4-4e）---
HL, WARN = "#C4471F", "#62BFF5"
CARD_CSS = f"""
.cw{{position:absolute;left:0;right:0;top:215px;bottom:300px;display:flex;align-items:center;justify-content:center}}
.cd{{background:rgba(8,8,8,.86);border-radius:30px;padding:46px 84px 52px;text-align:center;color:#fff;max-width:1400px;box-shadow:0 10px 40px rgba(0,0,0,.35)}}
.cd .k{{font-size:42px;font-weight:700;color:#BDBDBD;margin-bottom:22px}}
.cd .b{{font-weight:900;line-height:1.16}}
.cd .s{{font-size:44px;font-weight:700;color:#D0D0D0;margin-top:26px;line-height:1.35}}
.cd .hl{{color:{HL}}} .cd .wn{{color:{WARN}}}
.cap{{position:absolute;left:140px;right:auto;top:110px;background:rgba(8,8,8,.72);color:#fff;font-size:30px;font-weight:700;padding:10px 22px;border-radius:10px}}
.ff{{position:absolute;right:120px;top:170px;background:#C4471F;color:#fff;font-size:64px;font-weight:900;padding:10px 34px;border-radius:16px;letter-spacing:.04em}}
"""


def card(inner, cap="", pop=0.05):
    c = f'<div class="cap">{cap}</div>' if cap else ""
    return f'<style>{CARD_CSS}</style>{c}<div class="cw"><div class="cd" data-a="pop" data-t="{pop:.2f}" data-d="0.4">{inner}</div></div>'


def C_word(lines, kicker="", sub="", size=130):
    k = f'<div class="k">{kicker}</div>' if kicker else ""
    s = f'<div class="s">{sub}</div>' if sub else ""
    return f'{k}<div class="b" style="font-size:{size}px">{"<br>".join(lines)}</div>{s}'


def bg_card(name, file, ss, inner=None, cap="", ei_pose=None, dim=0.5, slow=1.0, ff=False, poses=None, img_first=None):
    """背景に映像を流し、上にカード（inner が None なら映像だけ・左上に何の映像か）。
    img_first=割合：画像を見せたい場面。はじめはカードを出さず、背景を暗くしないで絵を見せ、場面のその割合の所でカードを出す
    （2026-11-13 エイイチの指示「画像の内容を見せたい時はカードを一時的に非表示に。センターから右にずらさない」）"""
    if inner:
        pop = 0.05
        if img_first is not None:
            pop = round(scene_len(name) * img_first, 2)
            dim = 1.0
        parts = card(inner, cap=cap, pop=pop)
    else:
        parts = f'<style>{CARD_CSS}</style>' + (f'<div class="cap">{cap}</div>' if cap else '')
    if ff:
        parts += '<div class="ff" data-a="pop" data-t="0.1" data-d="0.3">▶▶ 早送り</div>'
    if ei_pose:
        parts += ei_wipe(ei_pose, name, poses=poses)
    html = overlay(parts)
    MOTION[name] = dict(type="html", bg=dict(file=file, ss=ss, dim=dim, slow=slow), html=html)
    S[name] = html


def ei(name, page_html, pose='udegumi', pop=None, poses=None):
    S[name] = add(page_html, ei_wipe(pose, name, pop=pop, poses=poses))
    MOTION[name] = dict(type='html', html=S[name])


def img(name, file, title, w=1100, top=230, tag=None, credit=""):
    """画像を出し、上に題（ゴシックの太字。明朝・手書き風は使わない 2026-11-13 エイイチの指示）。画像は y 830 より上・x 400 より右に収める"""
    src = 'file://' + _os.path.join(_here, file)
    left = (1920 - w) // 2
    body = (f'<div style="position:absolute;left:0;right:0;top:110px;text-align:center">'
            f'<span data-a="pop" data-t="0.1" data-d="0.35" style="display:inline-block;font-weight:900;font-size:96px;color:#0D0D0D">{title}</span></div>'
            f'<img data-a="pop" data-t="0.35" data-d="0.5" src="{src}" style="position:absolute;left:{left}px;top:{top}px;width:{w}px">')
    S[name] = page(body, tag=tag)
    MOTION[name] = dict(type='html', html=S[name])


L166 = "src/closeup166/closeup166_load.mp4"       # ① 薬を積む（ハコブくんは新しい形）
R171 = "src/closeup171/closeup171_receive.mp4"     # ② 受け取る（本番のカメラ）
R171S = "src/closeup171/closeup171_receive_side_half.mp4"  # ② 受け取る（横のカメラ・0.5倍の速さ）
R162 = "src/closeup162/closeup162_receive.mp4"
D1 = "src/day1_blue.mp4"                           # 病院の1日（1便の通し・5倍速）
CAP3 = "Unity｜場面の寄り（3DCG）"
TAGS = {n: f"<b>{n}</b>{w}" for n, w in ((1, "使い道"), (2, "絵と大きさ"), (3, "数字で形"), (4, "並べて直す"), (5, "骨と顔"), (6, "ポーズで確認"), (7, "Unity で動かす"))}
STEPS7 = [("1", "使い道"), ("2", "絵と大きさ"), ("3", "数字で形"), ("4", "並べて直す"), ("5", "骨と顔"), ("6", "ポーズで確認"), ("7", "Unityで動かす")]

# 0 何に使うためか
bg_card("hook_use", L166, 4, C_word(['薬の受け渡しを', '<span class="hl">近くで見せたい</span>'], "病院の搬送ロボット", "", 120), cap=CAP3)
img("hook_use2", "src/LOG496_01_handover_scene.png", "ハコブくん × 大城さん", w=1000, top=262)
S["hook_joke"] = p2_word(['体が', '<span class="blue">できた！</span>'], kicker="ハコブくん、ついに", kc="muted", size=200, sub="……腕は、まだない")
bg_card("hook_why", D1, 8, C_word(['人は<span class="wn">青い筒</span>', 'ロボットは<span class="hl">白い箱</span>'], "これまでの病院のシミュレーション", "", 110), cap="Unity｜病院の1日（5倍速）", dim=0.6)
bg_card("hook_why2", D1, 24, C_word(['どう受け取るかは', '<span class="hl">見えない</span>'], "何回止まったかは、数えられる", "", 120), cap="Unity｜病院の1日（5倍速）", dim=0.6)
S["hook_joke2"] = p2_word(['筒から', '<span class="blue">箱へ</span>'], kicker="薬の受け渡し、これまでは", kc="muted", size=200, sub="絵としては、ちょっと寂しい")
S["hook_today"] = p2_word(['作る順番', '<span class="red">7つ</span>'], kicker="そのまま真似できる形で", kc="muted", size=200)
# 1 自己紹介
O.update({
 "title": overlay('<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">フィジカルAI解説</div><div class="c">案内役：ハコブくん（AI音声）</div></div>'),
 "title_eiichi": overlay('<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">エイイチ</div><div class="c">デザイナー／ロボティクスの研究・開発</div></div>'),
 "outro": overlay('<div class="lt"><img class="a" src="' + LOGO + '"><div class="b">相談は概要欄の連絡先から</div><div class="c">高評価・チャンネル登録／コメントはエイイチが全部読んでいます</div></div>'),
})
VIDEO.update({
 "title": ("src/day124_crop.mp4", 4.5, False),
 "title_eiichi": ("src/day124_crop.mp4", None, False),
 "outro": ("src/day124_crop.mp4", 18.0, False),
})
S["toc"] = p2_word(['7つの', '<span class="red">手順</span>'], kicker="3Dを作る順番", kc="muted", size=200)
S["toc2"] = p2_list(STEPS7, kicker="7つの手順", size=62)
S["toc_note"] = p2_word(['ぜんぶ', '<span class="red">3DCG</span>'], kicker="ハコブくんはマンガのロボット", kc="muted", size=200, sub="実物は、ありません")
# 手順1
S["s1"] = chapter2("手順1", "使い道を決める", "見た目の決まりも、先に")
S["s1_use"] = p2_list([("1", "試算"), ("2", "病院の1日"), ("3", "<span class=\"red\">場面を近くで</span>"), ("4", "本物で撮る")], kicker="病院の搬送ロボットを見せる、4つの段", size=96, tag=TAGS[1])
ei("ei_steps", p2_word(['4つが', '<span class="red">つながれば</span>'], kicker="エイイチの言葉", kc="muted", size=170, sub="より伝わりやすくなる", tag=TAGS[1]), pop=0.1)
S["s1_look"] = p2_pair("粘土の人形", "しすぎない", da="簡略化した見た目", db="デフォルメ", ca="blue", cb="red", mid="＋", kicker="見た目の決まり", sub="人とロボットを、同じ決まりで", size=120, tag=TAGS[1])
S["s1_tip"] = p2_word(['なんか違う、で', '<span class="red">迷子にならない</span>'], kicker="先に決めるコツ", kc="muted", size=120, tag=TAGS[1])
# 手順2
S["s2"] = chapter2("手順2", "絵と大きさ", "元の絵と、大きさをそろえる")
img("s2_ref", "src/LOG496_04_oshiro_ref.png", "マンガの三面図", w=450, top=262, tag=TAGS[2])
S["s2_size"] = p2_pair("1.15m", "1.65m", da="ハコブくん（高さ）", db="大城さん（身長）", ca="blue", cb="red", mid="／", kicker="実寸で作る", sub="頭は、身長の約7分の1", size=190, tag=TAGS[2])
S["s2_joke"] = p2_word(['50センチ', '<span class="blue">小さい</span>'], kicker="ハコブくん、大城さんより", kc="muted", size=190, sub="見上げるのには、慣れています", tag=TAGS[2])
# 手順3
S["s3"] = chapter2("手順3", "数字で形を作る", "形を作るのは Blender")
S["s3_split"] = p2_pair("Blender", "Unity", da="形を作る", db="動かして撮る", ca="blue", cb="red", mid="→", kicker="道具の役わけ", size=170, tag=TAGS[3])
S["s3_tool"] = p2_word(['画面を開かず', '<span class="red">コードで</span>'], kicker="Blender を Python で動かす", kc="muted", size=170, sub="コードを書いたのは、AI の Claude", tag=TAGS[3])
img("s3_robot", "src/hakobu_compare_v2_20261113.png", "Blender で、三面図から箱を組む", w=840, top=262, tag=TAGS[3])
img("s3_human", "src/nurse_oshiro_3d_preview.png", "Python で形を足し、Blender へ", w=1300, top=340, tag=TAGS[3])
S["s3_why"] = p2_word(['首の長さも', '<span class="red">ただの数字</span>'], kicker="形は、ぜんぶコードの中", kc="muted", size=170, sub="首が長い → 数字を1つ変えるだけ", tag=TAGS[3])
S["s3_joke"] = p2_word(['人間の首は', '<span class="blue">縮まない</span>'], kicker="ちなみに", kc="muted", size=170, sub="3Dの、いいところ", tag=TAGS[3])
# 手順4
S["s4"] = chapter2("手順4", "並べて直す", "絵に描き出して、見て、直す")
img("s4_render", "src/LOG496_03_hakobu_preview.png", "確認用の絵も Blender で", w=520, top=262, tag=TAGS[4])
bg_card("s4_side", "src/bg_hakobu_before.mp4", 0, C_word(['1人だけ', '<span class="hl">切り抜いた絵</span>'], "2人を並べると、ハコブくんの角だけ硬い", "", 130), cap="直す前のハコブくん（三面図の角のまま）", dim=0.55, img_first=0.5)
img("s4_fix", "src/LOG496_02_hakobu_before_after.png", "体の角を丸めた", w=840, top=262, tag=TAGS[4])
S["s4_tip"] = p2_word(['1人ずつより', '<span class="red">並べて見る</span>'], kicker="手順4のコツ", kc="muted", size=170, tag=TAGS[4])
img("s4_oshiro", "src/oshiro_first.png", "大城さん、最初の形", w=560, top=262, tag=TAGS[4])
bg_card("ei_first", "src/bg_oshiro_first.mp4", 0, C_word(['首長すぎ', '<span class="hl">肩いかつすぎ</span>'], "エイイチの最初のひと言", "", 140), cap="大城さん、最初の形", ei_pose="udegumi", dim=0.5, img_first=0.4)
bg_card("ei_first2", "src/bg_oshiro_first.mp4", 6, C_word(['バランス', '<span class="hl">悪っ！</span>'], "正直な感想", "", 200), cap="大城さん、最初の形", ei_pose="udegumi", dim=0.5, img_first=0.35,
        poses=[(at('ei_first2', 1, 0.1), 'yubisasu'), (round(at('ei_first2', 1, 0.1) + 3.0, 2), 'udegumi')])
# 早送り（最初の形と直した形を、目・肩・首に寄りながら切り替える。2026-11-13 エイイチの指示「目とか肩とかアップにして動きを」）
bg_card("s4_ff", "src/ff_oshiro.mp4", 0, None, cap="最初の形 ⇄ 直した形", dim=1.0, ff=True)
S["s4_ff2"] = p2_num("15", "回", kicker="作り直し", sub="首・肩・腕・目・Vネック", tag=TAGS[4])
S["s4_tip2"] = p2_pair("人", "AI", da="言葉で言う", db="数字に直す", ca="blue", cb="red", mid="→", kicker="直す所の伝え方", size=200, tag=TAGS[4])
# 手順5
S["s5"] = chapter2("手順5", "骨と顔", "動かすための見えない棒")
S["s5_bone"] = p2_num("45", "本", kicker="大城さんの骨（見えない棒）", sub="Blender で入れる", tag=TAGS[5])
S["s5_name"] = p2_word(['骨の名前は', '<span class="red">決まりどおり</span>'], kicker="ポイント｜Unity の人型の決まり", kc="muted", size=160, sub="よその歩く動きを、そのまま当てはめられる", tag=TAGS[5])
img("s5_face", "src/LOG496_08_talk_scene.png", "目・眉・口5つ", w=1000, top=262, tag=TAGS[5])
S["s5_joke"] = p2_word(['眉は', '<span class="blue">ありません</span>'], kicker="ハコブくんの顔は、画面", kc="muted", size=200, sub="困った顔は、気持ちでがんばる", tag=TAGS[5])
# 手順6
S["s6"] = chapter2("手順6", "ポーズで確認", "今日いちばん手こずった所")
img("s6_pose", "src/LOG496_06_nurse_poses.png", "8つのポーズ", w=1040, top=262, tag=TAGS[6])
bg_card("ei_sleeve", "src/bg_torn1.mp4", 0, C_word(['袖が', '<span class="hl">切れてるよ</span>'], "エイイチから", "", 120), cap="袖が裂けた所（直す前のコードで描き出し直した絵）", ei_pose="udegumi", dim=0.85, img_first=0.5)
S["s6_try1"] = p2_item("つなぎ目が裂けた", "袖を、別の部品に", no="1回目", size=130, tag=TAGS[6])
S["s6_try2"] = p2_item("中の肌が飛び出た", "袖の中の肌は、作らない", no="2回目", size=130, tag=TAGS[6])
S["s6_joke"] = p2_word(['見えない所は', '<span class="blue">作らない</span>'], kicker="3Dの世界", kc="muted", size=170, sub="なかなか、割り切っています", tag=TAGS[6])
bg_card("s6_try3", "src/bg_torn3.mp4", 0, C_word(['<span class="hl">3回目</span>', '肩がゆがむ'], "腕を上げると", "", 140), cap="3回目（直す前のコードで描き出した絵）", dim=0.85, img_first=0.5)
img("s6_fix", "src/LOG496_07_sleeve_fixed.png", "袖をまるごと腕の骨に", w=1100, top=290, tag=TAGS[6])
S["s6_tip"] = p2_word(['ポーズの確認を', '<span class="red">飛ばさない</span>'], kicker="動かすと、止まっている時には見えない所が壊れる", kc="muted", size=150, tag=TAGS[6])
S["s6_hakobu"] = p2_word(['袖の悩み', '<span class="blue">なし</span>'], kicker="服を着ていないハコブくん", kc="muted", size=200, tag=TAGS[6])
# 手順7
S["s7"] = chapter2("手順7", "Unityで動かす", "Blender から Unity へ")
S["s7_fbx"] = p2_pair("Blender", "Unity", da="形と骨", db="読み込んで動かす", ca="blue", cb="red", mid="FBX", kicker="バトンタッチ", size=170, tag=TAGS[7])
bg_card("ei_unity", L166, 1, C_word(['熱いっ！'], "Unity でここまでできるの", "", 200), cap=CAP3, ei_pose='udegumi',
        poses=[(at('ei_unity', 1, 0.1), 'hirameku'), (round(at('ei_unity', 1, 0.1) + 3.0, 2), 'udegumi')])
S["s7_mixamo"] = p2_num("33", "本", kicker="Mixamo から借りた動き", sub="歩く・うなずく・おじぎ など｜ファイルは配らない", tag=TAGS[7])
bg_card("s7_scene", L166, 8, None, cap="Unity｜薬剤部で、薬を積む（3DCG）", dim=1.0)
bg_card("s7_scene2", R171S, 2, None, cap="Unity｜届け先で、カードをかざして受け取る（3DCG・横から）", dim=1.0)
bg_card("s7_ff", R171, 0, None, cap="Unity｜細かい直し", dim=1.0, slow=0.3, ff=True)
bg_card("s7_end", R162, 20, C_word(['手と顔が', '<span class="hl">出てきた</span>'], "筒と箱だった世界に", "", 150), cap=CAP3)
# まとめ・これから
S["sum"] = p2_word(['7つの手順', '<span class="red">もう一度</span>'], kicker="まとめ", kc="muted", size=180)
S["sum2"] = p2_list(STEPS7, kicker="3Dを作る順番", size=62)
S["sum_code"] = p2_word(['形のコードは', '<span class="red">記事に</span>'], kicker="2人の形の作り方", kc="muted", size=170, sub="同じコードを動かせば、同じ形ができる")
S["next"] = p2_word(['この3Dで', '<span class="red">何を見せる？</span>'], kicker="これから", kc="muted", size=170)
ei("ei_next", p2_word(['渡す・受け取る', '<span class="red">トラブル・会話</span>'], kicker="エイイチの言葉", kc="muted", size=140), pop=0.1)
S["next2"] = p2_word(['作りはじめた', '<span class="red">ばかり</span>'], kicker="まだ", kc="muted", size=180, sub="本物の看護師さんに近いかは、これから確かめる")
S["next_joke"] = p2_word(['廊下で', '<span class="blue">すれ違う</span>'], kicker="次の場面", kc="muted", size=190, sub="ぶつからないように、がんばります")
bg_card("site", R171S, 8, C_word(['pixelstomotors.com'], "作り方のコードと記録を、毎日公開", "", 120), cap=CAP3)

# --- 白い紙の場面を減らす（2026-11-13 エイイチの指示「なるべく白背景の時間を減らして（意味ある形で）」）---
# 話していることに合う映像や3Dの絵を後ろに流し、文字はカードに載せる
def C_list(items, kicker="", size=64, tool=None):
    """tool={番号: 道具} があれば、右に道具の札（準備・Blender・Unity）を出し、同じ道具の行をまとめて見せる"""
    TC = {"準備": "#8A8A8A", "Blender": "#E87D0D", "Unity": "#62BFF5"}
    rows = ""
    for n, t in items:
        tg = ""
        if tool:
            tl = tool.get(n, "")
            tg = f'<span style="display:inline-block;min-width:{int(size*3.2)}px;margin-left:30px;font-size:{int(size*0.62)}px;padding:4px 16px;border-radius:999px;border:3px solid {TC[tl]};color:{TC[tl]};text-align:center;vertical-align:middle">{tl}</span>'
        rows += f'<div style="font-size:{size}px;font-weight:900;line-height:1.32;text-align:left;display:flex;align-items:center;justify-content:space-between"><span><span class="hl" style="margin-right:26px">{n}</span>{t}</span>{tg}</div>'
    k = f'<div class="k">{kicker}</div>' if kicker else ""
    return f'{k}<div style="display:inline-block">{rows}</div>'

TOOL7 = {"1": "準備", "2": "準備", "3": "Blender", "4": "Blender", "5": "Blender", "6": "Blender", "7": "Unity"}

def chap(name, no, word, sub, file, ss):
    bg_card(name, file, ss, C_word([f'<span class="hl">{word}</span>'], no, sub, 150), dim=0.45)

HDIAG, HFRONT, HAND, TALK = "src/bg_hakobu_diag.mp4", "src/bg_hakobu_front.mp4", "src/bg_handover.mp4", "src/bg_talk.mp4"
HCMP, OFIN, SFIX, POSES, FF = "src/bg_hakobu_cmp.mp4", "src/bg_oshiro_final.mp4", "src/bg_sleeve_fixed.mp4", "src/bg_poses.mp4", "src/ff_oshiro.mp4"
bg_card("hook_joke", HDIAG, 0, C_word(['体が', '<span class="wn">できた！</span>'], "ハコブくん、ついに", "……腕は、まだない", 170), img_first=0.3)
bg_card("hook_joke2", D1, 40, C_word(['筒から', '<span class="wn">箱へ</span>'], "薬の受け渡し、これまでは", "絵としては、ちょっと寂しい", 170), cap="Unity｜病院の1日（5倍速）", dim=0.6)
bg_card("hook_today", R162, 2, C_word(['作る順番', '<span class="hl">7つ</span>'], "そのまま真似できる形で", "", 170), cap=CAP3)
bg_card("toc", L166, 12, C_word(['7つの', '<span class="hl">手順</span>'], "3Dを作る順番", "", 170), cap=CAP3)
bg_card("toc2", L166, 18, C_list(STEPS7, "7つの手順と、使う道具", 40, TOOL7), cap=CAP3)
bg_card("toc_note", R171S, 0, C_word(['ぜんぶ', '<span class="hl">3DCG</span>'], "ハコブくんはマンガのロボット", "実物は、ありません", 170), cap=CAP3)
chap("s1", "手順1｜準備", "使い道を決める", "見た目の決まりも、先に", HAND, 0)
bg_card("s1_use", D1, 15, C_list([("1", "試算"), ("2", "病院の1日"), ("3", '<span class="hl">場面を近くで</span>'), ("4", "本物で撮る")], "病院の搬送ロボットを見せる、4つの段", 80), cap="Unity｜病院の1日（5倍速）", dim=0.55)
bg_card("ei_steps", D1, 30, C_word(['4つが', '<span class="hl">つながれば</span>'], "エイイチの言葉", "より伝わりやすくなる", 150), cap="Unity｜病院の1日（5倍速）", ei_pose="udegumi", dim=0.55)
bg_card("s1_look", HAND, 3, C_word(['粘土の人形', '<span class="hl">デフォルメしすぎない</span>'], "見た目の決まり", "人とロボットを、同じ決まりで", 110), img_first=0.35)
bg_card("s1_tip", HAND, 8, C_word(['なんか違う、で', '<span class="hl">迷子にならない</span>'], "先に決めるコツ", "", 120))
chap("s2", "手順2｜準備", "絵と大きさ", "元の絵と、大きさをそろえる", TALK, 0)
bg_card("s2_size", TALK, 3, C_word(['<span class="wn">1.15m</span> ／ <span class="hl">1.65m</span>'], "実寸で作る｜ハコブくん（高さ）・大城さん（身長）", "頭は、身長の約7分の1", 150), img_first=0.35)
bg_card("s2_joke", TALK, 8, C_word(['50センチ', '<span class="wn">小さい</span>'], "ハコブくん、大城さんより", "見上げるのには、慣れています", 160))
chap("s3", "手順3｜Blender", "数字で形を作る", "道具は Blender（形）と Unity（動き）", HCMP, 0)
bg_card("s3_split", L166, 4, C_word(['<span class="wn">Blender</span> → <span class="hl">Unity</span>'], "道具の役わけ", "形を作る → 動かして撮る", 120), cap=CAP3)
bg_card("s3_tool", HCMP, 4, C_word(['<span class="wn">Blender</span>', '<span class="hl">コードで動かす</span>'], "3Dの形を作る無料のソフト", "画面は開かず Python で｜コードは AI の Claude", 130), dim=0.45, img_first=0.35)
bg_card("s3_why", OFIN, 0, C_word(['首の長さも', '<span class="hl">ただの数字</span>'], "形は、ぜんぶコードの中", "首が長い → 数字を1つ変えるだけ", 150), dim=0.45)
bg_card("s3_joke", OFIN, 8, C_word(['人間の首は', '<span class="wn">縮まない</span>'], "ちなみに", "3Dの、いいところ", 150), dim=0.45)
chap("s4", "手順4｜Blender", "並べて直す", "絵に描き出して、見て、直す", HAND, 4)
bg_card("s4_tip", HAND, 10, C_word(['1人ずつより', '<span class="hl">並べて見る</span>'], "手順4のコツ", "", 150))
bg_card("s4_ff2", FF, 0, C_word(['<span class="hl">15</span>回'], "作り直し", "首・肩・腕・目・Vネック", 200), dim=0.45)
bg_card("s4_tip2", OFIN, 4, C_word(['<span class="wn">人</span> → <span class="hl">AI</span>'], "直す所の伝え方", "言葉で言う → 数字に直す", 170), dim=0.45)
chap("s5", "手順5｜Blender", "骨と顔", "動かすための見えない棒", POSES, 0)
bg_card("s5_bone", POSES, 4, C_word(['<span class="hl">45</span>本'], "大城さんの骨（見えない棒）", "骨は Blender で入れる", 200), cap="8つのポーズ（骨で付けた姿勢）", dim=0.45, img_first=0.35)
bg_card("s5_name", R162, 6, C_word(['骨の名前は', '<span class="hl">決まりどおり</span>'], "ポイント｜Unity の人型の決まり", "よその歩く動きを、そのまま当てはめられる", 140), cap=CAP3)
bg_card("s5_joke", HFRONT, 0, C_word(['眉は', '<span class="wn">ありません</span>'], "ハコブくんの顔は、画面", "困った顔は、気持ちでがんばる", 170), img_first=0.35)
chap("s6", "手順6｜Blender", "ポーズで確認", "今日いちばん手こずった所", POSES, 6)
bg_card("s6_try1", "src/bg_torn1.mp4", 0, C_word(['<span class="hl">1回目</span>', 'つなぎ目が裂けた'], "腕を前に出すと", "袖を、別の部品に", 130), cap="1回目（直す前のコードで描き出し直した絵）", dim=0.85, img_first=0.5)
bg_card("s6_try2", "src/bg_torn2.mp4", 0, C_word(['<span class="hl">2回目</span>', '中の肌が飛び出た'], "腕を前に出すと", "袖の中の肌は、作らない", 130), cap="2回目（直す前のコードで描き出し直した絵）", dim=0.85, img_first=0.5)
bg_card("s6_joke", SFIX, 0, C_word(['見えない所は', '<span class="wn">作らない</span>'], "3Dの世界", "なかなか、割り切っています", 150), dim=0.45, img_first=0.4)
bg_card("s6_tip", POSES, 18, C_word(['ポーズの確認を', '<span class="hl">飛ばさない</span>'], "動かすと、止まっている時には見えない所が壊れる", "", 130), dim=0.45)
bg_card("s6_hakobu", HFRONT, 6, C_word(['袖の悩み', '<span class="wn">なし</span>'], "服を着ていないハコブくん", "", 170), img_first=0.35)
chap("s7", "手順7｜Unity", "Unityで動かす", "Blender から Unity へ", L166, 0)
bg_card("s7_fbx", L166, 6, C_word(['<span class="wn">Blender</span> → <span class="hl">Unity</span>'], "バトンタッチ｜FBX で渡す", "形と骨 → 読み込んで動かす", 120), cap=CAP3)
bg_card("s7_mixamo", R162, 10, C_word(['<span class="hl">33</span>本'], "Mixamo から借りた動き", "歩く・うなずく・おじぎ など｜ファイルは配らない", 200), cap=CAP3)
bg_card("sum", R171S, 4, C_word(['7つの手順', '<span class="hl">もう一度</span>'], "まとめ", "", 160), cap=CAP3)
bg_card("sum2", R171S, 8, C_list(STEPS7, "3Dを作る順番と、使う道具", 40, TOOL7), cap=CAP3)
bg_card("sum_code", HCMP, 8, C_word(['形のコードは', '<span class="hl">記事に</span>'], "2人の形の作り方", "同じコードを動かせば、同じ形ができる", 150), dim=0.45)
bg_card("next", R162, 14, C_word(['この3Dで', '<span class="hl">何を見せる？</span>'], "これから", "", 150), cap=CAP3)
bg_card("ei_next", R162, 18, C_word(['渡す・受け取る', '<span class="hl">トラブル・会話</span>'], "エイイチの言葉", "", 130), cap=CAP3, ei_pose="udegumi")
bg_card("next2", R171S, 12, C_word(['作りはじめた', '<span class="hl">ばかり</span>'], "まだ", "本物の看護師さんに近いかは、これから確かめる", 150), cap=CAP3)
bg_card("next_joke", R162, 24, C_word(['廊下で', '<span class="wn">すれ違う</span>'], "次の場面", "ぶつからないように、がんばります", 160), cap=CAP3)

use_motion(MOTION, VIDEO, O, _here, MOVE)
