# 第1回「何台入れたら店は変わる」の背景の録画（試算 v2.7 のページをローカルで開いて録る）
# 使い方: simulation/v2_7 で python3 -m http.server 8765 を動かし、xvfb-run -s '-screen 0 1920x1400x24' python3 rec_背景の録画.py [名前 ...]
# 「配膳ロボットで1人減らせるか」の src/rec_背景の録画.py を写して書き換えた（2026-10-24）
import asyncio, sys, os, subprocess
from playwright.async_api import async_playwright
URL = 'http://localhost:8765/serving-robot.v27.html'   # iCloud simulation/v2_7/serving-robot.v27.html（p2m-site public/sim/serving-robot.html と同じ md5 88adf2b2）
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 mark_text(pg, text):
    r = 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();if(r.top>0&&r.top<1080)return [r.left+r.width/2,r.top+r.height/2]}}}}return null}})()")
    if r:
        await pg.evaluate(f"(()=>{{const c=document.getElementById('fakecur');c.style.left='{r[0]}px';c.style.top='{r[1]+40}px'}})()"); await pg.wait_for_timeout(900)
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 segi(pg, sid, i, wait=1500):   # 何番目のボタン（混み具合など値が名前でないもの）
    await click(pg, pg.locator(f'#{sid} label[for="{sid[4:] if False else ""}"]') if False else pg.locator(f'#{sid} label').nth(i), wait)
async def store(pg, i, wait=1500): await click(pg, pg.locator(f'#stores label[for="store-{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_open(pg):          # 標準ファミレス・3人：0→1→2→3台
    await pg.wait_for_timeout(1200); await scroll_to(pg, RES, 1500)
    for n in (0, 1, 2, 3): await seg(pg, RS, n, 1800)
    await pg.wait_for_timeout(1500)
async def c_more(pg):          # 3人：3→4→5→6台
    for n in (3, 4, 5, 6): await seg(pg, RS, n, 1700)
    await pg.wait_for_timeout(1500)
c_more.pre = pre_scroll(RES)
async def c_one(pg):           # 3人・1台：料理が届くまで 2分12秒
    await seg(pg, RS, 1, 1800); await mark_text(pg, '料理ができてから届くまで'); await pg.wait_for_timeout(3500)
    await seg(pg, RS, 2, 1800); await mark_text(pg, '料理ができてから届くまで'); await pg.wait_for_timeout(2500)
c_one.pre = pre_scroll(RES)
async def c_table(pg):         # 「ロボットの台数を変えると」の比べ
    y = await ytext(pg, 'ロボットの台数を変えると'); await scroll_to(pg, y-80, 2200); await pg.wait_for_timeout(5000)
async def c_two(pg):           # 2人：0→1→2→3→6台
    await seg(pg, ST, 2, 1800)
    for n in (1, 2, 3, 6): await seg(pg, RS, n, 1600)
    await pg.wait_for_timeout(1500)
c_two.pre = pre_scroll(RES)
async def c_crowd(pg):         # 混雑・3人：0→1台
    await segi(pg, 'seg-arr', 2, 1800); await seg(pg, RS, 0, 1800); await seg(pg, RS, 1, 2500)
c_crowd.pre = pre_scroll(RES)
async def c_buffet(pg):        # 焼肉食べ放題・2人：0→1台
    await store(pg, 4, 1500); await scroll_to(pg, RES, 1200); await seg(pg, ST, 2, 1500); await seg(pg, RS, 0, 1600); await seg(pg, RS, 1, 2500)
async def c_hotel(pg):         # 洗い場が遠い店・2人：0→1台
    await store(pg, 3, 1500); await scroll_to(pg, RES, 1200); await seg(pg, ST, 2, 1500); await seg(pg, RS, 0, 1600); await seg(pg, RS, 1, 2500)
async def c_patrol(pg):        # 2人・食事中に集めて回る・3台
    await seg(pg, ST, 2, 1500); await mode(pg, 2, 1800); await seg(pg, RS, 3, 3000)
c_patrol.pre = pre_scroll(RES)
CL = dict(open=c_open, more=c_more, one=c_one, table=c_table, two=c_two, crowd=c_crowd, buffet=c_buffet, hotel=c_hotel, patrol=c_patrol)
async def main():
    for k in (sys.argv[1:] or CL): await clip('sim_'+k, CL[k])
asyncio.run(main())
