/* ===========================================================================
   Cloud AOS
   ---------------------------------------------------------------------------
   The palette, radii and type scale come from AgroOS_Frontend/src/Theme.js, so
   the cloud app reads as the same product as the tablets rather than a
   lookalike. Values are duplicated here rather than shared because there is no
   build step on this host -- when Theme.js changes, change these too.

   Themes work three ways, in priority order:
     [data-theme="dark"]  explicit choice, set by the toggle before first paint
     [data-theme="light"] explicit choice the other way
     prefers-color-scheme the OS default when nothing has been chosen
   =========================================================================== */

:root {
  --primary: #1E40AF;
  --primary-contrast: #FFFFFF;
  --primary-hover: rgba(30, 64, 175, 0.85);

  --bg: #F9FAFB;
  --paper: #FFFFFF;
  --paper-raised: #F5F5F5;

  --text: #222222;
  --text-secondary: #555555;
  --text-muted: #6B7280;

  --border: rgba(0, 0, 0, 0.12);
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.12);

  --error: #D32F2F;
  --success: #388E3C;
  --success-hover: #2e6b30;
  --warning: #F57C00;
  --info: #1976D2;
  --note: #facb2f;
  --note-contrast: #000000;
  --muted-header: #616161;

  /* ---- Spacing -----------------------------------------------------------
     A 4px base, so rhythm is a decision made once rather than per component.
     The values before this were each picked in isolation, and it showed. */
  --space-1: 4px;
  --space-2: 8px;
  --space-3: 12px;
  --space-4: 16px;
  --space-5: 20px;
  --space-6: 24px;
  --space-7: 32px;
  --space-8: 40px;

  /* ---- Type --------------------------------------------------------------
     Sized for the people who actually use this: truck drivers and plant staff,
     often outdoors, often in a hurry, sometimes in gloves.

     The previous scale was tuned for density -- fitting more jobs on a screen.
     That was optimising for the wrong thing. Seeing four jobs you can read
     beats seeing six you have to squint at, so everything here is roughly 20%
     up from that pass. */
  --text-xs: 0.8125rem;   /* 13 */
  --text-sm: 0.9375rem;   /* 15 */
  --text-base: 1.125rem;  /* 18 */
  --text-lg: 1.3125rem;   /* 21 */
  --text-xl: 1.5rem;      /* 24 */
  --text-2xl: 1.75rem;    /* 28 */

  /* ---- Controls ----------------------------------------------------------
     Minimum tap targets. 44px is the usual accessibility floor; these are
     deliberately above it, because the floor assumes a bare fingertip indoors. */
  --tap: 56px;
  --tap-compact: 48px;
  --tabbar-height: 78px;

  --radius-sm: 10px;
  --radius: 14px;
  --radius-lg: 20px;
  --radius-pill: 999px;

  --font: "SF Pro Display", Roboto, Arial, sans-serif;

  /* ---- Status ------------------------------------------------------------
     The palette from AgroOS_Frontend/src/Theme.js. These used to be inlined
     into style attributes, which the CSP blocks -- so until now they never
     rendered at all. */
  --status-in-progress: #2e7d32;
  --status-in-progress-fg: #ffffff;
  --status-paused: #fcc419;
  --status-paused-fg: #5c4813;
  --status-viewed: #94d82d;
  --status-viewed-fg: #2b4a0a;
  --status-reactivated: #22b8cf;
  --status-reactivated-fg: #ffffff;
  --status-needs-approval: #ef6c00;
  --status-needs-approval-fg: #ffffff;
  --status-completed: #9c36b5;
  --status-completed-fg: #ffffff;
  --status-default: #e0e0e0;
  --status-default-fg: #424242;
}

:root[data-theme="dark"] {
  --primary: #90CAF9;
  --primary-contrast: #121212;
  --primary-hover: rgba(144, 202, 249, 0.85);

  --bg: #121212;
  --paper: #1E1E1E;
  --paper-raised: #424242;

  --text: #EDEDED;
  --text-secondary: #B0BEC5;
  --text-muted: #9AA5AC;

  --border: rgba(255, 255, 255, 0.12);
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
  --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);

  --error: #EF5350;
  --success: #66BB6A;
  --success-hover: #4d9950;
  --warning: #FFA726;
  --info: #42A5F5;
  --muted-header: #424242;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --primary: #90CAF9;
    --primary-contrast: #121212;
    --primary-hover: rgba(144, 202, 249, 0.85);

    --bg: #121212;
    --paper: #1E1E1E;
    --paper-raised: #424242;

    --text: #EDEDED;
    --text-secondary: #B0BEC5;
    --text-muted: #9AA5AC;

    --border: rgba(255, 255, 255, 0.12);
    --shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
    --shadow-lg: 0 8px 24px rgba(0, 0, 0, 0.6);

    --error: #EF5350;
    --success: #66BB6A;
    --success-hover: #4d9950;
    --warning: #FFA726;
    --info: #42A5F5;
    --muted-header: #424242;
  }
}

/* --------------------------------------------------------------------------
   Reset
   -------------------------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

/* The white flash between pages lives here.
 *
 * Between two documents the browser paints the ROOT element's background, not
 * the body's. With none set it uses its own default -- white -- so every server
 * navigation flashed white, in dark mode especially.
 *
 * color-scheme matters just as much: it tells the browser which canvas colour to
 * paint before any CSS of ours applies. Without it a dark-themed app still gets
 * a white gap on every navigation, and there is no stylesheet trick that fixes
 * that from inside the page.
 */
html {
  -webkit-text-size-adjust: 100%;
  background: var(--bg);
  color-scheme: light;
}

:root[data-theme="dark"] { color-scheme: dark; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { color-scheme: dark; }
}

body {
  margin: 0;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 1rem;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  /* No overscroll-behavior here on purpose: the browser has its own
     pull-to-refresh on both platforms, and this app no longer competes with it.
     A custom one was redundant, and its document-level touch handlers were what
     made quick taps on the tab bar get eaten. */
}

/* Removes the 300ms tap delay legacy, and the grey flash Android paints over
   anything tapped -- real :active states below do that job better. */
a, button, .tab, .task-card, .theme-option {
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

/* Cross-document page transitions: pages cross-fade instead of flashing white.
   Browsers without support simply navigate, so there is nothing to fall back
   to. Disabled entirely under reduced motion, at the bottom of this file. */
@view-transition { navigation: auto; }

h1, h2, h3 { margin: 0; line-height: 1.3; }

a { color: var(--primary); }

/* --------------------------------------------------------------------------
   Title bar
   --------------------------------------------------------------------------
   A title bar, not a toolbar. It says where you are; the tab bar says where you
   can go, and Account holds everything else. That division is what keeps a
   phone screen from filling with chrome.
   -------------------------------------------------------------------------- */
.titlebar {
  background: var(--primary);
  color: var(--primary-contrast);
  box-shadow: var(--shadow);
  position: sticky;
  top: 0;
  z-index: 20;
  padding-top: env(safe-area-inset-top);
}

.titlebar-inner {
  max-width: 1100px;
  margin: 0 auto;
  padding: 0.65rem 1rem;
  display: flex;
  align-items: center;
  gap: 0.75rem;
  min-height: 52px;
}

.brand {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  text-decoration: none;
  color: inherit;
}

.brand-mark { font-size: 1.25rem; font-weight: 800; letter-spacing: 0.5px; }
.brand-sub  { font-size: 0.8rem; font-weight: 600; opacity: 0.85; }

.titlebar-back {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  /* 44px of tap target in the corner a thumb reaches for first. */
  width: 44px;
  height: 44px;
  margin-left: -0.65rem;
  border-radius: 50%;
  color: inherit;
  text-decoration: none;
  flex-shrink: 0;
}

.titlebar-back:active { background: rgba(255, 255, 255, 0.2); }

.titlebar-title {
  font-size: 1.05rem;
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Wide screens get the destinations inline, because a bar pinned to the bottom
   of a desktop browser window reads as a mistake. */
.titlebar-nav { display: none; margin-left: auto; gap: 0.25rem; }

.titlebar-nav-link {
  padding: 0.5rem 0.9rem;
  border-radius: var(--radius-sm);
  color: inherit;
  text-decoration: none;
  font-weight: 600;
  font-size: 0.9rem;
  opacity: 0.8;
}

.titlebar-nav-link:hover { background: rgba(255, 255, 255, 0.15); opacity: 1; }
.titlebar-nav-link.is-active { background: rgba(255, 255, 255, 0.22); opacity: 1; }

@media (min-width: 700px) {
  .titlebar-nav { display: flex; }
}

/* --------------------------------------------------------------------------
   Tab bar
   -------------------------------------------------------------------------- */
.tabbar {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  z-index: 20;
  display: flex;
  background: var(--paper);
  border-top: 1px solid var(--border);
  /* Clears the iPhone home indicator. viewport-fit=cover in the layout is what
     makes this inset non-zero. */
  padding-bottom: env(safe-area-inset-bottom);
}

.tab {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-height: 56px;
  padding: 0.4rem 0.25rem;
  color: var(--text-muted);
  text-decoration: none;
  font-size: 0.7rem;
  font-weight: 600;
  transition: color 0.15s ease;
}

.tab.is-active { color: var(--primary); }
.tab:active { background: var(--paper-raised); }
.tab:focus-visible { outline: 3px solid var(--primary); outline-offset: -3px; }

/* Body padding so the last card is never trapped behind the fixed bar.
   Tied to a token rather than a literal, because these two drifted apart the
   moment the bar got taller and the last card went under it. */
body.has-tabbar .page {
  padding-bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom) + var(--space-6));
}

@media (min-width: 700px) {
  .tabbar { display: none; }
  body.has-tabbar .page { padding-bottom: 3rem; }
}

/* --------------------------------------------------------------------------
   Navigation progress
   --------------------------------------------------------------------------
   The gap this covers is network latency between tapping and the next document
   painting, which on a phone is the only part of a page load anyone waits for.
   -------------------------------------------------------------------------- */
.nav-progress {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  height: 3px;
  z-index: 60;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease;
}

.nav-progress.is-active { opacity: 1; }

.nav-progress span {
  display: block;
  height: 100%;
  width: 0;
  background: var(--success);
  transition: width 0.3s ease;
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */
.page {
  flex: 1;
  width: 100%;
  max-width: 1100px;
  margin: 0 auto;
  padding: 1.5rem 1rem 3rem;
}

.footer {
  padding: 1rem;
  text-align: center;
  font-size: 0.75rem;
  color: var(--text-muted);
}

.card {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lg);
  padding: 1.5rem;
}

.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.page-greeting {
  font-size: 1.25rem;
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Skeletons
   --------------------------------------------------------------------------
   Only ever shown while something is genuinely loading -- the Refresh button
   fetching a new list. On a server-rendered page there is nothing to skeleton
   around, and painting one anyway would be a lie about what the app is doing.
   -------------------------------------------------------------------------- */
.skeleton-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  padding: 1rem 1.25rem;
  margin-bottom: 1.25rem;
}

.skeleton-line {
  height: 12px;
  border-radius: 6px;
  background: var(--paper-raised);
  margin-bottom: 0.75rem;
  position: relative;
  overflow: hidden;
}

.skeleton-line:last-child { margin-bottom: 0; }
.skeleton-line.is-title { height: 18px; width: 70%; }
.skeleton-line.is-short { width: 40%; }

.skeleton-line::after {
  content: "";
  position: absolute;
  inset: 0;
  transform: translateX(-100%);
  background: linear-gradient(
    90deg,
    transparent,
    rgba(255, 255, 255, 0.35),
    transparent
  );
  animation: shimmer 1.4s infinite;
}

:root[data-theme="dark"] .skeleton-line::after {
  background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.08), transparent);
}

@keyframes shimmer {
  100% { transform: translateX(100%); }
}

@keyframes spin {
  100% { transform: rotate(360deg); }
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.4rem;
  /* 44px minimum on every tap target: this is used on phones, in gloves,
     outdoors. */
  min-height: 44px;
  padding: 0.7rem 1.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius-sm);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 600;
  text-transform: none;
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease, border-color 0.2s ease;
}

.button:focus-visible {
  outline: 3px solid var(--primary);
  outline-offset: 2px;
}

.button:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}

/* A tap should feel acknowledged before the network answers. */
.button:active:not(:disabled) { transform: scale(0.97); }

.button-compact {
  min-height: 40px;
  padding: 0.45rem 0.9rem;
  font-size: 0.875rem;
}

.button-full { width: 100%; }

/* --------------------------------------------------------------------------
   Busy buttons
   --------------------------------------------------------------------------
   The spinner replaces the label rather than sitting beside it, so the button
   does not change width mid-press and shift everything around it.
   -------------------------------------------------------------------------- */
.button.is-busy {
  position: relative;
  color: transparent !important;
  pointer-events: none;
}

.button.is-busy::after {
  content: "";
  position: absolute;
  top: 50%;
  left: 50%;
  width: 18px;
  height: 18px;
  margin: -9px 0 0 -9px;
  border: 2px solid currentColor;
  border-radius: 50%;
  /* currentColor is transparent on a busy button, so the ring is drawn from the
     border colours directly. */
  border-color: rgba(255, 255, 255, 0.4);
  border-top-color: #fff;
  animation: spin 0.7s linear infinite;
}

.button-outline.is-busy::after,
.button-danger-outline.is-busy::after {
  border-color: rgba(0, 0, 0, 0.2);
  border-top-color: var(--primary);
}

.button-primary { background: var(--primary); color: var(--primary-contrast); }
.button-primary:hover:not(:disabled) { background: var(--primary-hover); }

.button-success { background: var(--success); color: #ffffff; }
.button-success:hover:not(:disabled) { background: var(--success-hover); }

.button-outline {
  background: transparent;
  color: var(--primary);
  border-color: var(--primary);
}
.button-outline:hover { background: var(--primary); color: var(--primary-contrast); }

.button-danger-outline {
  background: transparent;
  color: var(--error);
  border-color: var(--error);
}
.button-danger-outline:hover { background: var(--error); color: #ffffff; }




/* --------------------------------------------------------------------------
   Login
   -------------------------------------------------------------------------- */
.field-label {
  display: block;
  text-align: left;
  font-size: 0.8rem;
  font-weight: 600;
  color: var(--text-secondary);
  margin-bottom: 0.35rem;
}

/* --------------------------------------------------------------------------
   Panels
   -------------------------------------------------------------------------- */
.panel { margin-bottom: 2rem; }

.panel-head {
  padding: 0.85rem 1.25rem;
  border-radius: var(--radius) var(--radius) 0 0;
  box-shadow: var(--shadow);
}

.panel-head h2 {
  font-size: 1.1rem;
  font-weight: 700;
  letter-spacing: 0.5px;
  text-align: center;
}

.panel-head-primary { background: var(--primary); color: var(--primary-contrast); }
.panel-head-muted   { background: var(--muted-header); color: #ffffff; }

.panel-body {
  background: var(--paper);
  border: 1px solid var(--border);
  border-top: none;
  border-radius: 0 0 var(--radius) var(--radius);
  padding: 1.25rem;
  box-shadow: var(--shadow);
}

.muted-center { text-align: center; color: var(--text-secondary); margin: 1rem 0; }

/* --------------------------------------------------------------------------
   Task cards
   -------------------------------------------------------------------------- */
.task-card {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  background: var(--paper);
  overflow: hidden;
  margin-bottom: 1.25rem;
  /* Cards are the thing that changes between refreshes, so they get their own
     transition name for the cross-fade rather than moving with the whole page. */
  transition: box-shadow 0.15s ease;
}

.task-card:last-child { margin-bottom: 0; }



.task-title {
  font-size: 1.1rem;
  font-weight: 700;
  color: var(--primary);
  /* Long descriptions wrap rather than pushing the card sideways. */
  overflow-wrap: anywhere;
}


.task-meta { display: flex; flex-wrap: wrap; gap: 1.25rem; font-size: 0.9rem; }



.task-actions {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
}


.date-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 1.5rem 0 1.25rem;
  color: var(--text-secondary);
  font-size: 0.8rem;
  font-weight: 600;
  letter-spacing: 0.5px;
}

.date-divider::before,
.date-divider::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border);
}

.date-divider:first-child { margin-top: 0; }

/* --------------------------------------------------------------------------
   Empty state
   -------------------------------------------------------------------------- */
.empty-state {
  text-align: center;
  padding: 2rem 1rem;
  color: var(--text-secondary);
}

.empty-state svg { color: var(--success); margin-bottom: 0.5rem; }

.empty-title { font-size: 1.1rem; font-weight: 700; color: var(--text); margin: 0.25rem 0; }
.empty-sub   { margin: 0 0 1.25rem; font-size: 0.9rem; }

/* --------------------------------------------------------------------------
   Task detail
   -------------------------------------------------------------------------- */
.breadcrumb { margin: 0 0 1rem; font-size: 0.9rem; font-weight: 600; }

.detail-title { font-size: 1.35rem; font-weight: 700; margin-bottom: 0.75rem; overflow-wrap: anywhere; }

.detail-status { margin: 0 0 1.25rem; }

.status-pill {
  display: inline-block;
  padding: 0.35rem 0.9rem;
  border-radius: 999px;
  font-size: 0.85rem;
  font-weight: 700;
}

.detail-list { margin: 0; }

.detail-row {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1rem;
  padding: 0.65rem 0;
  border-bottom: 1px solid var(--border);
}

.detail-row:last-child { border-bottom: none; }

.detail-row dt {
  flex: 0 0 11rem;
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  font-weight: 600;
}

.detail-row dd { margin: 0; flex: 1; min-width: 8rem; overflow-wrap: anywhere; }

