{
  "version": 1,
  "id": "scn_cable_qc_line",
  "slug": "cable-qc-line",
  "name": "Live QC Line",
  "icon": "camera.metering.spot",
  "tint": "#315FC5",
  "settings": {
    "description": "Watch a cable extrusion line, catch the defects the camera sees, and hold the batch before the fault ships — every action on one record."
  },
  "assets": [
    {
      "id": "qc-line",
      "type": "daslab/webview",
      "name": "Live QC line",
      "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:#080b10; --panel:rgba(9,13,19,.68); --line:rgba(255,255,255,.09);\n    --ink:#e8eef5; --muted:#8a97a6; --dim:#5a6675;\n    --copper:#e0964f; --cyan:#37d3e6; --green:#3ad07f; --amber:#e6b23a; --red:#ff5257;\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}\n  .feedvid{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;filter:contrast(1.04) saturate(1.02) brightness(.9)}\n  .fallback{position:absolute;inset:0;background:radial-gradient(120% 80% at 50% 0%,#1a2330 0%,#0b0f15 60%,#05070a 100%)}\n  .fallback::before{content:\"\";position:absolute;left:-10%;right:-10%;top:46%;height:12%;background:linear-gradient(180deg,#2a2f36 0%,#454b54 18%,#0c0e12 52%,#31363d 82%,#15171b 100%);border-radius:40px;box-shadow:0 30px 80px rgba(0,0,0,.6),inset 0 2px 6px rgba(255,255,255,.18)}\n  .grain{position:absolute;inset:0;pointer-events:none;opacity:.05;mix-blend-mode:overlay;background-image:url(\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2'/></filter><rect width='100%25' height='100%25' filter='url(%23n)'/></svg>\")}\n  .vignette{position:absolute;inset:0;pointer-events:none;background:radial-gradient(120% 95% at 50% 42%,transparent 46%,rgba(0,0,0,.62) 100%)}\n  .scan{position:absolute;top:70px;bottom:118px;width:2px;left:50%;background:linear-gradient(180deg,transparent,rgba(55,211,230,.5) 50%,transparent 92%);box-shadow:0 0 24px 3px rgba(55,211,230,.22);animation:sweep 4.6s ease-in-out infinite}\n  @keyframes sweep{0%,100%{left:26%}50%{left:74%}}\n\n  .det{position:absolute;pointer-events:none;opacity:0;transform:scale(.96);transition:opacity .18s ease,transform .18s ease}\n  .det.show{opacity:1;transform:scale(1)}\n  .det .corner{position:absolute;width:12px;height:12px;border:2px solid var(--cyan)}\n  .det .tl{top:-1px;left:-1px;border-right:0;border-bottom:0}.det .tr{top:-1px;right:-1px;border-left:0;border-bottom:0}\n  .det .bl{bottom:-1px;left:-1px;border-right:0;border-top:0}.det .br{bottom:-1px;right:-1px;border-left:0;border-top:0}\n  .det .frame{position:absolute;inset:0;border:1px solid rgba(55,211,230,.28);background:rgba(55,211,230,.06)}\n  .det .tag{position:absolute;left:-1px;top:-19px;white-space:nowrap;font:600 9.5px/1 var(--mono);letter-spacing:.02em;color:#04252b;background:var(--cyan);padding:3px 5px;border-radius:4px 4px 4px 0;box-shadow:0 4px 12px rgba(0,0,0,.4)}\n  .det .tag .conf{opacity:.7;margin-left:5px}\n  .det.crit .corner{border-color:var(--red)}\n  .det.crit .frame{border-color:rgba(255,82,87,.4);background:rgba(255,82,87,.1);animation:pulse .8s ease-in-out infinite}\n  .det.crit .tag{background:var(--red);color:#fff}\n  @keyframes pulse{50%{background:rgba(255,82,87,.17)}}\n\n  .topbar{position:absolute;top:0;left:0;right:0;display:flex;align-items:center;gap:9px;z-index:5;padding:9px 12px 18px;background:linear-gradient(180deg,rgba(2,4,7,.9) 0%,rgba(2,4,7,.45) 62%,transparent 100%)}\n  .brand{font-weight:800;letter-spacing:.13em;font-size:11px;text-shadow:0 1px 8px rgba(0,0,0,.85);white-space:nowrap}\n  .brand b{color:var(--copper)}\n  .divider{width:1px;height:15px;background:var(--line);flex-shrink:0}\n  .lineName{font:600 10.5px/1 var(--sans);color:var(--ink);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;text-shadow:0 1px 8px rgba(0,0,0,.85)}\n  .lineName span{color:var(--muted);font-weight:400}\n  .live{margin-left:auto;display:flex;align-items:center;gap:6px;font:700 10px/1 var(--mono);letter-spacing:.13em;color:var(--red);text-shadow:0 1px 8px rgba(0,0,0,.85);flex-shrink:0}\n  .live .dot{width:7px;height:7px;border-radius:50%;background:var(--red);animation:beat 1.4s ease-out infinite}\n  @keyframes beat{0%{box-shadow:0 0 0 0 rgba(255,82,87,.55)}70%{box-shadow:0 0 0 8px rgba(255,82,87,0)}}\n  .clock{font:600 10px/1 var(--mono);color:var(--muted);letter-spacing:.04em;text-shadow:0 1px 8px rgba(0,0,0,.85);flex-shrink:0}\n\n  .kpis{position:absolute;top:38px;left:12px;right:12px;display:grid;grid-template-columns:repeat(4,1fr);gap:6px;z-index:4}\n  .kpi{background:var(--panel);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid var(--line);border-radius:9px;padding:6px 8px;min-width:0;overflow:hidden}\n  .kpi .l{font:600 8px/1.1 var(--sans);letter-spacing:.08em;text-transform:uppercase;color:var(--dim);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .kpi .v{margin-top:4px;font:600 16px/1 var(--mono);color:var(--ink);display:flex;align-items:baseline;gap:3px}\n  .kpi .v u{font-size:8.5px;text-decoration:none;color:var(--muted)}\n  .kpi .s{margin-top:3px;font:500 8px/1.2 var(--sans);color:var(--muted);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .kpi.good .v{color:var(--green)}.kpi.warn .v{color:var(--amber)}.kpi.bad .v{color:var(--red)}\n  .pill{display:inline-block;padding:1px 4px;border-radius:4px;font:600 8px/1.2 var(--mono)}\n  .pill.ok{background:rgba(58,208,127,.15);color:var(--green)}.pill.warn{background:rgba(230,178,58,.16);color:var(--amber)}\n\n  .feed{position:absolute;left:12px;bottom:12px;width:52%;max-width:400px;z-index:4;background:var(--panel);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid var(--line);border-radius:10px;overflow:hidden}\n  .fh{display:flex;align-items:center;padding:6px 10px;border-bottom:1px solid var(--line)}\n  .fh h3{font:700 8.5px/1 var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}\n  .fh .c{margin-left:auto;font:600 8.5px/1 var(--mono);color:var(--dim)}\n  .rows{max-height:96px;overflow:hidden}\n  .row{display:grid;grid-template-columns:1fr auto;gap:8px;align-items:center;padding:6px 10px;border-bottom:1px solid rgba(255,255,255,.05)}\n  .row .d{font:600 10.5px/1.2 var(--sans);color:var(--ink);min-width:0}\n  .row .d small{display:block;font:500 8.5px/1.25 var(--mono);color:var(--muted);margin-top:2px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}\n  .sev{font:700 8px/1 var(--mono);letter-spacing:.04em;padding:3px 5px;border-radius:4px;white-space:nowrap}\n  .sev.MINOR{background:rgba(138,151,166,.16);color:var(--muted)}.sev.MAJOR{background:rgba(230,178,58,.16);color:var(--amber)}\n  .sev.HIGH{background:rgba(224,150,79,.18);color:var(--copper)}.sev.CRIT{background:rgba(255,82,87,.18);color:var(--red)}\n\n  .lines{position:absolute;right:12px;bottom:12px;width:34%;max-width:168px;z-index:4;background:var(--panel);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);border:1px solid var(--line);border-radius:10px;padding:8px 10px 9px}\n  .lines .lh{display:flex;align-items:baseline;gap:6px;margin-bottom:7px}\n  .lines .lh h3{font:700 8.5px/1 var(--sans);letter-spacing:.12em;text-transform:uppercase;color:var(--muted)}\n  .lines .lh .c{margin-left:auto;font:600 8.5px/1 var(--mono);color:var(--dim)}\n  .lgrid{display:grid;grid-template-columns:repeat(4,1fr);gap:4px}\n  .ltile{aspect-ratio:1.5;border-radius:3px;background:rgba(58,208,127,.22);border:1px solid rgba(58,208,127,.3);display:flex;align-items:center;justify-content:center;font:600 7.5px/1 var(--mono);color:rgba(232,238,245,.5)}\n  .ltile.amber{background:rgba(230,178,58,.32);border-color:rgba(230,178,58,.55);color:var(--amber)}\n  .ltile.red{background:rgba(255,82,87,.42);border-color:var(--red);color:#fff;box-shadow:0 0 8px rgba(255,82,87,.5)}\n  .ltile.active{outline:1px solid rgba(232,238,245,.55);outline-offset:1px}\n\n  .hold{position:absolute;top:112px;left:50%;transform:translateX(-50%) translateY(-8px);z-index:6;opacity:0;pointer-events:none;transition:.25s;background:linear-gradient(180deg,#ff5257,#c8393d);color:#fff;font:800 10px/1 var(--sans);letter-spacing:.06em;text-transform:uppercase;padding:8px 13px;border-radius:8px;box-shadow:0 10px 30px rgba(255,82,87,.4);display:flex;align-items:center;gap:8px;white-space:nowrap}\n  .hold.show{opacity:1;transform:translateX(-50%) translateY(0)}\n  .hold .b{background:rgba(0,0,0,.22);padding:2px 5px;border-radius:4px;font-family:var(--mono)}\n  .error{position:absolute;inset:0;z-index:9;display:grid;place-content:center;text-align:center;padding:20px;background:var(--bg);color:#b3c5ba;font:11px/1.5 var(--sans)}\n  .error:empty{display:none}\n  @media (max-width:430px){\n    .kpis{grid-template-columns:repeat(2,1fr)}\n    .feed{width:auto;right:12px;bottom:118px}\n    .lines{width:auto;left:12px}\n  }\n</style></head>\n<body>\n  <div class=\"stage\">\n    <video class=\"feedvid\" muted loop playsinline preload=\"auto\" aria-label=\"Cable extrusion line, inspection camera\"></video>\n    <div class=\"fallback\" id=\"fallback\"></div>\n    <div class=\"scan\"></div>\n    <div id=\"dets\"></div>\n    <div class=\"grain\"></div>\n    <div class=\"vignette\"></div>\n\n    <div class=\"topbar\">\n      <div class=\"brand\">CABLE <b>QC</b></div>\n      <div class=\"divider\"></div>\n      <div class=\"lineName\" id=\"lineName\">Extrusion Line 3 <span>· MV XLPE 24 kV</span></div>\n      <div class=\"live\"><span class=\"dot\"></span>LIVE</div>\n      <div class=\"divider\"></div>\n      <div class=\"clock\" id=\"clock\">12:01:00</div>\n    </div>\n\n    <div class=\"hold\" id=\"hold\"><span>⛔ Batch held — copper exposure</span><span class=\"b\" id=\"holdBatch\">B-4471</span></div>\n\n    <div class=\"kpis\">\n      <div class=\"kpi\" id=\"k-speed\"><div class=\"l\">Line speed</div><div class=\"v\"><span id=\"speed\">412</span><u>m/min</u></div><div class=\"s\">target 400–430</div></div>\n      <div class=\"kpi good\" id=\"k-od\"><div class=\"l\">Outer Ø</div><div class=\"v\"><span id=\"od\">12.41</span><u>mm</u></div><div class=\"s\"><span class=\"pill ok\" id=\"odpill\">in tol</span></div></div>\n      <div class=\"kpi\" id=\"k-ppm\"><div class=\"l\">Defect rate</div><div class=\"v\"><span id=\"ppm\">340</span><u>ppm</u></div><div class=\"s\">shift avg</div></div>\n      <div class=\"kpi good\" id=\"k-fpy\"><div class=\"l\">First-pass yield</div><div class=\"v\"><span id=\"fpy\">98.1</span><u>%</u></div><div class=\"s\">+2.4 vs manual</div></div>\n    </div>\n\n    <div class=\"feed\">\n      <div class=\"fh\"><h3>Defect feed</h3><div class=\"c\">live</div></div>\n      <div class=\"rows\" id=\"feed\"></div>\n    </div>\n\n    <div class=\"lines\">\n      <div class=\"lh\"><h3>Lines</h3><div class=\"c\" id=\"linesC\">12 · Line 3</div></div>\n      <div class=\"lgrid\" id=\"lgrid\"></div>\n    </div>\n    <div class=\"error\" id=\"error\"></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__||{};\n  let elapsed=Number(d.elapsed)||0, playing=false, speed=1, ready=false;\n  const host=replay?location.origin:'https://daslab.run';\n  const media=path=>new URL(path,host).href;\n  const video=document.querySelector('.feedvid');\n  video.muted=true; video.src=media(d.videoSrc||'/cable-qc-line.mp4');\n  video.addEventListener('loadeddata',()=>{el('fallback').style.display='none'});\n  video.addEventListener('loadedmetadata',()=>{ready=Number.isFinite(video.duration)&&video.duration>0;update()});\n  video.addEventListener('error',()=>{el('error').textContent='The inspection clip could not load.';});\n\n  // 12 lines, one slim bar each; Line 3 (index 2) is this run.\n  const lg=el('lgrid'); const bars=[];\n  for(let i=0;i<12;i++){ const t=document.createElement('div'); t.className='ltile'+(i===2?' active':''); t.textContent='L'+(i+1); lg.appendChild(t); bars.push(t); }\n\n  const two=n=>String(n).padStart(2,'0');\n  function clockText(){ const base=12*3600+1*60; const s=Math.floor(base+elapsed); return two(Math.floor(s/3600)%24)+':'+two(Math.floor(s/60)%60)+':'+two(s%60); }\n\n  function renderKpis(){\n    const k=d.kpis||{};\n    if(k.speed!=null) el('speed').textContent=Math.round(k.speed+(Math.sin(elapsed*1.3)*4));\n    if(k.od!=null) el('od').textContent=Number(k.od).toFixed(2);\n    if(k.ppm!=null) el('ppm').textContent=Math.round(k.ppm);\n    if(k.fpy!=null) el('fpy').textContent=Number(k.fpy).toFixed(1);\n    el('k-od').className='kpi '+(k.odState==='warn'?'warn':'good');\n    el('odpill').className='pill '+(k.odState==='warn'?'warn':'ok'); el('odpill').textContent=k.odPill||(k.odState==='warn'?'over tol':'in tol');\n    el('k-ppm').className='kpi '+(k.ppmState||'');\n    el('k-fpy').className='kpi '+(k.fpyState||'good');\n  }\n  function renderDets(){\n    const dets=el('dets'); dets.innerHTML='';\n    (d.detections||[]).forEach(ev=>{\n      const b=ev.box||{x:.5,y:.45,w:.14,h:.12}, crit=!!ev.crit;\n      const n=document.createElement('div'); n.className='det show'+(crit?' crit':'');\n      n.style.left=(b.x*100)+'%'; n.style.top=(b.y*100)+'%'; n.style.width=(b.w*100)+'%'; n.style.height=(b.h*100)+'%';\n      n.innerHTML='<div class=\"frame\"></div><span class=\"corner tl\"></span><span class=\"corner tr\"></span><span class=\"corner bl\"></span><span class=\"corner br\"></span><span class=\"tag\">'+(ev.type||'')+'<span class=\"conf\">'+Math.round((ev.conf||0)*100)+'%</span></span>';\n      dets.appendChild(n);\n    });\n  }\n  function renderFeed(){\n    const feed=el('feed'); feed.innerHTML='';\n    (d.feed||[]).slice(0,3).forEach(ev=>{\n      const r=document.createElement('div'); r.className='row';\n      r.innerHTML='<div class=\"d\">'+(ev.type||'')+'<small>'+(ev.time||'')+' · '+(ev.batch||'')+' · '+(ev.note||'')+'</small></div><div class=\"sev '+(ev.sev||'MINOR')+'\">'+(ev.sev||'MINOR')+'</div>';\n      feed.appendChild(r);\n    });\n  }\n  function renderLines(){\n    const st=d.lines||[];\n    bars.forEach((t,i)=>{ const s=st[i]||'ok'; t.className='ltile'+(i===2?' active':'')+(s==='red'?' red':s==='amber'?' amber':''); });\n  }\n  function renderHold(){\n    const h=el('hold');\n    if(d.hold){ el('holdBatch').textContent=d.hold.batch||'—'; h.classList.add('show'); }\n    else h.classList.remove('show');\n  }\n  // The live camera always runs — it is ambient footage, not part of the\n  // scrubbable timeline. Only the overlays (detections, KPIs, feed, lines) are\n  // driven by the replay clock. Reduced-motion visitors get a still frame.\n  function syncVideo(){\n    if(!ready) return;\n    if(matchMedia('(prefers-reduced-motion: reduce)').matches){ video.pause(); return; }\n    video.playbackRate=1;\n    if(video.paused) video.play().catch(()=>{});\n  }\n  function update(){\n    if(d.lineName) el('lineName').innerHTML=d.lineName;\n    el('clock').textContent=clockText();\n    renderKpis(); renderDets(); renderFeed(); renderLines(); renderHold(); syncVideo();\n  }\n  addEventListener('message',e=>{\n    if(!replay||e.source!==parent||e.origin!==location.origin||e.data?.type!=='daslab:replay-clock') return;\n    if(e.data.data) d=e.data.data;\n    elapsed=Math.max(0,Number(e.data.elapsed)||0)/1000; playing=e.data.playing===true; speed=Number(e.data.speed)||1;\n    update();\n  });\n  setInterval(()=>{ if(!replay||playing) renderKpis(); }, 420);\n  update();\n})();\n</script>\n</body></html>\n",
        "data": {
          "videoSrc": "/cable-qc-line.mp4",
          "videoOffset": 0,
          "lineName": "Extrusion Line 3 <span>· MV XLPE 24 kV · spool B-4471</span>"
        }
      },
      "layout": {
        "sortOrder": 0,
        "size": "large"
      }
    }
  ],
  "meta": {
    "author": "Daslab",
    "license": "MIT"
  }
}
