/* Velocity 360 design tokens and base styles. Shared by the operator app, the portal and the handheld.
   Palette and type follow the design documents: cool blue-grey ground, racking blue accent, amber for attention. */

:root {
  --ground: #f4f6f8;
  --surface: #ffffff;
  --surface-2: #eaeef2;
  --ink: #172029;
  --ink-2: #3d4a56;
  --muted: #5b6773;
  --line: #d5dde4;
  --line-strong: #b9c4cd;
  --accent: #1f5f8b;
  --accent-ink: #ffffff;
  --accent-soft: #e3eef7;
  --amber: #945a0b;
  --amber-soft: #fbf1df;
  --ok: #1f7a4d;
  --ok-soft: #dff3e8;
  --bad: #b3261e;
  --bad-soft: #fbe3e1;
  /* Practice (learn mode): a colour no real screen uses, so nobody takes the practice company for the real one. */
  --practice: #6b3fa0;
  --practice-ink: #ffffff;
  --practice-soft: #f1e9f9;
  --mono: "IBM Plex Mono", ui-monospace, Consolas, monospace;
  --sans: "IBM Plex Sans", "Segoe UI", system-ui, sans-serif;
  --display: "Archivo", "Segoe UI", system-ui, sans-serif;
  --radius: 4px;
  --shadow: 0 1px 2px rgba(23, 32, 41, 0.06);
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%235b6773' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* The handheld's marks: a scan box's barcode, a list row's chevron, a done step's tick. */
  --scan-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b6773' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 7V5a2 2 0 0 1 2-2h2'/%3E%3Cpath d='M17 3h2a2 2 0 0 1 2 2v2'/%3E%3Cpath d='M21 17v2a2 2 0 0 1-2 2h-2'/%3E%3Cpath d='M7 21H5a2 2 0 0 1-2-2v-2'/%3E%3Cpath d='M8 7v10'/%3E%3Cpath d='M12 7v10'/%3E%3Cpath d='M16 7v10'/%3E%3C/svg%3E");
  --chevron-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%235b6773' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  --tick-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  color-scheme: light dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ground: #0f1418; --surface: #161d23; --surface-2: #1d262e;
    --ink: #e6ebef; --ink-2: #c3ccd4; --muted: #95a2ad;
    --line: #2a353e; --line-strong: #3a4751;
    --accent: #6fb1e0; --accent-ink: #0f1418; --accent-soft: #1a2c3a;
    --amber: #e0a23c; --amber-soft: #2c2213;
    --ok: #5fcf8f; --ok-soft: #14301f; --bad: #ff8a80; --bad-soft: #3a1512;
    --practice: #c3a0ee; --practice-ink: #0f1418; --practice-soft: #261b33;
    --shadow: none;
    --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%2395a2ad' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
    /* The handheld's marks: a scan box's barcode, a list row's chevron, a done step's tick. */
    --scan-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2395a2ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 7V5a2 2 0 0 1 2-2h2'/%3E%3Cpath d='M17 3h2a2 2 0 0 1 2 2v2'/%3E%3Cpath d='M21 17v2a2 2 0 0 1-2 2h-2'/%3E%3Cpath d='M7 21H5a2 2 0 0 1-2-2v-2'/%3E%3Cpath d='M8 7v10'/%3E%3Cpath d='M12 7v10'/%3E%3Cpath d='M16 7v10'/%3E%3C/svg%3E");
    --chevron-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2395a2ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
    --tick-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230f1418' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
  }
}

:root[data-theme="dark"] {
  --ground: #0f1418; --surface: #161d23; --surface-2: #1d262e;
  --ink: #e6ebef; --ink-2: #c3ccd4; --muted: #95a2ad;
  --line: #2a353e; --line-strong: #3a4751;
  --accent: #6fb1e0; --accent-ink: #0f1418; --accent-soft: #1a2c3a;
  --amber: #e0a23c; --amber-soft: #2c2213;
  --ok: #5fcf8f; --ok-soft: #14301f; --bad: #ff8a80; --bad-soft: #3a1512;
  --practice: #c3a0ee; --practice-ink: #0f1418; --practice-soft: #261b33;
  --shadow: none;
  --select-chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5l5 5 5-5' fill='none' stroke='%2395a2ad' stroke-width='1.6' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E");
  /* The handheld's marks: a scan box's barcode, a list row's chevron, a done step's tick. */
  --scan-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2395a2ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3 7V5a2 2 0 0 1 2-2h2'/%3E%3Cpath d='M17 3h2a2 2 0 0 1 2 2v2'/%3E%3Cpath d='M21 17v2a2 2 0 0 1-2 2h-2'/%3E%3Cpath d='M7 21H5a2 2 0 0 1-2-2v-2'/%3E%3Cpath d='M8 7v10'/%3E%3Cpath d='M12 7v10'/%3E%3Cpath d='M16 7v10'/%3E%3C/svg%3E");
  --chevron-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2395a2ad' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E");
  --tick-mark: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%230f1418' stroke-width='3.5' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
}

* { box-sizing: border-box; }
html, body { margin: 0; height: 100%; }
body { background: var(--ground); color: var(--ink); font-family: var(--sans); font-size: 15px; line-height: 1.5; }
a { color: var(--accent); }
h1, h2, h3 { font-family: var(--display); font-weight: 600; line-height: 1.15; margin: 0; text-wrap: balance; }
h1 { font-size: 26px; }
h2 { font-size: 19px; }
code, .mono { font-family: var(--mono); font-size: 0.92em; }
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

/* MudBlazor draws what is typed into its inputs in the theme's subtitle weight, which the theme sets to 600 for
   headings, so every grid's search box typed in bold. Typed text is plain, as in the app's own fields. */
.mud-input { font-weight: 400; }

/* MudBlazor's reset carries `button:focus { outline: none }`, which outranks the bare rule above and would quietly
   take the keyboard ring off every button in the app. Said again here at a specificity that wins. */
a:focus-visible, button:focus-visible, select:focus-visible, input:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
}

/* Shell */
.app { display: grid; grid-template-columns: 232px minmax(0, 1fr); min-height: 100vh; }
.sidebar { background: var(--surface); border-right: 1px solid var(--line); padding: 18px 12px; display: flex; flex-direction: column; gap: 18px; position: sticky; top: 0; height: 100vh; overflow-y: auto; }
.brand { font-family: var(--display); font-weight: 700; font-size: 18px; letter-spacing: -0.01em; padding: 0 8px; }
.brand small { display: block; font-family: var(--mono); font-weight: 400; font-size: 11px; color: var(--muted); letter-spacing: 0.08em; text-transform: uppercase; }
.nav { display: flex; flex-direction: column; gap: 2px; }
.nav a { display: block; padding: 8px 10px; border-radius: var(--radius); color: var(--ink-2); text-decoration: none; border-left: 2px solid transparent; }
.nav a:hover { background: var(--surface-2); }
.nav a.active { color: var(--accent); background: var(--accent-soft); border-left-color: var(--accent); }
/* A group is a header and its links. The header is set like a link beside it, same weight and colour, so the rail
   reads as one list; its chevron says it opens. The links hang under it on a guide line, indented, so two open groups
   are two blocks and a link after a group is plainly not in it. The room an open group takes is below it only: room
   above would push the header just clicked down under the pointer. */