.detail-footnote {
  margin-top: 1.5rem;
  font-size: 0.85rem;
  color: var(--text-muted);
  font-style: italic;
}

.note-callout {
  background: var(--note);
  color: var(--note-contrast);
  border-radius: var(--radius-sm);
  padding: 1rem 1.25rem;
  margin-bottom: 1.5rem;
}

.note-callout h3 { font-size: 0.8rem; text-transform: uppercase; letter-spacing: 0.5px; margin-bottom: 0.4rem; }
.note-callout p  { margin: 0; overflow-wrap: anywhere; }

/* --------------------------------------------------------------------------
   Module forms (cloud test, and whatever follows it)
   -------------------------------------------------------------------------- */
.text-input {
  width: 100%;
  padding: 0.8rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--text);
  font-family: inherit;
  /* 16px minimum: iOS Safari zooms the whole page when a focused input is any
     smaller, and the layout never quite recovers. */
  font-size: 1rem;
  resize: vertical;
}

.text-input:focus-visible { outline: 3px solid var(--primary); outline-offset: 1px; }

.camera-block {
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  padding: 1rem;
  margin-bottom: 1.5rem;
}

.camera-heading {
  font-size: 0.8rem;
  text-transform: uppercase;
  letter-spacing: 0.5px;
  color: var(--text-muted);
  margin-bottom: 0.5rem;
}

.camera-status {
  font-size: 0.9rem;
  color: var(--text-secondary);
  margin-bottom: 0.75rem;
  min-height: 1.4rem;
}

.camera-preview {
  display: block;
  width: 100%;
  max-height: 60vh;
  border-radius: var(--radius-sm);
  background: #000;
  margin-bottom: 0.75rem;
}

.module-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}

.module-actions .button { flex: 1; min-width: 12rem; }

.save-indicator {
  font-size: 0.85rem;
  color: var(--text-muted);
  min-height: 1.2rem;
}

/* --------------------------------------------------------------------------
   Note dialog
   -------------------------------------------------------------------------- */
.note-dialog {
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  box-shadow: var(--shadow-lg);
  padding: 1.5rem;
  max-width: min(28rem, calc(100vw - 2rem));
  width: 100%;
}

.note-dialog::backdrop { background: rgba(0, 0, 0, 0.5); }

.note-dialog-title {
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 0.75rem;
  color: var(--primary);
}

.note-dialog-body { margin: 0 0 1.25rem; white-space: pre-wrap; overflow-wrap: anywhere; }

/* --------------------------------------------------------------------------
   Theme choice
   -------------------------------------------------------------------------- */
.theme-choice { display: flex; gap: 0.6rem; }

.theme-option {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.35rem;
  min-height: 72px;
  padding: 0.75rem 0.5rem;
  border: 2px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--bg);
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 0.8rem;
  font-weight: 600;
  cursor: pointer;
  transition: border-color 0.15s ease, color 0.15s ease;
}

.theme-option[aria-checked="true"] {
  border-color: var(--primary);
  color: var(--primary);
}

.theme-option:active { transform: scale(0.97); }
.theme-option:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

/* --------------------------------------------------------------------------
   Error page
   -------------------------------------------------------------------------- */
.error-card { max-width: 30rem; margin: 3rem auto; text-align: center; }
.error-heading { font-size: 1.5rem; font-weight: 700; margin-bottom: 0.75rem; }
.error-message { color: var(--text-secondary); margin-bottom: 1.5rem; }

/* --------------------------------------------------------------------------
   Motion
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  * {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
  }

  /* Turn the page cross-fade off entirely rather than making it instant — a
     0.01ms view transition still forces a snapshot and a composite for no
     benefit. */
  @view-transition { navigation: none; }

  /* Presses stop scaling: the point of that motion is feedback, and someone who
     has asked for less motion has asked for exactly this. */
  .button:active:not(:disabled),
  .theme-option:active { transform: none; }

  /* Skeletons stay, the shimmer goes. The shape still says "loading". */
  .skeleton-line::after { animation: none; }
}

/* ===========================================================================
   Phase 5 -- refreshed components
   ---------------------------------------------------------------------------
   Later in the file than the rules they replace, so these win on specificity
   ties. Superseded blocks above stay only where other pages still use them.
   =========================================================================== */

body {
  font-size: var(--text-base);
  line-height: 1.45;
}

/* --------------------------------------------------------------------------
   Status pills
   --------------------------------------------------------------------------
   Colour spent where it carries meaning, instead of on a full-width band. The
   values are the ones in Theme.js, driven by data-status rather than a style
   attribute the CSP would block -- which is why they never rendered before.
   -------------------------------------------------------------------------- */
.status-pill {
  display: inline-block;
  padding: 3px var(--space-2);
  border-radius: var(--radius-pill);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.2px;
  white-space: nowrap;
  background: var(--status-default);
  color: var(--status-default-fg);
}

.status-pill[data-status="in-progress"]    { background: var(--status-in-progress);    color: var(--status-in-progress-fg); }
.status-pill[data-status="paused"]         { background: var(--status-paused);         color: var(--status-paused-fg); }
.status-pill[data-status="viewed"]         { background: var(--status-viewed);         color: var(--status-viewed-fg); }
.status-pill[data-status="reactivated"]    { background: var(--status-reactivated);    color: var(--status-reactivated-fg); }
.status-pill[data-status="needs-approval"] { background: var(--status-needs-approval); color: var(--status-needs-approval-fg); }
.status-pill[data-status="completed"]      { background: var(--status-completed);      color: var(--status-completed-fg); }

/* --------------------------------------------------------------------------
   Sections
   -------------------------------------------------------------------------- */
.section { margin-bottom: var(--space-7); }

.section-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
}

.section-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 20px;
  height: 20px;
  padding: 0 6px;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--primary-contrast);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0;
}

.day-label {
  margin: var(--space-5) 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 700;
  color: var(--text-muted);
  letter-spacing: 0.4px;
}

.day-label:first-of-type { margin-top: 0; }

/* --------------------------------------------------------------------------
   Task card
   --------------------------------------------------------------------------
   One surface. The three stacked bands this replaced ran to about 200px, so a
   phone showed two cards; this is roughly half, and a screen holds four or
   five. Scanning a list for the right job is the actual use case.
   -------------------------------------------------------------------------- */
.task-card {
  background: var(--paper);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  /* Almost no shadow. Heavy elevation is a desktop idiom; on a small screen it
     eats contrast and makes everything look like it is floating. */
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04);
  padding: var(--space-4);
  margin-bottom: var(--space-3);
  transition: border-color 0.15s ease, transform 0.08s ease;
}

.task-card:last-child { margin-bottom: 0; }
.task-card:active { transform: scale(0.995); border-color: var(--primary); }

.task-card-top {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-1);
}

.task-title {
  font-size: var(--text-lg);
  font-weight: 650;
  line-height: 1.3;
  color: var(--text);
  overflow-wrap: anywhere;
}

.task-meta {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* A middot between items, so the line reads as one fact rather than several. */
.task-meta span + span::before {
  content: "\00b7";
  margin-right: var(--space-3);
}

.task-actions {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-wrap: wrap;
}

.note-chip {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-height: 36px;
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-pill);
  background: var(--note);
  color: var(--note-contrast);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 650;
  cursor: pointer;
}

.note-chip:active { transform: scale(0.96); }
.note-chip:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

/* A third weight of button: present, but not competing with Start. */
.button-quiet {
  background: transparent;
  color: var(--text-secondary);
  border-color: var(--border);
}

.button-quiet:hover { background: var(--paper-raised); color: var(--text); }

/* --------------------------------------------------------------------------
   Tab bar -- active indicator
   --------------------------------------------------------------------------
   A filled pill behind the icon. The previous version signalled the active tab
   with an icon colour change alone, which on a phone held at arm's length
   outdoors is close to no signal at all.
   -------------------------------------------------------------------------- */
.tab {
  gap: 3px;
  font-size: var(--text-xs);
  font-weight: 600;
  padding-top: var(--space-2);
}

.tab svg {
  position: relative;
  z-index: 1;
  transition: transform 0.18s cubic-bezier(0.2, 0, 0, 1);
}

.tab-icon-wrap {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 60px;
  height: 30px;
}

.tab-icon-wrap::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: var(--radius-pill);
  background: var(--primary);
  opacity: 0;
  transform: scaleX(0.5);
  transition: opacity 0.18s ease, transform 0.18s cubic-bezier(0.2, 0, 0, 1);
}

.tab.is-active .tab-icon-wrap::before { opacity: 0.16; transform: scaleX(1); }
.tab.is-active svg { transform: translateY(-1px); }
.tab.is-active span { font-weight: 750; }
.tab:active .tab-icon-wrap::before { opacity: 0.28; transform: scaleX(1); }

/* --------------------------------------------------------------------------
   Page head
   -------------------------------------------------------------------------- */
.page-greeting { font-size: var(--text-xl); font-weight: 700; }
.page-head { margin-bottom: var(--space-5); }

/* --------------------------------------------------------------------------
   Empty states
   -------------------------------------------------------------------------- */
.empty-state {
  text-align: center;
  padding: var(--space-8) var(--space-4);
  color: var(--text-secondary);
  background: var(--paper);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}

.empty-state-inline {
  padding: var(--space-5) var(--space-4);
  border-style: solid;
}

.empty-title {
  font-size: var(--text-lg);
  font-weight: 700;
  color: var(--text);
  margin: var(--space-2) 0 var(--space-1);
}

.empty-sub { margin: 0; font-size: var(--text-sm); }

/* --------------------------------------------------------------------------
   Misc
   -------------------------------------------------------------------------- */
.detail-footnote-tight { margin-top: 0; }

/* --------------------------------------------------------------------------
   Page transitions
   --------------------------------------------------------------------------
   Content fades up; the chrome does not move. A tab bar that fades along with
   the page is the thing that makes a web app look like a web page.
   -------------------------------------------------------------------------- */
.titlebar { view-transition-name: aos-titlebar; }
.tabbar   { view-transition-name: aos-tabbar; }

::view-transition-old(aos-titlebar),
::view-transition-new(aos-titlebar),
::view-transition-old(aos-tabbar),
::view-transition-new(aos-tabbar) {
  animation: none;
  mix-blend-mode: normal;
}

@keyframes vt-fade-out { to { opacity: 0; } }

@keyframes vt-fade-up {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

::view-transition-old(root) { animation: vt-fade-out 110ms ease both; }
::view-transition-new(root) { animation: vt-fade-up 260ms cubic-bezier(0.2, 0, 0, 1) both; }

/* Signing in or out (app.js: showLoader). The bars stop holding still, so
   they leave with the page under the "Signing out" overlay instead of
   showing over it -- and over the PIN screen -- for a moment. */
.is-leaving .titlebar,
.is-leaving .tabbar,
.is-leaving .sidebar,
.is-leaving .fb-header { view-transition-name: none !important; }

/* --------------------------------------------------------------------------
   Bottom sheet
   --------------------------------------------------------------------------
   Still a native dialog element, so focus trapping, the backdrop and Escape
   stay the browser's job. Only the presentation changes.
   -------------------------------------------------------------------------- */
.note-dialog {
  border: none;
  border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  background: var(--paper);
  color: var(--text);
  padding: var(--space-5) var(--space-5) var(--space-6);
  width: 100%;
  max-width: 100%;
  /* auto top margin with zero bottom pins it to the bottom edge. */
  margin: auto auto 0;
  padding-bottom: calc(var(--space-6) + env(safe-area-inset-bottom));
  box-shadow: 0 -8px 32px rgba(0, 0, 0, 0.18);
}

.note-dialog::backdrop { background: rgba(0, 0, 0, 0.45); }

.note-dialog[open] { animation: sheet-up 260ms cubic-bezier(0.2, 0, 0, 1); }
.note-dialog.is-closing { animation: sheet-down 200ms ease forwards; }

@keyframes sheet-up   { from { transform: translateY(100%); } }
@keyframes sheet-down { to   { transform: translateY(100%); } }

/* The grab handle: what tells someone the sheet can be swiped away. */
.sheet-handle {
  width: 36px;
  height: 4px;
  border-radius: var(--radius-pill);
  background: var(--border);
  margin: 0 auto var(--space-4);
}

@media (min-width: 700px) {
  /* Back to a centred dialog where there is no thumb to reach with. */
  .note-dialog {
    margin: auto;
    max-width: 28rem;
    border-radius: var(--radius);
    padding-bottom: var(--space-6);
  }

  .note-dialog[open] { animation: none; }
  .sheet-handle { display: none; }
}

/* --------------------------------------------------------------------------
   Motion preferences
   -------------------------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .note-dialog[open],
  .note-dialog.is-closing { animation: none; }

  ::view-transition-old(root),
  ::view-transition-new(root) { animation: none; }
}

/* ===========================================================================
   Phase 6
   =========================================================================== */

/* Announced to screen readers, invisible on screen. */
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

/* --------------------------------------------------------------------------
   Title bar
   -------------------------------------------------------------------------- */
.brand-icon { flex-shrink: 0; opacity: 0.95; }

.brand {
  align-items: center;
  gap: var(--space-2);
}

/* Chevron plus the word Back. An icon alone is a small target and says only
   that you can leave, not that there is somewhere to go. */
.titlebar-back {
  width: auto;
  height: 44px;
  gap: 1px;
  padding: 0 var(--space-3) 0 var(--space-2);
  margin-left: calc(var(--space-3) * -1);
  border-radius: var(--radius-pill);
  font-size: var(--text-base);
  font-weight: 600;
}

.titlebar-back:active { background: rgba(255, 255, 255, 0.22); }

/* Count beside the wide-screen nav links. */
.nav-count {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  margin-left: 5px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.28);
  font-size: 11px;
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Tab bar
   --------------------------------------------------------------------------
   Roomier, with a hairline between items. The extra height also grows the tap
   target, which is the other half of why quick taps were being missed.
   -------------------------------------------------------------------------- */
.tabbar { box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.05); }

.tab {
  gap: var(--space-1);
  min-height: var(--tabbar-height);
  padding: var(--space-3) var(--space-1) var(--space-2);
  position: relative;
}

/* A divider between tabs, inset from the edges so it reads as a separator
   rather than a border. */
.tab + .tab::after {
  content: "";
  position: absolute;
  left: 0;
  top: 22%;
  bottom: 22%;
  width: 1px;
  background: var(--border);
}

.tab-icon-wrap { width: 62px; height: 32px; }

/* The count. Sits on the icon corner, which is where a badge belongs and where
   the eye already is. */
.tab-badge {
  position: absolute;
  top: -2px;
  right: 8px;
  z-index: 2;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--primary-contrast);
  font-size: 11px;
  font-weight: 700;
  line-height: 1;
  /* A ring in the bar colour, so the badge stays legible when it overlaps the
     active pill behind it. */
  box-shadow: 0 0 0 2px var(--paper);
}

/* --------------------------------------------------------------------------
   Section head
   -------------------------------------------------------------------------- */
.section-head h2 {
  font-size: var(--text-sm);
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.6px;
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Task card
   -------------------------------------------------------------------------- */

/* The reason a disabled Start is disabled. It used to be a title attribute,
   which a touch user never sees -- they got a dead button and no explanation. */
.task-note-line {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-style: italic;
}

/* --------------------------------------------------------------------------
   Offline bar
   -------------------------------------------------------------------------- */
.offline-bar {
  position: sticky;
  top: 0;
  z-index: 30;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-4);
  background: var(--warning);
  color: #1b1200;
  font-size: var(--text-sm);
  font-weight: 650;
}

.offline-bar[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Sheet actions
   -------------------------------------------------------------------------- */
.sheet-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

@media (min-width: 700px) {
  .sheet-actions { flex-direction: row-reverse; }
}

/* ===========================================================================
   Login
   ---------------------------------------------------------------------------
   Laid out for one thumb, standing up. Brand at the top, entry in the middle,
   keypad low where the thumb already rests -- rather than a centred card, which
   puts the keys in the middle of the screen and asks for a second hand.
   =========================================================================== */
.login {
  display: flex;
  flex-direction: column;
  /* Height, max-width and padding are all set further down, where the phone
     and desktop cases are handled together. */
}

.login-top {
  display: flex;
  flex-direction: column;
  align-items: center;
  /* Takes the slack, so the keypad below is pinned low on a tall screen and
     simply sits under the entry on a short one. */
  flex: 1;
  justify-content: center;
  gap: var(--space-4);
  padding-bottom: var(--space-6);
}

.login-brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  color: var(--primary);
}

.login-brand-text { display: flex; flex-direction: column; line-height: 1.1; }
.login-brand-mark { font-size: var(--text-xl); font-weight: 800; letter-spacing: 0.5px; }

.login-brand-sub {
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 2px;
  text-transform: uppercase;
  opacity: 0.75;
}

.login-prompt {
  font-size: var(--text-lg);
  font-weight: 600;
  color: var(--text-secondary);
  margin: 0;
}

/* --------------------------------------------------------------------------
   The dots
   --------------------------------------------------------------------------
   PIN length varies here -- the plant allows anything from a couple of digits
   up -- so the row grows as digits are entered rather than showing a fixed set
   of placeholders that would lie about how many are expected.
   -------------------------------------------------------------------------- */
.pin-dots {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  /* Reserved whether or not it holds anything, so nothing below shifts as the
     first digit lands. */
  min-height: 28px;
}

.pin-dot {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: var(--primary);
  animation: dot-in 160ms cubic-bezier(0.2, 0, 0, 1);
}

@keyframes dot-in {
  from { transform: scale(0.4); opacity: 0; }
}

