Order tool: fan funding; deck: light and dark, 60/40 shareholding
Deploy test site / deploy (push) Successful in 1m2s

User, 20 Oct 2026: can the deck also be made in a light and dark mode;
the new order tool should also offer fan funding, so the platform
screenshots match and use the same calculator; the main investor holds
at least 60 %, so 40 % is for sale.

Order tool (docs/order.html, /demos/order/):
- New step 15 "How to pay": pay the plant directly (deposit) or let
  fans fund it; step 16 becomes "Review & launch" with the campaign
  simulation and its three outcomes.
- Campaign maths ported from make-a-record (fan price, length, cap, own
  copies, VAT; fans needed = max(100, break-even); keep at the goal and
  with more fans; price hint); R gains fee (3.5 %), VAT and campaign
  length with sources; no price changed.
- Quote card shows fan price, fans needed and the artist's keep on the
  fan route; route and campaign in the #q= hash; deep links
  #s=<step>&p=<preset>&route=fans.
- Parity with make-a-record: 206/206 checks over 40 configurations;
  default quotes unchanged (37,836 / 217 / 102,936 THB; partner
  mastering 43,676).

Investor deck (docs/pitch/):
- Light and dark theme like the other site pages (cnx-theme, T key and
  button), presenter and overview follow; charts from CSS variables;
  text pairs at WCAG AA in both themes; tool screenshots in both themes.
- Two PDFs: dark and light (print-friendly), each ~1.5 MB.
- Platform slide: both pictures from the order tool (Splatter 12" order
  step and its fan-funding view: 100 fans at 1,200 THB, break-even 46,
  ~480 THB a copy kept).
- BOI and financing slide: shareholding bar, lead investor at least 60 %,
  at most 40 % for co-investors (~32M / ~22M THB at the ~54M equity
  floor, arithmetic only); the ask offers up to 40 %.

Docs: artist-platform.md (fan funding in the order tool), README and
landing page entries.
This commit is contained in:
grabowski committed 2026-10-20 14:31:40 +07:00
1 parent c8307f9d5e
commit 744a56d22b
16 files changed
+646 -174

No files matched your search

Binary file not shown.
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 138 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 145 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 142 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 62 KiB

