/* board.css — the week board's lane chrome (0052, extracted by 0057)
   =====================================================================================
   Loaded by BOTH shells. Everything here is the board itself: the lanes, the day heads, the
   drop targets, the grip, the ghost and the gap. Nothing here styles a card — the card is the
   one part of the board each shell renders its own way, so v2.css keeps its `.board-card`
   rules and style.css keeps the desktop's.

   Extracted rather than copied. 0057 exists because the board reached one shell and not the
   other; a second copy of these rules is how the two boards start looking different a month
   later with every check still green. */

.board-nav { display: flex; align-items: center; gap: 8px; }
.board-nav button { min-width: 44px; padding: 0 14px; }
.board-range { flex: 1; text-align: center; font-weight: 700; font-size: 14px; color: var(--muted); }

.board-pastdue { border: 1px solid #a06ee0; border-radius: var(--radius); background: #150f22; padding: 10px 12px; }
.board-pastdue > summary { cursor: pointer; font-weight: 700; color: #d6c2ff; min-height: 32px; display: flex; align-items: center; gap: 8px; }
.board-pastdue.is-clear { border-color: var(--line); background: var(--panel); }
.board-pastdue.is-clear > summary { color: var(--muted); }
/* A wrapping grid, not the column stack .board-drop gives a day. Fifteen overdue cards at
   full width is ~1700px of lane before the first day column appears -- the grid the operator
   opened the page for is then entirely below the fold. Same card, same width as a day column,
   flowed across the available room: fifteen becomes three rows on a desktop and the board is
   visible again.
   Deliberately NOT a max-height with overflow-y. A scrollable lane is a second scroller
   nested inside the page's own, and dragging OUT of it -- the move this lane exists for --
   would have to fight it. The <details> is the way to make it small. */
.board-pastdue .board-pastdue-lane {
  margin-top: 10px;
  display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 8px;
  min-height: 0; align-items: start;
}

.board-grid { display: grid; gap: 8px; }
.board-day { display: flex; flex-direction: column; min-width: 0; border: 1px solid var(--line); border-radius: 12px; background: var(--panel); padding: 8px; }
.board-day.is-today { border-color: var(--accent); background: #101c2b; }
.board-day.is-past { opacity: .82; }
.board-day.is-weekend { background: #131922; }
.board-day.is-drop-target { border-color: var(--accent); box-shadow: 0 0 0 2px rgba(90,169,255,.35) inset; }
.board-day-head { display: flex; align-items: baseline; gap: 6px; padding: 2px 2px 8px; border-bottom: 1px solid var(--line); margin-bottom: 8px;
  /* 0066 — wraps rather than overflowing. A lane is a seventh of the board, and "Mon · 25 Aug ·
     08:00-12:30 · +" does not fit at every width. Without this the + spilled out of its own lane
     and sat on top of the NEXT day's header, where a click landed on the wrong day -- and the
     harness caught it as a pointer interception rather than as a wrong date, which is the same
     bug wearing a different hat. */
  flex-wrap: wrap; min-width: 0; }
/* 0066 (C2) — the per-day +. Lives here, in the sheet BOTH shells load for the board, rather
   than twice in style.css and v2.css: one copy cannot drift from the other. */
.board-day-add {
  flex: none; border: 1px solid var(--line); background: transparent; color: var(--muted);
  border-radius: 6px; width: 20px; height: 20px; line-height: 1; padding: 0; cursor: pointer;
  font-size: 14px;
}
.board-day-add:hover { color: var(--text); border-color: var(--accent); }
.board-day-name { font-weight: 800; font-size: 13px; }
.board-day.is-today .board-day-name { color: var(--accent); }
.board-day-num { font-size: 11px; color: var(--muted); white-space: nowrap; }
/* min-height, not height: an empty day must still be a target big enough to drop onto. */
.board-drop { display: flex; flex-direction: column; gap: 8px; min-height: 64px; }
.board-empty { flex: 1; min-height: 56px; border: 1px dashed var(--line); border-radius: 10px; opacity: .35; }

/* 44px because it is the only thing on the card a thumb has to hit precisely, and a drag that
   misses starts a page scroll instead. touch-action:none is what keeps the browser from
   claiming the gesture: without it the pointermove stream stops after the first event and the
   card never leaves its lane. */
.board-grip {
  flex: 0 0 auto; display: flex; align-items: center; justify-content: center;
  position: absolute; top: 0; right: 0; width: 44px; height: 44px;
  color: var(--muted); font-size: 17px; line-height: 1; border-radius: 10px;
  touch-action: none; -webkit-user-select: none; user-select: none; cursor: grab;
}
.board-grip:active { cursor: grabbing; background: var(--panel-3); color: var(--text); }
.board-card.is-dragging { opacity: .35; }
/* pointer-events:none is load-bearing: elementFromPoint has to see the lane UNDER the ghost.
   With the ghost catching hits, every move resolves to the ghost and the card can only ever
   be dropped where it started. */
.board-ghost {
  position: fixed; top: 0; left: 0; z-index: 90; pointer-events: none;
  opacity: .95; transform: translate(-9999px, -9999px);
  box-shadow: var(--shadow); border-color: var(--accent);
}
.board-gap { height: 40px; border: 2px dashed var(--accent); border-radius: 10px; background: rgba(90,169,255,.08); }
body.board-dragging { -webkit-user-select: none; user-select: none; }

/* --- The purchases list (0072) ---------------------------------------------------------
   Here rather than in either shell's stylesheet for the reason this file exists: the panel is
   ONE mount rendered on the desktop and on v2/shop.html, so a second copy of these rules is
   how the two start looking different a month later with every check still green.

   `.grow` is redeclared under .purchases-panel because both shells scope their own copy to
   `.compact-item`, and a purchase row is not one. */
.purchases-panel .grow { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.purchase-form-grid { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; }
.purchase-form-grid .field { flex: 0 0 auto; }
.purchase-form-grid .field.grow { flex: 1 1 200px; }
.purchase-filters { gap: 8px; }
.purchase-filters input[type="search"] { flex: 1; min-width: 140px; }

.purchase-list { display: flex; flex-direction: column; gap: 8px; margin: 10px 0; }
.purchase-row { display: flex; align-items: flex-start; gap: 10px; padding: 10px; border: 1px solid var(--line); border-radius: var(--radius, 10px); background: #0d141c; }
/* Bought rows are dimmed, not struck through: the item, its price and its link stay readable
   because "what did I pay for that lamp" is the question the archive exists to answer. */
.purchase-row.is-done { opacity: .68; }
.purchase-row.is-dragging { opacity: .55; border-color: var(--accent, #4aa3ff); }
.purchase-row input[type="checkbox"] { width: 22px; height: 22px; min-height: 0; margin-top: 2px; }
.purchase-tick { display: flex; align-items: center; }
.purchase-head { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }
.purchase-edit { margin-top: 10px; padding-top: 10px; border-top: 1px dashed var(--line); }
.purchase-archive { margin-top: 10px; border: 1px solid var(--line); border-radius: var(--radius, 10px); padding: 8px 10px; background: var(--panel, #0d141c); }
.purchase-archive > summary { cursor: pointer; font-weight: 700; color: var(--muted); min-height: 32px; display: flex; align-items: center; }
.purchase-empty { color: var(--muted); padding: 8px 0; }
.chip-removable { cursor: pointer; }

/* The mark, on the surfaces that are not the panel: the asset table's two columns, the v2 asset
   card, and both expanded rows. Big enough to catch while scanning a 244-row table. */
.purchase-mark { font-size: 14px; white-space: nowrap; }
.purchase-dismiss { white-space: nowrap; }
.asset-purchases { margin: 10px 0; }
.purchase-mini { padding: 3px 0; }
.purchase-mini.is-done { opacity: .68; }

@media (max-width: 700px) {
  .purchase-form-grid .field, .purchase-form-grid .field.grow { flex: 1 1 100%; }
  /* The buttons wrap under the text rather than squeezing it to nothing — this row carries up
     to five controls and a phone is 390px wide. */
  .purchase-row { flex-wrap: wrap; }
  .purchase-row .button-row { width: 100%; justify-content: flex-end; }
}

/* --- One row per location (0075) ---------------------------------------------------------
   Here rather than in a desktop-only sheet because board.css is loaded by BOTH shells, and
   the grouping engine lives in shared.js so the phone can adopt it without a second copy of
   either the logic or these rules drifting away from this one. */

/* The row says it stands for more than one record. A table that quietly shows 159 rows where
   it showed 242 is a table you stop trusting, so the count is never implicit. */
.group-badge {
  display: inline-block;
  margin-left: 6px;
  padding: 0 6px;
  border-radius: 999px;
  font-size: 0.75rem;
  font-weight: 600;
  line-height: 1.5;
  background: var(--chip-bg, rgba(127, 145, 165, 0.22));
  color: var(--muted, #8fa3b8);
  cursor: help;
}
.asset-row.is-grouped .group-badge { color: var(--fg, inherit); }

/* The other records at this location, inside the expanded row. */
.group-members {
  margin-top: 12px;
  padding-top: 10px;
  border-top: 1px solid var(--border, rgba(127, 145, 165, 0.28));
}
.group-member {
  margin-top: 10px;
  padding-left: 10px;
  border-left: 2px solid var(--border, rgba(127, 145, 165, 0.28));
}

/* Names which record the task cards under it belong to. Without this the two sets of tasks
   would run together and a track circuit's inspection would read as the crossing's. */
.group-task-head {
  margin: 12px 0 6px;
  font-weight: 600;
}
.group-task-head:first-child { margin-top: 0; }

/* 0076 — the source value under an edited name. Quiet: it is a reference, not a warning, and
   an operator correcting a street should not feel corrected in turn. */
.identity-source { margin-top: 4px; font-size: 12px; color: var(--muted, #8a94a6); display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }
.identity-source em { opacity: 0.75; }
input.identity-input { width: 100%; }

/* 0079 — the repeat line under a recurring task. Quiet, and only present when it repeats. */
.recur-line { margin-top: 6px; opacity: 0.85; }

/* 0080 (O3) — the inspections search. Two filter rows and a result list that is a GRID, not a
   table: the same markup has to read on a 1600px band under the assets table and on a phone
   held in one hand, and a table would need a sideways scroll on the second. Each cell carries
   its own label, so a wrapped row still says which date it is showing. */
.insx-filters { display: flex; flex-wrap: wrap; gap: 10px; align-items: flex-end; margin-bottom: 10px; }
.insx-filters .field { min-width: 130px; }
.insx-filters .field.grow { flex: 1 1 220px; }
.insx-range {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  border: 1px solid var(--line, #2d3b49); border-radius: 10px;
  padding: 4px 10px 8px; margin: 0;
}
.insx-range.grow { flex: 1 1 320px; }
.insx-range legend { font-size: 12px; color: var(--muted, #9fb0c2); padding: 0 4px; }
.insx-range input[type="text"] { width: 84px; }
.insx-range input[type="date"] { min-width: 132px; }
.insx-dash { color: var(--muted, #9fb0c2); }

.insx-results { display: flex; flex-direction: column; gap: 6px; }
.insx-row {
  display: grid; gap: 4px 14px; align-items: baseline;
  grid-template-columns: minmax(200px, 2fr) repeat(auto-fit, minmax(104px, 1fr));
  padding: 8px 10px; border: 1px solid var(--line, #2d3b49); border-radius: 10px;
  background: var(--panel-2, #1d2a36); cursor: pointer;
}
.insx-row:hover, .insx-row:focus-visible { border-color: var(--accent, #5aa9ff); outline: none; }
.insx-main { display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap; min-width: 0; }
.insx-name { color: var(--muted, #9fb0c2); overflow-wrap: anywhere; }
.insx-cell { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
/* The label is what makes the wrapped phone layout legible — without it a column of four bare
   dates says nothing about which is the final due. */
.insx-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted, #9fb0c2); }
.insx-value { overflow-wrap: anywhere; }
.insx-empty { padding: 12px 10px; color: var(--muted, #9fb0c2); }
.insx-more { padding-top: 6px; }
.insx-foot { margin-top: 10px; }

/* One column per row on a phone: the auto-fit grid above would otherwise put two dates side by
   side at 380px and clip both. */
@media (max-width: 700px) {
  .insx-row { grid-template-columns: 1fr 1fr; }
  .insx-main { grid-column: 1 / -1; }
}

/* 0082 — the AM / PM split in a day lane. Drawn between cards because the lane is a stack and
   not a timeline: there is no pixel that is noon, only the first card to start after it. */
.board-ampm {
  display: flex; align-items: center; gap: 8px;
  margin: 6px 0 4px; font-size: 10px; letter-spacing: 0.08em;
  color: var(--muted, #9fb0c2); text-transform: uppercase;
}
.board-ampm::after { content: ''; flex: 1; height: 1px; background: var(--line, #2d3b49); }
.board-ampm.is-am { margin-top: 0; opacity: 0.7; }

/* The shift control beside the week arrows. */
.board-shift { display: inline-flex; align-items: center; gap: 4px; }
.board-shift input[type="time"] { width: 86px; padding: 4px 6px; }

/* A day that runs past the end of the shift. Amber rather than red: it is a fact about the
   plan, not an error — the work is real and the day is simply too full. */
.board-day-hours.is-overrun { color: var(--warn, #e0a458); font-weight: 600; }

/* 0083 — sub-elements. board.css is the one stylesheet BOTH shells load, so the checklist
   cannot end up looking like two different features on the phone and the desk. */
.subtask-block { margin-top: 8px; border-top: 1px solid var(--line, #2a3350); padding-top: 6px; }
.subtask-head { margin-bottom: 4px; letter-spacing: .02em; }
.subtask-row { display: flex; align-items: center; gap: 8px; padding: 2px 0; }
.subtask-row input[type="checkbox"] { flex: 0 0 auto; width: 18px; height: 18px; margin: 0; cursor: pointer; }
.subtask-title { flex: 1 1 auto; font-size: 13px; line-height: 1.35; word-break: break-word; }
.subtask-row.is-done .subtask-title { text-decoration: line-through; opacity: .55; }
.subtask-remove { flex: 0 0 auto; padding: 0 7px; line-height: 1.6; }
.subtask-add { display: flex; gap: 6px; margin-top: 6px; }
.subtask-input { flex: 1 1 auto; min-width: 0; }
.badge.subtask-count { background: #223055; border-color: #33447a; }
.badge.subtask-count.is-full { background: #1d4030; border-color: #2f6b4c; }

/* --- 0085: the month picker, the complete-on control, and a carried card -------------------
   In board.css because it is the one stylesheet BOTH shells load. A copy in app.css and
   another in v2's would be two, and the one that drifts is the one on the device you are not
   holding. Colours come from the existing custom properties, so the picker follows the theme
   without knowing anything about it. */
.month-picker {
  position: absolute; z-index: 9000; width: 268px; padding: 10px;
  background: var(--panel, #14181f); color: var(--text, #e8edf5);
  border: 1px solid var(--line, #2a323f); border-radius: 10px;
  box-shadow: 0 12px 32px rgba(0,0,0,.45);
}
.month-picker[hidden] { display: none; }
.mp-head { display: flex; align-items: center; justify-content: space-between; gap: 6px; margin-bottom: 8px; }
.mp-label { font-weight: 600; font-size: 14px; }
.mp-step, .mp-btn {
  background: var(--chip, #1c222c); color: inherit; border: 1px solid var(--line, #2a323f);
  border-radius: 6px; cursor: pointer; font: inherit;
}
/* 40px: a thumb target. The ‹ › are the controls Tim named ("scroll back and forward") and on
   a phone they sit under the same finger that is about to press a date. */
.mp-step { width: 40px; height: 32px; font-size: 18px; line-height: 1; }
.mp-btn { padding: 5px 10px; font-size: 12px; }
.mp-step:hover, .mp-btn:hover { border-color: var(--accent, #4da3ff); }
.mp-week, .mp-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 2px; }
.mp-week { margin-bottom: 4px; }
.mp-week span { text-align: center; font-size: 10px; opacity: .55; text-transform: uppercase; }
.mp-day {
  aspect-ratio: 1 / 1; min-height: 32px; padding: 0; font: inherit; font-size: 12px;
  background: transparent; color: inherit; border: 1px solid transparent;
  border-radius: 6px; cursor: pointer;
}
.mp-day:hover { background: var(--chip, #1c222c); }
.mp-day.is-outside { opacity: .32; }
.mp-day.is-today { border-color: var(--accent, #4da3ff); }
.mp-day.is-selected { background: var(--accent, #4da3ff); color: #06121f; font-weight: 700; }
.mp-day:focus-visible { outline: 2px solid var(--accent, #4da3ff); outline-offset: 1px; }
.mp-foot { display: flex; justify-content: space-between; gap: 6px; margin-top: 8px; }

.complete-on { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin: 8px 0; }
.complete-on-label { font-size: 12px; opacity: .7; }
/* Underlined and dotted rather than button-shaped: it reads as a date you can correct, which
   is what "click on the complete on ___date____" describes — a blank being filled, not a
   command being issued. */
.complete-on-date {
  font: inherit; font-size: 13px; font-variant-numeric: tabular-nums;
  background: transparent; color: inherit; cursor: pointer;
  border: 1px solid transparent; border-bottom: 1px dashed var(--accent, #4da3ff);
  border-radius: 4px; padding: 3px 6px;
}
.complete-on-date:hover, .complete-on-date[aria-expanded="true"] {
  background: var(--chip, #1c222c); border-color: var(--accent, #4da3ff);
}
.complete-on.is-done .complete-on-date { border-bottom-style: solid; }

.carried-from { font-size: 11px; opacity: .8; }
.board-card.is-carried { border-left: 3px solid var(--warn, #e0a03a); }

/* 0087 — the unsaved-to-cloud button, in four section heads and three phone page heads.
   Deliberately not a danger colour: nothing is wrong, there is simply work here the server
   has not got. It reads as a pending action, and the fact that it is normally absent is what
   carries the signal — a control that is always on screen stops being read. */
.cloud-save-btn {
  border-color: var(--accent, #2b6cb0);
  color: var(--accent, #2b6cb0);
  font-weight: 600;
  white-space: nowrap;
}
.cloud-save-btn:hover { background: rgba(43, 108, 176, 0.10); }
.cloud-save-btn[hidden] { display: none !important; }
/* The live label on Inspection types. Muted when the cloud is current, so the panel head is
   quiet in the state it is in nearly all of the time. */
.cloud-state.is-dirty { color: var(--accent, #2b6cb0); font-weight: 600; }

/* 0088 — the yearly nobody has a date for. Measured at 92 of 224 inspected assets, so the
   styling has to survive being on screen a great deal: muted, no warning colour, and a question
   mark rather than a glyph. Nothing is wrong on these assets — the date is simply not on record,
   and only Tim can say whether one is owed. */
.yearly-unknown {
  margin: 6px 0 2px;
  padding: 6px 8px;
  border-left: 3px solid var(--muted, #94a3b8);
  background: rgba(148, 163, 184, 0.10);
  font-size: 0.86rem;
  line-height: 1.35;
}
.yearly-unknown strong { font-weight: 600; }
.yearly-unknown-marker {
  display: inline-block;
  margin-left: 6px;
  padding: 0 5px;
  border: 1px solid var(--muted, #94a3b8);
  border-radius: 3px;
  color: var(--muted, #94a3b8);
  font-size: 0.72rem;
  font-weight: 600;
  cursor: help;
  white-space: nowrap;
}

/* 0089 — the save history. A centred dialog rather than a panel, because it is a thing you open,
   act on and close, and because a permanently-visible list of 41 saves is 41 invitations to
   restore one by accident. */
.save-history {
  position: fixed; inset: 0; z-index: 900;
  display: flex; align-items: center; justify-content: center;
  background: rgba(15, 23, 42, 0.55);
  padding: 16px;
}
.save-history[hidden] { display: none !important; }
.save-history .sh-panel {
  background: var(--panel, #fff); color: inherit;
  border-radius: 10px; box-shadow: 0 18px 48px rgba(0,0,0,.35);
  width: min(520px, 100%); max-height: min(80vh, 720px);
  display: flex; flex-direction: column;
}
.save-history .sh-head {
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 12px 14px; border-bottom: 1px solid var(--border, #e2e8f0);
}
.save-history .sh-note { padding: 10px 14px; line-height: 1.4; }
.save-history .sh-list { overflow-y: auto; padding: 0 14px 14px; }
.save-history .sh-row {
  display: grid; grid-template-columns: 1fr auto auto;
  align-items: center; gap: 10px;
  padding: 7px 0; border-top: 1px solid var(--border, #e2e8f0);
}
.save-history .sh-when { font-variant-numeric: tabular-nums; }
.save-history .sh-current {
  margin-left: 8px; padding: 0 6px; border-radius: 3px;
  background: rgba(148,163,184,.20); font-size: .72rem; font-weight: 600;
}