/* Empty state: a faint rule, so the row is obviously waiting for something. */
.pin-dots:empty::after {
  content: "";
  width: 92px;
  height: 2px;
  border-radius: 2px;
  background: var(--border);
}

.login-message {
  margin: 0;
  min-height: 2.4em;
  text-align: center;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--error);
}

/* A wrong PIN shakes the dots and clears them. Driven by an attribute the
   server sets, so no JavaScript is involved in the feedback itself. */
.login[data-error="true"] .pin-dots { animation: shake 420ms ease; }

@keyframes shake {
  10%, 90% { transform: translateX(-3px); }
  20%, 80% { transform: translateX(5px); }
  30%, 50%, 70% { transform: translateX(-8px); }
  40%, 60% { transform: translateX(8px); }
}

/* --------------------------------------------------------------------------
   Keypad
   -------------------------------------------------------------------------- */
.keypad {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-3);
  margin-bottom: var(--space-4);
}

.key {
  display: flex;
  align-items: center;
  justify-content: center;
  /* Generous, because this is used in gloves and in a hurry. */
  min-height: 64px;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-2xl);
  font-weight: 500;
  cursor: pointer;
  user-select: none;
  -webkit-user-select: none;
  transition: background-color 0.12s ease, transform 0.06s ease;
}

.key:active:not(:disabled) {
  background: var(--primary);
  color: var(--primary-contrast);
  transform: scale(0.95);
}

.key:disabled { opacity: 0.45; cursor: not-allowed; }
.key:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

/* Clear and backspace read as controls, not digits. */
.key-secondary {
  font-size: var(--text-sm);
  font-weight: 650;
  color: var(--text-secondary);
  background: transparent;
  border-color: transparent;
}

.key-secondary:active:not(:disabled) {
  background: var(--paper-raised);
  color: var(--text);
}

.login-submit { min-height: 52px; font-size: var(--text-lg); }

.login-version {
  margin: var(--space-4) 0 0;
  text-align: center;
  font-size: var(--text-xs);
  color: var(--text-muted);
}

@media (prefers-reduced-motion: reduce) {
  .pin-dot,
  .login[data-error="true"] .pin-dots { animation: none; }
}

/* ===========================================================================
   Phase 7 -- sized for the field
   ---------------------------------------------------------------------------
   Everything below is the density pass being walked back. That work optimised
   for fitting more on screen; the people using this are reading it outdoors, in
   a hurry, sometimes in gloves, and four jobs they can read beats six they have
   to squint at.
   =========================================================================== */

body { line-height: 1.5; }

/* Muted text is where a density-tuned palette hurts most outdoors. Darker in
   light mode, lighter in dark, so secondary information stays readable in
   sunlight instead of disappearing into the background. */
:root { --text-muted: #4B5563; }
:root[data-theme="dark"] { --text-muted: #C2CAD1; }

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) { --text-muted: #C2CAD1; }
}

/* --------------------------------------------------------------------------
   Controls
   -------------------------------------------------------------------------- */
.button {
  min-height: var(--tap);
  padding: var(--space-3) var(--space-6);
  font-size: var(--text-base);
  border-radius: var(--radius);
}

.button-compact {
  min-height: var(--tap-compact);
  padding: var(--space-2) var(--space-5);
  font-size: var(--text-sm);
}

.note-chip {
  min-height: var(--tap-compact);
  padding: 0 var(--space-4);
  font-size: var(--text-sm);
  gap: var(--space-2);
}

/* --------------------------------------------------------------------------
   Title bar
   -------------------------------------------------------------------------- */
.titlebar-inner { min-height: 60px; padding: var(--space-3) var(--space-4); }
.titlebar-title { font-size: var(--text-lg); }
.titlebar-back { height: var(--tap); font-size: var(--text-base); }
.brand-mark { font-size: var(--text-xl); }
.brand-sub { font-size: var(--text-sm); }

/* --------------------------------------------------------------------------
   Tab bar
   -------------------------------------------------------------------------- */
.tab {
  min-height: var(--tabbar-height);
  font-size: var(--text-xs);
  padding: var(--space-3) var(--space-1) var(--space-3);
}

.tab-icon-wrap { width: 70px; height: 38px; }

.tab-badge {
  min-width: 22px;
  height: 22px;
  font-size: var(--text-xs);
  right: 6px;
}

/* --------------------------------------------------------------------------
   Task cards
   --------------------------------------------------------------------------
   The one screen a driver reads at a glance while doing something else.
   -------------------------------------------------------------------------- */
.task-card {
  padding: var(--space-5);
  margin-bottom: var(--space-4);
  border-radius: var(--radius-lg);
}

.task-title { font-size: var(--text-lg); font-weight: 700; }
.task-meta { font-size: var(--text-sm); margin-bottom: var(--space-4); }
.task-actions { gap: var(--space-3); }
.task-note-line { font-size: var(--text-sm); }

.status-pill {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-xs);
}

.section-head h2 { font-size: var(--text-sm); }

.section-count {
  min-width: 24px;
  height: 24px;
  font-size: var(--text-xs);
}

.day-label { font-size: var(--text-sm); }

/* --------------------------------------------------------------------------
   Detail and history
   -------------------------------------------------------------------------- */
.detail-title { font-size: var(--text-xl); }
.detail-row { padding: var(--space-3) 0; }
.detail-row dt { font-size: var(--text-sm); flex-basis: 12rem; }
.detail-row dd { font-size: var(--text-base); }
.detail-footnote { font-size: var(--text-sm); }

.empty-title { font-size: var(--text-xl); }
.empty-sub { font-size: var(--text-base); }

.page-greeting { font-size: var(--text-xl); }

/* --------------------------------------------------------------------------
   Forms and the theme picker
   -------------------------------------------------------------------------- */
.text-input {
  font-size: var(--text-base);
  padding: var(--space-4);
  min-height: var(--tap);
}

.theme-option { min-height: 86px; font-size: var(--text-sm); }
.field-label { font-size: var(--text-sm); }

/* --------------------------------------------------------------------------
   Login
   -------------------------------------------------------------------------- */
.key { min-height: 76px; font-size: var(--text-2xl); }
.key-secondary { font-size: var(--text-base); }
.login-submit { min-height: 60px; font-size: var(--text-lg); }
.login-prompt { font-size: var(--text-xl); }
.pin-dot { width: 17px; height: 17px; }
.pin-dots { gap: var(--space-4); min-height: 34px; }
.login-message { font-size: var(--text-base); }

/* --------------------------------------------------------------------------
   The loader
   --------------------------------------------------------------------------
   Covers the gap where the server is working behind a POST and the old page
   would otherwise just sit there looking frozen. It belongs to the page being
   left, so it goes when that page does -- there is nothing to dismiss.
   -------------------------------------------------------------------------- */
.app-loader {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-5);
  background: var(--bg);
  color: var(--primary);
  animation: loader-in 160ms ease both;
}

.app-loader[hidden] { display: none; }

.app-loader svg { animation: loader-pulse 1.4s ease-in-out infinite; }

.app-loader-text {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-base);
  font-weight: 650;
  letter-spacing: 0.3px;
}

@keyframes loader-in { from { opacity: 0; } }

@keyframes loader-pulse {
  0%, 100% { opacity: 0.45; transform: scale(0.94); }
  50%      { opacity: 1;    transform: scale(1); }
}

@media (prefers-reduced-motion: reduce) {
  .app-loader,
  .app-loader svg { animation: none; }
}

/* ===========================================================================
   Login -- exact fit on a phone, a real card on a desktop
   ===========================================================================
   Two quite different jobs from one markup. On a phone this is a lock screen
   and should occupy the device exactly. On a desktop a full-bleed lock screen
   reads as a mobile page someone forgot to finish, so it becomes a card.
   =========================================================================== */

/* The page wrapper's padding was being added to a full-viewport child, which is
   why the screen scrolled by exactly that much on every phone. */
body.is-login .page {
  padding: 0;
  display: flex;
}

body.is-login .footer { display: none; }

.login {
  /* height, not min-height: min-height lets the content push past the viewport,
     which is the scroll we are removing. svh is the viewport with the browser
     chrome showing -- the smallest it ever gets -- so nothing is hidden behind a
     toolbar that slides in. */
  height: 100svh;
  width: 100%;
  max-width: 30rem;
  margin: 0 auto;
  padding: var(--space-5) var(--space-5) var(--space-4);
  /* A safety valve, not an expectation. Everything below is sized off viewport
     height so it fits; this only engages somewhere genuinely extreme, like a
     phone in landscape, where clipping the keypad would be worse. */
  overflow-y: auto;
}

.login-top {
  /* Absorbs whatever is left over, and gives it back first when space is
     short -- so the keypad keeps its size and the breathing room above it
     shrinks instead. */
  flex: 1;
  min-height: 0;
  gap: var(--space-3);
  padding-bottom: var(--space-4);
}

/* Keys scale with the height they actually have. A fixed size fits a Pro Max
   and overflows an SE; this fits both without a device-specific breakpoint. */
.keypad {
  gap: clamp(8px, 1.4svh, 14px);
  margin-bottom: clamp(10px, 1.8svh, 18px);
}

.key {
  min-height: clamp(52px, 9svh, 78px);
  font-size: clamp(1.35rem, 3.4svh, 1.75rem);
}

.login-submit {
  min-height: clamp(50px, 7.6svh, 62px);
  font-size: var(--text-lg);
}

/* Reserved so an error appearing does not shove the keypad down, but no larger
   than one line needs. */
.login-message { min-height: 1.7em; }

.login-version { margin-top: var(--space-3); }

/* --------------------------------------------------------------------------
   Desktop
   -------------------------------------------------------------------------- */
@media (min-width: 700px) {
  body.is-login .page {
    align-items: center;
    justify-content: center;
    padding: var(--space-7) var(--space-5);
  }

  .login {
    /* Sized by its contents now, and centred by the wrapper -- a lock screen
       stretched down a 1080px window is the thing that reads as unfinished. */
    height: auto;
    max-width: 26rem;
    margin: auto;
    padding: var(--space-8) var(--space-7) var(--space-7);
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    overflow: visible;
  }

  .login-top {
    flex: 0 0 auto;
    gap: var(--space-4);
    padding-bottom: var(--space-6);
  }

  /* Fixed again: there is no viewport to fit to once it is a card. */
  .keypad { gap: var(--space-3); margin-bottom: var(--space-4); }
  .key { min-height: 62px; font-size: var(--text-xl); }
  .login-submit { min-height: 54px; }

  /* On a light card the keys need an edge; against the page background the
     border alone was enough. */
  .key { background: var(--bg); }
}

/* ===========================================================================
   Phase 8
   =========================================================================== */

/* --------------------------------------------------------------------------
   Dark mode: the title bar
   --------------------------------------------------------------------------
   The bar took var(--primary), which in dark mode is a pale blue -- so a dark
   app had a bright band across the top of every screen, which is the opposite
   of what dark mode is for. It gets its own tokens now: the brand blue in
   light, a dark surface in dark.
   -------------------------------------------------------------------------- */
:root {
  --titlebar-bg: var(--primary);
  --titlebar-fg: var(--primary-contrast);
  --titlebar-overlay: rgba(255, 255, 255, 0.22);

  /* White on the dark-mode green sits around 2.6:1 -- unreadable in sunlight,
     and not much better indoors. Dark text on it clears 8:1. */
  --success-contrast: #ffffff;
}

:root[data-theme="dark"] {
  --titlebar-bg: #1B1B1B;
  --titlebar-fg: #EDEDED;
  --titlebar-overlay: rgba(255, 255, 255, 0.12);
  --success-contrast: #0B1F0C;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --titlebar-bg: #1B1B1B;
    --titlebar-fg: #EDEDED;
    --titlebar-overlay: rgba(255, 255, 255, 0.12);
    --success-contrast: #0B1F0C;
  }
}

.titlebar {
  background: var(--titlebar-bg);
  color: var(--titlebar-fg);
  /* A border rather than a shadow in dark mode, where a shadow against near
     black is invisible and the bar loses its edge entirely. */
  border-bottom: 1px solid var(--border);
}

.titlebar-back:active,
.titlebar-back.is-pressed,
.titlebar-nav-link:hover { background: var(--titlebar-overlay); }
.titlebar-nav-link.is-active { background: var(--titlebar-overlay); }
.nav-count { background: var(--titlebar-overlay); }

.button-success { color: var(--success-contrast); }

/* The badge ring is the bar colour behind it, not always white paper. */
.tab-badge { box-shadow: 0 0 0 2px var(--paper); }

/* --------------------------------------------------------------------------
   Title bar: one height everywhere, title centred
   --------------------------------------------------------------------------
   Sub-pages were taller than the rest, because a 56px back button plus padding
   made a bar the brand pages never grew to. One height now, set here, with the
   back button sized to live inside it.
   -------------------------------------------------------------------------- */
:root { --titlebar-height: 68px; }

.titlebar-inner {
  position: relative;
  min-height: var(--titlebar-height);
  padding: var(--space-2) var(--space-4);
  gap: var(--space-2);
}

.titlebar-back {
  height: 48px;
  padding: 0 var(--space-3) 0 var(--space-2);
  gap: 2px;
  /* Above the centred title, so the tap target is never covered by it. */
  position: relative;
  z-index: 1;
}

/* Centred on the bar, not laid out after the back button -- which is what made
   the two read as a single run-on control. */
.titlebar-title.is-centered {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  max-width: 55%;
  text-align: center;
  font-size: var(--text-lg);
  font-weight: 650;
  pointer-events: none;
}

@media (min-width: 700px) {
  /* With inline nav on the right there is no symmetry to centre against, so the
     title goes back to sitting after the back button. */
  .titlebar-title.is-centered {
    position: static;
    transform: none;
    max-width: none;
    text-align: left;
  }
}

/* --------------------------------------------------------------------------
   Refresh
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   Account
   -------------------------------------------------------------------------- */
.page-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  flex-wrap: wrap;
}

.page-head form { margin: 0; }

/* --------------------------------------------------------------------------
   Press feedback
   --------------------------------------------------------------------------
   iOS does not give :active to a touch on most elements, and the tap highlight
   was already turned off -- so an iPhone showed nothing at all when a key was
   pressed. A class from touchstart covers both platforms identically.
   -------------------------------------------------------------------------- */
.key.is-pressed {
  background: var(--primary);
  color: var(--primary-contrast);
  transform: scale(0.95);
}

.button.is-pressed:not(:disabled),
.note-chip.is-pressed,
.theme-option.is-pressed,
.icon-action.is-pressed { transform: scale(0.96); filter: brightness(0.94); }

.tab.is-pressed .tab-icon-wrap::before { opacity: 0.28; transform: scaleX(1); }

@media (prefers-reduced-motion: reduce) {
  .key.is-pressed,
  .button.is-pressed:not(:disabled),
  .note-chip.is-pressed,
  .theme-option.is-pressed,
  .icon-action.is-pressed { transform: none; }
}

/* --------------------------------------------------------------------------
   Login: stop the last of the scroll
   --------------------------------------------------------------------------
   body carried min-height: 100vh. On a phone vh is the LARGE viewport -- the
   height with the address bar hidden -- so the body was taller than what you
   can actually see, by exactly the height of that bar. That was the remaining
   scroll, and no amount of sizing inside .login could have fixed it.
   -------------------------------------------------------------------------- */
body.is-login {
  height: 100svh;
  min-height: 0;
  overflow: hidden;
}

/* On a phone with room to spare, the card treatment from desktop. On a short
   one it stays full-bleed, because a card costs margins exactly where the
   keypad needs the height. */
@media (max-width: 699px) and (min-height: 760px) {
  body.is-login .page { padding: var(--space-4); align-items: center; }

  .login {
    height: auto;
    max-height: 100%;
    background: var(--paper);
    border: 1px solid var(--border);
    border-radius: var(--radius-lg);
    box-shadow: var(--shadow-lg);
    padding: var(--space-6) var(--space-5) var(--space-5);
  }

  .key { background: var(--bg); }
}

/* ===========================================================================
   Dark mode corrections
   ---------------------------------------------------------------------------
   Found by walking every colour pair rather than by looking at screens: three
   of these are only obviously wrong once you are outdoors at night, which is
   exactly when nobody is going to file a bug about it.
   =========================================================================== */

/* Status pills carried Theme.js values unchanged into dark mode. Several of
   those are dark greens and purples chosen to sit on white -- on a #1E1E1E card
   the pill and the card are nearly the same weight, so it stops reading as a
   pill at all. Same hues, lifted enough to separate, with text flipped where
   the background became light. */
:root[data-theme="dark"] {
  --status-in-progress: #4caf50;
  --status-in-progress-fg: #06230a;
  --status-reactivated: #4dd0e1;
  --status-reactivated-fg: #06282e;
  --status-needs-approval: #ffa726;
  --status-needs-approval-fg: #2b1600;
  --status-completed: #ba68c8;
  --status-completed-fg: #2a0733;
  --status-default: #3a3a3a;
  --status-default-fg: #D4D4D4;

  /* White on the dark-mode red is about 3.2:1. Only ever seen on hover, so
     desktop only -- but it costs nothing to be right. */
  --error-contrast: #2a0505;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --status-in-progress: #4caf50;
    --status-in-progress-fg: #06230a;
    --status-reactivated: #4dd0e1;
    --status-reactivated-fg: #06282e;
    --status-needs-approval: #ffa726;
    --status-needs-approval-fg: #2b1600;
    --status-completed: #ba68c8;
    --status-completed-fg: #2a0733;
    --status-default: #3a3a3a;
    --status-default-fg: #D4D4D4;
    --error-contrast: #2a0505;
  }
}

