"""Rebuild the six portal sections of resources/views/cinematic/how.blade.php around the REAL screens.

Each section: copy + three beats on the left; on the right a browser window (a phone on phones) holding the
captured portal page (capture.py) in an iframe. As the reader scrolls, the window's "camera" glides and zooms
onto the card the beat talks about, with a ring around it. The marks come from public/cinematic/how/manifest.json,
read by the Blade view at render time, so re-running capture.py updates the page without touching this file.

    python3 tools/how-it-works/build_page.py
"""
import os, re, json

ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..'))
VIEW = os.path.join(ROOT, 'resources', 'views', 'cinematic', 'how.blade.php')

# id, number, name, headline, highlight, lead, beats [(title, text, shot, mark, url)]
SECTIONS = [
    ('dashboard', '01', 'Dashboard', 'Everything you own,', 'on one page.',
     'The first screen after you sign in. Ryan owns one unit, and this is what he sees.',
     [('Your numbers', 'Value, loans, equity and monthly cash flow, totalled from the units you bought.', 'dash', 'kpi', 'propertylab.com.my/dashboard'),
      ('Your unit, stage by stage', 'What it is worth and earns, and where it stands on the seven stages from keys to tenant.', 'dash', 'unit', 'propertylab.com.my/dashboard'),
      ('What needs you', 'Your next task, and how far you are from your wealth goal.', 'dash', 'todo', 'propertylab.com.my/dashboard')]),
    ('plan', '02', 'Wealth Planning', 'Your goal, tested', 'against a bank.',
     'Ryan wants RM 6,000 a month in passive income by 45. The plan checks it with real bank rules.',
     [('Where you stand', 'What you want, what the plan reaches, and how far short it falls.', 'wp', 'stand', 'propertylab.com.my/wealth-planning'),
      ('Your next move', 'The steps that close the gap, in order.', 'wp', 'next', 'propertylab.com.my/wealth-planning'),
      ("The bank's view", 'Each purchase re-tested the way a bank would: income, commitments and loan limits.', 'wpbank', 'bank', 'propertylab.com.my/wealth-planning')]),
    ('evidence', '03', 'Analyze Property', 'Check a project', 'on real evidence.',
     'Ryan is looking at his next unit. Every project page starts open to everyone.',
     [('Open to everyone', 'Overview, developer record and aerial view are free. The numbers unlock when you sign in.', 'apguest', 'tabs', 'propertylab.com.my/my/projects/binastra-cochrane'),
      ('Investment analysis', 'Price against the market, rent and short-stay income, from real transactions and listings.', 'apinv', 'inv', 'propertylab.com.my/my/projects/binastra-cochrane'),
      ('Financial projection', 'Loan, instalment and cash flow, year by year.', 'apfin', 'fin', 'propertylab.com.my/my/projects/binastra-cochrane')]),
    ('learning', '04', 'Learning Hub', 'Understand', 'every step.',
     'From your first plan to reading a project like an analyst, in 中文 or English.',
     [('How to use PropertyLab', 'Short lessons for each tool, one action at a time.', 'lh', 'tools', 'propertylab.com.my/property/academy'),
      ('The DMAIC Road', 'Seven stops from mindset to takeoff, each one filled in with your own numbers.', 'lhroad', 'road', 'propertylab.com.my/property/academy'),
      ('Courses', 'Video courses with your progress saved.', 'lhel', 'el', 'propertylab.com.my/property/academy')]),
    ('coach', '05', 'AI Coach', 'Ask anything,', 'in your numbers.',
     'An AI that explains, using your own plan when you allow it. It does not tell you which property to buy.',
     [('Ask in plain words', 'Ryan asked why his plan falls short. He let the AI read his wealth plan.', 'aichat', 'q', 'propertylab.com.my/property/ai-advisor'),
      ('An answer about you', 'It answers with his figures and lays out his options.', 'aichat', 'a', 'propertylab.com.my/property/ai-advisor'),
      ('A playbook of prompts', '66 ready-made prompts for property, stocks, crypto and more, with the AI Active Blueprint membership.', 'aiip', 'ip', 'propertylab.com.my/property/ai-advisor')]),
    ('landlord', '06', 'Landlord Management', 'After the keys,', 'still looked after.',
     'Once you own a unit bought through PropertyLab, you follow it here.',
     [('Your properties', 'Every unit you own, with operators held to four published standards.', 'll', 'props', 'propertylab.com.my/property/landlord-management'),
      ('Seven stages to a tenant', 'Key collection, inspection, defects, renovation, finding a tenant: updated by our team.', 'llunit', 'steps', 'propertylab.com.my/property/my-properties'),
      ('Hand over the job', 'Ask the concierge to rent, renovate, manage or sell, and follow every step.', 'llreq', 'req', 'propertylab.com.my/property-concierge')]),
]