.nav-group { display: flex; flex-direction: column; gap: 2px; }
.nav-group.is-open { margin-bottom: 10px; }
.nav .group {
  display: flex; align-items: center; justify-content: space-between; gap: 8px; width: 100%;
  font: inherit; color: var(--ink-2);
  padding: 8px 10px 8px 12px; background: none; border: 0; border-radius: var(--radius); text-align: left;
}
.nav button.group { cursor: pointer; }
.nav button.group:hover { background: var(--surface-2); }
/* A chevron drawn from two borders: pointing right while shut, down while open. */
.nav .group .chev {
  flex: none; width: 7px; height: 7px; margin-right: 3px;
  border-right: 1.5px solid var(--muted); border-bottom: 1.5px solid var(--muted);
  transform: translateX(-2px) rotate(-45deg); transition: transform 0.15s ease;
}
.nav-group.is-open .group .chev { transform: translateY(-2px) rotate(45deg); }
.nav-sub { display: flex; flex-direction: column; gap: 2px; margin-left: 18px; padding-left: 8px; border-left: 1px solid var(--line-strong); }
/* A link's own left bar sits on the guide line, so the page you are on marks the line where it is. */
.nav-sub a { margin-left: -9px; padding: 6px 10px 6px 17px; font-size: 14px; border-top-left-radius: 0; border-bottom-left-radius: 0; }
@media (prefers-reduced-motion: reduce) { .nav .group .chev { transition: none; } }
.nav-rule { height: 1px; background: var(--line); margin: 10px 10px 6px; }
.sidebar-top { display: flex; align-items: center; justify-content: space-between; gap: 8px; }
.nav-toggle { display: none; }
/* For a keyboard: the first Tab offers to jump past the menu, and is out of sight until then. */
.skip-link { position: absolute; left: 8px; top: -60px; z-index: 100; padding: 10px 14px; background: var(--surface); border: 1px solid var(--accent); border-radius: var(--radius); }
.skip-link:focus { top: 8px; }
.main:focus { outline: none; }
.session { margin-top: auto; font-size: 13px; color: var(--muted); padding: 0 8px; }
.session b { color: var(--ink); font-weight: 500; }
.main { padding: 28px 32px 60px; min-width: 0; }
/* On a phone or a narrow window the rail is a bar across the top with a Menu button: laid out as a wrapping row, the
   links filled the whole first screen and every page started below it. */
@media (max-width: 860px) {
  .app { grid-template-columns: 1fr; }
  .sidebar { z-index: 50; height: auto; max-height: 100vh; overflow-y: auto; padding: 8px 16px; gap: 8px; border-right: 0; border-bottom: 1px solid var(--line); }
  .nav-toggle { display: inline-flex; }
  .sidebar:not(.is-open) .nav, .sidebar:not(.is-open) .session { display: none; }
  .session { margin-top: 0; padding-bottom: 8px; }
  .main { padding: 20px 16px 40px; }
}

/* Fingers, not a mouse: every control at least 44 pixels tall, and fields at 16 pixels so a phone does not zoom in
   on them. The handheld's own sizes are larger still. */
@media (max-width: 620px), (pointer: coarse) {
  .btn, .btn-sm { min-height: 44px; }
  input:not([type=checkbox]):not([type=radio]), select, textarea { min-height: 44px; font-size: 16px; }
  .nav a, .nav .group { padding-top: 12px; padding-bottom: 12px; }
}

/* Page */
.page-header { display: flex; justify-content: space-between; align-items: flex-end; gap: 16px; flex-wrap: wrap; margin-bottom: 22px; padding-bottom: 14px; border-bottom: 1px solid var(--line-strong); }
.eyebrow { font-family: var(--mono); font-size: 12px; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0 0 6px; }
.subtitle { color: var(--ink-2); margin: 6px 0 0; max-width: 70ch; }
.page-actions { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 16px; }
.stack { display: flex; flex-direction: column; gap: 12px; }
.row { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; }
.row > .spacer { flex: 1; }

/* Surfaces */
.card { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px 18px; box-shadow: var(--shadow); }
.card h2 { margin-bottom: 10px; }
.card-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; margin-bottom: 10px; }
.card-head h2 { margin: 0; }
.tiles { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 12px; margin-bottom: 16px; }
.tile .s { font-size: 13px; color: var(--muted); }
a.tile { display: block; color: inherit; text-decoration: none; }
a.tile:hover { border-color: var(--accent); }

/* Setup: a card per group, a row per screen (icon, name, a few words, a count where one call answers it). Rows are one
   line each, so every card reads the same way and nothing jumps when a count arrives. */
.line-icon { display: block; flex: none; }
.setup-find { position: relative; display: block; width: 260px; max-width: 100%; color: var(--muted); }
.setup-find .line-icon { position: absolute; left: 10px; top: 50%; transform: translateY(-50%); pointer-events: none; }
.setup-find input { padding-left: 32px; }
/* Columns rather than a grid, so a short group does not leave a hole under it until the tallest beside it ends. */
.setup-groups { columns: 340px 3; column-gap: 16px; }
.setup-group { break-inside: avoid; margin-bottom: 16px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.setup-group h2 { font-family: var(--mono); font-size: 12px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); padding: 12px 16px; background: var(--surface-2); border-bottom: 1px solid var(--line); }
.setup-group ul { list-style: none; margin: 0; padding: 0; }
.setup-group li + li { border-top: 1px solid var(--line); }
.setup-link { display: flex; align-items: center; gap: 12px; padding: 10px 14px 10px 16px; color: var(--ink); text-decoration: none; }
.setup-link:hover { background: var(--accent-soft); }
.setup-link:hover .setup-title { color: var(--accent); }
.setup-icon { flex: none; display: grid; place-items: center; width: 34px; height: 34px; border-radius: var(--radius); background: var(--accent-soft); color: var(--accent); }
.setup-text { flex: 1 1 auto; min-width: 0; display: flex; flex-direction: column; }
.setup-title { font-weight: 600; }
.setup-what { font-size: 13px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.setup-count { flex: none; min-width: 26px; padding: 1px 8px; border-radius: 999px; background: var(--surface-2); color: var(--ink-2); font-size: 12px; font-weight: 600; text-align: center; font-variant-numeric: tabular-nums; }
.setup-go { flex: none; color: var(--line-strong); }
.setup-link:hover .setup-go { color: var(--accent); }
@media (max-width: 860px) {
  .setup-groups { columns: 1; }
  .setup-find { width: 100%; }
  .setup-what { white-space: normal; }
}

/* A page with several faces, like a client or an item: the one showing is underlined. The pages shipped with the
   class and no rule, so the tabs ran together as one line of links. */
.tabs { display: flex; flex-wrap: wrap; gap: 2px; border-bottom: 1px solid var(--line); margin: 0 0 16px; }
.tabs a { padding: 8px 12px; margin-bottom: -1px; border-bottom: 2px solid transparent; color: var(--muted); font-weight: 500; text-decoration: none; }
.tabs a:hover { color: var(--ink); }
.tabs a.active { color: var(--ink); border-bottom-color: var(--accent); }
/* Where a record stands in its life, left to right (a work order: planned, staging, ready, building, closing, done).
   A finished step carries a tick and the current one is filled and bold, so neither is told by colour alone; the
   current step is aria-current="step". On a narrow screen the steps wrap and lose their connecting rule. */
/* A tab's cards, one under another; which of them show depends on where the record stands, so the gap belongs to the
   column rather than to a margin on whichever card happens to come first. */
.tab-cards { display: flex; flex-direction: column; gap: 16px; }
.steps { display: flex; list-style: none; margin: 0 0 16px; padding: 0; gap: 0; }
.steps li { display: flex; align-items: center; gap: 8px; flex: 1 1 0; min-width: 0; color: var(--muted); font-size: 13px; }
.steps li:not(:last-child)::after { content: ""; flex: 1 1 auto; height: 2px; min-width: 12px; margin: 0 8px; background: var(--line); }
.steps li.is-done:not(:last-child)::after { background: var(--ok); }
.steps .mark { flex: none; display: inline-grid; place-items: center; width: 24px; height: 24px; border-radius: 50%; border: 2px solid var(--line-strong); background: var(--surface); font-weight: 600; font-size: 12px; }
.steps li.is-done .mark { border-color: var(--ok); background: var(--ok) var(--tick-mark) center / 14px 14px no-repeat; color: transparent; }
.steps li.is-current { color: var(--ink); font-weight: 600; }
.steps li.is-current .mark { border-color: var(--accent); background: var(--accent); color: var(--accent-ink); }
.steps.is-stopped li.is-current .mark { border-color: var(--bad); background: var(--bad); }
/* Said to a screen reader and not shown: what a mark on screen already says to the eye. */
.sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.steps .word { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
@media (max-width: 860px) {
  .steps { flex-wrap: wrap; gap: 8px 14px; }
  .steps li { flex: none; }
  .steps li:not(:last-child)::after { display: none; }
}
.layout-main { display: grid; grid-template-columns: minmax(0, 2fr) minmax(0, 1fr); gap: 16px; align-items: start; }
@media (max-width: 1100px) { .layout-main { grid-template-columns: minmax(0, 1fr); } }
.kv { display: grid; grid-template-columns: max-content 1fr; gap: 6px 16px; font-size: 14px; }
.kv .k { color: var(--muted); }

/* The migration card: the move's settings on the left, what the last reading found set apart on the right; one column on a phone. */
.move-source { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 2.2fr); gap: 16px 40px; align-items: start; }
.move-found { border-left: 1px solid var(--line); padding-left: 32px; min-width: 240px; }
.move-found h3 { margin: 0; font-size: 13px; font-weight: 600; color: var(--muted); }
@media (max-width: 860px) {
  .move-source { grid-template-columns: 1fr; }
  .move-found { border-left: 0; border-top: 1px solid var(--line); padding: 12px 0 0; min-width: 0; }
}
.muted { color: var(--muted); }
.nowrap { white-space: nowrap; }
/* A second line in a grid cell that is cut short rather than wrapped: an item's name under its SKU. It asks for no
   width of its own (width 0, then the cell's), so the column is as wide as the line above it and the name fits that. */
.cell-clip { display: block; width: 0; min-width: 100%; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
/* A button in a table row is one action: "Review order" broke in two, its border with it. */
td .btn { white-space: nowrap; }
.small { font-size: 13px; }
.title-pills { display: inline-flex; gap: 6px; vertical-align: middle; margin-left: 8px; flex-wrap: wrap; }
.mt { margin-top: 16px; }
.tile { background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); padding: 14px 16px; }
.tile .k { font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.tile .v { font-family: var(--display); font-size: 26px; font-weight: 600; font-variant-numeric: tabular-nums; }
/* What a reading of the old system found (the migration card's right side): short lists in the old system's words, a
   number on each line and under it, small, what it becomes here. */
.read-head { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 6px; }
.read-figures { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px 28px; font-size: 13px; }
.read-figures dl { margin: 0; }
.read-group { font-family: var(--mono); font-size: 11px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); padding-bottom: 2px; border-bottom: 1px solid var(--line-strong); }
.read-figures dl > div:not(.read-group) { display: flex; justify-content: space-between; align-items: baseline; gap: 12px; padding: 4px 0; border-bottom: 1px solid var(--line); }
.read-figures dl > div:last-child { border-bottom: 0; }
.read-figures dt span { display: block; font-size: 12px; color: var(--muted); line-height: 1.3; }
.read-figures dd { margin: 0; font-weight: 600; font-variant-numeric: tabular-nums; }
.read-figures .is-warn dd { color: var(--amber); }

/* Tables */
.tablewrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--surface); }
table { border-collapse: collapse; width: 100%; font-size: 14px; }
th, td { text-align: left; vertical-align: top; padding: 8px 12px; border-bottom: 1px solid var(--line); }
th { font-family: var(--mono); font-weight: 500; font-size: 12px; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); background: var(--surface-2); white-space: nowrap; }
tr:last-child td { border-bottom: 0; }
td.n, th.n { text-align: right; font-variant-numeric: tabular-nums; }
/* A queue with many columns: narrower cell padding, and a heading that may take two lines, so it fits without scrolling sideways. */
table.tight th, table.tight td { padding-left: 8px; padding-right: 8px; }
th.wrap { white-space: normal; }
/* Grid totals (Grid.Sum / Grid.Total). A footer holds nothing but totals of number columns, so every footer cell lines
   up on the right with the numbers above it, and the first cell says what the row is when it has no total of its own. */