:root { --error-contrast: #ffffff; }

.button-danger-outline:hover { color: var(--error-contrast); }

/* The busy spinner was drawn in white regardless of theme. On a dark-mode
   primary button -- pale blue with dark text -- a white ring on pale blue is
   barely visible, so the button looked disabled rather than working. Each
   variant now spins in its own contrast colour. */
.button-primary.is-busy::after {
  border-color: color-mix(in srgb, var(--primary-contrast) 35%, transparent);
  border-top-color: var(--primary-contrast);
}

.button-success.is-busy::after {
  border-color: color-mix(in srgb, var(--success-contrast) 35%, transparent);
  border-top-color: var(--success-contrast);
}

.button-quiet.is-busy::after,
.button-outline.is-busy::after,
.button-danger-outline.is-busy::after {
  border-color: color-mix(in srgb, var(--text) 25%, transparent);
  border-top-color: var(--primary);
}

/* The camera preview keeps a black backdrop in both themes -- that is the
   colour of an unstarted video, not a themed surface. */

/* ===========================================================================
   Phase 9
   =========================================================================== */

/* --------------------------------------------------------------------------
   Title bar action
   --------------------------------------------------------------------------
   Refresh lives here now. A page-level action belongs in the page's bar rather
   than inside the list it acts on, and it keeps the button out of the fragment
   that gets swapped when it runs.
   -------------------------------------------------------------------------- */
.titlebar-action {
  /* margin-left auto pushes it right; the nav below sits beside it rather than
     claiming the whole remaining space for itself. */
  margin-left: auto;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  height: 44px;
  padding: 0 var(--space-3);
  border: none;
  border-radius: var(--radius-pill);
  background: transparent;
  color: inherit;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 650;
  cursor: pointer;
  /* Above the absolutely-centred title, so the tap target is never covered. */
  position: relative;
  z-index: 1;
}

.titlebar-action:active,
.titlebar-action.is-pressed { background: var(--titlebar-overlay); }
.titlebar-action:focus-visible { outline: 3px solid currentColor; outline-offset: -2px; }
.titlebar-action.is-spinning svg { animation: spin 0.8s linear infinite; }

/* Both live at the right end together, rather than each grabbing the slack. */
.titlebar-nav { margin-left: var(--space-2); }

/* The centred sub-page title must not run under a right-hand action. */
.titlebar-title.is-centered { max-width: 50%; }

/* --------------------------------------------------------------------------
   Login: air above the mark
   --------------------------------------------------------------------------
   Scales with the viewport like everything else on this screen, so a tall phone
   gets real breathing room and a short one still fits the keypad.
   -------------------------------------------------------------------------- */
.login { padding-top: clamp(28px, 8svh, 76px); }

@media (min-width: 700px) {
  /* The card sizes to its contents, so this is a fixed value again -- there is
     no viewport left to scale against. */
  .login { padding-top: var(--space-8); }
}

/* ===========================================================================
   Phase 10 -- alignment, and three bugs found by walking the states
   =========================================================================== */

/* --------------------------------------------------------------------------
   Title bar alignment
   --------------------------------------------------------------------------
   Moving Refresh into the bar broke this: the nav's margin-left: auto was
   replaced with a fixed gap, so it relied on Refresh being there to push it
   right. On History and Account there is no Refresh, and the nav collapsed
   against the brand.

   The push now comes from the left-hand element instead, which is present on
   every page -- so nothing downstream has to know what else is in the bar.
   -------------------------------------------------------------------------- */
.brand,
.titlebar-back { margin-right: auto; }

.titlebar-action { margin-left: 0; }
.titlebar-nav { margin-left: 0; gap: var(--space-1); }

/* --------------------------------------------------------------------------
   Refresh
   -------------------------------------------------------------------------- */
.titlebar-action {
  /* An outline, so it reads as a button rather than a label that happens to be
     tappable. currentColor keeps it correct against either title bar. */
  border: 1px solid currentColor;
  opacity: 0.92;
}

.titlebar-action:active,
.titlebar-action.is-pressed { opacity: 1; }

/* --------------------------------------------------------------------------
   Offline bar
   --------------------------------------------------------------------------
   It was sticky at top: 0 with a higher z-index than the title bar, which is
   also sticky at top: 0 -- so the moment you scrolled while offline it covered
   the header entirely. Only reachable by being offline AND scrolling, which is
   why it survived.

   Above the tab bar instead: always visible, nothing to stack against, and in
   easier reach of the eye than the very top of the screen.
   -------------------------------------------------------------------------- */
.offline-bar {
  position: fixed;
  top: auto;
  left: 0;
  right: 0;
  bottom: env(safe-area-inset-bottom);
  z-index: 30;
  border-top: 1px solid rgba(0, 0, 0, 0.15);
}

body.has-tabbar .offline-bar {
  bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
}

/* --------------------------------------------------------------------------
   Keypad
   --------------------------------------------------------------------------
   Clear and backspace are styled as controls rather than digits, so flashing
   them full primary on press contradicted that -- they looked like the most
   important keys on the pad for as long as a finger was down.
   -------------------------------------------------------------------------- */
.key-secondary.is-pressed,
.key-secondary:active:not(:disabled) {
  background: var(--paper-raised);
  color: var(--text);
}

/* --------------------------------------------------------------------------
   Bar controls
   --------------------------------------------------------------------------
   Back gets the same outline as Refresh. The usual reason not to -- an outlined
   Back makes navigation look like an action, and two outlined pills clutter a
   bar -- does not apply here: Back only renders on sub-pages and Refresh only
   on the dashboard, so they are never in the same bar. With no clutter to
   avoid, the clearer target wins.

   Both at 48px rather than the 44px floor, for the same reason everything else
   on this app is oversized.
   -------------------------------------------------------------------------- */
.titlebar-back,
.titlebar-action {
  height: 48px;
  border: 1px solid currentColor;
  border-radius: var(--radius-pill);
  opacity: 0.92;
}

.titlebar-back:active,
.titlebar-back.is-pressed,
.titlebar-action:active,
.titlebar-action.is-pressed {
  opacity: 1;
  background: var(--titlebar-overlay);
}

/* ===========================================================================
   CSL pickup

   Sized for the actual conditions: a driver outdoors, at arm's length, often in
   gloves. Every tap target here is at least --tap, and nothing depends on
   colour alone to carry meaning.
   =========================================================================== */

/* --------------------------------------------------------------------------
   Step rail

   Four named boxes rather than a progress bar. A bar says how far along you
   are; what a driver needs is which of four places they are standing in.
   -------------------------------------------------------------------------- */








/* --------------------------------------------------------------------------
   Job facts

   A definition list, because that is what it is. Two columns on a wide phone,
   stacked below 380px where the labels would otherwise squeeze the values into
   two characters a line.
   -------------------------------------------------------------------------- */
.info-grid {
  margin: 0 0 var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  overflow: hidden;
}

.info-row {
  display: grid;
  grid-template-columns: 40% 1fr;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border);
}

.info-row:last-child { border-bottom: none; }
.info-row:nth-child(odd) { background: var(--paper-raised); }

.info-row dt {
  margin: 0;
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-weight: 600;
}

.info-row dd {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  overflow-wrap: anywhere;
}

.info-link {
  display: inline-block;
  margin-left: var(--space-2);
  color: var(--primary);
  font-weight: 700;
  /* A phone number is a tap target, so it gets tap-target height. */
  min-height: var(--tap-compact);
  line-height: var(--tap-compact);
}

@media (max-width: 380px) {
  .info-row { grid-template-columns: 1fr; gap: var(--space-1); }
}

.tank-chip {
  display: inline-block;
  margin: 0 var(--space-1) var(--space-1) 0;
  padding: var(--space-1) var(--space-3);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--info) 16%, transparent);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Scanning
   -------------------------------------------------------------------------- */
/* The scan block is a container, not a card.
 *
 * It used to carry a border, a background and reserved space for a status line.
 * Once Scan and "type it instead" moved to the action bar, all it contained on
 * arrival was an empty status and a hidden typing box -- so it rendered as a
 * blank bordered rectangle sitting under the equipment card, which is exactly
 * what it looked like: a box with nothing in it. */
.scan-block { margin: 0 0 var(--space-5); }

/* No text, no space. min-height here was what kept the empty box tall. */
.scan-status:empty { display: none; }

.handoff-block {
  margin: 0 0 var(--space-5);
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
}

/* The network check on the handoff screen. Hidden until the driver taps "I'm
   at the plant"; after that the border says what the words say, so the answer
   reads across a cab. */
.handoff-network { border-width: 2px; }
.handoff-network[data-state="checking"] { border-color: var(--info); }
.handoff-network[data-state="online"] { border-color: var(--success); }
.handoff-network[data-state="offline"] { border-color: var(--warning); }

.handoff-status {
  margin: 0;
  font-weight: 600;
}

.handoff-network[data-state="online"] .handoff-status { color: var(--success); }
.handoff-network[data-state="offline"] .handoff-status { color: var(--warning); }

.handoff-offline { margin-top: var(--space-3); }

.handoff-block .handoff-question {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
}

/* "Show me how" for a blocked permission: a link, because the bar already has
   the button that matters (Retry network). */
.handoff-howto {
  display: inline-block;
  margin: 0 0 var(--space-3);
  padding: var(--space-2) 0;
  border: none;
  background: none;
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--primary);
  text-decoration: underline;
  cursor: pointer;
}

/* The way past what the check cannot vouch for -- a plant certificate the phone
   has never accepted, a browser setting in the way. Quiet on purpose: the
   buttons are the answer almost every time. */
.handoff-anyway {
  display: inline-block;
  margin-top: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-decoration: underline;
}

/* Small print for whoever is helping over the phone: which address was tried,
   and why it failed. */
.handoff-block .handoff-detail {
  margin: var(--space-3) 0 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow-wrap: anywhere;
}

/* The screen title, and the optional "what to do" beside it. */
.detail-title-row {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
}

.detail-title-row .detail-title {
  flex: 1 1 auto;
  min-width: 0;
}

.title-help {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin: -6px -10px 0 0;
  padding: 0;
  border: none;
  border-radius: 50%;
  background: transparent;
  color: var(--text-secondary);
  cursor: pointer;
}

.title-help:active { background: var(--paper-raised); }
.title-help:focus-visible { outline: 3px solid var(--primary); outline-offset: -2px; }

/* Retake is the photo itself; this says so, once there is a photo. */
.ticket-retake-hint {
  margin: 0 0 var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  text-align: center;
}

/* --------------------------------------------------------------------------
   More -- the bar's second button, and the sheet it opens
   -------------------------------------------------------------------------- */

/* Square, icon-only, and never stretched: the screen's one action takes the
   rest of the row. Three classes deep on purpose -- `.action-bar .button`
   further down gives every bar button `flex: 1 1 8rem`, and at equal
   specificity the later rule would stretch this one to half the bar. */
.action-bar .button.button-more {
  position: relative;
  flex: 0 0 var(--tap);
  width: var(--tap);
  min-width: var(--tap);
  padding: 0;
}

/* A manager's note exists. Small, but on the button a driver taps anyway. */
.more-dot {
  position: absolute;
  top: 10px;
  right: 10px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: var(--warning);
  box-shadow: 0 0 0 2px var(--paper);
}

.more-dot.is-inline {
  position: static;
  flex: 0 0 auto;
  box-shadow: none;
}

.sheet-menu {
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
}

.sheet-menu li + li { border-top: 1px solid var(--border); }

.sheet-menu-item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-3) 0;
  border: none;
  background: none;
  color: inherit;
  font: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
}

.sheet-menu-item svg { flex: 0 0 auto; color: var(--text-secondary); }
.sheet-menu-item:active:not(:disabled) { background: var(--paper-raised); }
.sheet-menu-item:disabled { cursor: default; opacity: 0.5; }

.sheet-menu-label { flex: 1 1 auto; }

.sheet-menu-sub {
  display: block;
  margin-top: 2px;
  font-size: var(--text-sm);
  font-weight: 400;
  color: var(--text-secondary);
}

/* A sheet's own buttons, stacked: the action first, the way out under it. */
.sheet-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.sheet-steps {
  margin: 0 0 var(--space-5);
  padding-left: var(--space-5);
  line-height: 1.5;
}

.sheet-steps li { margin-bottom: var(--space-2); }

/* Which run this is, above the step indicator. */
.run-notice {
  margin: 0 0 var(--space-3);
  font-size: var(--text-base);
  font-weight: 700;
  color: var(--primary);
}

.scan-heading {
  margin: 0 0 var(--space-2);
  font-size: var(--text-base);
  font-weight: 700;
}

.scan-status {
  margin-bottom: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-secondary);
  /* Reserve the second line so the block does not jump as messages change
     length -- which, mid-scan, reads as the camera glitching. */
  min-height: 2.6em;
}

.scan-block.is-confirmed .scan-status {
  color: var(--success);
  font-weight: 700;
}



.scan-hint {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* --------------------------------------------------------------------------
   Handoff to the plant
   -------------------------------------------------------------------------- */
.handoff-block p { margin: 0 0 var(--space-3); }



/* --------------------------------------------------------------------------
   Earlier runs

   Present but visibly finished. On run three the question a driver actually has
   is "what did I put in last time", and the answer should not need a tap.
   -------------------------------------------------------------------------- */
.run-history { margin: var(--space-6) 0 0; }

.run-row {
  padding: var(--space-3) var(--space-4);
  margin-bottom: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-sm);
  background: var(--paper-raised);
}

.run-row.is-past { opacity: 0.68; }

/* The record is dimmed; the way into it is not. A greyed-out control that still
   works is the kind of thing people stop trying. */
.run-row.is-past .button { opacity: 1; }

.run-row-head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-bottom: var(--space-1);
}

.run-row-number { font-weight: 700; }

.run-row-condition {
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--border);
  font-size: var(--text-xs);
  font-weight: 600;
}

.run-row-detail {
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-secondary);
}

.run-row-flag {
  color: var(--warning);
  font-weight: 700;
}

/* --------------------------------------------------------------------------
   Finish
   -------------------------------------------------------------------------- */
.finish-block {
  margin-top: var(--space-6);
  padding-top: var(--space-4);
  border-top: 1px solid var(--border);
}

.finish-block p { margin: 0 0 var(--space-3); }

/* ===========================================================================
   Capture dialogs -- scanner and camera

   Deliberately the same shapes as the plant app's BarcodeScanner.jsx and
   PhotoCamera.jsx. A driver who uses the tablet indoors and a phone at a
   railcar should recognise both immediately.

   Everything here lives in this stylesheet rather than inline, because the CSP
   is style-src 'self' with no 'unsafe-inline' -- an inline keyframe or style
   attribute is silently dropped, which is how the status colours failed to
   appear the first time this app was built.
   =========================================================================== */

.capture-dialog {
  border: none;
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  padding: var(--space-4);
  width: calc(100% - var(--space-4) * 2);
  max-width: 34rem;
  margin: auto;
  box-shadow: var(--shadow-lg);
}

.capture-dialog::backdrop { background: rgba(0, 0, 0, 0.72); }

.capture-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-3);
}

.capture-title {
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 700;
}

.capture-icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: var(--tap-compact);
  height: var(--tap-compact);
  flex: none;
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text);
  cursor: pointer;
}

.capture-icon:active { background: var(--paper-raised); }

/* --------------------------------------------------------------------------
   The stage

   Fixed 4:3 with object-fit: cover, so it never changes height. The first
   version let the <video> and the captured <img> size themselves, and the box
   jumped every time you took a photo -- which read as the page breaking.

   The 4:3 here is load-bearing beyond looks: the scanner's crop maths assumes
   this exact aspect when working out which part of the frame the viewfinder is
   actually over. Change one and the other has to change with it.
   -------------------------------------------------------------------------- */
.capture-stage {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  overflow: hidden;
  border-radius: var(--radius-sm);
  background: #000;
}

.capture-stage.is-tappable { cursor: pointer; }

.capture-video {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* The hint that tells someone the picture itself is the shutter. Shown once per
   open, then out of the way -- a permanent instruction becomes furniture. */
.capture-hint {
  position: absolute;
  top: 50%;
  left: 50%;
  margin: 0;
  padding: var(--space-2) var(--space-3);
  transform: translate(-50%, -50%);
  color: #fff;
  font-size: clamp(18px, 3.2vw, 28px);
  white-space: nowrap;
  text-shadow: 0 0 6px rgba(0, 0, 0, 0.8);
  pointer-events: none;
  opacity: 0;
}

.capture-hint.is-shown { animation: hint-scale-fade 3s ease-out forwards; }

@keyframes hint-scale-fade {
  0%   { transform: translate(-50%, -50%) scale(0.9); opacity: 0; }
  25%  { transform: translate(-50%, -50%) scale(1);   opacity: 1; }
  70%  { transform: translate(-50%, -50%) scale(1);   opacity: 1; }
  100% { transform: translate(-50%, -50%) scale(1);   opacity: 0; }
}

/* --------------------------------------------------------------------------
   Viewfinder

   Not decoration. Only what falls inside this box is cropped out and handed to
   the decoder, upscaled toward 1280px -- which is the single biggest reason the
   plant app reads codes the first version of this one could not. The 80%/40%
   here and the vfW/vfH constants in app.js are the same numbers and have to
   stay that way.
   -------------------------------------------------------------------------- */
.scan-viewfinder {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 80%;
  height: 40%;
  transform: translate(-50%, -50%);
  border: 8px solid #00e676;
  border-radius: 10px;
  box-sizing: border-box;
  overflow: hidden;
  pointer-events: none;
  animation: viewfinder-pulse 2s infinite;
}

@keyframes viewfinder-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(0, 230, 118, 0.7); }
  70%  { box-shadow: 0 0 0 25px rgba(0, 230, 118, 0); }
  100% { box-shadow: 0 0 0 0 rgba(0, 230, 118, 0); }
}

