{
  "version": 1,
  "id": "scn_plant_finance",
  "slug": "plant-finance",
  "name": "Manufacturing finance",
  "icon": "eurosign.circle",
  "tint": "#D3A85A",
  "settings": {
    "description": "Rebuild a manufacturer's numbers from its ledger so a lender can check them: sources and uses of every borrowed million, reported vs corrected result, margin per order, the hedge book, and a rulebook of claims re-checked against the production system on every read. Read-only; not an audit opinion."
  },
  "assets": [
    {
      "id": "pf-bridge",
      "type": "daslab/webview",
      "name": "Bank bridge",
      "fields": {
        "html": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>\n  :root{\n    --bg:#0b0f14; --panel:#10161e; --line:rgba(255,255,255,.08);\n    --ink:#e6ecf3; --muted:#8e9aa8; --dim:#5b6774;\n    --acc:#d3a85a; --green:#43c98a; --red:#ef5a5e; --amber:#e3b04b; --blue:#5aa5e6; --use:#7f90a4;\n    --mono:ui-monospace,\"SF Mono\",Menlo,Consolas,monospace;\n    --sans:Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;\n  }\n  *{box-sizing:border-box;margin:0;padding:0}\n  html,body{height:100%}\n  body{background:var(--bg);color:var(--ink);font-family:var(--sans);overflow:hidden;-webkit-font-smoothing:antialiased}\n  .stage{position:absolute;inset:0;overflow:hidden;background:var(--bg)}\n  .topbar{display:flex;align-items:center;gap:9px;padding:10px 14px;border-bottom:1px solid var(--line)}\n  .brand{font-weight:800;letter-spacing:.13em;font-size:11px;white-space:nowrap}\n  .brand b{color:var(--acc)}\n  .divider{width:1px;height:15px;background:var(--line);flex-shrink:0}\n  .role{font:600 10.5px/1 var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .role span{color:var(--muted);font-weight:400}\n  .chip{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.1em;color:var(--acc);background:rgba(211,168,90,.12);padding:4px 7px;border-radius:4px;white-space:nowrap;flex-shrink:0}\n  .body{position:absolute;top:39px;left:0;right:0;bottom:0;overflow:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}\n  .body>*{flex-shrink:0}\n  .num{font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:4px;overflow:hidden}\n  .card .hd{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:7px 10px;border-bottom:1px solid var(--line);font:600 8.5px/1 var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}\n  .card .hd .aside{font:500 8.5px/1 var(--mono);letter-spacing:0;text-transform:none;color:var(--muted)}\n  .legend{display:flex;gap:10px;font:500 8px/1 var(--mono);color:var(--muted);letter-spacing:0;text-transform:none}\n  .legend i{display:inline-block;width:7px;height:7px;border-radius:2px;vertical-align:-1px;margin-right:4px}\n  .chart{display:block;width:100%}\n  .chart svg{display:block}\n  .closes{display:flex;align-items:center;gap:8px;padding:7px 10px;border-top:1px solid var(--line);font:500 9px/1 var(--mono);color:var(--muted)}\n  .closes .num b{color:var(--ink);font-weight:600}\n  .stamp{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.12em;padding:4px 6px;border:1px solid currentColor;border-radius:3px}\n  .stamp.ok{color:var(--green)} .stamp.gap{color:var(--amber)}\n  .split{display:grid;grid-template-columns:1fr;gap:10px}\n  .split.two{grid-template-columns:1.25fr 1fr}\n  .flag{background:rgba(227,176,75,.07);border:1px solid rgba(227,176,75,.35);border-radius:4px;padding:10px 11px;display:flex;flex-direction:column;gap:7px;position:relative;overflow:hidden}\n  .flag[hidden]{display:none}\n  .flag .mk{align-self:flex-start;font:700 8px/1 var(--mono);letter-spacing:.1em;color:var(--amber);border:1px solid rgba(227,176,75,.5);border-radius:3px;padding:3px 5px}\n  .flag b{font:600 11px/1.3 var(--sans);color:var(--ink)}\n  .flag .amt{font:600 20px/1 var(--mono);color:var(--amber)}\n  .flag small{font:500 8.5px/1.45 var(--mono);color:var(--muted)}\n  .prov{margin-top:auto;font:500 8px/1.4 var(--mono);color:var(--dim)}\n  svg text{font-family:var(--mono)}\n  .grow{transform-box:fill-box;transform-origin:50% 100%;animation:grow .75s cubic-bezier(.2,.8,.2,1) both}\n  .grow.down{transform-origin:50% 0%}\n  @keyframes grow{from{transform:scaleY(var(--s0,0))}to{transform:scaleY(1)}}\n  .fade{animation:fade .5s ease both}\n  @keyframes fade{from{opacity:0}to{opacity:1}}\n  .stamp.in{animation:stamp .5s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes stamp{0%{opacity:0;transform:scale(1.6)}70%{opacity:1;transform:scale(.96)}100%{opacity:1;transform:scale(1)}}\n  .sweep::after{content:'';position:absolute;inset:0;background:linear-gradient(90deg,transparent,rgba(227,176,75,.2),transparent);transform:translateX(-100%);animation:sweep 1.1s ease-out .15s both;pointer-events:none}\n  @keyframes sweep{to{transform:translateX(100%)}}\n  @media (prefers-reduced-motion:reduce){*{animation:none!important}}\n  @media (max-width:520px){ .split.two{grid-template-columns:1fr} }\n</style></head>\n<body>\n  <div class=\"stage\">\n    <div class=\"topbar\">\n      <div class=\"brand\">DASLAB <b>LEDGER</b></div>\n      <div class=\"divider\"></div>\n      <div class=\"role\" id=\"role\">Bank bridge <span>· company code 1010 · sources and uses · € m</span></div>\n      <div class=\"chip\" id=\"chip\">ACDOCA · MIRROR</div>\n    </div>\n    <div class=\"body\">\n      <div class=\"card\">\n        <div class=\"hd\"><span>Sources and uses</span><span class=\"aside\" id=\"basis\"></span></div>\n        <div class=\"chart\" id=\"wf\"></div>\n        <div class=\"closes\" id=\"closes\"></div>\n      </div>\n      <div class=\"split\" id=\"split\">\n        <div class=\"card\" id=\"yrCard\">\n          <div class=\"hd\"><span>By year</span><span class=\"legend\"><span><i style=\"background:var(--blue)\"></i>net bank draws</span><span><i style=\"background:var(--red)\"></i>result</span></span></div>\n          <div class=\"chart\" id=\"yrs\"></div>\n        </div>\n        <div class=\"flag\" id=\"flag\" hidden></div>\n      </div>\n      <div class=\"prov\">Illustrated on a fictional company. Not a customer’s data.</div>\n    </div>\n  </div>\n<script>\n(()=>{\n  const el=id=>document.getElementById(id);\n  const replay=window.__DASLAB_REPLAY__===true;\n  let d=window.__DASLAB_WIDGET_DATA__||{}, last=null, key=JSON.stringify(d);\n  const fmt=n=>(n<0?'−':'')+Math.abs(n).toFixed(1);\n  const byKey=(list,k)=>(list||[]).find(r=>r.k===k);\n\n  /** Sources step up to their total, uses step back down to zero. A bar\n   * animates only when its value changed since the last commit. */\n  function renderWaterfall(anim){\n    const host=el('wf'); const W=host.clientWidth||520, H=W<440?196:206;\n    const cols=[]; let run=0;\n    (d.sources||[]).forEach(r=>{ cols.push({k:r.k,label:r.short,v:r.value,y0:run,y1:run+r.value,kind:r.value<0?'neg':'src',side:'sources'}); run+=r.value; });\n    const S=run; cols.push({k:'total',label:'Total',v:S,y0:0,y1:S,kind:'total',side:'total'});\n    (d.uses||[]).forEach(r=>{ cols.push({k:r.k,label:r.short,v:r.value,y0:run-r.value,y1:run,kind:(d.flag&&d.flag.k===r.k)?'flag':'use',side:'uses'}); run-=r.value; });\n    const gap=Math.abs(run)>.05?run:0;\n    if(gap) cols.push({k:'gap',label:'Gap',v:gap,y0:0,y1:gap,kind:'gap',side:'gap'});\n    const ymax=d.scaleMax||Math.ceil(Math.max(...cols.map(c=>Math.max(c.y0,c.y1)))/20)*20||20;\n    const narrow=W/cols.length<46, PL=26, PR=8, PT=16, PB=narrow?40:22;\n    const cw=(W-PL-PR)/cols.length, bw=Math.min(cw*.62,34);\n    const y=v=>PT+(H-PT-PB)*(1-v/ymax);\n    let g='';\n    for(let t=0;t<=ymax;t+=20){ g+='<line x1=\"'+PL+'\" x2=\"'+(W-PR)+'\" y1=\"'+y(t)+'\" y2=\"'+y(t)+'\" stroke=\"rgba(255,255,255,'+(t===0?.18:.05)+')\"/><text x=\"'+(PL-5)+'\" y=\"'+(y(t)+3)+'\" text-anchor=\"end\" fill=\"#5b6774\" font-size=\"7.5\">'+t+'</text>'; }\n    const color={src:'#5aa5e6',neg:'#ef5a5e',total:'#d3a85a',use:'#7f90a4',flag:'#e3b04b',gap:'rgba(227,176,75,.22)'};\n    cols.forEach((c,i)=>{\n      const cx=PL+cw*i+cw/2, top=y(Math.max(c.y0,c.y1)), bot=y(Math.min(c.y0,c.y1)), h=Math.max(bot-top,1);\n      const prevRow=last&&(c.side==='total'?{value:sum(last.sources)}:byKey(last[c.side],c.k));\n      const changed=anim&&(!prevRow||Math.abs(prevRow.value-c.v)>.05||(c.kind==='flag'&&!(last.flag)));\n      const down=c.kind==='use'||c.kind==='flag'||c.kind==='neg';\n      const s0=prevRow&&prevRow.value&&c.v?Math.max(0,Math.min(1,prevRow.value/c.v)):0;\n      const cls=changed?' class=\"grow'+(down?' down':'')+'\" style=\"--s0:'+s0+';animation-delay:'+(i*55)+'ms\"':'';\n      const stroke=c.kind==='gap'?' stroke=\"#e3b04b\" stroke-dasharray=\"3 2\"':'';\n      g+='<rect x=\"'+(cx-bw/2)+'\" y=\"'+top+'\" width=\"'+bw+'\" height=\"'+h+'\" rx=\"1.5\" fill=\"'+color[c.kind]+'\"'+stroke+cls+'/>';\n      if(i<cols.length-1){ const lvl=c.side==='uses'||c.kind==='neg'?Math.min(c.y0,c.y1):Math.max(c.y0,c.y1); const nx=PL+cw*(i+1)+cw/2-bw/2; g+='<line x1=\"'+(cx+bw/2)+'\" x2=\"'+nx+'\" y1=\"'+y(c.kind==='total'?c.y1:lvl)+'\" y2=\"'+y(c.kind==='total'?c.y1:lvl)+'\" stroke=\"rgba(255,255,255,.18)\" stroke-dasharray=\"2 2\"/>'; }\n      const vc=c.kind==='neg'?'#ef5a5e':c.kind==='total'?'#d3a85a':c.kind==='flag'||c.kind==='gap'?'#e3b04b':'#b7c2cf';\n      g+='<text x=\"'+cx+'\" y=\"'+(top-4)+'\" text-anchor=\"middle\" fill=\"'+vc+'\" font-size=\"'+(narrow?7:8)+'\" font-weight=\"600\"'+(changed?' class=\"fade\" style=\"animation-delay:'+(i*55+300)+'ms\"':'')+'>'+fmt(c.v)+'</text>';\n      const ly=H-PB+11;\n      g+=narrow?'<text transform=\"translate('+(cx+2)+','+(ly-2)+') rotate(-42)\" text-anchor=\"end\" fill=\"#8e9aa8\" font-size=\"7.5\">'+c.label+'</text>'\n        :'<text x=\"'+cx+'\" y=\"'+ly+'\" text-anchor=\"middle\" fill=\"'+(c.kind==='total'?'#d3a85a':'#8e9aa8')+'\" font-size=\"7.5\">'+c.label+'</text>';\n    });\n    const split=PL+cw*((d.sources||[]).length+.5);\n    g+='<text x=\"'+(PL+2)+'\" y=\"10\" fill=\"#5b6774\" font-size=\"7.5\" letter-spacing=\".08em\">SOURCES</text><text x=\"'+(split+cw+2)+'\" y=\"10\" fill=\"#5b6774\" font-size=\"7.5\" letter-spacing=\".08em\">USES</text>';\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n    return {S,U:sum(d.uses),gap};\n  }\n  function sum(list){ return (list||[]).reduce((a,r)=>a+(r.value||0),0); }\n\n  /** Diverging bars per year: draws up, result down. New years grow in. */\n  function renderYears(anim){\n    const host=el('yrs'); const yrs=d.years||[]; const W=host.clientWidth||300, H=104;\n    const maxD=Math.max(...yrs.map(v=>v.draw),1), maxL=Math.max(...yrs.map(v=>Math.max(0,-v.result)),1);\n    const PT=14, PB=16, sc=(H-PT-PB)/(maxD+maxL), mid=PT+maxD*sc;\n    const cw=(W-16)/Math.max(yrs.length,1), bw=Math.min(cw*.3,14);\n    let g='<line x1=\"8\" x2=\"'+(W-8)+'\" y1=\"'+mid+'\" y2=\"'+mid+'\" stroke=\"rgba(255,255,255,.18)\"/>';\n    const seen=new Set((last&&last.years||[]).map(v=>v.label));\n    yrs.forEach((v,i)=>{\n      const cx=8+cw*i+cw/2, hd=v.draw*sc, hr=Math.max(0,-v.result)*sc;\n      const fresh=anim&&!seen.has(v.label), dl=fresh?'animation-delay:'+(i*90)+'ms':'';\n      g+='<rect x=\"'+(cx-bw-1)+'\" y=\"'+(mid-hd)+'\" width=\"'+bw+'\" height=\"'+hd+'\" rx=\"1.5\" fill=\"#5aa5e6\"'+(fresh?' class=\"grow\" style=\"'+dl+'\"':'')+'/>';\n      g+='<rect x=\"'+(cx+1)+'\" y=\"'+mid+'\" width=\"'+bw+'\" height=\"'+hr+'\" rx=\"1.5\" fill=\"#ef5a5e\" opacity=\".85\"'+(fresh?' class=\"grow down\" style=\"'+dl+'\"':'')+'/>';\n      g+='<text x=\"'+(cx-bw/2-1)+'\" y=\"'+(mid-hd-3)+'\" text-anchor=\"middle\" fill=\"#8e9aa8\" font-size=\"7\">'+v.draw.toFixed(1)+'</text>';\n      g+='<text x=\"'+cx+'\" y=\"'+(H-4)+'\" text-anchor=\"middle\" fill=\"'+(fresh?'#e6ecf3':'#5b6774')+'\" font-size=\"7.5\">'+v.label+'</text>';\n    });\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n  }\n\n  function renderFlag(anim){\n    const f=d.flag, box=el('flag'); el('split').className='split'+(f?' two':''); box.hidden=!f;\n    if(!f) return;\n    const fresh=anim&&!(last&&last.flag);\n    box.className='flag'+(fresh?' sweep':'');\n    box.innerHTML='<span class=\"mk\">FLAG · FOR THE LENDER</span><div class=\"amt num\">€ '+f.amount.toFixed(1)+' m</div><b>'+f.title+'</b><small>'+f.detail+'</small>';\n  }\n\n  function render(anim){\n    if(d.role) el('role').innerHTML=d.role;\n    if(d.chip) el('chip').textContent=d.chip;\n    el('basis').textContent=d.basis||'';\n    renderFlag(anim);\n    const t=renderWaterfall(anim);\n    const wasClosed=last&&Math.abs(sum(last.sources)-sum(last.uses))<.05;\n    const closed=!t.gap;\n    const stampIn=anim&&closed!==wasClosed?' in':'';\n    el('closes').innerHTML='<span class=\"num\">sources <b>'+fmt(t.S)+'</b> '+(closed?'=':'≠')+' uses <b>'+fmt(t.U)+'</b></span>'+(closed?'<span class=\"stamp ok'+stampIn+'\">CLOSES</span>':'<span class=\"stamp gap'+stampIn+'\">GAP '+fmt(t.gap)+'</span>');\n    renderYears(anim);\n  }\n  addEventListener('message',e=>{\n    if(!replay||e.source!==parent||e.origin!==location.origin||e.data?.type!=='daslab:replay-clock'||!e.data.data) return;\n    const k=JSON.stringify(e.data.data); if(k===key) return;\n    last=d; d=e.data.data; key=k; render(true);\n  });\n  let w=0; new ResizeObserver(()=>{ if(document.body.clientWidth!==w){ w=document.body.clientWidth; render(false); } }).observe(document.body);\n  render(false);\n})();\n</script>\n</body></html>\n",
        "data": {
          "role": "Bank bridge <span>· sources and uses · € m</span>",
          "chip": "ACDOCA · MIRROR",
          "sources": [],
          "uses": []
        }
      },
      "layout": {
        "sortOrder": 0,
        "size": "large"
      }
    },
    {
      "id": "pf-truth",
      "type": "daslab/webview",
      "name": "Reported vs corrected",
      "fields": {
        "html": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>\n  :root{\n    --bg:#0b0f14; --panel:#10161e; --line:rgba(255,255,255,.08);\n    --ink:#e6ecf3; --muted:#8e9aa8; --dim:#5b6774;\n    --acc:#d3a85a; --green:#43c98a; --red:#ef5a5e; --amber:#e3b04b; --blue:#5aa5e6;\n    --mono:ui-monospace,\"SF Mono\",Menlo,Consolas,monospace;\n    --sans:Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;\n  }\n  *{box-sizing:border-box;margin:0;padding:0}\n  html,body{height:100%}\n  body{background:var(--bg);color:var(--ink);font-family:var(--sans);overflow:hidden;-webkit-font-smoothing:antialiased}\n  .stage{position:absolute;inset:0;overflow:hidden;background:var(--bg)}\n  .topbar{display:flex;align-items:center;gap:9px;padding:10px 14px;border-bottom:1px solid var(--line)}\n  .brand{font-weight:800;letter-spacing:.13em;font-size:11px;white-space:nowrap}\n  .brand b{color:var(--acc)}\n  .divider{width:1px;height:15px;background:var(--line);flex-shrink:0}\n  .role{font:600 10.5px/1 var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .role span{color:var(--muted);font-weight:400}\n  .chip{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.1em;color:var(--acc);background:rgba(211,168,90,.12);padding:4px 7px;border-radius:4px;white-space:nowrap;flex-shrink:0}\n  .body{position:absolute;top:39px;left:0;right:0;bottom:0;overflow:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}\n  .body>*{flex-shrink:0}\n  .num{font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:4px;overflow:hidden}\n  .card .hd{display:flex;justify-content:space-between;align-items:center;gap:10px;padding:7px 10px;border-bottom:1px solid var(--line);font:600 8.5px/1.3 var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}\n  .card .hd .aside{font:500 8.5px/1.3 var(--mono);letter-spacing:0;text-transform:none;color:var(--muted);text-align:right}\n  .card .hd .aside.warn{color:var(--amber)}\n  .chart svg{display:block}\n  svg text{font-family:var(--mono)}\n  .totals{display:flex;align-items:flex-end;gap:22px;padding:9px 12px 11px;border-top:1px solid var(--line)}\n  .tot small{display:block;font:600 7.5px/1 var(--sans);letter-spacing:.1em;text-transform:uppercase;color:var(--dim);margin-bottom:5px}\n  .tot b{font:600 24px/1 var(--mono);letter-spacing:-.02em}\n  .tot.rep b{color:#9fb3a8} .tot.cor b{color:var(--red)}\n  .tot .s{display:block;margin-top:4px;font:500 8px/1.3 var(--mono);color:var(--muted)}\n  .stamp{margin-left:auto;align-self:center;font:700 8px/1 var(--mono);letter-spacing:.12em;padding:4px 6px;border:1px solid currentColor;border-radius:3px;color:var(--red)}\n  .note{padding:7px 10px;border-top:1px solid var(--line);font:500 9px/1.45 var(--mono);color:var(--muted)}\n  .note b{color:var(--ink);font-weight:600}\n  .prov{margin-top:auto;font:500 8px/1.4 var(--mono);color:var(--dim)}\n  .grow{transform-box:fill-box;transform-origin:50% 100%;animation:grow .75s cubic-bezier(.2,.8,.2,1) both}\n  .grow.down{transform-origin:50% 0%}\n  @keyframes grow{from{transform:scaleY(0)}to{transform:scaleY(1)}}\n  .draw{animation:draw .9s cubic-bezier(.4,0,.2,1) both}\n  @keyframes draw{from{stroke-dashoffset:var(--len)}to{stroke-dashoffset:0}}\n  .fade{animation:fade .5s ease both}\n  @keyframes fade{from{opacity:0}to{opacity:1}}\n  .pop{transform-box:fill-box;transform-origin:50% 50%;animation:pop .45s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes pop{from{opacity:0;transform:scale(.2)}to{opacity:1;transform:scale(1)}}\n  .ring{transform-box:fill-box;transform-origin:50% 50%;animation:ring 1.2s ease-out both}\n  @keyframes ring{0%{opacity:.9;transform:scale(.6)}100%{opacity:.55;transform:scale(1)}}\n  .in{animation:stamp .5s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes stamp{0%{opacity:0;transform:scale(1.6)}70%{opacity:1;transform:scale(.96)}100%{opacity:1;transform:scale(1)}}\n  @media (prefers-reduced-motion:reduce){*{animation:none!important}}\n</style></head>\n<body>\n  <div class=\"stage\">\n    <div class=\"topbar\">\n      <div class=\"brand\">DASLAB <b>LEDGER</b></div>\n      <div class=\"divider\"></div>\n      <div class=\"role\" id=\"role\">Reported vs corrected <span>· company code 1010 · EBIT · € m</span></div>\n      <div class=\"chip\" id=\"chip\">ACDOCA · MIRROR</div>\n    </div>\n    <div class=\"body\">\n      <div class=\"card\">\n        <div class=\"hd\"><span>EBIT by year</span><span class=\"aside\" id=\"recSrc\">FI · annual accounts</span></div>\n        <div class=\"chart\" id=\"bell\"></div>\n        <div class=\"totals\" id=\"totals\"></div>\n      </div>\n      <div class=\"card\" id=\"pnlCard\" hidden>\n        <div class=\"hd\"><span>As filed · revenue and other operating income</span><span class=\"aside\">€ m · ACDOCA</span></div>\n        <div class=\"chart\" id=\"pnl\"></div>\n      </div>\n      <div class=\"card\" id=\"marginCard\" hidden>\n        <div class=\"hd\"><span>Standard product margin</span><span class=\"aside\">VBRP · CKMLCR</span></div>\n        <div class=\"chart\" id=\"margin\"></div>\n        <div class=\"note\" id=\"mNote\"></div>\n      </div>\n      <div class=\"prov\">Illustrated on a fictional company. Not a customer’s data.</div>\n    </div>\n  </div>\n<script>\n(()=>{\n  const el=id=>document.getElementById(id);\n  const replay=window.__DASLAB_REPLAY__===true;\n  let d=window.__DASLAB_WIDGET_DATA__||{}, last=null, key=JSON.stringify(d);\n  const sgn=n=>(n>0?'+':n<0?'−':'')+Math.abs(n).toFixed(0);\n  const total=a=>(a||[]).reduce((x,y)=>x+y,0);\n\n  /** One row per year: the reported dot, then an arrow drawn to the corrected\n   * dot once the removals are known. */\n  function renderBell(anim){\n    const host=el('bell'), W=host.clientWidth||520, years=d.years||[], rep=d.reported||[], rem=d.removed, cor=d.trueEbit;\n    const lo=d.axis?.[0]??-12, hi=d.axis?.[1]??8, PL=44, PR=18, PT=20, rowH=38, H=PT+rowH*years.length+14;\n    const x=v=>PL+(W-PL-PR)*(v-lo)/(hi-lo);\n    let g='';\n    for(let t=Math.ceil(lo/4)*4;t<=hi;t+=4){ g+='<line x1=\"'+x(t)+'\" x2=\"'+x(t)+'\" y1=\"'+(PT-6)+'\" y2=\"'+(H-12)+'\" stroke=\"rgba(255,255,255,'+(t===0?.22:.05)+')\"'+(t===0?'':' stroke-dasharray=\"2 3\"')+'/><text x=\"'+x(t)+'\" y=\"'+(PT-9)+'\" text-anchor=\"middle\" fill=\"#5b6774\" font-size=\"7.5\">'+sgn(t)+'</text>'; }\n    g+='<text x=\"'+(x(0)-5)+'\" y=\"'+(H-3)+'\" text-anchor=\"end\" fill=\"#5b6774\" font-size=\"7\">loss</text><text x=\"'+(x(0)+5)+'\" y=\"'+(H-3)+'\" fill=\"#5b6774\" font-size=\"7\">profit</text>';\n    const newArrows=anim&&rem&&!(last&&last.removed), newCor=anim&&cor&&!(last&&last.trueEbit), newFound=anim&&d.found&&!(last&&last.found);\n    years.forEach((yr,i)=>{\n      const cy=PT+rowH*i+rowH/2, rx=x(rep[i]);\n      g+='<text x=\"8\" y=\"'+(cy+3)+'\" fill=\"#8e9aa8\" font-size=\"9\">'+yr+'</text>';\n      if(rem){\n        const cx=x(rep[i]+rem[i]), len=Math.abs(rx-cx), ah=5;\n        g+='<line x1=\"'+(rx-6)+'\" x2=\"'+(cx+ah+3)+'\" y1=\"'+cy+'\" y2=\"'+cy+'\" stroke=\"#e3b04b\" stroke-width=\"1.6\" stroke-dasharray=\"'+len+'\" style=\"--len:'+len+';animation-delay:'+(i*180)+'ms\"'+(newArrows?' class=\"draw\"':'')+'/>';\n        g+='<path d=\"M'+(cx+ah+4)+','+(cy-4)+' L'+(cx+4)+','+cy+' L'+(cx+ah+4)+','+(cy+4)+'\" fill=\"none\" stroke=\"#e3b04b\" stroke-width=\"1.6\"'+(newArrows?' class=\"fade\" style=\"animation-delay:'+(i*180+700)+'ms\"':'')+'/>';\n        g+='<text x=\"'+((rx+cx)/2)+'\" y=\"'+(cy-7)+'\" text-anchor=\"middle\" fill=\"#e3b04b\" font-size=\"8\" font-weight=\"600\"'+(newArrows?' class=\"fade\" style=\"animation-delay:'+(i*180+500)+'ms\"':'')+'>'+sgn(rem[i])+'</text>';\n        const done=!!cor;\n        g+='<circle cx=\"'+cx+'\" cy=\"'+cy+'\" r=\"5.5\" fill=\"'+(done?'#ef5a5e':'#0b0f14')+'\" stroke=\"#ef5a5e\" stroke-width=\"1.6\"'+((newCor||newArrows)?' class=\"pop\" style=\"animation-delay:'+(i*180+(newCor?0:750))+'ms\"':'')+'/>';\n        if(done) g+='<text x=\"'+(cx-10)+'\" y=\"'+(cy+3)+'\" text-anchor=\"end\" fill=\"#ef5a5e\" font-size=\"9\" font-weight=\"600\"'+(newCor?' class=\"fade\"':'')+'>'+sgn(cor[i])+'</text>';\n      }\n      if(d.found) g+='<circle cx=\"'+rx+'\" cy=\"'+cy+'\" r=\"10\" fill=\"none\" stroke=\"#e3b04b\" stroke-width=\"1\" stroke-dasharray=\"2 2\"'+(newFound?' class=\"ring\" style=\"animation-delay:'+(i*150)+'ms\"':'')+'/>';\n      g+='<circle cx=\"'+rx+'\" cy=\"'+cy+'\" r=\"5.5\" fill=\"#9fb3a8\"/>';\n      g+='<text x=\"'+(rx+10)+'\" y=\"'+(cy+3)+'\" fill=\"#9fb3a8\" font-size=\"9\" font-weight=\"600\">'+sgn(rep[i])+'</text>';\n    });\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n  }\n\n  function renderTotals(anim){\n    const rep=total(d.reported), cor=d.trueEbit?total(d.trueEbit):null, fresh=anim&&cor!=null&&!(last&&last.trueEbit);\n    let h='<div class=\"tot rep\"><small>Reported, 3 years</small><b>'+sgn(rep)+'</b><span class=\"s\">as filed</span></div>';\n    if(d.removed) h+='<div class=\"tot'+(anim&&!(last&&last.removed)?' fade':'')+'\"><small>Removed</small><b style=\"color:var(--amber)\">'+sgn(total(d.removed))+'</b><span class=\"s\">'+(d.docs||'')+'</span></div>';\n    if(cor!=null) h+='<div class=\"tot cor'+(fresh?' fade':'')+'\"><small>Corrected</small><b>'+sgn(cor)+'</b><span class=\"s\">every column closes</span></div><span class=\"stamp'+(fresh?' in':'')+'\">LOSSES, NOT PROFITS</span>';\n    el('totals').innerHTML=h;\n  }\n\n  /** Margin by year as diverging bars; the year sold below cost grows down. */\n  function renderMargin(anim){\n    const m=d.margin, card=el('marginCard'); card.hidden=!m; if(!m) return;\n    const fresh=anim&&!(last&&last.margin), host=el('margin'), W=host.clientWidth||520, H=92, years=d.years||[];\n    const max=Math.max(...m.rows.map(r=>Math.abs(r.marginPct)),1), PT=12, PB=12, zero=PT+(H-PT-PB)*(5/(max+5));\n    const sc=(H-PT-PB)/(max+5), cw=(W-20)/m.rows.length, bw=Math.min(cw*.34,40);\n    let g='<line x1=\"10\" x2=\"'+(W-10)+'\" y1=\"'+zero+'\" y2=\"'+zero+'\" stroke=\"rgba(255,255,255,.2)\"/>';\n    m.rows.forEach((r,i)=>{\n      const cx=10+cw*i+cw/2, h=Math.abs(r.marginPct)*sc, neg=r.marginPct<0;\n      g+='<rect x=\"'+(cx-bw/2)+'\" y=\"'+(neg?zero:zero-h)+'\" width=\"'+bw+'\" height=\"'+h+'\" rx=\"1.5\" fill=\"'+(neg?'#ef5a5e':'#43c98a')+'\"'+(fresh?' class=\"grow'+(neg?' down':'')+'\" style=\"animation-delay:'+(i*160)+'ms\"':'')+'/>';\n      g+='<text x=\"'+(cx+bw/2+6)+'\" y=\"'+(neg?zero+h-2:zero-h+8)+'\" fill=\"'+(neg?'#ef5a5e':'#43c98a')+'\" font-size=\"9\" font-weight=\"600\">'+(neg?'−':'+')+Math.abs(r.marginPct).toFixed(1)+'%</text>';\n      if(W>=460){ g+='<text x=\"'+(cx-bw/2-6)+'\" y=\"'+(zero-3)+'\" text-anchor=\"end\" fill=\"#8e9aa8\" font-size=\"8\">'+years[i]+'</text>';\n        g+='<text x=\"'+(cx-bw/2-6)+'\" y=\"'+(zero+9)+'\" text-anchor=\"end\" fill=\"#5b6774\" font-size=\"7\">'+r.tonnes.toLocaleString('en-US')+' t</text>'; }\n      else g+='<text x=\"'+cx+'\" y=\"'+(H-2)+'\" text-anchor=\"middle\" fill=\"#8e9aa8\" font-size=\"8\">'+years[i]+'</text>';\n    });\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n    el('mNote').innerHTML=m.note||'';\n  }\n\n  /** The filed income statement as stacked bars: revenue, with other operating\n   * income on top. When the misbooked sales are found, that slice turns amber. */\n  function renderPnl(anim){\n    const p=d.pnl, card=el('pnlCard'); card.hidden=!p||!!d.margin; if(card.hidden) return;\n    const host=el('pnl'), W=host.clientWidth||520, H=112, years=d.years||[], PT=14, PB=16;\n    const max=Math.max(...p.map(r=>r.revenue+r.other))*1.1, h=v=>(H-PT-PB)*v/max, base=H-PB;\n    const cw=(W-20)/p.length, bw=Math.min(cw*.36,54), found=!!d.found, fresh=anim&&found&&!(last&&last.found);\n    let g='<line x1=\"10\" x2=\"'+(W-10)+'\" y1=\"'+base+'\" y2=\"'+base+'\" stroke=\"rgba(255,255,255,.18)\"/>';\n    p.forEach((r,i)=>{\n      const cx=10+cw*i+cw/2, hr=h(r.revenue), ho=h(r.other);\n      g+='<rect x=\"'+(cx-bw/2)+'\" y=\"'+(base-hr)+'\" width=\"'+bw+'\" height=\"'+hr+'\" rx=\"1.5\" fill=\"#7f90a4\" opacity=\".75\"/>';\n      g+='<rect x=\"'+(cx-bw/2)+'\" y=\"'+(base-hr-ho)+'\" width=\"'+bw+'\" height=\"'+ho+'\" rx=\"1.5\" fill=\"'+(found?'#e3b04b':'#b7c2cf')+'\"'+(fresh?' class=\"fade\" style=\"animation-delay:'+(i*150)+'ms\"':'')+'/>';\n      g+='<text x=\"'+(cx+bw/2+6)+'\" y=\"'+(base-hr-ho/2+3)+'\" fill=\"'+(found?'#e3b04b':'#8e9aa8')+'\" font-size=\"8\" font-weight=\"600\">'+r.other.toFixed(1)+(found&&W>=460?' other income':'')+'</text>';\n      g+='<text x=\"'+(cx+bw/2+6)+'\" y=\"'+(base-hr/2+3)+'\" fill=\"#8e9aa8\" font-size=\"8\">'+r.revenue.toFixed(1)+'</text>';\n      g+='<text x=\"'+cx+'\" y=\"'+(H-4)+'\" text-anchor=\"middle\" fill=\"#5b6774\" font-size=\"7.5\">'+years[i]+'</text>';\n    });\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n  }\n\n  function render(anim){\n    if(d.role) el('role').innerHTML=d.role;\n    if(d.chip) el('chip').textContent=d.chip;\n    const src=el('recSrc'); src.textContent=d.recSrc||(d.found?d.found:'FI · annual accounts'); src.className='aside'+(d.found&&!d.trueEbit?' warn':'');\n    renderBell(anim); renderTotals(anim); renderPnl(anim); renderMargin(anim);\n  }\n  addEventListener('message',e=>{\n    if(!replay||e.source!==parent||e.origin!==location.origin||e.data?.type!=='daslab:replay-clock'||!e.data.data) return;\n    const k=JSON.stringify(e.data.data); if(k===key) return;\n    last=d; d=e.data.data; key=k; render(true);\n  });\n  let w=0; new ResizeObserver(()=>{ if(document.body.clientWidth!==w){ w=document.body.clientWidth; render(false); } }).observe(document.body);\n  render(false);\n})();\n</script>\n</body></html>\n",
        "data": {
          "role": "Reported vs corrected <span>· EBIT · € m</span>",
          "chip": "ACDOCA · MIRROR",
          "years": [],
          "reported": []
        }
      },
      "layout": {
        "sortOrder": 1,
        "size": "large"
      }
    },
    {
      "id": "pf-deal",
      "type": "daslab/webview",
      "name": "Order margin",
      "fields": {
        "html": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>\n  :root{\n    --bg:#0b0f14; --panel:#10161e; --line:rgba(255,255,255,.08);\n    --ink:#e6ecf3; --muted:#8e9aa8; --dim:#5b6774;\n    --acc:#d3a85a; --green:#43c98a; --red:#ef5a5e; --amber:#e3b04b; --blue:#5aa5e6;\n    --mono:ui-monospace,\"SF Mono\",Menlo,Consolas,monospace;\n    --sans:Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;\n  }\n  *{box-sizing:border-box;margin:0;padding:0}\n  html,body{height:100%}\n  body{background:var(--bg);color:var(--ink);font-family:var(--sans);overflow:hidden;-webkit-font-smoothing:antialiased}\n  .stage{position:absolute;inset:0;overflow:hidden;background:var(--bg)}\n  .topbar{display:flex;align-items:center;gap:9px;padding:10px 14px;border-bottom:1px solid var(--line)}\n  .brand{font-weight:800;letter-spacing:.13em;font-size:11px;white-space:nowrap}\n  .brand b{color:var(--acc)}\n  .divider{width:1px;height:15px;background:var(--line);flex-shrink:0}\n  .role{font:600 10.5px/1 var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .role span{color:var(--muted);font-weight:400}\n  .chip{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.1em;color:var(--acc);background:rgba(211,168,90,.12);padding:4px 7px;border-radius:4px;white-space:nowrap;flex-shrink:0}\n  .body{position:absolute;top:39px;left:0;right:0;bottom:0;overflow:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}\n  .body>*{flex-shrink:0}\n  .num{font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:4px;overflow:hidden}\n  .card .hd{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:7px 10px;border-bottom:1px solid var(--line);font:600 8.5px/1 var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}\n  .card .hd .aside{font:500 8.5px/1.2 var(--mono);color:var(--muted);letter-spacing:0;text-transform:none;text-align:right}\n  .chart svg{display:block}\n  svg text{font-family:var(--mono)}\n  .verdict{display:flex;align-items:center;gap:8px;padding:7px 10px;border-top:1px solid var(--line);font:500 9px/1.3 var(--mono);color:var(--muted)}\n  .verdict b{color:var(--red);font-weight:600}\n  .stamp{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.12em;padding:4px 6px;border:1px solid currentColor;border-radius:3px;color:var(--acc);white-space:nowrap}\n  .flow{display:grid;grid-template-columns:repeat(4,1fr);gap:14px;padding:10px 10px 9px;position:relative}\n  .node{position:relative;border:1px solid var(--line);border-radius:4px;padding:6px 7px;min-width:0;background:rgba(255,255,255,.015);transition:border-color .3s}\n  .node:not(:last-child)::after{content:'';position:absolute;top:50%;right:-14px;width:14px;height:1px;background:rgba(255,255,255,.14)}\n  .node small{display:block;font:600 7px/1.2 var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}\n  .node b{display:block;margin-top:4px;font:600 9.5px/1 var(--mono);color:var(--dim);overflow:hidden;text-overflow:ellipsis}\n  .node.lit{border-color:rgba(211,168,90,.55);background:rgba(211,168,90,.06)}\n  .node.lit b{color:var(--ink)}\n  .node.lit:not(:last-child)::after{background:var(--acc)}\n  .node.light{animation:light .6s ease both}\n  @keyframes light{0%{border-color:var(--line);background:rgba(255,255,255,.015)}60%{background:rgba(211,168,90,.16)}100%{border-color:rgba(211,168,90,.55);background:rgba(211,168,90,.06)}}\n  .lines{display:flex;flex-direction:column;border-top:1px solid var(--line)}\n  .lines:empty{display:none}\n  .li{display:grid;grid-template-columns:18px 1fr auto;gap:8px;align-items:center;padding:5px 10px;border-bottom:1px solid var(--line);font-size:10px}\n  .li:last-child{border-bottom:0}\n  .li .op{font:600 11px/1 var(--mono);color:var(--dim);text-align:center}\n  .li .lab{min-width:0;color:var(--ink);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}\n  .li .lab .sc{display:inline-block;font:600 7px/1 var(--mono);letter-spacing:.06em;color:var(--muted);border:1px solid var(--line);padding:2px 4px;border-radius:3px;margin-left:6px;vertical-align:middle}\n  .li .amt{font-size:10.5px;font-weight:600}\n  .li.minus .amt{color:var(--muted)}\n  .li.scrap{background:rgba(227,176,75,.07)} .li.scrap .amt{color:var(--amber)}\n  .li.total{background:rgba(255,255,255,.03)} .li.total .amt,.li.total .op{color:var(--acc)}\n  .prov{margin-top:auto;font:500 8px/1.4 var(--mono);color:var(--dim)}\n  .growx{transform-box:fill-box;transform-origin:0 50%;animation:growx .8s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes growx{from{transform:scaleX(var(--s0,0))}to{transform:scaleX(1)}}\n  .fade{animation:fade .5s ease both}\n  @keyframes fade{from{opacity:0}to{opacity:1}}\n  .in{animation:stamp .5s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes stamp{0%{opacity:0;transform:scale(1.6)}70%{opacity:1;transform:scale(.96)}100%{opacity:1;transform:scale(1)}}\n  @media (prefers-reduced-motion:reduce){*{animation:none!important}}\n  @media (max-width:430px){ .flow{gap:10px} .node:not(:last-child)::after{right:-10px;width:10px} .node b{font-size:8.5px} }\n</style></head>\n<body>\n  <div class=\"stage\">\n    <div class=\"topbar\">\n      <div class=\"brand\">DASLAB <b>LEDGER</b></div>\n      <div class=\"divider\"></div>\n      <div class=\"role\" id=\"role\">Order margin <span>· company code 1010 · pick a sales order</span></div>\n      <div class=\"chip\" id=\"chip\">SD · CO · MM</div>\n    </div>\n    <div class=\"body\">\n      <div class=\"card\">\n        <div class=\"hd\"><span>Margin on this order</span><span class=\"aside num\" id=\"net\"></span></div>\n        <div class=\"chart\" id=\"bars\"></div>\n        <div class=\"verdict\" id=\"verdict\"></div>\n      </div>\n      <div class=\"card\" id=\"histCard\" hidden>\n        <div class=\"hd\"><span>This product at standard, recent orders</span><span class=\"aside\">VBAK · CKMLCR</span></div>\n        <div class=\"chart\" id=\"hist\"></div>\n      </div>\n      <div class=\"card\">\n        <div class=\"hd\"><span>Batch chain</span><span class=\"aside\">VBAK · LIPS · AUFK · MATDOC</span></div>\n        <div class=\"flow\" id=\"flow\"></div>\n        <div class=\"lines\" id=\"lines\"></div>\n      </div>\n      <div class=\"prov\">Illustrated on a fictional company. Not a customer’s data.</div>\n    </div>\n  </div>\n<script>\n(()=>{\n  const el=id=>document.getElementById(id);\n  const replay=window.__DASLAB_REPLAY__===true;\n  let d=window.__DASLAB_WIDGET_DATA__||{}, last=null, key=JSON.stringify(d);\n  const eur=n=>'€ '+Math.round(n).toLocaleString('en-US');\n\n  /** Three margins on one axis. The actual bar carries its scrap recovery as a\n   * second segment; a changed bar grows from where it stood. */\n  function renderBars(anim){\n    const host=el('bars'), W=host.clientWidth||520, order=['plan','quoted','actual'].filter(k=>d[k]);\n    const narrow=W<400, PL=narrow?76:92, PR=narrow?40:54, PT=16, rowH=42, H=PT+rowH*order.length+4, max=d.axisMax||35;\n    const x=v=>PL+(W-PL-PR)*v/max;\n    let g='';\n    for(let t=0;t<=max;t+=5){ g+='<line x1=\"'+x(t)+'\" x2=\"'+x(t)+'\" y1=\"'+(PT-4)+'\" y2=\"'+(H-4)+'\" stroke=\"rgba(255,255,255,'+(t===0?.18:.05)+')\"/><text x=\"'+x(t)+'\" y=\"'+(PT-7)+'\" text-anchor=\"middle\" fill=\"#5b6774\" font-size=\"7\">'+t+'%</text>'; }\n    order.forEach((k,i)=>{\n      const m=d[k], cy=PT+rowH*i+15; if(!m) return;\n      const prev=last&&last[k], changed=anim&&(!prev||prev.pct!==m.pct), s0=changed&&prev?Math.min(1,(prev.base??prev.pct)/m.pct):0;\n      const col=k==='actual'?'#d3a85a':k==='plan'?'#7f90a4':'#b7c2cf';\n      g+='<text x=\"'+(PL-8)+'\" y=\"'+(cy+3)+'\" text-anchor=\"end\" fill=\"#8e9aa8\" font-size=\"8.5\">'+m.label+'</text>';\n      const baseV=m.base??m.pct;\n      g+='<rect x=\"'+x(0)+'\" y=\"'+(cy-7)+'\" width=\"'+(x(baseV)-x(0))+'\" height=\"14\" rx=\"1.5\" fill=\"'+col+'\"'+(m.stale?' opacity=\".45\"':'')+(changed&&!(prev&&m.base===prev.pct)?' class=\"growx\" style=\"--s0:'+s0+'\"':'')+'/>';\n      if(m.stale){ g+='<rect x=\"'+x(0)+'\" y=\"'+(cy-7)+'\" width=\"'+(x(m.pct)-x(0))+'\" height=\"14\" rx=\"1.5\" fill=\"url(#hatch)\"'+(anim&&!(prev&&prev.stale)?' class=\"fade\"':'')+'/>'; }\n      if(m.base!=null){ const sc=anim&&!(prev&&prev.base!=null); g+='<rect x=\"'+x(m.base)+'\" y=\"'+(cy-7)+'\" width=\"'+(x(m.pct)-x(m.base))+'\" height=\"14\" fill=\"#e3b04b\"'+(sc?' class=\"growx\" style=\"animation-delay:.2s\"':'')+'/>'; }\n      g+='<text x=\"'+(x(m.pct)+6)+'\" y=\"'+(cy+4)+'\" fill=\"'+(k==='actual'?'#d3a85a':'#e6ecf3')+'\" font-size=\"11\" font-weight=\"600\"'+(changed?' class=\"fade\" style=\"animation-delay:.45s\"':'')+'>'+m.pct.toFixed(1)+'%</text>';\n      g+='<text x=\"'+x(0)+'\" y=\"'+(cy+19)+'\" fill=\"#5b6774\" font-size=\"7.5\">'+(m.cost!=null?'cost '+eur(m.cost)+' · ':'')+(m.src||'')+(m.note&&!narrow?' · '+m.note:'')+'</text>';\n    });\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\"><defs><pattern id=\"hatch\" width=\"5\" height=\"5\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\"><line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"5\" stroke=\"#7f90a4\" stroke-width=\"1.4\"/></pattern></defs>'+g+'</svg>';\n  }\n\n  function renderVerdict(anim){\n    const v=el('verdict'), p=d.plan, a=d.actual, q=d.quoted;\n    if(a&&p){ const fresh=anim&&!(last&&last.actual&&last.actual.pct===a.pct); v.innerHTML='<span>standard flatters this order by <b>'+(p.pct-a.pct).toFixed(1)+' pts</b>'+(q?' · actual vs quote '+(a.pct-q.pct>0?'+':'−')+Math.abs(a.pct-q.pct).toFixed(1)+' pts':'')+'</span><span class=\"stamp'+(fresh?' in':'')+'\">ACTUAL '+a.pct.toFixed(1)+'%</span>'; }\n    else if(p&&p.stale) v.innerHTML='<span>standard from the '+(p.run||'annual')+' costing run, before the metal rose</span><span class=\"stamp'+(anim&&!(last&&last.plan&&last.plan.stale)?' in':'')+'\" style=\"color:var(--amber)\">STALE STANDARD</span>';\n    else v.innerHTML='<span>as every SAP margin report shows it</span><span class=\"stamp\" style=\"color:var(--muted)\">'+(q?'QUOTE FOUND':'SAP STANDARD')+'</span>';\n  }\n\n  function renderFlow(anim){\n    const f=el('flow'); f.innerHTML='';\n    const was=new Set(((last&&last.chain)||[]).filter(n=>n.lit).map(n=>n.kind)); let seq=0;\n    (d.chain||[]).forEach(n=>{\n      const lighting=anim&&n.lit&&!was.has(n.kind);\n      const node=document.createElement('div'); node.className='node'+(n.lit?' lit':'')+(lighting?' light':'');\n      if(lighting){ node.style.animationDelay=(seq*280)+'ms'; seq++; }\n      node.innerHTML='<small>'+n.kind+'</small><b class=\"num\">'+(n.lit?n.id:n.table)+'</b>';\n      f.appendChild(node);\n    });\n  }\n  function renderLines(anim){\n    const ls=el('lines'); ls.innerHTML='';\n    const was=new Set(((last&&last.lines)||[]).map(l=>l.label+l.value));\n    (d.lines||[]).forEach((l,i)=>{\n      const row=document.createElement('div'); row.className='li'+(l.kind==='minus'?' minus':'')+(l.scrap?' scrap':'')+(l.total?' total':'')+(anim&&!was.has(l.label+l.value)?' fade':'');\n      if(anim) row.style.animationDelay=(900+i*120)+'ms';\n      row.innerHTML='<div class=\"op\">'+(l.total?'=':l.kind==='minus'?'−':'+')+'</div><div class=\"lab\">'+l.label+(l.src?'<span class=\"sc\">'+l.src+'</span>':'')+'</div><div class=\"amt num\">'+eur(l.value)+'</div>';\n      ls.appendChild(row);\n    });\n  }\n  /** Recent orders of the same product at the SAP standard: the base picture\n   * a margin report shows. Replaced by the cost lines once the chain is traced. */\n  function renderHist(){\n    const h=d.history, card=el('histCard'); card.hidden=!h||!!(d.lines&&d.lines.length); if(card.hidden) return;\n    const host=el('hist'), W=host.clientWidth||520, H=92, PT=14, PB=16, max=35, cw=(W-20)/h.length, bw=Math.min(cw*.5,30);\n    const y=v=>PT+(H-PT-PB)*(1-v/max);\n    let g='<line x1=\"10\" x2=\"'+(W-10)+'\" y1=\"'+y(0)+'\" y2=\"'+y(0)+'\" stroke=\"rgba(255,255,255,.18)\"/>';\n    h.forEach((o,i)=>{ const cx=10+cw*i+cw/2, cur=o.current, stale=d.plan&&d.plan.stale;\n      g+='<rect x=\"'+(cx-bw/2)+'\" y=\"'+y(o.pct)+'\" width=\"'+bw+'\" height=\"'+(y(0)-y(o.pct))+'\" rx=\"1.5\" fill=\"'+(cur?'#b7c2cf':'#7f90a4')+'\" opacity=\"'+(stale?.45:cur?.95:.7)+'\"/>';\n      g+='<text x=\"'+cx+'\" y=\"'+(y(o.pct)-3)+'\" text-anchor=\"middle\" fill=\"'+(cur?'#e6ecf3':'#8e9aa8')+'\" font-size=\"7.5\">'+o.pct.toFixed(1)+'</text>';\n      g+='<text x=\"'+cx+'\" y=\"'+(H-4)+'\" text-anchor=\"middle\" fill=\"'+(cur?'#d3a85a':'#5b6774')+'\" font-size=\"7\">'+o.so+'</text>'; });\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n  }\n\n  function render(anim){\n    if(d.role) el('role').innerHTML=d.role;\n    if(d.chip) el('chip').textContent=d.chip;\n    el('net').textContent=d.net!=null?'net '+eur(d.net):'';\n    renderBars(anim); renderVerdict(anim); renderHist(); renderFlow(anim); renderLines(anim);\n  }\n  addEventListener('message',e=>{\n    if(!replay||e.source!==parent||e.origin!==location.origin||e.data?.type!=='daslab:replay-clock'||!e.data.data) return;\n    const k=JSON.stringify(e.data.data); if(k===key) return;\n    last=d; d=e.data.data; key=k; render(true);\n  });\n  let w=0; new ResizeObserver(()=>{ if(document.body.clientWidth!==w){ w=document.body.clientWidth; render(false); } }).observe(document.body);\n  render(false);\n})();\n</script>\n</body></html>\n",
        "data": {
          "role": "Order margin <span>· pick a sales order</span>",
          "chip": "SD · CO · MM",
          "chain": [],
          "lines": []
        }
      },
      "layout": {
        "sortOrder": 2,
        "size": "large"
      }
    },
    {
      "id": "pf-hedge",
      "type": "daslab/webview",
      "name": "Hedge book",
      "fields": {
        "html": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>\n  :root{\n    --bg:#0b0f14; --panel:#10161e; --line:rgba(255,255,255,.08);\n    --ink:#e6ecf3; --muted:#8e9aa8; --dim:#5b6774;\n    --acc:#d3a85a; --green:#43c98a; --red:#ef5a5e; --amber:#e3b04b; --blue:#5aa5e6;\n    --mono:ui-monospace,\"SF Mono\",Menlo,Consolas,monospace;\n    --sans:Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;\n  }\n  *{box-sizing:border-box;margin:0;padding:0}\n  html,body{height:100%}\n  body{background:var(--bg);color:var(--ink);font-family:var(--sans);overflow:hidden;-webkit-font-smoothing:antialiased}\n  .stage{position:absolute;inset:0;overflow:hidden;background:var(--bg)}\n  .topbar{display:flex;align-items:center;gap:9px;padding:10px 14px;border-bottom:1px solid var(--line)}\n  .brand{font-weight:800;letter-spacing:.13em;font-size:11px;white-space:nowrap}\n  .brand b{color:var(--acc)}\n  .divider{width:1px;height:15px;background:var(--line);flex-shrink:0}\n  .role{font:600 10.5px/1 var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .role span{color:var(--muted);font-weight:400}\n  .chip{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.1em;color:var(--acc);background:rgba(211,168,90,.12);padding:4px 7px;border-radius:4px;white-space:nowrap;flex-shrink:0}\n  .body{position:absolute;top:39px;left:0;right:0;bottom:0;overflow:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}\n  .body>*{flex-shrink:0}\n  .num{font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap}\n  .sc{display:inline-block;font:600 7px/1 var(--mono);letter-spacing:.06em;color:var(--muted);border:1px solid var(--line);padding:2px 4px;border-radius:3px;text-transform:uppercase;vertical-align:middle;margin-left:5px}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:4px;overflow:hidden}\n  .card .hd{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:7px 10px;border-bottom:1px solid var(--line);font:600 8.5px/1 var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}\n  .card .hd .aside{font:500 8.5px/1 var(--mono);color:var(--muted);letter-spacing:0;text-transform:none}\n  .legend{display:flex;gap:10px;flex-wrap:wrap;justify-content:flex-end;font:500 8px/1 var(--mono);color:var(--muted);letter-spacing:0;text-transform:none}\n  .legend i{display:inline-block;width:7px;height:7px;border-radius:2px;vertical-align:-1px;margin-right:4px}\n  .chart svg{display:block}\n  svg text{font-family:var(--mono)}\n  .kpis{display:grid;grid-template-columns:repeat(3,1fr);border-top:1px solid var(--line)}\n  .kpi{padding:8px 10px;border-right:1px solid var(--line);position:relative;overflow:hidden;min-width:0}\n  .kpi:last-child{border-right:0}\n  .kpi .k{font:600 7.5px/1.2 var(--sans);letter-spacing:.1em;text-transform:uppercase;color:var(--dim)}\n  .kpi .v{margin-top:5px;font:600 17px/1 var(--mono);color:var(--ink)}\n  .kpi .v.warn{color:var(--amber)} .kpi .v.up{color:var(--red)} .kpi .v.ok{color:var(--green)}\n  .kpi .s{margin-top:4px;font:500 8px/1.35 var(--sans);color:var(--muted)}\n  .kpi .stamp{display:inline-block;margin-top:5px;font:700 7.5px/1 var(--mono);letter-spacing:.1em;color:var(--blue);border:1px solid currentColor;border-radius:3px;padding:3px 5px}\n  table{width:100%;border-collapse:collapse;font-size:9.5px}\n  th,td{padding:4px 10px;border-bottom:1px solid var(--line);text-align:right}\n  th{font:600 7.5px/1 var(--mono);letter-spacing:.08em;text-transform:uppercase;color:var(--dim)}\n  th:first-child,td:first-child{text-align:left}\n  tr:last-child td{border-bottom:0}\n  td.pos{color:var(--green)} td.neg{color:var(--red)}\n  tr.gap td{color:var(--amber);font-family:var(--mono);font-size:8.5px;text-align:left;background:rgba(227,176,75,.06)}\n  .prov{margin-top:auto;font:500 8px/1.4 var(--mono);color:var(--dim)}\n  .fade{animation:fade .5s ease both}\n  @keyframes fade{from{opacity:0}to{opacity:1}}\n  .pop{transform-box:fill-box;transform-origin:50% 100%;animation:pop .45s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes pop{from{opacity:0;transform:scaleY(0)}to{opacity:1;transform:scaleY(1)}}\n  .sweepx{transform-box:fill-box;transform-origin:0 50%;animation:sweepx .9s cubic-bezier(.4,0,.2,1) both}\n  @keyframes sweepx{from{transform:scaleX(0)}to{transform:scaleX(1)}}\n  .draw{animation:draw 1.1s cubic-bezier(.4,0,.2,1) both}\n  @keyframes draw{from{stroke-dashoffset:var(--len)}to{stroke-dashoffset:0}}\n  .in{animation:stamp .5s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes stamp{0%{opacity:0;transform:scale(1.6)}70%{opacity:1;transform:scale(.96)}100%{opacity:1;transform:scale(1)}}\n  @media (prefers-reduced-motion:reduce){*{animation:none!important}}\n  @media (max-width:430px){ .kpi .v{font-size:14px} th,td{padding:4px 7px} th:nth-child(2),td:nth-child(2){display:none} tr.gap td{display:table-cell} }\n</style></head>\n<body>\n  <div class=\"stage\">\n    <div class=\"topbar\">\n      <div class=\"brand\">DASLAB <b>LEDGER</b></div>\n      <div class=\"divider\"></div>\n      <div class=\"role\" id=\"role\">Hedge book <span>· metal · eight quarters · settlements against price</span></div>\n      <div class=\"chip\" id=\"chip\">FI · S1 · BANK REF</div>\n    </div>\n    <div class=\"body\">\n      <div class=\"card\">\n        <div class=\"hd\"><span>Exposed vs hedged</span><span class=\"legend\"><span><i style=\"background:var(--red)\"></i>metal index</span><span><i style=\"background:var(--green)\"></i>settled</span><span><i style=\"background:var(--amber)\"></i>no settlement</span></span></div>\n        <div class=\"chart\" id=\"chart\"></div>\n        <div class=\"kpis\" id=\"kpis\"></div>\n      </div>\n      <div class=\"card\" id=\"regCard\" hidden>\n        <div class=\"hd\"><span>Settlement register</span><span class=\"aside\" id=\"regSrc\">BSEG · bank reference</span></div>\n        <table id=\"reg\"></table>\n      </div>\n      <div class=\"prov\">Illustrated on a fictional company. Not a customer’s data.</div>\n    </div>\n  </div>\n<script>\n(()=>{\n  const el=id=>document.getElementById(id);\n  const replay=window.__DASLAB_REPLAY__===true;\n  let d=window.__DASLAB_WIDGET_DATA__||{}, last=null, key=JSON.stringify(d);\n\n  /** One column per quarter: the index line on top, metal received in € below.\n   * Settled quarters tint green, the gap is hatched amber across both. */\n  function renderChart(anim){\n    const host=el('chart'), W=host.clientWidth||520, q=d.quarters||[]; if(!q.length){ host.innerHTML=''; return; }\n    const H=206, PL=30, PR=10, LT=22, LB=118, BT=134, BB=188, n=q.length, cw=(W-PL-PR)/n, cx=i=>PL+cw*i+cw/2;\n    const prices=q.map(v=>v.price), lo=Math.min(...prices)*.95, hi=Math.max(...prices)*1.04, y=v=>LT+(LB-LT)*(1-(v-lo)/(hi-lo));\n    const pmax=Math.max(...q.map(v=>v.purchases))*1.25, by=v=>BB-(BB-BT)*v/pmax;\n    const settledNow=q.map(v=>!!v.settled), settledBefore=(last&&last.quarters||[]).map(v=>!!v.settled);\n    const gapNew=anim&&d.gap&&!(last&&last.gap), expNew=anim&&d.exposed&&!(last&&last.exposed);\n    let g='<defs><pattern id=\"hatch\" width=\"6\" height=\"6\" patternUnits=\"userSpaceOnUse\" patternTransform=\"rotate(45)\"><line x1=\"0\" y1=\"0\" x2=\"0\" y2=\"6\" stroke=\"rgba(227,176,75,.35)\" stroke-width=\"2\"/></pattern></defs>';\n    q.forEach((v,i)=>{ if(settledNow[i]) g+='<rect x=\"'+(PL+cw*i+1)+'\" y=\"'+LT+'\" width=\"'+(cw-2)+'\" height=\"'+(BB-LT)+'\" fill=\"rgba(67,201,138,.06)\"'+(anim&&!settledBefore[i]?' class=\"fade\" style=\"animation-delay:'+(i*90)+'ms\"':'')+'/>'; });\n    if(d.gap){ const x0=PL+cw*d.gap.from, x1=PL+cw*(d.gap.to+1);\n      g+='<g'+(gapNew?' class=\"sweepx\"':'')+'><rect x=\"'+x0+'\" y=\"'+LT+'\" width=\"'+(x1-x0)+'\" height=\"'+(BB-LT)+'\" fill=\"url(#hatch)\" stroke=\"rgba(227,176,75,.6)\" stroke-dasharray=\"3 3\"/></g>';\n      g+='<text x=\"'+((x0+x1)/2)+'\" y=\"'+(LT+11)+'\" text-anchor=\"middle\" fill=\"#e3b04b\" font-size=\"8.5\" font-weight=\"600\"'+(gapNew?' class=\"fade\" style=\"animation-delay:.7s\"':'')+'>'+d.gap.label+'</text>';\n      if(d.gap.sub) g+='<text x=\"'+((x0+x1)/2)+'\" y=\"'+(BT-4)+'\" text-anchor=\"middle\" fill=\"#e3b04b\" font-size=\"8.5\" font-weight=\"600\"'+(expNew?' class=\"fade\" style=\"animation-delay:.5s\"':'')+'>'+d.gap.sub+'</text>'; }\n    [lo,(lo+hi)/2,hi].forEach(v=>{ g+='<line x1=\"'+PL+'\" x2=\"'+(W-PR)+'\" y1=\"'+y(v)+'\" y2=\"'+y(v)+'\" stroke=\"rgba(255,255,255,.05)\"/><text x=\"'+(PL-4)+'\" y=\"'+(y(v)+3)+'\" text-anchor=\"end\" fill=\"#5b6774\" font-size=\"7\">'+Math.round(v)+'</text>'; });\n    const pts=q.map((v,i)=>cx(i).toFixed(1)+','+y(v.price).toFixed(1)).join(' ');\n    g+='<polyline points=\"'+pts+'\" fill=\"none\" stroke=\"#ef5a5e\" stroke-width=\"1.7\" stroke-linejoin=\"round\"/>';\n    q.forEach((v,i)=>{ g+='<circle cx=\"'+cx(i)+'\" cy=\"'+y(v.price)+'\" r=\"2.3\" fill=\"#ef5a5e\"/>'; });\n    g+='<text x=\"'+(cx(n-1)+2)+'\" y=\"'+(y(prices[n-1])-7)+'\" text-anchor=\"end\" fill=\"#ef5a5e\" font-size=\"8\" font-weight=\"600\">'+prices[n-1]+'</text>';\n    q.forEach((v,i)=>{\n      if(settledNow[i]){ const fresh=anim&&!settledBefore[i]; g+='<g'+(fresh?' class=\"pop\" style=\"animation-delay:'+(i*90)+'ms\"':'')+'><line x1=\"'+cx(i)+'\" x2=\"'+cx(i)+'\" y1=\"'+(LB-2)+'\" y2=\"'+(LB-12)+'\" stroke=\"#43c98a\" stroke-width=\"2\"/><circle cx=\"'+cx(i)+'\" cy=\"'+(LB-13)+'\" r=\"2.2\" fill=\"#43c98a\"/></g>'; }\n      const inGap=d.gap&&i>=d.gap.from&&i<=d.gap.to, col=d.exposed?(inGap?'#e3b04b':(v.settled?'#43c98a':'#7f90a4')):'#7f90a4', bw=Math.min(cw*.46,26);\n      g+='<rect x=\"'+(cx(i)-bw/2)+'\" y=\"'+by(v.purchases)+'\" width=\"'+bw+'\" height=\"'+(BB-by(v.purchases))+'\" rx=\"1.5\" fill=\"#7f90a4\" opacity=\"'+(d.exposed?.35:.8)+'\"/>';\n      if(d.exposed) g+='<rect x=\"'+(cx(i)-bw/2)+'\" y=\"'+by(v.purchases)+'\" width=\"'+bw+'\" height=\"'+(BB-by(v.purchases))+'\" rx=\"1.5\" fill=\"'+col+'\"'+(expNew?' class=\"pop\" style=\"animation-delay:'+(i*70)+'ms\"':'')+'/>';\n      g+='<text x=\"'+cx(i)+'\" y=\"'+(by(v.purchases)-3)+'\" text-anchor=\"middle\" fill=\"'+(d.exposed&&inGap?'#e3b04b':'#8e9aa8')+'\" font-size=\"7\">'+v.purchases.toFixed(1)+'</text>';\n      g+='<text x=\"'+cx(i)+'\" y=\"'+(H-6)+'\" text-anchor=\"middle\" fill=\"#5b6774\" font-size=\"7.5\">'+v.label+'</text>';\n    });\n    g+='<text x=\"'+(PL-4)+'\" y=\"'+(BT+6)+'\" text-anchor=\"end\" fill=\"#5b6774\" font-size=\"7\">€ m</text><line x1=\"'+PL+'\" x2=\"'+(W-PR)+'\" y1=\"'+BB+'\" y2=\"'+BB+'\" stroke=\"rgba(255,255,255,.14)\"/>';\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n  }\n  function renderKpis(anim){\n    const k=el('kpis'); k.innerHTML='';\n    const prev=new Map(((last&&last.kpis)||[]).map(m=>[m.label,m]));\n    (d.kpis||[]).forEach(m=>{ const p=prev.get(m.label), fresh=anim&&(!p||p.value!==m.value), stampNew=anim&&m.stamp&&!(p&&p.stamp);\n      const div=document.createElement('div'); div.className='kpi';\n      div.innerHTML='<div class=\"k\">'+m.label+'</div><div class=\"v num '+(m.state||'')+(fresh?' fade':'')+'\">'+m.value+'</div><div class=\"s\">'+(m.sub||'')+(m.src?'<span class=\"sc\">'+m.src+'</span>':'')+'</div>'+(m.stamp?'<span class=\"stamp'+(stampNew?' in':'')+'\">'+m.stamp+'</span>':'');\n      k.appendChild(div); });\n  }\n  function renderReg(anim){\n    const r=d.register; el('regCard').hidden=!r; if(!r) return;\n    const fresh=anim&&!(last&&last.register), gapFresh=anim&&r.some(x=>x.gap)&&!((last&&last.register)||[]).some(x=>x.gap);\n    let h='<tr><th>Reference</th><th>Posted</th><th class=\"num\">Notional €</th><th class=\"num\">Settlement €</th></tr>';\n    r.forEach((row,i)=>{\n      if(row.gap){ h+='<tr class=\"gap'+(gapFresh?' fade':'')+'\"><td colspan=\"4\">'+row.gap+'</td></tr>'; return; }\n      h+='<tr'+(fresh?' class=\"fade\" style=\"animation-delay:'+(i*80)+'ms\"':'')+'><td class=\"num\">'+row.ref+'</td><td class=\"num\">'+row.date+'</td><td class=\"num\">'+(row.notional||0).toLocaleString('en-US')+'</td><td class=\"num '+(row.amount<0?'neg':'pos')+'\">'+(row.amount<0?'−':'+')+Math.abs(row.amount).toLocaleString('en-US')+'</td></tr>';\n    });\n    el('reg').innerHTML=h;\n  }\n  function render(anim){\n    if(d.role) el('role').innerHTML=d.role;\n    if(d.chip) el('chip').textContent=d.chip;\n    renderChart(anim); renderKpis(anim); renderReg(anim);\n  }\n  addEventListener('message',e=>{\n    if(!replay||e.source!==parent||e.origin!==location.origin||e.data?.type!=='daslab:replay-clock'||!e.data.data) return;\n    const k=JSON.stringify(e.data.data); if(k===key) return;\n    last=d; d=e.data.data; key=k; render(true);\n  });\n  let w=0; new ResizeObserver(()=>{ if(document.body.clientWidth!==w){ w=document.body.clientWidth; render(false); } }).observe(document.body);\n  render(false);\n})();\n</script>\n</body></html>\n",
        "data": {
          "role": "Hedge book <span>· settlements against price</span>",
          "chip": "FI · S1 · BANK REF",
          "quarters": [],
          "kpis": []
        }
      },
      "layout": {
        "sortOrder": 3,
        "size": "large"
      }
    },
    {
      "id": "pf-rulebook",
      "type": "daslab/webview",
      "name": "Rulebook",
      "fields": {
        "html": "<!doctype html><html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width,initial-scale=1\"><style>\n  :root{\n    --bg:#0b0f14; --panel:#10161e; --line:rgba(255,255,255,.08);\n    --ink:#e6ecf3; --muted:#8e9aa8; --dim:#5b6774;\n    --acc:#d3a85a; --green:#43c98a; --red:#ef5a5e; --amber:#e3b04b; --blue:#5aa5e6; --violet:#c9a7e8;\n    --mono:ui-monospace,\"SF Mono\",Menlo,Consolas,monospace;\n    --sans:Inter,-apple-system,BlinkMacSystemFont,\"Segoe UI\",sans-serif;\n  }\n  *{box-sizing:border-box;margin:0;padding:0}\n  html,body{height:100%}\n  body{background:var(--bg);color:var(--ink);font-family:var(--sans);overflow:hidden;-webkit-font-smoothing:antialiased}\n  .stage{position:absolute;inset:0;overflow:hidden;background:var(--bg)}\n  .topbar{display:flex;align-items:center;gap:9px;padding:10px 14px;border-bottom:1px solid var(--line)}\n  .brand{font-weight:800;letter-spacing:.13em;font-size:11px;white-space:nowrap}\n  .brand b{color:var(--acc)}\n  .divider{width:1px;height:15px;background:var(--line);flex-shrink:0}\n  .role{font:600 10.5px/1 var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .role span{color:var(--muted);font-weight:400}\n  .chip{margin-left:auto;font:700 8px/1 var(--mono);letter-spacing:.1em;color:var(--acc);background:rgba(211,168,90,.12);padding:4px 7px;border-radius:4px;white-space:nowrap;flex-shrink:0}\n  .body{position:absolute;top:39px;left:0;right:0;bottom:0;overflow:auto;padding:12px 14px;display:flex;flex-direction:column;gap:10px}\n  .body>*{flex-shrink:0}\n  .num{font-family:var(--mono);font-variant-numeric:tabular-nums;white-space:nowrap}\n  .summary{display:flex;gap:6px;flex-wrap:wrap;align-items:center;perspective:400px}\n  .st{display:inline-block;font:600 8.5px/1 var(--mono);letter-spacing:.04em;padding:4px 6px;border-radius:4px;border:1px solid var(--line);color:var(--muted);white-space:nowrap}\n  .st.ok{color:var(--green);border-color:rgba(67,201,138,.4)} .st.single{color:var(--blue);border-color:rgba(90,165,230,.4)}\n  .st.tension{color:var(--amber);border-color:rgba(227,176,75,.5)} .st.open{color:var(--muted)}\n  .st.zero{opacity:.45}\n  .flip{animation:flip .6s cubic-bezier(.3,.7,.3,1) both}\n  @keyframes flip{0%{transform:rotateX(0)}45%{transform:rotateX(90deg);opacity:.2}55%{transform:rotateX(-90deg);opacity:.2}100%{transform:rotateX(0);opacity:1}}\n  .summary .checked{margin-left:auto;font:500 8.5px/1 var(--mono);color:var(--dim)}\n  .card{background:var(--panel);border:1px solid var(--line);border-radius:4px;overflow:hidden}\n  .card .hd{display:flex;justify-content:space-between;align-items:center;gap:8px;padding:7px 10px;border-bottom:1px solid var(--line);font:600 8.5px/1 var(--sans);letter-spacing:.11em;text-transform:uppercase;color:var(--dim)}\n  .card .hd .aside{font:500 8.5px/1 var(--mono);color:var(--muted);letter-spacing:0;text-transform:none}\n  .chart svg{display:block}\n  svg text{font-family:var(--mono)}\n  .rule{display:grid;grid-template-columns:28px 1fr auto;gap:8px;align-items:start;padding:7px 10px;border-bottom:1px solid var(--line);perspective:400px}\n  .rule:last-child{border-bottom:0}\n  .rule.active{background:rgba(227,176,75,.06)}\n  .rule.active.ok{background:rgba(67,201,138,.06)}\n  .rule .id{font:600 9.5px/1.3 var(--mono);color:var(--dim)}\n  .rule .claim{font:500 10px/1.35 var(--sans);color:var(--ink);min-width:0}\n  .rule .claim small{display:block;margin-top:3px;font:500 8px/1.4 var(--mono);color:var(--muted)}\n  .rule .claim small.note{color:var(--green)}\n  .rule .claim small.warn{color:var(--amber)}\n  .tags{display:inline-flex;gap:3px;margin-left:4px;vertical-align:middle}\n  .tag{font:600 7px/1 var(--mono);letter-spacing:.06em;color:var(--muted);border:1px solid var(--line);padding:2px 4px;border-radius:3px}\n  .tag.WORKBOOK{color:var(--violet);border-color:rgba(201,167,232,.4)} .tag.S4{color:var(--blue);border-color:rgba(90,165,230,.4)}\n  .tag.DOC{color:var(--acc);border-color:rgba(211,168,90,.4)} .tag.ASSUMED{color:var(--dim)}\n  .rule .st{align-self:start}\n  .prov{margin-top:auto;font:500 8px/1.4 var(--mono);color:var(--dim)}\n  .fade{animation:fade .5s ease both}\n  @keyframes fade{from{opacity:0}to{opacity:1}}\n  .pop{transform-box:fill-box;transform-origin:50% 50%;animation:pop .45s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes pop{from{opacity:0;transform:scale(.6)}to{opacity:1;transform:scale(1)}}\n  .march{animation:march .9s linear infinite}\n  @keyframes march{to{stroke-dashoffset:-12}}\n  .stampin{transform-box:fill-box;transform-origin:50% 50%;animation:stamp .5s cubic-bezier(.2,.8,.2,1) both}\n  @keyframes stamp{0%{opacity:0;transform:scale(1.7)}70%{opacity:1;transform:scale(.96)}100%{opacity:1;transform:scale(1)}}\n  @media (prefers-reduced-motion:reduce){*{animation:none!important}}\n  @media (max-width:430px){ .rule{grid-template-columns:24px 1fr auto;padding:6px 8px} .rule .claim{font-size:9.5px} }\n</style></head>\n<body>\n  <div class=\"stage\">\n    <div class=\"topbar\">\n      <div class=\"brand\">DASLAB <b>LEDGER</b></div>\n      <div class=\"divider\"></div>\n      <div class=\"role\" id=\"role\">Rulebook <span>· one rule, one claim, one source · re-checked on every read</span></div>\n      <div class=\"chip\" id=\"chip\">RULES · LIVE</div>\n    </div>\n    <div class=\"body\">\n      <div class=\"summary\" id=\"summary\"></div>\n      <div class=\"card\" id=\"evCard\">\n        <div class=\"hd\"><span>Evidence</span><span class=\"aside\" id=\"evAside\"></span></div>\n        <div class=\"chart\" id=\"ev\"></div>\n      </div>\n      <div class=\"card\" id=\"list\"></div>\n      <div class=\"prov\">Illustrated on a fictional company. Not a customer’s data.</div>\n    </div>\n  </div>\n<script>\n(()=>{\n  const el=id=>document.getElementById(id);\n  const replay=window.__DASLAB_REPLAY__===true;\n  let d=window.__DASLAB_WIDGET_DATA__||{}, last=null, key=JSON.stringify(d);\n  const STATUS={ok:{glyph:'✓',word:'confirmed',cls:'ok'},single:{glyph:'◐',word:'single-source',cls:'single'},tension:{glyph:'!',word:'tension',cls:'tension'},open:{glyph:'?',word:'open',cls:'open'}};\n  const tags=list=>'<span class=\"tags\">'+(list||[]).map(t=>'<span class=\"tag '+t+'\">'+t+'</span>').join('')+'</span>';\n  const counts=rules=>{ const c={ok:0,single:0,tension:0,open:0}; (rules||[]).forEach(r=>{ c[r.status]++; }); return c; };\n  const TAGC={WORKBOOK:'#c9a7e8',S4:'#5aa5e6',DOC:'#d3a85a',ASSUMED:'#5b6774'};\n\n  function box(x,y,w,h,n,stroke,cls){\n    return '<g'+(cls||'')+'><rect x=\"'+x+'\" y=\"'+y+'\" width=\"'+w+'\" height=\"'+h+'\" rx=\"4\" fill=\"#0d131a\" stroke=\"'+stroke+'\"/>'\n      +'<text x=\"'+(x+8)+'\" y=\"'+(y+13)+'\" fill=\"'+(TAGC[n.tag]||'#8e9aa8')+'\" font-size=\"7\" font-weight=\"700\" letter-spacing=\".08em\">'+n.tag+'</text>'\n      +'<text x=\"'+(x+8)+'\" y=\"'+(y+25)+'\" fill=\"#8e9aa8\" font-size=\"7.5\">'+n.label+'</text>'\n      +'<text x=\"'+(x+8)+'\" y=\"'+(y+h-8)+'\" fill=\"#e6ecf3\" font-size=\"12\" font-weight=\"600\">'+n.value+'</text></g>';\n  }\n  /** Two sources feed one rule. In tension the edges are amber and marked\n   * unequal; the mechanism node lands between them and the edges turn green. */\n  function renderEvidence(anim){\n    const e=d.evidence, host=el('ev'); if(!e){ el('evCard').hidden=true; return; } el('evCard').hidden=false;\n    const W=host.clientWidth||520, narrow=W<460, H=narrow?(e.mech?196:150):150, bw=narrow?Math.min(128,W*.4):160, bh=48, rw=narrow?Math.min(110,W*.34):128;\n    const A={x:8,y:14}, B={x:8,y:(narrow?150:H)-bh-14}, R={x:W-rw-8,y:narrow?14:H/2-30}, mw=narrow?rw:124, M=narrow?{x:R.x,y:R.y+76}:{x:(A.x+bw+R.x)/2-62,y:H/2-bh/2};\n    const st=e.state, col=st==='resolved'?'#43c98a':'#e3b04b', prev=last&&last.evidence;\n    const mechNew=anim&&e.mech&&!(prev&&prev.mech), resNew=anim&&st==='resolved'&&!(prev&&prev.state==='resolved');\n    let g='';\n    const edge=(x1,y1,x2,y2,extra)=>'<path d=\"M'+x1+','+y1+' C'+((x1+x2)/2)+','+y1+' '+((x1+x2)/2)+','+y2+' '+x2+','+y2+'\" fill=\"none\" stroke=\"'+col+'\" stroke-width=\"1.4\"'+(extra||'')+'/>';\n    const ry=R.y+30;\n    g+=edge(A.x+bw,A.y+bh/2,R.x,ry-8,st==='checking'?' stroke-dasharray=\"4 2\"':'');\n    g+=edge(B.x+bw,B.y+bh/2,R.x,ry+8,st==='checking'?' stroke-dasharray=\"4 2\" class=\"march\"':'');\n    if(!e.mech){\n      const nx=A.x+bw/2, ny=H/2;\n      g+='<text x=\"'+nx+'\" y=\"'+(ny+5)+'\" text-anchor=\"middle\" fill=\"#e3b04b\" font-size=\"15\" font-weight=\"600\">≠</text>';\n    } else {\n      g+=narrow\n        ?'<g'+(mechNew?' class=\"pop\"':'')+'><path d=\"M'+(B.x+bw)+','+(B.y+bh-10)+' C'+((B.x+bw+M.x)/2)+','+(B.y+bh-10)+' '+((B.x+bw+M.x)/2)+','+(M.y+bh/2)+' '+M.x+','+(M.y+bh/2)+'\" fill=\"none\" stroke=\"#5aa5e6\" stroke-width=\"1.2\" stroke-dasharray=\"3 2\"/></g>'\n        :'<g'+(mechNew?' class=\"pop\"':'')+'><path d=\"M'+(B.x+bw)+','+(B.y+10)+' C'+(M.x-10)+','+(B.y+10)+' '+(M.x-10)+','+(M.y+bh)+' '+M.x+','+(M.y+bh-8)+'\" fill=\"none\" stroke=\"#5aa5e6\" stroke-width=\"1.2\" stroke-dasharray=\"3 2\"/>'\n        +'<path d=\"M'+(M.x+mw/2)+','+M.y+' C'+(M.x+mw/2)+','+(A.y+bh/2)+' '+(M.x+mw/2)+','+(A.y+bh/2)+' '+(A.x+bw)+','+(A.y+bh-8)+'\" fill=\"none\" stroke=\"#5aa5e6\" stroke-width=\"1.2\" stroke-dasharray=\"3 2\"/></g>';\n      g+=box(M.x,M.y,mw,bh,e.mech,'rgba(90,165,230,.55)',mechNew?' class=\"pop\" style=\"animation-delay:.1s\"':'');\n      if(e.sum) g+='<text x=\"'+(narrow?B.x+2:M.x+mw/2)+'\" y=\"'+(narrow?B.y+bh+16:M.y+bh+14)+'\" text-anchor=\"'+(narrow?'start':'middle')+'\" fill=\"#8e9aa8\" font-size=\"8\"'+(mechNew?' class=\"fade\" style=\"animation-delay:.5s\"':'')+'>'+e.sum+'</text>';\n    }\n    g+=box(A.x,A.y,bw,bh,e.a,'rgba(201,167,232,.45)');\n    g+=box(B.x,B.y,bw,bh,e.b,'rgba(90,165,230,.45)');\n    g+='<rect x=\"'+R.x+'\" y=\"'+R.y+'\" width=\"'+rw+'\" height=\"60\" rx=\"4\" fill=\"#0d131a\" stroke=\"'+col+'\"/>';\n    g+='<text x=\"'+(R.x+8)+'\" y=\"'+(R.y+15)+'\" fill=\"#e6ecf3\" font-size=\"11\" font-weight=\"700\">E8</text><text x=\"'+(R.x+30)+'\" y=\"'+(R.y+15)+'\" fill=\"#8e9aa8\" font-size=\"7.5\">'+(narrow?'2025':'collections 2025')+'</text>';\n    const sw=narrow?rw-16:92, word=st==='resolved'?'CONFIRMED':st==='checking'?'CHECKING':'TENSION';\n    g+='<g'+(resNew?' class=\"stampin\"':'')+'><rect x=\"'+(R.x+8)+'\" y=\"'+(R.y+30)+'\" width=\"'+sw+'\" height=\"18\" rx=\"3\" fill=\"none\" stroke=\"'+col+'\" stroke-width=\"1.2\"/><text x=\"'+(R.x+8+sw/2)+'\" y=\"'+(R.y+42)+'\" text-anchor=\"middle\" fill=\"'+col+'\" font-size=\"8.5\" font-weight=\"700\" letter-spacing=\".12em\">'+word+'</text></g>';\n    host.innerHTML='<svg width=\"'+W+'\" height=\"'+H+'\" viewBox=\"0 0 '+W+' '+H+'\">'+g+'</svg>';\n    el('evAside').textContent=e.caption||'';\n  }\n  function renderSummary(anim){\n    const c=counts(d.rules), p=last?counts(last.rules):c;\n    el('summary').innerHTML=Object.keys(STATUS).map(k=>'<span class=\"st '+STATUS[k].cls+(c[k]?'':' zero')+(anim&&c[k]!==p[k]?' flip':'')+'\">'+STATUS[k].glyph+' '+c[k]+' '+STATUS[k].word+'</span>').join('')+'<span class=\"checked\">last read '+(d.checked||'now')+'</span>';\n  }\n  function renderRules(anim){\n    const host=el('list'); host.innerHTML='';\n    const prev=new Map(((last&&last.rules)||[]).map(r=>[r.id,r]));\n    (d.rules||[]).forEach(r=>{\n      const s=STATUS[r.status]||STATUS.open, p=prev.get(r.id), flipped=anim&&p&&p.status!==r.status, noted=anim&&p&&p.note!==r.note;\n      const row=document.createElement('div'); row.className='rule'+(r.active?' active '+s.cls:'');\n      row.innerHTML='<div class=\"id\">'+r.id+'</div><div class=\"claim\">'+r.claim+tags(r.tags)+(r.note?'<small class=\"'+(r.noteKind||'')+(noted?' fade':'')+'\">'+r.note+'</small>':'')+'</div><span class=\"st '+s.cls+(flipped?' flip':'')+'\">'+s.glyph+' '+s.word+'</span>';\n      host.appendChild(row);\n    });\n  }\n  function render(anim){\n    if(d.role) el('role').innerHTML=d.role;\n    if(d.chip) el('chip').textContent=d.chip;\n    renderSummary(anim); renderEvidence(anim); renderRules(anim);\n  }\n  addEventListener('message',e=>{\n    if(!replay||e.source!==parent||e.origin!==location.origin||e.data?.type!=='daslab:replay-clock'||!e.data.data) return;\n    const k=JSON.stringify(e.data.data); if(k===key) return;\n    last=d; d=e.data.data; key=k; render(true);\n  });\n  let w=0; new ResizeObserver(()=>{ if(document.body.clientWidth!==w){ w=document.body.clientWidth; render(false); } }).observe(document.body);\n  render(false);\n})();\n</script>\n</body></html>\n",
        "data": {
          "role": "Rulebook <span>· one rule, one claim, one source</span>",
          "chip": "RULES · LIVE",
          "rules": []
        }
      },
      "layout": {
        "sortOrder": 4,
        "size": "large"
      }
    }
  ],
  "meta": {
    "author": "Daslab",
    "license": "MIT"
  }
}
