/**
 * WHOLESALE.CSS
 * Wholesale orders — order blocks with real line items.
 * Rogue Origin Hemp Processing
 *
 * The app shell (layout, sidebar, header, buttons, empty/loading states) is
 * kept identical to complaints.css on purpose — every page in this hub shares
 * one chrome, and divergence here would read as a different product. Page
 * specifics start at ORDER BOARD.
 *
 * Design: docs/plans/2026-08-19-order-blocks-design.md
 */

/* ============================================
   APP LAYOUT
   ============================================ */

.app-container {
  display: flex;
  min-height: 100vh;
  background: var(--bg);
}

.sidebar {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: 260px;
  background: #070908;
  border-right: 1px solid rgba(255, 255, 255, 0.06);
  display: flex;
  flex-direction: column;
  z-index: 100;
  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

[data-theme="light"] .sidebar {
  background: #ffffff;
  border-right: 1px solid #e5e7eb;
}

.sidebar.collapsed {
  transform: translateX(-260px);
}

.main {
  flex: 1;
  margin-left: 260px;
  display: flex;
  flex-direction: column;
  min-height: 100vh;
  transition: margin-left 0.3s cubic-bezier(0.16, 1, 0.3, 1);
}

.sidebar.collapsed ~ .main {
  margin-left: 0;
}

/* ============================================
   SIDEBAR
   ============================================ */

.sidebar-header {
  padding: var(--space-md);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}

[data-theme="light"] .sidebar-header {
  border-bottom-color: #e5e7eb;
}

.sidebar-logo {
  width: 40px;
  height: 40px;
  flex-shrink: 0;
  filter: drop-shadow(0 2px 6px rgba(102, 137, 113, 0.2));
}

.sidebar-brand {
  font-family: var(--font-display);
  font-size: 1.375rem;
  font-weight: 400;
  color: var(--text);
  letter-spacing: -0.01em;
  line-height: 1.2;
}

[data-theme="light"] .sidebar-brand {
  color: #111827;
}

.sidebar-nav {
  flex: 1;
  padding: var(--space-md);
  overflow-y: auto;
}

.nav-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 12px 16px;
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 500;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.2s ease;
  margin-bottom: var(--space-xs);
  text-decoration: none;
  border: 1px solid transparent;
}

.nav-item:hover {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.06);
}

.nav-item.active {
  background: linear-gradient(135deg,
    rgba(228, 170, 79, 0.1) 0%,
    rgba(228, 170, 79, 0.04) 100%
  );
  color: var(--ro-gold);
  border-color: rgba(228, 170, 79, 0.2);
  box-shadow: 0 0 24px rgba(228, 170, 79, 0.06);
}

[data-theme="light"] .nav-item {
  color: #6b7280;
}

[data-theme="light"] .nav-item:hover {
  background: #f9fafb;
  color: #111827;
  border-color: #e5e7eb;
}

[data-theme="light"] .nav-item.active {
  background: linear-gradient(135deg,
    rgba(102, 137, 113, 0.1) 0%,
    rgba(102, 137, 113, 0.05) 100%
  );
  color: #668971;
  border-color: rgba(102, 137, 113, 0.2);
}

.nav-item i {
  font-size: 1.125rem;
  width: 20px;
  text-align: center;
}

.sidebar-footer {
  padding: var(--space-md);
  border-top: 1px solid rgba(255, 255, 255, 0.06);
  display: flex;
  align-items: center;
  gap: 12px;
}

[data-theme="light"] .sidebar-footer {
  border-top-color: #e5e7eb;
}

.sidebar-avatar {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: linear-gradient(135deg, var(--ro-green) 0%, #557d60 100%);
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 1rem;
  flex-shrink: 0;
  box-shadow: 0 2px 8px rgba(102, 137, 113, 0.2);
}

.sidebar-user {
  flex: 1;
  min-width: 0;
}

.sidebar-user strong {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

[data-theme="light"] .sidebar-user strong {
  color: #111827;
}

.sidebar-user small {
  display: block;
  font-family: var(--font-ui);
  font-size: 0.75rem;
  color: var(--text-muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ============================================
   HEADER
   ============================================ */

.header {
  position: sticky;
  top: 0;
  z-index: 50;
  background: rgba(10, 12, 11, 0.85);
  backdrop-filter: blur(16px) saturate(180%);
  -webkit-backdrop-filter: blur(16px) saturate(180%);
  border-bottom: 1px solid rgba(255, 255, 255, 0.06);
  padding: var(--space-sm) var(--space-md);
  display: flex;
  align-items: center;
  gap: var(--space-md);
}

[data-theme="light"] .header {
  background: rgba(255, 255, 255, 0.9);
  backdrop-filter: blur(16px);
  border-bottom-color: #e5e7eb;
}

.menu-btn {
  display: none;
  width: 44px;
  height: 44px;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: none;
  color: var(--text);
  font-size: 1.25rem;
  cursor: pointer;
  border-radius: var(--radius-sm);
  transition: all 0.2s ease;
}

.menu-btn:hover {
  background: rgba(255, 255, 255, 0.06);
}

.header-brand {
  display: flex;
  align-items: center;
  gap: 12px;
}

.header-logo {
  width: 32px;
  height: 32px;
  filter: drop-shadow(0 2px 4px rgba(102, 137, 113, 0.2));
}

.header-title {
  font-family: var(--font-display);
  font-size: 1.5rem;
  font-weight: 400;
  color: var(--text);
  letter-spacing: -0.01em;
  margin: 0;
}

[data-theme="light"] .header-title {
  color: #111827;
}

.header-actions {
  margin-left: auto;
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.icon-btn {
  width: 44px;
  height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  color: var(--text-secondary);
  font-size: 1.125rem;
  cursor: pointer;
  border-radius: var(--radius-md);
  transition: all 0.2s ease;
}

.icon-btn:hover {
  background: rgba(255, 255, 255, 0.06);
  color: var(--text);
  border-color: rgba(255, 255, 255, 0.08);
}

.icon-btn.primary {
  background: linear-gradient(135deg, var(--ro-gold) 0%, #d89a3f 100%);
  color: #0a0c0b;
  border: none;
  box-shadow: 0 2px 12px rgba(228, 170, 79, 0.25);
}

.icon-btn.primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 20px rgba(228, 170, 79, 0.35);
}

.icon-btn.primary.spinning i {
  animation: spin 0.6s linear infinite;
}

/* ============================================
   BUTTONS
   ============================================ */

.btn {
  padding: 10px 24px;
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  font-weight: 600;
  border: none;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: all 0.2s ease;
  display: inline-flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
}

.btn-primary {
  background: linear-gradient(135deg, var(--ro-green) 0%, #557d60 100%);
  color: white;
  box-shadow: 0 4px 12px rgba(102, 137, 113, 0.2);
}

.btn-primary:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px rgba(102, 137, 113, 0.3);
}

.btn-danger {
  background: linear-gradient(135deg, #c45c4a 0%, #b04a3d 100%);
  color: white;
  box-shadow: 0 4px 12px rgba(196, 92, 74, 0.2);
}

.btn-danger:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px rgba(196, 92, 74, 0.3);
}

.btn-gold {
  background: linear-gradient(135deg, var(--ro-gold) 0%, #d89a3f 100%);
  color: #0a0c0b;
  box-shadow: 0 4px 12px rgba(228, 170, 79, 0.2);
}

.btn-gold:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 20px rgba(228, 170, 79, 0.3);
}

.btn-secondary {
  background: rgba(255, 255, 255, 0.04);
  color: var(--text);
  border: 1px solid rgba(255, 255, 255, 0.08);
}

.btn-secondary:hover {
  background: rgba(255, 255, 255, 0.08);
  border-color: rgba(255, 255, 255, 0.12);
}

[data-theme="light"] .btn-secondary {
  background: #f9fafb;
  border-color: #e5e7eb;
  color: #374151;
}

.btn:active {
  transform: translateY(0);
}

.btn:disabled {
  opacity: 0.5;
  cursor: not-allowed;
  transform: none !important;
}

/* ============================================
   LOADING & EMPTY
   ============================================ */

.loading {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-xl);
  color: var(--text-muted);
}

.loading i {
  font-size: 2rem;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  from { transform: rotate(0deg); }
  to { transform: rotate(360deg); }
}

.empty-state {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: var(--space-xl);
  text-align: center;
  color: var(--text-muted);
}

.empty-state i {
  font-size: 3.5rem;
  margin-bottom: var(--space-md);
  opacity: 0.2;
}

.empty-state p {
  font-family: var(--font-ui);
  font-size: 1rem;
  margin: 0;
  color: var(--text-secondary);
}

/* ============================================
   MODAL
   ============================================ */

.modal-backdrop {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  backdrop-filter: blur(3px);
  -webkit-backdrop-filter: blur(3px);
  display: flex;
  align-items: flex-start;
  justify-content: center;
  padding: var(--space-lg);
  overflow-y: auto;
  z-index: 500;
}
.modal-backdrop[hidden] { display: none; }

.modal {
  width: min(780px, 100%);
  margin: auto;
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-elevated);
  display: flex;
  flex-direction: column;
}

.modal-head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
  padding: var(--space-md) var(--space-lg);
  border-bottom: 1px solid var(--border-light);
}
.modal-head h2 {
  font-family: var(--font-display);
  font-size: 1.25rem;
  font-weight: 400;
  margin: 0;
  color: var(--text);
}
.modal-head .spacer { margin-left: auto; }

.modal-body {
  padding: var(--space-lg);
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.modal-foot {
  display: flex;
  gap: var(--space-sm);
  align-items: center;
  padding: var(--space-md) var(--space-lg);
  border-top: 1px solid var(--border-light);
}
.modal-foot .spacer { margin-left: auto; }

/* ---- forms ---- */

.field { display: flex; flex-direction: column; gap: 6px; }
.field-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
  gap: var(--space-md);
}

/* Sits under an input, quieter than the label but still readable in both
   themes — tokens only, so it follows the theme rather than pinning a colour. */
.field-hint {
  font-family: var(--font-ui);
  font-size: 0.75rem;
  line-height: 1.4;
  color: var(--text-muted);
}

.field label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.field input,
.field select,
.field textarea {
  font-family: var(--font-ui);
  font-size: 0.9375rem;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 11px 12px;
  min-height: 44px;
  width: 100%;
}
.field input:focus,
.field select:focus,
.field textarea:focus {
  outline: none;
  border-color: var(--ro-green);
  box-shadow: 0 0 0 3px rgba(102, 137, 113, 0.18);
}
.field textarea { min-height: 72px; resize: vertical; }

/* ---- line items ---- */

.lines-head {
  display: flex;
  align-items: center;
  gap: var(--space-sm);
}
.lines-head h3 {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--text-muted);
  margin: 0;
  margin-right: auto;
}

.line-rows { display: flex; flex-direction: column; gap: var(--space-sm); }

.line-labels,
.line-row {
  display: grid;
  grid-template-columns: 76px minmax(130px, 2.1fr) minmax(92px, 1fr) minmax(80px, 0.9fr) minmax(70px, 0.8fr) minmax(86px, 1fr) 44px;
  gap: 8px;
  align-items: center;
}

/* Trim order. A line's position in this list IS the order the floor runs the
   strains in — `sort_order` is written as the index on save and the scheduler
   sequences a block's passes by it. Until now that was an invisible consequence
   of the order lines happened to be typed in, with no way to change it short of
   deleting them all and retyping. */
.line-rank {
  display: flex;
  align-items: center;
  gap: 2px;
}

.line-num {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  min-width: 16px;
  text-align: center;
}

.line-move {
  min-width: 26px;
  min-height: 44px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-secondary);
  cursor: pointer;
  font-size: 0.875rem;
  line-height: 1;
}
.line-move:hover:not(:disabled) { color: var(--text); border-color: var(--glass-stroke); }
.line-move:disabled { opacity: 0.25; cursor: default; }
.line-move:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 1px; }



