@@ -610,7 +618,7 @@ document.documentElement.setAttribute("data-theme",t);})();
Step 5 of 5
Campaign and production
-
Investor noteTraceability 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 noteTraceability 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
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=>`
`).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