/* Runs only while frames are actually being decoded, so it means "working on
   it" rather than "camera is on". */
.scan-laser {
  position: absolute;
  left: 0;
  right: 0;
  height: 4px;
  background: #ff1744;
  box-shadow: 0 0 12px 4px rgba(255, 23, 68, 0.7);
  pointer-events: none;
  opacity: 0;
}

.scan-laser.is-active {
  opacity: 1;
  animation: laser-sweep 1.2s ease-in-out infinite;
}

@keyframes laser-sweep {
  0%   { top: 0%; }
  50%  { top: calc(100% - 4px); }
  100% { top: 0%; }
}

/* Reduced motion: keep the viewfinder, drop the animation. Someone who asked
   for less movement should not get a pulsing box and a sweeping bar. */
@media (prefers-reduced-motion: reduce) {
  .scan-viewfinder { animation: none; }
  .scan-laser.is-active { animation: none; top: 50%; }
  .capture-hint.is-shown { animation: none; opacity: 1; }
}

.capture-status {
  margin: var(--space-3) 0;
  font-size: var(--text-sm);
  color: var(--text-secondary);
  /* Reserved so the dialog does not resize as messages change length. */
  min-height: 2.6em;
}

.capture-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
}

.capture-actions .button { flex: 1 1 auto; }

/* --------------------------------------------------------------------------
   The weight-ticket result on step 2
   -------------------------------------------------------------------------- */
.ticket-thumb {
  position: relative;
  width: 100%;
  aspect-ratio: 4 / 3;
  margin-bottom: var(--space-3);
  border: 1px dashed var(--border);
  border-radius: var(--radius-sm);
  background: var(--paper-raised);
  overflow: hidden;
}

.ticket-thumb img {
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
  background: #000;
}

.ticket-thumb-empty {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* Nothing photographed yet: a prompt, not a picture frame.

   The 4:3 box is right once there is a picture in it and absurd before -- most
   of a phone screen reserved to say "No photo yet", pushing the weight field
   and the buttons out of sight. It stays a comfortable tap target and grows to
   the photo when one arrives. */
.ticket-thumb.is-empty {
  aspect-ratio: auto;
}

.ticket-thumb.is-empty .ticket-thumb-empty {
  position: static;
  min-height: var(--tap);
  padding: var(--space-3);
}

/* --------------------------------------------------------------------------
   Blocking errors

   The save indicator beside the button is for "saved" and "saving". Anything
   that stops the driver moving on belongs here, where it cannot be missed.
   -------------------------------------------------------------------------- */
.module-error {
  margin: 0 0 var(--space-3);
  padding: var(--space-3);
  border-left: 4px solid var(--error);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--error) 12%, transparent);
  color: var(--text);
  font-size: var(--text-sm);
  font-weight: 600;
}

/* ===========================================================================
   CSL pickup — one screen at a time

   Replaces the single crowded page. Every screen asks one thing, and answering
   moves on, because this is used one-handed outdoors on a phone.
   =========================================================================== */

/* --------------------------------------------------------------------------
   Step indicator

   Mirrors StepIndicator.jsx: one pulsing control on a phone, the full row of
   circles on a wide screen. Seven circles across a 360px phone would be
   unreadable, which is why the plant app collapses it too.
   -------------------------------------------------------------------------- */
.stepper { margin: 0 0 var(--space-5); }

.stepper-mobile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2px;
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-2);
  border: none;
  background: transparent;
  color: inherit;
  font-family: inherit;
  cursor: pointer;
}

.stepper-dot {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  flex: none;
  border-radius: 50%;
  background: var(--primary);
  color: var(--primary-contrast);
  font-weight: 700;
  font-size: var(--text-sm);
}

.stepper-mobile .stepper-dot { animation: step-pulse 1.8s ease-in-out infinite; }

/* Derived from --primary rather than the literal light-mode blue it used to
   hardcode: dark mode redefines that token to #90CAF9, so the ring pulsed a
   colour the dot no longer was. */
@keyframes step-pulse {
  0%   { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 40%, transparent); }
  70%  { box-shadow: 0 0 0 8px color-mix(in srgb, var(--primary) 0%, transparent); }
  100% { box-shadow: 0 0 0 0 color-mix(in srgb, var(--primary) 0%, transparent); }
}

@media (prefers-reduced-motion: reduce) {
  .stepper-mobile .stepper-dot { animation: none; }
}

.stepper-name { font-size: var(--text-sm); font-weight: 600; }

.stepper-hint {
  font-size: var(--text-xs);
  color: var(--primary);
  font-weight: 500;
}

/* The row is the wide-screen form. Below 700px the single control above is the
   whole indicator. */
.stepper-row { display: none; }

@media (min-width: 700px) {
  .stepper-mobile { display: none; }

  .stepper-row {
    display: flex;
    justify-content: center;
    flex-wrap: wrap;
    gap: var(--space-4);
    margin: 0;
    padding: 0;
    list-style: none;
  }

  .stepper-step a,
  .stepper-step span {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--space-1);
    max-width: 88px;
    text-align: center;
    text-decoration: none;
    color: inherit;
  }

  /* Ahead of where the driver has reached: present, so the shape of the job is
     visible, but plainly not available yet. */
  .stepper-step .stepper-dot { background: var(--border); color: var(--text-secondary); }
  .stepper-step { opacity: 0.5; }

  .stepper-step.is-done,
  .stepper-step.is-active { opacity: 1; }

  .stepper-step.is-done .stepper-dot { background: var(--success); color: #fff; }
  .stepper-step.is-active .stepper-dot {
    background: var(--primary);
    color: var(--primary-contrast);
    animation: step-pulse 1.8s ease-in-out infinite;
  }

  .stepper-step .stepper-name { font-size: var(--text-xs); font-weight: 500; }
  .stepper-step.is-active .stepper-name { font-weight: 700; }
}

/* The phone menu of screens. */
.stepper-menu {
  margin: 0 0 var(--space-4);
  padding: 0;
  list-style: none;
}

.stepper-menu a,
.stepper-menu .is-locked {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: var(--tap);
  padding: var(--space-2) 0;
  color: inherit;
  text-decoration: none;
  font-weight: 600;
}

.stepper-menu .is-locked { opacity: 0.45; }
.stepper-menu .is-locked .stepper-dot { background: var(--border); color: var(--text-secondary); }
.stepper-menu .stepper-dot.is-done { background: var(--success); color: #fff; }
.stepper-menu [aria-current="step"] { color: var(--primary); }

/* --------------------------------------------------------------------------
   Reviewing an earlier screen

   Stated outright. A driver who does not realise they are looking at a finished
   screen will try to answer it, get nothing, and conclude the app is broken.
   -------------------------------------------------------------------------- */
.review-banner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin: 0 0 var(--space-5);
  padding: var(--space-3) var(--space-4);
  border-left: 4px solid var(--info);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--info) 12%, transparent);
}

.review-banner p { margin: 0; flex: 1 1 12rem; font-size: var(--text-sm); }

/* What was answered, when looking back. */
.screen-answer {
  margin: var(--space-4) 0 0;
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-sm);
  background: var(--paper-raised);
  font-size: var(--text-base);
  font-weight: 600;
}



/* --------------------------------------------------------------------------
   Equipment card

   The plant app's verification card (EquipmentVerification.jsx, single-asset
   mode): 160x160, icon over a bold label, grey until verified and green after.
   The colour is the point -- a driver checking whether they already scanned
   something should get the answer across a cab, not by reading.
   -------------------------------------------------------------------------- */
.equip-wrap {
  display: flex;
  flex-direction: column;
  align-items: center;
  margin: 0 0 var(--space-5);
}

.equip-verified {
  margin: 0 0 var(--space-3);
  color: var(--success);
  font-weight: 700;
  text-align: center;
}

.equip-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  width: 160px;
  height: 160px;
  padding: var(--space-3);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text-secondary);
  box-shadow: var(--shadow);
  text-align: center;
  transition: all 0.3s ease-in-out;
}

.equip-card.is-verified {
  border-color: var(--success);
  background: color-mix(in srgb, var(--success) 18%, var(--paper));
  color: var(--text);
}

.equip-icon { display: inline-flex; }

.equip-label {
  font-size: var(--text-sm);
  font-weight: 700;
  overflow-wrap: anywhere;
}

/* --------------------------------------------------------------------------
   One-tap choices

   The tap is the answer. No radio to select and then confirm: on a screen that
   asks one question, that second step is the commonest place to get stuck.
   -------------------------------------------------------------------------- */
.choice-list {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-5) 0 0;
}

.choice {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  width: 100%;
  min-height: var(--tap);
  padding: var(--space-3) var(--space-4);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: all 0.15s ease-in-out;
}

.choice:active,
.choice.is-pressed { background: var(--paper-raised); }

.choice.is-chosen {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 14%, transparent);
}

.choice[disabled] { cursor: default; }
.choice[disabled]:not(.is-chosen) { opacity: 0.45; }

.choice-label { flex: 1 1 auto; }

.choice-flag {
  flex: none;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--warning) 22%, transparent);
  color: var(--text);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Interstitial

   The "go somewhere else now" screens. Deliberately sparse: there is nothing to
   answer, and the only thing that matters is that the driver reads one line.
   -------------------------------------------------------------------------- */
.interstitial {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-4);
  margin: var(--space-6) 0;
  padding: var(--space-6) var(--space-4);
  border-radius: var(--radius);
  background: var(--paper-raised);
  color: var(--text-secondary);
  text-align: center;
}

.interstitial p {
  margin: 0;
  font-size: var(--text-base);
  font-weight: 600;
  color: var(--text);
}

/* ===========================================================================
   Action bar

   Each screen's actions, fixed in thumb reach, the way StickyActionBar.jsx does
   it in the plant app. It replaces the Tasks / History / Account tabs for the
   length of a run -- see $hideTabs in layout.php for why.

   Labelled buttons, not the icon-only ones the plant app collapses to on a
   phone: there is no glyph for "I'm at the railcar", and a driver guessing at
   icons is worse than one reading two words.
   =========================================================================== */
.action-bar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  background: var(--paper);
  box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.1);
  padding: var(--space-3) var(--space-4);
  padding-bottom: calc(var(--space-3) + env(safe-area-inset-bottom));
}

.action-bar-inner {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  max-width: 46rem;
  margin: 0 auto;
}

/* Every button shares the row evenly, so a single action fills the width and
   becomes impossible to miss or mis-tap. */
.action-bar .button {
  flex: 1 1 8rem;
  min-height: var(--tap);
  justify-content: center;
}

/* Room for the bar, so the end of a long screen is not sitting underneath it.
 *
 * --actionbar-height is measured and published by app.js, because the bar wraps:
 * three buttons on a narrow phone take two rows, and a fixed guess based on one
 * row left the bottom of the weigh-ticket screen behind it. The fallback here
 * covers the moment before script runs. */
body.has-actionbar .page {
  padding-bottom: calc(var(--actionbar-height, var(--tap)) + var(--space-6));
}

body.has-actionbar .offline-bar {
  bottom: calc(var(--actionbar-height, var(--tap)) + env(safe-area-inset-bottom));
}

/* The finished screen has an ordinary button in the flow rather than a fixed
   bar -- there is no action left to keep in thumb reach. */
.screen-done-actions { margin-top: var(--space-6); }

/* --------------------------------------------------------------------------
   Manager notes, after the first screen

   Present on every screen but out of the way. A <details> rather than a dialog:
   the note is short, and this needs no script.
   -------------------------------------------------------------------------- */
/* --------------------------------------------------------------------------
   [hidden] has to be restored

   Any rule like `.capture-video { display: block }` is a class selector, so it
   outranks the browser's own `[hidden] { display: none }` and an element set
   `.hidden` stays on screen. That is not hypothetical here: it is why a captured
   photo never appeared -- the stopped black video kept its place and pushed the
   image out of the stage. The same trap has explicit overrides further up this
   file for .offline-bar and .app-loader.

   Anything given a display value by class and toggled with .hidden in script
   needs a line here. It has caught four things so far, in four separate places,
   which says the mistake is easy rather than that the list is finished: the
   symptom is always "the element I hid is still on screen".

   .button is deliberately unqualified. It used to read .action-bar .button,
   which rescued the buttons in the run's action bar and left every other hidden
   button in the app showing -- .button carries display: inline-flex, so the
   rule is needed wherever one is toggled, not only where it was first noticed.
   -------------------------------------------------------------------------- */
.capture-video[hidden],
.camera-preview[hidden],
.ticket-thumb img[hidden],
.ticket-thumb-empty[hidden],
.ticket-retake-hint[hidden],
.capture-icon[hidden],
.handoff-network[hidden],
.handoff-offline[hidden],
.handoff-howto[hidden],
.handoff-anyway[hidden],
.handoff-detail[hidden],
.more-dot[hidden],
.button[hidden] { display: none; }

/* --------------------------------------------------------------------------
   Stepper: saying where the active step is

   While reviewing, the indicator has to answer two questions -- which screen is
   on show, and where the driver actually is. Naming only the first strands
   somebody on a screen that will not respond.
   -------------------------------------------------------------------------- */
.stepper-dot.is-reviewing { background: var(--info); }

.stepper-return {
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--primary);
}

@media (min-width: 700px) {
  /* The current step keeps its pulse even while another is being viewed, so the
     way forward is always visible. Mirrors the plant app. */
  .stepper-step.is-current { opacity: 1; }

  .stepper-step.is-current .stepper-dot {
    background: var(--primary);
    color: var(--primary-contrast);
    animation: step-pulse 1.8s ease-in-out infinite;
  }

  /* Viewing a finished step: mark it, but do not let it outshine the current
     one -- the driver needs to know where to get back to. */
  .stepper-step.is-active:not(.is-current) .stepper-dot {
    background: var(--info);
    color: #fff;
    animation: none;
  }
}

/* --------------------------------------------------------------------------
   Scanning unavailable

   Loud on purpose. Quietly hiding the Scan button reads as the feature having
   been removed rather than as the browser not having it -- Chrome on Windows
   has no BarcodeDetector, and neither does Safari, so this is the state a
   desktop tester lands in by default.
   -------------------------------------------------------------------------- */
.scan-block.is-unsupported .scan-status {
  padding: var(--space-3);
  border-left: 4px solid var(--warning);
  border-radius: var(--radius-sm);
  background: color-mix(in srgb, var(--warning) 14%, transparent);
  color: var(--text);
  font-weight: 600;
}

/* ===========================================================================
   Fish bins

   The driver's pickup and drop-off log (fishbins.js), laid out like a phone
   app rather than a page:

   - One fixed frame the height of what the screen can actually show (dvh, so
     the browser's own bars are accounted for). Nothing in it scrolls; every
     screen is a flex column that shares out the height it is given, and the
     open-ended things (today's log, sync detail) live in bottom sheets.
   - On a phone the shared title bar gives way to the module's own header, and
     the tab bar shows only on the region screen (body.fb-immersive past it).
   - Screens slide: forward in from the right, back in from the left.
   =========================================================================== */

body.is-fishbins {
  height: 100vh;
  height: 100dvh;
  min-height: 0;
  overflow: hidden;
  /* A pull-to-refresh halfway through a scan would throw the form away. */
  overscroll-behavior: none;
}

/* The status pill in the module header says the same thing, and the bar would
   sit on top of Submit. */
body.is-fishbins .offline-bar { display: none !important; }

body.is-fishbins.fb-immersive .tabbar { display: none; }

@media (max-width: 699px) {
  body.is-fishbins .titlebar { display: none; }
}

.fishbins [hidden],
.fb-sheet [hidden],
.fb-camera [hidden] { display: none !important; }

/* --- The frame -------------------------------------------------------------- */
.fishbins {
  position: fixed;
  left: 0;
  right: 0;
  top: 0;
  bottom: calc(var(--tabbar-height) + env(safe-area-inset-bottom));
  z-index: 10;
  display: flex;
  flex-direction: column;
  /* The status-bar inset is the header's padding, so that strip is header-coloured. */
  background: var(--bg);
}

body.fb-immersive .fishbins {
  bottom: 0;
  padding-bottom: env(safe-area-inset-bottom);
}

@media (min-width: 700px) {
  .fishbins,
  body.fb-immersive .fishbins {
    top: calc(var(--titlebar-height) + 1px);
    bottom: 0;
    max-width: 560px;
    margin: 0 auto;
    padding-top: 0;
    border-left: 1px solid var(--border);
    border-right: 1px solid var(--border);
  }
}

/* --- Header ----------------------------------------------------------------- */
/* Exactly the height of every other page's title bar (var(--titlebar-height)
   plus its 1px border, plus the status-bar inset), so switching tabs never
   makes the bar jump. */
.fb-header {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  min-height: calc(var(--titlebar-height) + 1px + env(safe-area-inset-top));
  padding: env(safe-area-inset-top) var(--space-4) 0;
  border-bottom: 1px solid var(--border);
  background: var(--titlebar-bg, var(--primary));
  color: var(--titlebar-fg, var(--primary-contrast));
  transition: background-color 0.3s ease, color 0.3s ease;
}

/* The AgroOS Cloud mark where other pages show it: on the first screen
   (no Back) on a phone, always on a wide screen (which has no Back). */
/* The AgroOS Cloud mark, phone only, on the first screen. Keyed to the Back
   button being hidden -- which it is in the markup -- so the mark is there
   from the first paint rather than appearing once the script has run. */
.fb-header-brand { display: none; flex: none; }
.fb-header-back[hidden] ~ .fb-header-brand { display: block; }

