# 第2回「配膳ロボットで1人減らせるか」の背景の録画（試算 v2.7 のページをローカルで開いて録る）
# 使い方: p2m-site/public/sim で python3 -m http.server 8765 を動かし、xvfb-run -s '-screen 0 1920x1400x24' python3 rec2.py [名前 ...]
# 第1回の src/rec_背景の録画.py を写して書き換えた（2026-10-25）。ページは p2m-site public/sim/serving-robot.html（md5 88adf2b2・v2.7）
import asyncio, sys, os, subprocess
from playwright.async_api import async_playwright
URL = 'http://localhost:8765/serving-robot.html'
CUR = """(() => { if (document.getElementById('fakecur')) return;
 const c=document.createElement('div'); c.id='fakecur';
 c.style.cssText='position:fixed;left:960px;top:700px;width:34px;height:34px;margin:-17px 0 0 -17px;border-radius:50%;background:rgba(240,90,40,.35);border:3px solid #F05A28;z-index:99999;pointer-events:none;transition:left .7s ease, top .7s ease, transform .15s';
 document.body.appendChild(c);})()"""
async def cur_to(pg, loc):
    await loc.scroll_into_view_if_needed()
    bb = await loc.bounding_box(); x, y = bb['x']+bb['width']/2, bb['y']+bb['height']/2
    await pg.evaluate(f"(()=>{{const c=document.getElementById('fakecur');c.style.left='{x}px';c.style.top='{y}px'}})()")
    await pg.wait_for_timeout(800)
async def click(pg, loc, wait=1400):
    await cur_to(pg, loc)
    await pg.evaluate("(()=>{const c=document.getElementById('fakecur');c.style.transform='scale(.7)';setTimeout(()=>c.style.transform='',150)})()")
    await loc.click(); await pg.wait_for_timeout(wait)
async def scroll_to(pg, y, ms=1200):
    await pg.evaluate(f"window.scrollTo({{top:{y},behavior:'smooth'}})"); await pg.wait_for_timeout(ms)
async def ytext(pg, text):
    return await pg.evaluate(f"(()=>{{const w=document.createTreeWalker(document.body,NodeFilter.SHOW_TEXT);let n;while(n=w.nextNode()){{if(n.textContent.trim().startsWith({text!r})){{const r=n.parentElement.getBoundingClientRect();return r.top+window.scrollY}}}}return 0}})()")
async def seg(pg, sid, v, wait=1500):
    inp = pg.locator(f'#{sid} input[value="{v}"]')
    if await inp.count():
        await click(pg, pg.locator(f'#{sid} label[for="' + (await inp.get_attribute('id')) + '"]'), wait)
    else:
        await click(pg, pg.locator(f'#{sid} button.more'), 900)
        await click(pg, pg.locator(f'#{sid}-panel button[data-v="{v}"]'), wait)
async def nth(pg, sid, i, wait=1500): await click(pg, pg.locator(f'#{sid} label').nth(i), wait)
async def mode(pg, i, wait=1500): await click(pg, pg.locator(f'#modes label[for="mode-{i}"]'), wait)
RS, ST = 'seg-robots', 'seg-staff'
RES = 600

async def clip(name, fn):
    os.makedirs('out', exist_ok=True)
    async with async_playwright() as p:
        b = await p.chromium.launch(headless=False, args=['--window-position=0,0','--window-size=1920,1300','--hide-scrollbars','--force-device-scale-factor=1'])
        ctx = await b.new_context(no_viewport=True); pg = await ctx.new_page()
        await pg.goto(URL, wait_until='networkidle')
        await pg.add_style_tag(content='html{scroll-behavior:auto} ::-webkit-scrollbar{display:none}')
        await pg.evaluate(CUR); await pg.wait_for_timeout(1200)
        top = await pg.evaluate('window.outerHeight-window.innerHeight'); ih = await pg.evaluate('window.innerHeight')
        if ih < 1080: raise SystemExit(f'inner {ih}')
        pre = getattr(fn, 'pre', None)
        if pre: await pre(pg)
        rec = subprocess.Popen(['ffmpeg','-v','error','-y','-f','x11grab','-framerate','30','-video_size','1920x1080','-draw_mouse','0','-i',os.environ.get('DISPLAY',':99')+f'.0+0,{top}',
                                '-c:v','libx264','-preset','veryfast','-crf','14','-pix_fmt','yuv420p',f'out/{name}.mp4'], stdin=subprocess.PIPE)
        await pg.wait_for_timeout(700); await fn(pg); rec.communicate(b'q')
        await ctx.close(); await b.close(); print('done', name, flush=True)

def pre_scroll(y):
    async def f(pg): await pg.evaluate(f"window.scrollTo(0,{y})"); await pg.wait_for_timeout(600)
    return f

async def c_entry2(pg):        # 3人・0台 → 2人・2台（料理を運ぶだけ）
    await seg(pg, RS, 0, 2200); await seg(pg, ST, 2, 1600); await seg(pg, RS, 2, 3000)
c_entry2.pre = pre_scroll(RES)
async def c_bus(pg):           # 2人・2台：まとめた皿も運ぶ
    await seg(pg, ST, 2, 1500); await mode(pg, 1, 3500)
c_bus.pre = pre_scroll(RES)
async def c_patrol6(pg):       # 2人・食事中に集めて回る：1→6台
    await seg(pg, ST, 2, 1200); await mode(pg, 2, 1500)
    for n in (1, 2, 3, 4, 5, 6): await seg(pg, RS, n, 1700)
    await pg.wait_for_timeout(1200)
c_patrol6.pre = pre_scroll(RES)
async def c_crowdp(pg):        # 混雑・集めて回る・3台：2人 → 3人
    await nth(pg, 'seg-arr', 2, 1500); await mode(pg, 2, 1500); await seg(pg, RS, 3, 1500)
    await seg(pg, ST, 2, 2600); await seg(pg, ST, 3, 2600)
c_crowdp.pre = pre_scroll(RES)
async def c_cost(pg):          # 費用の欄：集めて回る・3台 → 月3万円 → 4時間 → 買う・補助金
    await mode(pg, 2, 1000); await seg(pg, RS, 3, 1000)
    y = await pg.evaluate("document.getElementById('cost').getBoundingClientRect().top+window.scrollY")
    await scroll_to(pg, y-20, 2200); await pg.wait_for_timeout(3500)
    await nth(pg, 'seg-cprice', 0, 3200); await nth(pg, 'seg-cprice', 1, 1500)
    await nth(pg, 'seg-chours', 1, 3200); await nth(pg, 'seg-chours', 0, 1500)
    await nth(pg, 'seg-cpay', 1, 2000); await nth(pg, 'seg-csub', 1, 3500)
    await scroll_to(pg, y-20, 1500); await pg.wait_for_timeout(2500)
async def c_cut(pg):           # 「スタッフを1人減らして」の比べ：集めて回る・3人 → ロボット2台・3台
    await mode(pg, 2, 1200)
    y = await pg.evaluate("document.getElementById('ctable').getBoundingClientRect().top+window.scrollY")
    await scroll_to(pg, y-260, 2000)
    await nth(pg, 'seg-crob', 1, 2500); await nth(pg, 'seg-crob', 2, 3500)
CL = dict(entry2=c_entry2, bus=c_bus, patrol6=c_patrol6, crowdp=c_crowdp, cut=c_cut, cost=c_cost)
async def main():
    for k in (sys.argv[1:] or CL): await clip('sim_'+k, CL[k])
asyncio.run(main())