+230 -105
View File
@@ -5,24 +5,75 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="robots" content="noindex">
<title>CNX Vinyl: investment proposal</title>
<script>
(function(){var t=null;try{t=localStorage.getItem("cnx-theme")}catch(e){}
if(t!=="light"&&t!=="dark")t=window.matchMedia&&matchMedia("(prefers-color-scheme: light)").matches?"light":"dark";
var q=/[?&]th=(light|dark)(&|$)/.exec(location.search);if(q)t=q[1];
document.documentElement.setAttribute("data-theme",t);})();
</script>
<style>
:root{
--bg:#0d0f14; --panel:#131720; --panel2:#171c26;
color-scheme:dark;
--outer:#07080b; --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);
--tx:#efe9dd; --tx2:#d2cbbd; --mu:#a59e91; --dim:#8a8377;
/* --ac: accent for text, --acf: accent for fills (the same in the dark theme) */
--ac:#e8b04a; --acf:#e8b04a; --acs:rgba(232,176,74,.14); --acl:rgba(232,176,74,.45); --acb:rgba(232,176,74,.35);
--b:#8eaac3; --bf:#8eaac3; --bs:rgba(142,170,195,.16);
--neg:#d6876c; --negf:#d6876c; --negs:rgba(214,135,108,.14);
/* surfaces */
--card-a:rgba(255,255,255,.035); --card-b:rgba(255,255,255,.015); --card-sh:none;
--fs-a:rgba(255,255,255,.04); --fs-b:rgba(255,255,255,.012);
--warn-a:rgba(232,176,74,.07); --warn-b:rgba(232,176,74,.025);
--fig-bg:#101319; --fig-sh:0 24px 70px rgba(0,0,0,.5); --stage-sh:0 0 0 1px rgba(255,255,255,.03);
--groove:rgba(232,176,74,.07); --chain-a:rgba(232,176,74,.7); --chain-b:rgba(232,176,74,.25);
--disc-hole:#0d0f14; --disc-rim:#0d0f14; --disc-g1:#2b2f37; --disc-g2:#16191f; --disc-ring:rgba(255,255,255,.1);
/* chart series */
--zero:rgba(236,230,218,.35); --whisk:rgba(236,230,218,.6); --rev:rgba(236,230,218,.32); --elec:rgba(236,230,218,.35);
--track:rgba(236,230,218,.12); --cmpline:rgba(236,230,218,.4); --cmpleg:rgba(236,230,218,.45); --dash:rgba(236,230,218,.22); --par:rgba(142,170,195,.55);
/* tags */
--tag-hi:#eaa58d; --tag-hi-bd:rgba(214,135,108,.5); --tag-md-bd:rgba(232,176,74,.45);
--tag-ok:#a9cdb1; --tag-ok-bd:rgba(156,192,164,.45); --tag-ok-bg:rgba(156,192,164,.1);
--tag-no:#c7c0b3; --tag-no-bd:rgba(236,230,218,.25);
/* chrome: control bar, overview, presenter */
--ctl-bg:rgba(18,21,28,.86); --ctl-bd:rgba(236,230,218,.12); --ctl-sh:0 10px 30px rgba(0,0,0,.4); --hov:rgba(255,255,255,.08);
--jump-bg:rgba(18,21,28,.9); --th-bd:rgba(236,230,218,.14); --th-hov:rgba(232,176,74,.6); --lab-a:rgba(7,8,11,.92); --lab-b:rgba(7,8,11,0);
--pv-bg:#0a0b0f; --btn:rgba(255,255,255,.06); --btn-h:rgba(255,255,255,.12); --nav-btn:rgba(255,255,255,.05); --well:rgba(255,255,255,.02);
--serif:"Iowan Old Style","Palatino Linotype",Palatino,"Book Antiqua",Georgia,serif;
--sans:system-ui,-apple-system,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;
}
/* light theme: warm paper, ink text, the accents darkened for text contrast (WCAG AA, pairs in the skill note) */
html[data-theme=light]{
color-scheme:light;
--outer:#e2dacb; --bg:#f4eee3; --panel:#faf6ee; --panel2:#efe7d8;
--line:rgba(60,45,20,.18); --line2:rgba(60,45,20,.10);
--tx:#1c1914; --tx2:#38332b; --mu:#5a5348; --dim:#675f53;
--ac:#875606; --acf:#b97e17; --acs:rgba(196,137,31,.15); --acl:rgba(185,126,23,.55); --acb:rgba(160,108,18,.38);
--b:#2c5677; --bf:#557ea3; --bs:rgba(85,126,163,.14);
--neg:#9b3d24; --negf:#b3563a; --negs:rgba(179,86,58,.10);
--card-a:rgba(255,255,255,.62); --card-b:rgba(255,255,255,.40); --card-sh:0 1px 2px rgba(60,45,20,.05);
--fs-a:rgba(255,255,255,.66); --fs-b:rgba(255,255,255,.40);
--warn-a:rgba(196,137,31,.12); --warn-b:rgba(196,137,31,.05);
--fig-bg:#ffffff; --fig-sh:0 18px 46px rgba(70,50,20,.16); --stage-sh:0 0 0 1px rgba(60,45,20,.08),0 20px 60px rgba(60,45,20,.12);
--groove:rgba(150,104,24,.09); --chain-a:rgba(185,126,23,.85); --chain-b:rgba(185,126,23,.3);
--disc-hole:#f4eee3; --disc-rim:#2a2621; --disc-g1:#4a443b; --disc-g2:#2a2621; --disc-ring:rgba(60,45,20,.18);
--zero:rgba(28,25,20,.45); --whisk:rgba(28,25,20,.6); --rev:#a59a87; --elec:#a59a87;
--track:rgba(60,45,20,.12); --cmpline:rgba(60,45,20,.55); --cmpleg:rgba(60,45,20,.55); --dash:rgba(60,45,20,.3); --par:rgba(85,126,163,.6);
--tag-hi:#9b3d24; --tag-hi-bd:rgba(155,61,36,.4); --tag-md-bd:rgba(135,86,6,.4);
--tag-ok:#2f6a3f; --tag-ok-bd:rgba(47,106,63,.4); --tag-ok-bg:rgba(47,106,63,.08);
--tag-no:#4a443a; --tag-no-bd:rgba(60,45,20,.28);
--ctl-bg:rgba(251,248,241,.92); --ctl-bd:rgba(60,45,20,.16); --ctl-sh:0 10px 30px rgba(60,45,20,.16); --hov:rgba(60,45,20,.08);
--jump-bg:rgba(251,248,241,.95); --th-bd:rgba(60,45,20,.16); --th-hov:rgba(185,126,23,.7); --lab-a:rgba(244,238,227,.95); --lab-b:rgba(244,238,227,0);
--pv-bg:#ebe4d7; --btn:rgba(60,45,20,.06); --btn-h:rgba(60,45,20,.12); --nav-btn:rgba(255,255,255,.55); --well:rgba(255,255,255,.5);
}
*{box-sizing:border-box}
html{-webkit-text-size-adjust:100%;text-size-adjust:100%}
html,body{margin:0;height:100%;background:#07080b;overflow:hidden}
html,body{margin:0;height:100%;background:var(--outer);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)}
.stage{position:fixed;left:0;top:0;width:1920px;height:1080px;transform-origin:0 0;background:var(--bg);overflow:hidden;box-shadow:var(--stage-sh)}
.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}
@@ -32,7 +83,7 @@ button{font:inherit;color:inherit}
/* 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);
background:repeating-radial-gradient(circle at var(--gx,104%) var(--gy,108%),var(--groove) 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}
@@ -41,8 +92,8 @@ button{font:inherit;color:inherit}
.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)}
background:radial-gradient(circle,var(--disc-hole) 0 7%,var(--acf) 8% 30%,var(--disc-rim) 31% 35%,transparent 36%),
repeating-radial-gradient(circle,var(--disc-g1) 0 1px,var(--disc-g2) 1px 2.2px);box-shadow:0 0 0 1px var(--disc-ring)}
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}
@@ -64,8 +115,8 @@ p{margin:0}
.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))}
.card{background:linear-gradient(180deg,var(--card-a),var(--card-b));border:1px solid var(--line2);border-radius:14px;padding:24px 28px;box-shadow:var(--card-sh)}
.card.warn{border-color:var(--acb);background:linear-gradient(180deg,var(--warn-a),var(--warn-b))}
.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)}
@@ -73,19 +124,19 @@ p{margin:0}
.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 li::before{content:"";position:absolute;left:0;top:12px;width:8px;height:8px;border-radius:50%;background:var(--acf);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)}
.tag.hi{color:var(--tag-hi);border-color:var(--tag-hi-bd);background:var(--negs)}
.tag.md{color:var(--ac);border-color:var(--tag-md-bd);background:var(--acs)}
.tag.ok{color:var(--tag-ok);border-color:var(--tag-ok-bd);background:var(--tag-ok-bg)}
.tag.no{color:var(--tag-no);border-color:var(--tag-no-bd)}
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{border-radius:14px;overflow:hidden;border:1px solid var(--line);box-shadow:var(--fig-sh);background:var(--fig-bg);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}
@@ -99,7 +150,7 @@ 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}
svg.chart .z{stroke:var(--zero);stroke-width:1}
/* ---------- slide 1 ---------- */
.s1{--gx:72%;--gy:52%;--gr:62%}
@@ -124,7 +175,7 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
.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::before{content:"";position:absolute;left:30px;right:0;top:15px;height:2px;background:linear-gradient(90deg,var(--chain-a),var(--chain-b))}
.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}
@@ -136,11 +187,11 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
/* ---------- 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{flex:1 1 0;min-width:0;border:1px solid var(--line2);background:linear-gradient(180deg,var(--fs-a),var(--fs-b));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 .ar{flex:0 0 18px;align-self:center;height:2px;background:var(--acl)}
.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}
@@ -155,11 +206,11 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
.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 5: two views of the same order tool ---------- */
.s5 .bd{grid-template-columns:1fr 1fr;gap:56px;margin-top:26px;align-content:start}
.pf{display:flex;flex-direction:column;min-width:0}
.pf .h{font-family:var(--serif);font-size:26px;line-height:1.22;color:var(--tx);margin-top:24px;padding-bottom:.1em}
.pf .x{font-size:18px;line-height:1.45;color:var(--tx2);margin-top:6px}
/* ---------- slide 6 ---------- */
.s6 .bd{grid-template-columns:1000px 1fr;gap:52px;margin-top:36px}
@@ -171,7 +222,7 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
.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)}
.note7{margin-top:22px;font-size:17px;line-height:1.45;color:var(--mu);padding-left:18px;border-left:2px solid var(--acl)}
/* ---------- slide 8 ---------- */
.s8 .bd{grid-template-columns:1000px 1fr;gap:56px;margin-top:30px}
@@ -195,7 +246,7 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
.kp th{padding:0 10px 8px}
/* ---------- slide 11 ---------- */
.s11 .bd{grid-template-columns:1fr 720px;gap:56px;margin-top:32px}
.s11 .bd{grid-template-columns:1fr 720px;gap:56px;margin-top:26px}
.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}
@@ -205,7 +256,18 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
.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}
.share{margin-top:18px}
.share h3{margin-bottom:12px}
.share .bar,.share .lbl{display:grid;grid-template-columns:60fr 40fr;gap:6px}
.share .bar i{display:block;height:14px;border-radius:4px}
.share .bar .a{background:var(--acf)}
.share .bar .c{background:var(--acs);border:1px dashed var(--acb)}
.share .lbl{margin-top:10px}
.share .lbl b{display:block;font-family:var(--serif);font-weight:500;font-size:25px;line-height:1.2;color:var(--tx);padding-bottom:.1em}
.share .lbl b.ac{color:var(--ac)}
.share .lbl span{display:block;font-size:15.5px;line-height:1.4;color:var(--mu);margin-top:2px}
.share .nt{font-size:15px;line-height:1.4;color:var(--dim);margin-top:8px}
.fin .tbd{margin-top:18px;font-family:var(--serif);font-size:23px;line-height:1.3;color:var(--tx);padding:16px 22px;border-left:3px solid var(--acf);background:var(--acs);border-radius:0 10px 10px 0}
/* ---------- slide 12 ---------- */
.s12 .bd{grid-template-rows:auto auto;gap:16px;margin-top:20px}
@@ -232,7 +294,7 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
/* ---------- 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}
.s15 .bd{grid-template-columns:1fr 600px;grid-template-rows:auto 1fr;gap:30px 56px;margin-top:24px}
.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)}
@@ -251,7 +313,7 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
.tb th.ac{color:var(--ac)}
.tb tr.tot td.neg{color:var(--neg)}
.tot2 .v .usd{font-family:var(--sans);font-size:19px;color:var(--mu);margin-left:14px;letter-spacing:.02em;vertical-align:.35em}
.cmpA{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:14px;padding:12px 22px;border:1px dashed rgba(236,230,218,.22);border-radius:12px;font-size:16px;line-height:1.4;color:var(--mu)}
.cmpA{display:flex;justify-content:space-between;align-items:center;gap:20px;margin-top:14px;padding:12px 22px;border:1px dashed var(--dash);border-radius:12px;font-size:16px;line-height:1.4;color:var(--mu)}
.cmpA b{font-family:var(--serif);font-size:28px;line-height:1.16;font-weight:500;color:var(--tx2);white-space:nowrap;padding-bottom:.1em}
.s8b .lede{font-size:22px;max-width:1640px}
.s8b .bd{grid-template-columns:1fr 760px;gap:60px;margin-top:40px}
@@ -271,51 +333,59 @@ svg.chart .z{stroke:rgba(236,230,218,.35);stroke-width:1}
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}
/* screenshots of the web tools: a dark and a light version, swapped with the theme */
.thl{display:none!important}
html[data-theme=light] .thl{display:block!important}
html[data-theme=light] .thd{display:none!important}
.ctl .ic-moon{display:none}
html[data-theme=light] .ctl .ic-sun{display:none}
html[data-theme=light] .ctl .ic-moon{display:block}
/* 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)}
background:var(--ctl-bg);border:1px solid var(--ctl-bd);backdrop-filter:blur(8px);-webkit-backdrop-filter:blur(8px);
box-shadow:var(--ctl-sh);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 button:hover,.ctl a:hover{background:var(--hov);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 .sep{width:1px;height:20px;background:var(--th-bd);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}
.jump{position:fixed;left:50%;top:18px;transform:translateX(-50%);background:var(--jump-bg);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{position:fixed;inset:0;background:var(--outer);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{position:relative;aspect-ratio:16/9;border-radius:8px;overflow:hidden;border:1px solid var(--th-bd);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}
.ov .th:hover{border-color:var(--th-hov)}
.ov .th .lab{position:absolute;left:0;right:0;bottom:0;padding:6px 10px;font-size:12px;text-align:left;background:linear-gradient(0deg,var(--lab-a),var(--lab-b));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{position:fixed;inset:0;background:var(--pv-bg);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 header button{background:var(--btn);border:1px solid var(--line);border-radius:8px;padding:6px 12px;cursor:pointer;font-size:13px;color:var(--tx2)}
.pv header button:hover{background:var(--btn-h)}
.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 .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:var(--well)}
.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)}
.pv .nav button{flex:1;background:var(--nav-btn);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 ---------- */
@@ -466,7 +536,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
</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>
<div class="fig"><img class="thd" src="img/record-passport-dark.webp" width="1590" height="816" alt="Record passport demo: the production history of one pressed copy"><img class="thl" src="img/record-passport-light.webp" width="1590" height="816" 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>
@@ -478,24 +548,28 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<!-- 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>
<h2>One order tool: an itemised quote, or a pressing funded by fans</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>
<div class="pf">
<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>
<div class="fig"><img class="thd" src="img/order-splatter-dark.webp" width="1800" height="1125" alt="Order tool, step 4: a 12 inch splatter record configured, with the estimated price and ship date"><img class="thl" src="img/order-splatter-light.webp" width="1800" height="1125" alt="Order tool, step 4: a 12 inch splatter record configured, with the estimated price and ship date"></div>
<figcaption>Step 4 with the "Splatter 12"" preset: the picture follows every option, the quote card every line. Prices and dates indicative, not an offer.</figcaption>
</figure>
<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, so every configuration is market data.</div>
</div>
<div class="pf">
<figure>
<div class="fig"><img class="thd" src="img/order-fans-dark.webp" width="1800" height="1125" alt="Order tool, fan-funding view: fan price, fans needed and what the artist keeps"><img class="thl" src="img/order-fans-light.webp" width="1800" height="1125" alt="Order tool, fan-funding view: fan price, fans needed and what the artist keeps"></div>
<figcaption>The same record funded by fans: at 1,200 THB a copy it needs 100 fans (break-even 46), and the artist keeps ~480 THB a copy after setup, pressing and fees. Indicative; the platform's name is not decided.</figcaption>
</figure>
<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). The fans needed are the larger of break-even and the 100-copy minimum run, a planning figure. Campaigns open only after the first releases ship.</div>
</div>
</div>
<p class="src"><b>Source:</b> artist-platform.md; business plan B, 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>
<p class="src"><b>Source:</b> artist-platform.md ("The goal", campaigns and payments); business plan B, sections 4.8 and 5.1; digital-factory.md "ERPNext: the plant's ERP". Screenshots: order.html, the step-by-step order tool (demo). Public side ~USD 40k to 90k <i>est.</i>, inside the software block.</p>
<footer class="ft"><span>CNX Vinyl · Investment proposal, Plan B (~215M THB) · 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 B, section 4.8.</aside>
<aside class="notes">The platform is the plant's front door, and both pictures are the same tool. Anyone, from a bedroom artist to a label, configures a record and gets an itemised offer from the price list that drives the invoice in ERPNext. The same order can instead be funded by fans: the artist sets the fan price, and the fans needed follow as the larger of break-even and the 100-copy minimum. Fans pay into a ring-fenced account and are refunded automatically if the goal is missed; the rule comes from Qrates, whose wind-down caught funded projects. A Thai legal opinion comes first, and seller of record is open. Sources: artist-platform.md; business plan B, section 4.8.</aside>
</section>
<!-- 6 -->
@@ -565,7 +639,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<h2>Plan B: ~217M THB at the midpoint, the launch plant with everything in</h2>
<div class="bd">
<div>
<div class="legend" style="margin-bottom:12px"><span><i style="background:var(--ac)"></i>Plan B, block midpoint</span><span><i style="background:transparent;height:2px;border-top:2px solid rgba(236,230,218,.6);border-radius:0;vertical-align:4px"></i>low to high end of the block</span><span>M THB <span class="e">est.</span></span></div>
<div class="legend" style="margin-bottom:12px"><span><i style="background:var(--acf)"></i>Plan B, block midpoint</span><span><i style="background:transparent;height:2px;border-top:2px solid var(--whisk);border-radius:0;vertical-align:4px"></i>low to high end of the block</span><span>M THB <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="Plan B investment by block group, low, midpoint and high"></svg>
</div>
<div>
@@ -632,7 +706,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<h2>Plan B's 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</span><span><i style="background:var(--b)"></i>Operating profit, after Plan B's depreciation</span><span>M USD a year</span></div>
<div class="legend" style="margin-bottom:10px"><span><i style="background:var(--rev)"></i>Revenue</span><span><i style="background:var(--acf)"></i>EBITDA</span><span><i style="background:var(--bf)"></i>Operating profit, after Plan B's depreciation</span><span>M USD a year</span></div>
<svg class="chart" id="chOp" width="900" height="560" viewBox="0 0 900 560" role="img" aria-label="Plan B revenue, EBITDA and operating profit by scenario"></svg>
</div>
<div>
@@ -669,7 +743,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<h2>Ten years of cash: Plan B back to zero in year ~4.2, IRR ~22&nbsp;%</h2>
<div class="bd">
<div>
<div class="legend" style="margin-bottom:10px"><span><i style="background:var(--ac)"></i>Plan B, cumulative cash</span><span><i style="background:transparent;height:2px;border-top:2px dashed rgba(236,230,218,.45);border-radius:0;vertical-align:4px"></i>Plan A, for comparison</span><span>M USD, year 0 to 10</span></div>
<div class="legend" style="margin-bottom:10px"><span><i style="background:var(--acf)"></i>Plan B, cumulative cash</span><span><i style="background:transparent;height:2px;border-top:2px dashed var(--cmpleg);border-radius:0;vertical-align:4px"></i>Plan A, for comparison</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 B, with Plan A faint for comparison"></svg>
</div>
<div>
@@ -688,7 +762,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
</div>
<p class="src"><b>Source:</b> business plan B, section 9.4 and figure 12; fleet-calculator.html, presets planB and planA, cash view (values read from the page, October 2026); revenue-model.md. Estimates.</p>
<footer class="ft"><span>CNX Vinyl · Investment proposal, Plan B (~215M THB) · 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: 850,000 records at the team's faster Duplex planning rate, its own cost build-up including biomass fuel, no PPA bill because Plan B owns the array, and a ramp of 35, 70 and 90&nbsp;% before the mature year. Year 0 is the midpoint investment plus a 15M THB reserve, 6.56M USD. On that basis Plan B is back to zero in year 4.2, with an IRR of about 22&nbsp;% and an NPV of about 4.7M USD at 10&nbsp;%. Pre-tax, pre-financing, no terminal value. Why not A? Its curve, faint here, turns earlier on less capital. Put the adverse case beside it: about 37 years. Sources: fleet-calculator.html; business plan B, section 9.4.</aside>
<aside class="notes">This curve is the calculator's, not the operating table's: 850,000 records at the team's faster Duplex planning rate, its own cost build-up including biomass fuel, no PPA bill because Plan B owns the array, and a ramp of 35, 70 and 90&nbsp;% before the mature year. Year 0 is the midpoint investment plus a 15M THB reserve, 6.56M USD. On that basis Plan B is back to zero in year 4.2, with an IRR of about 22&nbsp;% and an NPV of about 4.7M USD at 10&nbsp;%. Pre-tax, pre-financing, no terminal value. Why not A? Its curve turns earlier on less capital. Put the adverse case beside it: about 37 years. Sources: fleet-calculator.html; business plan B, section 9.4.</aside>
</section>
<!-- 12 -->
@@ -703,6 +777,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<div class="r"><div class="k">Solar</div><div class="x">No longer promoted since 1 July 2025 (Announcement 3/2568): Plan B's owned array earns the bill saving, no tax benefit. The 271 kWh battery 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 class="r"><div class="k">Without BOI</div><div class="x">~5&nbsp;% 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></div><div class="st"><span class="tag no">Avoided</span></div></div>
</div>
<div class="fin">
<h3>Funding need, Plan B: reserve in, no contingency</h3>
@@ -710,18 +785,25 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<div class="card warn"><div class="v ac">~217M THB</div><div class="l">~6.56M USD at the midpoint, labelled ~215M; ~156M to 277M low to high</div></div>
<div class="card"><div class="v">~54M THB</div><div class="l">equity at least: a quarter of the funding at the 3:1 ceiling (arithmetic)</div></div>
</div>
<dl class="dl" style="margin-top:22px;grid-template-columns:130px 1fr;gap:12px 18px">
<div class="share">
<h3>Shareholding</h3>
<div class="bar"><i class="a"></i><i class="c"></i></div>
<div class="lbl">
<div><b class="ac">At least 60&nbsp;%</b><span>the lead investor: at least ~32M THB</span></div>
<div><b>At most 40&nbsp;%</b><span>co-investors (Ploy Printing as intended strategic co-investor, the founders, others): at most ~22M THB</span></div>
</div>
<p class="nt">At the ~54M THB equity floor and one share price, arithmetic only: share price, valuation and in-kind shares not agreed. Terms in the shareholders' agreement.</p>
</div>
<dl class="dl" style="margin-top:16px;grid-template-columns:130px 1fr;gap:12px 18px">
<dt>Collateral</dt><dd style="font-size:17.5px">The machinery and equipment, in Plan B including the owned PV array and battery. The building only if the lease grants its ownership and the right to mortgage it. 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">Equipment (presses, metal path, metrology, lab) ~85M THB; building, crane, workshop and corridor ~40M; utilities ~35M; solar and battery ~15M; software, cassette and CD-R, freight, working capital and phase 0 ~28M (block midpoints <span class="e">est.</span>), plus the reserve, in tranches (slide 16).</dd>
<dt>Without BOI</dt><dd style="font-size:17.5px">~5&nbsp;% 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>
<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 B, sections 1 ("The ask"), 6.1, 7.1, 8.1, 8.4 and 12.2; total-investment.md "Launch plant". Equity floor and use-of-funds groups are arithmetic on the plan's figures, before financing costs.</p>
<p class="src"><b>Source:</b> boi.md; business plan B, sections 1 ("The ask"), 6.1, 7.1, 8.1, 8.4 and 12.2; total-investment.md "Launch plant" and "Two plans" (shareholding, 20 October 2026). Equity floor, the 60/40 amounts and use-of-funds groups are arithmetic on the plan's figures, before financing costs.</p>
<footer class="ft"><span>CNX Vinyl · Investment proposal, Plan B (~215M THB) · 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. Solar is no longer promoted since 1 July 2025, so Plan B owns its array for the bill alone; the battery may qualify. The condition that shapes financing is debt-to-equity no higher than 3:1, so at least a quarter of Plan B's 217M, about 54M THB, is equity. No loan structure, rate or debt-service figure is on file; the structure is to be agreed. Collateral is the machinery, the array and the battery; the land is leased. Sources: boi.md; business plan B, sections 7.1 and 12.2.</aside>
<aside class="notes">Vinyl pressing lands in BOI Group B: duty-free machinery, full foreign ownership and work permits, no tax holiday on the core activity. Solar is no longer promoted; Plan B owns its array for the bill. The condition that shapes financing is debt-to-equity no higher than 3:1: at least a quarter of Plan B's 217M, about 54M THB, is equity. The lead investor holds at least 60&nbsp;% of the company, so at most 40&nbsp;% is open to co-investors; at that floor and one share price, about 32M and 22M THB, arithmetic only. No loan terms are on file. Collateral: machinery, array, battery; the land is leased. Sources: boi.md; business plan B, sections 7.1 and 12.2.</aside>
</section>
<!-- 13 -->
@@ -794,7 +876,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
</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">Ploy Printing</div><div class="ro">Chiang Mai · packaging</div><div class="x">Jackets, sleeves and inserts; intends to co-invest within the 40&nbsp;% open beside 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>
@@ -822,11 +904,11 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<h2>Approve phase 0 now; commit to Plan B 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 band. Then, on the gates clearing, a commitment in principle to Plan B at ~215M THB (~217M at the midpoint), released in tranches.</p>
<div class="ask" style="display:grid;grid-template-columns:1fr 1fr;gap:44px;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:8px">~USD 40k to 80k over 3 to 6 months, part of the plan price: the BOI consultant, Thai counsel for the lease, zoning and the shareholders' agreement, a soil test, the quotes.</p></div>
<div><h3>On offer to co-investors</h3><div class="v">Up to 40&nbsp;%</div><p class="s" style="margin-top:8px">of the company, beside the lead investor's at least 60&nbsp;%: Ploy Printing, the founders, others. Share price and valuation not agreed; terms in the shareholders' agreement.</p></div>
</div>
<h3 style="margin-top:26px">Plan B tranches, share of the investment before the reserve <span class="e">est.</span></h3>
<h3 style="margin-top:24px">Then Plan B at ~215M THB in 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">M THB</th></tr>
<tr><td>0</td><td>Now</td><td>Phase 0 work</td><td class="n">1 to 2&nbsp;%</td><td class="n">1.3 to 2.6</td></tr>
@@ -849,6 +931,7 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<h3 style="margin-top:26px">Decisions for the investor</h3>
<ul class="list sm">
<li>The equity and debt mix within 3:1 (equity at least ~54M THB)</li>
<li>The shareholders' agreement: the split of the 40&nbsp;%, board, reserved matters and tranche releases</li>
<li>How quotes landing above the plan price are funded</li>
<li>Each Plan B item weighed against its lever; CD replication before the BOI filing; hedging of the EUR orders</li>
</ul>
@@ -859,9 +942,9 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<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>), PV on the PV-ready office roof and canopies (~120 kWp, ~2.4M to 3.1M THB <span class="e">est.</span>), the phase 2 building.</div></div>
</div>
</div>
<p class="src"><b>Source:</b> business plan B, sections 8.3, 14.1 and 14.2; roadmap.md phases 0 to 2. Tranche amounts are the shares applied to Plan B's ~201.6M THB before the reserve (arithmetic). Gates before any order: market validation, the BOI certificate, and land (zoning, lease terms, soil).</p>
<p class="src"><b>Source:</b> business plan B, sections 8.3, 12.2, 14.1 and 14.2; total-investment.md "Two plans" (shareholding); roadmap.md phases 0 to 2. Tranche amounts are the shares applied to Plan B's ~201.6M THB before the reserve (arithmetic). Gates before any order: market validation, the BOI certificate, and land (zoning, lease terms, soil).</p>
<footer class="ft"><span>CNX Vinyl · Investment proposal, Plan B (~215M THB) · Draft for discussion, October 2026</span><span class="pg"></span></footer>
<aside class="notes">Now: approve phase 0, about 1.3M to 2.6M THB over three to six months, part of the plan price, for the BOI consultant, counsel, the soil test and the quotes. Then, once market validation, the BOI certificate and the land gates clear, a commitment in principle to Plan B at about 215M THB, released in tranches so nothing is ordered before the certificate and validated pricing. Decisions: the equity and debt mix within 3:1, how quotes landing high are funded, and CD replication before the BOI filing. Why not A? It stays on file as the lowest-capital variant. Sources: business plan B, sections 8.3 and 14.</aside>
<aside class="notes">Now: approve phase 0, about 1.3M to 2.6M THB over three to six months, for the BOI consultant, counsel for the lease and the shareholders' agreement, the soil test and the quotes. Then, once validation, the BOI certificate and the land gates clear, a commitment in principle to Plan B at about 215M THB, released in tranches. On offer to co-investors: up to 40&nbsp;% of the company, as the lead investor holds at least 60&nbsp;%; share price and valuation are not agreed. Decisions: the equity and debt mix within 3:1, the shareholders' agreement, funding quotes that land high. Why not A? It stays on file as the lowest-capital variant. Sources: business plan B, sections 8.3, 12.2 and 14.</aside>
</section>
</div>
@@ -874,7 +957,8 @@ body.pvmode .stage,body.pvmode .ctl{display:none}
<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>
<button type="button" id="bTh" title="Light / dark (T)" aria-label="Light or dark theme"><svg class="ic-sun" viewBox="0 0 24 24"><circle cx="12" cy="12" r="4"/><path d="M12 2.6v2.3M12 19.1v2.3M2.6 12h2.3M19.1 12h2.3M5.4 5.4l1.6 1.6M17 17l1.6 1.6M5.4 18.6L7 17M17 7l1.6-1.6"/></svg><svg class="ic-moon" viewBox="0 0 24 24"><path d="M19.5 14.6A7.8 7.8 0 0 1 9.4 4.5a7.8 7.8 0 1 0 10.1 10.1z"/></svg></button>
<a class="pdf" id="bPdf" href="CNX-Vinyl-Investor-Deck.pdf" download title="Download the PDF, dark theme">PDF</a>
</nav>
<script>
@@ -956,9 +1040,12 @@ const DATA = {
Charts: inline SVG built from DATA
====================================================================== */
const NS = "http://www.w3.org/2000/svg";
/* fill and stroke go through inline style so they can take var(): the charts follow the theme
without a redraw (SVG presentation attributes cannot take var()) */
const STY = { fill: 1, stroke: 1 };
function el(p, tag, a, txt) {
const e = document.createElementNS(NS, tag);
for (const k in a) e.setAttribute(k, a[k]);
for (const k in a) { if (STY[k]) e.style.setProperty(k, a[k]); else e.setAttribute(k, a[k]); }
if (txt != null) e.textContent = txt;
p.appendChild(e); return e;
}
@@ -966,7 +1053,9 @@ const f1 = x => x.toFixed(1);
const f2 = x => (x < 0 ? "-" : "") + Math.abs(x).toFixed(2);
const fr = x => (Math.round(x * 100) % 10 ? x.toFixed(2) : x.toFixed(1));
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)" };
/* every chart colour is a CSS variable (see :root and html[data-theme=light]); ac/b/neg for text, acf/bf/negf for fills */
const C = {};
["ac", "acf", "acs", "b", "bf", "neg", "negf", "tx", "tx2", "mu", "dim", "bg", "line", "line2", "rev", "elec", "track", "cmpline", "par"].forEach(k => { C[k] = "var(--" + k + ")"; });
function chPrice() {
const s = document.getElementById("chPrice"); if (!s) return;
@@ -976,7 +1065,7 @@ function chPrice() {
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;
const y = 36 + i * 92, col = r.k === "cnx" ? C.acf : r.k === "th" ? C.negf : C.bf;
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 });
@@ -1002,7 +1091,7 @@ function chInvest() {
B.forEach((b, i) => {
const y = top + i * rh, res = b.k === "res", r = b.b, m = mid(r), yy = y + 14;
el(s, "text", { x: 0, y: y + 29, fill: res ? C.mu : C.tx2, "font-size": 16.5 }, b.label);
el(s, "rect", { x: X(0), y: yy, width: X(m) - X(0), height: 19, rx: 3, fill: C.ac, "fill-opacity": res ? 0.3 : 0.92, stroke: res ? C.ac : "none", "stroke-dasharray": res ? "4 3" : "" });
el(s, "rect", { x: X(0), y: yy, width: X(m) - X(0), height: 19, rx: 3, fill: C.acf, "fill-opacity": res ? 0.3 : 0.92, stroke: res ? C.acf : "none", "stroke-dasharray": res ? "4 3" : "" });
const yc = yy + 9.5, wk = { stroke: C.tx, "stroke-opacity": .6, "stroke-width": 1.5 };
el(s, "line", Object.assign({ x1: X(r[0]), x2: X(r[1]), y1: yc, y2: yc }, wk));
el(s, "line", Object.assign({ x1: X(r[0]), x2: X(r[0]), y1: yc - 6, y2: yc + 6 }, wk));
@@ -1029,12 +1118,12 @@ function chOp() {
el(s, "text", { x: L - 12, y: Y(t) + 5, "text-anchor": "end", "font-size": 14 }, t + "M");
}
const gw = (R - L) / 3, bw = 62, gap = 16;
const ser = [["revenue", "rgba(236,230,218,.32)", C.tx2], ["ebitda", C.ac, C.ac], ["op", C.b, C.b]];
const ser = [["revenue", C.rev, C.tx2], ["ebitda", C.acf, C.ac], ["op", C.bf, C.b]];
D.cases.forEach((c, i) => {
const gx = L + gw * i + (gw - (bw * ser.length + gap * (ser.length - 1))) / 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, "rect", { x: x, y: Math.min(y0, y1), width: bw, height: Math.max(1, Math.abs(y1 - y0)), rx: 3, fill: v < 0 ? C.negf : 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": 15, "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);
@@ -1054,15 +1143,15 @@ function chCash() {
D.years.forEach(y => el(s, "text", { x: X(y), y: B + 28, "text-anchor": "middle", "font-size": 14 }, y === 0 ? "Y0" : String(y)));
const a = D.a.cum, b = D.b.cum;
// Plan A, faint
el(s, "polyline", { points: a.map((v, i) => X(i).toFixed(1) + "," + Y(v).toFixed(1)).join(" "), fill: "none", stroke: "rgba(236,230,218,.4)", "stroke-width": 2, "stroke-dasharray": "7 6", "stroke-linejoin": "round" });
el(s, "polyline", { points: a.map((v, i) => X(i).toFixed(1) + "," + Y(v).toFixed(1)).join(" "), fill: "none", stroke: C.cmpline, "stroke-width": 2, "stroke-dasharray": "7 6", "stroke-linejoin": "round" });
el(s, "text", { x: X(10) + 12, y: Y(a[10]) - 4, fill: C.mu, "font-size": 14 }, "A " + f2(a[10]));
// Plan B payback marker
const px = X(D.b.pay);
el(s, "line", { x1: px, x2: px, y1: Y(0), y2: Y(-4.6), stroke: C.ac, "stroke-dasharray": "3 4", "stroke-width": 1.5 });
el(s, "line", { x1: px, x2: px, y1: Y(0), y2: Y(-4.6), stroke: C.acf, "stroke-dasharray": "3 4", "stroke-width": 1.5 });
el(s, "text", { x: px + 8, y: Y(-5.4), fill: C.ac, "font-size": 15.5, "font-weight": 700 }, "back to zero, year ~" + D.b.pay);
// Plan B
el(s, "polyline", { points: b.map((v, i) => X(i).toFixed(1) + "," + Y(v).toFixed(1)).join(" "), fill: "none", stroke: C.ac, "stroke-width": 4, "stroke-linejoin": "round" });
b.forEach((v, i) => el(s, "circle", { cx: X(i), cy: Y(v), r: 5.5, fill: "#0d0f14", stroke: C.ac, "stroke-width": 2.8 }));
el(s, "polyline", { points: b.map((v, i) => X(i).toFixed(1) + "," + Y(v).toFixed(1)).join(" "), fill: "none", stroke: C.acf, "stroke-width": 4, "stroke-linejoin": "round" });
b.forEach((v, i) => el(s, "circle", { cx: X(i), cy: Y(v), r: 5.5, fill: C.bg, stroke: C.acf, "stroke-width": 2.8 }));
el(s, "text", { x: X(0) + 12, y: Y(b[0]) + 26, fill: C.ac, "font-size": 16, "font-weight": 700 }, f2(b[0]));
el(s, "text", { x: X(10) + 12, y: Y(b[10]) + 18, fill: C.ac, "font-size": 16, "font-weight": 700 }, f2(b[10]));
}
@@ -1070,7 +1159,7 @@ function chCash() {
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) => {
[["Biomass fuel", D.biomass, C.acf], ["Electric steam", D.electric, C.elec]].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 });
@@ -1081,8 +1170,8 @@ function chSteam() {
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, "rect", { x: 0, y: 24, width: W - 2, height: 18, rx: 3, fill: C.track });
el(s, "rect", { x: 0, y: 24, width: (W - 2) * D.used / D.plant, height: 18, rx: 3, fill: C.acf });
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");
@@ -1109,15 +1198,15 @@ function chGantt() {
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, "rect", { x: x, y: y + 6, width: X(r.e) - x, height: 13, rx: 3, fill: C.acs, stroke: C.acf, "stroke-dasharray": "4 3" });
el(s, "path", { d: `M${x} ${y + 3} l7 9.5 -7 9.5 -7 -9.5z`, fill: C.acf });
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)" });
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.acf : C.par });
(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 });
el(s, "path", { d: `M${x} ${y + 3} l6 9.5 -6 9.5 -6 -9.5z`, fill: C.tx, stroke: C.bg, "stroke-width": 1.5 });
});
});
// milestone labels for the BOI row, placed beside the bar
@@ -1129,9 +1218,9 @@ function chGantt() {
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, "rect", { x: 112, y: ly - 11, width: 26, height: 12, rx: 3, fill: C.acf });
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, "rect", { x: 260, y: ly - 11, width: 26, height: 12, rx: 3, fill: C.par });
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");
}
@@ -1173,28 +1262,60 @@ function go(n, opt) {
}
const next = () => go(cur + 1), prev = () => go(cur - 1);
/* --- sync between the audience window and the presenter window --- */
/* --- sync between the audience window and the presenter window: slide and theme --- */
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() };
function post(m) {
m.from = ME; m.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) {}
}
const send = n => post({ n: n });
function recv(m) {
if (!m || m.from === ME || typeof m.n !== "number") return;
if (!m || m.from === ME) return;
const id = m.from + ":" + m.t; if (id === lastMsg) return; lastMsg = id;
if (m.n !== cur) go(m.n, { remote: true });
if (m.th) { setTheme(m.th, { remote: true }); return; }
if (typeof m.n === "number" && 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("storage", e => {
if (e.key === KEY && e.newValue) { try { recv(JSON.parse(e.newValue)); } catch (err) {} }
if (e.key === "cnx-theme" && (e.newValue === "light" || e.newValue === "dark")) setTheme(e.newValue, { remote: true });
});
window.addEventListener("message", e => { if (e.data && e.data.cnxPitch) recv(e.data.cnxPitch); });
/* --- light / dark: localStorage cnx-theme, else prefers-color-scheme (followed live until the user picks) --- */
const de = document.documentElement;
const PDFS = { dark: ["CNX-Vinyl-Investor-Deck.pdf", "Download the PDF, dark theme"],
light: ["CNX-Vinyl-Investor-Deck-light.pdf", "Download the PDF, light theme (print-friendly)"] };
const theme = () => de.getAttribute("data-theme") === "light" ? "light" : "dark";
function setTheme(th, opt) {
opt = opt || {};
if (th !== "light" && th !== "dark") return;
if (opt.save) { try { localStorage.setItem("cnx-theme", th); } catch (e) {} }
const changed = th !== theme();
de.setAttribute("data-theme", th);
themeUi();
if (changed && !opt.remote) post({ th: th });
}
function themeUi() {
const th = theme(), a = document.getElementById("bPdf"), b = document.getElementById("bTh"), pb = document.getElementById("pvTheme");
if (a) { a.setAttribute("href", PDFS[th][0]); a.title = PDFS[th][1]; }
if (b) b.setAttribute("aria-pressed", th === "light" ? "true" : "false");
if (pb) pb.textContent = th === "light" ? "Dark (T)" : "Light (T)";
}
const toggleTheme = () => setTheme(theme() === "light" ? "dark" : "light", { save: true });
const mq = window.matchMedia && matchMedia("(prefers-color-scheme: light)");
if (mq && mq.addEventListener) mq.addEventListener("change", e => {
let s = null; try { s = localStorage.getItem("cnx-theme"); } catch (_) {}
if (s !== "light" && s !== "dark") setTheme(e.matches ? "light" : "dark", { remote: true });
});
function openPresenter() {
const url = location.href.split("#")[0].split("?")[0] + "?presenter#" + (cur + 1);
const url = location.href.split("#")[0].split("?")[0] + "?presenter&th=" + theme() + "#" + (cur + 1);
pvWin = window.open(url, "cnxPitchPresenter", "width=1280,height=800");
}
function fullscreen() {
@@ -1216,7 +1337,7 @@ function cloneSlide(i) {
}
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>';
ov.innerHTML = '<h4>All slides · click to open · O or Esc to close · T light / dark</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;
@@ -1247,7 +1368,7 @@ function buildPresenter() {
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>' +
'<span class="tm" id="pvTime">00:00:00</span><button type="button" id="pvPause">Pause</button><button type="button" id="pvReset">Reset timer</button><button type="button" id="pvTheme" title="Light / dark for both windows (T)">Light (T)</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>';
@@ -1259,6 +1380,7 @@ function buildPresenter() {
};
document.getElementById("pvPrev").onclick = prev;
document.getElementById("pvNext").onclick = next;
document.getElementById("pvTheme").onclick = toggleTheme;
setInterval(tick, 500); tick();
}
function tick() {
@@ -1287,7 +1409,7 @@ function onKey(e) {
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;
if (ovOpen && !/^[oOtT]$/.test(k)) return;
switch (k) {
case "ArrowRight": case "ArrowDown": case "PageDown": next(); break;
case " ": e.shiftKey ? prev() : next(); break;
@@ -1297,6 +1419,7 @@ function onKey(e) {
case "f": case "F": if (!PRESENTER) fullscreen(); break;
case "o": case "O": if (!PRESENTER) toggleOverview(); break;
case "p": case "P": if (!PRESENTER) openPresenter(); break;
case "t": case "T": toggleTheme(); break;
default: return;
}
e.preventDefault();
@@ -1322,6 +1445,7 @@ function init() {
document.getElementById("bOv").onclick = () => toggleOverview();
document.getElementById("bPv").onclick = openPresenter;
document.getElementById("bFs").onclick = fullscreen;
document.getElementById("bTh").onclick = toggleTheme;
document.addEventListener("mousemove", wake);
// touch: swipe left or right
let sx = 0, sy = 0, st = 0;
@@ -1331,6 +1455,7 @@ function init() {
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();
themeUi();
cur = -1; go(fromHash(), { remote: true });
if (!PRESENTER) wake();
}