@media (min-width: 800px) {
  .fb-header-back[hidden] ~ .fb-header-brand { display: none; }
}

/* Page changes fade the content while the bar stays put (the layout's
   title bar is "aos-titlebar"). Where this header stands in for that bar --
   a phone, and a desktop with the sidebar -- it takes the same name, so
   moving between Tasks and Fish bins morphs one bar into the other instead
   of sliding the header with the page and flashing the background above it.
   Not between 700 and 1023px, where both bars show and a shared name would
   cancel the transition. */
@media (max-width: 699px) {
  body.is-fishbins .fb-header { view-transition-name: aos-titlebar; }
}

@media (min-width: 1024px) {
  body.is-fishbins.has-sidebar .fb-header { view-transition-name: aos-titlebar; }
}

@media (min-width: 700px) {
  .fb-header {
    background: var(--paper);
    color: var(--text);
    border-bottom: 1px solid var(--border);
  }
}

.fb-header-back {
  flex: none;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  margin-left: calc(-1 * var(--space-2));
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: inherit;
  cursor: pointer;
}

.fb-header-back:active { background: rgba(255, 255, 255, 0.18); }

.fb-header-titles { flex: 1 1 auto; min-width: 0; }

.fb-header-title {
  overflow: hidden;
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.2;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.fb-header-sub {
  margin: 0;
  font-size: var(--text-xs);
  line-height: 1.2;
  opacity: 0.85;
}

/* Sync status. The dot carries the state; the words say how many. */
.fb-status {
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 36px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.18);
  color: inherit;
  font-family: inherit;
  font-size: 0.875rem;
  font-weight: 600;
  white-space: nowrap;
  cursor: pointer;
}

@media (min-width: 700px) {
  .fb-status { background: var(--paper-raised); }
}

.fb-status-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #4ADE80;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.12);
}