/* How tall a grid may grow before it scrolls (Grid.Standard, Grid.InCard). A maximum, not a height: a short list is
   only as tall as its rows, and a long one scrolls inside the same bound, header fixed and rows virtualized. */
.grid-standard .mud-table-container { max-height: min(62vh, 700px); overflow-y: auto; }
.grid-in-card .mud-table-container { max-height: min(48vh, 520px); overflow-y: auto; }

/* A column's sort and filter buttons belong to its title. MudBlazor spreads the two to opposite edges of the column,
   which leaves the icons nearer the next column's title than their own; here they follow the title, and in a column of
   numbers the two sit together on the right, over the numbers. */
.mud-data-grid .column-header { justify-content: flex-start; gap: 2px; }
/* MudBlazor pads a cell 16px left and 24px right, which on a ledger of nine or ten columns is a grid wider than the
   screen; a grid's cells pad 10px a side, near the app's own tables. */
.mud-data-grid .mud-table-row .mud-table-cell { padding-left: 10px; padding-right: 10px; }
/* A column's options button (group, hide) held 26px in every header, a tenth of a laptop's grid across ten columns.
   It sits over the header's right edge instead, shown when the header is pointed at or tabbed into. */
.mud-data-grid th.mud-table-cell { position: relative; }
.mud-data-grid th .column-options { position: absolute; right: 0; top: 50%; transform: translateY(-50%); opacity: 0; background: var(--surface-2); }
.mud-data-grid th:hover .column-options, .mud-data-grid th:focus-within .column-options { opacity: 1; }
.mud-data-grid th.n .column-header { justify-content: flex-end; }
/* An unsorted column's sort arrow and sort number are only transparent, and still hold 35px between the title and its
   filter. They take no room until the column is sorted; clicking the title still sorts it. */
.mud-data-grid .sort-direction-icon:not(.mud-direction-asc):not(.mud-direction-desc),
.mud-data-grid .mud-sort-index.invisible { display: none; }
table tfoot .footer-cell { text-align: right; font-variant-numeric: tabular-nums; border-top: 1px solid var(--line-strong); }
table tfoot .footer-cell:first-child { text-align: left; }
table tfoot .footer-cell:first-child:empty::before { content: "Total"; font-weight: 600; color: var(--muted); }

tr.clickable { cursor: pointer; }
tr.clickable:hover td { background: var(--accent-soft); }
tr.sel td { background: var(--accent-soft); }
td .row { flex-wrap: wrap; }
.empty { color: var(--muted); padding: 18px; text-align: center; }

/* Forms */
label { display: block; font-size: 13px; color: var(--ink-2); margin-bottom: 4px; }
input, select, textarea { width: 100%; font: inherit; color: var(--ink); background: var(--surface); border: 1px solid var(--line-strong); border-radius: var(--radius); padding: 8px 10px; }
input:focus, select:focus { border-color: var(--accent); }
/* A checkbox or radio is its own size, not the width of the field: at 100% it pushes its label onto the next line. */
input[type="checkbox"], input[type="radio"] { width: auto; padding: 0; }
/* The browser's own arrow sits against the border whatever the padding says, so a select draws its own, 12px in,
   and keeps the text clear of it. The chevron is a token so it follows dark mode. */
select {
  appearance: none; -webkit-appearance: none;
  background-image: var(--select-chevron); background-repeat: no-repeat; background-position: right 12px center; background-size: 12px 8px;
  padding-right: 34px;
}
select:disabled { opacity: 0.55; }
/* A dropdown that can be typed into and searched on the server (SearchCombo): it looks like a select, and its list hangs under it, over what follows. */
.combo { position: relative; }
.combo input {
  background-image: var(--select-chevron); background-repeat: no-repeat; background-position: right 12px center; background-size: 12px 8px;
  padding-right: 34px; text-overflow: ellipsis;
}
.combo input:disabled { opacity: 0.55; }
.combo-list {
  position: absolute; z-index: 20; top: calc(100% + 4px); left: 0; min-width: 100%; width: max-content; max-width: min(520px, calc(100vw - 32px));
  max-height: 280px; overflow-y: auto;
  margin: 0; padding: 4px 0; list-style: none; background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(15, 20, 24, 0.18);
}
/* A box at the right of a form opens its list leftwards, so a long name never runs off the screen. */
.combo-end .combo-list { left: auto; right: 0; }
.combo-list li { padding: 7px 10px; cursor: pointer; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.combo-list li.is-active { background: var(--surface-2); }
.combo-list li.is-chosen { font-weight: 600; }
.combo-list li.combo-note { cursor: default; color: var(--muted); font-size: 13px; white-space: normal; }
/* A dropdown typed into (SearchSelect): drawn as a select, its list over whatever is below it. */
.search-select { position: relative; width: 100%; }
.search-select > input {
  background-image: var(--select-chevron); background-repeat: no-repeat; background-position: right 12px center; background-size: 12px 8px;
  padding-right: 34px; text-overflow: ellipsis;
}
.search-select > input:disabled { opacity: 0.55; }
.search-select-list {
  position: absolute; z-index: 95; top: calc(100% + 2px); left: 0; right: 0; max-height: 280px; overflow-y: auto;
  margin: 0; padding: 4px 0; list-style: none; background: var(--surface); color: var(--ink);
  border: 1px solid var(--line-strong); border-radius: var(--radius); box-shadow: 0 8px 24px rgba(15, 20, 24, 0.18);
}
.search-select-list li { padding: 7px 10px; cursor: pointer; }
.search-select-list li.is-active { background: var(--accent-soft); }
.search-select-list li.is-chosen { font-weight: 600; }
.search-select-list li.search-select-none { color: var(--muted); cursor: default; font-size: 13px; }
.field { margin-bottom: 12px; }
.form-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 12px; }
/* Two fields that belong together, a start and an end, kept side by side in two columns of the form. Only where the
   grid has two columns to give: spanning two in a one-column grid would make a second column and push the form sideways. */