LOCK = '<svg viewBox="0 0 16 16" width="10" height="10" aria-hidden="true"><rect x="3" y="7" width="10" height="7" rx="1.5" fill="none" stroke="currentColor" stroke-width="1.4"/><path d="M5.5 7V5a2.5 2.5 0 0 1 5 0v2" fill="none" stroke="currentColor" stroke-width="1.4"/></svg>'


def section(sid, num, name, h1, h2, lead, beats):
    shots = []
    for b in beats:
        if b[2] not in shots:
            shots.append(b[2])
    lis = ''.join(f'<li data-shot="{b[2]}" data-mark="{b[3]}" data-url="{b[4]}"><i>{k + 1}</i><b>{b[0]}</b><span>{b[1]}</span></li>' for k, b in enumerate(beats))
    cams = ''.join(f'<div class="cam" data-shot="{s}"><iframe title="{name}: the real screen" tabindex="-1" aria-hidden="true" loading="lazy"></iframe><div class="ring"></div></div>' for s in shots)
    return f'''
<section class="step rs" id="{sid}" aria-label="{name}">
  <div class="pin">
    <div class="rs-copy">
      <div class="k">{num} / {name.upper()}</div>
      <h2 class="st-h">{h1}<br><span class="gr">{h2}</span></h2>
      <p class="st-p">{lead}</p>
      <ol class="beats">{lis}</ol>
    </div>
    <div class="rs-dev">
      <div class="rs-win"><div class="rs-bar"><i></i><i></i><i></i><span>{LOCK}<em class="rs-url">{beats[0][4]}</em></span><b class="rs-live">Live screen</b></div>
        <div class="rs-scr">{cams}<div class="rs-load">Loading the real screen…</div></div></div>
      <p class="rs-cap">A copy of the real portal page, as the demo member Ryan sees it.</p>
    </div>
  </div>
</section>
'''


CSS = r'''
/* ── Real screens (2026-10-05, owner: "some screenshots are not even actual screenshots from code and database").
   Each window holds a static copy of the real portal page (tools/how-it-works/capture.py) in an iframe, scaled
   by a "camera" that glides and zooms onto the card each beat talks about. ── */
.rs .pin{grid-template-columns:minmax(280px,360px) 1fr}
.rs .beats li span{display:block;margin-top:2px}
.rs-dev{position:relative;min-width:0;width:100%;max-width:960px;justify-self:center}
.rs-win{border-radius:16px;overflow:hidden;background:#fff;box-shadow:0 0 0 1px rgba(11,21,48,.1),0 2px 4px rgba(11,21,48,.04),0 50px 90px -40px rgba(11,21,48,.42)}
.rs-bar{display:flex;align-items:center;gap:8px;height:40px;padding:0 14px;background:#f2f5fb;border-bottom:1px solid #e3e8f2}
.rs-bar>i{width:10px;height:10px;border-radius:50%;background:#dfe5f0}.rs-bar>i:nth-child(1){background:#ff8f87}.rs-bar>i:nth-child(2){background:#ffcf6b}.rs-bar>i:nth-child(3){background:#6fd88b}
.rs-bar>span{flex:1;display:flex;align-items:center;gap:6px;max-width:440px;margin:0 auto;height:26px;padding:0 12px;border-radius:8px;background:#fff;box-shadow:inset 0 0 0 1px #e3e8f2;color:#7b86a0;font:500 11.5px "Inter",system-ui,sans-serif;white-space:nowrap;overflow:hidden}
.rs-url{font-style:normal;overflow:hidden;text-overflow:ellipsis}
.rs-live{display:inline-flex;align-items:center;gap:6px;font:600 10.5px "Inter",system-ui,sans-serif;letter-spacing:.08em;text-transform:uppercase;color:#0f9f6e}
.rs-live::before{content:"";width:7px;height:7px;border-radius:50%;background:#12b981;box-shadow:0 0 0 3px rgba(18,185,129,.18)}
.rs-scr{position:relative;height:min(64vh,600px);overflow:hidden;background:#f6f8fc}
.cam{position:absolute;left:0;top:0;transform-origin:0 0;opacity:0;transition:transform 1.15s cubic-bezier(.22,.7,.18,1),opacity .55s ease;will-change:transform}
.cam.on{opacity:1}
.cam iframe{display:block;border:0;background:#fff;pointer-events:none}
.ring{position:absolute;border-radius:18px;box-shadow:0 0 0 3px #2f6bff,0 0 0 9px rgba(47,107,255,.18),0 0 0 9999px rgba(11,21,48,.18);opacity:0;transition:opacity .45s ease .55s,left .9s cubic-bezier(.22,.7,.18,1),top .9s cubic-bezier(.22,.7,.18,1),width .9s,height .9s;pointer-events:none}
.cam.on .ring.show{opacity:1}
.rs-load{position:absolute;inset:0;display:grid;place-items:center;font:500 13px "Inter",system-ui,sans-serif;color:#9aa6bf;transition:opacity .4s;pointer-events:none}
.rs.ready .rs-load{opacity:0}
.rs-cap{margin:12px 2px 0;font-size:12px;color:#7b86a0;text-align:right}
@media (max-width:900px){
  .rs .pin{grid-template-columns:1fr;align-content:start;gap:12px;padding:76px 14px 12px}
  .rs .st-h{font-size:28px}.rs .st-p{display:none}
  .rs .beats{margin-top:10px}.rs .beats::before,.rs .beats::after{display:none}
  .rs .beats li{display:none;padding:0 0 0 38px;min-height:28px}.rs .beats li.on{display:block}
  .rs .beats li i{top:0}
  .rs-dev{max-width:330px}
  .rs-win{border-radius:34px;padding:10px;background:#0b1530;box-shadow:0 40px 80px -40px rgba(11,21,48,.6)}
  .rs-bar{display:none}
  .rs-scr{height:min(58vh,560px);border-radius:26px}
  .rs-cap{text-align:center;font-size:11px}
}
@media (prefers-reduced-motion:reduce){.cam{transition:opacity .3s}.ring{transition:opacity .3s}}
'''