.fb-status[data-state="waiting"] .fb-status-dot,
.fb-status[data-state="sending"] .fb-status-dot { background: #FBBF24; }
.fb-status[data-state="sending"] .fb-status-dot { animation: fb-pulse 1s ease-in-out infinite; }
.fb-status[data-state="offline"] .fb-status-dot { background: #CBD5E1; }
.fb-status[data-state="signed-out"] .fb-status-dot,
.fb-status[data-state="parked"] .fb-status-dot { background: #F87171; }

@keyframes fb-pulse { 50% { opacity: 0.35; } }

/* --- Screens and the slide between them -------------------------------------- */
.fb-stage {
  position: relative;
  flex: 1 1 auto;
  min-height: 0;
  overflow: hidden;
}

.fb-screen {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4) var(--space-4);
  background: var(--bg);
}

.fb-enter-fwd  { z-index: 2; animation: fb-in-right 280ms cubic-bezier(0.2, 0.8, 0.2, 1) both; }
.fb-leave-fwd  { z-index: 1; animation: fb-out-left 280ms cubic-bezier(0.2, 0.8, 0.2, 1) both; }
.fb-enter-back { z-index: 1; animation: fb-in-left 280ms cubic-bezier(0.2, 0.8, 0.2, 1) both; }
.fb-leave-back { z-index: 2; animation: fb-out-right 280ms cubic-bezier(0.2, 0.8, 0.2, 1) both; }

/* The screen on top casts a shadow on the one beneath, which is what makes the
   slide read as a card moving rather than the page jumping. */
.fb-enter-fwd,
.fb-leave-back { box-shadow: -10px 0 28px rgba(0, 0, 0, 0.14); }

@keyframes fb-in-right  { from { transform: translateX(100%); } }
@keyframes fb-out-left  { to   { transform: translateX(-28%); opacity: 0.5; } }
@keyframes fb-in-left   { from { transform: translateX(-28%); opacity: 0.5; } }
@keyframes fb-out-right { to   { transform: translateX(100%); } }

/* --- Mode colours ---------------------------------------------------------------
   Once the driver says Pick up or Drop off, the header and Save wear that
   colour -- the Power App's blue and orange -- so a glance at any part of the
   screen says which one is being logged. */
:root {
  --fb-pickup: #3860B2;
  --fb-dropoff: #E8590C;
  /* The colours as text, icons and outlines on the page background, and how
     strongly the Pick up / Drop off cards are tinted. In light mode these are
     the colours themselves; on a dark background the deep blue and orange
     nearly vanish, so dark mode lifts them and tints harder. */
  --fb-pickup-ink: var(--fb-pickup);
  --fb-dropoff-ink: var(--fb-dropoff);
  --fb-choice-tint: 10%;
}

:root[data-theme="dark"] {
  --fb-pickup-ink: #9DB8FF;
  --fb-dropoff-ink: #FFA36E;
  --fb-choice-tint: 26%;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --fb-pickup-ink: #9DB8FF;
    --fb-dropoff-ink: #FFA36E;
    --fb-choice-tint: 26%;
  }
}

.fishbins[data-mode="pickup"] .fb-header  { background: var(--fb-pickup); color: #FFFFFF; }
.fishbins[data-mode="dropoff"] .fb-header { background: var(--fb-dropoff); color: #FFFFFF; }

/* --- Steps and list screens ------------------------------------------------------ */
.fb-step {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  font-size: var(--text-lg);
  font-weight: 800;
}

.fb-step-number {
  display: grid;
  place-items: center;
  width: 28px;
  height: 28px;
  border-radius: 50%;
  background: var(--primary);
  color: var(--primary-contrast);
  font-size: var(--text-sm);
}

/* Regions and suppliers: big rows that share the height out, each a whole
   thumb-sized target. */
.fb-list {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  /* Only ever needed past seven or so rows on a small phone. */
  overflow-y: auto;
}

.fb-row-button {
  flex: 1 1 0;
  min-height: 56px;
  max-height: 96px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  padding: 0 var(--space-4) 0 var(--space-5);
  border: 0;
  border-radius: var(--radius);
  background: var(--paper);
  box-shadow: var(--shadow);
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform 0.12s ease, background 0.12s ease;
}

.fb-row-chevron { flex: none; margin-left: auto; color: var(--text-muted); }

/* A region's pin, in a disc: marks the rows as places to choose. */
.fb-row-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 40px;
  height: 40px;
  margin-left: calc(-1 * var(--space-2));
  border-radius: 50%;
  background: color-mix(in srgb, var(--primary) 12%, var(--paper));
  color: var(--primary);
}

.fb-row-text { display: flex; flex-direction: column; min-width: 0; }

.fb-row-name {
  font-size: 1.25rem;
  font-weight: 700;
  line-height: 1.2;
  overflow-wrap: anywhere;
}

.fb-row-detail {
  margin-top: 2px;
  color: var(--text-secondary);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* Pressed feedback only while the finger is down. No focus ring and no "last
   used" mark either: when the driver comes back to this screen every row is
   back to plain. */
.fb-row-button:focus,
.fb-today-bar:focus { outline: none; }

.fb-row-button:focus-visible,
.fb-today-bar:focus-visible { outline: 3px solid var(--primary); outline-offset: 2px; }

.fb-row-button:active,
.fb-today-bar:active { transform: scale(0.98); background: var(--paper-raised); }

/* The row just tapped: filled and ticked for the moment before the next
   screen slides in, so the driver sees which one they hit. The list is
   rebuilt each time its screen opens, so this never outlives the tap. */
.fb-row-button.is-tapped {
  background: color-mix(in srgb, var(--primary) 14%, var(--paper));
  box-shadow: inset 0 0 0 3px var(--primary), var(--shadow);
  transform: scale(0.98);
}

.fb-row-button.is-tapped .fb-row-chevron { color: var(--primary); }
.fb-row-button.is-tapped .fb-row-icon { background: var(--primary); color: var(--primary-contrast); }

/* Today's counts on a supplier row, in the stop screen's mode colours. */
.fb-counts { display: flex; flex-wrap: wrap; gap: var(--space-1); margin-top: 2px; }

.fb-count {
  padding: 1px var(--space-2);
  border-radius: var(--radius-pill);
  color: #FFFFFF;
  font-size: var(--text-xs);
  font-weight: 700;
}

.fb-count-pickup  { background: var(--fb-pickup); }
.fb-count-dropoff { background: var(--fb-dropoff); }

/* "Your bins today": a summary, not a choice. Flat and tinted with no
   shadow, set apart from the region cards above, with its own icon and the
   count as a big number. */
.fb-today-bar {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-height: 60px;
  margin-top: var(--space-2);
  padding: 0 var(--space-4);
  border: 1px solid color-mix(in srgb, var(--primary) 28%, transparent);
  border-radius: var(--radius);
  background: color-mix(in srgb, var(--primary) 8%, var(--bg));
  color: var(--text);
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: transform 0.12s ease;
}

.fb-today-icon { flex: none; color: var(--primary); }

.fb-today-text { flex: 1 1 auto; display: flex; flex-direction: column; min-width: 0; }
.fb-today-label { font-size: var(--text-base); font-weight: 700; }
.fb-today-hint { color: var(--text-secondary); font-size: var(--text-xs); font-weight: 600; }

.fb-today-count {
  flex: none;
  min-width: 44px;
  padding: 2px var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--primary);
  color: var(--primary-contrast);
  font-size: 1.25rem;
  font-weight: 800;
  text-align: center;
}

.fb-today-alert {
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--error);
  color: #FFFFFF;
  font-size: var(--text-xs);
  font-weight: 700;
}

/* --- The stop ------------------------------------------------------------------ */
.fb-screen-stop { padding-bottom: var(--space-3); }

.fb-form {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
}

/* Pick up | Drop off: once chosen, a two-way switch at the top of the stop.
   Before that, see the big cards below. */
.fb-modes {
  flex: none;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}

.fb-mode {
  height: 64px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  border: 3px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  font-family: inherit;
  font-size: 1.25rem;
  font-weight: 800;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease, opacity 0.15s ease;
}

.fb-mode-pickup svg  { color: var(--fb-pickup-ink); }
.fb-mode-dropoff svg { color: var(--fb-dropoff-ink); }

.fb-mode-pickup.is-chosen  { border-color: var(--fb-pickup); background: var(--fb-pickup); color: #FFFFFF; }
.fb-mode-dropoff.is-chosen { border-color: var(--fb-dropoff); background: var(--fb-dropoff); color: #FFFFFF; }
.fb-mode.is-chosen svg { color: #FFFFFF; }

/* The one not chosen steps back, so the choice reads from across the cab. */
.fb-form[data-kind="pickup"] .fb-mode-dropoff,
.fb-form[data-kind="dropoff"] .fb-mode-pickup { opacity: 0.6; }

/* The chooser: before a choice, Pick up and Drop off cover the stop as two
   big cards in their own colours -- stacked on a phone, squares side by side
   on a wide screen. Nothing else is there to tap. */
.fb-form { position: relative; }

.fb-chooser {
  position: absolute;
  inset: 0;
  z-index: 2;
  display: none;
  flex-direction: column;
  gap: var(--space-2);
  background: var(--bg);
}

.fb-form[data-kind="none"] .fb-chooser,
.fb-chooser.is-leaving { display: flex; }

.fb-chooser .fb-label { margin: 0; }

.fb-chooser-cards {
  flex: 1 1 auto;
  min-height: 0;
  display: grid;
  grid-template-rows: 1fr 1fr;
  gap: var(--space-3);
}

.fb-choice {
  min-height: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-3);
  border: 3px solid;
  border-radius: calc(var(--radius) + 6px);
  font-family: inherit;
  font-size: 1.875rem;
  font-weight: 800;
  cursor: pointer;
  transition: transform 0.12s ease, background 0.18s ease, color 0.18s ease;
}

.fb-choice-pickup {
  border-color: color-mix(in srgb, var(--fb-pickup-ink) 60%, var(--border));
  background: color-mix(in srgb, var(--fb-pickup) var(--fb-choice-tint), var(--paper));
  color: var(--fb-pickup-ink);
}

.fb-choice-dropoff {
  border-color: color-mix(in srgb, var(--fb-dropoff-ink) 60%, var(--border));
  background: color-mix(in srgb, var(--fb-dropoff) var(--fb-choice-tint), var(--paper));
  color: var(--fb-dropoff-ink);
}

.fb-choice:active { transform: scale(0.97); }

/* The card just tapped fills with its colour as the chooser goes. */
.fb-choice-pickup.is-picked  { background: var(--fb-pickup); color: #FFFFFF; }
.fb-choice-dropoff.is-picked { background: var(--fb-dropoff); color: #FFFFFF; }

.fb-chooser-cards.is-shaking { animation: fb-shake 360ms ease; }

@media (min-width: 800px) {
  .fb-chooser-cards {
    grid-template-columns: 1fr 1fr;
    grid-template-rows: auto;
    align-content: center;
  }

  .fb-choice { aspect-ratio: 1; }
}

/* Leaving: the chooser lifts away while the stop rises in beneath it, one
   piece after another. Opacity and transform only, so it stays smooth. */
.fb-chooser.is-leaving {
  pointer-events: none;
  animation: fb-chooser-out 340ms cubic-bezier(0.4, 0, 0.2, 1) 90ms both;
}

.fb-chooser.is-leaving .fb-choice:not(.is-picked) {
  animation: fb-choice-dim 160ms ease both;
}

@keyframes fb-chooser-out {
  to { opacity: 0; transform: scale(0.96); }
}

@keyframes fb-choice-dim {
  to { opacity: 0.3; }
}

.fb-form.is-revealing > .fb-group,
.fb-form.is-revealing > .fb-here,
.fb-form.is-revealing > .fb-submit {
  animation: fb-fade-up 380ms cubic-bezier(0.2, 0.8, 0.2, 1) both;
}

.fb-form.is-revealing > .fb-group-modes { animation-delay: 120ms; }
.fb-form.is-revealing > .fb-group-bin   { animation-delay: 170ms; }
.fb-form.is-revealing > .fb-pickup-only { animation-delay: 220ms; }
.fb-form.is-revealing > .fb-here,
.fb-form.is-revealing > .fb-submit      { animation-delay: 260ms; }

/* A pane or group fading up into place (also the wide layout's stand-in
   for the phone's slide). */
.fb-fade { animation: fb-fade-up 260ms ease both; }

@keyframes fb-fade-up {
  from { opacity: 0; transform: translateY(12px); }
}

/* A group on the stop screen: what you are doing, the bin, its details.
   A card with a small title, so each control is read as part of one. */
.fb-group {
  flex: none;
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3) var(--space-3);
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 4px);
  background: var(--paper);
}

.fb-label {
  color: var(--text-secondary);
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* The keypad button sits inside the field on the right; the number stays
   centred because the field is padded the same on both sides. */
.fb-bin-wrap { position: relative; }

.fb-keypad {
  position: absolute;
  top: 6px;
  right: 6px;
  bottom: 6px;
  width: 52px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: calc(var(--radius) - 4px);
  background: var(--paper-raised);
  color: var(--text-secondary);
  cursor: pointer;
}

.fb-keypad:active { transform: scale(0.94); }


.fb-bin {
  width: 100%;
  height: 64px;
  padding: 0 64px;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  font-family: inherit;
  font-size: 2rem;
  font-weight: 800;
  letter-spacing: 0.3em;
  /* Letter-spacing also trails the last digit; this evens it out so the
     number sits truly centred. */
  text-indent: 0.3em;
  text-align: center;
  /* No blinking caret: a scanner does the typing, and a caret blinking over
     the prompt looked broken. Typing by hand uses the keypad sheet. */
  caret-color: transparent;
}


/* The real placeholder is a single space (so :placeholder-shown works); the
   words are this prompt, centred both ways over the field. It pulses gently
   while the field is waiting for a scan. */
.fb-bin::placeholder { color: transparent; }

.fb-bin-prompt {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  color: var(--text-muted);
  font-size: 1.5rem;
  letter-spacing: 0.02em;
  font-weight: 700;
  pointer-events: none;
}

.fb-bin:not(:placeholder-shown) + .fb-bin-prompt { display: none; }

/* Clear: inside the field on the left, opposite the keypad button, and only
   there while the field holds a number. */
.fb-bin-clear {
  position: absolute;
  top: 6px;
  left: 6px;
  bottom: 6px;
  width: 52px;
  display: none;
  place-items: center;
  border: 0;
  border-radius: calc(var(--radius) - 4px);
  background: var(--paper-raised);
  color: var(--text-secondary);
  cursor: pointer;
}

.fb-bin:not(:placeholder-shown) ~ .fb-bin-clear { display: grid; }
.fb-bin-clear:active { transform: scale(0.94); }

@media (min-width: 800px) {
  .fb-bin-clear { width: 64px; }
}

@media (max-height: 640px) {
  .fb-bin-clear { width: 44px; }
}

/* Sized with the field: smaller on short phones, larger on a tablet. */
@media (max-height: 640px) {
  .fb-bin-prompt { font-size: 1.3rem; }
}

@media (min-width: 800px) {
  .fb-bin-prompt { font-size: 1.85rem; }
}

.fb-bin:focus + .fb-bin-prompt {
  color: var(--primary);
  animation: fb-prompt-pulse 1.6s ease-in-out infinite;
}

.fishbins[data-mode="pickup"] .fb-bin:focus + .fb-bin-prompt  { color: var(--fb-pickup-ink); }
.fishbins[data-mode="dropoff"] .fb-bin:focus + .fb-bin-prompt { color: var(--fb-dropoff-ink); }

@keyframes fb-prompt-pulse {
  0%, 100% { opacity: 1; }
  50%      { opacity: 0.35; }
}

.fb-bin:focus {
  outline: none;
  border-color: var(--primary);
  box-shadow: 0 0 0 4px color-mix(in srgb, var(--primary) 22%, transparent);
}

/* The Power App's red field: a bad scan is unmissable at arm's length. */
.fb-bin.is-invalid {
  border-color: var(--error);
  background: color-mix(in srgb, var(--error) 14%, var(--paper));
}

.fb-bin.is-shaking,
.fb-modes.is-shaking { animation: fb-shake 360ms ease; }

/* Locked until Pick up or Drop off is chosen: dashed, greyed, and saying
   so. Taps pass through to the wrapper, which answers them. */
.fb-bin:disabled {
  padding: 0 var(--space-3);
  border-style: dashed;
  background: var(--paper-raised);
  opacity: 1;
  pointer-events: none;
}

.fb-bin:disabled::placeholder { font-size: 0.9375rem; }

.fb-keypad:disabled { display: none; }

@keyframes fb-shake {
  20%, 60% { transform: translateX(-8px); }
  40%, 80% { transform: translateX(8px); }
}

/* Condition, lid and photo belong to a pickup only. Until a mode is chosen
   they stay out of the way too, so the screen asks one thing at a time. */
.fb-form[data-kind="none"] .fb-pickup-only,
.fb-form[data-kind="dropoff"] .fb-pickup-only { display: none; }

/* Condition: a segmented control, Good / Worn / Damaged. One white thumb
   behind the options slides to the choice (--fb-seg-index, set by the
   script); the chosen word takes the condition's colour. */
.fb-segment {
  --fb-seg-count: 3;
  --fb-seg-index: 0;
  --fb-seg-colour: var(--success);
  position: relative;
  display: flex;
  padding: 4px;
  border-radius: var(--radius);
  background: var(--paper-raised);
}

.fb-segment[data-chosen="worn"]    { --fb-seg-colour: var(--warning); }
.fb-segment[data-chosen="damaged"] { --fb-seg-colour: var(--error); }

.fb-segment-thumb {
  position: absolute;
  top: 4px;
  bottom: 4px;
  left: 4px;
  width: calc((100% - 8px) / var(--fb-seg-count));
  border-radius: calc(var(--radius) - 4px);
  background: var(--paper);
  box-shadow: 0 1px 4px rgba(0, 0, 0, 0.16);
  transform: translateX(calc(100% * var(--fb-seg-index)));
  transition: transform 0.24s cubic-bezier(0.3, 0.7, 0.4, 1);
}

.fb-segment-option {
  position: relative;
  flex: 1 1 0;
  height: 44px;
  border: 0;
  border-radius: calc(var(--radius) - 4px);
  background: transparent;
  color: var(--text-secondary);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
  transition: color 0.2s ease;
}

.fb-segment-option.is-chosen { color: var(--fb-seg-colour); }

.fb-row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--space-2);
}

.fb-tile {
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  overflow: hidden;
  padding: 0 var(--space-2);
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  font-family: inherit;
  font-size: 1rem;
  font-weight: 700;
  cursor: pointer;
}

.fb-tile.is-chosen {
  border-color: var(--primary);
  background: color-mix(in srgb, var(--primary) 12%, var(--paper));
}

.fb-check {
  position: relative;
  flex: none;
  width: 24px;
  height: 24px;
  border: 2px solid var(--text-muted);
  border-radius: 7px;
}

.fb-tile.is-chosen .fb-check {
  border-color: var(--primary);
  background: var(--primary);
}

/* The tick, drawn rather than a font glyph so it sits dead centre. */
.fb-tile.is-chosen .fb-check::after {
  content: "";
  position: absolute;
  left: 6px;
  top: 2px;
  width: 6px;
  height: 11px;
  border: solid var(--primary-contrast);
  border-width: 0 3px 3px 0;
  transform: rotate(45deg);
}

.fb-tile-icon { flex: none; color: var(--text-secondary); }
.fb-tile-label { min-width: 0; line-height: 1.15; }

/* The photo itself, filling the left of the tile, big enough to check. */
.fb-tile-thumb {
  flex: none;
  width: 64px;
  height: 46px;
  border-radius: 8px;
  object-fit: cover;
}

.fb-tile-photo.has-photo { border-color: var(--success); justify-content: flex-start; }
.fb-tile-photo.has-photo .fb-tile-icon { display: none; }

/* Pick up / Drop off as a small label, in the app's colours for them --
   used in every list of bins (Today, Today here). */
.fb-kind {
  display: inline-flex;
  align-items: center;
  flex: none;
  height: 22px;
  margin-right: var(--space-2);
  padding: 0 var(--space-2);
  border-radius: 999px;
  color: #FFFFFF;
  font-size: 0.75rem;
  font-weight: 800;
  vertical-align: middle;
  white-space: nowrap;
}

.fb-kind-pickup  { background: var(--fb-pickup); }
.fb-kind-dropoff { background: var(--fb-dropoff); }

/* Today at this supplier, under the bin details: the counts as labels on a
   row that opens the list of those bins in a sheet (#fb-here-sheet). */
.fb-here {
  flex: none;
  border: 1px solid var(--border);
  border-radius: calc(var(--radius) + 4px);
  background: var(--paper);
}

.fb-here-toggle {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--space-1);
  width: 100%;
  min-height: 48px;
  padding: 0 var(--space-3);
  border: 0;
  background: transparent;
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 700;
  cursor: pointer;
}

.fb-here-toggle:disabled { cursor: default; }
.fb-here-label { margin-right: auto; }
.fb-here-toggle .fb-kind { margin-right: 0; }
.fb-here-chevron { flex: none; color: var(--text-muted); }
.fb-here-toggle:disabled .fb-here-chevron { opacity: 0.3; }

/* The sheet's line under the supplier name. */
.fb-here-counts {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  font-weight: 600;
}

/* Save sits at the bottom whatever is above it. */
.fb-submit { margin-top: auto; }

.fb-submit {
  flex: none;
  height: 64px;
  padding: 0 var(--space-3);
  border: 0;
  border-radius: var(--radius);
  background: var(--success);
  color: #FFFFFF;
  font-family: inherit;
  font-size: 1.2rem;
  font-weight: 800;
  box-shadow: var(--shadow);
  cursor: pointer;
  transition: transform 0.12s ease, background 0.15s ease;
}

.fishbins[data-mode="pickup"] .fb-submit:not(:disabled)  { background: var(--fb-pickup); }
.fishbins[data-mode="dropoff"] .fb-submit:not(:disabled) { background: var(--fb-dropoff); }

/* Disabled still reads as an instruction ("Scan a bin"), not as a dead grey
   button: dashed, in the text colour. */
.fb-submit:disabled {
  background: transparent;
  border: 2px dashed var(--border);
  color: var(--text-secondary);
  box-shadow: none;
  cursor: default;
}

.fb-submit:active:not(:disabled),
.fb-tile:active,
.fb-mode:active,
.fb-segment-option:active { transform: scale(0.97); }

.fb-empty { margin: 0; color: var(--text-muted); font-size: var(--text-sm); }

/* --- Tablet and desktop ----------------------------------------------------------
   From 800px wide the screens stand side by side instead of taking turns
   (fishbins.js: showPanes). Left: regions, or the supplier list, which stays
   in view at the stop with the open supplier marked. Middle: the stop, or a
   placeholder until a supplier is chosen. Today's log stays the "Your bins
   today" sheet, as on a phone. Controls grow for a tablet held
   at arm's length. The breakpoint matches fishbins.js. */
.fb-placeholder,
.fb-pane-back { display: none; }

@media (min-width: 800px) {
  .fishbins,
  body.fb-immersive .fishbins {
    max-width: none;
    border-left: 0;
    border-right: 0;
  }

  /* The supplier list beside the stop is how to move on, so no header Back. */
  .fb-header-back { display: none; }

  .fb-stage {
    display: grid;
    grid-template-columns: minmax(300px, 360px) minmax(0, 1fr);
    grid-template-rows: minmax(0, 1fr);
  }

  .fb-screen {
    position: relative;
    inset: auto;
    min-height: 0;
    padding: var(--space-5) var(--space-6) var(--space-6);
  }

  .fb-screen-home,
  .fb-screen-suppliers {
    grid-column: 1;
    grid-row: 1;
    border-right: 1px solid var(--border);
    background: var(--paper-raised);
  }

  .fb-screen-stop,
  .fb-placeholder {
    grid-column: 2;
    grid-row: 1;
  }

  /* The stop's own column, centred, so a desktop's width does not stretch
     the buttons into bars. */
  .fb-form {
    width: 100%;
    max-width: 640px;
    margin: 0 auto;
    gap: var(--space-4);
  }

  .fb-pane-back {
    flex: none;
    display: flex;
    align-items: center;
    gap: var(--space-1);
    align-self: flex-start;
    min-height: 40px;
    margin: calc(-1 * var(--space-2)) 0 0 calc(-1 * var(--space-2));
    padding: 0 var(--space-3) 0 var(--space-1);
    border: 0;
    border-radius: var(--radius-pill);
    background: transparent;
    color: var(--primary);
    font-family: inherit;
    font-size: var(--text-sm);
    font-weight: 700;
    cursor: pointer;
  }

  .fb-pane-back:active { background: var(--paper); }

  .fb-placeholder {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: var(--space-3);
    color: var(--text-muted);
  }

  .fb-placeholder-text { margin: 0; font-size: var(--text-lg); font-weight: 700; }

  /* The open supplier, while its stop is on screen. */
  .fb-row-button.is-current {
    background: color-mix(in srgb, var(--primary) 12%, var(--paper));
    box-shadow: inset 0 0 0 3px var(--primary), var(--shadow);
  }

  .fb-row-button.is-current .fb-row-chevron { color: var(--primary); }

  /* The list need not share out the full height on a tall screen. */
  .fb-row-button { max-height: 80px; }

  /* Bigger targets for a tablet. */
  .fb-mode { height: 80px; font-size: 1.5rem; }
  .fb-bin { height: 88px; font-size: 2.75rem; padding: 0 76px; }
  .fb-bin:disabled::placeholder { font-size: var(--text-lg); }
  .fb-keypad { width: 64px; }
  .fb-segment-option { height: 56px; font-size: 1.125rem; }
  .fb-tile { height: 68px; font-size: 1.125rem; }
  .fb-tile-thumb { width: 80px; height: 56px; }
  .fb-here-toggle { font-size: var(--text-base); }
  .fb-submit { height: 80px; font-size: 1.4rem; }
}

/* --- Short phones -------------------------------------------------------------
   Under ~640px of usable height (an older or small phone with the browser's
   bars showing) everything tightens so the pickup screen still fits whole. */
@media (max-height: 640px) {
  .fb-screen { gap: var(--space-2); padding: var(--space-2) var(--space-3) var(--space-3); }
  .fb-form { gap: var(--space-2); }
  .fb-group { gap: var(--space-1); padding: var(--space-1) var(--space-2) var(--space-2); }
  .fb-step { font-size: var(--text-base); }
  .fb-mode { height: 52px; font-size: 1.125rem; }
  .fb-label { font-size: 0.6875rem; }
  .fb-bin { height: 52px; font-size: 1.6rem; }
  .fb-keypad { width: 44px; }
  .fb-row-icon { width: 34px; height: 34px; }
  .fb-segment-option { height: 38px; }
  .fb-tile { height: 48px; }
  .fb-tile-thumb { height: 38px; width: 54px; }
  .fb-submit { height: 54px; font-size: 1.05rem; }
  .fb-row-button { min-height: 48px; }
  .fb-row-name { font-size: 1.125rem; }
  .fb-today-bar { min-height: 50px; }
}

/* --- Saved: a green wash and a drawn tick over the whole screen ---------------
   Under the banner (z 70) so Undo stays on top, and pointer-events none so a
   tap or scan made during it lands as if it were not there. About 0.9 s. */
.fb-success {
  position: fixed;
  inset: 0;
  z-index: 60;
  display: grid;
  place-items: center;
  background: color-mix(in srgb, var(--success) 22%, transparent);
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

.fb-success.is-shown {
  visibility: visible;
  animation: fb-success-wash 900ms ease both;
}

.fb-success-mark { filter: drop-shadow(0 6px 18px rgba(0, 0, 0, 0.18)); }

.fb-success-circle { fill: var(--success); }

.fb-success-tick {
  fill: none;
  stroke: #FFFFFF;
  stroke-width: 5;
  stroke-linecap: round;
  stroke-linejoin: round;
  stroke-dasharray: 36;
  stroke-dashoffset: 36;
}

.fb-success.is-shown .fb-success-mark { animation: fb-success-pop 900ms cubic-bezier(0.2, 0.9, 0.3, 1.2) both; }
.fb-success.is-shown .fb-success-tick { animation: fb-success-draw 320ms 120ms ease-out forwards; }

@keyframes fb-success-wash {
  0%   { opacity: 0; }
  15%  { opacity: 1; }
  70%  { opacity: 1; }
  100% { opacity: 0; }
}

@keyframes fb-success-pop {
  0%   { transform: scale(0.5); }
  30%  { transform: scale(1.06); }
  45%  { transform: scale(1); }
  100% { transform: scale(1); }
}

@keyframes fb-success-draw {
  to { stroke-dashoffset: 0; }
}

/* --- Notifications -------------------------------------------------------------
   Drops from the top like the phone's own: frosted, rounded, an icon in a
   coloured disc. Hidden above the screen until .is-shown. */
.fb-banner {
  position: fixed;
  left: var(--space-2);
  right: var(--space-2);
  top: calc(env(safe-area-inset-top) + var(--space-2));
  z-index: 70;
  max-width: 520px;
  margin: 0 auto;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-radius: 18px;
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  -webkit-backdrop-filter: blur(18px) saturate(1.6);
  backdrop-filter: blur(18px) saturate(1.6);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.22), 0 0 0 1px var(--border);
  color: var(--text);
  cursor: pointer;
  opacity: 0;
  transform: translateY(calc(-100% - 24px));
  transition: transform 260ms cubic-bezier(0.2, 0.8, 0.2, 1), opacity 200ms ease;
}

.fb-banner.is-shown { opacity: 1; transform: none; }

.fb-banner-icon {
  flex: none;
  display: grid;
  place-items: center;
  width: 32px;
  height: 32px;
  border-radius: 50%;
  background: var(--success);
  color: #FFFFFF;
  font-size: 1.05rem;
  font-weight: 800;
  line-height: 1;
}

.fb-banner-icon::before { content: "\2713"; }

.fb-banner[data-tone="error"] .fb-banner-icon   { background: var(--error); }
.fb-banner[data-tone="warning"] .fb-banner-icon { background: var(--warning); }
.fb-banner[data-tone="info"] .fb-banner-icon    { background: var(--info); }

.fb-banner[data-tone="error"] .fb-banner-icon::before,
.fb-banner[data-tone="warning"] .fb-banner-icon::before { content: "!"; }
.fb-banner[data-tone="info"] .fb-banner-icon::before    { content: "i"; }

.fb-banner-body { min-width: 0; }

.fb-banner-title {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 700;
  line-height: 1.3;
}

.fb-banner-text {
  margin: 2px 0 0;
  color: var(--text-secondary);
  font-size: var(--text-xs);
  line-height: 1.35;
}

/* --- Sheets -------------------------------------------------------------------- */
.fb-sheet[open] {
  display: flex;
  flex-direction: column;
  max-height: 85vh;
  max-height: 85dvh;
}

.fb-sheet-scroll {
  flex: 1 1 auto;
  min-height: 0;
  margin: 0 calc(-1 * var(--space-5));
  padding: 0 var(--space-5);
  overflow-y: auto;
}

.fb-sheet-lead {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
  font-size: var(--text-sm);
}

.fb-sheet-heading {
  margin: 0 0 var(--space-1);
  color: var(--error);
  font-size: var(--text-xs);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

.fb-sheet-actions {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.fb-sheet-actions form { margin: 0; }

/* --- Keypad sheet: typing a bin number by hand ---------------------------------
   Big digits in a display, a 3 x 4 grid of keys a thumb can't miss, and the
   confirm button saying which bin it will use. */
.fb-keypad-display {
  display: block;
  margin: 0 0 var(--space-1);
  padding: var(--space-3) 0;
  border: 2px solid var(--border);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--text);
  font-size: 2.5rem;
  font-weight: 800;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

.fb-keypad-display.is-empty { color: var(--text-muted); }

.fb-keypad-hint {
  margin: 0 0 var(--space-3);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  text-align: center;
}

.fb-keypad-hint.is-error { color: var(--error); font-weight: 700; }

.fb-keypad-keys {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: var(--space-2);
  margin-bottom: var(--space-4);
}

.fb-key {
  height: 60px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: var(--radius);
  background: var(--paper-raised);
  color: var(--text);
  font-family: inherit;
  font-size: 1.6rem;
  font-weight: 700;
  cursor: pointer;
  touch-action: manipulation;
  -webkit-tap-highlight-color: transparent;
}

.fb-key:active { transform: scale(0.96); background: var(--border); }

.fb-key-quiet { font-size: var(--text-base); color: var(--text-secondary); }

.fb-keypad-use { min-height: 56px; font-size: var(--text-lg); }

@media (max-height: 640px) {
  .fb-key { height: 50px; }
  .fb-keypad-display { font-size: 2rem; padding: var(--space-2) 0; }
}

.fb-parked { margin-bottom: var(--space-4); }

.fb-log { list-style: none; margin: 0; padding: 0; }
.fb-log-item { padding: var(--space-3) 0; border-bottom: 1px solid var(--border); }
.fb-log-item:last-child { border-bottom: 0; }
.fb-log-head { display: flex; align-items: center; justify-content: space-between; gap: var(--space-2); }
.fb-log-what { font-weight: 700; }
.fb-log-meta { margin: var(--space-1) 0 0; color: var(--text-secondary); font-size: var(--text-sm); }
.fb-log-error { margin: var(--space-2) 0; color: var(--error); font-size: var(--text-sm); font-weight: 600; }

.fb-chip {
  flex: none;
  padding: 2px var(--space-2);
  border-radius: var(--radius-pill);
  background: color-mix(in srgb, var(--warning) 22%, transparent);
  font-size: var(--text-xs);
  font-weight: 600;
}

/* Undo, on the right of a "saved" banner. Its own target, so a tap meant for
   it never just dismisses the banner. */
.fb-banner-body { flex: 1 1 auto; }

.fb-banner-action {
  flex: none;
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-pill);
  background: var(--text);
  color: var(--paper);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 800;
  cursor: pointer;
}

/* The duplicate sheet: one sentence, then Skip big and Log it again quiet,
   with space between so a hurried tap lands on Skip. */
.fb-dup-line {
  margin: 0 0 var(--space-5);
  font-size: var(--text-base);
  line-height: 1.45;
}

.fb-dup-skip { min-height: 60px; font-size: var(--text-lg); }

#fb-dup .fb-sheet-actions { gap: var(--space-4); }

/* A switch row in the sync sheet (Sounds). */
.fb-setting {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  min-height: 52px;
  margin: 0 0 var(--space-4);
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius);
  background: var(--paper-raised);
  color: var(--text);
  font-family: inherit;
  font-size: var(--text-base);
  font-weight: 600;
  cursor: pointer;
}

.fb-switch {
  position: relative;
  width: 50px;
  height: 30px;
  border-radius: var(--radius-pill);
  background: var(--border);
  transition: background 0.15s ease;
}

.fb-switch::after {
  content: "";
  position: absolute;
  top: 3px;
  left: 3px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: #FFFFFF;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
  transition: transform 0.15s ease;
}

.fb-setting svg { flex: none; color: var(--text-muted); }

.fb-setting-text { display: flex; flex-direction: column; align-items: flex-start; text-align: left; }
.fb-setting-hint { color: var(--text-secondary); font-size: var(--text-xs); font-weight: 500; }

/* Switches one after another sit as a group. */
.fb-setting + .fb-setting { margin-top: calc(-1 * var(--space-2)); }

.fb-install-steps {
  margin: 0 0 var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  background: var(--paper-raised);
  color: var(--text-secondary);
  font-size: var(--text-sm);
  line-height: 1.45;
}

.fb-setting[aria-checked="true"] .fb-switch { background: var(--success); }
.fb-setting[aria-checked="true"] .fb-switch::after { transform: translateX(20px); }

/* --- Camera ---------------------------------------------------------------------
   Full screen and black, the shutter in thumb reach: the phone's own camera,
   inside the app. data-mode is live | review | failed. */
.fb-camera {
  position: fixed;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  max-height: none;
  margin: 0;
  padding: 0;
  border: 0;
  overflow: hidden;
  background: #000000;
  color: #FFFFFF;
}

.fb-camera::backdrop { background: #000000; }
.fb-camera[open] { animation: fb-camera-in 220ms ease-out; }

@keyframes fb-camera-in { from { opacity: 0; transform: scale(1.04); } }

.fb-camera-video,
.fb-camera-still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  background: #000000;
}

.fb-camera-video { object-fit: cover; }
.fb-camera-still { object-fit: contain; }

.fb-camera.is-flash::after {
  content: "";
  position: absolute;
  inset: 0;
  background: #FFFFFF;
  pointer-events: none;
  animation: fb-flash 320ms ease-out forwards;
}

@keyframes fb-flash { from { opacity: 0.9; } to { opacity: 0; } }

.fb-camera-message {
  position: absolute;
  left: var(--space-6);
  right: var(--space-6);
  top: 42%;
  margin: 0;
  transform: translateY(-50%);
  font-size: 1.0625rem;
  line-height: 1.45;
  text-align: center;
}

.fb-camera-top {
  position: absolute;
  top: calc(env(safe-area-inset-top) + var(--space-3));
  left: var(--space-3);
  right: var(--space-3);
  display: flex;
  justify-content: space-between;
}

.fb-camera-round {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.45);
  color: #FFFFFF;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  cursor: pointer;
}

