Deploy test site / deploy (push) Successful in 55s
User, 19 Oct 2026: build a high-end investor pitch deck; the whole plant, web slides on the test site with presenter mode plus a PDF, 12 to 15 slides, both plans, for a BOI / bank / institutional reader. - docs/pitch/index.html (site /pitch/): title, opportunity, the chain under one roof, how a record is made (9 stages, 3 gates, traceability, ERPNext), platform, site and building, sustainability and region, investment A vs B by block with the six levers (no contingency in either price), operating picture with the adverse case, ten-year cash and returns (calculator presets), BOI promotion and the 3:1 debt-to-equity condition (financing structure to be agreed), schedule, risks, team and vendors, the ask (phase 0 then tranches). - Every figure carries a source line (business plans draft 0.3 and the owning docs); speaker notes per slide; P opens a synced presenter window, O the overview, F fullscreen, #n deep links. - img/: six WebP pictures (calculator renders, order tool, fan funding, record passport); CNX-Vinyl-Investor-Deck.pdf, 15 pages 16:9, images re-encoded to 1.5 MB. - site/build.sh copies docs/pitch/ to /pitch/ (new DIRS list); the deploy also triggers on docs/pitch/**; landing page and README entries. - Checks: 15 slides at 1920/1440/1280/390 px without overflow, navigation, presenter sync both ways, PDF pages and text, no em dashes.
1272 lines
103 KiB
HTML
1272 lines
103 KiB
HTML
<!doctype html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="utf-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||
<meta name="robots" content="noindex">
|
||
<title>CNX Vinyl: investment proposal</title>
|
||
<style>
|
||
:root{
|
||
--bg:#0d0f14; --panel:#131720; --panel2:#171c26;
|
||
--line:rgba(236,230,218,.15); --line2:rgba(236,230,218,.08);
|
||
--tx:#efe9dd; --tx2:#d2cbbd; --mu:#a59e91; --dim:#827b70;
|
||
--ac:#e8b04a; --acs:rgba(232,176,74,.14); --b:#8eaac3; --bs:rgba(142,170,195,.16);
|
||
--neg:#d6876c; --negs:rgba(214,135,108,.14);
|
||
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
|
||
--sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
|
||
}
|
||
*{box-sizing:border-box}
|
||
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
|
||
html,body{margin:0;height:100%;background:#07080b;overflow:hidden}
|
||
body{font-family:var(--sans);color:var(--tx);-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
||
button{font:inherit;color:inherit}
|
||
|
||
/* ---------- canvas: 1920 x 1080, scaled to fit, letterboxed ---------- */
|
||
.stage{position:fixed;left:0;top:0;width:1920px;height:1080px;transform-origin:0 0;background:var(--bg);overflow:hidden;box-shadow:0 0 0 1px rgba(255,255,255,.03)}
|
||
.slide{position:absolute;inset:0;width:1920px;height:1080px;padding:80px 104px 150px;display:flex;flex-direction:column;background:var(--bg);
|
||
font-variant-numeric:tabular-nums lining-nums;opacity:0;visibility:hidden;transform:translateX(26px);
|
||
transition:opacity .42s ease,transform .42s ease,visibility 0s linear .42s}
|
||
.slide.active{opacity:1;visibility:visible;transform:none;transition:opacity .42s ease,transform .42s ease,visibility 0s}
|
||
.slide.before{transform:translateX(-26px)}
|
||
@media (prefers-reduced-motion:reduce){.slide,.slide.active,.slide.before{transition:none;transform:none}}
|
||
|
||
/* vinyl groove motif: fine concentric rings, faded out with a mask */
|
||
.slide.grooves::before{content:"";position:absolute;inset:0;pointer-events:none;
|
||
background:repeating-radial-gradient(circle at var(--gx,104%) var(--gy,108%),rgba(232,176,74,.07) 0 1px,transparent 1.4px 9px);
|
||
-webkit-mask-image:radial-gradient(circle at var(--gx,104%) var(--gy,108%),#000 0,#000 18%,transparent var(--gr,44%));
|
||
mask-image:radial-gradient(circle at var(--gx,104%) var(--gy,108%),#000 0,#000 18%,transparent var(--gr,44%))}
|
||
.slide > *{position:relative}
|
||
|
||
/* ---------- type ---------- */
|
||
.kicker{font-size:17px;letter-spacing:.2em;text-transform:uppercase;color:var(--ac);font-weight:600;display:flex;align-items:center;gap:14px;height:24px}
|
||
.kicker .no{color:var(--dim);font-weight:500}
|
||
.disc{width:22px;height:22px;border-radius:50%;flex:none;
|
||
background:radial-gradient(circle,#0d0f14 0 7%,var(--ac) 8% 30%,#0d0f14 31% 35%,transparent 36%),
|
||
repeating-radial-gradient(circle,#2b2f37 0 1px,#16191f 1px 2.2px);box-shadow:0 0 0 1px rgba(255,255,255,.1)}
|
||
h1{font-family:var(--serif);font-weight:500;font-size:132px;line-height:1.12;margin:28px 0 0;letter-spacing:-.01em}
|
||
h2{font-family:var(--serif);font-weight:500;font-size:54px;line-height:1.2;margin:14px 0 0;max-width:1712px;letter-spacing:-.005em}
|
||
h3{font-size:16px;letter-spacing:.16em;text-transform:uppercase;color:var(--mu);font-weight:600;margin:0 0 14px}
|
||
p{margin:0}
|
||
.lede{font-size:24px;line-height:1.42;color:var(--tx2);margin-top:14px;max-width:1500px}
|
||
.bd{flex:1;min-height:0;margin-top:40px;display:grid;gap:48px}
|
||
.t{font-size:20px;line-height:1.42;color:var(--tx2)}
|
||
.t b,.t strong{color:var(--tx);font-weight:600}
|
||
.s{font-size:17px;line-height:1.4;color:var(--mu)}
|
||
.e{font-style:normal;font-size:.78em;letter-spacing:.04em;color:var(--dim);margin-left:.25em}
|
||
.ac{color:var(--ac)} .bc{color:var(--b)} .neg{color:var(--neg)}
|
||
.serif{font-family:var(--serif)}
|
||
|
||
/* source line and running footer */
|
||
.src{position:absolute;left:104px;right:104px;bottom:64px;font-size:15px;line-height:1.4;color:var(--dim)}
|
||
.src b{color:var(--mu);font-weight:600}
|
||
.ft{position:absolute;left:104px;right:104px;bottom:24px;height:30px;display:flex;justify-content:space-between;align-items:flex-end;
|
||
font-size:14px;letter-spacing:.08em;color:var(--dim);border-top:1px solid var(--line2);text-transform:uppercase}
|
||
.ft .pg{color:var(--mu);letter-spacing:.12em}
|
||
|
||
/* building blocks */
|
||
.card{background:linear-gradient(180deg,rgba(255,255,255,.035),rgba(255,255,255,.015));border:1px solid var(--line2);border-radius:14px;padding:24px 28px}
|
||
.card.warn{border-color:rgba(232,176,74,.35);background:linear-gradient(180deg,rgba(232,176,74,.07),rgba(232,176,74,.025))}
|
||
.stat{display:flex;flex-direction:column;gap:8px}
|
||
.stat .v{font-family:var(--serif);font-size:58px;line-height:1.16;color:var(--ac);white-space:nowrap}
|
||
.stat .v.b{color:var(--b)}
|
||
.stat .l{font-size:18px;line-height:1.4;color:var(--tx2)}
|
||
.rule{height:1px;background:var(--line);margin:22px 0}
|
||
.list{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:14px}
|
||
.list li{position:relative;padding-left:22px;font-size:20px;line-height:1.42;color:var(--tx2)}
|
||
.list li::before{content:"";position:absolute;left:0;top:12px;width:8px;height:8px;border-radius:50%;background:var(--ac);opacity:.85}
|
||
.list.sm li{font-size:18px}
|
||
.list.sm li::before{top:10px}
|
||
.dl{display:grid;grid-template-columns:150px 1fr;gap:14px 22px;margin:0}
|
||
.dl dt{font-size:15px;letter-spacing:.14em;text-transform:uppercase;color:var(--ac);font-weight:600;padding-top:4px}
|
||
.dl dd{margin:0;font-size:19px;line-height:1.42;color:var(--tx2)}
|
||
.tag{display:inline-block;font-size:13px;letter-spacing:.1em;text-transform:uppercase;padding:3px 10px;border-radius:99px;border:1px solid var(--line);color:var(--mu);font-weight:600;white-space:nowrap}
|
||
.tag.hi{color:#eaa58d;border-color:rgba(214,135,108,.5);background:var(--negs)}
|
||
.tag.md{color:var(--ac);border-color:rgba(232,176,74,.45);background:var(--acs)}
|
||
.tag.ok{color:#a9cdb1;border-color:rgba(156,192,164,.45);background:rgba(156,192,164,.1)}
|
||
.tag.no{color:#c7c0b3;border-color:rgba(236,230,218,.25)}
|
||
figure{margin:0}
|
||
.fig{border-radius:14px;overflow:hidden;border:1px solid var(--line);box-shadow:0 24px 70px rgba(0,0,0,.5);background:#101319;line-height:0}
|
||
.fig img{display:block;width:100%;height:auto}
|
||
figcaption{font-size:15px;line-height:1.4;color:var(--dim);margin-top:12px}
|
||
table.tb{width:100%;border-collapse:collapse;font-size:18px;line-height:1.35}
|
||
.tb th{text-align:left;font-size:13px;letter-spacing:.14em;text-transform:uppercase;color:var(--mu);font-weight:600;padding:0 12px 10px;border-bottom:1px solid var(--line)}
|
||
.tb td{padding:10px 12px;border-bottom:1px solid var(--line2);vertical-align:top;color:var(--tx2)}
|
||
.tb td.n,.tb th.n{text-align:right;white-space:nowrap}
|
||
.tb tr.tot td{color:var(--tx);font-weight:600;border-bottom:none;border-top:1px solid var(--line)}
|
||
.legend{display:flex;gap:26px;font-size:16px;color:var(--mu);align-items:center}
|
||
.legend i{display:inline-block;width:14px;height:14px;border-radius:3px;margin-right:8px;vertical-align:-2px}
|
||
svg.chart{display:block;overflow:visible}
|
||
svg.chart text{font-family:var(--sans);fill:var(--mu);font-size:16px}
|
||
svg.chart .ax{stroke:var(--line);stroke-width:1}
|
||
svg.chart .gr{stroke:var(--line2);stroke-width:1}
|
||
svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
|
||
|
||
/* ---------- slide 1 ---------- */
|
||
.s1{--gx:72%;--gy:52%;--gr:62%}
|
||
.s1 .bd{grid-template-columns:700px 1fr;gap:56px;margin-top:0;align-items:center}
|
||
.s1 .sub{font-family:var(--serif);font-size:38px;line-height:1.22;color:var(--tx);margin-top:26px}
|
||
.s1 .plans{font-size:22px;line-height:1.45;color:var(--tx2);margin-top:26px}
|
||
.s1 .keys{display:grid;grid-template-columns:repeat(3,1fr);gap:22px;margin-top:40px}
|
||
.s1 .keys .v{font-family:var(--serif);font-size:40px;color:var(--ac);line-height:1.16;white-space:nowrap}
|
||
.s1 .keys .v.b{color:var(--b)}
|
||
.s1 .keys .l{font-size:15px;line-height:1.4;color:var(--mu);margin-top:10px}
|
||
.s1 .by{font-size:16px;line-height:1.5;color:var(--mu);margin-top:40px}
|
||
|
||
/* ---------- slide 2 ---------- */
|
||
.s2 .bd{grid-template-columns:520px 620px 1fr;gap:50px}
|
||
.s2 .card .list li{font-size:19.5px}
|
||
.s2 .card .list{gap:18px}
|
||
.big3{display:flex;flex-direction:column;gap:26px}
|
||
.big3 .v{font-family:var(--serif);font-size:44px;line-height:1.16;color:var(--ac)}
|
||
.big3 .l{font-size:19.5px;line-height:1.42;color:var(--tx2);margin-top:8px}
|
||
|
||
/* ---------- slide 3 ---------- */
|
||
.chain{display:grid;grid-template-columns:repeat(8,1fr);gap:0;margin-top:34px;position:relative}
|
||
.chain .st{position:relative;padding:0 14px 0 0}
|
||
.chain .dot{width:30px;height:30px;border-radius:50%;border:2px solid var(--ac);background:var(--bg);display:grid;place-items:center;font-size:14px;font-weight:700;color:var(--ac);position:relative;z-index:1}
|
||
.chain .st::before{content:"";position:absolute;left:30px;right:0;top:15px;height:2px;background:linear-gradient(90deg,rgba(232,176,74,.7),rgba(232,176,74,.25))}
|
||
.chain .st:last-child::before{display:none}
|
||
.chain .nm{font-family:var(--serif);font-size:25px;color:var(--tx);margin-top:16px;line-height:1.22}
|
||
.chain .vd{font-size:16px;line-height:1.4;color:var(--mu);margin-top:8px}
|
||
.s3 .bd{grid-template-columns:1fr 860px;gap:56px;margin-top:40px}
|
||
.tiles3{display:grid;grid-template-columns:repeat(3,1fr);gap:20px}
|
||
.tiles3 .card{padding:22px 22px}
|
||
.tiles3 .stat .v{font-size:46px}
|
||
.tiles3 .stat .l{font-size:16px}
|
||
|
||
/* ---------- slide 4 ---------- */
|
||
.flow{display:flex;align-items:stretch;margin-top:34px;gap:0}
|
||
.flow .fs{flex:1 1 0;min-width:0;border:1px solid var(--line2);background:linear-gradient(180deg,rgba(255,255,255,.04),rgba(255,255,255,.012));border-radius:12px;padding:16px 14px 16px}
|
||
.flow .fs .n{font-size:13px;letter-spacing:.14em;color:var(--ac);font-weight:700}
|
||
.flow .fs .nm{font-family:var(--serif);font-size:21px;line-height:1.22;color:var(--tx);margin-top:6px}
|
||
.flow .fs .ck{font-size:14.5px;line-height:1.38;color:var(--mu);margin-top:8px}
|
||
.flow .ar{flex:0 0 18px;align-self:center;height:2px;background:rgba(232,176,74,.45)}
|
||
.flow .gate{flex:0 0 96px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:8px}
|
||
.flow .gate .dm{width:34px;height:34px;transform:rotate(45deg);border:2px solid var(--ac);background:var(--acs)}
|
||
.flow .gate .gl{font-size:13px;line-height:1.3;text-align:center;color:var(--ac);font-weight:600}
|
||
.s4 .bd{grid-template-columns:1fr 640px;gap:52px;margin-top:34px}
|
||
.pill4{display:grid;grid-template-columns:1fr 1fr;gap:34px 44px;align-content:start}
|
||
.pill4 .h{font-family:var(--serif);font-size:27px;line-height:1.22;color:var(--tx)}
|
||
.pill4 .x{font-size:19.5px;line-height:1.45;color:var(--tx2);margin-top:8px}
|
||
.s4 figure{align-self:start}
|
||
.kp4{grid-column:1 / -1;display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);margin-top:6px}
|
||
.kp4 > div{padding:22px 24px 0 0}
|
||
.kp4 > div + div{padding-left:24px;border-left:1px solid var(--line2)}
|
||
.kp4 .v{font-family:var(--serif);font-size:46px;line-height:1.16;color:var(--ac);white-space:nowrap}
|
||
.kp4 .l{font-size:17px;line-height:1.4;color:var(--mu);margin-top:10px}
|
||
|
||
/* ---------- slide 5 ---------- */
|
||
.s5 .bd{grid-template-columns:960px 1fr;gap:52px;margin-top:34px}
|
||
.feat{display:flex;flex-direction:column;gap:22px}
|
||
.feat .h{font-family:var(--serif);font-size:24px;line-height:1.22;color:var(--tx)}
|
||
.feat .x{font-size:17.5px;line-height:1.42;color:var(--tx2);margin-top:4px}
|
||
|
||
/* ---------- slide 6 ---------- */
|
||
.s6 .bd{grid-template-columns:1000px 1fr;gap:52px;margin-top:36px}
|
||
.s6 .dl{grid-template-columns:132px 1fr;gap:15px 20px}
|
||
.s6 .dl dd{font-size:18px}
|
||
|
||
/* ---------- slide 7 ---------- */
|
||
.s7 .bd{grid-template-columns:repeat(4,1fr);gap:28px;margin-top:36px}
|
||
.s7 .card{display:flex;flex-direction:column;gap:14px;padding:24px 24px}
|
||
.s7 .card .t{font-size:19px;line-height:1.45}
|
||
.s7 .stat .v{font-size:50px}
|
||
.note7{margin-top:22px;font-size:17px;line-height:1.45;color:var(--mu);padding-left:18px;border-left:2px solid rgba(232,176,74,.5)}
|
||
|
||
/* ---------- slide 8 ---------- */
|
||
.s8 .bd{grid-template-columns:1000px 1fr;gap:56px;margin-top:30px}
|
||
.tot2{display:grid;grid-template-columns:1fr 1fr;gap:18px}
|
||
.tot2 .card{padding:20px 22px}
|
||
.tot2 .h{font-size:14px;letter-spacing:.16em;text-transform:uppercase;font-weight:700}
|
||
.tot2 .v{font-family:var(--serif);font-size:50px;line-height:1.16;margin-top:6px;white-space:nowrap}
|
||
.tot2 .l{font-size:15.5px;line-height:1.42;color:var(--tx2);margin-top:8px}
|
||
.lev td{font-size:17px;padding:7px 10px}
|
||
.lev th{padding:0 10px 8px}
|
||
|
||
/* ---------- slide 9 ---------- */
|
||
.s9 .bd{grid-template-columns:900px 1fr;gap:56px;margin-top:28px}
|
||
.op td{font-size:17px;padding:7px 10px}
|
||
.op th{padding:0 10px 8px}
|
||
.op td.k{color:var(--mu)}
|
||
|
||
/* ---------- slide 10 ---------- */
|
||
.s10 .bd{grid-template-columns:1000px 1fr;gap:56px;margin-top:30px}
|
||
.kp td{font-size:18px;padding:9px 10px}
|
||
.kp th{padding:0 10px 8px}
|
||
|
||
/* ---------- slide 11 ---------- */
|
||
.s11 .bd{grid-template-columns:1fr 720px;gap:56px;margin-top:32px}
|
||
.boi{display:flex;flex-direction:column;gap:0}
|
||
.boi .r{display:grid;grid-template-columns:170px 1fr 150px;gap:20px;padding:13px 0;border-bottom:1px solid var(--line2);align-items:start}
|
||
.boi .r .k{font-size:14px;letter-spacing:.14em;text-transform:uppercase;color:var(--ac);font-weight:600;padding-top:3px}
|
||
.boi .r .x{font-size:17.5px;line-height:1.42;color:var(--tx2)}
|
||
.boi .r .st{text-align:right;padding-top:1px}
|
||
.fin .need{display:grid;grid-template-columns:1fr 1fr;gap:18px}
|
||
.fin .need .card{padding:18px 22px}
|
||
.fin .need .v{font-family:var(--serif);font-size:44px;line-height:1.16;white-space:nowrap}
|
||
.fin .need .l{font-size:15.5px;color:var(--mu);margin-top:6px;line-height:1.4}
|
||
.fin .tbd{margin-top:18px;font-family:var(--serif);font-size:26px;line-height:1.3;color:var(--tx);padding:16px 22px;border-left:3px solid var(--ac);background:var(--acs);border-radius:0 10px 10px 0}
|
||
|
||
/* ---------- slide 12 ---------- */
|
||
.s12 .bd{grid-template-rows:auto auto;gap:22px;margin-top:22px}
|
||
.s12 .cols{display:grid;grid-template-columns:1fr 1fr 1fr;gap:40px}
|
||
.s12 .cols .t{font-size:17px}
|
||
.seq{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:9px;counter-reset:q}
|
||
.seq li{counter-increment:q;position:relative;padding-left:40px;font-size:17px;line-height:1.4;color:var(--tx2)}
|
||
.seq li::before{content:counter(q);position:absolute;left:0;top:-1px;width:26px;height:26px;border-radius:50%;border:2px solid var(--ac);color:var(--ac);font-size:13px;font-weight:700;display:grid;place-items:center}
|
||
|
||
/* ---------- slide 13 ---------- */
|
||
.rk td{font-size:16.5px;padding:8px 12px;line-height:1.36}
|
||
.rk td:first-child{width:118px}
|
||
.rk td:nth-child(2){width:760px;color:var(--tx)}
|
||
.rk th{padding:0 12px 9px}
|
||
|
||
/* ---------- slide 14 ---------- */
|
||
.s14 .bd{grid-template-columns:520px 470px 1fr;gap:48px;margin-top:34px}
|
||
.ppl{display:flex;flex-direction:column;gap:20px}
|
||
.ppl .nm{font-family:var(--serif);font-size:27px;color:var(--tx);line-height:1.22}
|
||
.ppl .ro{font-size:15px;letter-spacing:.12em;text-transform:uppercase;color:var(--ac);font-weight:600;margin-top:6px}
|
||
.ppl .x{font-size:17px;line-height:1.42;color:var(--tx2);margin-top:6px}
|
||
.vd td{font-size:16.5px;padding:9px 10px}
|
||
.vd td:first-child{color:var(--tx);font-weight:600;white-space:nowrap}
|
||
|
||
/* ---------- slide 15 ---------- */
|
||
.s15{--gx:100%;--gy:0%;--gr:40%}
|
||
.s15 .bd{grid-template-columns:1fr 600px;grid-template-rows:auto 1fr;gap:30px 56px;margin-top:30px}
|
||
.unl{grid-column:1 / -1;display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid var(--line);align-self:end}
|
||
.unl .u{padding:20px 30px 0 0}
|
||
.unl .u + .u{padding-left:30px;border-left:1px solid var(--line2)}
|
||
.unl .h{font-family:var(--serif);font-size:25px;line-height:1.22;color:var(--ac)}
|
||
.unl .x{font-size:17.5px;line-height:1.45;color:var(--tx2);margin-top:8px}
|
||
.ask .v{font-family:var(--serif);font-size:56px;line-height:1.16;color:var(--ac);white-space:nowrap}
|
||
.tr td{font-size:17px;padding:9px 10px;line-height:1.35}
|
||
.tr th{padding:0 10px 8px}
|
||
.tr td:first-child{color:var(--ac);font-weight:700;width:44px}
|
||
.chk{margin:0;padding:0;list-style:none;display:flex;flex-direction:column;gap:10px}
|
||
.chk li{position:relative;padding-left:30px;font-size:17px;line-height:1.4;color:var(--tx2)}
|
||
.chk li::before{content:"";position:absolute;left:0;top:4px;width:16px;height:16px;border:2px solid var(--ac);border-radius:4px}
|
||
|
||
/* Palatino's descent runs below the line box: keep it inside the element */
|
||
h1,h2,.stat .v,.s1 .keys .v,.big3 .v,.tot2 .v,.fin .need .v,.ask .v,.kp4 .v,.chain .nm,.flow .fs .nm,.ppl .nm,.pill4 .h,.feat .h,.unl .h{padding-bottom:.1em}
|
||
h1{padding-bottom:.14em}
|
||
|
||
/* notes are for the presenter only */
|
||
.notes{display:none}
|
||
|
||
/* ---------- controls ---------- */
|
||
.ctl{position:fixed;left:50%;bottom:16px;transform:translateX(-50%);display:flex;align-items:center;gap:2px;padding:5px 6px;border-radius:99px;
|
||
background:rgba(18,21,28,.86);border:1px solid rgba(236,230,218,.12);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
|
||
box-shadow:0 10px 30px rgba(0,0,0,.4);opacity:0;transition:opacity .5s ease;z-index:20;font-size:14px;color:var(--tx2)}
|
||
body.ui .ctl,.ctl:focus-within,.ctl:hover{opacity:1}
|
||
.ctl button,.ctl a{width:36px;height:34px;border:0;background:transparent;border-radius:99px;display:grid;place-items:center;cursor:pointer;color:var(--tx2);text-decoration:none}
|
||
.ctl button:hover,.ctl a:hover{background:rgba(255,255,255,.08);color:var(--tx)}
|
||
.ctl svg{width:18px;height:18px;stroke:currentColor;fill:none;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}
|
||
.ctl .cnt{min-width:66px;text-align:center;font-variant-numeric:tabular-nums;letter-spacing:.04em;color:var(--tx)}
|
||
.ctl .sep{width:1px;height:20px;background:rgba(236,230,218,.14);margin:0 4px}
|
||
.ctl a.pdf{width:auto;padding:0 12px;font-size:12.5px;font-weight:700;letter-spacing:.1em}
|
||
.jump{position:fixed;left:50%;top:18px;transform:translateX(-50%);background:rgba(18,21,28,.9);border:1px solid var(--line);border-radius:10px;padding:8px 16px;font-size:18px;color:var(--ac);z-index:30;font-variant-numeric:tabular-nums}
|
||
|
||
/* ---------- overview ---------- */
|
||
.ov{position:fixed;inset:0;background:#07080b;z-index:40;overflow:auto;padding:28px 32px 40px}
|
||
.ov h4{margin:0 0 18px;font-size:14px;letter-spacing:.2em;text-transform:uppercase;color:var(--mu);font-weight:600}
|
||
.ov .grid{display:grid;grid-template-columns:repeat(5,1fr);gap:18px}
|
||
.ov .th{position:relative;aspect-ratio:16/9;border-radius:8px;overflow:hidden;border:1px solid rgba(236,230,218,.14);cursor:pointer;background:var(--bg);padding:0}
|
||
.ov .th.cur{outline:2px solid var(--ac);outline-offset:2px}
|
||
.ov .th:hover{border-color:rgba(232,176,74,.6)}
|
||
.ov .th .lab{position:absolute;left:0;right:0;bottom:0;padding:6px 10px;font-size:12px;text-align:left;background:linear-gradient(0deg,rgba(7,8,11,.92),rgba(7,8,11,0));color:var(--tx2);z-index:2}
|
||
.mini{position:absolute;left:0;top:0;width:1920px;height:1080px;transform-origin:0 0;pointer-events:none;overflow:hidden;background:var(--bg)}
|
||
.mini .slide{transition:none!important;transform:none!important;opacity:1!important;visibility:visible!important}
|
||
@media (max-width:900px){.ov .grid{grid-template-columns:repeat(2,1fr)}}
|
||
|
||
/* ---------- presenter view ---------- */
|
||
.pv{position:fixed;inset:0;background:#0a0b0f;display:grid;grid-template-rows:56px 1fr;color:var(--tx);font-family:var(--sans)}
|
||
.pv header{display:flex;align-items:center;gap:22px;padding:0 22px;border-bottom:1px solid var(--line2);font-size:15px;color:var(--mu)}
|
||
.pv header .ttl{letter-spacing:.16em;text-transform:uppercase;font-weight:600;color:var(--ac);font-size:13px}
|
||
.pv header .cn{font-size:20px;color:var(--tx);font-variant-numeric:tabular-nums}
|
||
.pv header .tm{font-size:24px;color:var(--tx);font-variant-numeric:tabular-nums;margin-left:auto;font-family:var(--serif)}
|
||
.pv header button{background:rgba(255,255,255,.06);border:1px solid var(--line);border-radius:8px;padding:6px 12px;cursor:pointer;font-size:13px;color:var(--tx2)}
|
||
.pv header button:hover{background:rgba(255,255,255,.12)}
|
||
.pv .body{display:grid;grid-template-columns:1.55fr 1fr;gap:20px;padding:18px 22px;min-height:0}
|
||
.pv .col{display:flex;flex-direction:column;gap:10px;min-height:0}
|
||
.pv .lb{font-size:12px;letter-spacing:.18em;text-transform:uppercase;color:var(--dim);font-weight:600}
|
||
.pv .frame{position:relative;aspect-ratio:16/9;width:100%;border-radius:8px;overflow:hidden;border:1px solid var(--line);background:var(--bg)}
|
||
.pv .frame.next{opacity:.92}
|
||
.pv .frame .end{position:absolute;inset:0;display:grid;place-items:center;color:var(--dim);font-size:16px}
|
||
.pv .nt{flex:1;min-height:0;overflow:auto;font-size:19px;line-height:1.55;color:var(--tx2);padding:14px 18px;border:1px solid var(--line2);border-radius:8px;background:rgba(255,255,255,.02)}
|
||
.pv .nav{display:flex;gap:10px}
|
||
.pv .nav button{flex:1;background:rgba(255,255,255,.05);border:1px solid var(--line);border-radius:8px;padding:10px;cursor:pointer;color:var(--tx2)}
|
||
body.pvmode .stage,body.pvmode .ctl{display:none}
|
||
|
||
/* ---------- print / PDF: one slide per page, backgrounds on ---------- */
|
||
@page{size:1920px 1080px;margin:0}
|
||
@media print{
|
||
html,body{overflow:visible!important;height:auto!important;background:var(--bg)!important;width:1920px}
|
||
*{-webkit-print-color-adjust:exact!important;print-color-adjust:exact!important}
|
||
.stage{position:static!important;transform:none!important;width:1920px!important;height:auto!important;overflow:visible!important;box-shadow:none}
|
||
.slide{position:relative!important;opacity:1!important;visibility:visible!important;transform:none!important;transition:none!important;
|
||
break-after:page;page-break-after:always;break-inside:avoid;overflow:hidden}
|
||
.slide:last-child{break-after:auto;page-break-after:auto}
|
||
.notes,.ctl,.ov,.pv,.jump{display:none!important}
|
||
.fig{box-shadow:none}
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<div class="stage" id="stage">
|
||
|
||
<!-- 1 -->
|
||
<section class="slide s1 grooves" data-title="CNX Vinyl">
|
||
<div class="kicker"><i class="disc"></i>Investment proposal<span class="no">· Draft for discussion · October 2026</span></div>
|
||
<div class="bd">
|
||
<div>
|
||
<h1>CNX Vinyl</h1>
|
||
<p class="sub">A full-chain vinyl record plant for Asia-Pacific, in Chiang Mai, Thailand</p>
|
||
<p class="plans">Investment proposal, Plans A (~180M THB) and B (~215M THB), October 2026, draft. One plant, two ways to fund it.</p>
|
||
<div class="keys">
|
||
<div><div class="v">~182M</div><div class="l">THB, Plan A at the midpoint, labelled ~180M: six cost levers</div></div>
|
||
<div><div class="v b">~217M</div><div class="l">THB, Plan B at the midpoint, labelled ~215M: everything in</div></div>
|
||
<div><div class="v">0.58 to 0.90M</div><div class="l">records a year at 60 % specials, the same three presses in both <span class="e">est.</span></div></div>
|
||
</div>
|
||
<p class="by">Simona Meesaiyati, Project Lead · Alexander Grabowski, Technical Consultant · supported by buildfor.life · label name Poy Records</p>
|
||
</div>
|
||
<figure>
|
||
<div class="fig"><img src="img/plan-a-cover.webp" width="1800" height="1010" alt="Isometric render of the planned plant: factory hall with solar roof, office, utility yard, car park and road"></div>
|
||
<figcaption>Plan A as the plant and business plan calculator draws it: schematic, on the Payap scenario layout and a placeholder plot. The site is not decided.</figcaption>
|
||
</figure>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plans A and B, draft 0.3 (October 2026), cover and section 1; total-investment.md "Two plans". Render: fleet-calculator.html, Plan A preset. Both prices include the 10M to 20M THB ramp-up reserve and carry no contingency.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">CNX Vinyl, label name Poy Records, is a planned record plant near Chiang Mai with the whole chain under one roof: cutting, plating, pressing, packaging and fulfilment. We present two versions of one plant. Plan A is about 182M THB at the midpoint, labelled 180M, with six cost levers; Plan B is about 217M THB, labelled 215M, with everything in. Both include the 10M to 20M THB ramp-up reserve and neither carries a contingency. Both run the same three presses. Every figure comes from business plan draft 0.3 and the planning repository, and estimates are labelled. Nothing is ordered, signed or sited yet. Sources: business plan A, section 1; total-investment.md "Two plans".</aside>
|
||
</section>
|
||
|
||
<!-- 2 -->
|
||
<section class="slide s2" data-title="The opportunity">
|
||
<div class="kicker"><i class="disc"></i>The opportunity</div>
|
||
<h2>Asia-Pacific labels press on other continents. We know of no full-chain plant in the region.</h2>
|
||
<div class="bd">
|
||
<div>
|
||
<h3>Where regional customers press today</h3>
|
||
<div class="big3">
|
||
<div><div class="v">Europe, the US, Japan</div><div class="l">Labels in Southeast Asia, Australia and much of East Asia ship masters there, wait in those queues and pay freight and time zones on top.</div></div>
|
||
<div><div class="v">10 to 16 weeks</div><div class="l">roughly, for a standard black LP at European and US plants today, after 6 to 9 months at the 2021 to 2022 peak.</div></div>
|
||
<div><div class="v">One shop</div><div class="l">The one Thai pressing shop we have seen presses only: it buys its stampers abroad, has no climate control, and cannot economically serve runs under 300 units.</div></div>
|
||
</div>
|
||
</div>
|
||
<div>
|
||
<h3>Price per record: the two market data points on file</h3>
|
||
<svg class="chart" id="chPrice" width="620" height="330" viewBox="0 0 620 330" role="img" aria-label="Price per copy at 300 copies"></svg>
|
||
<p class="s" style="margin-top:18px">Scopes differ. Europe: black pressing with printed labels and a paper inner sleeve at 300 copies. CNX: the plan's assumed price, USD 3.76 to 5.27, covering pressing, labels, inner sleeve and assembly at 60 % specials. Thailand: the per-unit price from the August 2026 invoices (650 to 950 THB all-in at 300), a printed jacket included; several times the CNX assumption even after 100 to 150 THB for the jacket.</p>
|
||
</div>
|
||
<div class="card warn" style="align-self:start">
|
||
<h3 class="ac">What is still open</h3>
|
||
<ul class="list sm">
|
||
<li><b>Market validation is open</b> (roadmap item 1): no buyer prices yet. The Thai data is one buyer, one plant, one afternoon.</li>
|
||
<li>Making Vinyl Europe, Haarlem: strong supplier interest in full traceability. Supply-side evidence, not buyer validation.</li>
|
||
<li>The plan keeps its assumed price until buyers have been asked.</li>
|
||
<li>We compete on capability, not on price: in-house plating, climate control, metrology, traceability, short runs and represses from archived metal.</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plan A, sections 1, 3.1 to 3.3 and 9.1; revenue-model.md "First market data point" and "Record Industry's calculator"; roadmap.md item 1. THB at 33 per USD.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">The customer we serve presses abroad today. Lead times have normalised to roughly 10 to 16 weeks for a standard black LP, but freight, time zones and the queue remain. The one Thai shop we have seen presses only; its stampers come from abroad, so short runs and represses are slow and expensive. On price, our assumption of USD 3.76 to 5.27 a record sits between the European ex-works level of about USD 2.43 and the Thai market. Be clear with lenders: buyer-side validation, roadmap item 1, is still open, and the Thai evidence is one supplier and one afternoon. The financing case waits on it. Sources: business plan A, sections 3.1 to 3.3; revenue-model.md; roadmap.md.</aside>
|
||
</section>
|
||
|
||
<!-- 3 -->
|
||
<section class="slide s3 grooves" data-title="What we build" style="--gx:100%;--gy:100%;--gr:36%">
|
||
<div class="kicker"><i class="disc"></i>What we build</div>
|
||
<h2>The complete vinyl chain under one roof, bought in one capital round</h2>
|
||
<div class="chain">
|
||
<div class="st"><div class="dot">1</div><div class="nm">Mastering</div><div class="vd">Console and chain; one to two engineers</div></div>
|
||
<div class="st"><div class="dot">2</div><div class="nm">Lacquer cutting</div><div class="vd">Sillitoe SDMS cutting system</div></div>
|
||
<div class="st"><div class="dot">3</div><div class="nm">Galvanics</div><div class="vd">Pheenix Alpha line; zero liquid discharge</div></div>
|
||
<div class="st"><div class="dot">4</div><div class="nm">Stamper prep</div><div class="vd">Sibert back-finish, punch, coin</div></div>
|
||
<div class="st"><div class="dot">5</div><div class="nm">Pressing</div><div class="vd">Newbilt Duplex system and Classic</div></div>
|
||
<div class="st"><div class="dot">6</div><div class="nm">Finishing</div><div class="vd">Trim, sleeve, shrink; optical QC</div></div>
|
||
<div class="st"><div class="dot">7</div><div class="nm">Packaging</div><div class="vd">Ploy Printing, Chiang Mai</div></div>
|
||
<div class="st"><div class="dot">8</div><div class="nm">Fulfilment</div><div class="vd">Bulk to labels, parcels to fans</div></div>
|
||
</div>
|
||
<div class="bd">
|
||
<div>
|
||
<div class="tiles3">
|
||
<div class="card stat"><div class="v">3 presses</div><div class="l">One Newbilt Duplex system (two semi-automatic HRP 12, EX 50 main and EX 35 colour extruder) and one Newbilt Classic, fully automatic</div></div>
|
||
<div class="card stat"><div class="v">0.58 to 0.90M</div><div class="l">records a year on two shifts at 60 % specials, central 0.74M <span class="e">est.</span>; 0.71 to 0.97M at a standard-heavy mix</div></div>
|
||
<div class="card stat"><div class="v">60 %+</div><div class="l">special pressings: splatter, marble, colour effects. They run only on the Duplex, which sets the pace</div></div>
|
||
</div>
|
||
<ul class="list sm" style="margin-top:26px">
|
||
<li><b>Metal archive:</b> the father and the mother of every side are kept, so a repress starts from our own metal: 2 to 3 weeks from an archived mother (planning figure).</li>
|
||
<li><b>Growth from revenue, not in either price:</b> a second Duplex system lifts output to ~1.0 to 1.5M records a year; its floor, utility positions and pipe tees are reserved.</li>
|
||
</ul>
|
||
</div>
|
||
<figure>
|
||
<div class="fig"><img src="img/plan-a-iso-hall.webp" width="1800" height="1010" alt="Isometric view of the press hall with the Duplex system, the Classic and reserved bays"></div>
|
||
<figcaption>Press hall, Plan A preset: Duplex system 1 and the Classic; dashed bays are reserved for growth. Schematic (fleet-calculator.html).</figcaption>
|
||
</figure>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plan A, sections 1, 4.0 to 4.4 and 4.7; total-investment.md "Launch plant"; scale.md; production-timeline.md; equipment/pressing-line.md.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">This is a plant, not a pressing shop: the cutting room, galvanics and stamper preparation are in-house from the first release, so there are no bridge stampers and no outsourced cutting. Three Newbilt presses: a Duplex system of two semi-automatic presses with a colour extruder, and one fully automatic Classic at about 128 records an hour (vendor flyer). At the planned 60 % special pressings the Duplex sets the pace: 0.58 to 0.90 million records a year on two shifts, central 0.74 million; the calculator gives about 0.94 million at the team's planning rates. Expect the question why the whole chain at once; the answer is on the risk slide. Sources: business plan A, sections 4.0 to 4.4; scale.md.</aside>
|
||
</section>
|
||
|
||
<!-- 4 -->
|
||
<section class="slide s4" data-title="How a record is made">
|
||
<div class="kicker"><i class="disc"></i>How a record is made here</div>
|
||
<h2>Nine stages, three artist approval gates, every record traceable</h2>
|
||
<div class="flow">
|
||
<div class="fs"><div class="n">1</div><div class="nm">Audio intake</div><div class="ck">Checksums, format, technical QA</div></div>
|
||
<div class="gate"><div class="dm"></div><div class="gl">Gate 1<br>master plan</div></div>
|
||
<div class="fs"><div class="n">2</div><div class="nm">Vinyl mastering</div><div class="ck">Engineer QA of the vinyl master</div></div>
|
||
<div class="gate"><div class="dm"></div><div class="gl">Gate 2<br>master</div></div>
|
||
<div class="fs"><div class="n">3</div><div class="nm">Lathe cutting</div><div class="ck">Groove microscope, test cut, dead-wax scribe</div></div>
|
||
<div class="ar"></div>
|
||
<div class="fs"><div class="n">4</div><div class="nm">Silvering, electro­forming</div><div class="ck">Bath logs, XRF thickness per part</div></div>
|
||
<div class="ar"></div>
|
||
<div class="fs"><div class="n">5</div><div class="nm">Stamper prep</div><div class="ck">Concentricity, dimensions, visual</div></div>
|
||
<div class="ar"></div>
|
||
<div class="fs"><div class="n">6</div><div class="nm">Pressing</div><div class="ck">Telemetry per cycle, 100 % optical, sampled audio</div></div>
|
||
<div class="gate"><div class="dm"></div><div class="gl">Gate 3<br>test pressing</div></div>
|
||
<div class="fs"><div class="n">7</div><div class="nm">Finishing</div><div class="ck">Trim quality, sleeving</div></div>
|
||
<div class="ar"></div>
|
||
<div class="fs"><div class="n">8</div><div class="nm">Packaging</div><div class="ck">Matrix code scanned against the jacket barcode</div></div>
|
||
<div class="ar"></div>
|
||
<div class="fs"><div class="n">9</div><div class="nm">Fulfilment</div><div class="ck">Carrier tracking per shipment</div></div>
|
||
</div>
|
||
<div class="bd">
|
||
<div class="pill4">
|
||
<div><div class="h">Traceability</div><div class="x">A record's key is stamper + press + cycle timestamp + side. Logs are append-only; a complaint resolves to one stamper, press, shift and material lot.</div></div>
|
||
<div><div class="h">One system of record</div><div class="x">ERPNext holds orders, gates, job cards, stock, inspections and metal-part genealogy; machine data sits beside it, keyed to the same names.</div></div>
|
||
<div><div class="h">Layered quality control</div><div class="x">Inline optical inspection of every record, press telemetry every cycle, sampled audio comparison against the master; a lab gate for every PVC lot.</div></div>
|
||
<div><div class="h">Metal and climate</div><div class="x">XRF thickness per stamper serial from launch; the whole plant at 20 to 26 °C and 35 to 60 % RH, filtered for the burning season.</div></div>
|
||
<div class="kp4">
|
||
<div><div class="v">100 %</div><div class="l">of records through inline optical inspection</div></div>
|
||
<div><div class="v">3 gates</div><div class="l">artist approvals recorded before work continues</div></div>
|
||
<div><div class="v">~10 sets</div><div class="l">of stampers per archived mother; 5 to 10 mothers per father (rules of thumb)</div></div>
|
||
</div>
|
||
</div>
|
||
<figure>
|
||
<div class="fig"><img src="img/record-passport.webp" width="1600" height="819" alt="Record passport demo: the production history of one pressed copy"></div>
|
||
<figcaption>Record passport demo: what a fan sees from the code on the sleeve (IDs simulated, make-a-record.html).</figcaption>
|
||
</figure>
|
||
</div>
|
||
<p class="src"><b>Source:</b> process-flow.md; traceability.md; quality-automation.md; digital-factory.md; business plan A, sections 4 and 5.1 to 5.3.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">Walk the chain left to right. The three diamonds are the artist's approvals: the master plan, the master and the test pressing; nothing proceeds without a recorded approval. What a lender should take away is control: every lacquer, father, mother, stamper, press cycle and material lot gets an identity that knows its parents, so a complaint resolves backwards in minutes. Every record passes inline optical inspection; audio is compared on samples, first-off per stamper and more often as a stamper ages. At Making Vinyl Europe this traceability was what the industry responded to most; that is supplier feedback, not buyer validation. Sources: process-flow.md; traceability.md; quality-automation.md; business plan A, section 5.1.</aside>
|
||
</section>
|
||
|
||
<!-- 5 -->
|
||
<section class="slide s5" data-title="The platform">
|
||
<div class="kicker"><i class="disc"></i>The platform</div>
|
||
<h2>Online ordering with itemised quotes, and fan-funded pressing</h2>
|
||
<div class="bd">
|
||
<figure>
|
||
<div class="fig"><img src="img/order-splatter.webp" width="1800" height="1125" alt="Order page, step 4: a 12 inch splatter record configured, with the estimated price and ship date"></div>
|
||
<figcaption>Step-by-step order page, step 4 with the "Splatter 12"" preset: the picture follows every option. Demo; prices and dates indicative, not an offer (order.html).</figcaption>
|
||
</figure>
|
||
<div class="feat">
|
||
<div><div class="h">An itemised offer for every customer</div><div class="x">Every line priced from one price list, per title and per copy, with audio and artwork checked at upload. ERPNext is the system of record: the order, the three gates and the invoice live there. Public early with indicative prices and an interest list, so every configuration is market data.</div></div>
|
||
<div><div class="h">Fan-funded campaigns</div><div class="x">Fans pre-order into a ring-fenced account and are refunded automatically if the goal is missed (the recommended model). Minimum run 100 copies, a planning figure. Campaigns open only after the first releases ship.</div></div>
|
||
<figure>
|
||
<div class="fig"><img src="img/make-a-record-fans.webp" width="1400" height="760" alt="Investor demo: the fan funding step with fan price, fans needed and break-even"></div>
|
||
<figcaption>Investor demo, fan-funding step (make-a-record.html). The platform's name is not decided.</figcaption>
|
||
</figure>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> artist-platform.md; business plan A, sections 4.8 and 5.1; digital-factory.md "ERPNext: the plant's ERP". Public side ~USD 40k to 90k <i>est.</i>, inside the software block.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">The platform is the plant's front door. Anyone, from a bedroom artist to a label, configures a record and gets an itemised offer from the same price list that drives the invoice in ERPNext. The fan-funding mode lets an artist pre-sell a run: fans pay into a ring-fenced account and are refunded automatically if the goal is missed; the rule comes from Qrates, whose service was suspended with funded projects caught in the wind-down. A Thai legal opinion comes before the first campaign, and whether CNX is seller of record is still an open decision. The name is not decided. The public side is an estimated USD 40k to 90k. Sources: artist-platform.md; business plan A, section 4.8.</aside>
|
||
</section>
|
||
|
||
<!-- 6 -->
|
||
<section class="slide s6 grooves" data-title="Site and building" style="--gx:100%;--gy:100%;--gr:30%">
|
||
<div class="kicker"><i class="disc"></i>Site and building</div>
|
||
<h2>A ~1,500 m² plant on leased land, utilities built for four modules</h2>
|
||
<div class="bd">
|
||
<figure>
|
||
<div class="fig"><img src="img/plan-a-iso-gf.webp" width="1800" height="1010" alt="Isometric ground floor with the roofs cut away, labelled zones from galvanics to warehouse"></div>
|
||
<figcaption>Ground floor with the roofs cut away, Plan A preset: galvanics, stamper prep, PVC lab, press hall, inspection, packaging and warehouse; the office with the cutting room. Schematic of the calculator's Payap scenario layout on a placeholder plot.</figcaption>
|
||
</figure>
|
||
<dl class="dl">
|
||
<dt>Site</dt><dd>Long lease, working candidate in Lamphun (Mueang Chi), Chiang Mai options under review; Northern Region Industrial Estate as fallback. Nothing signed.</dd>
|
||
<dt>Land</dt><dd>~4 rai minimum, 6 to 8 rai with expansion. Leasing keeps ~12M to 32M THB of land purchase out of the round; rent ~0.3M to 1.0M THB a year <span class="e">est.</span></dd>
|
||
<dt>Building</dt><dd>Steel portal frame, 60 × 25 m (1,500 m²), crane-ready; 30 kN/m² slab raised 1.2 m for dock-height loading; insulated, airtight envelope.</dd>
|
||
<dt>Utility yard</dt><dd>~379 m²: boiler hall and chiller compound for four modules each. Launch: two 500 kg/h biomass boilers, two 250 kW chillers.</dd>
|
||
<dt>Roof</dt><dd>~520 kWp PV on the factory roof <span class="e">est.</span>; the office roof and the canopies built PV-ready.</dd>
|
||
<dt>Growth</dt><dd>Duplex bays and utility positions reserved; a 45 × 38 m (1,710 m²) second building fits the Mueang Chi plot.</dd>
|
||
</dl>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plan A, sections 6.1 and 6.2; site-candidates.md; scale.md; render: fleet-calculator.html, Plan A preset. The 60 × 25 m hall is the Mueang Chi design; the layout is fixed once a plot passes zoning and lease-ability.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">The strategy is a long lease of raw land with a purpose-built plant on it. That keeps an estimated 12M to 32M THB of land purchase out of the round, but it also means there is no land to pledge: the lease must give the company ownership of the buildings and the right to mortgage or assign them. Mueang Chi in Lamphun is the working candidate the design and the construction RFQ are tested against; its zoning is still unverified. The point Payap University gave in Chiang Mai failed zoning; its other land is unchecked. The render shows the calculator's Payap scenario layout, not a decided site. Sources: business plan A, sections 6.1 and 6.2; site-candidates.md.</aside>
|
||
</section>
|
||
|
||
<!-- 7 -->
|
||
<section class="slide s7" data-title="Sustainability and utilities">
|
||
<div class="kicker"><i class="disc"></i>Sustainability, utilities and the region</div>
|
||
<h2>Biomass steam, factory-roof solar and zero liquid discharge, in Northern Thailand</h2>
|
||
<div class="bd">
|
||
<div class="card">
|
||
<h3>Steam from biomass</h3>
|
||
<svg class="chart" id="chSteam" width="370" height="132" viewBox="0 0 370 132" role="img" aria-label="Steam energy cost a year, biomass against electric"></svg>
|
||
<p class="t">Two 500 kg/h modules on wood chips: <b>~0.67M THB</b> of fuel a year against <b>~4.9M THB</b> for electric steam, saving ~4.2M THB <span class="e">est.</span> Grid demand ~264 kW instead of ~980 kW.</p>
|
||
<p class="t">Heat recovered first: flue-gas economiser, condensate return, a heat pump lifting the presses' ~60 °C cooling return into feedwater.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h3>Solar on the factory roof</h3>
|
||
<div class="stat"><div class="v">~520 kWp</div></div>
|
||
<svg class="chart" id="chSolar" width="370" height="76" viewBox="0 0 370 76" role="img" aria-label="Share of the plant's electricity from the roof array"></svg>
|
||
<p class="t">~730 to 780 MWh a year, ~87 % used on site: ~633 MWh, ~61 % of the plant's ~1,036 MWh <span class="e">est.</span> Zero export on PEA's grid.</p>
|
||
<p class="t"><b class="ac">Plan A:</b> through a solar PPA, ~1.9M THB a year power price <span class="e">est.</span> <b class="bc">Plan B:</b> owned, with the 271 kWh battery.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h3>Water and material</h3>
|
||
<div class="stat"><div class="v">Zero</div><div class="l">process water discharged</div></div>
|
||
<p class="t">Closed cooling loop. Galvanics rinse water goes through a zero-discharge evaporator with metal recovery, in its own treatment building.</p>
|
||
<p class="t">80 % virgin, 20 % regrind: edge trim and rejects are ground in-house and go back to the extruder.</p>
|
||
</div>
|
||
<div class="card">
|
||
<h3>Northern Thailand</h3>
|
||
<div class="stat"><div class="v">~15 to 25</div><div class="l">jobs across shifts at volume <span class="e">est.</span></div></div>
|
||
<p class="t">Skilled roles in galvanics, cutting, QC and IT; Payap University programmes as a training pipeline (under discussion).</p>
|
||
<p class="t">Chiang Mai, Lamphun and Lampang: the Northern Economic Corridor, promoted by BOI as a creative-economy base. Packaging from Chiang Mai.</p>
|
||
</div>
|
||
</div>
|
||
<p class="note7">Biomass brings fuel contracts, a monsoon stock, ash handling, a 14 m stack with a dust collector and a certified boiler attendant; the stack is a visible point source in Chiang Mai's burning season, so dust control is drawn from the start.</p>
|
||
<p class="src"><b>Source:</b> business plan A, sections 1, 5.4, 8.4 and 12.1; biomass.md; sustainability.md "PV size with biomass steam"; boi.md "Regional angle"; scale.md. Fuel at ~850k records, planning price 1.35 to 2 THB/kg delivered.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">Steam is the plant's largest energy load, and both plans burn biomass from launch: about 0.67M THB of wood chips a year against about 4.9M THB for electric steam, which also cuts grid demand from about 980 kW to about 264 kW. Solar goes on the factory roof only, sized to the load: about 520 kWp, most of it used on site because a factory on PEA's grid runs zero export. The BOI no longer promotes solar since 1 July 2025, so owning the array earns the bill saving, not a tax benefit. Galvanics runs zero liquid discharge, the strongest answer to the effluent permit. All figures are estimates. Sources: biomass.md; sustainability.md; business plan A, sections 5.4 and 8.4.</aside>
|
||
</section>
|
||
|
||
<!-- 8 -->
|
||
<section class="slide s8" data-title="Investment">
|
||
<div class="kicker"><i class="disc"></i>Investment</div>
|
||
<h2>Plan A ~182M THB, Plan B ~217M THB: the same plant, six levers apart</h2>
|
||
<div class="bd">
|
||
<div>
|
||
<div class="legend" style="margin-bottom:12px"><span><i style="background:var(--ac)"></i>Plan A</span><span><i style="background:var(--b)"></i>Plan B</span><span>Bars: block midpoint, M THB. Lines: low to high end <span class="e">est.</span></span></div>
|
||
<svg class="chart" id="chInvest" width="1000" height="600" viewBox="0 0 1000 600" role="img" aria-label="Investment by block, Plan A and Plan B"></svg>
|
||
</div>
|
||
<div>
|
||
<div class="tot2">
|
||
<div class="card"><div class="h ac">Plan A</div><div class="v ac">~182M THB</div><div class="l">167.1M plus the 10M to 20M ramp-up reserve; ~132M to 233M low to high. Labelled ~180M: ~2M above it at the midpoint.</div></div>
|
||
<div class="card"><div class="h bc">Plan B</div><div class="v bc">~217M THB</div><div class="l">201.6M plus the same reserve; ~156M to 277M low to high. Labelled ~215M.</div></div>
|
||
</div>
|
||
<h3 style="margin-top:24px">Plan A's six levers, saving at the midpoint</h3>
|
||
<table class="tb lev">
|
||
<tr><th>Lever</th><th class="n">M THB</th></tr>
|
||
<tr><td>1 · Solar and battery through a solar PPA</td><td class="n">~14.7</td></tr>
|
||
<tr><td>2 · Metrology launch tier; confocal, profiler from revenue</td><td class="n">~8.8</td></tr>
|
||
<tr><td>3 · Visitor corridor built later</td><td class="n">~3.6</td></tr>
|
||
<tr><td>4 · Cassette and CD-R duplication later</td><td class="n">~4.0</td></tr>
|
||
<tr><td>5 · Crane rented for the installations</td><td class="n">~3.2</td></tr>
|
||
<tr><td>6 · Compressor through the Chinese route</td><td class="n">~0.25</td></tr>
|
||
<tr class="tot"><td>Plan B buys all six at launch</td><td class="n">~34.5</td></tr>
|
||
</table>
|
||
<p class="s" style="margin-top:16px"><b class="ac">No contingency in either price</b> (at 15 %: ~25M THB on A, ~30M on B). Not in the price: the central hydraulic plant, archive shelving, CD replication, financing costs, currency moves, duty and import VAT (BOI assumed).</p>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plans A and B, sections 8.1 and 8.2; total-investment.md "Launch plant" and "Two plans". Block groups and their midpoints are arithmetic on the block ranges. A Chinese biomass boiler quote ~2M THB below the Thai package would bring Plan A to 180M.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">Both plans buy every machine. Plan B takes none of the six levers; Plan A leases the solar array through a PPA, buys the confocal and profiler from revenue, builds the visitor corridor later, adds cassette and CD-R later, rents a crane for the installations and takes a Chinese-brand compressor: about 34.5M THB apart at the midpoint. A credit committee will ask three things: the prices carry no contingency; the Classic and the Pheenix line have no vendor price, the Pheenix line being priced at OMS's reference plus 20 %; and the building depends on a soil test. Sources: total-investment.md "Two plans"; business plan A, sections 8.1, 8.2 and 8.4.</aside>
|
||
</section>
|
||
|
||
<!-- 9 -->
|
||
<section class="slide s9" data-title="Operating picture">
|
||
<div class="kicker"><i class="disc"></i>Operating picture, mature year</div>
|
||
<h2>The central case works; the adverse case loses money</h2>
|
||
<div class="bd">
|
||
<div>
|
||
<div class="legend" style="margin-bottom:10px"><span><i style="background:rgba(236,230,218,.32)"></i>Revenue</span><span><i style="background:var(--ac)"></i>EBITDA, both plans</span><span><i style="background:#b9833a"></i>Operating profit A</span><span><i style="background:var(--b)"></i>Operating profit B</span></div>
|
||
<svg class="chart" id="chOp" width="900" height="560" viewBox="0 0 900 560" role="img" aria-label="Revenue, EBITDA and operating profit by scenario"></svg>
|
||
</div>
|
||
<div>
|
||
<table class="tb op">
|
||
<tr><th>USD a year</th><th class="n">Adverse</th><th class="n">Central</th><th class="n">Favourable</th></tr>
|
||
<tr><td class="k">Records</td><td class="n">0.58M</td><td class="n">0.74M</td><td class="n">0.90M</td></tr>
|
||
<tr><td class="k">Price per record</td><td class="n">3.76</td><td class="n">4.49</td><td class="n">5.27</td></tr>
|
||
<tr><td class="k">Direct cost per record</td><td class="n">2.70</td><td class="n">2.00</td><td class="n">1.35</td></tr>
|
||
<tr><td class="k">Revenue</td><td class="n">2.28M</td><td class="n">3.65M</td><td class="n">5.41M</td></tr>
|
||
<tr><td class="k">Revenue, THB</td><td class="n">~75M</td><td class="n">~120M</td><td class="n">~179M</td></tr>
|
||
<tr><td class="k">EBITDA</td><td class="n">0.23M</td><td class="n">1.73M</td><td class="n">3.79M</td></tr>
|
||
<tr><td class="k">Operating profit A</td><td class="n neg">-0.41M</td><td class="n">1.33M</td><td class="n">3.54M</td></tr>
|
||
<tr><td class="k">Operating profit B</td><td class="n neg">-0.55M</td><td class="n">1.24M</td><td class="n">3.50M</td></tr>
|
||
<tr><td class="k">Payback A, plan price</td><td class="n">~31 yr</td><td class="n">~3.2 yr</td><td class="n">~1.1 yr</td></tr>
|
||
<tr><td class="k">Payback B, plan price</td><td class="n">~37 yr</td><td class="n">~3.8 yr</td><td class="n">~1.2 yr</td></tr>
|
||
</table>
|
||
<div class="card warn" style="margin-top:20px;padding:18px 22px">
|
||
<p class="t" style="font-size:17.5px"><b>Read the adverse column as the honest one.</b> A central EBITDA margin of ~47 % is far above the 15 to 25 % pressing plants earn: the direct cost leaves out indirect labour, freight, scrap above the reject allowance, sales commissions and the extra cost of specials.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> revenue-model.md "Operating pictures of Plan A and Plan B (October 2026)"; business plans A and B, section 9.4. Estimates, mature year at 60 % specials; payback is EBITDA on the plan price including the reserve. Break-even on operating profit ~0.2M records (A), ~0.24M (B); the adverse case needs ~1.0M (A) or ~1.1M (B) records to cover depreciation.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">These are mature-year pictures, not a forecast. Each column is internally consistent: adverse pairs low volume with the low price, the high cost, the high capital and the high overheads. Revenue and EBITDA are the same in both plans because the plant is the same; only depreciation and payback differ. The central case gives EBITDA of about USD 1.73M, roughly 57M THB. Lenders should read the adverse column: it loses money, and the central margin of about 47 % is too good to trust until indirect labour, freight, commissions and the cost of specials are in the model. Price realisation and utilisation decide solvency. Sources: revenue-model.md; business plan A, section 9.4.</aside>
|
||
</section>
|
||
|
||
<!-- 10 -->
|
||
<section class="slide s10" data-title="Cash and returns">
|
||
<div class="kicker"><i class="disc"></i>Cash and returns</div>
|
||
<h2>Ten years of cash: back to zero in year ~3.8 (Plan A) and ~4.2 (Plan B)</h2>
|
||
<div class="bd">
|
||
<div>
|
||
<div class="legend" style="margin-bottom:10px"><span><i style="background:var(--ac)"></i>Plan A, cumulative cash</span><span><i style="background:var(--b)"></i>Plan B, cumulative cash</span><span>M USD, year 0 to 10</span></div>
|
||
<svg class="chart" id="chCash" width="1000" height="640" viewBox="0 0 1000 640" role="img" aria-label="Cumulative cash over ten years, Plan A and Plan B"></svg>
|
||
</div>
|
||
<div>
|
||
<table class="tb kp">
|
||
<tr><th>Calculator presets</th><th class="n ac">Plan A</th><th class="n bc">Plan B</th></tr>
|
||
<tr><td>Cash payback</td><td class="n">year ~3.8</td><td class="n">year ~4.2</td></tr>
|
||
<tr><td>IRR, ten years</td><td class="n">~26 %</td><td class="n">~22 %</td></tr>
|
||
<tr><td>NPV at 10 %</td><td class="n">~5.4M USD</td><td class="n">~4.7M USD</td></tr>
|
||
<tr><td>Year 0: investment and reserve</td><td class="n">5.52M USD</td><td class="n">6.56M USD</td></tr>
|
||
<tr><td>Mature-year EBITDA</td><td class="n">2.17M USD</td><td class="n">2.23M USD</td></tr>
|
||
<tr><td>Simple payback: plan price before the reserve / mature EBITDA</td><td class="n">2.3 yr</td><td class="n">2.7 yr</td></tr>
|
||
</table>
|
||
<p class="t" style="margin-top:20px;font-size:17px"><b>Basis:</b> 850k records at 60 % specials at the team's planning rate (3 records a minute per Duplex system), the calculator's own cost build-up with biomass fuel, the cutting engineer and, in Plan A, the PPA power price. Ramp 35, 70 and 90 % in years 1 to 3. Pre-tax, pre-financing, no BOI tax holiday, no terminal value: a sketch, not a forecast.</p>
|
||
<div class="card warn" style="margin-top:18px;padding:16px 22px"><p class="t" style="font-size:17px">In the revenue model's adverse case, EBITDA pays the plan price back in ~31 years (A) and ~37 years (B). A year-by-year cash model for the ramp is a phase 0 output.</p></div>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plans A and B, section 9.4 and figure 12; fleet-calculator.html, presets planA and planB, cash view (values read from the page, October 2026); revenue-model.md. Estimates.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">This curve is the calculator's, not the operating table's. It runs 850,000 records at the team's faster Duplex planning rate, its own cost build-up including biomass fuel and, for Plan A, the PPA power bill, and a ramp of 35, 70 and 90 % before the mature year. Year 0 is the midpoint investment plus a 15M THB reserve. On that basis Plan A is back to zero in year 3.8 with an IRR of about 26 %; Plan B in year 4.2 at about 22 %. It is pre-tax and pre-financing, with no terminal value. Put the adverse case beside it: about 31 and 37 years. Sources: fleet-calculator.html; business plans A and B, section 9.4.</aside>
|
||
</section>
|
||
|
||
<!-- 11 -->
|
||
<section class="slide s11" data-title="BOI and financing">
|
||
<div class="kicker"><i class="disc"></i>BOI promotion and financing</div>
|
||
<h2>Duty-free machinery under BOI, debt-to-equity no higher than 3:1</h2>
|
||
<div class="bd">
|
||
<div class="boi">
|
||
<div class="r"><div class="k">Category</div><div class="x">Basic plastic consumer-goods manufacturing (Category 6), incentive Group B: no tax holiday on the core activity. Sub-code to confirm with a BOI-registered consultant.</div><div class="st"><span class="tag no">To confirm</span></div></div>
|
||
<div class="r"><div class="k">Privileges</div><div class="x">Machinery import duty exemption, with import VAT; 100 % foreign ownership; visas and work permits through the One Stop Service; raw-material duty relief for export production.</div><div class="st"><span class="tag ok">Group B core</span></div></div>
|
||
<div class="r"><div class="k">Upgrades</div><div class="x">Smart and Sustainable Industry (15/2565, refreshed July 2025): typically 3 years of CIT exemption, capped around 50 % of the upgrade spend; R&D merit at 1 % of revenue adds 1 to 3 years.</div><div class="st"><span class="tag md">To structure</span></div></div>
|
||
<div class="r"><div class="k">Solar</div><div class="x">No longer promoted since 1 July 2025 (Announcement 3/2568). Only battery storage may still qualify, capped at 12M THB per MW.</div><div class="st"><span class="tag hi">Solar dropped</span></div></div>
|
||
<div class="r"><div class="k">Conditions</div><div class="x">Debt-to-equity no higher than 3:1 for the life of the privileges; minimum 1M THB excluding land and working capital; modern machinery; progress and annual reports; machinery imported within ~30 months, full operation by ~36 months.</div><div class="st"><span class="tag md">Binding</span></div></div>
|
||
<div class="r"><div class="k">Process</div><div class="x">e-Investment filing and analyst meeting; ~40 working days to a decision under 200M THB, 60 to 90 above; 4 to 6 months in practice. No machinery order before the certificate.</div><div class="st"><span class="tag no">Phase 0</span></div></div>
|
||
</div>
|
||
<div class="fin">
|
||
<h3>Funding need, reserve in, no contingency</h3>
|
||
<div class="need">
|
||
<div class="card"><div class="v ac">~182M THB</div><div class="l">Plan A, ~5.52M USD. Equity at 3:1: at least a quarter, ~46M THB</div></div>
|
||
<div class="card"><div class="v bc">~217M THB</div><div class="l">Plan B, ~6.56M USD. Equity at 3:1: at least a quarter, ~54M THB</div></div>
|
||
</div>
|
||
<dl class="dl" style="margin-top:22px;grid-template-columns:130px 1fr;gap:12px 18px">
|
||
<dt>Collateral</dt><dd style="font-size:17.5px">The machinery and equipment. The building only if the lease grants its ownership and the right to mortgage it, a lease condition. No land collateral: the site is a long lease of raw land.</dd>
|
||
<dt>Equity</dt><dd style="font-size:17.5px">The lead investor; Ploy Printing as intended strategic co-investor (terms not yet discussed); the founders.</dd>
|
||
<dt>Use of funds</dt><dd style="font-size:17.5px">Plan A at the midpoint: equipment (presses, metal path, metrology, lab) ~76M THB, utilities ~35M, building and site ~33M, software, freight, working capital and phase 0 ~23M (block midpoints <span class="e">est.</span>), plus the reserve; released in tranches (slide 15).</dd>
|
||
<dt>Without BOI</dt><dd style="font-size:17.5px">~5 % duty on ~2.3M USD of imported machinery (~0.11M USD) and ~0.17M USD of import VAT up front <span class="e">est.</span></dd>
|
||
</dl>
|
||
<p class="tbd">Financing structure to be agreed. No loan terms, interest rate or debt-service figures are on file.</p>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> boi.md; business plan A, sections 6.1, 7.1, 8.4 and 12.2; business plan B, section 1 ("The ask"); total-investment.md "Launch plant". Equity floors and use-of-funds groups are arithmetic on the plan's figures, before financing costs.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">Vinyl pressing lands in BOI Group B: no tax holiday on the core activity, but duty-free machinery with the import VAT, full foreign ownership and work permits. Tax value comes from the upgrade measures for automation, metrology and energy efficiency. Solar is no longer promoted since 1 July 2025. The condition that shapes financing is debt-to-equity no higher than 3:1 for the life of the privileges, so at least a quarter of the funding is equity. There is no loan structure, rate or debt-service figure on file; the structure is to be agreed with the investor and lenders. Collateral is the machinery; the land is leased. Sources: boi.md; business plan A, sections 7.1 and 12.2.</aside>
|
||
</section>
|
||
|
||
<!-- 12 -->
|
||
<section class="slide s12" data-title="Schedule">
|
||
<div class="kicker"><i class="disc"></i>Schedule</div>
|
||
<h2>First paying release: Q4 2028 to mid-2029, and still open</h2>
|
||
<div class="bd">
|
||
<svg class="chart" id="chGantt" width="1712" height="468" viewBox="0 0 1712 468" role="img" aria-label="Calendar from phase 0 to first paying release"></svg>
|
||
<div class="cols">
|
||
<div>
|
||
<h3>Commissioning sequence</h3>
|
||
<ol class="seq">
|
||
<li>Cutting room and galvanics, with the Sibert line: the presses need their stampers.</li>
|
||
<li>Newbilt Duplex system, with boilers B-1 and B-2 and chillers CH-1 and CH-2.</li>
|
||
<li>Newbilt Classic, once the metal path and the press process are stable.</li>
|
||
</ol>
|
||
</div>
|
||
<div>
|
||
<h3>Long-lead orders, after the certificate</h3>
|
||
<p class="t">Pheenix galvanics line, Sillitoe lathe, Sibert line, Newbilt Duplex system and Classic, two biomass boiler modules, the first two chiller modules. Press build 9 to 12 months, assumed; the other build times are not quoted.</p>
|
||
</div>
|
||
<div>
|
||
<h3>What moves the date</h3>
|
||
<p class="t">The BOI filing date and the vendor build times: a 15 to 18 month press build moves launch to mid-2029. Ordering with a deposit before the certificate, if BOI allows it, could bring launch 6 to 9 months earlier.</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> project-timeline.md, calendar anchored August 2026 (nothing in phase 0 has started, so it shifts day for day; not yet redrawn for the launch plant); business plan A, sections 10 and 11.1; roadmap.md phases 0 to 4.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">This is the August 2026 calendar, the only one on file. It was drawn for a one-press launch and has not been redrawn for the launch plant: commissioning the cutting room and galvanics before the presses is not yet in it, and only the press build time is even assumed. So the date is open: Q4 2028 at the optimistic end, mid-2029 if the press build takes 15 to 18 months. Nothing in phase 0 has started, so it slides day for day. The critical path is BOI certificate, long-lead orders, vendor builds, freight and commissioning; construction has three to five months of float. BOI windows keep 15 to 18 months of margin. Sources: project-timeline.md; business plan A, sections 10 and 11.</aside>
|
||
</section>
|
||
|
||
<!-- 13 -->
|
||
<section class="slide s13" data-title="Risks and mitigations">
|
||
<div class="kicker"><i class="disc"></i>Risks and mitigations</div>
|
||
<h2>The risks we carry, and what retires each one</h2>
|
||
<div class="bd" style="margin-top:28px">
|
||
<table class="tb rk">
|
||
<tr><th>Severity</th><th>Risk</th><th>Mitigation and status</th></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>Pricing power is unproven: market validation (roadmap item 1) is open, with no buyer prices yet</td><td>Validation gate with regional labels; the platform's calculator as an interest list; no large spending before it clears</td></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>Utilisation and price: the adverse case loses money; the extra cost of specials is not yet in the direct cost</td><td>Treat the adverse column as the honest one; specials gain an upper bound until compound quotes; second Duplex waits for demand</td></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>No contingency in the price: at 15 %, ~25M THB on A and ~30M on B</td><td>Quotes before commitment; release in tranches; how overruns are funded agreed up front</td></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>Galvanics and cutting on the critical path, galvanics the hardest skill to hire. Sibert advised getting presses, boilers, operators and feedback loops right first</td><td>Advice carried as a risk: commission the metal path first and measure it; hire early, train at the vendors; Stamper Discs and SST as overflow contacts</td></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>Prices not on file: the Newbilt Classic (placeholder band) and the Pheenix line (OMS reference + 20 %, no Pheenix quote)</td><td>Newbilt re-quote and Pheenix quote in phase 0; OMS's offer as the galvanics benchmark</td></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>Vendor build times unknown: the press build carries ~40 % of the schedule</td><td>Build time and order-book position required in every re-quote</td></tr>
|
||
<tr><td><span class="tag hi">High</span></td><td>Zoning unverified: the Payap point failed, Mueang Chi is unchecked</td><td>Verify on the land-use map and with counsel before the BOI filing; industrial estate as fallback</td></tr>
|
||
<tr><td><span class="tag md">Medium</span></td><td>Lease terms and building ownership: a weak lease makes the building hard to finance</td><td>Registered 30 to 50 year lease with renewal, building ownership and assignment rights; Thai counsel in phase 0</td></tr>
|
||
<tr><td><span class="tag md">Medium</span></td><td>Galvanics effluent and biomass emissions in the first factory licence</td><td>Zero-discharge design and treatment building from the start; dust collector and 14 m stack drawn; fuel contracts</td></tr>
|
||
<tr><td><span class="tag md">Medium</span></td><td>Currency: EUR on Newbilt and Pheenix, USD on Sibert and Sillitoe, costs in THB</td><td>Currency and validity in every quote; THB pricing regionally; hedging decided with the investor</td></tr>
|
||
</table>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plan A, sections 11.2 and 13 (severities as the plan states them); revenue-model.md "Honest caveats"; roadmap.md item 1; site-candidates.md.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">Every plan has risks; the point is which are being retired, by what and when. The severities are the business plan's own. The top three are commercial: pricing power is unproven, the adverse case loses money, and there is no contingency. The next group is execution: galvanics and cutting now sit on the critical path, the Classic and the Pheenix line have no vendor price, and no build time is quoted. Sibert, which sells into this plant, advised getting presses, boilers and operators right first; we decided the full chain anyway and carry that advice as a risk. Most of these retire in phase 0, before any capital is committed. Source: business plan A, section 13.</aside>
|
||
</section>
|
||
|
||
<!-- 14 -->
|
||
<section class="slide s14" data-title="Team and vendors">
|
||
<div class="kicker"><i class="disc"></i>Team, partners and vendors</div>
|
||
<h2>A small founding team, specialist vendors and a local packaging partner</h2>
|
||
<div class="bd">
|
||
<div class="ppl">
|
||
<h3>Team</h3>
|
||
<div><div class="nm">Simona Meesaiyati</div><div class="ro">Project Lead</div><div class="x">Management and the audio side.</div></div>
|
||
<div><div class="nm">Alexander Grabowski</div><div class="ro">Technical Consultant</div><div class="x">The technical side: equipment and vendors, utilities, the building model, the traceability and QC specification, the factory software.</div></div>
|
||
<div><div class="nm">buildfor.life</div><div class="ro">Consulting</div><div class="x">Supports the project in a consulting capacity.</div></div>
|
||
<div><div class="x"><b style="color:var(--tx)">To hire at launch:</b> a galvanics process engineer and a cutting engineer (critical path), a plant manager, two shifts of operators, QC, maintenance, sales. About 20 people at the start <span class="e">est.</span></div></div>
|
||
</div>
|
||
<div class="ppl">
|
||
<h3>Partners</h3>
|
||
<div><div class="nm">Ploy Printing</div><div class="ro">Chiang Mai · packaging</div><div class="x">Jackets, sleeves and inserts; intends to co-invest alongside the lead investor, terms not yet discussed. Supply priced at arm's length, Ploy abstaining on its own contract.</div></div>
|
||
<div><div class="nm">Payap University</div><div class="ro">Chiang Mai · under discussion</div><div class="x">Joint student programmes in mastering, lathe cutting, IT and business. Nothing agreed yet.</div></div>
|
||
<div><div class="nm">Advisers, phase 0</div><div class="ro">To engage</div><div class="x">A BOI-registered consultant; Thai counsel for the lease, the company and the shareholder agreement.</div></div>
|
||
</div>
|
||
<div>
|
||
<h3>Vendors</h3>
|
||
<table class="tb vd">
|
||
<tr><th>Vendor</th><th>Supplies</th><th>On file</th></tr>
|
||
<tr><td>Newbilt<br><span class="s">Germany</span></td><td>Duplex system and Classic presses</td><td>2024 quote EUR 200,500 for the Single 2.1, expired; Classic not priced</td></tr>
|
||
<tr><td>Pheenix Alpha<br><span class="s">Sweden</span></td><td>Galvanics: PASU, PPPU, PDPU, PMPB</td><td>No quote; priced at OMS's reference + 20 %</td></tr>
|
||
<tr><td>Sibert<br><span class="s">UK</span></td><td>Stamper prep: VSF, VSP, VSC</td><td>USD 250k budget confirmed, October 2026</td></tr>
|
||
<tr><td>Sillitoe<br><span class="s">Australia</span></td><td>SDMS cutting system</td><td>List price USD 125k</td></tr>
|
||
<tr><td>Ploy Printing<br><span class="s">Chiang Mai</span></td><td>Print and packaging</td><td>Partner</td></tr>
|
||
</table>
|
||
<p class="s" style="margin-top:16px">Making Vinyl Europe, Haarlem, 30 September to 2 October 2026: strong interest in full traceability; Pheenix offered references and a factory visit, OMS sent galvanics reference prices, SST Frankfurt offered help. Supplier-side evidence.</p>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> README "Team"; business plan A, sections 1, 12.1 and 12.2; equipment/pressing-line.md; equipment/galvanics.md; suppliers/vendor-landscape.md.</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">The founding team is two people: Simona leads the project and the audio side, Alexander the technical side; buildfor.life supports in a consulting capacity. The plant needs most of its crew from the start, and two hires are on the critical path: a galvanics process engineer and a cutting engineer. The vendors are established specialists: Newbilt for all three presses, Pheenix Alpha for galvanics, Sibert for stamper preparation, Sillitoe for the cutting system. Ploy Printing intends to co-invest and will supply packaging, so that contract is priced at arm's length and Ploy abstains on it. Shareholder terms are open. Sources: README; business plan A, section 12.</aside>
|
||
</section>
|
||
|
||
<!-- 15 -->
|
||
<section class="slide s15 grooves" data-title="The ask">
|
||
<div class="kicker"><i class="disc"></i>The ask and next steps</div>
|
||
<h2>Approve phase 0 now; commit in tranches once the gates clear</h2>
|
||
<div class="bd">
|
||
<div>
|
||
<div class="ask" style="display:grid;grid-template-columns:500px 1fr;gap:32px;align-items:start">
|
||
<div><h3>Now: phase 0</h3><div class="v">1.3M to 2.6M THB</div><p class="s" style="margin-top:10px">~USD 40k to 80k over 3 to 6 months, part of the plan price</p></div>
|
||
<p class="t" style="padding-top:34px">BOI consultant, Thai counsel for the lease and zoning, a soil boring test, and the quotes that settle most of the investment band. Then, on the gates clearing, a commitment in principle to one plan, released in tranches.</p>
|
||
</div>
|
||
<h3 style="margin-top:26px">Tranches, share of the investment before the reserve <span class="e">est.</span></h3>
|
||
<table class="tb tr">
|
||
<tr><th>#</th><th>Trigger</th><th>Covers</th><th class="n">Share</th><th class="n">A, M THB</th><th class="n">B, M THB</th></tr>
|
||
<tr><td>0</td><td>Now</td><td>Phase 0 work</td><td class="n">1 to 2 %</td><td class="n">1.3 to 2.6</td><td class="n">1.3 to 2.6</td></tr>
|
||
<tr><td>1</td><td>BOI certificate, lease, zoning, soil, pricing validated</td><td>Deposits on every long-lead order</td><td class="n">20 to 30 %</td><td class="n">~33 to 50</td><td class="n">~40 to 60</td></tr>
|
||
<tr><td>2</td><td>Building permit; build dates in writing</td><td>Construction, utilities, balance payments</td><td class="n">35 to 45 %</td><td class="n">~58 to 75</td><td class="n">~71 to 91</td></tr>
|
||
<tr><td>3</td><td>Building watertight; first machinery shipped</td><td>Fit-out, clean zones, ZLD, metrology, software</td><td class="n">15 to 20 %</td><td class="n">~25 to 33</td><td class="n">~30 to 40</td></tr>
|
||
<tr><td>4</td><td>Metal path commissioned; first stampers</td><td>Working capital; the reserve from here on</td><td class="n">8 to 12 %</td><td class="n">~13 to 20</td><td class="n">~16 to 24</td></tr>
|
||
</table>
|
||
</div>
|
||
<div>
|
||
<h3>The quotes that firm the numbers</h3>
|
||
<ul class="chk">
|
||
<li>Newbilt re-quote: Duplex with the EX 35, the Classic, build time</li>
|
||
<li>Pheenix galvanics quote, against OMS's reference</li>
|
||
<li>Sillitoe quote and build time; Sibert's itemised quotation</li>
|
||
<li>Building RFQ to two or three Thai contractors, with the soil test</li>
|
||
<li>Metrology quotes; solar PPA offers next to a purchase quote</li>
|
||
<li>Biomass boilers from Getabec and Chinese makers; fuel offers</li>
|
||
</ul>
|
||
<h3 style="margin-top:26px">Decisions for the investor</h3>
|
||
<ul class="list sm">
|
||
<li>Plan A or Plan B; the equity and debt mix within 3:1</li>
|
||
<li>How quotes landing above the plan price are funded</li>
|
||
<li>CD replication, yes or no, before the BOI filing; hedging of the EUR orders</li>
|
||
</ul>
|
||
</div>
|
||
<div class="unl">
|
||
<div class="u"><div class="h">Phase 0 unlocks</div><div class="x">The BOI filing, the lease heads of terms and the zoning answer, and quotes in place of the estimated blocks: the next version of the plan.</div></div>
|
||
<div class="u"><div class="h">The round unlocks</div><div class="x">Every long-lead order and the build, commissioned in sequence: metal path, Duplex, Classic. First paying release Q4 2028 to mid-2029, open.</div></div>
|
||
<div class="u"><div class="h">Revenue then funds</div><div class="x">The second Duplex system (~15.4M THB at the midpoint <span class="e">est.</span>), metrology, corridor, cassette and CD-R where Plan A defers them.</div></div>
|
||
</div>
|
||
</div>
|
||
<p class="src"><b>Source:</b> business plans A and B, sections 8.3, 14.1 and 14.2; roadmap.md phases 0 to 2. Gates before any order: market validation, the BOI certificate, and land (zoning, lease terms, soil).</p>
|
||
<footer class="ft"><span>CNX Vinyl · Investment proposal · Draft for discussion, October 2026</span><span class="pg"></span></footer>
|
||
<aside class="notes">The ask has two steps. Now: approve phase 0, about 1.3M to 2.6M THB over three to six months, part of the plan price. It buys the BOI consultant, Thai counsel on the lease and zoning, the soil test and the quotes that settle most of the investment band. Then, once market validation, the BOI certificate and the land gates clear, a commitment in principle to one plan, released in tranches so nothing is ordered before the certificate and validated pricing. Decisions for the investor: which plan, the equity and debt mix within 3:1, how quotes landing high are funded, and CD replication before the BOI filing. Sources: business plan A, sections 8.3 and 14.</aside>
|
||
</section>
|
||
|
||
</div>
|
||
|
||
<nav class="ctl" id="ctl" aria-label="Slide controls">
|
||
<button type="button" id="bPrev" title="Previous slide (Left)" aria-label="Previous slide"><svg viewBox="0 0 24 24"><path d="M15 5l-7 7 7 7"/></svg></button>
|
||
<span class="cnt" id="cnt">1 / 15</span>
|
||
<button type="button" id="bNext" title="Next slide (Right)" aria-label="Next slide"><svg viewBox="0 0 24 24"><path d="M9 5l7 7-7 7"/></svg></button>
|
||
<span class="sep"></span>
|
||
<button type="button" id="bOv" title="Overview (O)" aria-label="Overview"><svg viewBox="0 0 24 24"><rect x="4" y="4" width="6.5" height="6.5" rx="1"/><rect x="13.5" y="4" width="6.5" height="6.5" rx="1"/><rect x="4" y="13.5" width="6.5" height="6.5" rx="1"/><rect x="13.5" y="13.5" width="6.5" height="6.5" rx="1"/></svg></button>
|
||
<button type="button" id="bPv" title="Presenter view (P)" aria-label="Presenter view"><svg viewBox="0 0 24 24"><rect x="3" y="4" width="18" height="12" rx="1.5"/><path d="M8 20h8M12 16v4"/></svg></button>
|
||
<button type="button" id="bFs" title="Fullscreen (F)" aria-label="Fullscreen"><svg viewBox="0 0 24 24"><path d="M4 9V4h5M20 9V4h-5M4 15v5h5M20 15v5h-5"/></svg></button>
|
||
<a class="pdf" id="bPdf" href="CNX-Vinyl-Investor-Deck.pdf" download title="Download the PDF">PDF</a>
|
||
</nav>
|
||
|
||
<script>
|
||
"use strict";
|
||
/* ======================================================================
|
||
DATA: every charted value with its source key (see SRC).
|
||
Units: M THB, M USD, M THB a year, MWh a year; dates ISO.
|
||
====================================================================== */
|
||
const SRC = {
|
||
bp33: "Business plan A, section 3.3; revenue-model.md 'First market data point' and 'Record Industry's calculator'",
|
||
bp91: "Business plan A, section 9.1 (blended price USD 3.76 to 5.27 at 60 % specials)",
|
||
ti: "total-investment.md 'Launch plant' (block ranges) and 'Two plans'",
|
||
bpA81: "Business plan A, section 8.1, all blocks",
|
||
bpB81: "Business plan B, section 8.1, all blocks",
|
||
rm: "revenue-model.md 'Operating pictures of Plan A and Plan B (October 2026)'; business plans A and B, section 9.4",
|
||
calcA: "fleet-calculator.html preset planA, cash view (business plan A, figure 12)",
|
||
calcB: "fleet-calculator.html preset planB, cash view (business plan B, figure 12)",
|
||
bio: "biomass.md; business plan A, sections 1 and 8.4",
|
||
pv: "sustainability.md 'What ~520 kWp means on the grid'; business plan A, section 1",
|
||
tl: "project-timeline.md, Gantt (August 2026 calendar)",
|
||
bp10: "Business plan A, section 10"
|
||
};
|
||
const DATA = {
|
||
fx: { usdThb: 33, src: "ti" },
|
||
price: [
|
||
{ k: "eu", label: "European plant, ex works (Record Industry, Haarlem)", lo: 80.2, hi: 80.2, txt: "USD 2.43, ~80 THB", src: "bp33" },
|
||
{ k: "cnx", label: "CNX assumed blended price, 60\u00a0% specials", lo: 124, hi: 174, txt: "124 to 174 THB", src: "bp91" },
|
||
{ k: "th", label: "Thai market, per unit incl. a printed jacket (ResurRec)", lo: 535, hi: 780, txt: "535 to 780 THB", src: "bp33" }
|
||
],
|
||
/* investment groups, [low, high] in M THB; sums give 121.65 to 212.6 (A) and 146.15 to 257.1 (B) */
|
||
invest: {
|
||
blocks: [
|
||
{ k: "press", label: "Presses: Newbilt Duplex and Classic", a: [21.2, 27.4], b: [21.2, 27.4], src: "ti" },
|
||
{ k: "metal", label: "Metal path: cutting, galvanics, ZLD, prep", a: [30.5, 46.0], b: [30.5, 46.0], src: "ti" },
|
||
{ k: "metro", label: "Metrology and PVC lab", a: [7.4, 20.2], b: [13.4, 31.7], src: "bpA81" },
|
||
{ k: "util", label: "Utilities, HVAC, utility yard", a: [24.25, 44.9], b: [24.45, 45.2], src: "bpA81" },
|
||
{ k: "bldg", label: "Building, crane, workshop, corridor", a: [23.7, 41.8], b: [27.8, 51.2], src: "bpB81" },
|
||
{ k: "solar", label: "Solar PV and battery, owned", a: [0, 0], b: [12.4, 17.0], src: "bpB81", aTxt: "via the PPA" },
|
||
{ k: "soft", label: "Software, ERPNext, platform", a: [3.8, 10.2], b: [3.8, 10.2], src: "ti" },
|
||
{ k: "cass", label: "Cassette and CD-R duplication", a: [0, 0], b: [1.8, 6.3], src: "bpB81", aTxt: "later" },
|
||
{ k: "other", label: "Phase 0, freight and installation, working capital", a: [10.8, 22.1], b: [10.8, 22.1], src: "ti" },
|
||
{ k: "res", label: "Ramp-up reserve", a: [10, 20], b: [10, 20], src: "ti" }
|
||
],
|
||
total: { a: { mid: 167.1, price: 182, lo: 132, hi: 233 }, b: { mid: 201.6, price: 217, lo: 156, hi: 277 }, src: "ti" }
|
||
},
|
||
op: {
|
||
cases: ["Adverse", "Central", "Favourable"],
|
||
revenue: [2.28, 3.65, 5.41], ebitda: [0.23, 1.73, 3.79],
|
||
opA: [-0.41, 1.33, 3.54], opB: [-0.55, 1.24, 3.50], src: "rm"
|
||
},
|
||
cash: {
|
||
years: [0,1,2,3,4,5,6,7,8,9,10],
|
||
a: { cum: [-5.52,-5.06,-3.68,-1.77,0.41,2.58,4.75,6.93,9.10,11.27,13.45], pay: 3.8, src: "calcA" },
|
||
b: { cum: [-6.56,-6.08,-4.66,-2.70,-0.47,1.76,3.99,6.22,8.46,10.69,12.92], pay: 4.2, src: "calcB" }
|
||
},
|
||
steam: { biomass: 0.67, electric: 4.9, src: "bio" },
|
||
solar: { used: 633, plant: 1036, share: 61, src: "pv" },
|
||
gantt: { from: "2026-08-01", to: "2029-07-31", src: "tl", rows: [
|
||
{ g: "Phase 0", label: "Market validation", s: "2026-08-01", e: "2026-11-29" },
|
||
{ g: "Phase 0", label: "BOI consultant and application", s: "2026-08-01", e: "2026-12-14" },
|
||
{ g: "Phase 0", label: "Newbilt re-quote, with build time", s: "2026-08-01", e: "2026-10-30" },
|
||
{ g: "BOI", label: "BOI review to certificate", s: "2026-12-15", e: "2027-05-14", crit: 1, ms: [["2026-12-15", "filed"], ["2027-05-15", "certificate"]] },
|
||
{ g: "BOI", label: "Company incorporation", s: "2027-05-15", e: "2027-06-14", crit: 1 },
|
||
{ g: "Site", label: "Zoning, soil test, lease", s: "2026-09-01", e: "2027-02-28" },
|
||
{ g: "Site", label: "Design and building permit", s: "2027-03-01", e: "2027-06-29" },
|
||
{ g: "Site", label: "Construction", s: "2027-07-01", e: "2028-02-26" },
|
||
{ g: "Site", label: "Factory licence (Ror.Ngor. 4)", s: "2028-01-01", e: "2028-05-30" },
|
||
{ g: "Site", label: "Utilities, solar, fit-out", s: "2028-03-01", e: "2028-06-29" },
|
||
{ g: "Machines", label: "Press build at vendor (assumed)", s: "2027-06-15", e: "2028-04-10", crit: 1 },
|
||
{ g: "Machines", label: "Sea freight", s: "2028-04-10", e: "2028-05-30", crit: 1 },
|
||
{ g: "Machines", label: "Install, commission, train", s: "2028-07-01", e: "2028-09-14", crit: 1 },
|
||
{ g: "Launch", label: "Hire and train the launch team", s: "2028-05-01", e: "2028-08-29" },
|
||
{ g: "Launch", label: "Test pressings, QC baseline", s: "2028-09-15", e: "2028-10-30", crit: 1 },
|
||
{ g: "Launch", label: "First paying release", s: "2028-11-15", e: "2029-06-30", open: 1 }
|
||
] }
|
||
};
|
||
|
||
/* ======================================================================
|
||
Charts: inline SVG built from DATA
|
||
====================================================================== */
|
||
const NS = "http://www.w3.org/2000/svg";
|
||
function el(p, tag, a, txt) {
|
||
const e = document.createElementNS(NS, tag);
|
||
for (const k in a) e.setAttribute(k, a[k]);
|
||
if (txt != null) e.textContent = txt;
|
||
p.appendChild(e); return e;
|
||
}
|
||
const f1 = x => x.toFixed(1);
|
||
const f2 = x => (x < 0 ? "-" : "") + Math.abs(x).toFixed(2);
|
||
const mid = r => (r[0] + r[1]) / 2;
|
||
const C = { ac: "#e8b04a", b: "#8eaac3", tx: "#efe9dd", tx2: "#d2cbbd", mu: "#a59e91", dim: "#827b70", neg: "#d6876c", line: "rgba(236,230,218,.15)", line2: "rgba(236,230,218,.08)" };
|
||
|
||
function chPrice() {
|
||
const s = document.getElementById("chPrice"); if (!s) return;
|
||
const x0 = 10, x1 = 590, max = 1000, X = v => x0 + v / max * (x1 - x0);
|
||
for (const t of [0, 250, 500, 750, 1000]) {
|
||
el(s, "line", { x1: X(t), x2: X(t), y1: 20, y2: 292, class: "gr" });
|
||
el(s, "text", { x: X(t), y: 318, "text-anchor": t === 0 ? "start" : t === 1000 ? "end" : "middle", "font-size": 15 }, t === 1000 ? "1,000 THB" : t.toLocaleString("en-US"));
|
||
}
|
||
DATA.price.forEach((r, i) => {
|
||
const y = 36 + i * 92, col = r.k === "cnx" ? C.ac : r.k === "th" ? C.neg : C.b;
|
||
el(s, "text", { x: x0, y: y, fill: C.tx2, "font-size": 17 }, r.label);
|
||
if (r.hi - r.lo < 1) {
|
||
el(s, "circle", { cx: X(r.lo), cy: y + 30, r: 9, fill: col });
|
||
} else {
|
||
el(s, "rect", { x: X(r.lo), y: y + 19, width: Math.max(4, X(r.hi) - X(r.lo)), height: 22, rx: 4, fill: col });
|
||
}
|
||
const right = X(r.hi) + 14 + 150 > x1;
|
||
el(s, "text", { x: right ? X(r.lo) - 14 : X(r.hi) + 14, y: y + 36, "text-anchor": right ? "end" : "start", fill: C.tx, "font-size": 18, "font-weight": 600 }, r.txt);
|
||
});
|
||
}
|
||
|
||
function chInvest() {
|
||
const s = document.getElementById("chInvest"); if (!s) return;
|
||
const L = 372, R = 905, max = 55, X = v => L + v / max * (R - L), rh = 56, top = 10;
|
||
for (const t of [0, 10, 20, 30, 40, 50]) {
|
||
el(s, "line", { x1: X(t), x2: X(t), y1: top, y2: top + rh * DATA.invest.blocks.length, class: "gr" });
|
||
el(s, "text", { x: X(t), y: top + rh * DATA.invest.blocks.length + 24, "text-anchor": "middle", "font-size": 14 }, t + "M");
|
||
}
|
||
DATA.invest.blocks.forEach((b, i) => {
|
||
const y = top + i * rh, res = b.k === "res";
|
||
el(s, "text", { x: 0, y: y + 33, fill: res ? C.mu : C.tx2, "font-size": 16.5 }, b.label);
|
||
[["a", C.ac, 8], ["b", C.b, 29]].forEach(([p, col, dy]) => {
|
||
const r = b[p], m = mid(r), yy = y + dy;
|
||
if (r[1] === 0) {
|
||
el(s, "text", { x: X(0) + 6, y: yy + 13, fill: C.dim, "font-size": 14, "font-style": "italic" }, b[p + "Txt"] || "none");
|
||
return;
|
||
}
|
||
el(s, "rect", { x: X(0), y: yy, width: X(m) - X(0), height: 17, rx: 3, fill: col, "fill-opacity": res ? 0.35 : 0.92, stroke: res ? col : "none", "stroke-dasharray": res ? "4 3" : "" });
|
||
el(s, "line", { x1: X(r[0]), x2: X(r[1]), y1: yy + 8.5, y2: yy + 8.5, stroke: C.tx, "stroke-opacity": .55, "stroke-width": 1.5 });
|
||
el(s, "line", { x1: X(r[0]), x2: X(r[0]), y1: yy + 3, y2: yy + 14, stroke: C.tx, "stroke-opacity": .55, "stroke-width": 1.5 });
|
||
el(s, "line", { x1: X(r[1]), x2: X(r[1]), y1: yy + 3, y2: yy + 14, stroke: C.tx, "stroke-opacity": .55, "stroke-width": 1.5 });
|
||
el(s, "text", { x: Math.max(X(r[1]), X(m)) + 10, y: yy + 14, fill: col, "font-size": 15, "font-weight": 600 }, f1(m));
|
||
});
|
||
if (i < DATA.invest.blocks.length - 1) el(s, "line", { x1: 0, x2: R + 70, y1: y + rh, y2: y + rh, stroke: C.line2 });
|
||
});
|
||
}
|
||
|
||
function chOp() {
|
||
const s = document.getElementById("chOp"); if (!s) return;
|
||
const D = DATA.op, L = 64, R = 890, T = 20, B = 500, lo = -1, hi = 6, Y = v => T + (hi - v) / (hi - lo) * (B - T);
|
||
for (let t = lo; t <= hi; t++) {
|
||
el(s, "line", { x1: L, x2: R, y1: Y(t), y2: Y(t), class: t === 0 ? "z" : "gr" });
|
||
el(s, "text", { x: L - 12, y: Y(t) + 5, "text-anchor": "end", "font-size": 14 }, t + "M");
|
||
}
|
||
const gw = (R - L) / 3, bw = 46, gap = 12;
|
||
const ser = [["revenue", "rgba(236,230,218,.32)", C.tx2], ["ebitda", C.ac, C.ac], ["opA", "#b9833a", "#d9a560"], ["opB", C.b, C.b]];
|
||
D.cases.forEach((c, i) => {
|
||
const gx = L + gw * i + (gw - (bw * 4 + gap * 3)) / 2;
|
||
ser.forEach(([k, fill, tcol], j) => {
|
||
const v = D[k][i], x = gx + j * (bw + gap), y0 = Y(0), y1 = Y(v);
|
||
el(s, "rect", { x: x, y: Math.min(y0, y1), width: bw, height: Math.max(1, Math.abs(y1 - y0)), rx: 3, fill: v < 0 ? C.neg : fill });
|
||
el(s, "text", { x: x + bw / 2, y: v < 0 ? y1 + 20 : y1 - 8, "text-anchor": "middle", fill: v < 0 ? C.neg : tcol, "font-size": 14.5, "font-weight": 600 }, f2(v));
|
||
});
|
||
el(s, "text", { x: L + gw * i + gw / 2, y: B + 40, "text-anchor": "middle", fill: C.tx, "font-size": 18, "font-weight": 600 }, c);
|
||
if (i) el(s, "line", { x1: L + gw * i, x2: L + gw * i, y1: T, y2: B + 10, stroke: C.line2 });
|
||
});
|
||
}
|
||
|
||
function chCash() {
|
||
const s = document.getElementById("chCash"); if (!s) return;
|
||
const D = DATA.cash, L = 70, R = 900, T = 20, B = 580, lo = -8, hi = 14;
|
||
const X = y => L + y / 10 * (R - L), Y = v => T + (hi - v) / (hi - lo) * (B - T);
|
||
for (let t = lo; t <= hi; t += 2) {
|
||
el(s, "line", { x1: L, x2: R, y1: Y(t), y2: Y(t), class: t === 0 ? "z" : "gr" });
|
||
el(s, "text", { x: L - 12, y: Y(t) + 5, "text-anchor": "end", "font-size": 14 }, t + "M");
|
||
}
|
||
D.years.forEach(y => el(s, "text", { x: X(y), y: B + 28, "text-anchor": "middle", "font-size": 14 }, y === 0 ? "Y0" : String(y)));
|
||
// payback markers
|
||
[["a", C.ac, -1], ["b", C.b, 1]].forEach(([p, col, side]) => {
|
||
const px = X(D[p].pay);
|
||
el(s, "line", { x1: px, x2: px, y1: Y(0), y2: Y(side < 0 ? 7.6 : -4.6), stroke: col, "stroke-dasharray": "3 4", "stroke-width": 1.5 });
|
||
el(s, "text", { x: px + (side < 0 ? -8 : 8), y: Y(side < 0 ? 8 : -5.4), "text-anchor": side < 0 ? "end" : "start", fill: col, "font-size": 15, "font-weight": 600 }, "year ~" + D[p].pay);
|
||
});
|
||
[["b", C.b], ["a", C.ac]].forEach(([p, col]) => {
|
||
const pts = D[p].cum.map((v, i) => X(i).toFixed(1) + "," + Y(v).toFixed(1)).join(" ");
|
||
el(s, "polyline", { points: pts, fill: "none", stroke: col, "stroke-width": 3.5, "stroke-linejoin": "round" });
|
||
D[p].cum.forEach((v, i) => el(s, "circle", { cx: X(i), cy: Y(v), r: 5, fill: "#0d0f14", stroke: col, "stroke-width": 2.5 }));
|
||
});
|
||
const a = D.a.cum, b = D.b.cum;
|
||
el(s, "text", { x: X(0) + 10, y: Y(a[0]) - 14, fill: C.ac, "font-size": 15, "font-weight": 600 }, f2(a[0]));
|
||
el(s, "text", { x: X(0) + 10, y: Y(b[0]) + 26, fill: C.b, "font-size": 15, "font-weight": 600 }, f2(b[0]));
|
||
el(s, "text", { x: X(10) + 12, y: Y(a[10]) - 2, fill: C.ac, "font-size": 15, "font-weight": 600 }, f2(a[10]));
|
||
el(s, "text", { x: X(10) + 12, y: Y(b[10]) + 16, fill: C.b, "font-size": 15, "font-weight": 600 }, f2(b[10]));
|
||
}
|
||
|
||
function chSteam() {
|
||
const s = document.getElementById("chSteam"); if (!s) return;
|
||
const D = DATA.steam, L = 0, R = 228, X = v => L + v / 5 * (R - L);
|
||
[["Biomass fuel", D.biomass, C.ac], ["Electric steam", D.electric, "rgba(236,230,218,.35)"]].forEach(([lab, v, col], i) => {
|
||
const y = 6 + i * 62;
|
||
el(s, "text", { x: 0, y: y + 14, fill: C.tx2, "font-size": 15 }, lab);
|
||
el(s, "rect", { x: X(0), y: y + 22, width: X(v) - X(0), height: 20, rx: 3, fill: col });
|
||
el(s, "text", { x: X(v) + 10, y: y + 38, fill: i ? C.tx2 : C.ac, "font-size": 15, "font-weight": 600 }, "~" + v.toFixed(2).replace(/0$/, "") + "M THB/yr");
|
||
});
|
||
}
|
||
|
||
function chSolar() {
|
||
const s = document.getElementById("chSolar"); if (!s) return;
|
||
const D = DATA.solar, W = 370;
|
||
el(s, "rect", { x: 0, y: 24, width: W - 2, height: 18, rx: 3, fill: "rgba(236,230,218,.12)" });
|
||
el(s, "rect", { x: 0, y: 24, width: (W - 2) * D.used / D.plant, height: 18, rx: 3, fill: C.ac });
|
||
el(s, "text", { x: 0, y: 16, fill: C.tx2, "font-size": 14 }, "Share of the plant's electricity");
|
||
el(s, "text", { x: 0, y: 64, fill: C.ac, "font-size": 15, "font-weight": 600 }, "~" + D.share + "\u00a0%");
|
||
el(s, "text", { x: W - 2, y: 64, "text-anchor": "end", fill: C.mu, "font-size": 14 }, "~" + D.used + " of ~" + D.plant.toLocaleString("en-US") + " MWh");
|
||
}
|
||
|
||
function chGantt() {
|
||
const s = document.getElementById("chGantt"); if (!s) return;
|
||
const D = DATA.gantt, L = 390, R = 1700, T = 30, rh = 25;
|
||
const d = x => new Date(x + "T00:00:00Z").getTime(), t0 = d(D.from), t1 = d(D.to);
|
||
const X = x => L + (d(x) - t0) / (t1 - t0) * (R - L);
|
||
const n = D.rows.length, Bt = T + n * rh;
|
||
// month grid and year labels
|
||
for (let y = 2026; y <= 2029; y++) for (let m = 0; m < 12; m++) {
|
||
const iso = y + "-" + String(m + 1).padStart(2, "0") + "-01";
|
||
if (d(iso) < t0 || d(iso) > t1) continue;
|
||
const x = X(iso);
|
||
el(s, "line", { x1: x, x2: x, y1: T - 6, y2: Bt, stroke: m === 0 ? C.line : C.line2 });
|
||
if (m % 3 === 0) el(s, "text", { x: x + 4, y: T - 12, "font-size": 13, fill: m === 0 ? C.tx2 : C.dim }, (m === 0 ? y + " " : "") + ["Jan", "Apr", "Jul", "Oct"][m / 3]);
|
||
}
|
||
let g = "";
|
||
D.rows.forEach((r, i) => {
|
||
const y = T + i * rh;
|
||
if (r.g !== g) { g = r.g; if (i) el(s, "line", { x1: 0, x2: R, y1: y, y2: y, stroke: C.line }); el(s, "text", { x: 0, y: y + 17, fill: C.ac, "font-size": 12, "font-weight": 700, "letter-spacing": "1.5" }, g.toUpperCase()); }
|
||
el(s, "text", { x: 112, y: y + 17, fill: r.crit ? C.tx : C.tx2, "font-size": 15 }, r.label);
|
||
if (r.open) {
|
||
const x = X(r.s);
|
||
el(s, "rect", { x: x, y: y + 6, width: X(r.e) - x, height: 13, rx: 3, fill: "rgba(232,176,74,.12)", stroke: C.ac, "stroke-dasharray": "4 3" });
|
||
el(s, "path", { d: `M${x} ${y + 3} l7 9.5 -7 9.5 -7 -9.5z`, fill: C.ac });
|
||
el(s, "text", { x: x - 14, y: y + 17, "text-anchor": "end", fill: C.ac, "font-size": 14.5, "font-weight": 600 }, "Q4 2028 to mid-2029, open");
|
||
return;
|
||
}
|
||
el(s, "rect", { x: X(r.s), y: y + 6, width: Math.max(4, X(r.e) - X(r.s)), height: 13, rx: 3, fill: r.crit ? C.ac : "rgba(142,170,195,.55)" });
|
||
(r.ms || []).forEach(([dt, lab]) => {
|
||
const x = X(dt);
|
||
el(s, "path", { d: `M${x} ${y + 3} l6 9.5 -6 9.5 -6 -9.5z`, fill: C.tx, stroke: "#0d0f14", "stroke-width": 1.5 });
|
||
});
|
||
});
|
||
// milestone labels for the BOI row, placed beside the bar
|
||
const boi = D.rows.findIndex(r => r.ms), yb = T + boi * rh;
|
||
el(s, "text", { x: X("2027-05-15") + 12, y: yb + 17, fill: C.tx, "font-size": 13.5 }, "certificate ~May 2027");
|
||
// metal-path note at the test-pressing row
|
||
const tp = D.rows.findIndex(r => r.label.startsWith("Test")), yt = T + tp * rh;
|
||
el(s, "text", { x: X("2028-09-15") - 12, y: yt + 17, "text-anchor": "end", fill: C.neg, "font-size": 13.5 }, "metal-path commissioning comes first; not yet in this calendar");
|
||
el(s, "line", { x1: 0, x2: R, y1: Bt, y2: Bt, stroke: C.line });
|
||
// legend
|
||
const ly = Bt + 26;
|
||
el(s, "rect", { x: 112, y: ly - 11, width: 26, height: 12, rx: 3, fill: C.ac });
|
||
el(s, "text", { x: 146, y: ly, "font-size": 14 }, "critical path");
|
||
el(s, "rect", { x: 260, y: ly - 11, width: 26, height: 12, rx: 3, fill: "rgba(142,170,195,.55)" });
|
||
el(s, "text", { x: 294, y: ly, "font-size": 14 }, "parallel track with float");
|
||
el(s, "text", { x: R, y: ly, "text-anchor": "end", "font-size": 14, fill: C.dim }, "August 2026 calendar, optimistic end drawn; every duration an estimate");
|
||
}
|
||
|
||
/* ======================================================================
|
||
Deck engine: scaling, navigation, hash, overview, presenter, sync
|
||
====================================================================== */
|
||
const stage = document.getElementById("stage");
|
||
const slides = Array.from(stage.querySelectorAll(":scope > .slide"));
|
||
const N = slides.length;
|
||
const params = new URLSearchParams(location.search);
|
||
const PRESENTER = params.has("presenter");
|
||
let cur = 0, pvWin = null, buf = "", bufT = 0;
|
||
const ME = Math.random().toString(36).slice(2);
|
||
let lastMsg = "";
|
||
|
||
slides.forEach((s, i) => { const pg = s.querySelector(".pg"); if (pg) pg.textContent = String(i + 1).padStart(2, "0") + " / " + String(N).padStart(2, "0"); });
|
||
|
||
function fit() {
|
||
const W = window.innerWidth, H = window.innerHeight, k = Math.min(W / 1920, H / 1080);
|
||
stage.style.transform = `translate(${(W - 1920 * k) / 2}px, ${(H - 1080 * k) / 2}px) scale(${k})`;
|
||
}
|
||
function fromHash() {
|
||
const m = /^#(\d+)/.exec(location.hash);
|
||
return m ? Math.min(N, Math.max(1, +m[1])) - 1 : 0;
|
||
}
|
||
function go(n, opt) {
|
||
opt = opt || {};
|
||
n = Math.min(N - 1, Math.max(0, n | 0));
|
||
const changed = n !== cur;
|
||
cur = n;
|
||
slides.forEach((s, i) => { s.classList.toggle("active", i === n); s.classList.toggle("before", i < n); s.setAttribute("aria-hidden", i === n ? "false" : "true"); });
|
||
const c = document.getElementById("cnt"); if (c) c.textContent = (n + 1) + " / " + N;
|
||
const h = "#" + (n + 1);
|
||
if (location.hash !== h) history.replaceState(null, "", location.pathname + location.search + h);
|
||
if (PRESENTER) renderPresenter();
|
||
if (ovOpen) markOverview();
|
||
if (!opt.remote && (changed || opt.force)) send(n);
|
||
}
|
||
const next = () => go(cur + 1), prev = () => go(cur - 1);
|
||
|
||
/* --- sync between the audience window and the presenter window --- */
|
||
const KEY = "cnx-pitch-sync";
|
||
let bc = null;
|
||
try { bc = new BroadcastChannel(KEY); bc.onmessage = e => recv(e.data); } catch (e) { bc = null; }
|
||
function send(n) {
|
||
const m = { n: n, from: ME, t: Date.now() + Math.random() };
|
||
lastMsg = m.from + ":" + m.t;
|
||
try { if (bc) bc.postMessage(m); } catch (e) {}
|
||
try { localStorage.setItem(KEY, JSON.stringify(m)); } catch (e) {}
|
||
try { if (window.opener && !window.opener.closed) window.opener.postMessage({ cnxPitch: m }, "*"); } catch (e) {}
|
||
try { if (pvWin && !pvWin.closed) pvWin.postMessage({ cnxPitch: m }, "*"); } catch (e) {}
|
||
}
|
||
function recv(m) {
|
||
if (!m || m.from === ME || typeof m.n !== "number") return;
|
||
const id = m.from + ":" + m.t; if (id === lastMsg) return; lastMsg = id;
|
||
if (m.n !== cur) go(m.n, { remote: true });
|
||
}
|
||
window.addEventListener("storage", e => { if (e.key === KEY && e.newValue) { try { recv(JSON.parse(e.newValue)); } catch (err) {} } });
|
||
window.addEventListener("message", e => { if (e.data && e.data.cnxPitch) recv(e.data.cnxPitch); });
|
||
|
||
function openPresenter() {
|
||
const url = location.href.split("#")[0].split("?")[0] + "?presenter#" + (cur + 1);
|
||
pvWin = window.open(url, "cnxPitchPresenter", "width=1280,height=800");
|
||
}
|
||
function fullscreen() {
|
||
const d = document;
|
||
if (!d.fullscreenElement) { const r = d.documentElement.requestFullscreen; if (r) r.call(d.documentElement).catch(() => {}); }
|
||
else if (d.exitFullscreen) d.exitFullscreen();
|
||
}
|
||
|
||
/* --- overview --- */
|
||
let ov = null, ovOpen = false;
|
||
function cloneSlide(i) {
|
||
const c = slides[i].cloneNode(true);
|
||
c.classList.add("active"); c.classList.remove("before");
|
||
c.removeAttribute("aria-hidden");
|
||
c.querySelectorAll(".notes").forEach(x => x.remove());
|
||
c.querySelectorAll("[id]").forEach(x => x.removeAttribute("id"));
|
||
const m = document.createElement("div"); m.className = "mini"; m.appendChild(c);
|
||
return m;
|
||
}
|
||
function buildOverview() {
|
||
ov = document.createElement("div"); ov.className = "ov"; ov.id = "ov"; ov.hidden = true;
|
||
ov.innerHTML = '<h4>All slides · click to open · O or Esc to close</h4><div class="grid"></div>';
|
||
const g = ov.querySelector(".grid");
|
||
slides.forEach((s, i) => {
|
||
const b = document.createElement("button"); b.type = "button"; b.className = "th"; b.dataset.i = i;
|
||
b.setAttribute("aria-label", "Slide " + (i + 1) + ": " + (s.dataset.title || ""));
|
||
b.appendChild(cloneSlide(i));
|
||
const lab = document.createElement("span"); lab.className = "lab"; lab.textContent = (i + 1) + " · " + (s.dataset.title || ""); b.appendChild(lab);
|
||
b.addEventListener("click", () => { toggleOverview(false); go(i); });
|
||
g.appendChild(b);
|
||
});
|
||
document.body.appendChild(ov);
|
||
}
|
||
function scaleMinis(root) {
|
||
root.querySelectorAll(".mini").forEach(m => { const w = m.parentElement.clientWidth; if (w) m.style.transform = "scale(" + (w / 1920) + ")"; });
|
||
}
|
||
function markOverview() { if (ov) ov.querySelectorAll(".th").forEach(b => b.classList.toggle("cur", +b.dataset.i === cur)); }
|
||
function toggleOverview(on) {
|
||
if (!ov) buildOverview();
|
||
ovOpen = on == null ? !ovOpen : on;
|
||
ov.hidden = !ovOpen;
|
||
if (ovOpen) { scaleMinis(ov); markOverview(); const c = ov.querySelector(".th.cur"); if (c) c.focus({ preventScroll: true }); }
|
||
}
|
||
|
||
/* --- presenter view (same file, ?presenter) --- */
|
||
let pv = null, t0 = Date.now(), paused = 0, pauseAt = 0;
|
||
function buildPresenter() {
|
||
document.body.classList.add("pvmode");
|
||
document.title = "Presenter · CNX Vinyl: investment proposal";
|
||
pv = document.createElement("div"); pv.className = "pv"; pv.id = "pv";
|
||
pv.innerHTML =
|
||
'<header><span class="ttl">CNX Vinyl · presenter view</span><span class="cn" id="pvCnt"></span><span id="pvTitle"></span>' +
|
||
'<span class="tm" id="pvTime">00:00:00</span><button type="button" id="pvPause">Pause</button><button type="button" id="pvReset">Reset timer</button></header>' +
|
||
'<div class="body"><div class="col"><div class="lb">Current slide</div><div class="frame" id="pvCur"></div>' +
|
||
'<div class="nav"><button type="button" id="pvPrev">Previous</button><button type="button" id="pvNext">Next</button></div></div>' +
|
||
'<div class="col"><div class="lb">Next</div><div class="frame next" id="pvNext2"></div><div class="lb">Speaker notes</div><div class="nt" id="pvNotes"></div></div></div>';
|
||
document.body.appendChild(pv);
|
||
document.getElementById("pvReset").onclick = () => { t0 = Date.now(); paused = 0; if (pauseAt) pauseAt = Date.now(); tick(); };
|
||
document.getElementById("pvPause").onclick = e => {
|
||
if (pauseAt) { paused += Date.now() - pauseAt; pauseAt = 0; e.target.textContent = "Pause"; }
|
||
else { pauseAt = Date.now(); e.target.textContent = "Resume"; }
|
||
};
|
||
document.getElementById("pvPrev").onclick = prev;
|
||
document.getElementById("pvNext").onclick = next;
|
||
setInterval(tick, 500); tick();
|
||
}
|
||
function tick() {
|
||
const el2 = document.getElementById("pvTime"); if (!el2) return;
|
||
const ms = (pauseAt || Date.now()) - t0 - paused, s = Math.max(0, Math.floor(ms / 1000));
|
||
el2.textContent = [Math.floor(s / 3600), Math.floor(s / 60) % 60, s % 60].map(x => String(x).padStart(2, "0")).join(":");
|
||
}
|
||
function renderPresenter() {
|
||
if (!pv) return;
|
||
const a = document.getElementById("pvCur"), b = document.getElementById("pvNext2");
|
||
a.innerHTML = ""; a.appendChild(cloneSlide(cur));
|
||
b.innerHTML = "";
|
||
if (cur + 1 < N) b.appendChild(cloneSlide(cur + 1)); else b.innerHTML = '<div class="end">End of the deck</div>';
|
||
const nt = slides[cur].querySelector(".notes");
|
||
document.getElementById("pvNotes").textContent = nt ? nt.textContent.trim() : "";
|
||
document.getElementById("pvCnt").textContent = (cur + 1) + " / " + N;
|
||
document.getElementById("pvTitle").textContent = slides[cur].dataset.title || "";
|
||
scaleMinis(pv);
|
||
}
|
||
|
||
/* --- input --- */
|
||
function onKey(e) {
|
||
if (e.altKey || e.ctrlKey || e.metaKey) return;
|
||
const k = e.key;
|
||
if (/^[0-9]$/.test(k)) { buf += k; bufT = Date.now(); showJump(); return; }
|
||
if (k === "Enter" && buf) { const n = parseInt(buf, 10); buf = ""; showJump(); if (n >= 1) go(n - 1); e.preventDefault(); return; }
|
||
if (k === "Backspace" && buf) { buf = buf.slice(0, -1); showJump(); return; }
|
||
if (k === "Escape") { if (ovOpen) toggleOverview(false); buf = ""; showJump(); return; }
|
||
if (ovOpen && k !== "o" && k !== "O") return;
|
||
switch (k) {
|
||
case "ArrowRight": case "ArrowDown": case "PageDown": next(); break;
|
||
case " ": e.shiftKey ? prev() : next(); break;
|
||
case "ArrowLeft": case "ArrowUp": case "PageUp": prev(); break;
|
||
case "Home": go(0); break;
|
||
case "End": go(N - 1); break;
|
||
case "f": case "F": if (!PRESENTER) fullscreen(); break;
|
||
case "o": case "O": if (!PRESENTER) toggleOverview(); break;
|
||
case "p": case "P": if (!PRESENTER) openPresenter(); break;
|
||
default: return;
|
||
}
|
||
e.preventDefault();
|
||
}
|
||
let jumpEl = null;
|
||
function showJump() {
|
||
if (!jumpEl) { jumpEl = document.createElement("div"); jumpEl.className = "jump"; document.body.appendChild(jumpEl); }
|
||
jumpEl.hidden = !buf; jumpEl.textContent = "Go to slide " + buf + " ↵";
|
||
}
|
||
setInterval(() => { if (buf && Date.now() - bufT > 4000) { buf = ""; showJump(); } }, 1000);
|
||
|
||
let idle = 0;
|
||
function wake() { document.body.classList.add("ui"); clearTimeout(idle); idle = setTimeout(() => document.body.classList.remove("ui"), 2600); }
|
||
|
||
function init() {
|
||
chPrice(); chInvest(); chOp(); chCash(); chSteam(); chSolar(); chGantt();
|
||
fit();
|
||
window.addEventListener("resize", () => { fit(); if (ovOpen) scaleMinis(ov); if (pv) scaleMinis(pv); });
|
||
document.addEventListener("keydown", onKey);
|
||
window.addEventListener("hashchange", () => { const n = fromHash(); if (n !== cur) go(n); });
|
||
document.getElementById("bPrev").onclick = prev;
|
||
document.getElementById("bNext").onclick = next;
|
||
document.getElementById("bOv").onclick = () => toggleOverview();
|
||
document.getElementById("bPv").onclick = openPresenter;
|
||
document.getElementById("bFs").onclick = fullscreen;
|
||
document.addEventListener("mousemove", wake);
|
||
// touch: swipe left or right
|
||
let sx = 0, sy = 0, st = 0;
|
||
stage.addEventListener("touchstart", e => { const t = e.changedTouches[0]; sx = t.clientX; sy = t.clientY; st = Date.now(); wake(); }, { passive: true });
|
||
stage.addEventListener("touchend", e => {
|
||
const t = e.changedTouches[0], dx = t.clientX - sx, dy = t.clientY - sy;
|
||
if (Math.abs(dx) > 40 && Math.abs(dx) > Math.abs(dy) * 1.3 && Date.now() - st < 800) { dx < 0 ? next() : prev(); }
|
||
}, { passive: true });
|
||
if (PRESENTER) buildPresenter();
|
||
cur = -1; go(fromHash(), { remote: true });
|
||
if (!PRESENTER) wake();
|
||
}
|
||
init();
|
||
</script>
|
||
</body>
|
||
</html>
|