// Browser-safe. Geometry for the roast-curve SVG grid (viewBox 0 0 714 334), matching // manual-roast-planner.html's worksheet box 10 grid exactly. import { MACHINE } from "./reference-data.js?v=20260730-release2"; export const PLOT = { x0: 52, x1: 652, y0: 24, y1: 294, tMaxS: 900 }; export function tToX(seconds) { const clamped = Math.max(0, Math.min(PLOT.tMaxS, seconds)); return PLOT.x0 + (clamped / PLOT.tMaxS) * (PLOT.x1 - PLOT.x0); } // 60C -> y294 (bottom axis), 200C label sits at y42 (confirmed by the FC band rect y=65.4,h=19.8 = 176-187C) export function tempToY(degC) { return PLOT.y1 - (degC - 60) * 1.8; } export function rorToY(rorCPerMin) { return PLOT.y1 - rorCPerMin * 9; } /** Build the five plan-curve points from the ledger + box-8 temps, falling back to machine medians. */ export function buildPlanCurve(plan, ledger) { const temps = plan.temps ?? {}; const num = (v) => { const n = Number.parseFloat(v); return Number.isFinite(n) ? n : null; }; const points = [ { key: "charge", label: "Charge", timeS: 0, tempC: num(temps.charge?.tempC) ?? MACHINE.charge.medianC }, { key: "tp", label: "Turning point", timeS: parseTpTime(temps.tp?.time), tempC: num(temps.tp?.tempC) ?? MACHINE.turningPoint.medianC }, { key: "yellow", label: "Yellow", timeS: ledger.yellow, tempC: num(temps.yellow?.tempC) ?? MACHINE.yellow.medianC }, { key: "fc", label: "First crack", timeS: ledger.A, tempC: num(temps.fc?.tempC) ?? MACHINE.firstCrack.medianC }, { key: "drop", label: "Drop", timeS: ledger.D, tempC: num(temps.drop?.tempC) ?? MACHINE.drop.medianC }, ]; return points.filter((p) => p.timeS !== null && p.timeS !== undefined); } function parseTpTime(str) { if (!str) return 52; // MACHINE.turningPoint.medianTime, "0:52" const m = String(str).match(/^(\d+):(\d{1,2})$/); if (!m) return null; return Number(m[1]) * 60 + Number(m[2]); } /** Build an SVG "d" string through points, sorted by time, plain polyline (no kink invented). */ export function pointsToPathD(points) { const sorted = [...points].sort((a, b) => a.timeS - b.timeS); if (sorted.length === 0) return ""; return sorted .map((p, i) => `${i === 0 ? "M" : "L"}${tToX(p.timeS).toFixed(1)},${tempToY(p.tempC).toFixed(1)}`) .join(" "); }