.fb-camera-bottom {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  display: flex;
  justify-content: center;
  padding: var(--space-6) var(--space-4) calc(env(safe-area-inset-bottom) + var(--space-6));
  background: linear-gradient(transparent, rgba(0, 0, 0, 0.65));
}

.fb-camera-live,
.fb-camera-review,
.fb-camera-full,
.fb-camera-failed {
  display: none;
  width: 100%;
  justify-content: center;
  gap: var(--space-3);
}

.fb-camera[data-mode="live"] .fb-camera-live,
.fb-camera[data-mode="review"] .fb-camera-review,
.fb-camera[data-mode="full"] .fb-camera-full,
.fb-camera[data-mode="failed"] .fb-camera-failed { display: flex; }

/* Live: the shutter dead centre, Done (once there are photos) to its right. */
.fb-camera-live { align-items: center; }
.fb-camera-side { flex: 1 1 0; display: flex; justify-content: flex-end; }
.fb-camera-live > .fb-camera-side:first-child { justify-content: flex-start; }

/* The photos taken so far: a row above the controls, each with an x. */
.fb-camera-strip {
  position: absolute;
  left: 0;
  right: 0;
  bottom: calc(env(safe-area-inset-bottom) + 150px);
  display: flex;
  gap: var(--space-2);
  padding: 0 var(--space-4);
  overflow-x: auto;
  scrollbar-width: none;
}

.fb-camera-thumb {
  position: relative;
  flex: none;
  width: 64px;
  height: 64px;
  border-radius: 10px;
  overflow: hidden;
  border: 2px solid rgba(255, 255, 255, 0.85);
  background: rgba(255, 255, 255, 0.15);
}

.fb-camera-thumb img { width: 100%; height: 100%; object-fit: cover; display: block; }

.fb-camera-thumb-remove {
  position: absolute;
  top: 2px;
  right: 2px;
  width: 24px;
  height: 24px;
  display: grid;
  place-items: center;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.7);
  color: #FFFFFF;
  font-size: 16px;
  font-weight: 700;
  line-height: 1;
  cursor: pointer;
}

.fb-camera-count {
  position: absolute;
  top: calc(env(safe-area-inset-top) + var(--space-3));
  left: 50%;
  transform: translateX(-50%);
  margin: 0;
  padding: 6px 14px;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.45);
  color: #FFFFFF;
  font-size: var(--text-sm);
  font-weight: 700;
}

.fb-camera-count:empty { display: none; }

/* The shutter: a white disc inside a white ring, like every phone's. */
.fb-shutter {
  width: 78px;
  height: 78px;
  padding: 5px;
  border: 4px solid #FFFFFF;
  border-radius: 50%;
  background: #FFFFFF;
  background-clip: content-box;
  cursor: pointer;
  transition: transform 0.1s ease;
}

.fb-shutter:active:not(:disabled) { transform: scale(0.9); }
.fb-shutter:disabled { opacity: 0.4; cursor: default; }

.fb-camera-pill {
  flex: 1 1 0;
  max-width: 180px;
  height: 52px;
  border: 0;
  border-radius: var(--radius-pill);
  background: rgba(255, 255, 255, 0.2);
  color: #FFFFFF;
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  font-family: inherit;
  font-size: 1.0625rem;
  font-weight: 700;
  cursor: pointer;
}

.fb-camera-pill-primary { background: #FFFFFF; color: #000000; }
.fb-camera-pill-danger  { color: #FF8A80; }

/* --- Reduced motion ---------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
  .fb-enter-fwd, .fb-leave-fwd, .fb-enter-back, .fb-leave-back,
  .fb-bin.is-shaking, .fb-modes.is-shaking,
  .fb-form[data-kind="none"] .fb-mode, .fb-fade,
  .fb-camera[open],
  .fb-camera.is-flash::after,
  .fb-status[data-state="sending"] .fb-status-dot { animation: none; }

  .fb-banner, .fb-segment-thumb { transition: none; }

  /* The wash still fades in and out; the tick is simply there. */
  .fb-success.is-shown .fb-success-mark { animation: none; }
  .fb-success.is-shown .fb-success-tick { animation: none; stroke-dashoffset: 0; }
}

/* ==========================================================================
   Desktop (1024px and wider)
   --------------------------------------------------------------------------
   Below this the app is the phone app, at any width. From here it is laid out
   as a desktop app: the sections in a sidebar down the left (brand colour,
   signed-in person at the foot), a light header naming the page with its
   actions, and content given room -- cards in a grid rather than one narrow
   column in the middle of a wide screen.
   ========================================================================== */
.sidebar,
.titlebar-heading { display: none; }

@media (min-width: 1024px) {
  :root { --sidebar-width: 248px; }

  body.has-sidebar { padding-left: var(--sidebar-width); }

  /* --- Sidebar ------------------------------------------------------------ */
  .sidebar {
    position: fixed;
    inset: 0 auto 0 0;
    z-index: 30;
    width: var(--sidebar-width);
    display: flex;
    flex-direction: column;
    gap: var(--space-2);
    padding: var(--space-5) var(--space-3) var(--space-4);
    background: var(--titlebar-bg);
    color: var(--titlebar-fg);
    border-right: 1px solid var(--border);
    view-transition-name: aos-sidebar;
  }

  ::view-transition-old(aos-sidebar),
  ::view-transition-new(aos-sidebar) { animation: none; mix-blend-mode: normal; }

  .sidebar-brand {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    padding: 0 var(--space-3) var(--space-5);
    color: inherit;
    text-decoration: none;
  }

  .sidebar-brand-text { font-size: 1.35rem; font-weight: 800; letter-spacing: 0.3px; }
  .sidebar-brand-text span { font-size: 0.85rem; font-weight: 600; opacity: 0.8; margin-left: 2px; }

  .sidebar-nav { display: flex; flex-direction: column; gap: 4px; }

  .sidebar-link {
    display: flex;
    align-items: center;
    gap: var(--space-3);
    min-height: 46px;
    padding: 0 var(--space-3);
    border-radius: var(--radius-sm);
    color: inherit;
    text-decoration: none;
    font-size: 1rem;
    font-weight: 600;
    opacity: 0.82;
    transition: background 0.15s ease, opacity 0.15s ease;
  }

  .sidebar-link:hover { background: var(--titlebar-overlay); opacity: 1; }

  .sidebar-link.is-active {
    background: var(--titlebar-overlay);
    opacity: 1;
    box-shadow: inset 3px 0 0 currentColor;
  }

  .sidebar-link:focus-visible,
  .sidebar-user:focus-visible { outline: 3px solid currentColor; outline-offset: -3px; }

  .sidebar-link-label { flex: 1 1 auto; }

  .sidebar-badge {
    min-width: 24px;
    padding: 1px 8px;
    border-radius: var(--radius-pill);
    background: var(--titlebar-fg);
    color: var(--titlebar-bg);
    font-size: 0.8125rem;
    font-weight: 800;
    text-align: center;
  }

  .sidebar-user {
    margin-top: auto;
    display: flex;
    align-items: center;
    gap: var(--space-3);
    padding: var(--space-3);
    border-radius: var(--radius-sm);
    color: inherit;
    text-decoration: none;
    border-top: 1px solid var(--titlebar-overlay);
  }

  .sidebar-user:hover,
  .sidebar-user.is-active { background: var(--titlebar-overlay); }

  .sidebar-avatar {
    flex: none;
    display: grid;
    place-items: center;
    width: 38px;
    height: 38px;
    border-radius: 50%;
    background: var(--titlebar-fg);
    color: var(--titlebar-bg);
    font-size: 0.9rem;
    font-weight: 800;
  }

  .sidebar-user-text { display: flex; flex-direction: column; min-width: 0; }

  .sidebar-user-name {
    font-weight: 700;
    font-size: 0.95rem;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .sidebar-user-role { font-size: 0.8rem; opacity: 0.75; }

  /* --- Header: names the page; the sidebar has the brand and the nav ------ */
  body.has-sidebar .titlebar {
    background: var(--paper);
    color: var(--text);
    box-shadow: none;
    border-bottom: 1px solid var(--border);
  }

  body.has-sidebar .titlebar .brand,
  body.has-sidebar .titlebar-nav { display: none; }

  body.has-sidebar .titlebar-inner {
    max-width: none;
    padding: 0 var(--space-7);
  }

  .titlebar-heading {
    display: block;
    margin: 0;
    font-size: var(--text-xl);
    font-weight: 800;
  }

  body.has-sidebar .titlebar-title.is-centered { font-size: var(--text-xl); font-weight: 800; }

  body.has-sidebar .titlebar-back { color: var(--primary); }
  body.has-sidebar .titlebar-back:hover,
  body.has-sidebar .titlebar-back:active { background: var(--paper-raised); }

  body.has-sidebar .titlebar-action {
    margin-left: auto;
    border: 1px solid var(--border);
    background: var(--paper);
    color: var(--text);
    padding: 0 var(--space-4);
  }

  body.has-sidebar .titlebar-action:hover { background: var(--paper-raised); }

  /* --- Content ------------------------------------------------------------ */
  body.has-sidebar .page {
    max-width: 1240px;
    padding: var(--space-7) var(--space-7) var(--space-8);
  }

  /* The header already names the page; keep the head row for its actions. */
  body.has-sidebar .page-head .page-greeting { visibility: hidden; }

  /* Task cards side by side when there is room for two. */
  body.has-sidebar .section {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(420px, 1fr));
    gap: var(--space-4);
    align-items: start;
  }

  body.has-sidebar .section > .section-head,
  body.has-sidebar .section > .day-label,
  body.has-sidebar .section > .empty-state { grid-column: 1 / -1; }

  body.has-sidebar .section > .task-card { margin: 0; height: 100%; }

  body.has-sidebar .section > .day-label { margin: var(--space-3) 0 0; }

  /* Account: the two panels side by side, with quiet headings instead of
     full-width colour bars. */
  body.is-account.has-sidebar .page {
    display: grid;
    grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--space-5);
    align-content: start;
    align-items: start;
  }

  body.is-account.has-sidebar .page > :not(.panel) { grid-column: 1 / -1; }
  body.is-account.has-sidebar .page > .panel { margin: 0; }

  body.has-sidebar .panel {
    border: 1px solid var(--border);
    border-radius: var(--radius);
    background: var(--paper);
    overflow: hidden;
  }

  body.has-sidebar .panel-head,
  body.has-sidebar .panel-head-primary,
  body.has-sidebar .panel-head-muted {
    background: transparent;
    color: var(--text);
    box-shadow: none;
    border-radius: 0;
    border-bottom: 1px solid var(--border);
    padding: var(--space-4) var(--space-5);
  }

  body.has-sidebar .panel-head h2 { text-align: left; font-size: 1.05rem; letter-spacing: 0; }

  /* --- Fish bins: beside the sidebar, its own header the only header ------ */
  body.is-fishbins.has-sidebar .titlebar { display: none; }

  body.has-sidebar .fishbins,
  body.has-sidebar.fb-immersive .fishbins {
    top: 0;
    left: var(--sidebar-width);
    bottom: 0;
  }

  body.has-sidebar .fb-header {
    min-height: calc(var(--titlebar-height) + 1px);
    padding: 0 var(--space-6);
  }

  body.has-sidebar .fb-header-title { font-size: var(--text-xl); font-weight: 800; }
}

/* "See all regions" under a driver's usual ones: a quiet text button, so
   the usual regions stay the obvious choice. */
.fb-more-regions {
  flex: none;
  align-self: center;
  min-height: 44px;
  padding: 0 var(--space-4);
  border: 0;
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--primary);
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 700;
  cursor: pointer;
}

.fb-more-regions:active { background: var(--paper-raised); }

/* A fish truck driver: Fish bins is the whole app. No title bar, tab bar or
   sidebar on any screen size -- the Fish bins header is the only bar, and
   the frame fills the screen. Sign out is in its status sheet. */
body.is-fish-driver .tabbar,
body.is-fish-driver .sidebar,
body.is-fish-driver.is-fishbins .titlebar { display: none !important; }

body.is-fish-driver .fishbins,
body.is-fish-driver.fb-immersive .fishbins {
  top: 0;
  left: 0;
  bottom: 0;
  max-width: none;
  margin: 0;
  border: 0;
  padding-bottom: env(safe-area-inset-bottom);
}

body.is-fish-driver .fb-header { view-transition-name: aos-titlebar; }

/* Signed in as ... / Sign out, in the Fish bins status sheet (drivers). */
.fb-account {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-3);
  margin: 0 0 var(--space-2);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  background: var(--paper-raised);
}

.fb-account-text { display: flex; flex-direction: column; min-width: 0; }
.fb-account-name { font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.fb-signout-warning {
  margin: 0 0 var(--space-4);
  color: var(--error);
  font-size: var(--text-sm);
  font-weight: 600;
}

/* Greyed sign-out (no signal): a plain note, not an alarm. */
.fb-signout-warning.is-note { color: var(--text-muted); font-weight: 500; }

/* No signal: who the bins are being logged under, under the header. The
   whole strip is the way to change it. */
.fb-who {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-wrap: wrap;
  gap: var(--space-1) var(--space-3);
  width: 100%;
  min-height: 44px;
  margin: 0;
  padding: var(--space-2) var(--space-4);
  border: 0;
  background: var(--warning);
  color: #1a1200;
  font-family: inherit;
  font-size: var(--text-sm);
  font-weight: 700;
  text-align: center;
  cursor: pointer;
}

.fb-who-action { text-decoration: underline; text-underline-offset: 3px; }

.fb-who[hidden] { display: none; }
