Files
roast_command_center/public/app.css
T
Shane MaynardandClaude Fable 5 4a3d192c2c
Test and deploy / test-and-deploy (push) Successful in 56s
Move After-the-Roast onto actual roasts; add Artisan-importable updated .alog download
One system: post-roast observations (weights, colour, DTR, cup notes,
'one change next batch', disproof) now live on the uploaded roast
(actual_roasts.after, migration 011), edited on the roast detail view
with weights/DTR prefilled from the .alog itself. The planner's screen
section is removed (the print worksheet keeps its hand-fill copy), and
the lot 'last refine' suggestion reads the note from both the new home
and legacy plans, newest wins.

Every roast now downloads two ways: the untouched original .alog, and
an updated supplemental copy with the app's data written back as valid
Artisan fields (weight, beans, roastingnotes incl. the LLM review,
cuppingnotes incl. linked cupping score/flavors) — serialized as a
Python literal so Artisan's ast.literal_eval re-imports it.

Co-Authored-By: Claude Fable 5 <[email protected]>
2026-08-09 08:35:11 -04:00

3081 lines
60 KiB
CSS

/* Roast Planner — screen UI. worksheet.css is print-only (loaded with media="print"); this file
owns the entire on-screen experience and is deliberately independent of the print layout. */
:root {
--ink: #1b1614;
--ink-2: #5b524b;
--ink-3: #8c8179;
--surface: #fbf8f4;
--surface-raised: #ffffff;
--surface-sunken: #f2ece4;
--line: #e4dcd2;
--line-strong: #d3c8ba;
--ember: #a8481a;
--ember-soft: #fbefe6;
--ember-strong: #7f350f;
--pass: #2f6f4e;
--pass-bg: #e8f3ed;
--warn: #a8741a;
--warn-bg: #fbf3e1;
--fail: #b03a22;
--fail-bg: #fbeae5;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 16px;
--shadow-card:
0 1px 2px rgba(27, 22, 20, 0.04), 0 8px 24px -12px rgba(27, 22, 20, 0.12);
--shadow-pop: 0 12px 32px -8px rgba(27, 22, 20, 0.28);
--font-ui:
-apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Inter,
Helvetica, Arial, sans-serif;
--font-serif:
"Iowan Old Style", "Palatino Linotype", Palatino, Georgia,
"Times New Roman", serif;
--font-mono: "SF Mono", Menlo, Consolas, monospace;
--header-h: 60px;
--roast: #2a1d16;
--cream: #fbf8f4;
--nav-w: 232px;
--nav-w-collapsed: 64px;
}
* {
box-sizing: border-box;
}
.hidden {
display: none !important;
}
html {
background: var(--surface);
scroll-padding-top: calc(var(--header-h) + 18px);
}
.panel-card {
scroll-margin-top: calc(var(--header-h) + 18px);
}
body {
margin: 0;
background: var(--surface);
color: var(--ink);
font-family: var(--font-ui);
font-size: 15px;
line-height: 1.45;
-webkit-font-smoothing: antialiased;
}
h1,
h2,
h3 {
margin: 0;
font-family: var(--font-ui);
}
output {
font-variant-numeric: tabular-nums;
}
/* ─── Header ──────────────────────────────────────────────────────────── */
.app-header {
position: sticky;
top: 0;
z-index: 30;
height: var(--header-h);
display: flex;
align-items: center;
gap: 24px;
background: var(--surface-raised);
border-bottom: 1px solid var(--line);
padding: 0 20px;
}
.header-row-top {
display: contents;
}
.brand {
display: flex;
align-items: center;
gap: 10px;
flex: 0 0 auto;
}
.brand-mark {
font-size: 22px;
color: var(--ember);
line-height: 1;
}
.brand-text h1 {
font-size: 15px;
font-weight: 700;
letter-spacing: -0.1px;
}
.brand-sub {
margin: 0;
font-size: 11.5px;
color: var(--ink-3);
}
/* No overflow scrolling: below 1180px this row already sits on its own full-width line, and
at ≤720px it becomes a fixed five-column grid — a hidden horizontal scroll region in a
header is undiscoverable, especially on touch. */
.section-nav {
display: flex;
align-items: center;
gap: 2px;
flex: 1 1 auto;
flex-wrap: wrap;
}
.section-nav a {
flex: 0 0 auto;
padding: 6px 12px;
border-radius: 999px;
font-size: 12.5px;
font-weight: 500;
color: var(--ink-2);
text-decoration: none;
white-space: nowrap;
transition:
background 0.15s,
color 0.15s;
}
.section-nav a:hover {
background: var(--surface-sunken);
color: var(--ink);
}
.section-nav a.active {
background: var(--ember-soft);
color: var(--ember-strong);
font-weight: 600;
}
.header-actions {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
.header-divider {
width: 1px;
height: 22px;
background: var(--line);
margin: 0 2px;
}
.account-menu {
position: relative;
}
.account-menu summary {
list-style: none;
}
.account-menu summary::-webkit-details-marker {
display: none;
}
.account-menu-popover {
position: absolute;
right: 0;
top: calc(100% + 6px);
z-index: 35;
min-width: 132px;
padding: 6px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background: var(--surface-raised);
box-shadow: var(--shadow-pop);
}
.account-email {
margin: 4px 10px 7px;
font-size: 11px;
color: var(--ink-3);
overflow-wrap: anywhere;
}
.account-menu-popover a,
.account-menu-popover button {
display: block;
width: 100%;
padding: 9px 10px;
border: 0;
border-radius: var(--radius-sm);
background: transparent;
color: var(--ink-2);
font: inherit;
font-size: 13px;
text-align: left;
text-decoration: none;
cursor: pointer;
}
.account-menu-popover a:hover,
.account-menu-popover button:hover {
background: var(--surface-sunken);
color: var(--ink);
}
.ghost-btn,
.primary-btn,
label.filebtn {
font-family: var(--font-ui);
font-size: 12.5px;
font-weight: 600;
cursor: pointer;
border-radius: var(--radius-sm);
padding: 7px 12px;
white-space: nowrap;
border: 1px solid transparent;
transition:
background 0.15s,
border-color 0.15s,
color 0.15s;
}
.ghost-btn {
background: transparent;
color: var(--ink-2);
border-color: var(--line);
}
.ghost-btn:hover {
background: var(--surface-sunken);
color: var(--ink);
}
.ghost-btn[aria-pressed="true"] {
background: var(--ember-soft);
border-color: var(--ember);
color: var(--ember-strong);
}
.ghost-btn.small {
font-size: 12px;
padding: 5px 10px;
}
.ghost-btn:disabled {
opacity: 0.45;
cursor: default;
}
.primary-btn,
label.filebtn {
background: var(--ember);
color: #fff;
border-color: var(--ember);
}
.primary-btn:hover,
label.filebtn:hover {
background: var(--ember-strong);
}
.primary-btn:disabled {
opacity: 0.5;
cursor: default;
}
#file-load {
display: none;
}
label.filebtn input[type="file"] {
display: none;
}
.autosave-chip {
display: flex;
align-items: center;
gap: 6px;
font-size: 11.5px;
color: var(--ink-3);
padding: 4px 10px;
border-radius: 999px;
background: var(--surface-sunken);
margin-left: 2px;
}
.autosave-chip .dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: var(--ink-3);
transition: background 0.2s;
}
.autosave-chip.failed .dot {
background: var(--fail);
}
.autosave-chip.saving .dot {
background: var(--warn);
animation: pulse 1s ease-in-out infinite;
}
.autosave-chip.saved .dot {
background: var(--pass);
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.35;
}
}
/* ─── Drawers ─────────────────────────────────────────────────────────── */
.drawer-overlay {
position: fixed;
inset: 0;
background: rgba(27, 22, 20, 0.32);
z-index: 40;
opacity: 1;
transition: opacity 0.18s;
}
.drawer-overlay.hidden {
display: none;
}
.drawer {
position: fixed;
top: 0;
right: 0;
bottom: 0;
width: min(420px, 92vw);
z-index: 41;
background: var(--surface-raised);
box-shadow: var(--shadow-pop);
display: flex;
flex-direction: column;
transform: translateX(0);
transition: transform 0.2s;
}
.drawer.hidden {
display: none;
}
.drawer-head {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 20px;
border-bottom: 1px solid var(--line);
}
.drawer-head h3 {
font-size: 14.5px;
font-weight: 700;
}
.icon-btn {
background: none;
border: none;
font-size: 14px;
color: var(--ink-3);
cursor: pointer;
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
}
.icon-btn:hover {
background: var(--surface-sunken);
color: var(--ink);
}
.drawer-body {
padding: 18px 20px;
overflow-y: auto;
display: flex;
flex-direction: column;
gap: 12px;
}
.stack-label {
font-size: 12px;
font-weight: 600;
color: var(--ink-2);
}
.text-input {
width: 100%;
padding: 9px 11px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
font-size: 13.5px;
font-family: var(--font-ui);
background: var(--surface-raised);
color: var(--ink);
}
.text-input:focus {
outline: none;
border-color: var(--ember);
box-shadow: 0 0 0 3px var(--ember-soft);
}
.checkbox-row {
display: flex;
align-items: center;
gap: 7px;
font-size: 13px;
color: var(--ink-2);
}
.drawer-actions {
display: flex;
gap: 8px;
}
.drawer-result {
font-size: 12.5px;
color: var(--ink-2);
line-height: 1.55;
}
.drawer-result a {
color: var(--ember);
}
.drawer-result .warnings {
color: var(--warn);
margin: 6px 0 0;
padding-left: 18px;
}
.lib-list {
list-style: none;
margin: 0;
padding: 0;
max-height: 220px;
overflow-y: auto;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
}
.lib-list.hidden {
display: none;
}
.lib-list li {
padding: 8px 10px;
font-size: 12.5px;
border-bottom: 1px solid var(--line);
cursor: pointer;
}
.lib-list li:last-child {
border-bottom: none;
}
.lib-list li:hover {
background: var(--surface-sunken);
}
/* ─── Field-help dialog ───────────────────────────────────────────────── */
.help-dialog {
padding: 0;
border: none;
border-radius: var(--radius-lg);
background: var(--surface-raised);
box-shadow: var(--shadow-pop);
width: min(680px, 94vw);
max-height: min(82vh, 780px);
}
.help-dialog::backdrop {
background: rgba(27, 22, 20, 0.32);
}
.help-card {
display: flex;
flex-direction: column;
max-height: inherit;
}
.help-head {
display: flex;
align-items: flex-start;
justify-content: space-between;
gap: 12px;
padding: 16px 20px 12px;
border-bottom: 1px solid var(--line);
}
.help-kicker {
display: block;
font-size: 10.5px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ember);
}
.help-head h3 {
font-size: 15px;
font-weight: 700;
margin-top: 2px;
}
.help-body {
padding: 14px 20px;
overflow-y: auto;
font-size: 12.5px;
line-height: 1.55;
color: var(--ink-2);
}
.help-body h4 {
margin: 14px 0 6px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--ink-2);
}
.help-body h4:first-child {
margin-top: 0;
}
.help-body p {
margin: 0 0 8px;
}
.help-body dl {
margin: 0;
}
.help-checklist {
list-style: none;
margin: 0 0 10px;
padding: 0;
display: flex;
flex-direction: column;
gap: 5px;
}
.help-checklist li {
position: relative;
padding-left: 20px;
}
.help-checklist li::before {
content: "";
position: absolute;
left: 0;
top: 5px;
width: 10px;
height: 10px;
border: 1.5px solid var(--line-strong);
border-radius: 2px;
}
.help-table-wrap {
overflow-x: auto;
margin: 6px 0 10px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
}
.help-table {
width: 100%;
border-collapse: collapse;
font-size: 12px;
}
.help-table th {
text-align: left;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--ink-3);
padding: 7px 10px 5px;
border-bottom: 1px solid var(--line-strong);
background: var(--surface);
white-space: nowrap;
}
.help-table td {
padding: 6px 10px;
border-bottom: 1px solid var(--line);
vertical-align: top;
}
.help-table tbody tr:last-child td {
border-bottom: none;
}
.help-table td.num {
font-family: var(--font-mono);
font-variant-numeric: tabular-nums;
white-space: nowrap;
font-size: 11.5px;
}
.help-table tr.grp td {
background: var(--ember-soft);
color: var(--ember-strong);
font-weight: 700;
font-size: 11px;
}
.help-table tr.current td {
background: var(--ember-soft);
box-shadow: inset 3px 0 0 var(--ember);
}
.help-aside {
border-left: 3px solid var(--ember);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
background: var(--ember-soft);
padding: 10px 12px;
margin: 10px 0;
}
.help-aside .tag {
display: block;
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ember-strong);
margin-bottom: 4px;
}
.help-aside p:last-child {
margin-bottom: 0;
}
.help-aside.warn {
border-left-color: var(--warn);
background: var(--warn-bg);
}
.help-aside.warn .tag {
color: var(--warn);
}
.help-formula {
font-family: var(--font-mono);
font-size: 12px;
text-align: center;
background: var(--surface-sunken);
border-radius: var(--radius-sm);
padding: 8px 10px;
margin: 8px 0;
overflow-x: auto;
white-space: nowrap;
}
.help-attrib {
margin: 0;
padding: 10px 20px 14px;
border-top: 1px solid var(--line);
font-size: 11px;
color: var(--ink-3);
}
@media (max-width: 720px) {
.help-dialog {
width: 100vw;
max-width: none;
margin: auto 0 0;
border-radius: var(--radius-lg) var(--radius-lg) 0 0;
max-height: 88vh;
}
}
@media print {
.help-btn,
.help-dialog {
display: none !important;
}
}
/* ─── Workspace layout ────────────────────────────────────────────────── */
.workspace-grid {
display: grid;
grid-template-columns: 1fr 380px;
gap: 28px;
align-items: start;
max-width: 1360px;
margin: 0 auto;
padding: 28px 24px 80px;
}
.form-col {
min-width: 0;
display: flex;
flex-direction: column;
}
.dock {
position: sticky;
top: calc(var(--header-h) + 20px);
display: flex;
flex-direction: column;
gap: 18px;
max-height: calc(100vh - var(--header-h) - 40px);
overflow-y: auto;
padding-bottom: 4px;
}
@media (max-width: 1180px) {
.workspace-grid {
grid-template-columns: 1fr;
}
.dock {
position: static;
max-height: none;
}
}
@media (max-width: 1080px) {
.app-header {
flex-wrap: wrap;
height: auto;
padding: 10px 14px;
gap: 10px;
}
.section-nav {
order: 3;
flex-basis: 100%;
}
}
@media (max-width: 720px) {
.workspace-grid {
padding: 18px 14px 60px;
}
}
/* ─── Panel cards (form sections) ────────────────────────────────────── */
.panel-card {
background: var(--surface-raised);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
margin-bottom: 20px;
box-shadow: var(--shadow-card);
}
.panel-head {
display: flex;
align-items: center;
justify-content: space-between;
gap: 16px;
padding: 16px 22px;
border-bottom: 1px solid var(--line);
}
.panel-head h2 {
font-size: 16px;
font-weight: 700;
letter-spacing: -0.1px;
}
.panel-body {
padding: 20px 22px 22px;
}
.subhead {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.06em;
font-weight: 700;
color: var(--ink-3);
margin: 20px 0 12px;
}
.subhead:first-child {
margin-top: 0;
}
/* ─── Plan vs. roast-day phase staging ──────────────────────────────────── */
.phase-divider {
display: flex;
align-items: baseline;
gap: 12px;
margin: 6px 0 20px;
padding-top: 18px;
border-top: 2px solid var(--line-strong);
}
.phase-divider-label {
font-size: 11px;
font-weight: 800;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ember-strong);
white-space: nowrap;
}
.phase-divider-text {
font-size: 12px;
color: var(--ink-3);
line-height: 1.45;
}
.phase-chip {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.06em;
text-transform: uppercase;
color: var(--ink-3);
background: var(--surface-sunken);
padding: 3px 9px;
border-radius: 999px;
white-space: nowrap;
}
.panel-card.phase-later {
opacity: 0.72;
transition: opacity 0.2s;
}
.panel-card.phase-later:hover,
.panel-card.phase-later:focus-within,
.panel-card.phase-later.has-data {
opacity: 1;
}
/* ─── Fields ──────────────────────────────────────────────────────────── */
.field-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
gap: 14px 18px;
margin-bottom: 14px;
}
.field-grid:last-child {
margin-bottom: 0;
}
.field {
position: relative;
display: flex;
flex-direction: column;
gap: 5px;
}
.field.wide {
grid-column: 1 / -1;
}
.field.accent .field-label {
color: var(--ember-strong);
}
.field-label {
font-size: 12px;
font-weight: 600;
color: var(--ink-2);
display: flex;
align-items: center;
gap: 4px;
}
.help-btn {
position: relative;
flex: 0 0 auto;
width: 15px;
height: 15px;
padding: 0;
border: 1px solid var(--line-strong);
border-radius: 50%;
background: none;
color: var(--ink-3);
font: 600 9.5px/1 var(--font-ui);
cursor: pointer;
display: inline-flex;
align-items: center;
justify-content: center;
}
/* Keeps the 15px visual dot but gives touch/mouse a ~25px target (WCAG's 24px minimum) —
matters on a mobile PWA. Kept modest rather than the fuller 44px so it doesn't swallow
clicks meant for the label text 4px away or the input just below. */
.help-btn::after {
content: "";
position: absolute;
inset: -5px;
}
.help-btn:hover,
.help-btn:focus-visible {
border-color: var(--ember);
color: var(--ember);
background: var(--ember-soft);
}
.help-btn:focus-visible {
outline: 2px solid var(--ember);
outline-offset: 1px;
}
.subhead .help-btn,
.dock-card-head .help-btn,
.panel-head h2 .help-btn {
margin-left: 6px;
vertical-align: 1px;
}
/* ─── Teaching layer ─────────────────────────────────────────────────────
Replaces the old per-field "?" tooltip system: a collapsible "why" panel
per section, a permanent one-line note under only the causally-loaded
fields, and fail-only reasoning on the sanity-check tiles. */
.why-panel {
margin: 0 0 16px;
border-left: 3px solid var(--ember);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
background: var(--ember-soft);
}
.why-panel summary {
cursor: pointer;
padding: 9px 12px;
font-size: 11px;
font-weight: 700;
letter-spacing: 0.05em;
text-transform: uppercase;
color: var(--ember-strong);
list-style-position: inside;
}
.why-panel summary:focus-visible {
outline: 2px solid var(--ember);
outline-offset: 2px;
}
.why-panel p {
padding: 0 14px 10px;
font-size: 12.5px;
line-height: 1.55;
color: var(--ink-2);
margin: 0 0 6px;
}
.why-panel p:last-child {
padding-bottom: 12px;
}
.field-note {
display: block;
font-size: 11px;
line-height: 1.45;
color: var(--ink-3);
margin: 2px 0 0;
}
/* ─── Cupping session ────────────────────────────────────────────────────── */
.cup-score-row,
.cup-tick-row,
.cup-stepper-row {
display: grid;
grid-template-columns: 140px 1fr auto auto;
align-items: center;
gap: 10px;
padding: 8px 0;
border-bottom: 1px solid var(--line);
}
.cup-stepper-row {
grid-template-columns: 140px auto auto auto;
}
.cup-score-row:last-of-type,
.cup-tick-row:last-of-type {
border-bottom: none;
}
.cup-score-label {
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
}
.cup-score-row input[type="range"] {
width: 100%;
accent-color: var(--ember);
}
.cup-score-row.unscored input[type="range"] {
opacity: 0.45;
}
.cup-score-value {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 700;
color: var(--ink);
min-width: 40px;
text-align: right;
}
.cup-score-clear {
padding: 3px 7px;
}
.cup-tick-cells {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.cup-tick {
width: 24px;
height: 24px;
border-radius: var(--radius-sm);
border: 1px solid var(--line-strong);
background: var(--surface-raised);
cursor: pointer;
}
.cup-tick[aria-pressed="true"] {
background: var(--ember);
border-color: var(--ember);
}
.flavor-chip {
display: inline-flex;
align-items: center;
gap: 6px;
padding: 5px 6px 5px 12px;
border-radius: 999px;
border: 1px solid var(--ember);
background: var(--ember-soft);
color: var(--ember-strong);
font-size: 12.5px;
font-weight: 500;
}
.flavor-chip-remove {
width: 24px;
height: 24px;
border: none;
border-radius: 50%;
background: transparent;
color: var(--ember-strong);
cursor: pointer;
font-size: 11px;
}
.flavor-chip-remove:hover {
background: rgba(168, 72, 26, 0.18);
}
.cup-stepper-row output {
min-width: 20px;
text-align: center;
font-family: var(--font-mono);
font-weight: 700;
}
.cupping-defects {
margin-top: 10px;
}
.cupping-defects summary {
cursor: pointer;
font-size: 12px;
font-weight: 700;
color: var(--ink-2);
padding: 6px 0;
}
.flavor-family {
margin-bottom: 8px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
padding: 4px 12px;
}
.flavor-family summary {
cursor: pointer;
padding: 8px 0;
font-size: 13px;
font-weight: 600;
color: var(--ink-2);
}
.flavor-family .subhead {
margin: 10px 0 6px;
}
#cup-radar {
width: 100%;
height: auto;
aspect-ratio: 1;
}
/* ─── Planner inventory/cupping integration ─────────────────────────────── */
.inventory-consume {
display: flex;
align-items: center;
gap: 10px;
margin-top: 14px;
padding: 10px 12px;
background: var(--surface-sunken);
border-radius: var(--radius-md);
}
.inventory-consume-label {
font-size: 12.5px;
color: var(--ink-2);
flex: 1 1 auto;
}
.cupping-link {
display: flex;
align-items: center;
gap: 12px;
margin-top: 16px;
padding-top: 16px;
border-top: 1px solid var(--line);
}
.field-input {
width: 100%;
padding: 8px 10px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
font-family: var(--font-ui);
font-size: 13.5px;
color: var(--ink);
background: var(--surface-raised);
transition:
border-color 0.15s,
box-shadow 0.15s;
}
.field-input.sm {
text-align: center;
font-family: var(--font-mono);
font-size: 13px;
}
.field-input.note {
font-family: var(--font-ui);
}
.field-input:focus {
outline: none;
border-color: var(--ember);
box-shadow: 0 0 0 3px var(--ember-soft);
}
.field-input.prefilled {
background: #fff6de;
border-color: #e4c766;
}
.field-input::placeholder {
color: var(--ink-3);
opacity: 0.7;
}
.unit-input {
position: relative;
}
.unit-input .field-input {
padding-right: 32px;
}
.unit-input.sm .field-input {
padding-right: 26px;
}
.unit-input .unit {
position: absolute;
right: 10px;
top: 50%;
transform: translateY(-50%);
font-size: 11.5px;
color: var(--ink-3);
pointer-events: none;
}
.unit-input.sm .unit {
right: 7px;
font-size: 10.5px;
}
/* field-id badge — hidden until hover/focus, or pinned via "Field IDs" toggle */
.field[data-fid]::after {
content: attr(data-fid);
position: absolute;
top: -6px;
right: 0;
font-family: var(--font-mono);
font-size: 9.5px;
font-weight: 700;
color: var(--ember);
background: var(--ember-soft);
padding: 1px 5px;
border-radius: 5px;
opacity: 0;
transition: opacity 0.12s;
pointer-events: none;
}
.field[data-fid]:hover::after,
.field[data-fid]:focus-within::after {
opacity: 1;
}
body.show-fids .field[data-fid]::after {
opacity: 1;
}
/* ─── Choice controls (segmented + chips) ───────────────────────────── */
.segmented {
display: inline-flex;
flex-wrap: wrap;
gap: 2px;
background: var(--surface-sunken);
padding: 2px;
border-radius: var(--radius-md);
}
.segmented.small {
padding: 2px;
}
.seg-opt {
position: relative;
cursor: pointer;
}
.seg-opt input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.seg-opt span {
display: block;
padding: 6px 12px;
border-radius: 8px;
font-size: 12.5px;
font-weight: 500;
color: var(--ink-2);
white-space: nowrap;
transition:
background 0.15s,
color 0.15s,
box-shadow 0.15s;
}
.seg-opt input:checked + span {
background: var(--surface-raised);
color: var(--ember-strong);
font-weight: 700;
box-shadow: 0 1px 2px rgba(27, 22, 20, 0.12);
}
.seg-opt input:focus-visible + span {
outline: 2px solid var(--ember);
outline-offset: 1px;
}
.chip-group {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.chip-opt {
cursor: pointer;
}
.chip-opt input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.chip-opt span {
display: inline-flex;
align-items: center;
padding: 7px 14px;
border-radius: 999px;
border: 1px solid var(--line);
font-size: 12.5px;
font-weight: 500;
color: var(--ink-2);
background: var(--surface-raised);
transition: all 0.15s;
}
.chip-opt input:checked + span {
background: var(--ember-soft);
border-color: var(--ember);
color: var(--ember-strong);
font-weight: 700;
}
.chip-opt input:focus-visible + span {
outline: 2px solid var(--ember);
outline-offset: 1px;
}
/* ─── Blend cards ─────────────────────────────────────────────────────── */
#blend-body.collapsed .blend-cards,
#blend-body.collapsed .blend-total,
#blend-body.collapsed #btn-add-blend,
#blend-body.collapsed .subhead,
#blend-body.collapsed .field.wide,
#blend-body.collapsed .field-grid {
display: none;
}
.blend-cards {
display: flex;
flex-direction: column;
gap: 10px;
margin-bottom: 12px;
}
.blend-card {
display: grid;
grid-template-columns: 1.4fr 1fr 1fr 0.7fr 0.8fr auto;
gap: 10px;
align-items: end;
background: var(--surface-sunken);
border-radius: var(--radius-md);
padding: 12px;
}
.blend-card .field-label {
font-size: 10.5px;
}
.blend-remove {
background: none;
border: 1px solid var(--line);
color: var(--ink-3);
width: 30px;
height: 30px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 13px;
}
.blend-remove:hover {
background: var(--fail-bg);
color: var(--fail);
border-color: var(--fail);
}
.blend-total {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 14px;
}
.blend-total-bar {
flex: 1 1 auto;
height: 6px;
border-radius: 999px;
background: var(--surface-sunken);
overflow: hidden;
}
.blend-total-fill {
height: 100%;
background: var(--ember);
width: 0%;
transition:
width 0.2s,
background 0.2s;
}
.blend-total-fill.over,
.blend-total-fill.under {
background: var(--fail);
}
.blend-total-label {
font-size: 11.5px;
font-family: var(--font-mono);
color: var(--ink-3);
white-space: nowrap;
}
@media (max-width: 640px) {
.blend-card {
grid-template-columns: 1fr 1fr;
}
}
/* ─── Milestone lists (temps / roast log) ────────────────────────────── */
.milestone-list {
display: flex;
flex-direction: column;
gap: 6px;
}
.milestone-row {
display: grid;
grid-template-columns: 110px 64px 1fr 90px;
gap: 12px;
align-items: center;
padding: 8px 4px;
border-bottom: 1px solid var(--line);
}
.milestone-row:last-child {
border-bottom: none;
}
.milestone-row.log-row {
grid-template-columns: 110px 64px 72px 64px 1fr;
}
.milestone-label {
font-size: 13px;
font-weight: 600;
color: var(--ink);
}
.milestone-plan {
font-family: var(--font-mono);
font-size: 13px;
color: var(--ink-2);
text-align: center;
background: var(--surface-sunken);
border-radius: var(--radius-sm);
padding: 6px 4px;
}
.milestone-time {
text-align: center;
}
.band-track {
position: relative;
height: 6px;
border-radius: 999px;
background: var(--surface-sunken);
}
.band-range {
position: absolute;
top: 0;
bottom: 0;
background: var(--pass-bg);
border-radius: 999px;
}
.band-marker {
position: absolute;
top: 50%;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--ink);
border: 2px solid var(--surface-raised);
box-shadow: 0 0 0 1px var(--ink-3);
transform: translate(-50%, -50%);
display: none;
transition: left 0.15s;
}
.band-marker.out-of-band {
background: var(--fail);
box-shadow: 0 0 0 1px var(--fail);
}
@media (max-width: 640px) {
.milestone-row,
.milestone-row.log-row {
grid-template-columns: 1fr;
gap: 6px;
}
.band-track {
order: 3;
}
}
/* ─── Actuator timeline ───────────────────────────────────────────────── */
.actuator-timeline {
display: flex;
flex-direction: column;
}
.actuator-step {
display: flex;
gap: 14px;
position: relative;
padding-bottom: 16px;
}
.actuator-step:last-child {
padding-bottom: 0;
}
.actuator-rail {
display: flex;
flex-direction: column;
align-items: center;
flex: 0 0 auto;
}
.actuator-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--ember);
margin-top: 10px;
flex: 0 0 auto;
}
.actuator-line {
width: 1px;
flex: 1 1 auto;
background: var(--line);
margin-top: 4px;
}
.actuator-step:last-child .actuator-line {
display: none;
}
.actuator-card {
flex: 1 1 auto;
background: var(--surface-sunken);
border-radius: var(--radius-md);
padding: 12px 14px;
display: grid;
grid-template-columns: 70px 1fr 1fr auto;
gap: 10px;
align-items: end;
}
.actuator-card .field-label {
font-size: 10.5px;
}
.actuator-why {
grid-column: 1 / -1;
}
.actuator-remove {
background: none;
border: 1px solid var(--line);
color: var(--ink-3);
width: 28px;
height: 28px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 12px;
align-self: start;
}
.actuator-remove:hover {
background: var(--fail-bg);
color: var(--fail);
border-color: var(--fail);
}
@media (max-width: 640px) {
.actuator-card {
grid-template-columns: 1fr 1fr;
}
}
/* ─── Dock: shared card chrome ───────────────────────────────────────── */
.dock-card {
background: var(--surface-raised);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
padding: 18px 20px;
}
.dock-card-head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 12px;
}
.dock-card-head h2 {
font-size: 13px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--ink-2);
}
/* ─── Ledger ──────────────────────────────────────────────────────────── */
.ledger-group {
display: flex;
flex-direction: column;
}
.ledger-row {
display: flex;
align-items: center;
gap: 8px;
padding: 6px 0;
border-bottom: 1px solid var(--line);
}
.ledger-row:last-child {
border-bottom: none;
}
.l-fid {
font-family: var(--font-mono);
font-size: 9.5px;
color: var(--ink-3);
background: var(--surface-sunken);
border-radius: 4px;
padding: 1px 4px;
flex: 0 0 auto;
}
.l-label {
font-size: 12.5px;
color: var(--ink-2);
flex: 1 1 auto;
}
.l-val {
font-family: var(--font-mono);
font-size: 13px;
font-weight: 600;
color: var(--ink);
text-align: right;
min-width: 52px;
}
.ledger-input {
width: 60px;
text-align: right;
font-family: var(--font-mono);
font-size: 13px;
font-weight: 600;
padding: 4px 7px;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background: var(--surface-sunken);
color: var(--ink);
}
.ledger-input:focus {
outline: none;
border-color: var(--ember);
box-shadow: 0 0 0 3px var(--ember-soft);
background: var(--surface-raised);
}
.ledger-total {
display: flex;
align-items: baseline;
justify-content: space-between;
margin: 10px 0;
padding-top: 8px;
border-top: 1.5px solid var(--ink);
}
.ledger-total span {
font-size: 12px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.04em;
color: var(--ink-2);
}
.ledger-total output {
font-family: var(--font-serif);
font-size: 26px;
font-weight: 600;
color: var(--ink);
}
.ledger-total.drop {
border-top: none;
margin-top: 4px;
padding-top: 10px;
background: var(--ember-soft);
border-radius: var(--radius-md);
padding: 12px 14px;
margin-bottom: 0;
}
.ledger-total.drop span {
color: var(--ember-strong);
}
.ledger-total.drop output {
color: var(--ember-strong);
font-size: 30px;
}
.ledger-info {
display: flex;
align-items: center;
gap: 8px;
flex-wrap: wrap;
margin: 10px 0 16px;
padding: 8px 10px;
border-left: 2px solid var(--line-strong);
background: var(--surface-sunken);
border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.info-pill {
display: flex;
align-items: center;
gap: 5px;
font-size: 11.5px;
color: var(--ink-2);
}
.info-pill span {
color: var(--ink-3);
}
.info-pill output {
font-family: var(--font-mono);
font-weight: 600;
}
.info-note {
font-size: 10px;
color: var(--ink-3);
flex-basis: 100%;
}
/* ─── Sanity checks ───────────────────────────────────────────────────── */
.checks-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 10px;
}
.check-tile {
border: 1px solid var(--line);
border-radius: var(--radius-md);
padding: 10px 12px;
display: flex;
flex-direction: column;
gap: 2px;
position: relative;
}
.check-name {
font-size: 10.5px;
color: var(--ink-3);
font-weight: 600;
text-transform: uppercase;
letter-spacing: 0.02em;
}
.check-value {
font-family: var(--font-mono);
font-size: 18px;
font-weight: 700;
color: var(--ink);
}
.check-band {
font-size: 10.5px;
color: var(--ink-3);
}
.check-tile::before {
content: "";
position: absolute;
top: 10px;
right: 10px;
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ink-3);
}
.check-tile.pass::before {
background: var(--pass);
}
.check-tile.fail::before {
background: var(--fail);
}
.check-tile.pass {
border-color: var(--pass-bg);
background: var(--pass-bg);
}
.check-tile.fail {
border-color: var(--fail-bg);
background: var(--fail-bg);
}
.check-tile.pass .check-value {
color: var(--pass);
}
.check-tile.fail .check-value {
color: var(--fail);
}
.check-why {
display: none;
font-size: 10.5px;
line-height: 1.4;
color: var(--ink-2);
}
.check-tile.fail .check-why {
display: block;
}
.check-tile.informational .check-why {
display: block;
}
.check-tile.informational .check-band {
font-style: italic;
}
.refine-suggestion {
margin-top: 6px;
padding: 8px 10px;
border-radius: var(--radius-sm);
border: 1px solid var(--ember-soft);
background: var(--ember-soft);
display: flex;
flex-direction: column;
align-items: flex-start;
gap: 6px;
}
.refine-suggestion span {
font-size: 11.5px;
color: var(--ink-2);
line-height: 1.4;
}
.dock-note {
font-size: 10.5px;
color: var(--ink-3);
line-height: 1.4;
margin: -4px 0 10px;
}
/* ─── Curve card ──────────────────────────────────────────────────────── */
.curve-legend {
display: flex;
align-items: center;
gap: 10px;
font-size: 10.5px;
color: var(--ink-3);
text-transform: none;
letter-spacing: 0;
font-weight: 500;
}
.curve-legend .sw {
display: inline-block;
width: 12px;
height: 2px;
margin-right: 3px;
vertical-align: middle;
}
.sw-plan {
background: var(--ink);
}
.sw-ref {
background: var(--ink-3);
border-top: 1px dashed var(--ink-3);
background: none;
height: 0;
border-bottom: 1.5px dashed var(--ink-3);
}
.curve-wrap {
border-radius: var(--radius-md);
overflow: hidden;
border: 1px solid var(--line);
}
.ledger-warnings {
padding: 10px 12px;
border: 1px solid #e8d49e;
border-radius: var(--radius-md);
background: var(--warn-bg);
color: #705318;
font-size: 12px;
}
.ledger-warnings p {
margin: 0;
}
.plan-list {
margin: 0;
padding: 0;
list-style: none;
display: grid;
gap: 8px;
}
.plan-list-row {
display: flex;
align-items: stretch;
gap: 6px;
}
.plan-list-item {
width: 100%;
display: grid;
gap: 3px;
padding: 12px;
text-align: left;
border: 1px solid var(--line);
border-radius: var(--radius-sm);
background: var(--surface-raised);
color: var(--ink);
font: inherit;
cursor: pointer;
}
.plan-list-delete {
flex-shrink: 0;
align-self: center;
}
.plan-list-item:hover,
.plan-list-item.active {
border-color: var(--ember);
background: var(--ember-soft);
}
.plan-list-item strong {
font-size: 13px;
}
.plan-list-item span {
font-size: 11px;
color: var(--ink-3);
}
/* ─── Side navigation (app shell) ──────────────────────────────────────── */
.app-shell {
display: flex;
min-height: 100vh;
}
.side-nav {
position: fixed;
top: 0;
bottom: 0;
left: 0;
width: var(--nav-w);
/* Above .drawer-overlay (40) and .drawer (41): when the mobile off-canvas nav is open, its
own backdrop must not intercept clicks on the nav's items (matches how right-side drawers
already sit above the same shared overlay). */
z-index: 42;
display: flex;
flex-direction: column;
background: var(--roast);
color: var(--cream);
transition: width 0.18s ease;
overflow-x: hidden;
}
.side-nav-brand {
display: flex;
align-items: center;
gap: 10px;
padding: 16px;
flex: 0 0 auto;
white-space: nowrap;
}
.side-nav-brand .brand-mark {
color: var(--ember-soft, #fbefe6);
font-size: 22px;
}
.side-nav-brand span.brand-name {
font-weight: 700;
font-size: 14.5px;
letter-spacing: -0.1px;
}
.nav-group {
display: flex;
flex-direction: column;
gap: 2px;
padding: 6px 8px;
}
.nav-group-title {
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.08em;
font-weight: 700;
color: rgba(251, 248, 244, 0.45);
padding: 12px 10px 6px;
white-space: nowrap;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 9px 10px;
border-radius: var(--radius-sm);
color: rgba(251, 248, 244, 0.82);
text-decoration: none;
font-size: 13.5px;
font-weight: 500;
background: transparent;
border: none;
width: 100%;
text-align: left;
cursor: pointer;
font-family: inherit;
white-space: nowrap;
position: relative;
}
.nav-item:hover {
background: rgba(251, 248, 244, 0.08);
color: var(--cream);
}
.nav-item .nav-icon {
flex: 0 0 auto;
width: 18px;
text-align: center;
font-size: 15px;
}
.nav-item[aria-current="page"] {
background: rgba(168, 72, 26, 0.35);
color: #fff;
font-weight: 700;
}
.nav-item[aria-current="page"]::before {
content: "";
position: absolute;
left: -8px;
top: 6px;
bottom: 6px;
width: 3px;
border-radius: 2px;
background: var(--ember);
}
.nav-spacer {
flex: 1 1 auto;
}
.nav-collapse-toggle {
margin: 8px;
align-self: flex-start;
}
.nav-user {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-top: 1px solid rgba(251, 248, 244, 0.12);
}
.nav-user-avatar {
flex: 0 0 auto;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--ember);
color: #fff;
display: flex;
align-items: center;
justify-content: center;
font-size: 12.5px;
font-weight: 700;
}
.nav-user-detail {
min-width: 0;
display: flex;
flex-direction: column;
white-space: nowrap;
}
.nav-user-email {
font-size: 12px;
color: var(--cream);
overflow: hidden;
text-overflow: ellipsis;
}
.nav-user-logout {
font-size: 11px;
color: rgba(251, 248, 244, 0.55);
background: none;
border: none;
padding: 0;
text-align: left;
cursor: pointer;
font-family: inherit;
}
.nav-user-logout:hover {
color: var(--cream);
text-decoration: underline;
}
.app-workspace {
flex: 1 1 auto;
min-width: 0;
margin-left: var(--nav-w);
transition: margin-left 0.18s ease;
}
.app-workspace.nav-collapsed {
margin-left: var(--nav-w-collapsed);
}
.nav-hamburger {
display: none;
}
/* The user-toggled collapse (desktop only — the toggle button itself is hidden below 901px, but
the persisted .collapsed class isn't otherwise scoped, and its higher specificity than the
<900px off-canvas rules would otherwise still shrink/relabel the mobile drawer if a user had
collapsed the rail on desktop before viewing on a phone). */
@media (min-width: 901px) {
.side-nav.collapsed {
width: var(--nav-w-collapsed);
}
.side-nav.collapsed .brand-name,
.side-nav.collapsed .nav-label,
.side-nav.collapsed .nav-group-title,
.side-nav.collapsed .nav-user-detail {
display: none;
}
.side-nav.collapsed .nav-item {
justify-content: center;
padding: 10px;
}
}
/* Icon-only rail: only between the mobile off-canvas breakpoint and the desktop breakpoint —
scoped with min-width so it never fights the <900px off-canvas rules below for the same
element (that previously caused a CSS-specificity conflict that broke the mobile nav width). */
@media (min-width: 901px) and (max-width: 1180px) {
.side-nav {
width: var(--nav-w-collapsed);
}
.side-nav .brand-name,
.side-nav .nav-label,
.side-nav .nav-group-title,
.side-nav .nav-user-detail {
display: none;
}
.side-nav .nav-item {
justify-content: center;
padding: 10px;
}
.app-workspace {
margin-left: var(--nav-w-collapsed);
}
}
@media (max-width: 900px) {
.side-nav {
transform: translateX(-100%);
transition: transform 0.2s;
width: min(280px, 82vw);
}
.side-nav.mobile-open {
transform: translateX(0);
}
.side-nav .brand-name,
.side-nav .nav-label,
.side-nav .nav-group-title,
.side-nav .nav-user-detail {
display: block;
}
.side-nav .nav-item {
justify-content: flex-start !important;
}
.app-workspace {
margin-left: 0 !important;
}
.nav-hamburger {
display: inline-flex;
}
.nav-collapse-toggle {
display: none;
}
}
/* ─── Account / admin content shell ─────────────────────────────────────── */
.page-content {
max-width: 860px;
margin: 0 auto;
padding: 28px 24px 80px;
display: flex;
flex-direction: column;
gap: 20px;
}
.page-content.wide {
max-width: 1160px;
}
.panel-head p.panel-sub {
margin: 2px 0 0;
font-size: 12px;
color: var(--ink-3);
font-weight: 400;
}
.field-row {
display: flex;
gap: 14px;
flex-wrap: wrap;
}
.field-row .field {
flex: 1 1 200px;
}
.session-device-label {
font-size: 12.5px;
color: var(--ink-2);
display: inline-block;
max-width: 200px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
vertical-align: middle;
}
.danger-zone {
border-color: var(--fail-bg);
}
.danger-zone .panel-head {
background: var(--fail-bg);
}
.danger-zone .panel-head h2 {
color: var(--fail);
}
.inline-form {
display: flex;
gap: 8px;
flex-wrap: wrap;
align-items: flex-end;
}
.inline-form .field {
flex: 1 1 160px;
}
.empty-state {
font-size: 13px;
color: var(--ink-3);
padding: 14px 0;
text-align: center;
}
@media (max-width: 720px) {
.page-content {
padding: 16px 14px 60px;
}
}
/* ─── Data table (admin, account) ──────────────────────────────────────── */
.data-table {
width: 100%;
border-collapse: collapse;
font-size: 13px;
}
.data-table th {
text-align: left;
font-size: 10.5px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--ink-3);
font-weight: 700;
padding: 9px 10px;
background: var(--surface-sunken);
border-bottom: 1px solid var(--line);
white-space: nowrap;
}
.data-table td {
padding: 10px;
border-bottom: 1px solid var(--line);
vertical-align: middle;
}
.data-table tbody tr:hover {
background: var(--surface-sunken);
}
.data-table td.num {
text-align: right;
font-variant-numeric: tabular-nums;
}
.data-table-actions {
display: flex;
gap: 6px;
justify-content: flex-end;
flex-wrap: wrap;
}
.table-wrap {
overflow-x: auto;
border: 1px solid var(--line);
border-radius: var(--radius-md);
}
.table-wrap .data-table {
border: none;
}
.data-table tr.archived {
opacity: 0.55;
}
.lot-remaining {
display: flex;
align-items: center;
gap: 8px;
min-width: 160px;
}
.lot-remaining .blend-total-bar {
flex: 1 1 auto;
}
.lot-remaining .blend-total-label {
white-space: nowrap;
}
/* ─── Badges, switch, toast, stat tiles ─────────────────────────────────── */
.badge {
display: inline-flex;
align-items: center;
padding: 2px 9px;
border-radius: 999px;
font-size: 10.5px;
font-weight: 700;
text-transform: uppercase;
letter-spacing: 0.03em;
}
.badge-admin {
background: var(--ember-soft);
color: var(--ember-strong);
}
.badge-user {
background: var(--surface-sunken);
color: var(--ink-2);
}
.badge-disabled {
background: var(--fail-bg);
color: var(--fail);
}
.badge-current {
background: var(--pass-bg);
color: var(--pass);
}
.switch {
position: relative;
display: inline-block;
width: 38px;
height: 22px;
flex: 0 0 auto;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.switch-track {
position: absolute;
inset: 0;
background: var(--line-strong);
border-radius: 999px;
cursor: pointer;
transition: background 0.15s;
}
.switch-track::before {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 18px;
height: 18px;
border-radius: 50%;
background: #fff;
transition: transform 0.15s;
box-shadow: 0 1px 2px rgba(27, 22, 20, 0.3);
}
.switch input:checked + .switch-track {
background: var(--ember);
}
.switch input:checked + .switch-track::before {
transform: translateX(16px);
}
.switch input:focus-visible + .switch-track {
outline: 2px solid var(--ember);
outline-offset: 2px;
}
.toast-stack {
position: fixed;
right: 18px;
bottom: 18px;
z-index: 60;
display: flex;
flex-direction: column;
gap: 8px;
max-width: min(360px, calc(100vw - 36px));
}
.toast {
background: var(--ink);
color: var(--cream);
padding: 11px 14px;
border-radius: var(--radius-sm);
font-size: 12.5px;
box-shadow: var(--shadow-pop);
display: flex;
align-items: center;
gap: 10px;
animation: toast-in 0.18s ease-out;
}
.toast.warn {
background: var(--warn);
}
.toast.fail {
background: var(--fail);
}
@keyframes toast-in {
from {
opacity: 0;
transform: translateY(8px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.stat-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
gap: 14px;
}
.stat-tile {
background: var(--surface-raised);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
padding: 16px 18px;
}
.stat-tile-label {
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--ink-3);
font-weight: 700;
}
.stat-tile-value {
font-family: var(--font-serif);
font-size: 26px;
font-weight: 600;
color: var(--ink);
margin-top: 4px;
}
/* ─── Auth pages (login/signup/forgot/reset/setup) ─────────────────────── */
.auth-page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
padding: 32px 18px;
}
.auth-card {
width: 100%;
max-width: 400px;
padding: 30px 28px 28px;
display: flex;
flex-direction: column;
gap: 14px;
}
.auth-card .auth-brand {
display: flex;
align-items: center;
gap: 8px;
margin-bottom: 2px;
}
.auth-card .auth-brand .brand-mark {
color: var(--ember);
font-size: 22px;
}
.auth-card h1 {
font-size: 19px;
font-weight: 700;
}
.auth-card p.lede {
margin: -6px 0 4px;
font-size: 13px;
color: var(--ink-2);
}
.auth-card form {
display: flex;
flex-direction: column;
gap: 12px;
}
.auth-card label {
display: flex;
flex-direction: column;
gap: 5px;
font-size: 12.5px;
font-weight: 600;
color: var(--ink-2);
}
.auth-card .primary-btn {
padding: 10px 12px;
font-size: 13.5px;
margin-top: 4px;
}
.auth-links {
display: flex;
justify-content: space-between;
font-size: 12.5px;
}
.auth-links a {
color: var(--ember);
text-decoration: none;
}
.auth-links a:hover {
text-decoration: underline;
}
.auth-message {
font-size: 12.5px;
border-radius: var(--radius-sm);
padding: 9px 11px;
}
.auth-message:empty {
display: none;
}
.auth-message.error {
background: var(--fail-bg);
color: var(--fail);
}
.auth-message.info {
background: var(--pass-bg);
color: var(--pass);
}
.auth-page .muted {
font-size: 11.5px;
color: var(--ink-3);
text-align: center;
margin: 0;
}
/* ─── Marketing landing page ────────────────────────────────────────────── */
.marketing-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 16px 28px;
max-width: 1160px;
margin: 0 auto;
}
.marketing-header .brand {
display: flex;
align-items: center;
gap: 8px;
font-weight: 700;
font-size: 15px;
}
.marketing-header .brand .brand-mark {
color: var(--ember);
font-size: 20px;
}
.marketing-header nav {
display: flex;
gap: 10px;
}
.hero {
background: var(--roast);
color: var(--cream);
}
.hero-inner {
max-width: 1160px;
margin: 0 auto;
padding: 48px 28px 76px;
display: grid;
grid-template-columns: 1.1fr 1fr;
gap: 40px;
align-items: center;
}
.hero h1 {
font-family: var(--font-serif);
font-size: clamp(32px, 4.6vw, 44px);
line-height: 1.12;
letter-spacing: -0.5px;
margin: 0 0 16px;
}
.hero p.lede {
font-size: 16px;
line-height: 1.55;
color: rgba(251, 248, 244, 0.82);
margin: 0 0 24px;
max-width: 46ch;
}
.hero-ctas {
display: flex;
gap: 10px;
flex-wrap: wrap;
}
.hero-ctas .ghost-btn {
color: var(--cream);
border-color: rgba(251, 248, 244, 0.35);
}
.hero-ctas .ghost-btn:hover {
background: rgba(251, 248, 244, 0.12);
color: #fff;
}
.hero-visual {
background: var(--surface-raised);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-pop);
padding: 18px 20px;
color: var(--ink);
}
.marketing-section {
max-width: 1160px;
margin: 0 auto;
padding: 64px 28px;
}
.marketing-section h2 {
font-size: 24px;
font-weight: 700;
text-align: center;
margin: 0 0 8px;
}
.marketing-section p.section-lede {
text-align: center;
color: var(--ink-2);
max-width: 52ch;
margin: 0 auto 36px;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
gap: 18px;
}
.feature-card {
background: var(--surface-raised);
border: 1px solid var(--line);
border-radius: var(--radius-lg);
box-shadow: var(--shadow-card);
padding: 20px 22px;
}
.feature-card .feature-icon {
font-size: 20px;
color: var(--ember);
margin-bottom: 10px;
}
.feature-card h3 {
font-size: 15px;
font-weight: 700;
margin: 0 0 8px;
}
.feature-card p {
font-size: 13.5px;
color: var(--ink-2);
line-height: 1.5;
margin: 0;
}
.feature-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
gap: 14px;
margin-top: 14px;
}
.feature-row-item {
display: flex;
gap: 10px;
align-items: flex-start;
font-size: 13px;
color: var(--ink-2);
}
.feature-row-item .feature-icon {
color: var(--ember);
flex: 0 0 auto;
}
.steps-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 22px;
counter-reset: step;
}
.step-item {
position: relative;
padding-left: 40px;
}
.step-item::before {
counter-increment: step;
content: counter(step);
position: absolute;
left: 0;
top: 0;
width: 28px;
height: 28px;
border-radius: 50%;
background: var(--ember-soft);
color: var(--ember-strong);
font-weight: 700;
font-size: 13px;
display: flex;
align-items: center;
justify-content: center;
}
.step-item h3 {
font-size: 14px;
font-weight: 700;
margin: 0 0 4px;
}
.step-item p {
font-size: 13px;
color: var(--ink-2);
margin: 0;
line-height: 1.5;
}
.cta-band {
background: var(--ember);
color: #fff;
text-align: center;
padding: 52px 28px;
}
.cta-band h2 {
font-family: var(--font-serif);
font-size: 26px;
margin: 0 0 18px;
}
.cta-band .primary-btn {
background: #fff;
color: var(--ember-strong);
border-color: #fff;
padding: 11px 20px;
font-size: 14px;
}
.cta-band .primary-btn:hover {
background: var(--surface-sunken);
}
.marketing-footer {
padding: 22px 28px 40px;
max-width: 1160px;
margin: 0 auto;
display: flex;
justify-content: space-between;
align-items: center;
font-size: 12.5px;
color: var(--ink-3);
}
.marketing-footer a {
color: var(--ink-2);
margin-left: 14px;
text-decoration: none;
}
.marketing-footer a:hover {
text-decoration: underline;
}
@media (max-width: 860px) {
.hero-inner {
grid-template-columns: 1fr;
padding-bottom: 48px;
}
.hero-visual {
order: -1;
}
}
/* ─── PWA and mobile layout ───────────────────────────────────────────── */
.connection-status {
position: sticky;
top: var(--header-h);
z-index: 29;
padding: 8px 14px;
background: var(--warn-bg);
border-bottom: 1px solid #e8d49e;
color: #705318;
font-size: 13px;
font-weight: 600;
text-align: center;
}
.connection-status.hidden {
display: none;
}
@media (max-width: 720px) {
:root {
/* The mobile header is multi-row (top bar / section pills / actions); keep this in sync
with its actual stacked height so sticky/scroll-anchor offsets stay correct. */
--header-h: 166px;
}
body {
font-size: 16px;
padding-bottom: env(safe-area-inset-bottom);
}
.app-header {
position: sticky;
padding: 10px 12px;
gap: 8px;
padding-top: max(10px, env(safe-area-inset-top));
}
.header-row-top {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
width: 100%;
order: 0;
}
.brand-mark {
font-size: 24px;
}
.brand-text h1 {
font-size: 16px;
}
.brand-sub {
font-size: 12px;
}
.section-nav {
width: 100%;
order: 2;
margin: 0;
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 4px;
}
.section-nav a {
min-height: 40px;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 7px 2px;
font-size: 11.5px;
text-align: center;
}
.header-actions {
order: 3;
width: 100%;
display: flex;
align-items: center;
gap: 8px;
min-width: 0;
}
/* The chip yields space to the buttons rather than forcing the row wide. */
.header-actions .autosave-chip {
flex: 1 1 0;
min-width: 0;
justify-content: center;
overflow: hidden;
}
.header-actions .autosave-text {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.header-actions #btn-print,
.header-actions #btn-expand-why {
flex: 0 0 auto;
min-height: 44px;
padding: 8px 16px;
}
.header-divider {
display: none;
}
.autosave-chip {
min-height: 32px;
margin: 0;
}
.ghost-btn,
.primary-btn,
label.filebtn {
min-height: 44px;
}
.drawer-body .ghost-btn,
.drawer-body .primary-btn,
.drawer-body label.filebtn {
width: 100%;
text-align: center;
}
.workspace-grid {
padding: 14px 10px 48px;
gap: 14px;
}
.panel-card {
margin-bottom: 14px;
border-radius: 12px;
}
.panel-head {
padding: 14px;
align-items: flex-start;
}
.panel-head h2 {
font-size: 16px;
padding-top: 4px;
}
.panel-body {
padding: 14px;
}
.why-panel p {
font-size: 13px;
}
.field-grid {
grid-template-columns: 1fr;
gap: 12px;
}
.field-input,
.text-input {
min-height: 44px;
font-size: 16px;
}
.field-input.sm {
font-size: 16px;
}
.segmented {
display: flex;
width: 100%;
}
.seg-opt {
flex: 1 1 auto;
}
.seg-opt span {
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
padding: 6px 9px;
text-align: center;
white-space: normal;
}
.chip-group {
gap: 7px;
}
.chip-opt {
flex: 1 1 auto;
}
.chip-opt span {
min-height: 40px;
justify-content: center;
width: 100%;
padding: 7px 10px;
text-align: center;
}
.blend-card {
padding: 12px;
gap: 10px;
}
.blend-remove,
.actuator-remove,
.icon-btn {
min-width: 44px;
min-height: 44px;
}
.milestone-row,
.milestone-row.log-row {
padding: 12px 0;
}
.milestone-plan {
min-height: 40px;
display: flex;
align-items: center;
justify-content: center;
}
.actuator-step {
gap: 0;
}
.actuator-rail {
display: none;
}
.actuator-card {
padding: 12px;
}
.dock-card {
padding: 14px;
border-radius: 12px;
}
.checks-grid {
grid-template-columns: 1fr;
}
.check-tile {
min-height: 76px;
}
.phase-divider {
flex-direction: column;
gap: 4px;
}
.panel-head {
flex-wrap: wrap;
}
.curve-wrap {
overflow-x: auto;
}
#curve-svg-live {
min-width: 520px;
}
.drawer {
width: 100%;
max-width: none;
}
.drawer-head {
padding: 14px;
}
.drawer-body {
padding: 14px;
}
}
@media (prefers-reduced-motion: reduce) {
*,
*::before,
*::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
scroll-behavior: auto !important;
transition-duration: 0.01ms !important;
}
}
/* ─── Print: hide the screen shell, show the paper worksheet ───────────── */
#print-sheet {
display: none;
}
.pv {
display: none;
}
@media print {
.side-nav,
.app-header,
.drawer,
.drawer-overlay,
.workspace-grid {
display: none !important;
}
/* .app-shell/.app-workspace are otherwise flex/margin containers sized for the on-screen
nav; with everything inside them hidden above, they'd still occupy space and push
#print-sheet (a sibling further down the body) below a blank page. */
.app-shell,
.app-workspace {
display: contents;
}
body {
background: #fff;
}
#print-sheet {
display: block;
}
.ws-input {
display: none !important;
}
.pv {
display: inline-block;
min-width: inherit;
border-bottom: 0.9pt solid var(--ink);
}
.opt input:checked + span {
border: 0.9pt solid var(--ink);
background: none;
color: var(--ink);
border-radius: 50%;
padding: 0 1.4mm;
}
td.passcell.pass::after,
td.passcell.fail::after {
content: none;
}
td.passcell.pass::before {
content: "✓ ";
}
td.passcell.fail::before {
content: "✗ ";
}
td.passcell.informational::before {
content: "— ";
color: var(--mid);
}
}
/* ─── Legacy .opt styling reused by the (hidden-on-screen) print worksheet's own radios ── */
.opt {
display: inline-block;
cursor: pointer;
margin-right: 1.5mm;
}
.opt input {
position: absolute;
opacity: 0;
width: 0;
height: 0;
}
.opt span {
padding: 0 0.8mm;
border-radius: 3mm;
}
.opt input:checked + span {
background: #f6efe7;
border: 0.9pt solid #7a3b12;
color: #7a3b12;
font-weight: 700;
}
input.f,
select.f {
border: none;
border-bottom: 0.9pt solid #1a1512;
background: transparent;
font: inherit;
height: 4.2mm;
padding: 0 0.5mm;
color: #1a1512;
}
/* ── Brewer picker + brew log (brews/beans pages) ─────────────────────── */
.brewer-cat-title {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--ink-3);
margin: 14px 0 6px;
}
.brewer-cat-title:first-child {
margin-top: 0;
}
.brewer-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(94px, 1fr));
gap: 8px;
}
.brewer-tile {
display: flex;
flex-direction: column;
align-items: center;
gap: 6px;
padding: 10px 6px 8px;
border: 1px solid var(--line);
border-radius: var(--radius-md);
background: var(--surface-raised);
cursor: pointer;
color: var(--ink-2);
font: inherit;
transition: border-color 0.12s, background 0.12s, color 0.12s;
}
.brewer-tile:hover {
border-color: var(--line-strong);
color: var(--ink);
}
.brewer-tile.selected {
border-color: var(--ember);
color: var(--ember-strong);
background: var(--ember-soft);
box-shadow: inset 0 0 0 1px var(--ember);
}
.brewer-tile svg {
width: 42px;
height: 42px;
display: block;
}
.brewer-tile .brewer-name {
font-size: 11.5px;
line-height: 1.2;
text-align: center;
}
.method-chip {
display: inline-flex;
align-items: center;
gap: 6px;
white-space: nowrap;
}
.method-chip svg {
width: 20px;
height: 20px;
flex: none;
color: var(--ink-2);
}
.rating-pill {
display: inline-block;
min-width: 34px;
text-align: center;
padding: 2px 8px;
border-radius: 999px;
background: var(--surface-sunken);
font-variant-numeric: tabular-nums;
font-weight: 600;
}
.rating-pill.good {
background: var(--pass-bg);
color: var(--pass);
}
.rating-pill.poor {
background: var(--fail-bg);
color: var(--fail);
}
.token-reveal {
background: var(--surface-sunken);
border: 1px dashed var(--line-strong);
border-radius: var(--radius-sm);
padding: 10px 12px;
font-family: var(--font-mono, ui-monospace, monospace);
font-size: 12.5px;
word-break: break-all;
user-select: all;
}
/* ── Plan chat drawer ─────────────────────────────────────────────────── */
.chat-thread {
display: flex;
flex-direction: column;
gap: 8px;
max-height: 50vh;
overflow-y: auto;
margin-bottom: 10px;
}
.chat-msg {
padding: 8px 11px;
border-radius: var(--radius-md);
font-size: 13px;
line-height: 1.45;
white-space: pre-wrap;
max-width: 92%;
}
.chat-msg.user {
align-self: flex-end;
background: var(--ember-soft);
color: var(--ember-strong);
}
.chat-msg.assistant {
align-self: flex-start;
background: var(--surface-sunken);
color: var(--ink);
}
.chat-msg.pending {
color: var(--ink-3);
font-style: italic;
}
.chat-input-row {
display: flex;
gap: 8px;
align-items: flex-end;
}
.chat-input-row textarea {
flex: 1;
resize: vertical;
min-height: 40px;
}
/* ── Planner header tools (page-scoped actions moved out of the left nav) ──
Desktop: the tools render inline in the header actions row (display: contents).
≤720px: they collapse behind a single "Tools ▾" disclosure that opens a dropdown card, so
the mobile header keeps its fixed three-row height (--header-h) with no wrapped or
horizontally scrolling button rows. */
.header-tools {
flex-wrap: wrap;
justify-content: flex-end;
row-gap: 6px;
max-width: 100%;
position: relative;
}
.header-tools-toggle {
display: none;
}
.header-tools-group {
display: contents;
}
@media (max-width: 720px) {
.header-tools {
flex-wrap: nowrap;
}
.header-tools-toggle {
display: inline-flex;
align-items: center;
flex: 0 0 auto;
}
.header-tools-toggle[aria-expanded="true"] {
background: var(--ember-soft);
border-color: var(--ember);
color: var(--ember-strong);
}
.header-tools-group {
display: none;
}
.header-tools-group.open {
display: flex;
flex-direction: column;
align-items: stretch;
gap: 6px;
position: absolute;
right: 0;
top: calc(100% + 6px);
z-index: 70;
min-width: 220px;
padding: 10px;
background: var(--surface-raised);
border: 1px solid var(--line);
border-radius: var(--radius-md);
box-shadow: var(--shadow-pop);
}
.header-tools-group .ghost-btn {
justify-content: flex-start;
text-align: left;
width: 100%;
}
.header-tools-group .header-divider {
display: block;
width: 100%;
height: 1px;
margin: 2px 0;
}
}