import { chromium } from 'playwright'; import { mkdir } from 'node:fs/promises'; const sleep=ms=>new Promise(r=>setTimeout(r,ms)); await mkdir('video/raw',{recursive:true}); const browser=await chromium.launch({headless:true}); const context=await browser.newContext({ viewport:{width:405,height:720}, deviceScaleFactor:1, recordVideo:{dir:'video/raw',size:{width:720,height:1280}}, colorScheme:'light' }); const page=await context.newPage(); await page.goto('http://127.0.0.1:4173',{waitUntil:'networkidle'}); const now=Date.now(); const seed=Array.from({length:7},(_,i)=>({ id:`demo-${i}`,occurredAt:new Date(now-(7-i)*86400000).toISOString(), bristolType:[3,4,4,6,4,3,4][i],color:'brown',urgency:i===3?3:1,discomfort:i===3?2:0, note:['Morning','After coffee','Easy','Travel day','Back on track','Morning','Feeling regular'][i],photoDataUrl:'', symptoms:{blood:false,blackOrDarkRed:false,severePain:false,vomiting:false,fever:false,cannotPassGas:false} })); await page.evaluate(data=>localStorage.setItem('timmy-ledger-v1',JSON.stringify(data)),seed); await page.reload({waitUntil:'networkidle'}); await page.addStyleTag({content:` #demo-touch{position:fixed;z-index:9999;width:54px;height:54px;border:4px solid rgba(255,255,255,.95);background:rgba(245,201,91,.5);box-shadow:0 0 0 8px rgba(21,125,120,.25);border-radius:50%;pointer-events:none;transform:translate(-50%,-50%)} .demo-focus{box-shadow:0 0 0 5px rgba(245,201,91,.95),0 0 0 10px rgba(21,125,120,.3)!important;transition:.2s!important} `}); async function touch(selector,{click=true,after=700}={}){ const loc=page.locator(selector).first();await loc.scrollIntoViewIfNeeded();const b=await loc.boundingBox();if(!b)throw new Error(`missing ${selector}`); await page.evaluate(({x,y})=>{document.querySelector('#demo-touch')?.remove();const d=document.createElement('div');d.id='demo-touch';d.style.left=x+'px';d.style.top=y+'px';document.body.append(d);d.animate([{transform:'translate(-50%,-50%) scale(.55)',opacity:.2},{transform:'translate(-50%,-50%) scale(1)',opacity:1},{transform:'translate(-50%,-50%) scale(.72)',opacity:.8}],{duration:520,easing:'ease-out'});setTimeout(()=>d.remove(),650)},{x:b.x+b.width/2,y:b.y+b.height/2}); await sleep(300);if(click)await loc.click();await sleep(after); } async function focus(selector,ms=1500){const loc=page.locator(selector).first();await loc.scrollIntoViewIfNeeded();await loc.evaluate(e=>e.classList.add('demo-focus'));await sleep(ms);await loc.evaluate(e=>e.classList.remove('demo-focus')).catch(()=>{});} // 0–8: Hero and value. await sleep(3000); await page.evaluate(()=>scrollTo({top:260,behavior:'smooth'}));await sleep(2600); await page.evaluate(()=>scrollTo({top:0,behavior:'smooth'}));await sleep(1400); // 8–17: form selection. await touch('[data-log]'); await sleep(1500); await touch('[data-type="3"]',{after:600}); await touch('[data-type="4"]',{after:1700}); await touch('#next',{after:900}); // 17–29: context and private photo lane. await touch('#color',{click:false,after:900}); await page.locator('#urgency').fill('2');await focus('#urgency',900); await page.locator('#discomfort').fill('1');await focus('#discomfort',700); await page.locator('#note').fill('After lunch — feeling normal');await focus('#note',1200); await focus('.photo-drop',2500); await focus('.fine',1300); await touch('#next',{after:1400}); // 29–43: safety boundary. await sleep(1700); await touch('[data-symptom="blood"]',{after:900}); await focus('.alert',5200); await touch('[data-symptom="blood"]',{after:800}); await touch('#save',{after:1700}); // 43–51: pattern view and calendar. await focus('.summary-card',2500); await touch('[data-view="calendar"]',{after:1500}); await focus('.calendar',3000); // 51–62: Timmy boundary. await touch('[data-view="timmy"]',{after:1600}); await touch('[data-prompt="pattern"]',{after:1400}); await touch('[data-prompt="food"]',{after:1100}); await focus('#chat',3600); // 62–70: privacy and control. await touch('[data-view="privacy"]',{after:1400}); await focus('.privacy-list',2600); await page.evaluate(()=>scrollTo({top:document.body.scrollHeight,behavior:'smooth'}));await sleep(2300); await focus('.danger-zone',1400); // 70–76: branded close. await page.evaluate(()=>{ const o=document.createElement('div');o.id='outro';o.innerHTML=`
YOUR INTELLIGENT POOPING PAL
Know your gut.
Keep your dignity.
TIMMY THE TALKING TURD
`; Object.assign(o.style,{position:'fixed',inset:'0',zIndex:'10000',background:'linear-gradient(145deg,#f7f3ea,#f5c95b)',display:'flex',flexDirection:'column',alignItems:'center',justifyContent:'center',textAlign:'center',color:'#28211d',fontFamily:'DM Sans,system-ui',opacity:'0'}); o.querySelector('img').style.cssText='width:190px;height:190px;filter:drop-shadow(0 16px 18px rgba(63,37,30,.18))'; o.querySelector('.small').style.cssText='font-size:12px;font-weight:800;letter-spacing:.13em;color:#157d78;margin-top:18px'; o.querySelector('.big').style.cssText='font-size:39px;line-height:1.04;font-weight:800;letter-spacing:-1.5px;margin:12px 0 24px'; o.querySelector('.pill').style.cssText='background:#28211d;color:white;padding:14px 20px;border-radius:99px;font-size:12px;font-weight:800;letter-spacing:.08em'; document.body.append(o);o.animate([{opacity:0},{opacity:1}],{duration:700,fill:'forwards'}); }); await sleep(6100); const video=page.video();await page.close();await video.saveAs('video/timmy-app-demo-raw.webm');await context.close();await browser.close(); console.log('video/timmy-app-demo-raw.webm');