:root {
  --bg: #12161c;
  --panel: #1a2029;
  --panel2: #212936;
  --border: #2e3947;
  --text: #e8edf3;
  --muted: #93a1b3;
  --accent: #f5a623;
  --accent2: #4da3ff;
  --green: #3ecf8e;
  --red: #ff5c5c;
  --orange: #ff9f43;
  --yellow: #ffd166;
  --purple: #c08cff;
}
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body { height: 100%; }
body {
  font-family: "Segoe UI", system-ui, sans-serif;
  background: var(--bg);
  color: var(--text);
  font-size: 14px;
}
#app { display: flex; height: 100vh; }

/* ---------- sidebar ---------- */
#sidebar {
  width: 210px;
  min-width: 210px;
  background: var(--panel);
  border-right: 1px solid var(--border);
  display: flex;
  flex-direction: column;
  padding: 14px 10px;
  gap: 4px;
}
.brand {
  font-size: 20px;
  font-weight: 700;
  padding: 6px 10px 16px;
  letter-spacing: 0.5px;
}
.brand span { color: var(--accent); }
.nav-btn {
  text-align: left;
  background: none;
  border: none;
  color: var(--muted);
  padding: 10px 12px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 14px;
  font-family: inherit;
}
.nav-btn:hover { background: var(--panel2); color: var(--text); }
.nav-btn.active { background: var(--panel2); color: var(--accent); font-weight: 600; }
.sidebar-foot { margin-top: auto; font-size: 11px; color: var(--muted); padding: 10px; line-height: 1.5; word-break: break-all; }

/* ---------- main ---------- */
#view { flex: 1; overflow-y: auto; padding: 24px 28px 60px; }
h1 { font-size: 22px; margin-bottom: 4px; }
h2 { font-size: 16px; margin: 18px 0 10px; }
.sub { color: var(--muted); margin-bottom: 18px; }
.row { display: flex; gap: 12px; align-items: center; flex-wrap: wrap; }
.spread { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px; }

/* ---------- cards / stats ---------- */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(180px, 1fr)); gap: 12px; margin: 14px 0; }
.stat {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 14px 16px;
}
.stat .num { font-size: 26px; font-weight: 700; }
.stat .lbl { color: var(--muted); font-size: 12px; margin-top: 2px; }
.panel {
  background: var(--panel);
  border: 1px solid var(--border);
  border-radius: 10px;
  padding: 16px;
  margin-bottom: 14px;
}

/* ---------- tables ---------- */
table { width: 100%; border-collapse: collapse; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--border); vertical-align: top; }
th { color: var(--muted); font-size: 12px; text-transform: uppercase; letter-spacing: 0.4px; font-weight: 600; }
tr.clickable { cursor: pointer; }
tr.clickable:hover td { background: var(--panel2); }
td .small { color: var(--muted); font-size: 12px; }

