From 5090609df3f24f46e259fec2d9f32e7dbc9339c5 Mon Sep 17 00:00:00 2001 From: grabowski Date: Fri, 16 Oct 2026 09:42:57 +0700 Subject: [PATCH] Calculators: options from Record Industry's calculator; its prices as a benchmark Reviewed Record Industry's (Haarlem, NL) public vinyl calculator: a seven-step wizard with a live itemised cost list priced on its server. Read its prices for a reference 12" LP at 200 / 300 / 500 / 1,000 copies and for each option worth copying (checked October 2026). Both artist demos (make-a-record and artist-order): - audio arrives as digital files, analog tape (+ tape transfer) or a cut lacquer (no blanks, cutting or mastering; mastering choice disabled with the reason; cutting and mastering stages skipped in the timeline; the passport starts at "Lacquer received") - labels printed, plain white or plain black (credit line; the label artwork drop is disabled for plain labels; the mock-up shows the plain label) - press cleaning before a colour run, on by default, disabled for black - 7" dinked (large centre hole), 7" only, drawn in the mock-up - download card and barcode sticker extras - run-out inscription (A-Z, 0-9, up to 15), scribed along the dead wax in the mock-up and named in the passport - new rates (tape 3,800, cleaning 2,300 per title; plain labels -12 per disc; download card 43, barcode 6 per copy) are estimates with Record Industry's prices as the basis, each listed in the detailed demo - make-a-record investor notes: Record Industry's 9 to 11 week quote after test-pressing approval against the 7 to 9 week CNX quote, and its 2 % reject allowance against 100 % optical inspection Defaults unchanged (37,836 THB per title, 232 per copy at 100, break-even 41, goal 100, 548 kept). Docs: artist-platform.md (choices table, benchmark section: what was taken, what was left out, commercial terms worth deciding); revenue-model.md (second Western data point: 219 THB a copy at 300 ex-works, per-title and per-copy reading against the assumptions, not a re-base); production-timeline.md (its published lead times). --- docs/artist-order.html | 67 +++++++++++++++++++++++-------- docs/artist-platform.md | 47 ++++++++++++++++++++-- docs/finance/revenue-model.md | 41 +++++++++++++++++++ docs/make-a-record.html | 75 +++++++++++++++++++++++++---------- docs/production-timeline.md | 7 +++- 5 files changed, 195 insertions(+), 42 deletions(-) diff --git a/docs/artist-order.html b/docs/artist-order.html index a3a5d77..c59db33 100644 --- a/docs/artist-order.html +++ b/docs/artist-order.html @@ -76,6 +76,8 @@ document.documentElement.setAttribute("data-theme",t);})(); .drop { display: flex; align-items: center; gap: 8px; border: 1.5px dashed var(--stroke); border-radius: 6px; padding: 8px 12px; margin: 8px 0; color: var(--mute); font-size: 14px; cursor: pointer; } .drop:hover, .drop.over { border-color: var(--accent); color: var(--fg); } .drop b { color: var(--fg); font-weight: 600; } + .drop.off { opacity: 0.4; cursor: not-allowed; } + .drop.off:hover { border-color: var(--stroke); color: var(--mute); } table { border-collapse: collapse; width: 100%; font-size: 14px; } th, td { padding: 6px 8px; border-bottom: 1px solid var(--line); text-align: left; vertical-align: top; } th { color: var(--mute); font-weight: 600; font-size: 12.5px; } @@ -194,9 +196,11 @@ document.documentElement.setAttribute("data-theme",t);})();
Size
Speed
Discs
+
Centre hole
Weight
Vinyl colour
Colours
+
Press cleaning

Copies, for a direct order

@@ -206,18 +210,22 @@ document.documentElement.setAttribute("data-theme",t);})();

Sleeves and jacket

+
Labels
Inner sleeve
Jacket
Extras -
-

Labels are printed and pressed into the record, the only kind this plant makes. Printed jackets come from a local packaging partner and are passed through at the partner's price plus a stated handling margin.

+ + +
+

Labels are paper labels pressed into the record, printed or plain white or black. Printed jackets come from a local packaging partner and are passed through at the partner's price plus a stated handling margin.

Audio and test pressings

-
Master
+
Audio arrives as
+
Master
Test pressings for you

Cutting and plating happen in-house: lacquer, father, mother and stampers. The mother is kept, so a repress later skips cutting.

@@ -229,6 +237,7 @@ document.documentElement.setAttribute("data-theme",t);})();

Release

Artist
Title
+
Run-out inscription scribed next to the run-out groove, A to Z and 0 to 9, up to 15

Tracks and sides

