Side length per format: team table with songs a side; palette control
Deploy test site / deploy (push) Successful in 47s

Side length "at most" per format is now the table the team supplied
(October 2026, from recordplayerexpert.com, songs counted at an average
4:32): 12" 22 / 15 min, 10" 15 / 12 min, 7" 7 / 5 min at 33 1/3 / 45 rpm.
Changes against the earlier microcut.studio figures: 10" 33 1/3 16 -> 15,
10" 45 11 -> 12, 7" 45 6 -> 5. The comfortable column stays the lower end
of microcut.studio's ranges.

- artist-platform.md: table with a songs column, both sources, and the
  three formats where cutting rooms disagree (10" 45 is now more generous
  than microcut.studio and Diggers Factory) as re-check points for the
  cutting engineer
- artist-order.html: LIM and the side-length table (songs column, sources)
- make-a-record.html: LIM; format cards show the at-most time and about
  how many songs a side; step 2 hint adds the songs; the last control in
  each colour row is now a labelled "Palette" pill with a colour wheel,
  showing the chosen colour and "Your colour" when a custom colour is set
This commit is contained in:
grabowski committed 2026-10-15 16:36:11 +07:00
1 parent 607f6631a7
commit 4b20ca4f48
3 files changed
+50 -30

No files matched your search