.line-labels {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
}

.line-row input,
.line-row select {
  font-family: var(--font-ui);
  font-size: 0.875rem;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 10px;
  min-height: 44px;
  width: 100%;
}
.line-row input[type="number"] {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
}
.line-row input:focus,
.line-row select:focus {
  outline: none;
  border-color: var(--ro-green);
  box-shadow: 0 0 0 3px rgba(102, 137, 113, 0.18);
}

.line-del {
  min-width: 44px;
  min-height: 44px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 1.125rem;
}
.line-del:hover { color: var(--danger); border-color: var(--danger); }

/* Canonical pounds shown beside a kg entry, so the operator can see what was
   stored rather than having to trust the conversion silently. */
.line-conv {
  grid-column: 1 / -1;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-muted);
  padding-left: 2px;
  margin-top: -4px;
}

.modal-total {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  padding-top: var(--space-sm);
  border-top: 1px solid var(--border-light);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}
.modal-total .lbs { font-size: 1.0625rem; font-weight: 600; color: var(--text); }
.modal-total .usd { margin-left: auto; font-size: 0.9375rem; color: var(--text-secondary); }

.form-error {
  color: var(--danger);
  font-size: 0.8125rem;
  min-height: 1.2em;
}

/* ============================================
   QUEUE
   ============================================ */

/* Kept from the retired board: the queue bar still uses both. */
.chip {
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--text-secondary);
  background: transparent;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 8px 14px;
  min-height: 44px;
  cursor: pointer;
  transition: all 0.2s ease;
}
.chip:hover { color: var(--text); border-color: var(--border-strong); }



/* The sentence explaining the number in the box, now beside it rather than in a
   separate row underneath. It also fills the dead span the bar used to carry
   between the crew control and the actions. */
.crew-basis {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-muted);
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* An override replaces measured data with a typed guess, so it must not look
   like the resting state. */
#q-crew.overridden {
  border-color: var(--warning);
  box-shadow: 0 0 0 2px rgba(228, 170, 79, 0.18);
  color: var(--warning);
  font-weight: 700;
}

.crew-input input {
  width: 88px;
  min-height: 44px;
  padding: 10px;
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
  background: var(--bg);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
}
.crew-input input:focus {
  outline: none;
  border-color: var(--ro-green);
  box-shadow: 0 0 0 3px rgba(102, 137, 113, 0.18);
}

.queue-view { padding: var(--space-md) var(--space-lg) var(--space-xl); }
.queue-view[hidden] { display: none; }

.queue-meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-sm);
  flex-wrap: wrap;
  padding-bottom: var(--space-sm);
  margin-bottom: var(--space-md);
  border-bottom: 1px solid var(--border-light);
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-muted);
}
.queue-meta .qm-tz {
  font-family: var(--font-mono);
  color: var(--text-muted);
  letter-spacing: 0.04em;
}
/* Used by the queue meta line AND by each block header. It was scoped to
   `.queue-meta`, so the block header's spacer did nothing and the id, customer,
   percentage and date all bunched against the left edge with the rest of the
   card empty. */
.qm-spacer { margin-left: auto; }
.queue-meta .qm-clear { color: var(--text-secondary); }

.block-list { display: flex; flex-direction: column; gap: 10px; }

/* A block is an order. The card is the drag surface; the passes inside it are
   not draggable, because line order comes from how the order was typed. */
.block-card {
  background: var(--bg-card);
  border: 1px solid var(--border);
  border-radius: var(--radius-md);
  cursor: grab;
}
.block-card.estimated { border-color: var(--warning); }
.block-card.dragging { opacity: 0.4; cursor: grabbing; }
.block-card.drop-target { border-color: var(--ro-green); box-shadow: 0 0 0 2px rgba(102, 137, 113, 0.25); }

.block-head {
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 9px 14px 9px 0;
  border-bottom: 1px solid var(--border-light);
  flex-wrap: wrap;
}

/* The order's number, as a field. It reads as text until touched — a card of
   orders should not look like a form — but it is a real input, so it is
   reachable by tab and by tap without a hidden click-to-edit mode. */
.block-id {
  font-family: var(--font-mono);
  font-size: 0.8125rem;
  font-weight: 700;
  color: var(--text);
  background: none;
  border: none;
  border-radius: 7px;
  padding: 2px 6px;
  min-width: 8ch;
  /* No dashed underline. It was there to say "this is clickable" back when the
     id was the ONLY way into an order; the card now carries an explicit edit
     control, and a link-styled identifier just reads as decoration. */
  padding: 0;
  cursor: pointer;
}
.block-id:hover { background: rgba(255, 255, 255, 0.08); }
[data-theme="light"] .block-id:hover { background: rgba(45, 58, 50, 0.08); }
.block-id:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.12);
  box-shadow: 0 0 0 2px var(--ro-green);
}
[data-theme="light"] .block-id:focus { background: #fff; }
/* The generated MO key, shown only while no real number has been typed. */
.block-id::placeholder { color: var(--text-muted); font-weight: 400; }
.block-id:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 2px; }

/* Open-in-Shopify, beside the reference. Quiet until hovered: it sits in a
   header the operator reads dozens of times a day and clicks rarely, so it
   should not compete with the number itself. */
.block-shopify {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin-left: -2px;
  border-radius: 6px;
  color: var(--text-muted);
  text-decoration: none;
  opacity: 0.55;
  transition: opacity 0.15s ease, background 0.15s ease, color 0.15s ease;
}
.block-shopify i { font-size: 0.9375rem; }
.block-shopify:hover,
.block-shopify:focus-visible {
  opacity: 1;
  color: var(--ro-green);
  background: rgba(255, 255, 255, 0.08);
}
[data-theme="light"] .block-shopify:hover,
[data-theme="light"] .block-shopify:focus-visible { background: rgba(45, 58, 50, 0.08); }
.block-shopify:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 1px; }