@@ -353,6 +362,11 @@ const R={ insert:{v:12,u:"per copy",t:"Printed insert",k:"est",src:"Estimate."}, sticker:{v:6,u:"per copy",t:"Hype sticker",k:"est",src:"Estimate."}, numbered:{v:5,u:"per copy",t:"Hand-numbered edition",k:"est",src:"Estimate."}, + tape:{v:3800,u:"per title",t:"Analog tape transfer",k:"est",src:"Estimate. Record Industry (NL) charges 100 EUR more for cutting from tape than from a digital master (online calculator, checked October 2026)."}, + clean:{v:2300,u:"per title",t:"Press cleaned before a colour run",k:"est",src:"Estimate. Record Industry charges 60 EUR for press cleaning and warns that without it records can carry the previous colour (online calculator, October 2026)."}, + plainLabel:{v:-12,u:"per disc",t:"Plain labels instead of printed (credit)",k:"est",src:"Estimate. Record Industry prices white unprinted labels about 0.32 EUR a record below CMYK printed at 300 copies (online calculator, October 2026)."}, + dlcard:{v:43,u:"per copy",t:"Download card, printed both sides",k:"est",src:"Estimate. Record Industry: 361 EUR for a 4/4 card at 300 copies, about 1.13 EUR a copy (online calculator, October 2026)."}, + barcode:{v:6,u:"per copy",t:"Barcode sticker on the back",k:"est",src:"Estimate. Record Industry: 0.15 EUR a copy (online calculator, October 2026)."}, fee:{v:0.035,u:"of the fan price",t:"Payment fees",k:"doc",src:"About 3 to 4 % on cards, less on PromptPay, to be confirmed with the provider (artist platform)."}, minRun:{v:100,u:"copies",t:"Minimum campaign run",k:"doc",src:"Planning figure, to be decided (artist platform, open decision 4)."}, deposit:{v:0.5,u:"of a direct order",t:"Deposit on a direct order",k:"est",src:"Placeholder; deposit terms are not set yet."} @@ -400,6 +414,7 @@ const S={ step:1,cur:"THB",size:"12",rpm:"33",discs:1,weight:140,colour:"black",c1:"#c0392b",c2:"#f2c14e", qty:300,inner:"paper",jacket:"printed",shrink:true,insert:false,sticker:false,numbered:false, mastering:true,tps:5,loud:false,artist:"Demo Artist",title:"Night Market Songs", + source:"digital",labels:"printed",cleaning:true,dinked:false,dlcard:false,barcode:false,catno:"DEMO001", tracks:[],demoTracks:true,rights:false,gate1:false,art:{jacket:null,label:null}, fanPrice:1200,days:30,cap:0,artistCopies:0,vat:false, mode:null,launched:false,launchDate:null,scen:"make",simDay:0,outcome:null,outcomeAct:null,finalFans:0,prod:null, @@ -424,25 +439,32 @@ function addWD(d,wd){const x=new Date(d);let n=Math.ceil(wd);while(n>0){x.setDat const sideNames=()=>S.discs>1?["A","B","C","D"]:["A","B"]; const rpmT=()=>S.rpm==="33"?"33⅓ rpm":"45 rpm"; const colourT=()=>({black:"black",solid:"solid colour",marbled:"marbled",splatter:"splatter"})[S.colour]; -function descT(){return (S.discs>1?"2 × ":"")+S.size+"\", "+rpmT()+", "+(S.size==="12"?S.weight+" g ":"")+colourT()+", "+JNAME[S.jacket].toLowerCase();} +const dinkOn=()=>S.dinked&&S.size==="7",mastOn=()=>S.mastering&&S.source!=="lacquer",cleanOn=()=>S.cleaning&&S.colour!=="black"; +/* a stage runs unless the order skips it: no mastering or cutting when the customer sends a cut lacquer */ +const stageOn=s=>s.opt==="mastering"?mastOn():s.id==="cut"?S.source!=="lacquer":true; +function descT(){return (S.discs>1?"2 × ":"")+S.size+"\""+(dinkOn()?" dinked":"")+", "+rpmT()+", "+(S.size==="12"?S.weight+" g ":"")+colourT()+", "+JNAME[S.jacket].toLowerCase();} function toast(t){const el=document.createElement("div");el.className="toast";el.textContent=t;document.body.appendChild(el);setTimeout(()=>el.remove(),2200);} /* ---- pricing ---- */ function quote(q){ const sz=S.size,d=S.discs,sides=2*d,T=[],C=[],du=d>1?"discs":"disc"; - if(S.mastering)T.push({t:"Vinyl mastering",n:sides,u:"sides",r:R.master.v}); - T.push({t:"Lacquer blanks, 70 EUR each",n:sides,u:"sides",r:R.blank.v}); - T.push({t:"Lacquer cutting",n:sides,u:"sides",r:R.cut.v}); + if(mastOn())T.push({t:"Vinyl mastering",n:sides,u:"sides",r:R.master.v}); + if(S.source==="tape")T.push({t:"Analog tape transfer",n:1,u:"",r:R.tape.v}); + if(S.source!=="lacquer"){T.push({t:"Lacquer blanks, 70 EUR each",n:sides,u:"sides",r:R.blank.v});T.push({t:"Lacquer cutting",n:sides,u:"sides",r:R.cut.v});} T.push({t:"Electroforming: father, mother (kept for represses), stamper",n:sides,u:"sides",r:R.metal.v}); T.push({t:"Press setup",n:d,u:du,r:R.setup.v}); if(S.tps>0)T.push({t:"Test pressings for you",n:S.tps,u:"copies",r:R.tp.v}); + if(cleanOn())T.push({t:"Press cleaned before your colour",n:1,u:"",r:R.clean.v}); C.push({t:"Pressing, "+sz+"\""+(sz==="12"?" 140 g":"")+", printed labels",n:d,u:du,r:R.press.v[sz]}); if(sz==="12"&&S.weight===180)C.push({t:"180 g instead of 140 g",n:d,u:du,r:R.g180.v}); if(S.colour!=="black")C.push({t:"Colour vinyl, "+colourT(),n:d,u:du,r:R.colour.v[S.colour]}); C.push({t:"Inner sleeve, "+(S.inner==="poly"?"poly-lined":"paper"),n:d,u:du,r:R.inner.v[S.inner][sz]}); C.push({t:"Assembly and packing",n:1,u:"",r:R.assy.v}); if(S.jacket!=="none"){const p=R.jacket.v[S.jacket][sz][tierIdx(q)];C.push({t:JNAME[S.jacket]+(sz==="7"&&S.jacket!=="plain"?" (picture sleeve)":""),n:1,u:"",r:Math.round(p*(1+R.handling.v)),pass:p});} + if(S.labels!=="printed")C.push({t:"Plain "+S.labels+" labels instead of printed",n:d,u:du,r:R.plainLabel.v}); if(S.shrink)C.push({t:"Shrink wrap",n:1,u:"",r:R.shrink.v}); + if(S.dlcard)C.push({t:"Download card",n:1,u:"",r:R.dlcard.v}); + if(S.barcode)C.push({t:"Barcode sticker",n:1,u:"",r:R.barcode.v}); if(S.insert)C.push({t:"Printed insert",n:1,u:"",r:R.insert.v}); if(S.sticker)C.push({t:"Hype sticker",n:1,u:"",r:R.sticker.v}); if(S.numbered)C.push({t:"Hand-numbered",n:1,u:"",r:R.numbered.v}); @@ -452,7 +474,7 @@ function quote(q){ } function leadInfo(campaign){ const g3=STG.findIndex(s=>s.id==="g3"); - const pre=STG.slice(0,g3+1).filter(s=>(!s.opt||S[s.opt])&&!((campaign||S.gate1)&&(s.id==="intake"||s.id==="g1"))); + const pre=STG.slice(0,g3+1).filter(s=>stageOn(s)&&!((campaign||S.gate1)&&(s.id==="intake"||s.id==="g1"))); const lo=pre.reduce((a,s)=>a+s.d[0],0),hi=pre.reduce((a,s)=>a+s.d[1],0); const pw=[Math.max(1,Math.round(lo/5)),Math.max(1,Math.ceil(hi/5))]; const printed=S.jacket==="printed"||S.jacket==="gatefold"; @@ -572,7 +594,9 @@ function artChecks(k){ function checkList(){ const L=[],files=S.tracks.filter(t=>t.file),ready=files.filter(t=>t.info&&!t.info.reading); if(!S.tracks.length)L.push(["bad","Track list","Add at least one track."]); - if(!files.length)L.push(["pend","Master files","None added: the checks use the lengths typed in. Drop WAV, FLAC or AIFF files to check the files themselves."]); + if(S.source==="lacquer")L.push(["pend","Lacquer","Cut by the customer's studio and sent by express courier the day it is cut: a lacquer has to be plated within days. Inspected under the microscope on arrival. Files are for the side plan and previews only."]); + else if(S.source==="tape")L.push(["pend","Tape","Analog tape is transferred and checked on arrival; format, speed and EQ curve are confirmed with the customer first."]); + if(S.source!=="digital"){}else if(!files.length)L.push(["pend","Master files","None added: the checks use the lengths typed in. Drop WAV, FLAC or AIFF files to check the files themselves."]); else{ const lossy=ready.filter(t=>!t.info.lossless); L.push(lossy.length?["bad","File format",lossy.map(t=>esc(t.file.name)).join(", ")+": not WAV, FLAC or AIFF. Send the lossless master."]:["ok","File format",ready.length+" lossless file"+(ready.length===1?"":"s")]); @@ -588,7 +612,7 @@ function checkList(){ :warn.length?["warn","Side length",warn.map(s=>"side "+s.n+" "+mmss(s.sec)).join(", ")+": past the comfortable "+mmss(lim[0])+", so the cut gets quieter"] :empty.length?["warn","Side length","Side "+empty.map(s=>s.n).join(", ")+" has no music"]:["ok","Side length","Every side within the comfortable "+mmss(lim[0])]); if(S.jacket==="printed"||S.jacket==="gatefold"){const c=artChecks("jacket");L.push(!c?["pend","Jacket artwork","Not added yet"]:c.some(x=>x[0]==="bad")?["bad","Jacket artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:c.some(x=>x[0]!=="ok")?["warn","Jacket artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:["ok","Jacket artwork","Size, resolution and colour space fine"]);} - {const c=artChecks("label");L.push(!c?["pend","Label artwork","Not added yet"]:c.some(x=>x[0]==="bad")?["bad","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:c.some(x=>x[0]!=="ok")?["warn","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:["ok","Label artwork","Size, resolution and colour space fine"]);} + if(S.labels==="printed"){const c=artChecks("label");L.push(!c?["pend","Label artwork","Not added yet"]:c.some(x=>x[0]==="bad")?["bad","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:c.some(x=>x[0]!=="ok")?["warn","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:["ok","Label artwork","Size, resolution and colour space fine"]);} L.push(["pend","Engineer check","After upload a cutting engineer confirms that side length and level are cuttable. This decides, not the table above."]); L.push(["pend","Rights check","An audio fingerprint check catches obvious bootlegs before a person looks; you confirm the rights below."]); return L; @@ -610,12 +634,15 @@ function mockSVG(id){ let g=``; if(S.colour==="marbled")g+=``; if(S.colour==="splatter"){const r=rnd(7);let dots="";for(let i=0;i<80;i++){const a=r()*6.283,d=Math.sqrt(r())*100,s=1+r()*5;dots+=``;}g+=`${dots}`;} - for(let r=100;r>=rl+5;r-=4)g+=``; + for(let r=100;r>=rl+10;r-=4)g+=``; + if(S.catno){const rr=(rl+5).toFixed(1);g+=`${esc(S.catno)} A`;} g+=``; const L=rl.toFixed(1); - if(S.art.label&&S.art.label.url)g+=``; + if(S.labels==="white")g+=``; + else if(S.labels==="black")g+=``; + else if(S.art.label&&S.art.label.url)g+=``; else g+=`${esc(S.artist.slice(0,20))}${esc(S.title.slice(0,24))}`; - return g+``; + return g+``; } const recs=(S.discs>1?disc(cx+32*k):"")+disc(cx); let jk="";const J={x:0,y:0,w:212}; @@ -660,6 +687,13 @@ function syncSegs(){ $("#c1").title=blk?"Black vinyl: no colour to choose":"Vinyl colour"; $("#c2").title=two?"Second colour":"Only for marbled and splatter"; $("#colHint").textContent=blk?"black vinyl, no colour to choose":S.colour==="solid"?"vinyl colour":"base and second colour"; + const s7=S.size==="7";$$("#rowDink button").forEach(x=>{x.disabled=!s7;x.title=s7?"":"Only 7\" singles come with a large centre hole";});$("#rowDink").classList.toggle("off",!s7); + $$("#rowClean button").forEach(x=>{x.disabled=blk;x.title=blk?"Black vinyl needs no cleaning":"";});$("#rowClean").classList.toggle("off",blk); + $("#cleanHint").textContent=blk?"only for colour vinyl":S.cleaning?"purged first; light colours can still show the odd speck":"some records can carry streaks of the previous colour"; + const offJ=!(S.jacket==="printed"||S.jacket==="gatefold"),offL=S.labels!=="printed"; + $("#dropJacket").classList.toggle("off",offJ);$("#fileJacket").disabled=offJ;$("#dropJacket").title=offJ?(S.jacket==="none"?"The customer supplies the jacket":"A plain jacket has no print"):""; + $("#dropLabel").classList.toggle("off",offL);$("#fileLabel").disabled=offL;$("#dropLabel").title=offL?"Plain labels have no print":""; + const lq=S.source==="lacquer";$$("#rowMaster button").forEach(x=>{x.disabled=lq;x.title=lq?"The lacquer is already cut and mastered":"";if(lq)x.classList.remove("on");});$("#rowMaster").classList.toggle("off",lq); } function renderNav(){ const done={1:!!S.visited[2],2:S.gate1,3:!!S.mode,4:S.launched,5:false}; @@ -769,7 +803,7 @@ function trackerHTML(){

${P.origin==="direct"?"Your order updates":"Updates fans see on the campaign page"}

    ${feed}

In the real platform these come from the factory's own event log, so the progress is the plant's record, not a status someone remembers to update.

`; } function newProd(origin,start,fans){ - const L=leadInfo(true),st=STG.filter(s=>s.id!=="intake"&&s.id!=="g1"&&(!s.opt||S[s.opt])); + const L=leadInfo(true),st=STG.filter(s=>s.id!=="intake"&&s.id!=="g1"&&stageOn(s)); const mid=s=>(s.d[0]+s.d[1])/2,after=st.filter(s=>["run","fin","ship"].includes(s.id)).reduce((a,s)=>a+mid(s),0),qMid=(L.qw[0]+L.qw[1])/2*5; let cur=new Date(start); const rows=st.map(s=>{const d=s.id==="queue"?Math.max(0,qMid-after):mid(s);cur=addWD(cur,d);return Object.assign({},s,{date:new Date(cur)});}); @@ -870,7 +904,7 @@ function renderAll(){syncSegs();renderNav();renderSides();renderArt();renderChec function go(s){if(s<1||s>5)return;S.step=s;S.visited[s]=true;renderAll();const top=$("header").offsetHeight;if(window.scrollY>top)window.scrollTo(0,top);} /* ---- events ---- */ -const PLAN_KEYS=["size","rpm","discs","loud"],RUN_KEYS=["weight","colour","inner","jacket","mastering"]; +const PLAN_KEYS=["size","rpm","discs","loud","source"],RUN_KEYS=["weight","colour","inner","jacket","mastering","labels","cleaning","dinked"]; function setK(k,v){ if(S[k]===v)return;S[k]=v; if(k==="size"&&v!=="12"&&S.jacket==="gatefold")S.jacket="printed"; @@ -911,6 +945,7 @@ document.addEventListener("input",e=>{ else if(id==="tps"){const v=Math.round(+t.value);if(v>=0&&v<=10){S.tps=v;if(S.mode)resetCampaign();renderAll();}} else if(id==="c1"||id==="c2"){S[id]=t.value;renderAll();} else if(id==="artist"||id==="title"){S[id]=t.value;renderAll();} + else if(id==="catno"){const v=t.value.toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,15);if(v!==t.value){const c=t.selectionStart;t.value=v;try{t.setSelectionRange(c,c);}catch(e){}}S.catno=v;renderAll();} else if(t.classList.contains("tt")){trkOf(t).title=t.value;S.demoTracks=false;if(S.mode==="campaign")updateS5();} else if(id==="fanPrice"){const v=+t.value;if(v>=100&&v<=20000){S.fanPrice=v;if(S.mode==="campaign")resetCampaign();renderAll();}} else if(id==="days"){S.days=+t.value;if(S.mode==="campaign")resetCampaign();renderAll();} @@ -969,7 +1004,7 @@ dropZone($("#dropLabel"),$("#fileLabel"),f=>handleArt("label",f)); /* ---- init ---- */ $("#qty").value=S.qty;$("#qtyR").value=S.qty;$("#tps").value=S.tps;$("#c1").value=S.c1;$("#c2").value=S.c2; -$("#artist").value=S.artist;$("#title").value=S.title;$("#fanPrice").value=S.fanPrice;$("#days").value=S.days;$("#cap").value=S.cap;$("#acop").value=S.artistCopies; +$("#artist").value=S.artist;$("#title").value=S.title;$("#catno").value=S.catno;$("#fanPrice").value=S.fanPrice;$("#days").value=S.days;$("#cap").value=S.cap;$("#acop").value=S.artistCopies; $$("input[data-b]").forEach(c=>c.checked=!!S[c.dataset.b]); renderTracks();renderPL();renderAll(); window.__ao={S,quote,campEcon,leadInfo,sideStats,checkList,go,ACT}; diff --git a/docs/artist-platform.md b/docs/artist-platform.md index 0abf007..dd11b35 100644 --- a/docs/artist-platform.md +++ b/docs/artist-platform.md @@ -93,19 +93,58 @@ their projects. See "Payments" below. | Choice | Options | |---|---| -| Format | 12" LP, 10", 7"; single or double disc; 33⅓ or 45 rpm | +| Format | 12" LP, 10", 7"; single or double disc; 33⅓ or 45 rpm; 7" with a small or a large (dinked) centre hole | | Weight | 140 g, 180 g | -| Colour | Black, solid colour, marbled or splatter (the semi-automatic line's speciality) | +| Colour | Black, solid colour, marbled or splatter (the semi-automatic line's speciality); for colour, whether the press is cleaned first | | Quantity | Free entry, with the price-per-copy curve shown live | -| Labels | Printed, pressed into the record (the only kind this plant makes) | +| Labels | Paper labels pressed into the record: printed, or plain white or black (white labels for promos and DJ copies) | +| Run-out inscription | The customer's catalogue number or text, scribed next to the run-out groove of every side (letters and numbers, up to 15) | | Inner sleeve | Paper, poly-lined | | Jacket | None (customer supplies), plain, printed single, printed gatefold; printed by the local packaging partner | -| Extras | Insert, hype sticker, shrink wrap, numbered edition | +| Extras | Insert, hype sticker, shrink wrap, numbered edition, download card, barcode sticker | +| Audio source | Digital files, analog tape (transferred in-house), or a lacquer cut by the customer's studio (straight to plating; must arrive within days of cutting) | | Audio services | Customer supplies a vinyl-ready master, or CNX vinyl mastering per side | | Metal | Lacquer cutting and stampers (in-house; bridge stampers from Stamper Discs until the galvanics line exists) | | Test pressings | Number of copies for the artist | | Delivery | Bulk to one address, dropship to each buyer, or collect at the plant | +### Benchmark: Record Industry's calculator (October 2026) + +Record Industry (Haarlem, NL; host plant of Making Vinyl Europe 2026) +runs a public calculator +(https://www.recordindustry.com/calculator/, checked October 2026): seven +steps (pre-production, vinyl, labels, inner sleeve, outer sleeve, extras, +quote or order) with an itemised cost list beside the form that updates +on every choice, an info box under each question, and a draft order +that the plant confirms by hand before the pre-payment invoice. Prices +are computed on its server. What it shows that this plan had not +covered, and what the demos took from it: + +- **Taken into both demos:** audio arriving as tape or as a supplied + lacquer (it charges 470 EUR for cutting plus stampers from a digital + master, 570 from tape, 240 for plating a supplied lacquer), plain white + or black labels, press cleaning before a colour run (60 EUR, with the + warning that without it records can carry the previous colour), the + dinked 7", download cards and barcode stickers, and a catalogue number + scribed in the run-out (letters and numbers, up to 15). The new rates + are estimates with its prices as the basis, listed in the detailed + demo. +- **Left out for now:** label print modes (CMYK, Pantone, one colour), + board grades and finishes (kraft, Hansaboard, matt or gloss varnish, + laminates), die-cut holes, 6 mm spines, booklets, foil numbering, + sticker position, protective outer sleeves, BIO PVC and a recycled + colour mix, and multi-address shipping. Each is a price-list line once + the packaging partner and the compound suppliers quote. +- **Commercial terms worth deciding for CNX:** a flat order set-up fee + (60 EUR) and a small-run surcharge (60 EUR under 300, 40 under 500); + 20 copies over the order invoiced to cover under- or over-production, + credited if fewer are delivered; customers asked to accept about 2 % + rejects; full pre-payment for new customers; minimum 200 copies. + +Its prices and lead times are recorded as a benchmark in the +[revenue model](finance/revenue-model.md) and the +[production timeline](production-timeline.md). + ### What the customer gets back - **Every line itemised**, grouped into **per title** (mastering, diff --git a/docs/finance/revenue-model.md b/docs/finance/revenue-model.md index 787d677..1f769ab 100644 --- a/docs/finance/revenue-model.md +++ b/docs/finance/revenue-model.md @@ -466,6 +466,47 @@ re-basing the whole model on one supplier's price list, in a market with one supplier, would be worse than leaving it low. Test it at Making Vinyl, then re-base. +### Second Western data point: Record Industry's calculator (October 2026) + +Record Industry (Haarlem, NL) publishes an online calculator +(https://www.recordindustry.com/calculator/, checked October 2026; ex 21 % +Dutch VAT, ex freight, 20 copies over the order included). A 12" 140 g +black LP, cut from a digital master with stampers, 5 test pressings, +printed labels, white paper inner sleeve, printed 3 mm jacket on 300 gsm +board, shrink wrap: + +| Copies | Order total | Per copy | Per copy, USD | Per copy, THB | +|---|---|---|---|---| +| 200 | 1,494.68 EUR | 7.47 EUR | 8.63 | ~285 | +| 300 | 1,721.22 EUR | 5.74 EUR | 6.63 | ~219 | +| 500 | 2,098.12 EUR | 4.20 EUR | 4.85 | ~160 | +| 1,000 | 3,169.78 EUR | 3.17 EUR | 3.66 | ~121 | + +At 1 EUR = 1.155 USD and 1 USD = 33 THB. How it is built: an order +set-up fee of 60 EUR plus a small-run charge (60 EUR under 300, 40 under +500); cutting with stampers 470 EUR a title (570 from tape, 240 to plate +a supplied lacquer); black vinyl 1.49 EUR a record; colour +35 EUR +set-up and +0.14 EUR a record, two-colour splatter +85 EUR and +0.99 EUR +a record, press cleaning 60 EUR; the printed jacket steps down with the +print run (1.93 EUR a copy at 200, 0.64 at 1,000). + +What it says about the assumptions above: + +- **Per title**, its set-up, small-run charge, cutting, stampers and test + pressings come to about 580 to 600 EUR (~670 to 690 USD, ~22,000 to + 23,000 THB): the low end of the 580 to 1,730 USD assumption. +- **Per copy** at 300, everything but the per-title items comes to about + 3.81 EUR (~4.40 USD, ~145 THB), and black pressing with printed labels + and a paper inner sleeve to about 2.43 USD (2.09 USD at 1,000): below + the 2.90 to 5.80 USD range, which looks high for black at this plant. +- **The Thai market price** of 650 to 950 THB a copy at 300 is about 3 to + 4.3 times this plant's ex-works price (before freight, Thai import duty + and VAT, which the Thai buyer pays on an import). +- The artist ordering demos (price list demo-0.1) come to about 343 THB a + copy at 300 with vinyl mastering, 310 THB without: above this plant's + ex-works price and well below the Thai market. Both price levels are + open until roadmap item 1; this is a benchmark, not a re-base. + ## Revenue at full capacity | Scenario | Records/yr | Pressing revenue | Title services | Total USD | Total THB | diff --git a/docs/make-a-record.html b/docs/make-a-record.html index 8413b30..e4d2182 100644 --- a/docs/make-a-record.html +++ b/docs/make-a-record.html @@ -472,6 +472,7 @@ document.documentElement.setAttribute("data-theme",t);})();
Speed
Discs
Weight
+
Centre hole
@@ -486,6 +487,10 @@ document.documentElement.setAttribute("data-theme",t);})();
Colour
Second colour
+
+
Clean the press before your colour
+

+

Packaging

@@ -496,19 +501,21 @@ document.documentElement.setAttribute("data-theme",t);})();
+
Labels
Inner sleeve
-
Extras
+
Extras

Sound

+
Your audio arrives as
Test pressings for you
5
-

You hear and approve a real pressing before the run. The mother plate is archived, so a repress later skips cutting and plating.

+

You hear and approve a real pressing before the run. The mother plate is archived, so a repress later skips cutting and plating.

@@ -522,6 +529,7 @@ document.documentElement.setAttribute("data-theme",t);})();
Artist
Release title
+
Run-out inscription scribed by hand next to the run-out groove of every side: letters and numbers, up to 15

Tracks and sides

@@ -555,7 +563,7 @@ document.documentElement.setAttribute("data-theme",t);})();
Step 3 of 5

Your price

Every line itemised, with no fees that appear later. The price per copy falls fast as the run grows.

-
Investor note

Why short runs work here. The one Thai plant on file quotes a 150-copy minimum that is "rarely accepted because the cost is way too high": its one-off cost per release (imported stampers, outsourced cutting) does not shrink with the run. In-house cutting and plating collapses that cost.

Prices on this page are indicative, at Western benchmark rates. The Thai market pays 650 to 950 THB a copy at 300 copies today; the real price list follows market validation.

+
Investor note

Why short runs work here. The one Thai plant on file quotes a 150-copy minimum that is "rarely accepted because the cost is way too high": its one-off cost per release (imported stampers, outsourced cutting) does not shrink with the run. In-house cutting and plating collapses that cost.

Prices on this page are indicative, at Western benchmark rates. The Thai market pays 650 to 950 THB a copy at 300 copies today; the real price list follows market validation.

Faster than an established EU plant. Record Industry (NL) quotes 9 to 11 weeks from test-pressing approval to the finished order (its calculator, October 2026); the CNX quote here is 7 to 9.

Copies
copies
@@ -610,7 +618,7 @@ document.documentElement.setAttribute("data-theme",t);})();
Step 5 of 5

Campaign and production

-
Investor note

Traceability is the product. Fan updates come from the factory's own event log, and every copy resolves to its stamper, press, cycle and side. At Making Vinyl Europe 2026 this is what the industry responded to most.

One release becomes a recurring one. The archived mother makes a repress about 1.5 to 2.5 weeks of plant time: the highest-margin order the plant will take.

+
Investor note

Traceability is the product. Fan updates come from the factory's own event log, and every copy resolves to its stamper, press, cycle and side. At Making Vinyl Europe 2026 this is what the industry responded to most. An established EU plant asks customers to accept about 2 % rejects (Record Industry FAQ, October 2026); CNX inspects every record optically.

One release becomes a recurring one. The archived mother makes a repress about 1.5 to 2.5 weeks of plant time: the highest-margin order the plant will take.

@@ -650,7 +658,9 @@ const R={ press:{v:{12:115,10:95,7:55}},g180:{v:15},colour:{v:{solid:20,marbled:35,splatter:45}}, inner:{v:{paper:{12:7,10:6,7:4},poly:{12:15,10:12,7:8}}},assy:{v:10}, jacket:{v:{printed:{12:[86,73,64,55],10:[68,58,51,44],7:[27,23,20,17]},gatefold:{12:[127,109,95,82]},plain:{12:[27,27,27,27],10:[20,20,20,20],7:[8,8,8,8]}}}, - handling:{v:0.10},shrink:{v:5},insert:{v:12},sticker:{v:6},numbered:{v:5},fee:{v:0.035},minRun:{v:100},deposit:{v:0.5} + handling:{v:0.10},shrink:{v:5},insert:{v:12},sticker:{v:6},numbered:{v:5}, + /* added October 2026, estimates with Record Industry's calculator as the basis (see artist-order.html) */ + tape:{v:3800},clean:{v:2300},plainLabel:{v:-12},dlcard:{v:43},barcode:{v:6},fee:{v:0.035},minRun:{v:100},deposit:{v:0.5} }; const tierIdx=q=>q>=1000?3:q>=500?2:q>=300?1:0; /* side length per format, minutes [comfortable, at most]: comfortable = lower end of the microcut.studio format guide; @@ -659,7 +669,7 @@ const LIM={"12":{"33":[18,22],"45":[12,15]},"10":{"33":[12,15],"45":[8,12]},"7": const SONG=272; /* seconds: the 4:32 average song the at-most table counts songs with */ const LOUD=0.6; const JNAME={none:"Your own jacket",plain:"Plain jacket",printed:"Printed jacket",gatefold:"Gatefold jacket"}; -const XN={shrink:"Shrink wrap",insert:"Insert",sticker:"Hype sticker",numbered:"Numbered"}; +const XN={shrink:"Shrink wrap",insert:"Insert",sticker:"Hype sticker",numbered:"Numbered",dlcard:"Download card",barcode:"Barcode sticker"}; const ART={jacket:{t:"Jacket front",mm:{12:320,10:266,7:190}},label:{t:"Label",mm:{12:106,10:106,7:96}}}; /* stages, working days [low, high]: mature column of the production timeline (same table as artist-order.html) */ const STG=[ @@ -699,6 +709,7 @@ const S={ step:1,cur:"THB",size:"12",rpm:"33",discs:1,weight:140,colour:"black",c1:"#b3202a",c2:"#f2c14e", qty:300,inner:"paper",jacket:"printed",shrink:true,insert:false,sticker:false,numbered:false, mastering:true,tps:5,loud:false,artist:"Mae Ping Collective",title:"Night Market Songs", + source:"digital",labels:"printed",cleaning:true,dinked:false,dlcard:false,barcode:false,catno:"MPC001", tracks:[],demoTracks:true,rights:false,gate1:false,art:{jacket:null,label:null}, fanPrice:1200,days:30,cap:0,limited:false,capN:300,artistCopies:0,vat:false, mode:null,launched:false,launchDate:null,scen:"make",simDay:0,outcome:null,outcomeAct:null,finalFans:0,prod:null, @@ -725,7 +736,10 @@ const rpmT=()=>S.rpm==="33"?"33⅓ rpm":"45 rpm"; const colourName=()=>({black:"Black vinyl",solid:"Solid colour",marbled:"Marbled",splatter:"Splatter"})[S.colour]; const colourT=()=>({black:"black",solid:"solid colour",marbled:"marbled",splatter:"splatter"})[S.colour]; const SIZE_MM={"12":{rec:302,jk:314,lab:100,name:"12\" LP"},"10":{rec:250,jk:260,lab:100,name:"10\""},"7":{rec:175,jk:184,lab:90,name:"7\" single"}}; -function descT(){return (S.discs>1?"2 × ":"")+SIZE_MM[S.size].name+", "+rpmT()+", "+(S.size==="12"?S.weight+" g ":"")+colourT()+" vinyl";} +function descT(){return (S.discs>1?"2 × ":"")+SIZE_MM[S.size].name+(dinkOn()?" (dinked)":"")+", "+rpmT()+", "+(S.size==="12"?S.weight+" g ":"")+colourT()+" vinyl";} +const dinkOn=()=>S.dinked&&S.size==="7",mastOn=()=>S.mastering&&S.source!=="lacquer",cleanOn=()=>S.cleaning&&S.colour!=="black"; +/* a stage runs unless the order skips it: no mastering or cutting when the artist sends a cut lacquer */ +const stageOn=s=>s.opt==="mastering"?mastOn():s.id==="cut"?S.source!=="lacquer":true; function toast(t){$$(".toast").forEach(x=>x.remove());const el=document.createElement("div");el.className="toast";el.textContent=t;document.body.appendChild(el);setTimeout(()=>el.remove(),2600);} function hsh(s){let h=2166136261;for(const c of String(s)){h^=c.charCodeAt(0);h=Math.imul(h,16777619);}return h>>>0;} const idOf=(p,s,n)=>p+"-"+hsh(s).toString(36).toUpperCase().padStart(7,"0").slice(0,n||4); @@ -734,19 +748,23 @@ function rnd(seed){return function(){seed|=0;seed=seed+0x6D2B79F5|0;let t=Math.i /* ---- pricing (same model as artist-order.html) ---- */ function quote(q){ const sz=S.size,d=S.discs,sides=2*d,T=[],C=[],du=d>1?"discs":"disc"; - if(S.mastering)T.push({t:"Vinyl mastering",n:sides,u:"sides",r:R.master.v}); - T.push({t:"Lacquer blanks",n:sides,u:"sides",r:R.blank.v}); - T.push({t:"Lacquer cutting",n:sides,u:"sides",r:R.cut.v}); + if(mastOn())T.push({t:"Vinyl mastering",n:sides,u:"sides",r:R.master.v}); + if(S.source==="tape")T.push({t:"Analog tape transfer",n:1,u:"",r:R.tape.v}); + if(S.source!=="lacquer"){T.push({t:"Lacquer blanks",n:sides,u:"sides",r:R.blank.v});T.push({t:"Lacquer cutting",n:sides,u:"sides",r:R.cut.v});} T.push({t:"Plating: father, mother (kept for represses), stamper",n:sides,u:"sides",r:R.metal.v}); T.push({t:"Press setup",n:d,u:du,r:R.setup.v}); if(S.tps>0)T.push({t:"Test pressings for you",n:S.tps,u:"copies",r:R.tp.v}); + if(cleanOn())T.push({t:"Press cleaned before your colour",n:1,u:"",r:R.clean.v}); C.push({t:"Pressing, "+sz+"\""+(sz==="12"?" 140 g":"")+", printed labels",n:d,u:du,r:R.press.v[sz]}); if(sz==="12"&&S.weight===180)C.push({t:"180 g heavyweight",n:d,u:du,r:R.g180.v}); if(S.colour!=="black")C.push({t:"Colour vinyl, "+colourT(),n:d,u:du,r:R.colour.v[S.colour]}); C.push({t:"Inner sleeve, "+(S.inner==="poly"?"poly-lined":"paper"),n:d,u:du,r:R.inner.v[S.inner][sz]}); C.push({t:"Assembly and packing",n:1,u:"",r:R.assy.v}); if(S.jacket!=="none"){const p=R.jacket.v[S.jacket][sz][tierIdx(q)];C.push({t:JNAME[S.jacket]+(sz==="7"&&S.jacket!=="plain"?" (picture sleeve)":""),n:1,u:"",r:Math.round(p*(1+R.handling.v)),pass:p});} + if(S.labels!=="printed")C.push({t:"Plain "+S.labels+" labels instead of printed",n:d,u:du,r:R.plainLabel.v}); if(S.shrink)C.push({t:"Shrink wrap",n:1,u:"",r:R.shrink.v}); + if(S.dlcard)C.push({t:"Download card",n:1,u:"",r:R.dlcard.v}); + if(S.barcode)C.push({t:"Barcode sticker",n:1,u:"",r:R.barcode.v}); if(S.insert)C.push({t:"Printed insert",n:1,u:"",r:R.insert.v}); if(S.sticker)C.push({t:"Hype sticker",n:1,u:"",r:R.sticker.v}); if(S.numbered)C.push({t:"Hand-numbered",n:1,u:"",r:R.numbered.v}); @@ -756,7 +774,7 @@ function quote(q){ } function leadInfo(campaign){ const g3=STG.findIndex(s=>s.id==="g3"); - const pre=STG.slice(0,g3+1).filter(s=>(!s.opt||S[s.opt])&&!((campaign||S.gate1)&&(s.id==="intake"||s.id==="g1"))); + const pre=STG.slice(0,g3+1).filter(s=>stageOn(s)&&!((campaign||S.gate1)&&(s.id==="intake"||s.id==="g1"))); const lo=pre.reduce((a,s)=>a+s.d[0],0),hi=pre.reduce((a,s)=>a+s.d[1],0); const pw=[Math.max(1,Math.round(lo/5)),Math.max(1,Math.ceil(hi/5))]; const printed=S.jacket==="printed"||S.jacket==="gatefold"; @@ -877,7 +895,9 @@ const jacketArt=()=>S.jacket==="printed"||S.jacket==="gatefold"; function checkList(){ const L=[],files=S.tracks.filter(t=>t.file),ready=files.filter(t=>t.info&&!t.info.reading); if(!S.tracks.length)L.push(["bad","Track list","Add at least one track."]); - if(!files.length)L.push(["pend","Master files","Not added yet: these checks use the lengths typed in. Drop WAV, FLAC or AIFF files to check the files themselves."]); + if(S.source==="lacquer")L.push(["pend","Your lacquer","Cut by your studio and sent by express courier the day it is cut: a lacquer has to be plated within days. Inspected under the microscope on arrival. Files below are for the side plan and the previews."]); + else if(S.source==="tape")L.push(["pend","Your tape","Analog tape is transferred and checked on arrival; format, speed and EQ curve are confirmed with you first."]); + if(S.source!=="digital"){}else if(!files.length)L.push(["pend","Master files","Not added yet: these checks use the lengths typed in. Drop WAV, FLAC or AIFF files to check the files themselves."]); else{ const lossy=ready.filter(t=>!t.info.lossless); L.push(lossy.length?["bad","File format",lossy.map(t=>esc(t.file.name)).join(", ")+": not WAV, FLAC or AIFF. Send the lossless master."]:["ok","File format",ready.length+" lossless file"+(ready.length===1?"":"s")]); @@ -893,7 +913,7 @@ function checkList(){ :warn.length?["warn","Side length",warn.map(s=>"Side "+s.n+" "+mmss(s.sec)).join(", ")+": past the "+mmss(lim[0])+" that sounds best, so the cut gets quieter"] :empty.length?["warn","Side length","Side "+empty.map(s=>s.n).join(", ")+" has no music"]:["ok","Side length","Every side within the "+mmss(lim[0])+" that sounds best"]); if(jacketArt()){const c=artChecks("jacket");L.push(!c?["pend","Jacket artwork","Not added yet"]:c.some(x=>x[0]==="bad")?["bad","Jacket artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:c.some(x=>x[0]!=="ok")?["warn","Jacket artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:["ok","Jacket artwork","Size, resolution and colour space fine"]);} - {const c=artChecks("label");L.push(!c?["pend","Label artwork","Not added yet"]:c.some(x=>x[0]==="bad")?["bad","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:c.some(x=>x[0]!=="ok")?["warn","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:["ok","Label artwork","Size, resolution and colour space fine"]);} + if(S.labels==="printed"){const c=artChecks("label");L.push(!c?["pend","Label artwork","Not added yet"]:c.some(x=>x[0]==="bad")?["bad","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:c.some(x=>x[0]!=="ok")?["warn","Label artwork",c.filter(x=>x[0]!=="ok").map(x=>x[1]).join("; ")]:["ok","Label artwork","Size, resolution and colour space fine"]);} L.push(["pend","Engineer check","After upload a cutting engineer confirms the sides are cuttable. This decides, not the bars above."]); return L; } @@ -917,12 +937,16 @@ function mockSVG(id){ let g=``; if(S.colour==="marbled")g+=``; if(S.colour==="splatter"){const r=rnd(7);let dots="";for(let i=0;i<80;i++){const a=r()*6.283,d=Math.sqrt(r())*100,z=1+r()*5;dots+=``;}g+=`${dots}`;} - for(let r=101;r>=rl+5;r-=3.2)g+=``; + for(let r=101;r>=rl+10;r-=3.2)g+=``; + /* run-out inscription, scribed in the dead wax between the label and the last groove */ + if(S.catno){const rr=(rl+5).toFixed(1);g+=`${esc(S.catno)} A`;} const L=rl.toFixed(1); - if(S.art.label&&S.art.label.url)g+=``; + if(S.labels==="white")g+=``; + else if(S.labels==="black")g+=``; + else if(S.art.label&&S.art.label.url)g+=``; else g+=`${esc(S.artist.slice(0,22))}${esc(S.title.slice(0,26))}SIDE A · ${S.rpm==="33"?"33⅓":"45"}`; g+=`${wedge(-128,-104)}${wedge(52,76)}`; - return g+``; + return g+``; } const recs=(S.discs>1?disc(cx+30*s):"")+disc(cx); let jk="";const J=236; @@ -986,6 +1010,12 @@ function syncControls(){ inp.disabled=off;pal.title=off?why:"Open the palette and pick any colour";pal.classList.toggle("off",off);pal.classList.toggle("on",!off&&custom);pal.style.setProperty("--cc",S[c]); pal.querySelector(".pt").textContent=!off&&custom?"Your colour":"Palette";if(inp.value.toLowerCase()!==S[c].toLowerCase())inp.value=S[c];}); $("#tpsV").textContent=S.tps; + const s7=S.size==="7";$$("#fDink button").forEach(x=>{x.disabled=!s7;x.title=s7?"":"Only 7\" singles come with a large centre hole";});$("#fDink").classList.toggle("off",!s7); + $$("#fClean button").forEach(x=>{x.disabled=blk;x.title=blk?"Black vinyl needs no cleaning":"";});$("#fClean").classList.toggle("off",blk); + $("#cleanHint").textContent=blk?"Only for colour vinyl.":S.cleaning?"The press is purged before your run, so no trace of the previous colour gets in. Light colours can still show the odd speck.":"Saves the purge, but some records can carry streaks of the previous colour."; + const lq=S.source==="lacquer";$$(".choice[data-k=mastering]").forEach(x=>{x.disabled=lq;x.title=lq?"Your lacquer is already cut and mastered":"";if(lq){x.classList.remove("on");x.setAttribute("aria-pressed","false");}}); + $("#srcHint").textContent=lq?"Your lacquer goes straight to plating. You still hear and approve a test pressing, and the mother is archived for represses.":S.source==="tape"?"Your tape is transferred in-house, then cut. You hear and approve a real pressing before the run.":"You hear and approve a real pressing before the run. The mother plate is archived, so a repress later skips cutting and plating."; + const dl=$("#dropLabel"),offL=S.labels!=="printed";dl.classList.toggle("off",offL);$("#fileLabel").disabled=offL;dl.title=offL?"Plain labels have no print":""; const dj=$("#dropJacket"),offJ=!jacketArt();dj.classList.toggle("off",offJ);$("#fileJacket").disabled=offJ;dj.title=offJ?(S.jacket==="none"?"You supply your own jacket":"A plain jacket has no print"):""; $$(".qchips .chip").forEach(c=>c.classList.toggle("on",+c.dataset.q===S.qty)); } @@ -1106,7 +1136,7 @@ function trackerHTML(){

${P.origin==="direct"?"Your order updates":"Updates your fans see"} from the factory log

    ${feed}

These come from the plant's own event log: the progress is the factory's record, not a status someone remembers to update.

${fin?passportHTML():""}`; } function newProd(origin,start,fans){ - const L=leadInfo(true),st=STG.filter(s=>s.id!=="intake"&&s.id!=="g1"&&(!s.opt||S[s.opt])); + const L=leadInfo(true),st=STG.filter(s=>s.id!=="intake"&&s.id!=="g1"&&stageOn(s)); const mid=s=>(s.d[0]+s.d[1])/2,after=st.filter(s=>["run","fin","ship"].includes(s.id)).reduce((a,s)=>a+mid(s),0),qMid=(L.qw[0]+L.qw[1])/2*5; let cur=new Date(start); const rows=st.map(s=>{const d=s.id==="queue"?Math.max(0,qMid-after):mid(s);cur=addWD(cur,d);return Object.assign({},s,{date:new Date(cur)});}); @@ -1184,9 +1214,10 @@ function passportHTML(){ const run=by("run"),rd=new Date(run.date);rd.setHours(8+hsh(seed+"h")%9,hsh(seed+"m")%60,hsh(seed+"s")%60); const p2=v=>String(v).padStart(2,"0"),ts=rd.getFullYear()+"-"+p2(rd.getMonth()+1)+"-"+p2(rd.getDate())+" "+p2(rd.getHours())+":"+p2(rd.getMinutes())+":"+p2(rd.getSeconds()); const st=idOf("S",seed+"st"),lac=idOf("L",seed+"l"),fa=idOf("F",seed+"f"),mo=idOf("M",seed+"mo"),lot=idOf("C",seed+"c",3); - const it=[["Master received","Checksums verified at both ends, stored in the client vault",P.start]]; + const it=S.source==="lacquer"?[]:[[S.source==="tape"?"Tape received":"Master received",S.source==="tape"?"Transferred in-house, stored in the client vault":"Checksums verified at both ends, stored in the client vault",P.start]]; if(by("g2"))it.push(["Mastered for vinyl","Master approved by the artist",by("g2").date]); - it.push(["Cut to lacquer","Lacquer "+lac+", serial scribed in the run-out groove",by("cut").date]); + if(by("cut"))it.push(["Cut to lacquer","Lacquer "+lac+(S.catno?", \u201C"+S.catno+" A\u201D":", serial")+" scribed in the run-out",by("cut").date]); + else it.push(["Lacquer received","Cut by the artist's studio, inspected under the microscope on arrival",P.start]); it.push(["Plated in-house","Father "+fa+" → mother "+mo+" (archived for represses) → stamper "+st,by("plate").date]); it.push(["Test pressing approved","Heard and approved by the artist",by("g3").date]); it.push(["Pressed","Press D1-B (Newbilt Duplex) at "+ts.slice(11)+", compound lot "+lot+", press data logged for this cycle",run.date]); @@ -1223,6 +1254,7 @@ function renderStage(){ $("#mock").innerHTML=mockSVG("ms"); $("#stT").textContent=S.title||"Untitled";$("#stA").textContent=S.artist||""; const sp=[(S.discs>1?"2 × ":"")+SIZE_MM[S.size].name,rpmT()];if(S.size==="12")sp.push(S.weight+" g");sp.push(colourName(),JNAME[S.jacket]); + if(S.labels!=="printed")sp.push("Plain "+S.labels+" labels");if(S.catno)sp.push("Run-out "+S.catno);if(S.source!=="digital")sp.push(S.source==="tape"?"From tape":"Your lacquer"); Object.keys(XN).forEach(k=>{if(S[k])sp.push(XN[k]);}); $("#stSpecs").innerHTML=sp.map(x=>`${esc(x)}`).join(""); const camp=(S.step===4||S.mode==="campaign")&&S.mode!=="direct";let pr,mb; @@ -1249,7 +1281,7 @@ function launch(){if(!canLaunch()){if(!S.gate1)go(2);return;}resetCampaign();S.m function setFanPrice(v){S.fanPrice=v;if(S.mode==="campaign")resetCampaign();renderAll();} function setQty(q){S.qty=Math.min(5000,Math.max(100,Math.round(q)));if(S.mode==="direct")resetCampaign();renderAll();} function syncInputs(){ - $("#artist").value=S.artist;$("#title").value=S.title;$("#qty").value=S.qty;$("#qtyR").value=Math.min(2000,S.qty); + $("#artist").value=S.artist;$("#title").value=S.title;$("#catno").value=S.catno;$("#qty").value=S.qty;$("#qtyR").value=Math.min(2000,S.qty); $("#fanPrice").value=S.fanPrice;$("#fanR").value=Math.min(3000,Math.max(500,S.fanPrice));$("#days").value=S.days;$("#cap").value=S.capN;$("#limT").checked=S.limited;$("#acop").value=S.artistCopies;$("#vat").checked=S.vat;$("#rights").checked=S.rights; } function resetDemo(){ @@ -1258,7 +1290,7 @@ function resetDemo(){ } /* ---- events ---- */ -const PLAN_KEYS=["size","rpm","discs","loud"],RUN_KEYS=["weight","colour","inner","jacket","mastering"]; +const PLAN_KEYS=["size","rpm","discs","loud","source"],RUN_KEYS=["weight","colour","inner","jacket","mastering","labels","cleaning","dinked"]; function setK(k,v){ if(S[k]===v)return;S[k]=v; if(k==="size"&&v!=="12"&&S.jacket==="gatefold")S.jacket="printed"; @@ -1310,6 +1342,7 @@ document.addEventListener("input",e=>{ else if(id==="qty"){const v=Math.round(+t.value);if(v>=100&&v<=5000)setQty(v);} else if(t.type==="color"){const c=t.closest("[data-c]").dataset.c;S[c]=t.value;renderAll();} else if(id==="artist"||id==="title"){S[id]=t.value;renderStage();if(S.mode==="campaign")updateS5();} + else if(id==="catno"){const v=t.value.toUpperCase().replace(/[^A-Z0-9]/g,"").slice(0,15);if(v!==t.value){const c=t.selectionStart;t.value=v;try{t.setSelectionRange(c,c);}catch(e){}}S.catno=v;renderStage();if(S.mode==="campaign")updateS5();} else if(t.classList.contains("tt")){trkOf(t).title=t.value;S.demoTracks=false;renderSides();} else if(id==="fanPrice"){const v=+t.value;if(v>=100&&v<=20000){S.fanPrice=v;if(S.mode==="campaign")resetCampaign();renderAll();}} else if(id==="fanR"){S.fanPrice=+t.value;if(S.mode==="campaign")resetCampaign();renderAll();} diff --git a/docs/production-timeline.md b/docs/production-timeline.md index 2749cf0..d4343a3 100644 --- a/docs/production-timeline.md +++ b/docs/production-timeline.md @@ -34,7 +34,12 @@ Reported EU/US plant lead times run roughly **10 to 16 weeks** for a standard black LP, down from the 6-to-9-month peak of 2021–22. That figure is an industry impression gathered in conversation, not a surveyed one, and it should be tested at Making Vinyl alongside the -price. Two cautions on the comparison: +price. One published data point: Record Industry (Haarlem, NL) quotes +**3 weeks to test pressings** (from complete files, or from a supplied +lacquer's arrival) and then **9 to 11 weeks** for the finished order +after the customer approves the test pressings and the artwork +(calculator FAQ, checked October 2026), which on the same reference +point compares with the 7 to 9 week mature quote below. Two cautions on the comparison: - **It is not like-for-like.** The EU/US figure is a *quoted* lead time carrying queue and buffer; the CNX figures above are unbuffered stage