Site: light and dark mode for the landing page, plant overview and fleet calculator
Deploy test site / deploy (push) Successful in 36s

User, October 2026: add bright/dark mode to the site for both tools.

- Each page sets data-theme on <html> before first paint: the saved choice
  (localStorage "cnx-theme", shared by all cnxvinyl.lol pages) or, without
  one, the system setting (prefers-color-scheme), which it keeps following
  until the user picks a mode. Toggle: header button (sun / moon).
- Plant overview: light overrides for every SVG class; pipe colours are
  now CSS variables (--p-*), used by the pipes and the legend, darkened in
  light mode where the dark-mode tints were too pale (DI water, granulate,
  stamper sets, cooling return); press state text and the VEX zone fills
  use variables instead of inline hex, so they flip with the theme.
- Fleet calculator and landing page: light palettes; input, flag, badge
  and ladder-highlight colours moved to variables.

Checked headless in Edge: both system schemes pick the right mode, the
toggle persists across pages, computed colours flip (press text, zones,
pipes, legend), no text overflow in either mode, no JS errors.
This commit is contained in:
grabowski committed 2026-10-10 18:27:58 +07:00
1 parent c6bec71442
commit 6df1301fb9
3 files changed
+159 -27

No files matched your search

+40 -9
View File
@@ -5,6 +5,11 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<title>CNX Vinyl · press fleet calculator</title> <title>CNX Vinyl · press fleet calculator</title>
<script>
(function(){var t=null;try{t=localStorage.getItem("cnx-theme")}catch(e){}
if(t!=="light"&&t!=="dark")t=window.matchMedia&&matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";
document.documentElement.setAttribute("data-theme",t);})();
</script>
<style> <style>
:root { :root {
--bg: #0a0f1a; --panel: #0f1626; --panel2: #131c30; --stroke: #2a3a56; --bg: #0a0f1a; --panel: #0f1626; --panel2: #131c30; --stroke: #2a3a56;
@@ -12,6 +17,22 @@
--warn: #ffb547; --bad: #ff6b6b; --dspec: #c58bff; --dstd: #6aa8ff; --warn: #ffb547; --bad: #ff6b6b; --dspec: #c58bff; --dstd: #6aa8ff;
--classic: #3ddc84; --classic: #3ddc84;
} }
:root { color-scheme: dark; --input: #0a1120; --flagw: #1b1a12; --flago: #0f1a14; --flagb: #1d1214; --cur: #12261c;
--bv: #9fd0ff; --bvb: #2c4c73; --bd: #b5e8c7; --bdb: #2b5a3d; --be: #ffd59a; --beb: #6b5426; --bu: #e2c4ff; --bub: #563a73; }
html[data-theme=light] {
color-scheme: light;
--bg: #f4f6fa; --panel: #ffffff; --panel2: #eef2f8; --stroke: #cfd8e6; --line: #e4e9f1;
--fg: #1b2533; --mute: #5b6880; --accent: #0e8f4f; --warn: #c27c00; --bad: #c62828;
--dspec: #9b5de5; --dstd: #2f7de1; --classic: #1fae68;
--input: #ffffff; --flagw: #fff6e0; --flago: #e8f6ee; --flagb: #fdeaea; --cur: #e1f3e8;
--bv: #1f5f9e; --bvb: #9cc0e6; --bd: #1e7a45; --bdb: #9fd3b3; --be: #8a5a00; --beb: #e3c27a; --bu: #6b3fa0; --bub: #c9a8ec;
}
.themebtn {
position: absolute; top: 24px; right: 20px; background: var(--panel); color: var(--fg);
border: 1px solid var(--stroke); border-radius: 5px; padding: 6px 12px; font: inherit; font-size: 14px; cursor: pointer;
}
.themebtn:hover { border-color: var(--accent); }
header { position: relative; }
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { body {
margin: 0; background: var(--bg); color: var(--fg); margin: 0; background: var(--bg); color: var(--fg);
@@ -41,7 +62,7 @@
.row small, .hint { color: var(--mute); font-size: 12.5px; } .row small, .hint { color: var(--mute); font-size: 12.5px; }
.hint { margin: 6px 0 0; } .hint { margin: 6px 0 0; }
input[type=number] { input[type=number] {
width: 118px; background: #0a1120; color: var(--fg); border: 1px solid var(--stroke); width: 118px; background: var(--input); color: var(--fg); border: 1px solid var(--stroke);
border-radius: 4px; padding: 5px 7px; font: inherit; text-align: right; border-radius: 4px; padding: 5px 7px; font: inherit; text-align: right;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
} }
@@ -71,24 +92,24 @@
padding-top: 14px; border-bottom-color: var(--stroke); padding-top: 14px; border-bottom-color: var(--stroke);
} }
td.src { color: var(--mute); font-size: 12.5px; } td.src { color: var(--mute); font-size: 12.5px; }
.flag { border-left: 3px solid var(--warn); background: #1b1a12; padding: 7px 10px; margin: 8px 0 0; font-size: 14px; } .flag { border-left: 3px solid var(--warn); background: var(--flagw); padding: 7px 10px; margin: 8px 0 0; font-size: 14px; }
.flag.ok { border-color: var(--accent); background: #0f1a14; } .flag.ok { border-color: var(--accent); background: var(--flago); }
.flag.bad { border-color: var(--bad); background: #1d1214; } .flag.bad { border-color: var(--bad); background: var(--flagb); }
.bar { display: flex; height: 24px; border-radius: 4px; overflow: hidden; margin: 14px 0 8px; background: var(--panel2); } .bar { display: flex; height: 24px; border-radius: 4px; overflow: hidden; margin: 14px 0 8px; background: var(--panel2); }
.bar div { height: 100%; } .bar div { height: 100%; }
.legend { display: flex; flex-wrap: wrap; gap: 6px 18px; font-size: 13.5px; color: var(--mute); } .legend { display: flex; flex-wrap: wrap; gap: 6px 18px; font-size: 13.5px; color: var(--mute); }
.legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; } .legend i { display: inline-block; width: 10px; height: 10px; border-radius: 2px; margin-right: 6px; }
#ladT tbody tr { cursor: pointer; } #ladT tbody tr { cursor: pointer; }
#ladT tbody tr:hover td { background: var(--panel2); } #ladT tbody tr:hover td { background: var(--panel2); }
#ladT tbody tr.cur td { background: #12261c; } #ladT tbody tr.cur td { background: var(--cur); }
.badge { .badge {
display: inline-block; font-size: 11px; padding: 0 6px; border-radius: 3px; display: inline-block; font-size: 11px; padding: 0 6px; border-radius: 3px;
border: 1px solid var(--stroke); color: var(--mute); white-space: nowrap; border: 1px solid var(--stroke); color: var(--mute); white-space: nowrap;
} }
.badge.vendor { color: #9fd0ff; border-color: #2c4c73; } .badge.vendor { color: var(--bv); border-color: var(--bvb); }
.badge.doc { color: #b5e8c7; border-color: #2b5a3d; } .badge.doc { color: var(--bd); border-color: var(--bdb); }
.badge.est { color: #ffd59a; border-color: #6b5426; } .badge.est { color: var(--be); border-color: var(--beb); }
.badge.user { color: #e2c4ff; border-color: #563a73; } .badge.user { color: var(--bu); border-color: var(--bub); }
#asT input[type=number] { width: 92px; } #asT input[type=number] { width: 92px; }
#asT tr.chg input { border-color: var(--warn); } #asT tr.chg input { border-color: var(--warn); }
.btns { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; } .btns { display: flex; gap: 10px; flex-wrap: wrap; margin-top: 12px; }
@@ -109,6 +130,7 @@
</head> </head>
<body> <body>
<header> <header>
<button class="themebtn" id="themeBtn" type="button">Light mode</button>
<h1>Press fleet calculator</h1> <h1>Press fleet calculator</h1>
<span class="tag">planning estimates · not a quote</span> <span class="tag">planning estimates · not a quote</span>
<p class="lead"> <p class="lead">
@@ -591,6 +613,15 @@ $("copy").addEventListener("click", () => {
if (navigator.clipboard) navigator.clipboard.writeText(location.href).then(done, done); else done(); if (navigator.clipboard) navigator.clipboard.writeText(location.href).then(done, done); else done();
}); });
render(); render();
(function(){
var b=document.getElementById("themeBtn"),de=document.documentElement;
function set(t,save){de.setAttribute("data-theme",t);if(save){try{localStorage.setItem("cnx-theme",t)}catch(e){}}
b.textContent=t==="light"?"☾ Dark mode":"☀ Light mode";b.title="Switch to "+(t==="light"?"dark":"light")+" mode";}
set(de.getAttribute("data-theme")||"dark",false);
b.addEventListener("click",function(){set(de.getAttribute("data-theme")==="light"?"dark":"light",true);b.blur();});
var mq=window.matchMedia&&matchMedia("(prefers-color-scheme: light)");
if(mq&&mq.addEventListener)mq.addEventListener("change",function(e){var s=null;try{s=localStorage.getItem("cnx-theme")}catch(_){}if(!s)set(e.matches?"light":"dark",false);});
})();
</script> </script>
</body> </body>
</html> </html>
+90 -18
View File
@@ -4,10 +4,17 @@
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<title>CNX Vinyl · Plant overview (simulated SCADA)</title> <title>CNX Vinyl · Plant overview (simulated SCADA)</title>
<script>
(function(){var t=null;try{t=localStorage.getItem("cnx-theme")}catch(e){}
if(t!=="light"&&t!=="dark")t=window.matchMedia&&matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";
document.documentElement.setAttribute("data-theme",t);})();
</script>
<style> <style>
:root{ :root{
--bg:#0a0f1a;--panel:#0f1626;--box:#121a2b;--stroke:#2a3a56;--head:#1a2640; --bg:#0a0f1a;--panel:#0f1626;--box:#121a2b;--stroke:#2a3a56;--head:#1a2640;
--fg:#d6deeb;--mute:#8a97b0;--run:#3ddc84;--idle:#6b7a90;--warn:#ffbf1f;--alarm:#ff3b3b; --fg:#d6deeb;--mute:#8a97b0;--run:#3ddc84;--idle:#6b7a90;--warn:#ffbf1f;--alarm:#ff3b3b;
--ink:#e6ecf7;--z0:#1b2438;--z1:#3a2a1c;--z2:#4a2e1a;--z3:#5a301a;
--p-steam:#ff5a3c;--p-cond:#c96a4e;--p-cw:#3aa0ff;--p-cwr:#7fc0ff;--p-hyd:#f5a623;--p-chw:#2e7fd0;--p-di:#9ad7ff;--p-mat:#b9c2d0;--p-reg:#8ea36f;--p-col:#c86bff;--p-metal:#c0c8d6;--p-scrap:#c2b280;--p-route:#3a4d70;--p-lbl:#e6ecf7;
--mono:ui-monospace,"Cascadia Mono",Consolas,"Liberation Mono",monospace; --mono:ui-monospace,"Cascadia Mono",Consolas,"Liberation Mono",monospace;
} }
*{box-sizing:border-box;margin:0;padding:0} *{box-sizing:border-box;margin:0;padding:0}
@@ -107,8 +114,8 @@ footer{flex:0 0 clamp(150px,26vh,210px);display:grid;grid-template-columns:1.1fr
.thin{stroke-width:1.4;stroke-dasharray:3 7} .thin{stroke-width:1.4;stroke-dasharray:3 7}
@keyframes dash{to{stroke-dashoffset:-16}} @keyframes dash{to{stroke-dashoffset:-16}}
.paused .flow,.paused .bub,.paused .convd,.paused .screw{animation-play-state:paused} .paused .flow,.paused .bub,.paused .convd,.paused .screw{animation-play-state:paused}
.steam{stroke:#ff5a3c}.cond{stroke:#c96a4e}.cw{stroke:#3aa0ff}.cwr{stroke:#7fc0ff}.hyd{stroke:#f5a623}.chw{stroke:#2e7fd0}.di{stroke:#9ad7ff} .steam{stroke:var(--p-steam)}.cond{stroke:var(--p-cond)}.cw{stroke:var(--p-cw)}.cwr{stroke:var(--p-cwr)}.hyd{stroke:var(--p-hyd)}.chw{stroke:var(--p-chw)}.di{stroke:var(--p-di)}
.mat{stroke:#b9c2d0}.reg{stroke:#8ea36f}.col{stroke:#c86bff}.metal{stroke:#c0c8d6}.scrap{stroke:#c2b280}.route{stroke:#3a4d70}.lbl{stroke:#e6ecf7} .mat{stroke:var(--p-mat)}.reg{stroke:var(--p-reg)}.col{stroke:var(--p-col)}.metal{stroke:var(--p-metal)}.scrap{stroke:var(--p-scrap)}.route{stroke:var(--p-route)}.lbl{stroke:var(--p-lbl)}
.conv{stroke:#1c2740;stroke-width:14;fill:none} .conv{stroke:#1c2740;stroke-width:14;fill:none}
.convd{stroke:#2e4066;stroke-width:14;stroke-dasharray:3 9;fill:none;animation:dash 1.6s linear infinite} .convd{stroke:#2e4066;stroke-width:14;stroke-dasharray:3 9;fill:none;animation:dash 1.6s linear infinite}
.gate polygon{fill:#1a2640;stroke:#ffbf1f;stroke-width:1.5} .gate polygon{fill:#1a2640;stroke:#ffbf1f;stroke-width:1.5}
@@ -142,6 +149,61 @@ footer{flex:0 0 clamp(150px,26vh,210px);display:grid;grid-template-columns:1.1fr
.hopper{fill:#121a2b;stroke:#2a3a56} .hopper{fill:#121a2b;stroke:#2a3a56}
.zone{stroke:#2a3a56;stroke-width:1} .zone{stroke:#2a3a56;stroke-width:1}
.screw{stroke:#6d7d99;stroke-width:2;stroke-dasharray:6 6;animation:dash 0.9s linear infinite;fill:none} .screw{stroke:#6d7d99;stroke-width:2;stroke-dasharray:6 6;animation:dash 0.9s linear infinite;fill:none}
/* Light mode: same drawing, light paper. Toggle in the header; the choice is
shared with the other cnxvinyl.lol pages (localStorage "cnx-theme"). */
:root{color-scheme:dark}
html[data-theme=light]{color-scheme:light;
--bg:#eef2f7;--panel:#ffffff;--box:#ffffff;--stroke:#c3cede;--head:#e3e9f3;
--fg:#1b2533;--mute:#5b6880;--run:#16a35a;--idle:#8a97a8;--warn:#c98a00;--alarm:#e0352b;
--ink:#1b2533;--z0:#e9eef5;--z1:#f6dcc8;--z2:#f2c6a5;--z3:#eeae85;--p-steam:#e2462a;--p-cond:#b0573c;--p-cw:#1f84e0;--p-cwr:#4f9be0;--p-hyd:#d48600;--p-chw:#2468b0;--p-di:#2fa8d8;--p-mat:#7d8aa0;--p-reg:#6a8740;--p-col:#a24de0;--p-metal:#7f8ea6;--p-scrap:#97864a;--p-route:#a9b8cf;--p-lbl:#33415a;}
html[data-theme=light] button{background:#f3f6fb}
html[data-theme=light] button:hover{background:#e4ebf6}
html[data-theme=light] button.on{background:#d6e6fb;border-color:#3b74b8}
html[data-theme=light] button.demo{border-color:#d9b44a;color:#8a5a00}
html[data-theme=light] .mapctl button{background:rgba(255,255,255,.92)}
html[data-theme=light] .panel p,html[data-theme=light] .panel ul,html[data-theme=light] .legend{color:#33415a}
html[data-theme=light] footer{box-shadow:none}
html[data-theme=light] .mbox{fill:#ffffff;stroke:#c3cede}
html[data-theme=light] .machine:hover .mbox,html[data-theme=light] .machine:hover .frame{stroke:#3b74b8}
html[data-theme=light] .machine.sel .mbox,html[data-theme=light] .machine.sel .frame{stroke:#1f5fbf}
html[data-theme=light] .mhead{fill:#e3e9f3}
html[data-theme=light] .mtitle{fill:#1b2533}
html[data-theme=light] .msub,html[data-theme=light] .small{fill:#5b6880}
html[data-theme=light] .mtag,html[data-theme=light] .ctxt{fill:#2c3a52}
html[data-theme=light] .lane{fill:#7a879c}
html[data-theme=light] .roomlbl{fill:#5f78a0}
html[data-theme=light] .room{fill:rgba(60,90,140,.05);stroke:#a9bbd6}
html[data-theme=light] .lamp{fill:#a3adbb}
html[data-theme=light] .lamp.run{fill:#16a35a}
html[data-theme=light] .lamp.warn{fill:#e8a400}
html[data-theme=light] .lamp.alarm{fill:#e0352b}
html[data-theme=light] .pipe{opacity:.32}
html[data-theme=light] .conv{stroke:#dfe5ee}
html[data-theme=light] .convd{stroke:#b5c1d4}
html[data-theme=light] .gate polygon{fill:#fff6dc;stroke:#d99a00}
html[data-theme=light] .gate.ok polygon{stroke:#16a35a;fill:#e2f5e9}
html[data-theme=light] .gate text{fill:#5b6880}
html[data-theme=light] .cellr,html[data-theme=light] .pos{fill:#eef4fa;stroke:#8fa9c6}
html[data-theme=light] .cell.on .cellr{stroke:#16a35a}
html[data-theme=light] .cell.done .cellr{stroke:#d99a00}
html[data-theme=light] .liquid{fill:#bfe0f2}
html[data-theme=light] .cell.on .liquid{fill:#bde8d1}
html[data-theme=light] .bub{fill:#3f97cc}
html[data-theme=light] .pos.on{stroke:#16a35a;fill:#e2f5e9}
html[data-theme=light] .thk,html[data-theme=light] .pbar{fill:#e3e9f3}
html[data-theme=light] .thkv{fill:#4d6a8a}
html[data-theme=light] .pbarv{fill:#16a35a}
html[data-theme=light] .platen{fill:#c7d1df;stroke:#8e9db4}
html[data-theme=light] .platen.heat{fill:#e0704f}
html[data-theme=light] .platen.cool{fill:#6fa3d8}
html[data-theme=light] .frame{fill:#f7f9fc;stroke:#8e9db4}
html[data-theme=light] .silo,html[data-theme=light] .hopper{fill:#ffffff;stroke:#c3cede}
html[data-theme=light] .fill{fill:#c9d0db}
html[data-theme=light] .fill.reg{fill:#c5d3a8}
html[data-theme=light] .fill.col{fill:#dcc2ea}
html[data-theme=light] .zone{stroke:#c3cede}
html[data-theme=light] .screw{stroke:#7d8aa0}
</style> </style>
</head> </head>
<body> <body>
@@ -158,6 +220,7 @@ footer{flex:0 0 clamp(150px,26vh,210px);display:grid;grid-template-columns:1.1fr
<button id="btnLot" class="demo" title="A new PVC lot arrives: the lab quarantines it until its tests pass (about 2 h; watch at 60×)">New PVC lot</button> <button id="btnLot" class="demo" title="A new PVC lot arrives: the lab quarantines it until its tests pass (about 2 h; watch at 60×)">New PVC lot</button>
<button id="btnRepress" class="demo" title="Repress order: stampers from the archived mothers, no artist gates (watch at 60× to 600×)">Repress</button> <button id="btnRepress" class="demo" title="Repress order: stampers from the archived mothers, no artist gates (watch at 60× to 600×)">Repress</button>
<button id="btnInfo" title="Station detail, event log and legend">Info</button> <button id="btnInfo" title="Station detail, event log and legend">Info</button>
<button id="themeBtn" title="Switch light / dark mode">☀</button>
</div> </div>
<div class="kpis"> <div class="kpis">
<div class="kpi"><b id="kClock">D1 08:00:00</b><small>sim clock</small></div> <div class="kpi"><b id="kClock">D1 08:00:00</b><small>sim clock</small></div>
@@ -204,18 +267,18 @@ footer{flex:0 0 clamp(150px,26vh,210px);display:grid;grid-template-columns:1.1fr
<div class="panel" id="logPanel"><h2>Event log</h2><div class="log" id="log"></div></div> <div class="panel" id="logPanel"><h2>Event log</h2><div class="log" id="log"></div></div>
<div class="panel" id="legendPanel"><h2>Legend</h2> <div class="panel" id="legendPanel"><h2>Legend</h2>
<div class="legend"> <div class="legend">
<div><i style="border-color:#ff5a3c"></i>Saturated steam 10 bar</div> <div><i style="border-color:var(--p-steam)"></i>Saturated steam 10 bar</div>
<div><i class="d" style="border-color:#c96a4e"></i>Condensate return</div> <div><i class="d" style="border-color:var(--p-cond)"></i>Condensate return</div>
<div><i style="border-color:#3aa0ff"></i>Press cooling water</div> <div><i style="border-color:var(--p-cw)"></i>Press cooling water</div>
<div><i class="d" style="border-color:#7fc0ff"></i>Cooling water return</div> <div><i class="d" style="border-color:var(--p-cwr)"></i>Cooling water return</div>
<div><i style="border-color:#f5a623"></i>Hydraulic oil 180 bar</div> <div><i style="border-color:var(--p-hyd)"></i>Hydraulic oil 180 bar</div>
<div><i class="d" style="border-color:#2e7fd0"></i>Galvanics cooling 12 to 20 °C</div> <div><i class="d" style="border-color:var(--p-chw)"></i>Galvanics cooling 12 to 20 °C</div>
<div><i class="d" style="border-color:#9ad7ff"></i>DI water</div> <div><i class="d" style="border-color:var(--p-di)"></i>DI water</div>
<div><i class="d" style="border-color:#b9c2d0"></i>PVC granulate</div> <div><i class="d" style="border-color:var(--p-mat)"></i>PVC granulate</div>
<div><i class="d" style="border-color:#8ea36f"></i>Regrind</div> <div><i class="d" style="border-color:var(--p-reg)"></i>Regrind</div>
<div><i class="d" style="border-color:#c86bff"></i>Colour compound</div> <div><i class="d" style="border-color:var(--p-col)"></i>Colour compound</div>
<div><i class="d" style="border-color:#c0c8d6"></i>Stamper sets to presses</div> <div><i class="d" style="border-color:var(--p-metal)"></i>Stamper sets to presses</div>
<div><i class="d" style="border-color:#c2b280"></i>Edge trim and rejects</div> <div><i class="d" style="border-color:var(--p-scrap)"></i>Edge trim and rejects</div>
<div class="note">Lamps: green run · grey idle · amber hold, change or waiting · red alarm. Yellow diamonds are the three artist approval gates, with the number of titles waiting at each. Two shifts, 06:00 to 22:00: presses and operator steps stop at night, plating already in the bath finishes. Discs on the metal path: gold lacquer, silver silvered lacquer, grey father or mother, ringed stamper.</div> <div class="note">Lamps: green run · grey idle · amber hold, change or waiting · red alarm. Yellow diamonds are the three artist approval gates, with the number of titles waiting at each. Two shifts, 06:00 to 22:00: presses and operator steps stop at night, plating already in the bath finishes. Discs on the metal path: gold lacquer, silver silvered lacquer, grey father or mother, ringed stamper.</div>
</div> </div>
</div> </div>
@@ -593,7 +656,7 @@ function drawPress(m){const g=el('g',{class:'machine',id:'m_'+m.id});const {x,y,
m.topSt=el('rect',{x:px+8,y:y+54,width:pw-16,height:3,fill:'#b9c2cc'},g); m.topSt=el('rect',{x:px+8,y:y+54,width:pw-16,height:3,fill:'#b9c2cc'},g);
m.cake=el('g',{visibility:'hidden'},g);el('circle',{cx:x+w/2,cy:y+90,r:7,fill:'#3d3a33',stroke:'#8a7a55'},m.cake); m.cake=el('g',{visibility:'hidden'},g);el('circle',{cx:x+w/2,cy:y+90,r:7,fill:'#3d3a33',stroke:'#8a7a55'},m.cake);
m.pY=[y+38,y+80]; m.pY=[y+38,y+80];
m.stateEl=el('text',{x:x+w/2,y:y+138,class:'mtag','text-anchor':'middle',style:'font-weight:700;fill:#e6ecf7'},g,'OFF'); m.stateEl=el('text',{x:x+w/2,y:y+138,class:'mtag','text-anchor':'middle',style:'font-weight:700;fill:var(--ink)'},g,'OFF');
m.tagEls=[0,1,2].map(i=>el('text',{x:x+7,y:y+154+i*13,class:'mtag'},g,'')); m.tagEls=[0,1,2].map(i=>el('text',{x:x+7,y:y+154+i*13,class:'mtag'},g,''));
m.g=g;g.addEventListener('click',()=>select(m.id));} m.g=g;g.addEventListener('click',()=>select(m.id));}
function drawVex(m){const g=el('g',{class:'machine',id:'m_'+m.id});const {x,y,w,h}=m; function drawVex(m){const g=el('g',{class:'machine',id:'m_'+m.id});const {x,y,w,h}=m;
@@ -606,7 +669,7 @@ function drawVex(m){const g=el('g',{class:'machine',id:'m_'+m.id});const {x,y,w,
el('path',{d:`M${x+56},${y+50} H${x+84} V${y+60} L${x+76},${y+74} H${x+64} L${x+56},${y+60} Z`,class:'hopper'},g); el('path',{d:`M${x+56},${y+50} H${x+84} V${y+60} L${x+76},${y+74} H${x+64} L${x+56},${y+60} Z`,class:'hopper'},g);
el('text',{x:x+8,y:y+82,class:'small'},g,'◀ cake A'); el('text',{x:x+8,y:y+82,class:'small'},g,'◀ cake A');
el('text',{x:x+w-8,y:y+82,class:'small','text-anchor':'end'},g,'cake B ▶'); el('text',{x:x+w-8,y:y+82,class:'small','text-anchor':'end'},g,'cake B ▶');
m.zoneEls=[0,1,2].map(i=>el('rect',{x:x+10+i*40,y:y+88,width:38,height:26,class:'zone',fill:'#1b2438'},g)); m.zoneEls=[0,1,2].map(i=>el('rect',{x:x+10+i*40,y:y+88,width:38,height:26,class:'zone',style:'fill:var(--z0)'},g));
m.zoneT=[0,1,2].map(i=>el('text',{x:x+29+i*40,y:y+105,class:'ctxt','text-anchor':'middle'},g,'')); m.zoneT=[0,1,2].map(i=>el('text',{x:x+29+i*40,y:y+105,class:'ctxt','text-anchor':'middle'},g,''));
el('line',{x1:x+10,y1:y+120,x2:x+w-10,y2:y+120,class:'screw'},g); el('line',{x1:x+10,y1:y+120,x2:x+w-10,y2:y+120,class:'screw'},g);
m.g=g;g.addEventListener('click',()=>select(m.id));} m.g=g;g.addEventListener('click',()=>select(m.id));}
@@ -788,7 +851,7 @@ function renderTags(){
m.fillEl.setAttribute('y',(m.y+m.h-30-hh).toFixed(1));m.fillEl.setAttribute('height',hh.toFixed(1));m.lvlEl.textContent=f0(l*100)+' %';m.lamp.setAttribute('class','lamp '+(l<0.1?'alarm':l<0.25?'warn':'run'));}); m.fillEl.setAttribute('y',(m.y+m.h-30-hh).toFixed(1));m.fillEl.setAttribute('height',hh.toFixed(1));m.lvlEl.textContent=f0(l*100)+' %';m.lamp.setAttribute('class','lamp '+(l<0.1?'alarm':l<0.25?'warn':'run'));});
PRESS.forEach(P=>{const m=M[P.id];const T=P.set&&P.set.T; PRESS.forEach(P=>{const m=M[P.id];const T=P.set&&P.set.T;
m.stateEl.textContent=pressLabel(P);const amber=P.waiting||['CHANGE','UNMOUNT'].includes(P.state)||P.mode==='tp'; m.stateEl.textContent=pressLabel(P);const amber=P.waiting||['CHANGE','UNMOUNT'].includes(P.state)||P.mode==='tp';
m.stateEl.style.fill=P.state==='NOPVC'||P.state==='STOP'?'#ff3b3b':amber?'#ffbf1f':(P.state==='IDLE'||P.state==='OFF')?'#8a97b0':'#e6ecf7'; m.stateEl.style.fill=P.state==='NOPVC'||P.state==='STOP'?'var(--alarm)':amber?'var(--warn)':(P.state==='IDLE'||P.state==='OFF')?'var(--mute)':'var(--ink)';
m.tagEls[0].textContent=running(P)?'F '+f0(P.force)+' t · '+f0(P.moldT)+' °C':P.state==='STOP'?P.stopWhy:P.state==='OFF'?'night: shift 06:00':'mould '+f0(P.moldT)+' °C'; m.tagEls[0].textContent=running(P)?'F '+f0(P.force)+' t · '+f0(P.moldT)+' °C':P.state==='STOP'?P.stopWhy:P.state==='OFF'?'night: shift 06:00':'mould '+f0(P.moldT)+' °C';
m.tagEls[1].textContent=T?T.id+' · '+P.set.k:(P.state==='OFF'?'':'waiting for a set'); m.tagEls[1].textContent=T?T.id+' · '+P.set.k:(P.state==='OFF'?'':'waiting for a set');
m.tagEls[2].textContent=!T?'':P.mode==='tp'?'test press '+(TP_N-P.tpLeft)+'/'+TP_N:P.mode==='fa'?'first article':'run '+T.cycles+'/'+T.target; m.tagEls[2].textContent=!T?'':P.mode==='tp'?'test press '+(TP_N-P.tpLeft)+'/'+TP_N:P.mode==='fa'?'first article':'run '+T.cycles+'/'+T.target;
@@ -796,7 +859,7 @@ function renderTags(){
['tA','tB','tC1','tC2'].forEach((id,i)=>lampSet(id,running(PRESS[i])?'run':'idle')); ['tA','tB','tC1','tC2'].forEach((id,i)=>lampSet(id,running(PRESS[i])?'run':'idle'));
const dRun=PRESS.slice(0,2).some(running); const dRun=PRESS.slice(0,2).some(running);
M.vex.zoneT.forEach((t,i)=>t.textContent=f0(dRun?jit([132,146,156][i],1):[92,96,98][i])+'°'); M.vex.zoneT.forEach((t,i)=>t.textContent=f0(dRun?jit([132,146,156][i],1):[92,96,98][i])+'°');
M.vex.zoneEls.forEach((z,i)=>z.setAttribute('fill',dRun?['#3a2a1c','#4a2e1a','#5a301a'][i]:'#1b2438'));lampSet('vex',dRun?'run':'idle');lampSet('ex35',S.ex35?'run':'idle'); M.vex.zoneEls.forEach((z,i)=>{z.style.fill=dRun?'var(--z'+(i+1)+')':'var(--z0)';});lampSet('vex',dRun?'run':'idle');lampSet('ex35',S.ex35?'run':'idle');
M.pppu.pos.forEach((p,i)=>{const w=ST.pppu.slots[i];p.r.setAttribute('class','pos'+(w?' on':''));p.a.textContent=w?w.it.T.sid+w.it.side+' 20 A':'free';p.b.textContent=w?dur(w.end-S.t):'';}); M.pppu.pos.forEach((p,i)=>{const w=ST.pppu.slots[i];p.r.setAttribute('class','pos'+(w?' on':''));p.a.textContent=w?w.it.T.sid+w.it.side+' 20 A':'free';p.b.textContent=w?dur(w.end-S.t):'';});
lampSet('pppu',ST.pppu.slots.some(Boolean)?'run':ST.pppu.q.length?'warn':'idle'); lampSet('pppu',ST.pppu.slots.some(Boolean)?'run':ST.pppu.q.length?'warn':'idle');
[0,1].forEach(u=>{const m=M['pdpu'+(u+1)];let any=false,done=false; [0,1].forEach(u=>{const m=M['pdpu'+(u+1)];let any=false,done=false;
@@ -957,6 +1020,15 @@ function frame(now){const dt=Math.min(0.1,(now-last)/1000);last=now;
requestAnimationFrame(frame); requestAnimationFrame(frame);
window.__plant={S,TITLES,RACK,PRESS,CELLS,ST,PLATEQ,M,tick}; window.__plant={S,TITLES,RACK,PRESS,CELLS,ST,PLATEQ,M,tick};
})(); })();
(function(){
var b=document.getElementById("themeBtn"),de=document.documentElement;
function set(t,save){de.setAttribute("data-theme",t);if(save){try{localStorage.setItem("cnx-theme",t)}catch(e){}}
b.textContent=t==="light"?"☾":"☀";b.title="Switch to "+(t==="light"?"dark":"light")+" mode";}
set(de.getAttribute("data-theme")||"dark",false);
b.addEventListener("click",function(){set(de.getAttribute("data-theme")==="light"?"dark":"light",true);b.blur();});
var mq=window.matchMedia&&matchMedia("(prefers-color-scheme: light)");
if(mq&&mq.addEventListener)mq.addEventListener("change",function(e){var s=null;try{s=localStorage.getItem("cnx-theme")}catch(_){}if(!s)set(e.matches?"light":"dark",false);});
})();
</script> </script>
</body> </body>
</html> </html>
+29
View File
@@ -5,11 +5,28 @@
<meta name="viewport" content="width=device-width, initial-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex, nofollow"> <meta name="robots" content="noindex, nofollow">
<title>CNX Vinyl · internal test site</title> <title>CNX Vinyl · internal test site</title>
<script>
(function(){var t=null;try{t=localStorage.getItem("cnx-theme")}catch(e){}
if(t!=="light"&&t!=="dark")t=window.matchMedia&&matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";
document.documentElement.setAttribute("data-theme",t);})();
</script>
<style> <style>
:root { :root {
--bg: #0a0f1a; --panel: #0f1626; --stroke: #2a3a56; --bg: #0a0f1a; --panel: #0f1626; --stroke: #2a3a56;
--fg: #d6deeb; --mute: #8a97b0; --accent: #3ddc84; --fg: #d6deeb; --mute: #8a97b0; --accent: #3ddc84;
} }
:root { color-scheme: dark; }
html[data-theme=light] {
color-scheme: light;
--bg: #f4f6fa; --panel: #ffffff; --stroke: #cfd8e6;
--fg: #1b2533; --mute: #5b6880; --accent: #0e8f4f;
}
main { position: relative; }
.themebtn {
position: absolute; top: 48px; right: 20px; background: var(--panel); color: var(--fg);
border: 1px solid var(--stroke); border-radius: 5px; padding: 6px 12px; font: inherit; font-size: 14px; cursor: pointer;
}
.themebtn:hover { border-color: var(--accent); }
* { box-sizing: border-box; } * { box-sizing: border-box; }
body { body {
margin: 0; background: var(--bg); color: var(--fg); margin: 0; background: var(--bg); color: var(--fg);
@@ -41,6 +58,7 @@
</head> </head>
<body> <body>
<main> <main>
<button class="themebtn" id="themeBtn" type="button">Light mode</button>
<h1>CNX Vinyl</h1> <h1>CNX Vinyl</h1>
<span class="tag">internal test site</span> <span class="tag">internal test site</span>
<p class="lead"> <p class="lead">
@@ -79,5 +97,16 @@
<footer>Built from <code>site/</code> in the cnx_vinyl repository.</footer> <footer>Built from <code>site/</code> in the cnx_vinyl repository.</footer>
</main> </main>
<script>
(function(){
var b=document.getElementById("themeBtn"),de=document.documentElement;
function set(t,save){de.setAttribute("data-theme",t);if(save){try{localStorage.setItem("cnx-theme",t)}catch(e){}}
b.textContent=t==="light"?"☾ Dark mode":"☀ Light mode";b.title="Switch to "+(t==="light"?"dark":"light")+" mode";}
set(de.getAttribute("data-theme")||"dark",false);
b.addEventListener("click",function(){set(de.getAttribute("data-theme")==="light"?"dark":"light",true);b.blur();});
var mq=window.matchMedia&&matchMedia("(prefers-color-scheme: light)");
if(mq&&mq.addEventListener)mq.addEventListener("change",function(e){var s=null;try{s=localStorage.getItem("cnx-theme")}catch(_){}if(!s)set(e.matches?"light":"dark",false);});
})();
</script>
</body> </body>
</html> </html>