+7 -5
View File
@@ -358,8 +358,10 @@ const R={
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."}
};
const tierIdx=q=>q>=1000?3:q>=500?2:q>=300?1:0;
/* side length per format, minutes [comfortable, at most]: microcut.studio format guide (lower end of each range), checked October 2026 */
const LIM={"12":{"33":[18,22],"45":[12,15]},"10":{"33":[12,16],"45":[8,11]},"7":{"33":[5,7],"45":[4.5,6]}};
/* side length per format, minutes [comfortable, at most]: comfortable = lower end of the microcut.studio format guide;
at most = the team's table (October 2026, from recordplayerexpert.com), see artist-platform.md "Side length per format" */
const LIM={"12":{"33":[18,22],"45":[12,15]},"10":{"33":[12,15],"45":[8,12]},"7":{"33":[5,7],"45":[4.5,5]}};
const SONG=272; /* seconds: the 4:32 average song the at-most table counts songs with */
const LOUD=0.6; /* Diggers Factory: dance music ~11:27 against 19:23 standard on a 12" LP */
const JNAME={none:"Jacket supplied by you",plain:"Plain jacket",printed:"Printed jacket",gatefold:"Printed gatefold jacket"};
const ART={jacket:{t:"Jacket front",mm:{12:320,10:266,7:190}},label:{t:"Label",mm:{12:106,10:106,7:96}}};
@@ -850,13 +852,13 @@ function rateText(k){
}
function renderPL(){
const kb={doc:"doc",team:"team",est:"estimate"};
const lim=[["12\"","33"],["12\"","45"],["10\"","33"],["10\"","45"],["7\"","33"],["7\"","45"]].map(([s,r])=>{const v=LIM[s.replace("\"","")][r];return `<tr><td>${s}, ${r==="33"?"33⅓":"45"} rpm</td><td class="num">${mmss(v[0]*60)}</td><td class="num">${mmss(v[1]*60)}</td></tr>`;}).join("");
const lim=[["12\"","33"],["12\"","45"],["10\"","33"],["10\"","45"],["7\"","33"],["7\"","45"]].map(([s,r])=>{const v=LIM[s.replace("\"","")][r];return `<tr><td>${s}, ${r==="33"?"33⅓":"45"} rpm</td><td class="num">${mmss(v[0]*60)}</td><td class="num">${mmss(v[1]*60)}</td><td class="num">${(v[1]*60/SONG).toFixed(1)}</td></tr>`;}).join("");
$("#plCard").innerHTML=`<h2>Price list ${PLV}, and where every number comes from</h2>
<p class="hint">The planning docs give the price per title (19,000 to 57,000 THB, midpoint 38,000) and per copy (115 to 150 THB, midpoint 132) as working assumptions at Western benchmark rates. This demo splits them into lines; every split is an estimate. The Thai market pays far more today (650 to 950 THB a copy at 300 copies), so do not read the price level off this page: the real price list follows market validation. Rates are in THB; USD at 1 USD = 33 THB.</p>
<div class="tscroll"><table><tbody><tr class="hd"><th>Item</th><th>Rate</th><th>Unit</th><th>Kind</th><th>Basis</th></tr>${Object.keys(R).map(k=>`<tr><td>${R[k].t}</td><td>${rateText(k)}</td><td><small>${R[k].u}</small></td><td><span class="badge b-${R[k].k}">${kb[R[k].k]}</span></td><td><small>${R[k].src}</small></td></tr>`).join("")}</tbody></table></div>
<h2 style="margin-top:18px">Side length per format</h2>
<div class="tscroll"><table style="max-width:460px"><tbody><tr class="hd"><th>Format</th><th class="num">Comfortable</th><th class="num">At most</th></tr>${lim}</tbody></table></div>
<p class="hint">Published cutting-room guidance, checked October 2026: microcut.studio's format guide (lower end of each range); Diggers Factory lists hard maxima of 22, 12, 17, 10, 6 and 5 minutes and A to Z Media 14 to 18 minutes as ideal on a 12" LP. Loud or bass-heavy music: limits times 0.6, from Diggers Factory's dance-music figures. The engineer check decides, not this table.</p>
<div class="tscroll"><table style="max-width:460px"><tbody><tr class="hd"><th>Format</th><th class="num">Comfortable</th><th class="num">At most</th><th class="num">Songs</th></tr>${lim}</tbody></table></div>
<p class="hint">Comfortable: lower end of the ranges in microcut.studio's format guide (checked October 2026). At most: the team's table (October 2026, from recordplayerexpert.com, which counts songs at an average 4:32). Cutting rooms differ: microcut.studio gives 16, 11 and 6 minutes at most for 10" 33⅓, 10" 45 and 7" 45 (this table 15, 12 and 5), Diggers Factory hard maxima of 22, 12, 17, 10, 6 and 5 minutes, A to Z Media 14 to 18 minutes as ideal on a 12" LP. Loud or bass-heavy music: limits times 0.6, from Diggers Factory's dance-music figures. The engineer check decides, not this table.</p>
<h2 style="margin-top:18px">Lead time</h2>
<p class="hint">Stage durations: mature-plant column of the production timeline. Quote: 7 to 9 weeks for a new title, full service, mature plant, counted from test-pressing approval as the timeline says a quote should be made; 1 to 2 weeks less without a printed jacket. Campaign ship month: campaign close, plus the stages before test-pressing approval, plus the quote.</p>
<h2 style="margin-top:18px">Not in the demo yet</h2>
+25 -15
View File
@@ -143,23 +143,33 @@ Transparency rules, so the word means something:
### Side length per format
The audio check compares each side with the format's limit. Until the
cutting engineer sets CNX's own figures, the demo uses published
cutting-room guidance (checked October 2026):
cutting engineer sets CNX's own figures, the demos use these (October
2026):
| Format | Comfortable | At most |
|---|---|---|
| 12", 33⅓ rpm | 18 min | 22 min |
| 12", 45 rpm | 12 min | 15 min |
| 10", 33⅓ rpm | 12 min | 16 min |
| 10", 45 rpm | 8 min | 11 min |
| 7", 33⅓ rpm | 5 min | 7 min |
| 7", 45 rpm | 4.5 min | 6 min |
| Format | Comfortable | At most | Songs a side, at most |
|---|---|---|---|
| 12", 33⅓ rpm | 18 min | 22 min | ~4.9 |
| 12", 45 rpm | 12 min | 15 min | ~3.3 |
| 10", 33⅓ rpm | 12 min | 15 min | ~3.3 |
| 10", 45 rpm | 8 min | 12 min | ~2.6 |
| 7", 33⅓ rpm | 5 min | 7 min | ~1.5 |
| 7", 45 rpm | 4.5 min | 5 min | ~1.1 |
Source: the microcut.studio format guide
(https://microcut.studio/guides/vinyl-format-guide-12-10-7/, lower end of
each range). Diggers Factory lists hard maxima of 22, 12, 17, 10, 6 and 5
minutes for the same formats (stricter on 12" at 45 rpm), and A to Z Media
14 to 18 minutes as ideal on a 12" LP. Loud or bass-heavy music needs
**At most** and **songs** are the table the team supplied in October
2026, from recordplayerexpert.com ("How much can a vinyl record hold",
https://recordplayerexpert.com/vinyl-record-capacity, checked October
2026), which counts songs at an average length of 4:32. **Comfortable**
is the lower end of each range in the microcut.studio format guide
(https://microcut.studio/guides/vinyl-format-guide-12-10-7/).
The published cutting-room figures disagree with the at-most column in
three places, so treat those as re-check points for the cutting engineer:
10" at 33⅓ rpm (15 min here; microcut.studio up to 16, Diggers Factory
17), 10" at 45 rpm (12 min here; microcut.studio 11, Diggers Factory 10,
so this table is the most generous) and 7" at 45 rpm (5 min here, as
Diggers Factory; microcut.studio 6). Diggers Factory lists hard maxima of
22, 12, 17, 10, 6 and 5 minutes for the six formats (stricter on 12" at
45 rpm), and A to Z Media 14 to 18 minutes as ideal on a 12" LP. Loud or bass-heavy music needs
shorter sides: Diggers Factory's dance-music figures are about 60 % of its
standard ones (11:27 against 19:23 on a 12" LP), which the demo applies as
a factor of 0.6. The engineer check, not this table, decides whether a
+18 -10
View File
@@ -123,10 +123,14 @@ document.documentElement.setAttribute("data-theme",t);})();
.swatch:hover:not(:disabled) { transform: scale(1.1); }
.swatch.on { box-shadow: 0 0 0 2px var(--acc); }
.swatch:disabled { opacity: 0.2; cursor: not-allowed; }
input[type=color] { width: 30px; height: 30px; padding: 0; border: 0; border-radius: 50%; background: none; cursor: pointer; }
input[type=color]::-webkit-color-swatch-wrapper { padding: 0; }
input[type=color]::-webkit-color-swatch { border: 1.5px dashed var(--faint); border-radius: 50%; }
input[type=color]:disabled { opacity: 0.2; cursor: not-allowed; }
.pal { position: relative; display: inline-flex; align-items: center; gap: 8px; margin-left: 6px; padding: 3px 12px 3px 3px; border: 1.5px solid var(--line); border-radius: 99px; background: var(--card); font-size: 13px; font-weight: 600; color: var(--ink); cursor: pointer; transition: border-color .15s; }
.pal:hover { border-color: var(--stroke); }
.pal input[type=color] { position: absolute; inset: 0; width: 100%; height: 100%; opacity: 0; cursor: pointer; border: 0; padding: 0; z-index: 2; }
.pal .cw { width: 26px; height: 26px; border-radius: 50%; flex: none; position: relative; background: conic-gradient(#ff4d4d, #ffb23b, #f2ee3b, #3bd36a, #3bc4f2, #5361ff, #c03bff, #ff4d4d); }
.pal .cw::after { content: "+"; position: absolute; inset: 5px; border-radius: 50%; background: var(--card); color: var(--ink); display: grid; place-items: center; font-size: 14px; font-weight: 800; line-height: 1; }
.pal.on { border-color: var(--acc); background: var(--acc-s); }
.pal.on .cw::after { content: ""; background: var(--cc); inset: 4px; }
.pal.off { opacity: 0.3; cursor: not-allowed; } .pal.off input { cursor: not-allowed; }
.chips { display: flex; flex-wrap: wrap; gap: 8px; }
.chip { border: 1.5px solid var(--line); background: var(--card); border-radius: 99px; padding: 7px 14px; cursor: pointer; font-size: 14px; transition: border-color .15s, background .15s; }
.chip:hover { border-color: var(--stroke); }
@@ -645,8 +649,10 @@ const R={
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}
};
const tierIdx=q=>q>=1000?3:q>=500?2:q>=300?1:0;
/* side length per format, minutes [comfortable, at most]: microcut.studio format guide, checked October 2026 */
const LIM={"12":{"33":[18,22],"45":[12,15]},"10":{"33":[12,16],"45":[8,11]},"7":{"33":[5,7],"45":[4.5,6]}};
/* side length per format, minutes [comfortable, at most]: comfortable = lower end of the microcut.studio format guide;
at most = the team's table (October 2026, from recordplayerexpert.com), see artist-platform.md "Side length per format" */
const LIM={"12":{"33":[18,22],"45":[12,15]},"10":{"33":[12,15],"45":[8,12]},"7":{"33":[5,7],"45":[4.5,5]}};
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"};
@@ -966,12 +972,14 @@ function syncControls(){
$$("#fWeight button").forEach(x=>{x.disabled=!w12;x.title=w12?"":"Only 12\" records come in 140 or 180 g";});
$("#fWeight").classList.toggle("off",!w12);
const g=$("#bGate");g.disabled=!w12;g.title=w12?"":"Gatefold jackets are for 12\" records";
$$("[data-sub]").forEach(el=>{const L=LIM[el.dataset.sub][S.rpm];el.textContent="Up to "+L[0]+" min a side at "+(S.rpm==="33"?"33⅓":"45");});
$$("[data-sub]").forEach(el=>{const m=LIM[el.dataset.sub][S.rpm][1],n=Math.max(1,Math.round(m*60/SONG));el.textContent="Up to "+m+" min a side at "+(S.rpm==="33"?"33⅓":"45")+", about "+n+" song"+(n===1?"":"s");});
$$("[data-mini]").forEach(el=>el.innerHTML=miniDisc(el.dataset.mini));
[["c1",blk,"Black vinyl: no colour to choose"],["c2",!two,"Only for marbled and splatter"]].forEach(([c,off,why])=>{
const row=$(`.sw-row[data-c="${c}"]`);$(c==="c1"?"#fC1":"#fC2").classList.toggle("off",off);
row.querySelectorAll(".swatch").forEach(b=>{b.disabled=off;b.classList.toggle("on",!off&&b.dataset.col.toLowerCase()===S[c].toLowerCase());b.title=off?why:b.dataset.name;});
const inp=row.querySelector("input[type=color]");inp.disabled=off;inp.title=off?why:"Any colour";if(inp.value.toLowerCase()!==S[c].toLowerCase())inp.value=S[c];});
const inp=row.querySelector("input[type=color]"),pal=row.querySelector(".pal"),custom=!Array.from(row.querySelectorAll(".swatch")).some(b=>b.dataset.col.toLowerCase()===S[c].toLowerCase());
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 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));
@@ -997,7 +1005,7 @@ function renderTracks(){
}
function renderSides(){
const ss=sideStats(),L=ss[0].lim;
$("#sideHint").textContent=`${S.size}" at ${rpmT()}${S.loud?", loud music":""}: up to ${mmss(L[0])} a side sounds best, ${mmss(L[1])} at most`;
$("#sideHint").textContent=`${S.size}" at ${rpmT()}${S.loud?", loud music":""}: up to ${mmss(L[0])} a side sounds best, ${mmss(L[1])} at most (about ${Math.max(1,Math.round(L[1]/SONG))} songs of 4:32)`;
const lbl={ok:"fits",warn:"long",bad:"too long"};
$("#sides").innerHTML=ss.map(s=>{const tr=S.tracks.filter(t=>t.side===s.n);let x=0;
const segs=tr.map((t,i)=>{const w=Math.max(0,Math.min(100-x,(t.sec||0)/L[1]*100)),el=`<i class="s${i%2}" style="left:${x.toFixed(2)}%;width:${w.toFixed(2)}%" title="${esc(t.title)} ${mmss(t.sec)}"></i>`;x+=w;return el;}).join("");
@@ -1382,7 +1390,7 @@ async function tour(){
})();
/* ---- init ---- */
[["c1",C1],["c2",C2]].forEach(([c,list])=>{$(`.sw-row[data-c="${c}"]`).innerHTML=list.map(([h,n])=>`<button type="button" class="swatch" data-col="${h}" data-name="${n}" style="background:${h}" aria-label="${n}"></button>`).join("")+`<input type="color" aria-label="Custom colour">`;});
[["c1",C1],["c2",C2]].forEach(([c,list])=>{$(`.sw-row[data-c="${c}"]`).innerHTML=list.map(([h,n])=>`<button type="button" class="swatch" data-col="${h}" data-name="${n}" style="background:${h}" aria-label="${n}"></button>`).join("")+`<label class="pal"><input type="color" aria-label="Palette: pick any colour"><span class="cw"></span><span class="pt">Palette</span></label>`;});
syncInputs();renderTracks();renderAll();
const H=location.hash.slice(1).split(/[&,]/);
if(H.includes("notes")){$("#invT").checked=true;document.body.classList.add("inv-on");}