/* The nickname, as a field. Blank is the normal state — most orders will only
   ever have a number — so it shows a prompt rather than collapsing and leaving
   a hole in the header. */
.block-customer {
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  color: var(--text-secondary);
  background: none;
  border: 0;
  border-radius: 7px;
  padding: 2px 6px;
  min-width: 9ch;
  max-width: 22ch;
}
.block-customer:hover { background: rgba(255, 255, 255, 0.08); }
[data-theme="light"] .block-customer:hover { background: rgba(45, 58, 50, 0.08); }
.block-customer:focus {
  outline: none;
  color: var(--text);
  background: rgba(255, 255, 255, 0.12);
  box-shadow: 0 0 0 2px var(--ro-green);
}
[data-theme="light"] .block-customer:focus { background: #fff; }
.block-customer::placeholder { color: var(--text-muted); font-style: italic; }

/* The order's pounds, stated the same way every strain row states its own. */
.block-lbs {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-variant-numeric: tabular-nums;
  color: var(--text);
  white-space: nowrap;
}

.block-pct {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  color: var(--ro-green);
}

.block-eta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text);
  font-variant-numeric: tabular-nums;
}

/* Card controls. Quiet until the card is under the pointer — a queue of orders
   should read as dates and progress, not as a column of buttons — but always
   present for a keyboard, and always present on touch, where there is no hover
   to reveal them. */
.block-action {
  min-width: 30px;
  min-height: 30px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.875rem;
  opacity: 0;
  transition: opacity 0.15s ease, color 0.15s ease, border-color 0.15s ease;
}
.block-card:hover .block-action,
.block-action:focus-visible { opacity: 1; }

.block-action:hover { color: var(--text); border-color: var(--glass-stroke); }
.block-del:hover { color: var(--danger); border-color: var(--danger); }
.block-action:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 1px; }

/* No hover on a touch screen, so the controls simply stay visible. */
@media (hover: none) {
  .block-action { opacity: 1; }
}

.block-card > .run-track { margin: 10px 14px 0 14px; height: 22px; }

.block-passes {
  display: flex;
  flex-direction: column;
  gap: 1px;
  padding: 10px 14px 12px;
}

/* ---- one trim pass ---- */

.pass-row {
  padding: 8px 10px;
  background: var(--bg);
  border-radius: var(--radius-sm);
  display: flex;
  flex-direction: column;
  gap: 5px;
  min-width: 0;
}
.pass-row.estimated { box-shadow: inset 2px 0 0 var(--warning); }

/* ---- a pass line: burn-down and lead time, and nothing else ----

   The row used to carry the lot size, the byproduct, the trim rate and the
   coverage warning as well. All of that moved into `.pass-detail` below, on the
   operator's instruction: per strain the row shows pounds done against pounds
   ordered with a percentage, and when it lands. The rest is on demand.

   Grid rather than flex so the figures line up column-wise down the card — the
   whole point of a queue is comparing one row against the next. */

.pass-line {
  display: grid;
  /* cultivar · form · bar · quantity · percent · lead time · warning · tools */
  grid-template-columns: minmax(0, 1fr) 46px minmax(60px, 1.4fr) auto 46px 104px 12px 84px;
  gap: 10px;
  align-items: center;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--text-secondary);
  font-variant-numeric: tabular-nums;
}

.pl-cv {
  font-family: var(--font-ui);
  font-weight: 600;
  font-size: 0.875rem;
  color: var(--text);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.pl-form {
  text-transform: uppercase;
  letter-spacing: 0.06em;
  font-size: 0.5625rem;
  font-weight: 700;
  color: var(--text-muted);
}

.sl-track {
  display: flex;
  height: 7px;
  background: var(--border-light);
  border-radius: 4px;
  overflow: hidden;
}
.sl-fill { flex: 0 0 auto; height: 100%; background: var(--ro-green); border-radius: 4px; }

/* Pounds credited from stock, never trimmed. Hatched rather than solid, and in
   a neutral tone rather than the production green, so a full bar is not read as
   a full shift's output. */
.sl-credit {
  flex: 0 0 auto;
  height: 100%;
  border-radius: 4px;
  background: repeating-linear-gradient(
    135deg,
    var(--text-muted) 0 3px,
    transparent 3px 6px
  );
  opacity: 0.65;
}

.pl-num { color: var(--text); text-align: right; white-space: nowrap; }
.pl-pct { text-align: right; }

/* The lead time. The one figure here somebody might repeat to a buyer, so it
   carries the weight of the primary text colour. */
.pl-eta { color: var(--text); font-weight: 600; text-align: right; white-space: nowrap; }

/* A warning reachable only by hovering is a warning nobody sees on a phone, so
   the marker stays on the row even though its wording lives in the detail. */
.pl-warn { color: var(--warning); font-size: 0.625rem; text-align: center; }

/* ---- the detail ----
   Hover for a mouse, focus for a keyboard, tap for a phone. Deliberately not
   the `hidden` attribute: `[hidden] { display: none !important }` would outrank
   the :hover rule and the thing would never open. */

.pass-row { position: relative; }

/* Same problem one level down: the detail belongs to its row, and rows below it
   would otherwise paint on top of it. */
.pass-row:hover,
.pass-row:focus-within,
.pass-row.open { z-index: 30; }

.pass-detail {
  position: absolute;
  left: 10px;
  right: auto;
  top: calc(100% - 6px);
  z-index: 30;
  min-width: 236px;
  padding: 10px 12px;
  border-radius: var(--radius-md);
  display: grid;
  gap: 4px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-4px);
  transition: opacity 0.16s ease, transform 0.16s ease, visibility 0.16s;
  pointer-events: none;
}

/* Hover-open only while nothing is pinned, so a pinned detail and a hovered one
   are never on screen at the same time. */
body:not(.detail-pinned) .pass-row:hover .pass-detail,
.pass-row:focus-visible .pass-detail,
.pass-row.open .pass-detail {
  opacity: 1;
  visibility: visible;
  transform: translateY(0);
}

.pd-row {
  display: flex;
  gap: 14px;
  align-items: baseline;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
}
.pd-k { color: var(--text-muted); white-space: nowrap; }
.pd-v { margin-left: auto; color: var(--text); text-align: right; }

/* Raw needed exceeds raw on hand — the order cannot be finished from the barn
   as it stands. Coloured rather than merely marked, because this is the one row
   in the detail that is a problem rather than a fact. */
.pd-row.pd-short .pd-v { color: var(--warning); font-weight: 600; }

.pass-row:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 2px; }

.run-handle {
  display: grid;
  place-items: center;
  background: var(--bg);
  border-right: 1px solid var(--border-light);
  color: var(--text-muted);
  font-size: 1.125rem;
  letter-spacing: -2px;
}

.run-rank {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 700;
  color: var(--bg);
  background: var(--text-secondary);
  border-radius: var(--radius-sm);
  padding: 2px 7px;
  font-variant-numeric: tabular-nums;
}

.run-flag {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--warning);
  background: rgba(228, 170, 79, 0.16);
  border-radius: var(--radius-sm);
  padding: 2px 6px;
}

.run-sized {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-muted);
  overflow-wrap: anywhere;
}
.run-sized em { font-style: normal; font-weight: 600; color: var(--text-secondary); }

.run-track {
  position: relative;
  height: 20px;
  background: var(--bg);
  border-radius: var(--radius-sm);
  overflow: hidden;
}

.run-bar {
  position: absolute;
  top: 0;
  bottom: 0;
  background: var(--ro-green);
  border-radius: var(--radius-sm);
  display: flex;
  align-items: center;
  padding: 0 6px;
}
.run-bar.est { background: var(--warning); }
/* The bar is the block's span; the fill inside it is what has actually been
   trimmed. Two different claims, so they must not look like one. */
.run-bar-fill {
  position: absolute;
  left: 0; top: 0; bottom: 0;
  background: rgba(10, 12, 11, 0.22);
  border-radius: var(--radius-sm);
}
.run-bar span {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  color: #0a0c0b;
  white-space: nowrap;
}

/* ---- the promise date on an order card ---- */