.field-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
@media (min-width: 621px) { .field-pair { grid-column: span 2; } }
/* A list to choose from inside a form: its own scroll with the header pinned, a click anywhere on a row to choose it. */
.pick-table { max-height: 280px; overflow-y: auto; }
.pick-table thead th { position: sticky; top: 0; z-index: 1; }
.pick-table tbody tr { cursor: pointer; }
.pick-table tbody tr:hover td { background: var(--surface-2); }
.pick-table tr.is-chosen td { background: var(--accent-soft); }
.pick-table td:first-child, .pick-table th:first-child { width: 1%; padding-right: 0; }
.pick-table input[type="checkbox"] { width: auto; }
/* Lets a later one-column field fill the hole a two-column one leaves when it wraps. */
.form-grid.dense { grid-auto-flow: row dense; }
.check { display: flex; align-items: center; gap: 8px; }
.check input { width: auto; }
/* A yes or no that qualifies the field it sits in, like whether the seal was intact: under that field's input, where
   no width of the grid can move it beside some other field. */
.field > .check { margin: 6px 0 0; }

/* Buttons */
.btn { font: inherit; font-weight: 500; padding: 8px 14px; border-radius: var(--radius); border: 1px solid var(--line-strong); background: var(--surface); color: var(--ink); cursor: pointer; }
.btn:hover { background: var(--surface-2); }
.btn:disabled { opacity: 0.55; cursor: default; }
.btn-primary { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
.btn-primary:hover { filter: brightness(1.08); background: var(--accent); }
.btn-ghost { background: transparent; border-color: transparent; color: var(--accent); }
.btn-danger { color: var(--bad); border-color: var(--bad); }
.btn-sm { font-size: 13px; padding: 4px 10px; }
/* The chosen one of a row of filters or tabs: marked, not painted in the page's primary colour, which belongs to the
   one thing the page is for. aria-pressed says the same to a screen reader. */
/* A row's way in for a keyboard: the row still opens on a click anywhere, and its name is a real button. */
.link-button { background: none; border: 0; padding: 0; font: inherit; color: var(--accent); cursor: pointer; text-decoration: underline; text-align: left; }
.btn.is-on { background: var(--accent-soft); border-color: var(--accent); color: var(--accent); font-weight: 600; }

/* Pills and banners */
.pill { display: inline-block; font-family: var(--mono); font-size: 11.5px; letter-spacing: 0.04em; padding: 2px 8px; border-radius: 999px; border: 1px solid transparent; white-space: nowrap; }
.pill-ok { background: var(--ok-soft); color: var(--ok); }
.pill-warn { background: var(--amber-soft); color: var(--amber); }
.pill-bad { background: var(--bad-soft); color: var(--bad); }
.pill-muted { background: var(--surface-2); color: var(--muted); }
.pill-info { background: var(--accent-soft); color: var(--accent); }
.banner { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; padding: 10px 14px; border-radius: var(--radius); border: 1px solid; margin-bottom: 14px; }
.banner-error { background: var(--bad-soft); border-color: var(--bad); color: var(--ink); }
.banner-ok { background: var(--ok-soft); border-color: var(--ok); }
.banner-warn { background: var(--amber-soft); border-color: var(--amber); }
.banner-info { background: var(--accent-soft); border-color: var(--accent); }
.banner code { background: transparent; color: var(--muted); }

/* Scan field */
.scan-field { display: flex; gap: 8px; }
.scan-input { font-family: var(--mono); font-size: 18px; letter-spacing: 0.04em; text-transform: uppercase; }

/* Handheld: big targets, one task per screen, no mouse assumed. Flat: no gradients, no shadows but the one that
   keeps a refusal in sight; colour says what waits, a line icon says which job, a word names it. */
.handheld { max-width: 480px; margin: 0 auto; padding: 0 16px 32px; display: flex; flex-direction: column; gap: 14px; min-height: 100vh; font-size: 16px; }
.handheld h1 { font-size: 24px; line-height: 1.2; margin-top: 4px; }
.handheld .btn { min-height: 56px; font-size: 17px; border-radius: 10px; transition: background-color 150ms ease, border-color 150ms ease; }
.handheld .btn-primary { font-weight: 600; }
.handheld .btn-ghost { min-height: 48px; }
.handheld input, .handheld select { min-height: 56px; font-size: 18px; border-radius: 10px; padding: 10px 14px; }
.handheld textarea { font-size: 17px; border-radius: 10px; padding: 10px 14px; }
.handheld label { font-size: 15px; font-weight: 500; }
.handheld .card { border-radius: 12px; }
/* The button that does the step takes the row; what sits beside it stays its own width. */
.handheld .row > .btn-primary { flex: 1 1 auto; }
/* Pressed is what a finger sees: there is no hover on a glove. */
.handheld .btn:active, .handheld button.hh-step:active { background: var(--accent-soft); border-color: var(--accent); }
.handheld .btn-primary:active { background: var(--accent); filter: brightness(0.92); }
.handheld button.hh-step { cursor: pointer; text-align: left; width: 100%; font: inherit; color: inherit; }
/* A refusal stays in sight however far down the step is. */
.handheld .banner-error { position: sticky; top: 72px; z-index: 10; border-radius: 10px; box-shadow: 0 4px 12px rgba(23, 32, 41, 0.18); }
.handheld .banner { border-radius: 10px; }
.handheld .scan-type { align-self: flex-start; }
/* A scan box looks like one: the barcode drawn at its start, the code in large mono capitals. */
.handheld .scan-input { padding-left: 46px; background-image: var(--scan-mark); background-repeat: no-repeat; background-position: 14px center; background-size: 22px 22px; }
.hh-icon { flex: none; display: block; }
.hh-bar { display: flex; justify-content: space-between; align-items: center; gap: 8px; font-size: 14px; color: var(--muted); }
.hh-bar b { color: var(--ink); font-weight: 500; }

/* The bar on every floor screen: who and where, what waits on the device, the way back. Stays put while the step scrolls. */
.hh-appbar { position: sticky; top: 0; z-index: 20; display: flex; align-items: center; gap: 10px; margin: 0 -16px; padding: 10px 16px; min-height: 64px; background: var(--surface); border-bottom: 1px solid var(--line); }
.hh-who { display: flex; flex-direction: column; min-width: 0; flex: 1 1 auto; line-height: 1.25; }
.hh-who b { font-family: var(--display); font-size: 17px; font-weight: 600; color: var(--ink); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hh-who small { font-size: 13px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.hh-pill { display: inline-flex; align-items: center; gap: 6px; min-height: 44px; padding: 0 12px; border-radius: 999px; background: var(--accent-soft); color: var(--accent); font-weight: 600; font-size: 14px; text-decoration: none; white-space: nowrap; }
.hh-pill.is-bad { background: var(--bad-soft); color: var(--bad); }
.handheld .hh-menu-btn, .hh-foot .btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; }
.handheld .hh-menu-btn { min-height: 44px; padding: 0 14px; font-size: 15px; flex: none; }

/* The menu: jobs as tiles, tools as a list. */
.hh-section { margin: 6px 0 -4px; font-family: var(--sans); font-size: 13px; font-weight: 600; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); }
.hh-menu { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.hh-tile { position: relative; display: flex; flex-direction: column; align-items: flex-start; gap: 4px; min-height: 124px; padding: 16px; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; color: var(--ink); text-decoration: none; transition: background-color 150ms ease, border-color 150ms ease; }
.hh-tile-icon { display: grid; place-items: center; width: 44px; height: 44px; margin-bottom: 6px; border-radius: 12px; background: var(--surface-2); color: var(--muted); }
.hh-tile-name { font-family: var(--display); font-weight: 600; font-size: 18px; line-height: 1.2; }
.hh-tile small { font-family: var(--sans); font-weight: 400; color: var(--muted); font-size: 14px; line-height: 1.3; }
/* A job with something waiting draws the eye: the count, the colour, the edge. An idle job stays quiet. */
.hh-tile.has-work { border-color: var(--accent); }
.hh-tile.has-work .hh-tile-icon { background: var(--accent-soft); color: var(--accent); }
.hh-count { position: absolute; top: 14px; right: 14px; min-width: 32px; height: 32px; padding: 0 10px; display: grid; place-items: center; border-radius: 999px; background: var(--accent); color: var(--accent-ink); font-weight: 700; font-size: 16px; font-variant-numeric: tabular-nums; }
.hh-tile:active { background: var(--accent-soft); border-color: var(--accent); }
.hh-tools { display: flex; flex-direction: column; background: var(--surface); border: 1px solid var(--line); border-radius: 14px; overflow: hidden; }
.hh-tools a { display: flex; align-items: center; gap: 14px; min-height: 64px; padding: 10px 16px; color: var(--ink); text-decoration: none; transition: background-color 150ms ease; }
.hh-tools a + a { border-top: 1px solid var(--line); }
.hh-tools a > span { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.hh-tools a b { font-family: var(--display); font-weight: 600; font-size: 17px; }
.hh-tools a small { color: var(--muted); font-size: 14px; }
.hh-tools a > .hh-icon:first-child { color: var(--accent); }
.hh-tools a > .hh-icon:last-child { color: var(--muted); }
.hh-tools a:active { background: var(--accent-soft); }
.hh-foot { display: flex; justify-content: space-between; gap: 12px; margin-top: 8px; }
.hh-foot .btn { flex: 1 1 0; color: var(--muted); border: 1px solid var(--line); }

/* A step: what to do now, read at arm's length -- words in ink, the code in large mono. */
.hh-step { position: relative; background: var(--surface); border: 1px solid var(--line); border-radius: 12px; padding: 16px; }
.hh-step .k { font-family: var(--sans); font-size: 15px; font-weight: 600; color: var(--ink); }
.hh-step .v { font-family: var(--mono); font-size: 22px; font-weight: 600; letter-spacing: 0.02em; }
/* Done is a green edge and a tick, not only a colour. */
.hh-done { border-color: var(--ok); background: var(--ok-soft); padding-left: 52px; }
.hh-done::before { content: ""; position: absolute; left: 16px; top: 18px; width: 24px; height: 24px; border-radius: 50%; background: var(--ok) var(--tick-mark) center / 14px 14px no-repeat; }
.hh-big { font-family: var(--display); font-size: 36px; font-weight: 700; font-variant-numeric: tabular-nums; }
.hh-keypad { display: grid; grid-template-columns: repeat(3, 1fr); gap: 10px; }
.hh-keypad .btn { min-height: 64px; font-size: 24px; justify-content: center; }
/* A list of work to choose from: rows that read like a list, the code first, a chevron saying it opens. */
.hh-list { display: flex; flex-direction: column; gap: 10px; }
.handheld .hh-list button, .handheld .hh-list a { position: relative; display: flex; flex-direction: column; align-items: flex-start; justify-content: center; gap: 2px; text-align: left; width: 100%; min-height: 64px; padding: 12px 44px 12px 16px; border-radius: 12px; background-image: var(--chevron-mark); background-repeat: no-repeat; background-position: right 14px center; background-size: 20px 20px; }
.hh-list .v { font-family: var(--mono); font-size: 18px; font-weight: 600; }
@media (prefers-reduced-motion: reduce) {
  .handheld .btn, .hh-tile, .hh-tools a { transition: none; }
}
.mt-sm { margin-top: 6px; }
.bar { display: flex; height: 8px; border-radius: 4px; overflow: hidden; background: var(--line); }
.bar span { display: block; height: 100%; }
.bar .done { background: var(--ok); }
.bar .doing { background: var(--accent); }
.spark { display: grid; grid-template-columns: max-content 1fr max-content; gap: 6px 10px; align-items: center; font-size: 13px; }
.spark .track { background: var(--line); height: 8px; border-radius: 4px; overflow: hidden; }
.spark .track span { display: block; height: 100%; background: var(--accent); }
.choices { display: flex; flex-wrap: wrap; gap: 8px; }


/* Dock board: doors down the side, the working day across. A window is drawn where it is, because "is there room at
   four" is a question answered by looking. Positions come from the page as percentages of the day on screen. */
.dock-scroll { overflow-x: auto; overscroll-behavior-x: contain; padding-bottom: 4px; }
.dock-board { display: flex; flex-direction: column; gap: 4px; }
/* The door column stays put while the hours scroll under it; the shadow covers the gap so nothing shows through. */
.dock-board .dock-door { position: sticky; left: 0; z-index: 4; background: var(--surface); box-shadow: 8px 0 0 var(--surface); }
/* An empty spot on a door books a truck there, so it says it can be clicked. */
.dock-track[data-dock], .dock-week-cell { cursor: pointer; }
.dock-closed { position: absolute; top: 0; bottom: 0; background: repeating-linear-gradient(135deg, transparent 0 6px, var(--line) 6px 7px); }
/* Time already gone: a flat tint over the track, under the windows, so history reads as history. */
.dock-past { position: absolute; top: 0; bottom: 0; background: color-mix(in srgb, var(--ink) 7%, transparent); pointer-events: none; }
.dock-now { position: absolute; top: 0; bottom: 0; width: 0; border-left: 2px solid var(--bad); z-index: 1; pointer-events: none; }
.dock-ruler, .dock-lane { display: flex; align-items: stretch; gap: 8px; }
.dock-door { flex: 0 0 96px; display: flex; align-items: center; font-size: 13px; color: var(--muted); }
.dock-lane .dock-door { color: var(--ink); }
.dock-track { position: relative; flex: 1; height: 44px; background: var(--surface-2); border-radius: var(--radius); overflow: hidden; }
.dock-ruler .dock-track { height: 20px; background: transparent; }
.dock-hour { position: absolute; top: 0; bottom: 0; font-size: 11px; color: var(--muted); padding-left: 4px; border-left: 1px solid var(--line); }
/* Mixed from the ink rather than --line, which is nearly the track's own grey: the hour lines have to read on a door's
   track, on the time-gone shading over it and on the white "No door" row alike, in both themes. */
.dock-gridline { position: absolute; top: 0; bottom: 0; border-left: 1px solid color-mix(in srgb, var(--ink) 14%, transparent); }
.dock-slot {
  position: absolute; top: 3px; bottom: 3px; overflow: hidden; text-align: left; cursor: pointer;
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start; gap: 1px; padding: 2px 6px;
  border: 1px solid var(--line-strong); border-radius: var(--radius); background: var(--surface); color: var(--ink); font: inherit;
}
/* align-items is set on every button that lays out a column: MudBlazor's reset centres a button's content, and a class
   rule that says nothing about alignment inherits that. */
.dock-slot:hover { border-color: var(--accent); }
.dock-slot.is-selected { outline: 2px solid var(--accent); outline-offset: 1px; }
.dock-slot-time { font-size: 11px; line-height: 1.25; font-weight: 600; white-space: nowrap; }
.dock-slot-who { font-size: 11px; line-height: 1.25; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.dock-slot.is-in { background: var(--accent-soft); border-color: var(--accent); }
.dock-slot.is-out { background: var(--amber-soft); border-color: var(--amber); }
.dock-slot.is-arrived { background: var(--amber-soft); border-color: var(--amber); }
.dock-slot.is-working { background: var(--ok-soft); border-color: var(--ok); }
.dock-slot.is-done { background: var(--surface-2); border-color: var(--line); }
.dock-slot.is-done .dock-slot-time { text-decoration: line-through; color: var(--muted); }

/* A count that was taken back stays on the receipt, struck through, above the correction that took it back. */
tr.line-reversed td { color: var(--muted); }
tr.line-reversed td:nth-child(-n+5) { text-decoration: line-through; }
.dock-slot.is-off { background: var(--bad-soft); border-color: var(--bad); opacity: 0.7; }
/* Dragging: a booked window is grabbed anywhere and resized by its right edge. touch-action stops a finger's drag from
   scrolling the page instead of moving the window. */
.dock-slot[data-window] { cursor: grab; touch-action: none; user-select: none; }
.dock-slot-grip { position: absolute; top: 0; right: 0; bottom: 0; width: 8px; cursor: ew-resize; }
.dock-slot-grip::after { content: ""; position: absolute; top: 30%; bottom: 30%; right: 3px; border-right: 2px solid currentColor; opacity: 0.25; }
.is-dragging, .is-dragging .dock-slot { cursor: grabbing; user-select: none; }
.dock-slot.is-dragged { z-index: 2; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.18); pointer-events: none; }
.dock-slot.is-clash { background: var(--bad-soft); border-color: var(--bad); }
.dock-slot.is-clash .dock-slot-who { color: var(--bad); }
.dock-slot.is-lifted { opacity: 0.35; }
/* The row for windows with no door: set apart from the doors, since nothing on it holds one. */
.dock-lane-nodoor { margin-top: 6px; }
.dock-lane-nodoor .dock-track { background: var(--surface); box-shadow: inset 0 0 0 1px var(--line-strong); }

/* Week: doors down the side, a small timeline per door per day, one clock across the whole week. */
.dock-week { display: flex; flex-direction: column; gap: 4px; }
.dock-week-row { display: flex; gap: 4px; align-items: stretch; }
.dock-week-row > .dock-week-day, .dock-week-row > .dock-week-cell { flex: 1 1 0; min-width: 0; }
.dock-week .dock-door { flex: 0 0 96px; position: sticky; left: 0; z-index: 4; background: var(--surface); box-shadow: 4px 0 0 var(--surface); }
.dock-week-day {
  display: flex; flex-direction: column; align-items: flex-start; gap: 1px; padding: 4px 6px; text-align: left; cursor: pointer; font: inherit; color: var(--ink);
  background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.dock-week-day:hover, .dock-month-day:hover { border-color: var(--accent); }
.dock-week-day.is-today, .dock-month-day.is-today { border-color: var(--accent); box-shadow: inset 0 0 0 1px var(--accent); }
.dock-week-day.is-closed, .dock-month-day.is-closed { background: repeating-linear-gradient(135deg, var(--surface) 0 6px, var(--line) 6px 7px); }
.dock-week-date { font-weight: 600; font-size: 13px; }
.dock-week-cell { position: relative; height: 34px; background: var(--surface-2); border-radius: var(--radius); overflow: hidden; }
.dock-slot.dock-mini { top: 3px; bottom: 3px; padding: 0 3px; font-size: 10px; line-height: 1; white-space: nowrap; }
.dock-mini .dock-slot-grip { width: 5px; }
.dock-mini .dock-slot-grip::after { right: 1px; }

/* Month: a calendar a planner reads for how full each day is. */
.dock-month { display: grid; grid-template-columns: repeat(7, minmax(0, 1fr)); gap: 4px; }
.dock-month-head { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: 0.04em; padding: 0 4px; }
.dock-month-day {
  display: flex; flex-direction: column; align-items: flex-start; gap: 3px; min-height: 84px; padding: 6px; min-width: 0;
  text-align: left; cursor: pointer; font: inherit; color: var(--ink); background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius);
}
.dock-month-day.is-other { opacity: 0.45; }
.dock-month-date { font-weight: 600; }
.dock-load { display: block; width: 100%; height: 6px; background: var(--surface-2); border-radius: 3px; overflow: hidden; }
.dock-load-fill { display: block; height: 100%; background: var(--accent); }
.dock-load-fill.is-busy { background: var(--amber); }
.dock-load-fill.is-full { background: var(--bad); }
.dock-warn { color: var(--amber); font-weight: 600; }


/* Creating and editing happens over the page. The overlay is its own scroll container and contains its overscroll,
   so the list behind stays exactly where the reader left it without a line of JavaScript to hold it there. */
.modal-backdrop {
  position: fixed; inset: 0; z-index: 90; display: flex; justify-content: center; align-items: flex-start;
  padding: 48px 16px; overflow-y: auto; overscroll-behavior: contain;
  background: rgba(15, 20, 24, 0.45); backdrop-filter: blur(1px);
}
.modal {
  width: min(560px, 100%); background: var(--surface); color: var(--ink);
  border: 1px solid var(--line); border-radius: var(--radius); box-shadow: 0 12px 32px rgba(15, 20, 24, 0.22);
}
.modal-wide { width: min(880px, 100%); }
.modal:focus { outline: none; }
.modal-head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: 12px;
  padding: 14px 18px; border-bottom: 1px solid var(--line);
}
.modal-head h2 { font-size: 18px; }
.modal-head p { margin: 4px 0 0; max-width: 60ch; }
.modal-body { padding: 16px 18px; }
.modal-body > :first-child { margin-top: 0; }
/* What an attempt came to takes the focus so a long form scrolls back up to it; the banner is its own outline. */
.modal-outcome:focus { outline: none; }
.modal-body h2 { font-size: 15px; margin-top: 16px; }
.modal-foot {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  padding: 12px 18px; border-top: 1px solid var(--line); background: var(--surface-2);
  border-radius: 0 0 var(--radius) var(--radius);
}
.modal-foot .spacer { flex: 1; }

@media (max-width: 620px) {
  .modal-backdrop { padding: 16px 8px; }
}

/* Practice (learn mode). The band sits on top of every page while the app is in practice; the handheld beside the
   office screen is a phone-sized frame in a column of its own, so it never covers the page it is part of. */
.practice-band { position: sticky; top: 0; z-index: 20; display: flex; flex-wrap: wrap; align-items: center; gap: 8px 12px; margin: -28px -32px 20px; padding: 10px 32px;
  background: var(--practice); color: var(--practice-ink); font-size: 14px; }
.practice-band .tag { font-family: var(--display); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; }
.practice-band .spacer { flex: 1; }
.practice-band .btn { background: transparent; color: var(--practice-ink); border-color: var(--practice-ink); }
.practice-band .btn:hover { background: rgba(255, 255, 255, 0.14); }
.app.has-handheld { grid-template-columns: 232px minmax(0, 1fr) 392px; }
.practice-handheld { position: sticky; top: 0; height: 100vh; display: flex; flex-direction: column; gap: 8px; padding: 12px; background: var(--practice-soft); border-left: 3px solid var(--practice); }
.practice-handheld-head { display: flex; align-items: center; gap: 8px; }
.practice-handheld-head .spacer { flex: 1; }
.hh-preview-phone { display: block; width: min(420px, 100%); height: calc(100vh - 220px); min-height: 520px; border: 10px solid var(--ink); border-radius: 24px; background: var(--ground); }
.practice-phone { flex: 1; width: 100%; min-height: 0; border: 10px solid var(--ink); border-radius: 24px; background: var(--ground); }
.practice-strip { margin: 0 -16px -14px; padding: 6px 16px; background: var(--practice); color: var(--practice-ink); font-size: 13px; font-weight: 600; display: flex; flex-wrap: wrap; align-items: center; justify-content: center; gap: 4px 10px; }
.practice-strip .btn { background: transparent; color: var(--practice-ink); border-color: var(--practice-ink); }
.practice-strip .btn[aria-pressed="true"] { background: var(--practice-ink); color: var(--practice); }
@media (max-width: 1100px) {
  .app.has-handheld { grid-template-columns: 232px minmax(0, 1fr); }
  .practice-handheld { position: fixed; right: 12px; bottom: 12px; top: auto; width: 360px; height: min(720px, calc(100vh - 24px)); z-index: 30; border: 3px solid var(--practice); border-radius: var(--radius); }
}
@media (max-width: 860px) {
  .app.has-handheld { grid-template-columns: 1fr; }
  .practice-band { position: static; margin: -20px -16px 16px; padding: 10px 16px; }
  .practice-band .show-handheld, .practice-handheld { display: none; }
}

/* The demo player: the band becomes the demo's caption and controls, and stays above a dialog the demo opened. */
.practice-band.demo-player { z-index: 95; flex-direction: column; align-items: stretch; gap: 6px; }
.demo-caption { font-size: 14px; line-height: 1.4; min-height: 1.4em; } /* the practice band's own size, so every message on top reads alike */
.demo-controls { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.demo-controls .spacer { flex: 1; }
.demo-speeds { display: inline-flex; gap: 2px; }
.practice-band .btn.is-on { background: var(--practice-ink); color: var(--practice); }
.practice-band .btn.demo-resume { background: var(--practice-ink); color: var(--practice); font-weight: 600; }
.learn-pointer { position: fixed; left: 0; top: 0; z-index: 10000; pointer-events: none; margin: -2px 0 0 -3px; filter: drop-shadow(0 2px 3px rgba(0, 0, 0, 0.35)); }
.learn-pointer svg { display: block; transition: transform 120ms ease; transform-origin: 3px 2px; }
.learn-pointer.press svg { transform: scale(0.82); }
.learn-ring { position: fixed; z-index: 9999; pointer-events: none; border: 3px solid var(--practice); border-radius: 8px; box-shadow: 0 0 0 6px rgba(107, 63, 160, 0.22); opacity: 0; transition: opacity 180ms ease; }
.learn-ring.on { opacity: 1; animation: learn-ring 1.2s ease-in-out infinite; }
@keyframes learn-ring { 50% { box-shadow: 0 0 0 10px rgba(107, 63, 160, 0.12); } }
@media (prefers-reduced-motion: reduce) { .learn-ring.on { animation: none; } }
.learn-module { margin: 24px 0 12px; }
.learn-table td { padding: 6px 12px; }
.learn-table .learn-title { font-weight: 400; }
.learn-table a.learn-title { color: var(--ink); text-decoration: none; }
.learn-table a.learn-title:hover { color: var(--accent); text-decoration: underline; }
.learn-table tr.is-off { opacity: 0.6; }
.learn-table td .small { margin-top: 2px; }

/* A guided lesson's bar is one row: what to do now on the left, its buttons on the right. */
.practice-band.demo-player.is-guided { flex-direction: row; align-items: center; flex-wrap: wrap; }
.practice-band.demo-player.is-guided .demo-caption { flex: 1 1 320px; }
.practice-band.demo-player.is-guided .demo-controls { flex: 0 0 auto; }
.lesson-start { max-width: 720px; }
.demo-step { margin-right: 4px; white-space: nowrap; opacity: 0.9; }

/* A phone: two tiles to a row, so what needs the reader comes sooner. Last, so it outranks the rules it adjusts. */
@media (max-width: 620px) {
  body { font-size: 16px; }
  .tiles { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; }
  .tile { padding: 12px; }
  .tile .v { font-size: 22px; }
}

/* On a touch screen an option in a typed-into dropdown is a target like a button. */
@media (pointer: coarse) {
  .combo-list li { min-height: 44px; padding: 12px 10px; }
}

/* The warehouse map (Setup > Warehouse map, and the live map on the rail). Drawn in metres in an SVG that widens with the
   zoom inside its own scroll box; strokes stay a pixel wide at any zoom. Every colour is a token or mixed from one, so
   dark mode follows by itself. How full reads on one hue, pale to strong, so it never says red where nothing is wrong. */
.floor-card { padding-bottom: 10px; }
.floor-tools, .floor-chosen, .floor-legend { display: flex; flex-wrap: wrap; align-items: center; gap: 6px 8px; }
.floor-tools .spacer, .floor-chosen .spacer { flex: 1; }
.floor-chosen { min-height: 40px; margin: 8px 0; padding: 6px 10px; border-radius: var(--radius); background: var(--surface-2); }
.floor-legend { margin: 8px 0; font-size: 13px; color: var(--ink-2); gap: 4px 14px; }
.floor-key { display: inline-flex; align-items: center; gap: 6px; }
.floor-swatch { display: inline-block; width: 14px; height: 14px; border-radius: 2px; border: 1px solid var(--line-strong); }
.floor-snap, .floor-client { max-width: 180px; }
.floor-find { display: flex; gap: 6px; }
.floor-find input { max-width: 260px; }
.floor-zoom { display: inline-flex; align-items: center; gap: 4px; }
.floor-zoom-at { min-width: 44px; text-align: center; font-family: var(--mono); }
.floor-keys { outline: none; border-radius: var(--radius); }
.floor-keys:focus-visible { box-shadow: 0 0 0 2px var(--accent); }
.floor { position: relative; }
.floor-scroll { overflow: auto; max-height: 72vh; border: 1px solid var(--line); border-radius: var(--radius); background: var(--ground); overscroll-behavior: contain; }
.floor-svg { display: block; min-width: 100%; height: auto; user-select: none; -webkit-user-select: none; touch-action: none; cursor: grab; }
.floor.is-panning .floor-svg { cursor: grabbing; }
.floor-foot { display: flex; flex-wrap: wrap; margin-top: 8px; }
.floor-dirty { color: var(--amber); font-weight: 600; }
.floor-issues { margin: 0; padding-left: 18px; }
.floor-issues li { margin: 4px 0; }

.floor-svg rect, .floor-svg path { vector-effect: non-scaling-stroke; }
.fm-floor { fill: var(--surface); stroke: var(--line-strong); stroke-width: 2; }
.fm-grid-line { fill: none; stroke: var(--line); stroke-width: 0.5; opacity: 0.7; }
.fm-grid-major { stroke: var(--line-strong); opacity: 0.6; }
/* Letter spacing set in pixels on the page would be metres here, so it is put back to none. */
.floor-svg text { font-family: var(--sans); fill: var(--ink-2); pointer-events: none; letter-spacing: normal; word-spacing: normal; }
.fm-area { fill: color-mix(in srgb, var(--fm-tone, var(--muted)) 7%, transparent); stroke: color-mix(in srgb, var(--fm-tone, var(--muted)) 55%, transparent); stroke-width: 1; stroke-dasharray: 6 4; }
.fm-area-label { font-weight: 600; fill: color-mix(in srgb, var(--fm-tone, var(--muted)) 80%, var(--ink)); }
.fm-fn-storage, .fm-fn-pickface, .fm-fn-supplies { --fm-tone: var(--accent); }
.fm-fn-receiving, .fm-fn-dock, .fm-fn-staging, .fm-fn-packing { --fm-tone: var(--ok); }
.fm-fn-returns, .fm-fn-quarantine { --fm-tone: var(--amber); }
.fm-fn-production { --fm-tone: var(--practice); }
.fm-wall { fill: var(--ink-2); stroke: none; }
.fm-label-box { fill: transparent; stroke: none; }
.fm-label { font-weight: 600; fill: var(--ink); }
.fm-rack { fill: var(--surface-2); stroke: var(--ink-2); stroke-width: 1.5; }
.fm-aisle { font-family: var(--mono); font-weight: 700; fill: var(--ink); }
.fm-cell rect, .fm-spot { fill: var(--surface-2); stroke: var(--line-strong); stroke-width: 1; }
.fm-cell-label { font-family: var(--mono); fill: var(--ink-2); }
.fm-spot-shape .fm-spot { stroke: var(--ink-2); stroke-width: 1.5; }
.fm-spot-dock .fm-spot { fill: color-mix(in srgb, var(--ok) 18%, var(--surface)); }
.fm-spot-staging .fm-spot { fill: color-mix(in srgb, var(--ok) 9%, var(--surface)); stroke-dasharray: 5 3; }
.fm-spot-workstation .fm-spot { fill: color-mix(in srgb, var(--practice) 12%, var(--surface)); }
.fm-problem .fm-rack, .fm-problem .fm-spot { stroke: var(--bad); stroke-width: 2; stroke-dasharray: 5 3; }
.is-editable .fm-shape { cursor: move; }
.fm-selected { fill: none; stroke: var(--accent); stroke-width: 2.5; }
.fm-grip { fill: var(--surface); stroke: var(--accent); stroke-width: 2; }
.fm-grip-ew { cursor: ew-resize; }
.fm-grip-ns { cursor: ns-resize; }
.fm-grip-nwse { cursor: nwse-resize; }
.floor:not(.is-editable) .fm-cell[role="button"] { cursor: pointer; }
.floor:not(.is-editable) .fm-cell[role="button"]:hover rect { stroke: var(--accent); stroke-width: 2.5; }

/* How full: one hue, pale to strong; "used" is stock where nobody said how much the location takes. */
.fm-fill-empty rect, .floor-swatch.fm-fill-empty { fill: var(--surface); background: var(--surface); }
.fm-fill-low rect, .floor-swatch.fm-fill-low { fill: color-mix(in srgb, var(--accent) 22%, var(--surface)); background: color-mix(in srgb, var(--accent) 22%, var(--surface)); }
.fm-fill-half rect, .floor-swatch.fm-fill-half { fill: color-mix(in srgb, var(--accent) 48%, var(--surface)); background: color-mix(in srgb, var(--accent) 48%, var(--surface)); }
.fm-fill-high rect, .floor-swatch.fm-fill-high { fill: color-mix(in srgb, var(--accent) 74%, var(--surface)); background: color-mix(in srgb, var(--accent) 74%, var(--surface)); }
.fm-fill-full rect, .floor-swatch.fm-fill-full { fill: var(--accent); background: var(--accent); }
.fm-fill-full .fm-cell-label, .fm-fill-high .fm-cell-label { fill: var(--accent-ink); }
.fm-fill-used rect, .floor-swatch.fm-fill-used { fill: color-mix(in srgb, var(--muted) 40%, var(--surface)); background: color-mix(in srgb, var(--muted) 40%, var(--surface)); }
.fm-fill-none rect, .fm-st-none rect, .floor-swatch.fm-fill-none, .floor-swatch.fm-st-none { fill: transparent; background: transparent; stroke-dasharray: 3 3; }

/* Status: nothing to say is plain; blocked, counted and frozen each say so. */
.fm-st-ok rect, .floor-swatch.fm-st-ok { fill: var(--surface); background: var(--surface); }
.fm-st-blocked rect, .floor-swatch.fm-st-blocked { fill: var(--bad-soft); background: var(--bad-soft); stroke: var(--bad); }
.fm-st-counting rect, .floor-swatch.fm-st-counting { fill: var(--amber-soft); background: var(--amber-soft); stroke: var(--amber); }
.fm-st-frozen rect, .floor-swatch.fm-st-frozen { fill: var(--accent-soft); background: var(--accent-soft); stroke: var(--accent); stroke-dasharray: 4 2; }

/* Open work: one warm hue, pale to strong, with the number of tasks drawn in the bay. */
.fm-work-none rect, .floor-swatch.fm-work-none { fill: var(--surface); background: var(--surface); }
.fm-work-some rect, .floor-swatch.fm-work-some { fill: color-mix(in srgb, var(--amber) 25%, var(--surface)); background: color-mix(in srgb, var(--amber) 25%, var(--surface)); }
.fm-work-busy rect, .floor-swatch.fm-work-busy { fill: color-mix(in srgb, var(--amber) 55%, var(--surface)); background: color-mix(in srgb, var(--amber) 55%, var(--surface)); }
.fm-work-many rect, .floor-swatch.fm-work-many { fill: var(--amber); background: var(--amber); }
.fm-work-many .fm-cell-label { fill: var(--surface); font-weight: 700; }

/* A find: what it found stands out, everything else steps back. */
.fm-dim { opacity: 0.28; }
.fm-hit rect { stroke: var(--ink); stroke-width: 3; }

/* The handheld's map: where to go filled and ringed, where the job starts tinted and ringed with a dash. It fits the
   screen rather than scrolling, so a finger on it scrolls the page as anywhere else on a handheld. */
.hh-map-frame { border: 1px solid var(--line); border-radius: 12px; overflow: hidden; background: var(--ground); }
.hh-map-frame .floor-svg { width: 100%; max-height: 62vh; touch-action: auto; cursor: default; }
.fm-go rect, .floor-swatch.fm-go { fill: var(--accent); background: var(--accent); stroke: var(--accent); }
.fm-go .fm-cell-label { fill: var(--accent-ink); font-weight: 700; }
.fm-from rect, .floor-swatch.fm-from { fill: var(--amber-soft); background: var(--amber-soft); stroke: var(--amber); }
.fm-go-ring { fill: none; stroke: var(--accent); stroke-width: 3; }
.fm-from-ring { fill: none; stroke: var(--amber); stroke-width: 2.5; stroke-dasharray: 5 3; }
@media (prefers-reduced-motion: no-preference) {
  .fm-go-ring { animation: fm-go-pulse 1.6s ease-in-out infinite; }
}
@keyframes fm-go-pulse { 50% { stroke-opacity: 0.35; } }
.hh-map-btn { align-self: flex-start; }
.hh-sheet { position: fixed; inset: 0; z-index: 30; display: flex; flex-direction: column; background: var(--ground); }
.hh-sheet-bar { display: flex; align-items: center; justify-content: space-between; gap: 10px; min-height: 64px; padding: 10px 16px; background: var(--surface); border-bottom: 1px solid var(--line); }
.hh-sheet-bar b { font-family: var(--display); font-size: 18px; }
.hh-sheet-body { flex: 1 1 auto; overflow-y: auto; overscroll-behavior: contain; padding: 16px; }

@media (max-width: 860px) {
  .floor-scroll { max-height: 60vh; }
  .floor-find, .floor-find input { max-width: none; width: 100%; }
  .floor-snap, .floor-client { max-width: none; }
}

/* Get started (#607): a checklist drawn from /tenancy/setup. Phone width is one column; buttons stay 44px on touch via the global rule. */
.setup-pick { display: inline-flex; align-items: center; gap: 8px; font-size: 13px; color: var(--muted); }
.gs-progress { margin-bottom: 16px; max-width: 860px; }
.gs-bar { height: 8px; background: var(--surface-2); border-radius: 4px; overflow: hidden; margin-bottom: 6px; }
.gs-bar > span { display: block; height: 100%; background: var(--accent); }
.gs-group { max-width: 860px; margin-bottom: 16px; background: var(--surface); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow); overflow: hidden; }
.gs-head { display: flex; align-items: center; flex-wrap: wrap; gap: 12px; padding: 12px 16px; background: var(--surface-2); border-bottom: 1px solid var(--line); }
.gs-head h2 { font-family: var(--mono); font-size: 12px; font-weight: 500; letter-spacing: 0.08em; text-transform: uppercase; color: var(--muted); margin: 0; }
.gs-tally { margin-left: auto; }
.gs-steps, .gs-review { list-style: none; margin: 0; padding: 0; }
.gs-step { display: grid; grid-template-columns: 24px minmax(0, 1fr) auto; gap: 12px; align-items: center; padding: 12px 16px; border-top: 1px solid var(--line); }
.gs-step:first-child { border-top: 0; }
.gs-step.is-next { background: var(--accent-soft); }
.gs-dot { width: 20px; height: 20px; border-radius: 50%; border: 2px solid var(--line-strong); display: inline-flex; align-items: center; justify-content: center; font-size: 12px; }
.gs-dot.is-done { background: var(--ok); border-color: var(--ok); color: var(--surface); }
.gs-dot.is-skipped { background: var(--surface-2); color: var(--muted); }
.gs-title { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; font-weight: 500; }
.gs-title .tag { font-size: 11px; padding: 1px 6px; border-radius: 3px; background: var(--surface-2); color: var(--muted); font-weight: 400; }
.gs-actions { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; justify-content: flex-end; }
.gs-review { display: flex; flex-wrap: wrap; gap: 8px 20px; padding: 12px 16px; }
.gs-ready { max-width: 860px; display: flex; justify-content: space-between; align-items: flex-start; gap: 16px; flex-wrap: wrap; border: 1px dashed var(--line-strong); border-radius: var(--radius); padding: 14px 16px; }
.gs-check-title { font-size: 15px; font-weight: 600; margin: 0 0 6px; }
.gs-warn-title { margin: 10px 0 2px; color: var(--muted); }
.gs-issues { margin: 0; padding-left: 18px; font-size: 14px; }
@media (max-width: 860px) {
  .gs-step { grid-template-columns: 22px minmax(0, 1fr); }
  .gs-actions { grid-column: 2; justify-content: flex-start; }
}
