Calculators: options from Record Industry's calculator; its prices as a benchmark
Deploy test site / deploy (push) Successful in 45s
Deploy test site / deploy (push) Successful in 45s
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).
This commit is contained in:
1 parent
83f1aa94dc
commit
5090609df3
5 files changed
+195
-42
No files matched your search
+51
-16
@@ -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);})();
|
||||
<div class="row"><span class="lbl">Size</span><div class="seg" data-k="size"><button type="button" data-v="12">12" LP</button><button type="button" data-v="10">10"</button><button type="button" data-v="7">7" single</button></div></div>
|
||||
<div class="row"><span class="lbl">Speed</span><div class="seg" data-k="rpm"><button type="button" data-v="33">33⅓ rpm</button><button type="button" data-v="45">45 rpm</button></div></div>
|
||||
<div class="row"><span class="lbl">Discs</span><div class="seg" data-k="discs" data-num><button type="button" data-v="1">Single</button><button type="button" data-v="2">Double</button></div></div>
|
||||
<div class="row" id="rowDink"><span class="lbl">Centre hole</span><div class="seg" data-k="dinked" data-bool><button type="button" data-v="false">Small</button><button type="button" data-v="true">Large (dinked)</button></div></div>
|
||||
<div class="row" id="rowWeight"><span class="lbl">Weight</span><div class="seg" data-k="weight" data-num><button type="button" data-v="140">140 g</button><button type="button" data-v="180">180 g</button></div></div>
|
||||
<div class="row"><span class="lbl">Vinyl colour</span><div class="seg" data-k="colour"><button type="button" data-v="black">Black</button><button type="button" data-v="solid">Solid colour</button><button type="button" data-v="marbled">Marbled</button><button type="button" data-v="splatter">Splatter</button></div></div>
|
||||
<div class="row" id="rowColours"><span class="lbl">Colours</span><span><input type="color" id="c1" title="Vinyl colour"> <input type="color" id="c2" title="Second colour"> <small id="colHint"></small></span></div>
|
||||
<div class="row" id="rowClean"><span class="lbl">Press cleaning</span><span><div class="seg" data-k="cleaning" data-bool><button type="button" data-v="true">Clean before the colour</button><button type="button" data-v="false">No cleaning</button></div> <small id="cleanHint"></small></span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Copies, for a direct order</h2>
|
||||
@@ -206,18 +210,22 @@ document.documentElement.setAttribute("data-theme",t);})();
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Sleeves and jacket</h2>
|
||||
<div class="row"><span class="lbl">Labels</span><div class="seg" data-k="labels"><button type="button" data-v="printed">Printed</button><button type="button" data-v="white">Plain white</button><button type="button" data-v="black">Plain black</button></div></div>
|
||||
<div class="row"><span class="lbl">Inner sleeve</span><div class="seg" data-k="inner"><button type="button" data-v="paper">Paper</button><button type="button" data-v="poly">Poly-lined</button></div></div>
|
||||
<div class="row"><span class="lbl">Jacket</span><div class="seg" data-k="jacket"><button type="button" data-v="none">I supply it</button><button type="button" data-v="plain">Plain</button><button type="button" data-v="printed">Printed</button><button type="button" data-v="gatefold" id="bGate">Gatefold</button></div></div>
|
||||
<div class="row"><span class="lbl">Extras</span><span class="checks2">
|
||||
<label><input type="checkbox" data-b="shrink"> Shrink wrap</label>
|
||||
<label><input type="checkbox" data-b="insert"> Printed insert</label>
|
||||
<label><input type="checkbox" data-b="sticker"> Hype sticker</label>
|
||||
<label><input type="checkbox" data-b="numbered"> Numbered edition</label></span></div>
|
||||
<p class="hint">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.</p>
|
||||
<label><input type="checkbox" data-b="numbered"> Numbered edition</label>
|
||||
<label><input type="checkbox" data-b="dlcard"> Download card</label>
|
||||
<label><input type="checkbox" data-b="barcode"> Barcode sticker</label></span></div>
|
||||
<p class="hint">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.</p>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Audio and test pressings</h2>
|
||||
<div class="row"><span class="lbl">Master</span><div class="seg" data-k="mastering" data-bool><button type="button" data-v="false">I supply a vinyl-ready master</button><button type="button" data-v="true">CNX vinyl mastering</button></div></div>
|
||||
<div class="row"><span class="lbl">Audio arrives as</span><div class="seg" data-k="source"><button type="button" data-v="digital">Digital files</button><button type="button" data-v="tape">Analog tape</button><button type="button" data-v="lacquer">A cut lacquer</button></div></div>
|
||||
<div class="row" id="rowMaster"><span class="lbl">Master</span><div class="seg" data-k="mastering" data-bool><button type="button" data-v="false">I supply a vinyl-ready master</button><button type="button" data-v="true">CNX vinyl mastering</button></div></div>
|
||||
<div class="row"><span class="lbl">Test pressings for you</span><input type="number" id="tps" min="0" max="10" step="1"></div>
|
||||
<p class="hint">Cutting and plating happen in-house: lacquer, father, mother and stampers. The mother is kept, so a repress later skips cutting.</p>
|
||||
</div>
|
||||
@@ -229,6 +237,7 @@ document.documentElement.setAttribute("data-theme",t);})();
|
||||
<h2>Release</h2>
|
||||
<div class="row"><span class="lbl">Artist</span><input type="text" id="artist" maxlength="60"></div>
|
||||
<div class="row"><span class="lbl">Title</span><input type="text" id="title" maxlength="80"></div>
|
||||
<div class="row"><span class="lbl">Run-out inscription</span><span><input type="text" id="catno" maxlength="15" spellcheck="false" autocomplete="off" style="text-transform:uppercase;font-family:ui-monospace,Consolas,monospace"> <small>scribed next to the run-out groove, A to Z and 0 to 9, up to 15</small></span></div>
|
||||
</div>
|
||||
<div class="card">
|
||||
<h2>Tracks and sides</h2>
|
||||
@@ -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=`<g transform="translate(${x.toFixed(1)} ${cy.toFixed(1)}) scale(${k.toFixed(3)})"><circle r="105" fill="${base}"/>`;
|
||||
if(S.colour==="marbled")g+=`<rect x="-105" y="-105" width="210" height="210" fill="${S.c2}" filter="url(#${id}m)" clip-path="url(#${id}c)"/>`;
|
||||
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+=`<circle cx="${(Math.cos(a)*d).toFixed(1)}" cy="${(Math.sin(a)*d).toFixed(1)}" r="${s.toFixed(1)}" fill="${r()<0.8?S.c2:"#ffffff"}"/>`;}g+=`<g clip-path="url(#${id}c)">${dots}</g>`;}
|
||||
for(let r=100;r>=rl+5;r-=4)g+=`<circle r="${r}" fill="none" stroke="${groove}" stroke-width="${(0.6/k).toFixed(2)}"/>`;
|
||||
for(let r=100;r>=rl+10;r-=4)g+=`<circle r="${r}" fill="none" stroke="${groove}" stroke-width="${(0.6/k).toFixed(2)}"/>`;
|
||||
if(S.catno){const rr=(rl+5).toFixed(1);g+=`<path id="${id}ro${x|0}" d="M0 -${rr} A${rr} ${rr} 0 1 1 -0.01 -${rr}" fill="none"/><text font-size="4.6" font-family="ui-monospace,Consolas,monospace" letter-spacing="0.8" fill="${dark?"rgba(255,255,255,0.55)":"rgba(0,0,0,0.5)"}"><textPath href="#${id}ro${x|0}" startOffset="8%">${esc(S.catno)} A</textPath></text>`;}
|
||||
g+=`<circle r="105" fill="url(#${id}sh)"/>`;
|
||||
const L=rl.toFixed(1);
|
||||
if(S.art.label&&S.art.label.url)g+=`<image href="${S.art.label.url}" x="-${L}" y="-${L}" width="${(2*rl).toFixed(1)}" height="${(2*rl).toFixed(1)}" preserveAspectRatio="xMidYMid slice" clip-path="url(#${id}l)"/>`;
|
||||
if(S.labels==="white")g+=`<circle r="${L}" fill="#f3efe6"/>`;
|
||||
else if(S.labels==="black")g+=`<circle r="${L}" fill="#1c1c1c"/><circle r="${(rl*0.86).toFixed(1)}" fill="none" stroke="rgba(255,255,255,0.08)" stroke-width="0.8"/>`;
|
||||
else if(S.art.label&&S.art.label.url)g+=`<image href="${S.art.label.url}" x="-${L}" y="-${L}" width="${(2*rl).toFixed(1)}" height="${(2*rl).toFixed(1)}" preserveAspectRatio="xMidYMid slice" clip-path="url(#${id}l)"/>`;
|
||||
else g+=`<circle r="${L}" fill="#efe6d2"/><text y="${(-6*fs).toFixed(1)}" text-anchor="middle" font-size="${(7*fs).toFixed(1)}" font-weight="700" fill="#2a2a2a">${esc(S.artist.slice(0,20))}</text><text y="${(14*fs).toFixed(1)}" text-anchor="middle" font-size="${(6*fs).toFixed(1)}" fill="#444">${esc(S.title.slice(0,24))}</text>`;
|
||||
return g+`<circle r="${(2.6/k).toFixed(2)}" style="fill:var(--panel)"/></g>`;
|
||||
return g+`<circle r="${dinkOn()?(105*19.1/Z.rec).toFixed(2):(2.6/k).toFixed(2)}" style="fill:var(--panel)"/></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(){
|
||||
<div class="card"><h2>${P.origin==="direct"?"Your order updates":"Updates fans see on the campaign page"}</h2><ul class="feed">${feed}</ul><p class="hint">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.</p></div></div>`;
|
||||
}
|
||||
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};
|
||||
|
||||
Reference in new issue
Block a user