Add the Academy: narrated, animated roasting & brewing lessons; cupping link on roast detail
Test and deploy / test-and-deploy (push) Successful in 53s
Test and deploy / test-and-deploy (push) Successful in 53s
Academy (/academy, 'Learn' nav group): - 10 lessons / 45 slides across two tracks. Roasting: seed anatomy and composition, drying & the turning point, Maillard, first crack & development (DTR), curve/RoR reading (crash, flick, stall), defects → one-change discipline. Brewing: extraction physics (dissolving order, yield, grind, temp/time/ratio), then technique per brewer family — immersion (French press, AeroPress, Clever/Hario Switch, cold brew, cupping), percolation (bloom, V60, Chemex/Kalita, batch, percolator), espresso & pressure (puck prep, shot reading, dialing in, moka) - One cohesive scene system: 25 parameterized animated SVG scenes drawn from the app's palette (beans, curves, phase bars, brewers reusing the silhouette library), CSS keyframe animations, reduced-motion support - Narration: Azure TTS (en-US-Andrew HD, eastus) pre-generated to 45 committed MP3s by scripts/generate-academy-audio.mjs; slides speak phonetic respellings (my-YARD, KEM-ex, MOH-kah…) while captions show normal spelling; hands-free autoplay advances after each clip - Player: slide dots, keyboard arrows/space, per-slide captions Roast detail: 'Open cupping session' button next to cup notes (opens or creates the linked plan's session) — its score and flavors flow into the updated .alog download. Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Fable 5
parent
0b4857c225
commit
64c1b3605b
@@ -457,6 +457,35 @@ document.getElementById("after-form").addEventListener("submit", async (event) =
|
||||
}
|
||||
});
|
||||
|
||||
// Open (or create) the cupping session for this roast's plan — same flow the planner offers.
|
||||
document.getElementById("open-cupping").addEventListener("click", async (event) => {
|
||||
const button = event.currentTarget;
|
||||
if (!openId || button.disabled) return;
|
||||
const roast = roasts.find((r) => r.id === openId);
|
||||
const planId = roast?.roastPlanId;
|
||||
if (!planId) {
|
||||
showToast("Attach a plan to this roast first — cupping sessions hang off plans.", "fail");
|
||||
return;
|
||||
}
|
||||
button.disabled = true;
|
||||
try {
|
||||
const existing = await api(`/api/cupping?plan=${encodeURIComponent(planId)}`).catch(() => ({ sessions: [] }));
|
||||
if (existing.sessions?.length) {
|
||||
location.assign(`/cupping?session=${existing.sessions[0].id}`);
|
||||
return;
|
||||
}
|
||||
const created = await api("/api/cupping", {
|
||||
method: "POST",
|
||||
body: JSON.stringify({ roastPlanId: planId }),
|
||||
});
|
||||
location.assign(`/cupping?session=${created.session.id}`);
|
||||
} catch (error) {
|
||||
showToast(error.message, "fail");
|
||||
} finally {
|
||||
button.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
function closeDetail() {
|
||||
openId = null;
|
||||
history.replaceState(null, "", "/roasts");
|
||||
|
||||
Reference in New Issue
Block a user