.oc-eta {
  border-top: 1px solid var(--border-light);
  padding: 9px 17px 11px;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.oc-eta-date { font-size: 0.8125rem; color: var(--text-secondary); }
.oc-eta-date b {
  font-family: var(--font-mono);
  font-size: 0.875rem;
  font-weight: 600;
  color: var(--text);
}
.oc-soft { color: var(--warning); font-size: 0.6875rem; }
.oc-eta-why { font-size: 0.6875rem; color: var(--text-muted); }

/* Amber, not red: being short on packed goods mid-season is the normal state,
   not a failure. Red here would train the operator to ignore it. */
.oc-short {
  display: flex;
  align-items: flex-start;
  gap: 7px;
  margin: 0 17px 12px;
  padding: 7px 9px;
  border-radius: var(--radius-sm);
  background: rgba(228, 170, 79, 0.12);
  color: var(--warning);
  font-size: 0.6875rem;
  line-height: 1.35;
}
.oc-short svg { flex: none; margin-top: 2px; }

@media (max-width: 640px) {
  /* The boss reads this on a phone. The block card is already a vertical stack,
     so only the two grid rows need loosening: the pounds column is the first
     thing to drop, because the bar and the percentage already say it. */
  /* Two rows per line on a phone: the name and the lead time on top, the bar
     and the figures under it. Squeezing seven columns into 414px would drop the
     lead time, which is half of what the row exists to say. */
  .pass-line {
    grid-template-columns: 1fr auto auto;
    grid-template-areas:
      "cv  cv  eta"
      "bar num tools";
    row-gap: 6px;
    column-gap: 8px;
  }
  .pl-cv { grid-area: cv; }
  .pl-eta { grid-area: eta; }
  .sl-track { grid-area: bar; align-self: center; }
  .pl-num { grid-area: num; }
  .pl-tools { grid-area: tools; }
  .pl-form, .pl-pct, .pl-warn, .pl-tool-gap { display: none; }
  .pass-detail { right: 4px; left: 4px; min-width: 0; }
  .block-head { row-gap: 2px; padding-right: 8px; gap: 7px; }
  /* The auto-margin spacer pushes everything after it hard right, which on a
     narrow card forces the last control onto a line of its own. Without it the
     header simply wraps where it runs out of room. */
  .block-head .qm-spacer { display: none; }
  /* The customer takes the second line, after the controls rather than between
     them. */
  .block-customer { flex-basis: 100%; order: 9; }
  .block-id { font-size: 0.75rem; }
  .block-passes { padding: 8px 9px 10px; }
  .pass-lot { margin-left: 0; }
}

@media (max-width: 768px) {
    /* On a phone the sentence would push the actions onto a third row; the box
     and its highlight already say whether an override is in force. */
  .crew-basis { display: none; }
  .queue-view { padding: var(--space-md); }
}

/* ============================================
   TOUCH TARGETS
   ============================================ */

/* The shared .btn styles this page inherits land at 40-42px, under the 44px
   minimum the project requires (CLAUDE.md, Best Practices). Enforced here
   rather than in the shared shell, which other pages depend on unchanged.
   This board is operated on a barn tablet, so it is not a theoretical concern. */
.btn { min-height: 44px; }

/* ============================================
   RESPONSIVE
   ============================================ */

@media (max-width: 1024px) {
  .line-labels { display: none; }
  .line-row {
    grid-template-columns: 1fr 1fr;
    padding: 10px;
    border: 1px solid var(--border-light);
    border-radius: var(--radius-md);
  }
  .line-del { justify-self: end; }
}

@media (max-width: 768px) {
  .sidebar { transform: translateX(-260px); }
  .sidebar.open { transform: translateX(0); }
  .main { margin-left: 0; min-width: 0; }

  /* Four icon buttons plus the brand overflow a 390px viewport. Let the title
     give way rather than dropping a control the operator needs — the page name
     is already in the tab and the nav. */
  .menu-btn { display: flex; }
  .header { gap: var(--space-sm); }
  .header-brand { min-width: 0; flex: 1; }
  .header-logo { display: none; }
  .header-title {
    font-size: 1.125rem;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .header-actions { flex-shrink: 0; }
  .board { grid-template-columns: 1fr; padding: var(--space-md); }
  .board-bar { padding: var(--space-md) var(--space-md) 0; }
  .modal-backdrop { padding: 0; }
  .modal { border-radius: 0; min-height: 100vh; }
}

@media (prefers-reduced-motion: reduce) {
  .block-card, .chip, .nav-item, .done-row { transition: none; }
}

/* ============================================
   RUN COVERAGE
   ============================================ */

/* Amber, not red. Mid-season the bulk of stock is raw material waiting to be
   trimmed, so committing past what is packed is the ordinary state — red here
   would teach the operator to ignore it. The raw sack count travels alongside
   so "nothing packed yet" reads differently from "nothing to pack". */
.run-short {
  display: flex;
  align-items: baseline;
  gap: 6px;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--warning);
}
.run-short .rs-icon { font-size: 0.5625rem; }

/* An order reference on a run — the only route to an order still in the queue. */
/* ============================================
   COMPLETED ORDERS
   ============================================ */

.done-section { padding: 0 var(--space-lg) var(--space-xl); }

.done-head {
  display: flex;
  align-items: center;
  gap: 8px;
  width: 100%;
  padding: 12px 0;
  min-height: 44px;
  background: transparent;
  border: none;
  border-top: 1px solid var(--border-light);
  color: var(--text-secondary);
  font-family: var(--font-ui);
  font-size: 0.875rem;
  font-weight: 600;
  cursor: pointer;
  text-align: left;
}
.done-head:hover { color: var(--text); }
.done-caret { font-size: 0.75rem; color: var(--text-muted); }
.done-count {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-muted);
  background: var(--bg);
  border-radius: var(--radius-sm);
  padding: 2px 7px;
}

.done-list { display: flex; flex-direction: column; gap: 2px; }
.done-list[hidden] { display: none; }

.done-row {
  display: grid;
  grid-template-columns: 110px 84px minmax(120px, 1fr) minmax(140px, 2fr) 90px 90px;
  gap: 12px;
  align-items: center;
  width: 100%;
  padding: 10px 12px;
  min-height: 44px;
  background: transparent;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  cursor: pointer;
  text-align: left;
  font-family: var(--font-ui);
  font-size: 0.8125rem;
  color: var(--text-secondary);
  transition: background 0.15s ease;
}
.done-row:hover { background: var(--bg-card); border-color: var(--border-light); }