JS = r'''
/* real screens: three beats per section; each beat points the window's camera at one card */
(function(){
  var phone=function(){return innerWidth<=900;},reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;
  [].slice.call(document.querySelectorAll('.rs')).forEach(function(sec){
    var scr=sec.querySelector('.rs-scr'),url=sec.querySelector('.rs-url'),li=[].slice.call(sec.querySelectorAll('.beats li')),
        cams={},cur=-1,mode=null,loaded=false;
    [].slice.call(sec.querySelectorAll('.cam')).forEach(function(c){cams[c.dataset.shot]=c;});
    /* the manifest tag sits after this script, so read it at use time */
    function shot(name){var M=window.__howShots||{};return M[name+'_'+(phone()?'m':'d')]||M[name+'_d'];}
    function load(){ if(loaded&&mode===(phone()?'m':'d'))return; mode=phone()?'m':'d'; loaded=true; var left=0;
      Object.keys(cams).forEach(function(n){var s=shot(n),f=cams[n].querySelector('iframe');if(!s)return;left++;
        f.style.width=s.w+'px';f.style.height=s.h+'px';cams[n].style.width=s.w+'px';cams[n].style.height=s.h+'px';
        f.onload=function(){if(--left<=0){sec.classList.add('ready');}aim(true);};f.src=s.src;});}
    function aim(force){ if(cur<0)return; var b=li[cur],c=cams[b.dataset.shot],s=shot(b.dataset.shot);if(!c||!s)return;
      Object.keys(cams).forEach(function(n){cams[n].classList.toggle('on',cams[n]===c);});
      var W=scr.clientWidth,H=scr.clientHeight,r=s.marks[b.dataset.mark],ring=c.querySelector('.ring');
      var base=W/s.w,sc=base,tx=0,ty=0,pad=phone()?10:22;
      if(r){ sc=Math.min(W/(r[2]+pad*2),(H-pad*2)/Math.max(r[3],60),1.0); sc=Math.max(base,sc);
        tx=W/2-(r[0]+r[2]/2)*sc; ty=pad-r[1]*sc;
        if(r[3]*sc<H-pad*2) ty=(H-r[3]*sc)/2-r[1]*sc;
        tx=Math.min(0,Math.max(W-s.w*sc,tx)); ty=Math.min(0,Math.max(Math.min(0,H-s.h*sc),ty));
        ring.style.left=(r[0]-6)+'px';ring.style.top=(r[1]-6)+'px';ring.style.width=(r[2]+12)+'px';ring.style.height=(r[3]+12)+'px';ring.classList.add('show');
      } else ring.classList.remove('show');
      c.style.transform='translate('+tx.toFixed(1)+'px,'+ty.toFixed(1)+'px) scale('+sc.toFixed(4)+')';
      url.textContent=b.dataset.url; }
    function st(k){ if(k===cur)return; cur=k; li.forEach(function(b,i){b.classList.toggle('on',i===k);b.classList.toggle('past',i<k);}); aim(); }
    function pr(){var r=sec.getBoundingClientRect(),span=sec.offsetHeight-innerHeight;return Math.min(1,Math.max(0,-r.top/span));}
    li.forEach(function(b,i){b.addEventListener('click',function(){var span=sec.offsetHeight-innerHeight;scrollTo({top:sec.offsetTop+span*[.05,.5,.9][i],behavior:reduce?'auto':'smooth'});});});
    new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting)load();});},{rootMargin:'120% 0px'}).observe(sec);
    addEventListener('resize',function(){if(loaded&&mode!==(phone()?'m':'d'))load();aim();});
    (function fr(){var p=pr();st(p<.33?0:p<.68?1:2);sec.querySelector('.beats').style.setProperty('--p',Math.min(1,p*1.15).toFixed(3));requestAnimationFrame(fr);})();
  });
})();
'''


