Test and deploy / test-and-deploy (push) Successful in 59s
Co-Authored-By: Claude Fable 5 <[email protected]>
251 lines
10 KiB
HTML
251 lines
10 KiB
HTML
<!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 6–10 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>
|