diff --git a/docs/factory-console.html b/docs/factory-console.html index a2d97b6..ef3a375 100644 --- a/docs/factory-console.html +++ b/docs/factory-console.html @@ -144,11 +144,16 @@ document.documentElement.setAttribute("data-theme",t);})(); /* feed */ .feed { display: flex; flex-direction: column; font-size: 12.5px; } - .fr { display: grid; grid-template-columns: 62px minmax(0, 1.5fr) 82px minmax(0, 1fr); gap: 10px; padding: 4px 0; border-bottom: 1px dashed var(--line); align-items: baseline; } + /* fixed columns sized in characters of the mono font, so a longer ID never pushes the next column */ + .fr { display: grid; grid-template-columns: 8.4ch 10.6ch 5ch 9ch minmax(0, 1fr); column-gap: 14px; padding: 4px 0; border-bottom: 1px dashed var(--line); align-items: baseline; font-family: var(--mono); font-variant-numeric: tabular-nums; } + .fr > span { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .fr:last-child { border-bottom: 0; } - .fr .k { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; color: var(--mute); } - .fr .fo { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } - .fr .v { color: var(--ok); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } + .fr .k, .fr .pr { color: var(--mute); } + .fr .v { color: var(--ok); font-family: system-ui, -apple-system, "Segoe UI", sans-serif; } + /* the header row keeps the mono font on the row itself, so its ch-based columns match the data rows; only the labels change font */ + .fr.hd { color: var(--faint); border-bottom: 1px solid var(--line); padding-top: 0; } + .fr.hd > span { font-family: system-ui, -apple-system, "Segoe UI", sans-serif; font-size: 10.5px; text-transform: uppercase; letter-spacing: 0.06em; } + .feed.cf .fr { grid-template-columns: 8.4ch 7ch 10.6ch 5ch minmax(0, 1fr); } .fr.bad .v { color: var(--bad); font-weight: 650; } .al { display: flex; gap: 10px; padding: 8px 0; border-bottom: 1px solid var(--line); color: var(--ink); text-decoration: none; } @@ -235,7 +240,7 @@ document.documentElement.setAttribute("data-theme",t);})(); .wo.todo { border-left-color: var(--inv); } .wo.done { border-left-color: var(--ok); opacity: 0.86; } .wo-h { display: flex; align-items: center; gap: 8px; font-size: 11.5px; min-width: 0; } - .wo-h .who { margin-left: auto; color: var(--mute); white-space: nowrap; } + .wo-h .wid { margin-left: auto; color: var(--faint); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; } .wo b { font-size: 13.5px; font-weight: 650; } .wo .rf { font-size: 12px; color: var(--mute); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; } .wo small { color: var(--mute); } @@ -349,8 +354,10 @@ document.documentElement.setAttribute("data-theme",t);})(); .chero .dw { max-width: 170px; margin: 0 auto; } .chero h1 { font-size: 27px; } .big { font-size: 42px; } - .fr { grid-template-columns: 58px minmax(0, 1fr) minmax(0, 0.9fr); } + .fr { grid-template-columns: 8.4ch 10.6ch 4.4ch minmax(0, 1fr); column-gap: 9px; } .fr .fo { display: none; } + .feed.cf .fr { grid-template-columns: 8.4ch 6.4ch 10.6ch minmax(0, 1fr); } + .feed.cf .fr .pr { display: none; } .pipe a { grid-template-columns: 120px minmax(0, 1fr) 34px; } .pareto div { grid-template-columns: 112px minmax(0, 1fr) 40px; } ol.ks li { grid-template-columns: 30px minmax(0, 1fr); } @@ -820,7 +827,7 @@ function audioTable(list,showOrder){const L=list.slice(-10).reverse();if(!L.leng function paretoHTML(by){const L=Object.entries(by).sort((a,b)=>b[1]-a[1]);if(!L.length)return '

No rejects yet.

';const mx=L[0][1]; return `
${L.map(([r,n])=>`
${esc(r)}${bar(n/mx,"bad")}${fI(n)}
`).join("")}
`;} function feedHTML(list,n){if(!list.length)return '

The presses are off for the night. Records appear here as they leave the press.

'; - return `
${list.slice(-n).reverse().map(r=>`
${fTs(r.t)}${esc(r.key)}${r.o}${r.ok?"✓ pass":"✕ "+esc(r.r)}
`).join("")}
`;} + return `
TimeStamperPressOrderResult
${list.slice(-n).reverse().map(r=>{const [sid,pid]=r.key.split(" · ");return `
${fTs(r.t)}${esc(sid)}${esc(pid)}${r.o}${r.ok?"✓ pass":"✕ "+esc(r.r)}
`;}).join("")}
`;} const kpi=(l,v,s,c)=>`
${l}${v}${s||" "}
`; /* ================= factory pages ================= */ @@ -1081,13 +1088,13 @@ function woById(id){const W=workOrders().find(w=>w.id===id);if(W)return W; function woCur(w){const n=w.steps.length;return w.status==="done"?n:w.status==="todo"?0:Math.min(n-1,Math.floor(w.pr*n));} function woTime(w){ if(w.status==="done")return "done "+fDT(w.end)+(w.who?" · "+w.who:""); - if(w.status==="doing")return "started "+fDT(w.start)+(w.end&&isFinite(w.end)?", done about "+fDT(w.end):""); + if(w.status==="doing")return "started "+fDT(w.start)+(w.end&&isFinite(w.end)?", done about "+fDT(w.end):"")+(w.who?" · "+w.who:""); if(w.prio&&w.plan)return "now, waiting for the lab"; return w.plan?(w.st==="press"?"planned ":"arrives about ")+fDT(w.plan)+(w.where?" on "+w.where:""):"later"; } const stChip=k=>`${WSTN[k][1]}`; function woCard(w){const c=woCur(w),n=w.steps.length; - return `
${w.id}${stChip(w.st)}${esc(w.who||"")}
${esc(w.title)}
${esc(w.ref)}${w.refT?" · "+esc(w.refT):""}
${w.status==="doing"?bar(w.pr,w.st==="press"?"acc":w.st==="maint"?"warn":"")+`Step ${c+1} of ${n}: ${esc(w.steps[c][0])}`:""}${esc(woTime(w))}
`;} + return `
${stChip(w.st)}${w.id}
${esc(w.title)}
${esc(w.ref)}${w.refT?" · "+esc(w.refT):""}
${w.status==="doing"?bar(w.pr,w.st==="press"?"acc":w.st==="maint"?"warn":"")+`Step ${c+1} of ${n}: ${esc(w.steps[c][0])}`:""}${esc(woTime(w))}
`;} PAGES.work={ layout:()=>`
Factory console

Work orders

What every station does now and next. On the floor each station shows its own list on a kiosk: the operator taps the access badge and scans what each step asks for, and every confirmation lands in the event log.

`, blocks(){ @@ -1182,7 +1189,7 @@ const CUST={ const g=STAGES[o.si].gate; let live=""; if(o.si===SI.run||(o.si>SI.run&&o.feed.length)){ - live=`

Your latest copies

${o.feed.length?`
${o.feed.slice(-8).reverse().map(r=>`
${fTs(r.t)}${r.ok?"#"+fI(r.copy):"-"}${esc(r.key.split(" · ").slice(0,2).join(" · "))}${r.ok?"✓ passed":"✕ "+esc(r.r)}
`).join("")}
`:'

The first copies appear here as they leave the press.

'}

Record key: stamper, press and the second it was pressed. It is printed on the sleeve as a code and opens the record passport.

+ live=`

Your latest copies

${o.feed.length?`
TimeCopyStamperPressResult
${o.feed.slice(-8).reverse().map(r=>{const [sid,pid]=r.key.split(" · ");return `
${fTs(r.t)}${r.ok?"#"+fI(r.copy):"-"}${esc(sid)}${esc(pid)}${r.ok?"✓ passed":"✕ "+esc(r.r)}
`;}).join("")}
`:'

The first copies appear here as they leave the press.

'}

Record key: stamper, press and the second it was pressed. It is printed on the sleeve as a code and opens the record passport.

Right now

${fI(o.good)}pressed and passed
${fI(o.rej)}caught by the camera
${fI(o.audio.filter(a=>a.rt<=S.t).length)}listening checks passed
${p&&p.tel.length?`

Press ${p.id} last cycle: ${f1(p.tel[p.tel.length-1].cyc)} s, steam ${f1(p.tel[p.tel.length-1].steam)} °C, cake ${f1(p.tel[p.tel.length-1].cake)} g.

`:""}${o.audio.some(a=>a.rt>S.t)?`

${(n=>n+(n>1?" copies":" copy"))(o.audio.filter(a=>a.rt>S.t).length)} at the listening station now.

`:""}
`;} return { cDisc:discSVG(o,250,spin)+"",