def main():
    t = open(VIEW).read()
    a = t.index('<section class="step gs" id="dashboard">') if '<section class="step gs" id="dashboard">' in t else t.index('<section class="step rs" id="dashboard"')
    b = t.index('<section class="sec band">')
    t = t[:a] + ''.join(section(*s) for s in SECTIONS).lstrip('\n') + '\n' + t[b:]
    # styles
    t = re.sub(r'\n/\* ── Real screens .*?@media \(prefers-reduced-motion:reduce\)\{\.cam\{transition:opacity \.3s\}\.ring\{transition:opacity \.3s\}\}\n', '\n', t, flags=re.S)
    t = t.replace('</style>', CSS + '</style>', 1)
    # the manifest, read at render time
    tag = "<script>window.__howShots=@json(json_decode(@file_get_contents(public_path('cinematic/how/manifest.json')) ?: '{}', true));</script>"
    if 'window.__howShots' not in t:
        t = t.replace('</body>', tag + '\n</body>', 1)
    # the old evidence / generic-step scripts → the real-screen driver
    i = t.index('<script>\n(function(){\n  var step=document.getElementById(\'evidence\')') if "var step=document.getElementById('evidence')" in t else -1
    if i >= 0:
        j = t.index('</script>', i)
        keep = '''<script>
(function(){
  var reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;
  function fmt(v,d){return d?v.toFixed(d):Math.round(v).toLocaleString('en-US');}
  function count(root){[].slice.call(root.querySelectorAll('.n')).forEach(function(el){ if(el._c)return; el._c=1; var to=+el.dataset.n,d=+(el.dataset.d||0),t0=performance.now(),dur=reduce?1:700;
      (function tick(now){var w=Math.min(1,(now-t0)/dur),e=1-Math.pow(1-w,4);el.textContent=fmt(to*e,d);if(w<1)requestAnimationFrame(tick);})(t0);});}
  setTimeout(function(){var s=document.getElementById('stats');if(s)count(s);},700);
  var io=new IntersectionObserver(function(es){es.forEach(function(e){if(e.isIntersecting){e.target.classList.add('in');io.unobserve(e.target);}});},{threshold:.2});
  [].slice.call(document.querySelectorAll('.rv')).forEach(function(el,i){el.style.transitionDelay=(i%3)*.08+'s';io.observe(el);});
  [].slice.call(document.querySelectorAll('.tile')).forEach(function(t){t.addEventListener('pointermove',function(e){var r=t.getBoundingClientRect();t.style.setProperty('--mx',(e.clientX-r.left)+'px');t.style.setProperty('--my',(e.clientY-r.top)+'px');});});
})();
''' + JS
        t = t[:i] + keep + t[j:]
    else:
        t = re.sub(r'\n/\* real screens: three beats per section.*?\n\}\)\(\);\n', '\n' + JS, t, flags=re.S)
    open(VIEW, 'w').write(t)
    print('ok', len(t))


if __name__ == '__main__':
    main()