.done-row .dr-id { font-family: var(--font-mono); font-weight: 600; color: var(--text); }
.done-row .dr-status {
  font-family: var(--font-mono);
  font-size: 0.5625rem;
  font-weight: 700;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--text-muted);
}
.done-row .dr-status.s-finished { color: var(--info); }
.done-row .dr-cultivars {
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.done-row .dr-lbs,
.done-row .dr-usd {
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
  text-align: right;
  color: var(--text);
}
.done-row .dr-usd { color: var(--text-secondary); font-size: 0.75rem; }

.done-empty { margin: 0; padding: 10px 12px; font-size: 0.8125rem; color: var(--text-muted); }

@media (max-width: 900px) {
  .done-section { padding: 0 var(--space-md) var(--space-lg); }
  .done-row { grid-template-columns: 1fr 1fr; gap: 4px 12px; }
  .done-row .dr-cultivars { white-space: normal; }
}

/* ============================================
   LIQUID GLASS — "cure room"
   ============================================

   Appended deliberately rather than woven in: every rule here overrides a solid
   surface defined above, and keeping the whole treatment in one block means it
   can be read, judged, or deleted as a unit. Equal specificity, so source order
   decides — do not move this section up the file.

   The metaphor is the cure room, not a phone OS: cold green panes with the gold
   of dried flower as the only warm light behind them. The page's one indulgence
   is the progress bar, where the "liquid" is resin filling a glass tube and the
   meniscus marks how far the floor has actually got. That is the burn-down this
   board was built for, so the effect carries information rather than decorating.

   SCOPE: this page only. shared-base.css is untouched, so the other seven apps
   are exactly as they were.
   ============================================ */

:root {
  --glass-pane: rgba(255, 255, 255, 0.055);
  --glass-pane-strong: rgba(255, 255, 255, 0.085);
  --glass-stroke: rgba(255, 255, 255, 0.10);
  --glass-spec: rgba(255, 255, 255, 0.45);
  --glass-blur: 20px;
  --glass-sat: 1.5;
  --resin: 228, 170, 79;          /* --ro-gold, as channels for alpha mixing */
  --pane-shadow: 0 8px 32px rgba(0, 0, 0, 0.30), 0 1px 0 rgba(255, 255, 255, 0.05) inset;
}

[data-theme="light"] {
  /* Light needs far more body in the pane. At dark-theme alpha the type drops
     under 4.5:1 against the ambient wash, and this board gets read across a
     bright trim room. */
  --glass-pane: rgba(255, 255, 255, 0.55);
  --glass-pane-strong: rgba(255, 255, 255, 0.72);
  /* Neutral, not green. On a light page the stroke still has to be darker than
     the pane to exist at all, but it no longer carries a hue. */
  --glass-stroke: rgba(45, 58, 50, 0.20);
  --glass-spec: rgba(255, 255, 255, 0.95);
  --pane-shadow: 0 10px 34px rgba(45, 58, 50, 0.13), 0 1px 0 rgba(255, 255, 255, 0.85) inset;
}

/* ---- the light behind the glass ----
   Translucency over a flat colour is just a paler rectangle. This is the field
   the panes actually refract: two soft sources, green for the room and gold for
   the flower, fixed so they do not travel with the scroll. */

.app-container { position: relative; isolation: isolate; }

.app-container::before {
  content: '';
  position: fixed;
  inset: -20%;
  z-index: -1;
  pointer-events: none;
  background:
    radial-gradient(52% 46% at 18% 6%,  rgba(255, 255, 255, 0.055), transparent 70%),
    radial-gradient(46% 40% at 84% 4%,  rgba(255, 255, 255, 0.035), transparent 72%),
    radial-gradient(60% 52% at 70% 98%, rgba(255, 255, 255, 0.030), transparent 72%);
  filter: blur(26px);
  animation: cure-drift 48s ease-in-out infinite alternate;
}

[data-theme="light"] .app-container::before {
  background:
    radial-gradient(52% 46% at 18% 6%,  rgba(45, 58, 50, 0.055), transparent 70%),
    radial-gradient(46% 40% at 84% 4%,  rgba(45, 58, 50, 0.035), transparent 72%),
    radial-gradient(60% 52% at 70% 98%, rgba(45, 58, 50, 0.030), transparent 72%);
}

/* One slow breath, not a lava lamp. 48s is below the threshold where the eye
   tracks it as motion — it reads as the light changing, which is what a room does. */
@keyframes cure-drift {
  from { transform: translate3d(0, 0, 0) scale(1); }
  to   { transform: translate3d(-3%, 2%, 0) scale(1.08); }
}

.main { position: relative; z-index: 1; }

/* ---- panes ----
   One treatment, applied by hand because there is no build step to give us a mixin. */

.sidebar,
.header,
.block-card,
.done-head,
.modal {
  background: var(--glass-pane);
  -webkit-backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  backdrop-filter: blur(var(--glass-blur)) saturate(var(--glass-sat));
  border: 1px solid var(--glass-stroke);
}

/* A real pane catches light on its top edge only. A uniform 1px border reads as
   a drawn rectangle; this reads as an edge. */
.block-card,
.modal,
.done-head {
  position: relative;
  box-shadow: var(--pane-shadow);
}

.block-card::before,
.modal::before {
  content: '';
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  background: linear-gradient(90deg, transparent, var(--glass-spec) 22%, var(--glass-spec) 60%, transparent);
  pointer-events: none;
  z-index: 2;
}

.sidebar {
  background: var(--glass-pane-strong);
  border-right: 1px solid var(--glass-stroke);
}
[data-theme="light"] .sidebar {
  background: var(--glass-pane-strong);
  border-right: 1px solid var(--glass-stroke);
}

.header {
  border-left: 0;
  border-right: 0;
  border-top: 0;
}


/* ---- the block card ---- */

.block-card {
  border-radius: var(--radius-lg);
  transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
              box-shadow 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}

/* The card used to clip its own contents. It cannot any more: a pass detail
   opens past the bottom edge, and `overflow: hidden` would cut it in half.
   Nothing inside actually needed clipping — the specular edge fades to
   transparent before it reaches either corner, and the timeline track carries
   its own overflow rule. */
.block-card { overflow: visible; }

/* Cards are siblings in a column, so a later one paints over an earlier one's
   detail. Lift whichever card is being read. */
.block-card:hover,
.block-card:focus-within,
.block-card:has(.pass-row.open) { z-index: 20; }

.block-card:hover {
  transform: translateY(-2px);
  box-shadow: 0 14px 40px rgba(0, 0, 0, 0.34), 0 1px 0 rgba(255, 255, 255, 0.07) inset;
}
[data-theme="light"] .block-card:hover {
  box-shadow: 0 14px 40px rgba(45, 58, 50, 0.14), 0 1px 0 rgba(255, 255, 255, 0.9) inset;
}

.block-card.drop-target {
  border-color: rgba(255, 255, 255, 0.55);
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.30), var(--pane-shadow);
}
[data-theme="light"] .block-card.drop-target {
  border-color: rgba(45, 58, 50, 0.55);
  box-shadow: 0 0 0 2px rgba(45, 58, 50, 0.22), var(--pane-shadow);
}

.block-head,
.modal-head { border-bottom: 1px solid var(--glass-stroke); }
.modal-foot { border-top: 1px solid var(--glass-stroke); }

/* Passes are panes within a pane — lighter, and with no blur of their own.
   Stacking a second backdrop-filter inside the first costs an extra composite
   layer per row and buys nothing visible: the card behind them is already
   frosted, so there is no fresh backdrop left to sample. */
.pass-row {
  background: rgba(255, 255, 255, 0.035);
  border: 1px solid rgba(255, 255, 255, 0.05);
}
[data-theme="light"] .pass-row {
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.8);
}

/* ---- THE SIGNATURE: resin in a glass tube ----
   The bar is the block's span; the fill is what has actually been trimmed. The
   glass here is doing the one job that matters on this page. */

.run-track {
  background: rgba(0, 0, 0, 0.22);
  border: 1px solid var(--glass-stroke);
  box-shadow: inset 0 1px 3px rgba(0, 0, 0, 0.30);
}
[data-theme="light"] .run-track {
  background: rgba(45, 58, 50, 0.07);
  box-shadow: inset 0 1px 3px rgba(45, 58, 50, 0.10);
}

.run-bar {
  background: linear-gradient(180deg, rgba(255, 255, 255, 0.24), rgba(255, 255, 255, 0.13));
  border: 1px solid rgba(255, 255, 255, 0.16);
  overflow: hidden;
}
[data-theme="light"] .run-bar {
  background: linear-gradient(180deg, rgba(45, 58, 50, 0.24), rgba(45, 58, 50, 0.15));
}
/* A span resting on a guessed rate still reads differently, but as a dashed
   edge rather than a second colour. */
.run-bar.est { border-style: dashed; border-color: rgba(255, 255, 255, 0.34); }

/* The resin. Denser at the bottom, because that is how a liquid sits.
   The trailing edge is square: rounding it turns a 1% fill into a pill floating
   in the tube, which reads as a rendering fault rather than as "barely started".
   min-width keeps a real-but-tiny amount visible instead of rounding to nothing. */
.run-bar-fill {
  background: linear-gradient(180deg,
    rgba(var(--resin), 0.32), rgba(var(--resin), 0.78));
  box-shadow: inset 0 -6px 14px rgba(var(--resin), 0.42);
  border-radius: var(--radius-sm) 0 0 var(--radius-sm);
  min-width: 3px;
}

/* The meniscus — the leading edge, and the only hard specular on the page. It
   sits exactly at the percentage trimmed, so the brightest thing on the card is
   also the most load-bearing number on it. */
.run-bar-fill::after {
  content: '';
  position: absolute;
  top: 0;
  bottom: 0;
  right: -1px;
  width: 2px;
  background: linear-gradient(180deg, transparent, var(--glass-spec), transparent);
  box-shadow: 0 0 10px 2px rgba(var(--resin), 0.55);
}

/* Zero progress must not glow. A meniscus at 0% would read as "started". */
.run-bar-fill[style*="width: 0%"]::after { display: none; }

/* ---- the line burn-down ---- */

.sl-track {
  background: rgba(0, 0, 0, 0.20);
  box-shadow: inset 0 1px 2px rgba(0, 0, 0, 0.28);
}
[data-theme="light"] .sl-track {
  background: rgba(45, 58, 50, 0.09);
  box-shadow: inset 0 1px 2px rgba(45, 58, 50, 0.10);
}

.sl-fill {
  background: linear-gradient(90deg, rgba(102, 137, 113, 0.75), var(--ro-green));
  box-shadow: 0 0 8px rgba(102, 137, 113, 0.45);
}
.pass-line.filled .sl-fill {
  background: linear-gradient(90deg, rgba(var(--resin), 0.7), var(--ro-gold));
  box-shadow: 0 0 10px rgba(var(--resin), 0.5);
}
.pass-line.filled .pl-pct { color: var(--ro-gold); font-weight: 700; }
[data-theme="light"] .pass-line.filled .pl-pct { color: var(--ro-gold-text); }

/* The detail is frosted like the rest of the page, but it is NOT a pane.
   A pane sits over an ambient wash; this sits over the row it is describing, and
   at pane alpha that row's own figures read straight through it — "1,681 lb"
   landing on top of "0 / 120 lb". Anything that overlaps live text has to be a
   surface, so it is close to opaque and only lightly frosted. */