/* ---------- badges ---------- */
.badge {
  display: inline-block;
  padding: 2px 9px;
  border-radius: 20px;
  font-size: 11.5px;
  font-weight: 600;
  white-space: nowrap;
}
.badge.Critical { background: #4a1d1d; color: var(--red); }
.badge.High { background: #4a331d; color: var(--orange); }
.badge.Medium { background: #4a421d; color: var(--yellow); }
.badge.Low { background: #1d3a4a; color: var(--accent2); }
.badge.Open { background: #4a1d1d; color: var(--red); }
.badge.Monitoring { background: #4a421d; color: var(--yellow); }
.badge.Quoted { background: #1d3a4a; color: var(--accent2); }
.badge.Closed { background: #1d4a33; color: var(--green); }
.badge.Pass { background: #1d4a33; color: var(--green); }
.badge.Fail { background: #4a1d1d; color: var(--red); }
.badge.type { background: var(--panel2); color: var(--muted); }
/* circuit function */
.badge.fn-Lighting { background: #4a421d; color: var(--yellow); }
.badge.fn-Power { background: #1d3a4a; color: var(--accent2); }
.badge.fn-Comms { background: #3a1d4a; color: var(--purple); }
.badge.fn-Essential { background: #4a331d; color: var(--orange); }
.badge.fn-none { background: var(--panel2); color: var(--muted); }
/* effective-value source hint in planning tables */
.src { font-size: 10.5px; color: var(--muted); text-transform: uppercase; letter-spacing: .04em; }
.flag {
  background: #4a1d1d;
  color: var(--red);
  border: 1px solid #6b2b2b;
  border-radius: 8px;
  padding: 8px 12px;
  margin: 6px 0;
  font-size: 13px;
}
.flag.warn { background: #4a331d; color: var(--orange); border-color: #6b4a2b; }

/* ---------- buttons / inputs ---------- */
button.btn {
  background: var(--accent);
  color: #1a1205;
  border: none;
  border-radius: 8px;
  padding: 8px 14px;
  font-weight: 600;
  font-size: 13px;
  cursor: pointer;
  font-family: inherit;
}
button.btn:hover { filter: brightness(1.1); }
button.btn.ghost { background: var(--panel2); color: var(--text); border: 1px solid var(--border); }
button.btn.small { padding: 4px 10px; font-size: 12px; }
button.btn.danger { background: #4a1d1d; color: var(--red); border: 1px solid #6b2b2b; }
input, select, textarea {
  background: var(--panel2);
  border: 1px solid var(--border);
  color: var(--text);
  border-radius: 8px;
  padding: 8px 10px;
  font-size: 13.5px;
  font-family: inherit;
  width: 100%;
}
input:focus, select:focus, textarea:focus { outline: 1px solid var(--accent2); }
textarea { resize: vertical; min-height: 70px; }
.search-bar { max-width: 340px; }

/* ---------- tabs ---------- */
.tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--border); margin: 18px 0 14px; flex-wrap: wrap; }
.tab {
  background: none; border: none; color: var(--muted);
  padding: 9px 14px; cursor: pointer; font-size: 13.5px; font-family: inherit;
  border-bottom: 2px solid transparent;
}
.tab:hover { color: var(--text); }
.tab.active { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; }

/* ---------- detail grid ---------- */
.detail-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px 18px; }
.detail-grid .lbl { color: var(--muted); font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.4px; }
.detail-grid .val { margin-top: 2px; }

/* ---------- photos ---------- */
.photo-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 12px; }
.photo-card { background: var(--panel2); border: 1px solid var(--border); border-radius: 10px; overflow: hidden; }
.photo-card img { width: 100%; height: 130px; object-fit: cover; display: block; cursor: pointer; }
.photo-card .cap { padding: 6px 8px; font-size: 12px; color: var(--muted); word-break: break-all; }

/* ---------- modal ---------- */
#modalRoot:empty { display: none; }
.modal-overlay {
  position: fixed; inset: 0; background: rgba(0,0,0,0.6);
  display: flex; align-items: flex-start; justify-content: center;
  padding: 40px 16px; z-index: 100; overflow-y: auto;
}
.modal {
  background: var(--panel); border: 1px solid var(--border); border-radius: 12px;
  width: 100%; max-width: 640px;
  /* No bottom padding: the sticky action bar supplies it. Chrome clamps a
     sticky child to the scroller's CONTENT box, so any padding-bottom here
     leaves the bar floating that far above the modal's edge. */
  padding: 22px 22px 0;
  /* A switchboard form is taller than the screen. Scroll inside the modal
     rather than letting it run off the bottom — 80px is the overlay padding. */
  max-height: calc(100vh - 80px);
  overflow-y: auto;
}
.modal h3 { margin-bottom: 14px; }
.form-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
.form-grid .full { grid-column: 1 / -1; }
.field label { display: block; font-size: 12px; color: var(--muted); margin-bottom: 4px; }
/* section divider inside a long form (asset class fields, supply & isolation) */
.form-section {
  font-size: 12px; font-weight: 700; letter-spacing: 0.5px; text-transform: uppercase;
  color: var(--accent); border-top: 1px solid var(--border);
  padding-top: 12px; margin-top: 4px;
}
.form-section span { text-transform: none; letter-spacing: 0; font-weight: 400; color: var(--muted); margin-left: 8px; }
/* Pinned to the bottom of the modal so Save is reachable without scrolling to
   the end of a long form. The negative margins bleed it to the modal's edges,
   so the form scrolls underneath it rather than showing through the padding. */
.modal .actions {
  display: flex; justify-content: flex-end; gap: 8px;
  position: sticky; bottom: 0;
  margin: 18px -22px 0; padding: 14px 22px;
  background: var(--panel);
  border-top: 1px solid var(--border);
  border-radius: 0 0 12px 12px;
}

/* ---------- data-safety banner ----------
   Deliberately loud and not dismissible: it only appears when what you type is
   no longer reaching disk, which is not a thing to let scroll past. */
.db-banner {
  position: fixed; top: 0; left: 0; right: 0; z-index: 300;
  background: #4a1414; border-bottom: 2px solid var(--red); color: #ffe9e9;
  padding: 10px 16px; font-size: 13px; line-height: 1.5;
  box-shadow: 0 6px 18px rgba(0, 0, 0, 0.45);
}
.db-banner b { display: block; color: #ffd0d0; letter-spacing: 0.3px; }
.db-banner .path { font-family: Consolas, monospace; font-size: 12px; word-break: break-all; }
.db-banner.hidden { display: none; }

/* ---------- toast ---------- */
#toast {
  position: fixed; bottom: 20px; right: 20px; z-index: 200;
  background: var(--panel2); border: 1px solid var(--border); color: var(--text);
  padding: 10px 16px; border-radius: 8px; font-size: 13px;
  opacity: 0; transition: opacity 0.25s; pointer-events: none;
}
#toast.show { opacity: 1; }

/* ---------- history timeline ---------- */
.timeline { position: relative; margin: 6px 0 0 6px; padding-left: 22px; }
.timeline::before {
  content: ""; position: absolute; left: 0; top: 6px; bottom: 6px;
  width: 2px; background: var(--border);
}
.tl-item { position: relative; display: grid; grid-template-columns: 92px 1fr; gap: 12px; margin-bottom: 12px; align-items: start; }
.tl-dot {
  position: absolute; left: -27px; top: 12px;
  width: 11px; height: 11px; border-radius: 50%;
  border: 2px solid var(--bg);
}
.tl-date { color: var(--muted); font-size: 12.5px; padding-top: 10px; white-space: nowrap; }
.tl-card {
  background: var(--panel); border: 1px solid var(--border);
  border-radius: 10px; padding: 10px 14px; font-size: 13px; line-height: 1.5;
}

/* ---------- calendar ---------- */
.cal-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 4px; }
.cal-head {
  color: var(--muted); font-size: 11.5px; text-transform: uppercase;
  letter-spacing: 0.4px; font-weight: 600; padding: 4px 6px;
}
.cal-day {
  background: var(--panel); border: 1px solid var(--border); border-radius: 8px;
  min-height: 104px; padding: 5px 6px; cursor: pointer; overflow: hidden;
}
.cal-day:hover { border-color: var(--accent2); }
.cal-day.empty-day { background: none; border-color: transparent; cursor: default; }
.cal-day.is-today { border-color: var(--accent); }
.cal-num { color: var(--muted); font-size: 12px; font-weight: 600; margin-bottom: 3px; }
.cal-day.is-today .cal-num { color: var(--accent); }
.cal-ev {
  background: var(--panel2); border-left: 3px solid var(--accent2);
  border-radius: 4px; padding: 3px 5px; margin-bottom: 3px;
  font-size: 11.5px; line-height: 1.35; overflow: hidden; text-overflow: ellipsis;
}
.cal-ev:hover { background: var(--border); }
.cal-ev.due { border-left-color: var(--orange); color: var(--orange); background: #2a2118; }
.cal-ev.done { opacity: 0.55; border-left-color: var(--green); }
.cal-who { display: block; color: var(--muted); font-size: 10.5px; }

/* ---------- misc ---------- */
.empty { color: var(--muted); padding: 18px 4px; font-style: italic; }
pre.docket {
  background: var(--panel2); border: 1px solid var(--border); border-radius: 8px;
  padding: 10px; white-space: pre-wrap; font-size: 12.5px; color: var(--muted);
  max-height: 260px; overflow-y: auto; font-family: Consolas, monospace;
}
.chart-wrap { overflow-x: auto; }
a.link { color: var(--accent2); cursor: pointer; text-decoration: underline; }

/* ================================================================= phone
   Everything above is the desktop layout and is unchanged. This block is what
   makes the same renderer usable one-handed in a switchroom — see phase 3 in
   docs/cloud-migration.md.

   760px, not 480: it catches portrait phones and small tablets while leaving
   landscape phones (820px+) on the desktop layout, which is genuinely fine there.

   Nothing here is web-only. Narrow the desktop window and you get the same
   layout, which is the cheapest way to check a change without a phone. */
@media (max-width: 760px) {

  /* ---- layout: stop the flex row --------------------------------------
     The 210px sidebar becomes a sticky strip of the same buttons, swiped
     horizontally. #view stops being its own scroller so the PAGE scrolls —
     that is what lets the mobile address bar collapse and gives normal
     momentum scrolling. 100dvh not 100vh: iOS reports 100vh taller than the
     visible viewport, so the bottom of the app sits under the browser chrome. */
  #app { flex-direction: column; height: auto; min-height: 100dvh; }
  #sidebar {
    width: auto; min-width: 0;
    flex-direction: row; flex-wrap: nowrap;
    /* align-items, because flex defaults to stretch: without it the tallest item
       in the row (the status foot) sets the height of every nav button too. */
    align-items: center;
    overflow-x: auto; -webkit-overflow-scrolling: touch;
    border-right: none; border-bottom: 1px solid var(--border);
    /* The nav buttons must clear the status bar. index.html sets
       viewport-fit=cover and a black-translucent status bar, which is what lets
       the app fill the screen when installed to the home screen — but it also
       means the page starts UNDERNEATH the clock and the battery. The strip is
       sticky at top:0, so without this its first row of tabs sits behind them
       and the taps land on the system UI instead.

       Padding rather than a margin or a top offset, so the strip's background
       and its bottom border still run to the top of the screen — a gap above a
       sticky bar shows the page scrolling through it.

       In Safari the inset is 0 (the browser reserves the status bar itself), so
       this adds nothing there and only pays out on the installed app. */
    padding: calc(6px + env(safe-area-inset-top)) 8px 6px; gap: 2px;
    position: sticky; top: 0; z-index: 50;
  }
  #sidebar .brand { display: none; }
  .nav-btn { white-space: nowrap; padding: 10px 12px; flex: 0 0 auto; }
  .sidebar-foot {
    margin-top: 0; margin-left: auto; padding: 6px 8px;
    white-space: nowrap; word-break: normal; flex: 0 0 auto;
  }
  /* The status block stacks four lines on the desktop, where it sits at the
     bottom of a tall column. In a row it has to be a row, or it alone is 100px
     tall and drags the strip down with it. */
  #cloudFoot {
    display: flex; align-items: center; gap: 8px;
    margin-top: 0 !important; padding-top: 0 !important; border-top: none !important;
  }
  #view { flex: none; overflow: visible; padding: 14px 12px 40px; }

  h1 { font-size: 19px; }
  .spread { align-items: flex-start; }

  /* ---- tabs: left wrapping, deliberately -------------------------------
     .tabs is already flex-wrap. Turning it into a horizontal scroller would
     hide tabs 6-13 behind no affordance AND lose its scroll position on every
     click, because renderAssetDetail() rewrites view.innerHTML — "Test
     Results" is 6th on a board, so it would disappear every time you used it.
     Wrapping costs a few rows of height and hides nothing. A shorter tab set
     for the phone is a behaviour decision, not a CSS one. */
  .tab { padding: 8px 10px; font-size: 13px; }

  /* ---- tables: one rule, every list screen ------------------------------
     Tables are emitted as bare <table> inside .panel, so the panel is the
     scroller. Scoped to .panel table on purpose: a bare `table` min-width
     would overflow modal bodies, which have no horizontal scroller. */
  .panel { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  .panel table { min-width: 620px; }

  /* ---- forms: the leverage point ---------------------------------------
     openModal() renders every form in this app through .form-grid, so this
     single line makes all of them single-column, including the sign-in
     dialog. .full still spans the row and stays correct. */
  .form-grid { grid-template-columns: 1fr; }

  /* ---- modals -----------------------------------------------------------
     Three traps here, all load-bearing:
     1. !important — thirteen modals set max-width inline (style="max-width:900px"),
        and an inline style beats a media query without it.
     2. The sticky action bar's negative margins MUST match the modal's padding.
        They are -22px above to match 22px; padding is 14px here, so they are
        -14px here. Get this wrong and the bar stops bleeding to the edges.
     3. 100dvh, not 100vh — with 100vh on iOS the Save button sits underneath
        the browser toolbar and cannot be tapped. That is the desktop
        modal-actions-visible guard's failure mode, in a form it cannot see. */
  .modal-overlay { padding: 0; align-items: stretch; }
  .modal {
    max-width: none !important;
    width: 100%; border-radius: 0;
    max-height: 100dvh;
    padding: 16px 14px 0;
  }
  .modal .actions {
    margin: 14px -14px 0;
    padding: 12px 14px calc(12px + env(safe-area-inset-bottom));
  }

  /* ---- inputs: the iOS zoom rule ---------------------------------------
     ANY font-size below 16px makes iOS Safari zoom the page when the field
     takes focus, and it does not zoom back out. The tech taps "Value", the
     layout jumps, and the rest of the form is off screen. 13.5px above is
     right for a mouse and wrong for a thumb. */
  input, select, textarea { font-size: 16px; padding: 10px 12px; }
  button.btn { padding: 10px 16px; }
  button.btn.small { padding: 8px 12px; font-size: 13px; }
  .search-bar { max-width: none; }

  /* ---- odds and ends ---------------------------------------------------- */
  #toast { left: 12px; right: 12px; bottom: 12px; text-align: center; }
  .detail-grid { grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); }
  .cards { grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); }
  .tl-item { grid-template-columns: 74px 1fr; }
  .cal-grid { gap: 2px; }
  .cal-day { min-height: 72px; padding: 3px 4px; }

  /* Offered on the desktop, meaningless in a browser: there is no local file
     to switch back to. Hidden rather than removed, so app.js stays untouched —
     data/db.js already makes setBackend('sqlite') a no-op on the web, which is
     the actual guard. This is just not showing a door that leads nowhere. */
  body.web #gateLocal, body.web #cloudToLocal { display: none; }
}

/* "The local file above is idle" is written directly under the local-file status
   block, and on the web that block never paints — paintDbStatus returns early on a
   null status. So the line refers to something that is not above it, at any width.
   It is the second of two consecutive .small divs in the status block; see
   paintCloudFoot() in app.js, where that shape is fixed. */
body.web #cloudFoot div.small + div.small { display: none; }

/* A multi-answer field: checkboxes in a wrapping grid rather than a
   <select multiple>, which is close to unusable with a thumb. Wide enough that
   "Compliance Issue" does not wrap, and each label is a full tap target. */
.multi-grid { display: flex; flex-wrap: wrap; gap: 6px 14px; padding: 4px 0; }
.multi-grid label {
  display: flex; align-items: center; gap: 7px;
  color: var(--text); font-size: 13.5px; white-space: nowrap; cursor: pointer;
}
.multi-grid input { width: auto; margin: 0; }
@media (max-width: 760px) {
  /* One per line on a phone: two columns of checkboxes are a mis-tap waiting to
     happen, and this field has at most five options. */
  .multi-grid { flex-direction: column; gap: 2px; }
  .multi-grid label { padding: 7px 0; font-size: 15px; }
}

/* Landscape phone: tall enough is the problem, not wide enough. Only the modal
   suffers, and only because it is height-bound. */
@media (max-height: 560px) and (max-width: 940px) {
  .modal-overlay { padding: 8px; }
  .modal { max-height: calc(100dvh - 16px); }
}
