Files
2026-08-09 14:30:42 -04:00

251 lines
10 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Cupping — Roast Planner</title>
<link rel="stylesheet" href="/app.css?v=__ASSET_VERSION__" />
<link rel="icon" href="/icon.svg?v=__ASSET_VERSION__" type="image/svg+xml" />
<meta name="theme-color" content="#A8481A" />
</head>
<body>
<div class="app-shell">
<nav class="side-nav" id="side-nav" aria-label="Main navigation">
<div class="side-nav-brand">
<img class="brand-mark" src="/brand-mark.svg?v=__ASSET_VERSION__" alt="" aria-hidden="true" />
<span class="brand-name">Roast Planner</span>
</div>
<div id="nav-links"></div>
<div class="nav-spacer"></div>
<button
class="icon-btn nav-collapse-toggle"
type="button"
id="nav-collapse"
aria-label="Collapse navigation"
title="Collapse navigation"
>
«
</button>
<div class="nav-user">
<div class="nav-user-avatar" id="nav-user-avatar" aria-hidden="true"></div>
<div class="nav-user-detail">
<span class="nav-user-email" id="account-email"></span>
<button class="nav-user-logout" type="button" id="btn-logout">
Log out
</button>
</div>
</div>
</nav>
<div class="app-workspace" id="app-workspace">
<header class="app-header">
<div class="header-row-top">
<button
class="icon-btn nav-hamburger"
type="button"
id="nav-hamburger"
aria-label="Open navigation"
aria-expanded="false"
>
</button>
<div class="brand">
<div class="brand-text">
<h1 id="cupping-title">Cupping</h1>
<p class="brand-sub" id="cupping-subtitle">
Tasting and scoring sessions
</p>
</div>
</div>
</div>
<div class="header-actions hidden" id="session-autosave-wrap">
<span class="autosave-chip" id="cupping-autosave-status"
><span class="dot"></span
><span class="autosave-text">Not saved yet</span></span
>
<button id="btn-expand-why" type="button" class="ghost-btn">
Expand all "why" notes
</button>
</div>
</header>
<div class="drawer-overlay hidden" id="drawer-overlay"></div>
<main class="page-content wide" id="cupping-list-view">
<section class="panel-card" id="cupping-sessions-list">
<div class="panel-head"><h2>Cupping sessions</h2></div>
<div class="panel-body">
<div class="table-wrap">
<table class="data-table" id="sessions-table">
<thead>
<tr>
<th>Coffee</th>
<th class="num">Score</th>
<th class="num">Cups</th>
<th>Flavors</th>
<th>Updated</th>
<th></th>
</tr>
</thead>
<tbody id="sessions-body"></tbody>
</table>
</div>
</div>
</section>
<section class="panel-card" id="new-session-card">
<div class="panel-head"><h2>New session</h2></div>
<div class="panel-body">
<form id="new-session-form" class="inline-form">
<label class="field"
><span class="field-label">Roast (optional)</span>
<select class="field-input" id="new-session-plan">
<option value="">— no linked roast —</option>
</select></label
>
<label class="field"
><span class="field-label">Cups</span
><input
class="field-input"
type="number"
id="new-session-cups"
min="1"
max="12"
value="5"
/></label>
<button class="primary-btn" type="submit">
Start cupping
</button>
</form>
</div>
</section>
</main>
<main class="page-content wide hidden" id="cupping-session-view">
<div class="workspace-grid">
<div class="form-col">
<section class="panel-card" id="cup-scores">
<div class="panel-head">
<h2>Scores</h2>
<label class="field" style="margin:0">
<span class="field-label">Cups</span>
<input
class="field-input sm"
type="number"
id="cup-count"
min="1"
max="12"
/>
</label>
</div>
<div class="panel-body">
<details class="why-panel" data-why="cup-scores">
<summary>Why the scale starts at 6</summary>
<p>
The 610 range is a specialty-grading convention:
anything below specialty grade simply isn't scored, so
6 is the floor of the scale, not "zero". Score in
quarter-point steps and leave an attribute unscored
(—) rather than guessing — an unscored attribute
contributes nothing, which is honest; a guessed 7.5
pollutes every comparison you make later. The tick
rows count cups, not quality: five clean cups out of
five is full marks even if the coffee is merely good.
</p>
</details>
<div id="cup-score-rows"></div>
<div id="cup-tick-rows"></div>
<details class="cupping-defects">
<summary>Defects</summary>
<div id="cup-defect-rows"></div>
</details>
</div>
</section>
<section class="panel-card" id="cup-flavors">
<div class="panel-head"><h2>Flavors</h2></div>
<div class="panel-body">
<details class="why-panel" data-why="cup-flavors">
<summary>Tag what you can point at</summary>
<p>
A tag is only useful if you could defend it to
another taster with the cup in front of you. Two or
three confident descriptors beat a dozen hopeful ones
— the list is capped at 32, but a session that needs
10 is already suspect.
</p>
</details>
<p class="field-note hidden" id="flavor-limit-note">
32-tag limit reached.
</p>
<div class="flavor-wheel-wrap">
<svg
id="flavor-wheel"
role="group"
aria-label="Flavor wheel — tap a wedge to add or remove a flavor"
></svg>
</div>
<div id="flavor-families"></div>
</div>
</section>
<section class="panel-card" id="cup-notes">
<div class="panel-head"><h2>Notes</h2></div>
<div class="panel-body">
<details class="why-panel" data-why="cup-notes">
<summary>Notes outlive scores</summary>
<p>
Numbers compare roasts; sentences explain them. Write
what you'd want to read before roasting this coffee
again — texture, where it fell apart as it cooled,
what you'd change.
</p>
</details>
<textarea
class="field-input"
id="cup-notes-text"
maxlength="4000"
rows="5"
aria-label="Cupping notes"
></textarea>
<p class="field-note"><span id="cup-notes-count">0</span>/4000</p>
</div>
</section>
</div>
<aside class="dock">
<div class="dock-card">
<div class="dock-card-head"><h2>Score</h2></div>
<div class="stat-tile" style="box-shadow:none;border:none;padding:0">
<div class="stat-tile-label">Total score</div>
<div class="stat-tile-value" id="cup-total">0.00</div>
</div>
<p class="dock-note">Recomputed by the server on every save.</p>
<div class="chip-group" id="flavor-chips"></div>
</div>
<div class="dock-card">
<div class="dock-card-head"><h2>Radar</h2></div>
<svg
id="cup-radar"
viewBox="0 0 240 240"
role="img"
aria-label="Cupping score radar chart"
></svg>
</div>
</aside>
</div>
</main>
</div>
</div>
<script>
// Runs synchronously at parse time (before the deferred module script below) so opening
// /cupping?session=… never flashes the empty sessions list first while cupping.js's own
// async init (which awaits the auth check) is still getting underway.
if (new URLSearchParams(location.search).get("session")) {
document.getElementById("cupping-list-view").classList.add("hidden");
document.getElementById("cupping-session-view").classList.remove("hidden");
}
</script>
<script type="module" src="/js/cupping.js?v=__ASSET_VERSION__"></script>
</body>
</html>