.pass-detail {
  background: rgba(16, 20, 18, 0.97);
  -webkit-backdrop-filter: blur(14px) saturate(1.3);
  backdrop-filter: blur(14px) saturate(1.3);
  border: 1px solid var(--glass-stroke);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.55);
}
[data-theme="light"] .pass-detail {
  background: rgba(255, 255, 255, 0.98);
  box-shadow: 0 12px 32px rgba(45, 58, 50, 0.22);
}

/* ---- controls ---- */

.icon-btn,
.btn-secondary,
.chip {
  background: var(--glass-pane-strong);
  -webkit-backdrop-filter: blur(12px) saturate(1.4);
  backdrop-filter: blur(12px) saturate(1.4);
  border: 1px solid var(--glass-stroke);
}

.btn-primary {
  background: linear-gradient(180deg, var(--green-light), var(--ro-green));
  border: 1px solid rgba(255, 255, 255, 0.20);
  box-shadow: 0 2px 10px rgba(102, 137, 113, 0.35), 0 1px 0 rgba(255, 255, 255, 0.25) inset;
}

.modal-backdrop {
  background: rgba(8, 12, 10, 0.55);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
}
.modal { border-radius: var(--radius-lg); }

.done-head { border-radius: var(--radius-md); }
.done-row {
  background: rgba(255, 255, 255, 0.03);
  border: 1px solid rgba(255, 255, 255, 0.05);
}
[data-theme="light"] .done-row {
  background: rgba(255, 255, 255, 0.55);
  border: 1px solid rgba(255, 255, 255, 0.75);
}

/* ---- controls on glass ----
   Every input on this page was `background: var(--bg)`, which is #141816 in
   dark. Against a frosted pane that is not a field, it is a hole — the crew box
   and every control in the order editor read as gaps punched through the glass.
   They get a pane of their own instead: lighter than what is behind them, which
   is how a recessed surface actually behaves under a light source. */

.field input,
.field select,
.field textarea,
.line-row input,
.line-row select,
#q-crew {
  background: rgba(255, 255, 255, 0.07);
  border: 1px solid var(--glass-stroke);
  color: var(--text);
}
[data-theme="light"] .field input,
[data-theme="light"] .field select,
[data-theme="light"] .field textarea,
[data-theme="light"] .line-row input,
[data-theme="light"] .line-row select,
[data-theme="light"] #q-crew {
  background: rgba(255, 255, 255, 0.85);
}

.field input:focus,
.field select:focus,
.field textarea:focus,
.line-row input:focus,
.line-row select:focus,
#q-crew:focus {
  background: rgba(255, 255, 255, 0.12);
  border-color: var(--ro-green);
  box-shadow: 0 0 0 3px rgba(102, 137, 113, 0.22);
}
[data-theme="light"] .field input:focus,
[data-theme="light"] .field select:focus,
[data-theme="light"] .field textarea:focus,
[data-theme="light"] .line-row input:focus,
[data-theme="light"] .line-row select:focus,
[data-theme="light"] #q-crew:focus {
  background: #fff;
}

#q-crew {
  border-radius: var(--radius-sm);
  font-family: var(--font-mono);
  font-variant-numeric: tabular-nums;
}

::placeholder { color: var(--text-muted); opacity: 1; }

/* Native widgets — the date picker, the select popup, the number spinners — are
   drawn by the browser, not by this stylesheet. Without this they render in the
   OS's default scheme and a light dropdown drops out of a dark board. */
:root { color-scheme: dark; }
[data-theme="light"] { color-scheme: light; }

/* ---- legibility on glass ----
   Measured, not assumed: each of these was screenshotted on the rendered pane
   and its contrast computed, before and after the glass went on.

   Two things came out of that. The frosted panes cost real contrast on the
   dark theme — the block's pound count fell from 5.03 to 4.16 and crossed under
   AA — so the type here is pulled up a step to pay it back. And several of these
   captions were ALREADY failing before this pass: `sized by …` measured 1.00
   against its own background, which is invisible, not merely low. Muted grey on
   a dark surface was the culprit both times, so the muted role is retired on
   this page in favour of the secondary one. */

.field label,
.lines-head,
.line-del {
  color: var(--text-secondary);
}

.block-pct { color: var(--green-light); }
[data-theme="light"] .block-pct { color: var(--green-dark); }

/* ---- THE PULSE ----

   A soft halo that breathes around the block the floor is ACTUALLY RUNNING —
   the order whose status the cron has moved to `in_production`. Everything
   queued behind it sits still.

   That is the whole reason it is here rather than on every card: a glance at
   this board should answer "what is on the line right now" before it answers
   anything else, and the pulse is the only thing on the page that moves.

   Animating a pseudo-element's shadow rather than the card's own keeps the
   card's resting shadow intact and gives the compositor one element to redraw.
*/

.block-card.running::after {
  content: '';
  position: absolute;
  inset: -1px;
  border-radius: inherit;
  pointer-events: none;
  z-index: -1;
  box-shadow:
    0 0 0 1px rgba(255, 255, 255, 0.10),
    0 0 26px 2px rgba(255, 255, 255, 0.05);
  animation: pulse 3.4s ease-in-out infinite;
}
[data-theme="light"] .block-card.running::after {
  box-shadow:
    0 0 0 1px rgba(45, 58, 50, 0.14),
    0 0 26px 2px rgba(45, 58, 50, 0.07);
}

@keyframes pulse {
  50% {
    box-shadow:
      0 0 0 1px rgba(255, 255, 255, 0.20),
      0 0 46px 6px rgba(255, 255, 255, 0.11);
  }
}

/* ---- the quality floor ---- */

@media (prefers-reduced-motion: reduce) {
  .app-container::before { animation: none; }
  .block-card, .block-card:hover { transition: none; transform: none; }
  /* The pulse stops but the ring stays, so "this one is running" survives as a
     static outline rather than disappearing for anyone who asked for less
     motion. */
  .block-card.running::after { animation: none; }
  .pass-detail { transition: none; }
}

@media (max-width: 640px) {
  /* A frosted pane per card is a separate composite layer per card, and this is
     read on a phone in a trim room. The cards keep their tint and their edge;
     they stop sampling what is behind them. The ambient field is still visible
     around them, so the page still reads as glass. */
  .block-card {
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
    background: var(--glass-pane-strong);
  }
  .app-container::before { filter: blur(40px); animation: none; }
}

/* Firefox without the flag, and anything else lacking backdrop-filter, gets
   solid surfaces rather than near-transparent ones — a fallback has to stay
   readable, not pretty. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .sidebar, .header, .block-card, .done-head, .modal {
    background: var(--bg-card);
  }
}

/* ============================================
   MOTION AND FORM
   ============================================

   The board read as flat panels with hairlines: everything appeared instantly,
   nothing acknowledged a press, and every surface was separated by a 1px line.
   That is what makes an interface feel drawn rather than built.

   Three changes, applied everywhere rather than case by case:

   1. ONE EASING CURVE. Every transition on this page uses the same decelerating
      curve, and the same two durations. Mixed curves are what make motion feel
      arbitrary — a single one makes unrelated elements feel like parts of the
      same object.
   2. HAIRLINES BECOME LAYERS. Depth comes from a tinted surface plus a soft
      shadow and a light-catching top edge, not from a drawn border. A border
      says "here is a rectangle"; a shadow says "here is something on top of
      something else".
   3. EVERY CONTROL ANSWERS A PRESS. A control that does not move under the
      finger feels like a picture of a control.

   Appended last on purpose, same as the glass section: equal specificity, so
   source order decides. Do not move this up the file.
   ============================================ */

:root {
  /* Decelerating, and slightly long. Interfaces feel cheap when they move
     linearly or finish too fast to be seen. */
  --ease: cubic-bezier(0.32, 0.72, 0, 1);
  /* A little overshoot, for things that ARRIVE rather than change. */
  --ease-in-out-back: cubic-bezier(0.34, 1.35, 0.64, 1);
  --t-fast: 0.18s;
  --t-base: 0.34s;

  --r-card: 22px;
  --r-row: 14px;
  --r-ctl: 11px;
}

@media (prefers-reduced-motion: reduce) {
  :root { --t-fast: 0.01s; --t-base: 0.01s; }
}

/* ---- surfaces ---- */

.block-card {
  border: 0;
  border-radius: var(--r-card);
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.06) inset,
    0 1px 2px rgba(0, 0, 0, 0.20),
    0 8px 28px rgba(0, 0, 0, 0.28);
  transition:
    transform var(--t-base) var(--ease),
    box-shadow var(--t-base) var(--ease);
}
[data-theme="light"] .block-card {
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.9) inset,
    0 1px 2px rgba(45, 58, 50, 0.07),
    0 8px 28px rgba(45, 58, 50, 0.10);
}

.block-card:hover {
  transform: translateY(-3px);
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.09) inset,
    0 2px 6px rgba(0, 0, 0, 0.24),
    0 18px 44px rgba(0, 0, 0, 0.34);
}
[data-theme="light"] .block-card:hover {
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.95) inset,
    0 2px 6px rgba(45, 58, 50, 0.09),
    0 18px 44px rgba(45, 58, 50, 0.14);
}

/* The drawn top edge is replaced by the inset highlight above. */
.block-card::before { display: none; }

.block-head { border-bottom: 0; }
.block-list { gap: 14px; }
.block-passes { gap: 6px; padding: 8px 12px 14px; }

.done-head,
.modal {
  border: 0;
  border-radius: var(--r-card);
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.06) inset,
    0 8px 28px rgba(0, 0, 0, 0.26);
}
[data-theme="light"] .done-head,
[data-theme="light"] .modal {
  box-shadow:
    0 1px 0 0 rgba(255, 255, 255, 0.9) inset,
    0 8px 28px rgba(45, 58, 50, 0.10);
}
.modal::before { display: none; }

/* ---- pass rows ---- */

.pass-row {
  border-radius: var(--r-row);
  padding: 9px 12px;
  transition: background var(--t-fast) var(--ease);
}
.pass-row:hover { background: rgba(255, 255, 255, 0.055); }
[data-theme="light"] .pass-row:hover { background: rgba(255, 255, 255, 0.75); }
.pass-row.estimated { box-shadow: inset 2px 0 0 var(--warning); }

.pass-line { font-size: 0.78125rem; letter-spacing: -0.005em; }

/* Bars grow into place, so recorded production reads as something that
   happened rather than something that was always there. */
.sl-track { height: 8px; border-radius: 99px; }
.sl-fill {
  border-radius: 99px;
  transition: width var(--t-base) var(--ease);
}

/* ---- inline editing ---- */

/* Reads as text until touched. A field that always looks like a field turns a
   row of numbers into a form. */
.pl-num { display: inline-flex; align-items: baseline; gap: 2px; }
.pl-done { color: var(--text-secondary); }
.pl-unit { color: var(--text-secondary); }

.pl-qty {
  width: auto;
  min-width: 3ch;
  padding: 2px 5px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: right;
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.pl-qty:hover { background: rgba(255, 255, 255, 0.07); }
[data-theme="light"] .pl-qty:hover { background: rgba(45, 58, 50, 0.07); }
.pl-qty:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.10);
  box-shadow: 0 0 0 2px var(--ro-green);
}
[data-theme="light"] .pl-qty:focus { background: #fff; }

/* Pounds done, editable. Deliberately identical to .pl-qty: the two numbers sit
   either side of a slash and would look broken if only one of them invited a
   click. */
.pl-done-edit {
  width: auto;
  min-width: 3ch;
  padding: 2px 5px;
  border: 0;
  border-radius: 7px;
  background: transparent;
  color: var(--text);
  font: inherit;
  font-variant-numeric: tabular-nums;
  text-align: right;
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease);
}
.pl-done-edit:hover { background: rgba(255, 255, 255, 0.07); }
[data-theme="light"] .pl-done-edit:hover { background: rgba(45, 58, 50, 0.07); }
.pl-done-edit:focus {
  outline: none;
  background: rgba(255, 255, 255, 0.10);
  box-shadow: 0 0 0 2px var(--ro-green);
}
[data-theme="light"] .pl-done-edit:focus { background: #fff; }
/* A dotted underline marks a figure a human set by hand rather than one the
   floor produced, so the two are never confused at a glance. */
.pl-done-edit.credited { text-decoration: underline dotted currentColor; text-underline-offset: 3px; }

.pl-tools {
  position: relative;
  z-index: 40;
  display: inline-flex;
  align-items: center;
  gap: 1px;
  opacity: 0;
  transform: translateX(4px);
  transition: opacity var(--t-fast) var(--ease), transform var(--t-base) var(--ease);
}
.pass-row:hover .pl-tools,
.pass-row:focus-within .pl-tools { opacity: 1; transform: none; }
@media (hover: none) { .pl-tools { opacity: 1; transform: none; } }

/* 26px was under the 44px minimum this project sets for touch targets, and
   these are the controls somebody reaches for on a barn tablet with gloves on.
   Bigger everywhere, and full size where there is no pointer to be precise
   with. */
.pl-tool {
  min-width: 32px;
  min-height: 32px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 0;
  border-radius: 8px;
  color: var(--text-muted);
  cursor: pointer;
  font-size: 0.8125rem;
  line-height: 1;
  transition: background var(--t-fast) var(--ease), color var(--t-fast) var(--ease),
              transform var(--t-fast) var(--ease);
}
.pl-tool:hover { background: rgba(255, 255, 255, 0.10); color: var(--text); }
[data-theme="light"] .pl-tool:hover { background: rgba(45, 58, 50, 0.09); color: var(--text); }
.pl-remove:hover { color: var(--danger); }
.pl-tool:active { transform: scale(0.88); }
.pl-tool:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 1px; }
.pl-tool-gap { min-width: 32px; }

/* ---- the detail arrives rather than appears ---- */

.pass-detail {
  border: 0;
  border-radius: 18px;
  transform: translateY(-8px) scale(0.96);
  transform-origin: top left;
  transition:
    opacity var(--t-fast) var(--ease),
    transform var(--t-base) var(--ease-in-out-back),
    visibility var(--t-base);
}

/* Hover waits; a tap or a keyboard focus does not. Reaching across a row for
   its arrows is a movement, not a request to read the detail, and 400ms is
   longer than that movement takes. */
.pass-row:hover .pass-detail { transition-delay: 0.4s; }
.pass-row.open .pass-detail,
.pass-row:focus-visible .pass-detail { transition-delay: 0s; }
body:not(.detail-pinned) .pass-row:hover .pass-detail,
.pass-row:focus-visible .pass-detail,
.pass-row.open .pass-detail { transform: none; }

/* ---- controls answer a press ---- */

.btn, .chip, .icon-btn, .block-action, .line-move, .done-row, .block-id {
  transition:
    background var(--t-fast) var(--ease),
    color var(--t-fast) var(--ease),
    box-shadow var(--t-fast) var(--ease),
    transform var(--t-fast) var(--ease);
}
.btn:active, .chip:active, .icon-btn:active,
.block-action:active, .line-move:active:not(:disabled) { transform: scale(0.96); }

.btn, .chip, .icon-btn { border-radius: var(--r-ctl); }

/* The bell, lit when the cron is pushing to Telegram. Gold rather than green:
   it is the one control on this page whose effect lands somewhere other than
   this page, and it should not read like the rest of the chrome. */
.icon-btn.on {
  color: var(--ro-gold);
  background: rgba(228, 170, 79, 0.16);
  box-shadow: 0 0 0 1px rgba(228, 170, 79, 0.35) inset;
}
[data-theme="light"] .icon-btn.on { color: var(--ro-gold-text); }
.btn { padding: 11px 22px; }

.block-action {
  transform: translateX(4px);
  transition: opacity var(--t-fast) var(--ease), transform var(--t-base) var(--ease),
              color var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.block-card:hover .block-action,
.block-action:focus-visible { transform: none; }
.block-action:hover { background: rgba(255, 255, 255, 0.10); border-color: transparent; }
[data-theme="light"] .block-action:hover { background: rgba(45, 58, 50, 0.09); }

/* ---- the archive ---- */

.done-row {
  border: 0;
  border-radius: var(--r-row);
  padding: 11px 14px;
}
.done-row:hover { background: rgba(255, 255, 255, 0.07); transform: translateX(2px); }
[data-theme="light"] .done-row:hover { background: rgba(255, 255, 255, 0.85); }

/* ---- the modal settles in ---- */

.modal-backdrop { transition: opacity var(--t-base) var(--ease); }
.modal { animation: modal-in var(--t-base) var(--ease); }
@keyframes modal-in {
  from { opacity: 0; transform: translateY(12px) scale(0.985); }
}
@media (prefers-reduced-motion: reduce) {
  .modal { animation: none; }
}

.field input, .field select, .field textarea,
.line-row input, .line-row select,
#q-crew {
  border-radius: var(--r-ctl);
  transition: background var(--t-fast) var(--ease), box-shadow var(--t-fast) var(--ease),
              border-color var(--t-fast) var(--ease);
}


/* ============================================
   THE SETTINGS BUTTON — liquid split into a fan of actions
   ============================================

   One plus that separates into three controls. While they are close together
   the metaball filter reads them as a single mass; as they travel apart the
   neck between them thins and snaps, which is the whole effect.

   TWO LAYERS, and it is not optional. A CSS filter applies to the entire
   subtree, so an icon or a number inside the filtered container is blurred
   along with the blob behind it. `.fab-goo` therefore holds nothing but
   shapes, and `.fab-ui` carries the real controls on top, unfiltered. Both are
   driven by the same `--y` per action, so the shape and the control it stands
   for cannot drift apart.
   ============================================ */

.goo-defs { position: absolute; width: 0; height: 0; }

.fab-wrap {
  position: fixed;
  right: var(--space-lg);
  bottom: var(--space-lg);
  z-index: 60;
  width: 60px;
  height: 60px;
}

/* Where each action sits once the mass has split. The trigger stays at 0. */
/* Radius 98px, swept from due up to due left. Angles rather than a column, so
   the mass opens like a hand rather than a list. The crew pill takes the left
   position because it is the wide one and that is where the room is. */
.act-new,  .goo-new  { --x:  -8px;  --y: -98px; --d: 0.02s; }
.act-bell, .goo-bell { --x: -72px;  --y: -70px; --d: 0.06s; }
.act-crew, .goo-crew { --x: -88px;  --y:   0px; --d: 0.10s; }

.fab-goo,
.fab-ui {
  position: absolute;
  inset: 0;
}

.fab-goo {
  filter: url(#goo);
  /* Chrome will not composite an SVG filter on a layer it thinks is flat. */
  transform: translateZ(0);
}

/* ---- the shapes ---- */

.goo {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 60px;
  height: 60px;
  border-radius: 30px;
  background: #242825;
  box-shadow: 0 1px 0 rgba(255, 255, 255, 0.06) inset;
  transform: translate(0, 0) scale(0.6);
  opacity: 0;
  transition:
    transform 0.42s var(--ease-in-out-back) var(--d),
    opacity 0.2s var(--ease) var(--d);
}
.goo-trigger { opacity: 1; transform: none; transition: none; }

/* The crew control is a pill, not a disc — it holds a number and two steppers.
   The filter does not care what shape it is fed. */
.goo-crew { width: 150px; border-radius: 30px; }

.fab-wrap.open .goo {
  transform: translate(var(--x), var(--y)) scale(1);
  opacity: 1;
}

/* ---- the controls ---- */

.fab-act {
  position: absolute;
  right: 0;
  bottom: 0;
  width: 60px;
  height: 60px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 30px;
  background: transparent;      /* the blob underneath is the surface */
  color: rgba(255, 255, 255, 0.92);
  font-size: 1.375rem;
  cursor: pointer;
  transform: translate(0, 0) scale(0.6);
  opacity: 0;
  pointer-events: none;
  transition:
    transform 0.42s var(--ease-in-out-back) var(--d),
    opacity 0.2s var(--ease) var(--d);
}

.fab-wrap.open .fab-act {
  transform: translate(var(--x), var(--y)) scale(1);
  opacity: 1;
  pointer-events: auto;
}

.act-trigger {
  opacity: 1;
  transform: none;
  pointer-events: auto;
  transition: transform 0.42s var(--ease-in-out-back);
}
/* A plus becomes a close by turning an eighth of a circle. */
.fab-wrap.open .act-trigger { transform: rotate(135deg); }
.act-trigger:active { transform: scale(0.92); }
.fab-wrap.open .act-trigger:active { transform: rotate(135deg) scale(0.92); }

.fab-act:focus-visible { outline: 2px solid var(--ro-gold); outline-offset: 4px; }

/* Lit when the cron is pushing to Telegram. The blob under it is recoloured
   rather than the icon, so the state is visible at blob size. */
.fab-wrap.notifying .act-bell { color: var(--ro-gold); }

/* ---- the crew pill ---- */

.act-crew {
  width: 150px;
  grid-template-columns: 38px 1fr 38px;
  gap: 0;
  place-items: stretch;
}

.crew-step {
  border: 0;
  background: transparent;
  color: rgba(255, 255, 255, 0.75);
  font-size: 1.125rem;
  font-weight: 700;
  cursor: pointer;
  border-radius: 30px;
  transition: background var(--t-fast) var(--ease);
}
.crew-step:hover { background: rgba(255, 255, 255, 0.10); color: #fff; }
.crew-step:active { background: rgba(255, 255, 255, 0.16); }

#q-crew {
  width: 100%;
  min-height: 0;
  padding: 0;
  border: 0;
  background: transparent;
  box-shadow: none;
  color: rgba(255, 255, 255, 0.95);
  font-family: var(--font-mono);
  font-size: 0.9375rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  text-align: center;
  -moz-appearance: textfield;
  appearance: textfield;
}
/* The spinner is what used to set an empty field to its minimum. The steppers
   either side do the same job without a target small enough to hit by accident. */
#q-crew::-webkit-outer-spin-button,
#q-crew::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
#q-crew:focus { outline: none; box-shadow: inset 0 -2px 0 rgba(255, 255, 255, 0.4); background: transparent; }
#q-crew.overridden { color: var(--ro-gold); }

/* ---- the caption ---- */

.fab-caption {
  position: absolute;
  right: 0;
  bottom: 176px;
  width: max-content;
  max-width: 46vw;
  text-align: right;
  opacity: 0;
  transform: translateX(8px);
  pointer-events: none;
  transition: opacity var(--t-fast) var(--ease) 0.14s, transform var(--t-base) var(--ease) 0.14s;
}
.fab-wrap.open .fab-caption { opacity: 1; transform: none; pointer-events: auto; }

.fab-caption .crew-basis {
  margin: 0 0 6px;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--text-muted);
  white-space: normal;
}

/* Reduced motion keeps the split, drops the travel. */
@media (prefers-reduced-motion: reduce) {
  .goo, .fab-act, .fab-caption, .act-trigger { transition-duration: 0.01s; }
}

[data-theme="light"] .goo {
  background: #ffffff;
  box-shadow: 0 1px 0 rgba(45, 58, 50, 0.06) inset;
}
[data-theme="light"] .fab-act { color: var(--text); }
[data-theme="light"] .crew-step { color: var(--text-secondary); }
[data-theme="light"] .crew-step:hover { background: rgba(45, 58, 50, 0.08); color: var(--text); }
[data-theme="light"] #q-crew { color: var(--text); }
[data-theme="light"] #q-crew:focus { box-shadow: inset 0 -2px 0 rgba(45, 58, 50, 0.4); }
[data-theme="light"] .fab-wrap.notifying .act-bell { color: var(--ro-gold-text); }

@media (max-width: 640px) {
  .fab-wrap { right: var(--space-md); bottom: var(--space-md); }
  .fab-caption { max-width: 60vw; }
  /* The arc has to tuck in where a phone has no room to its left. */
  .act-crew, .goo-crew { --x: -76px; }
}

/* ---- the header, quieted ----
   Four boxed buttons became two ghost controls and a line of text. The two that
   went were doing work the page already does: unlocking happens by itself at
   the first write that needs it, and the board now refreshes on its own. */

.ghost-btn {
  min-width: 38px;
  min-height: 38px;
  padding: 0 10px;
  display: grid;
  place-items: center;
  background: transparent;
  border: 0;
  border-radius: var(--r-ctl);
  color: var(--header-text);
  opacity: 0.65;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease), background var(--t-fast) var(--ease);
}
.ghost-btn i { font-size: 1.125rem; }
.ghost-btn:hover { opacity: 1; background: rgba(255, 255, 255, 0.08); }
.ghost-btn:active { transform: scale(0.94); }
.ghost-btn:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 2px; }
[data-theme="light"] .ghost-btn:hover { background: rgba(45, 58, 50, 0.08); }

/* Reports rather than commands, so it is text — but still a button, because
   the honest answer to "is this current?" is sometimes "make it current now". */
.freshness {
  background: transparent;
  border: 0;
  padding: 4px 8px;
  border-radius: var(--r-ctl);
  color: var(--header-text);
  opacity: 0.45;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
  cursor: pointer;
  transition: opacity var(--t-fast) var(--ease), color var(--t-fast) var(--ease);
}
.freshness:hover { opacity: 1; }
.freshness:focus-visible { outline: 2px solid var(--ro-green); outline-offset: 2px; }
.freshness.stale { color: var(--ro-gold); opacity: 0.9; }

.header-actions { gap: 2px; align-items: center; }

@media (max-width: 640px) {
  /* The title already truncates on a phone; the timestamp is the first thing
     that can go. */
  .freshness { display: none; }
}

/* Full-size targets where there is no pointer to be precise with. Placed at the
   end deliberately: `@media` adds no specificity, so this has to come after the
   base `.pl-tool` rule to win — sitting above it, it silently did nothing. */
@media (hover: none) {
  .pl-tool { min-width: 44px; min-height: 44px; }
  .pl-tool-gap { min-width: 44px; }
}

/* A gap in the data, not a setting. Amber because unread production looks
   exactly like a floor that has stopped trimming. */
.fab-caption .crew-basis.warn { color: var(--ro-gold); }
