/* Engineer app.
   Palette: MCI Electrical's own amber over AccuMobile's teal chrome, so an
   engineer who used AccuMobile yesterday recognises the app but can see whose
   it is. Tokens come from ../assets/brand.css; everything below derives the
   light and dark surfaces around them.

   Open Sans is AccuMobile's body face and is self-hosted, not pulled from
   Google -- this app has to render in a property with no signal, so every byte
   it needs is in the service-worker shell. font-display:swap keeps first paint
   instant if the file is somehow missing.

   Large tap targets throughout: used one-handed, often with gloves, often in
   poor light. */

@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../assets/fonts/open-sans-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
                 U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122,
                 U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Open Sans";
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url("../assets/fonts/open-sans-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
                 U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF,
                 U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --ink:        #10222b;
  --ink-soft:   #43606d;
  --ink-faint:  #5a7480;
  --ground:     #e9eef1;
  --surface:    #ffffff;
  --rule:       #cfd9df;
  --rule-soft:  #e4eaee;

  /* Structural teal -- focus rings, selection, links, informational notices.
     Deliberately NOT the amber: amber is reserved for "this is the action". */
  --accent:     #1b3d49;
  --accent-ink: #ffffff;

  /* Amber is a fill colour only. On both grounds it needs dark text on it:
     #022835 on #f19e11 is 7.1:1, white on #f19e11 is 2.2:1 and unreadable. */
  --brand-ink:  #022835;

  /* AccuMobile's semantic hues are kept exactly for fills, borders and left
     edges -- that is the signal an engineer already reads at a glance. Text
     drawn IN those colours uses the *-ink variants, which are darkened enough
     to pass contrast on a pale background; the raw hues do not. */
  --go-ink:     #0f6b02;
  --stop-ink:   #b31d12;
  --warn-ink:   #7a4d00;
  --stop-bg:    #fdecea;
  --warn-bg:    #fdf1d9;
  --info-bg:    #e9f0f3;
  --paused:     var(--warn);

  --tap: 3rem;
  --pad: 1rem;
  --radius: 4px;

  /* We supply both palettes, so the browser must not force-darken the page --
     Chrome and Brave on Android will otherwise invert it themselves and the
     result is nobody's design. */
  color-scheme: light dark;
}

/* Handsets are used outdoors in daylight and in unlit roof spaces, so both
   themes have to hold up. The dark ground is tinted towards the brand teal
   rather than neutral grey, so the chrome and the page belong together.
   Tokens only -- no component styles in here. */
@media (prefers-color-scheme: dark) {
  :root {
    --ink:        #e6edf1;
    --ink-soft:   #a9bcc6;
    --ink-faint:  #8ba5b1;
    --ground:     #061a22;
    --surface:    #0f2b37;
    --rule:       #2c4d5a;
    --rule-soft:  #1a3844;

    --accent:     #6fc3e0;
    --accent-ink: #06171f;

    --go-ink:     #55c63c;
    --stop-ink:   #ff8f7d;
    --warn-ink:   #f2b53f;
    --stop-bg:    #2f1613;
    --warn-bg:    #2c2109;
    --info-bg:    #0d2f3c;
  }
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }

/* display:flex/grid on an element outranks the user-agent [hidden] rule, which
   has bitten this app twice. Stated globally so it cannot be forgotten again. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
}

body {
  background: var(--ground);
  color: var(--ink);
  font: 16px/1.5 "Open Sans", system-ui, -apple-system, "Segoe UI", Roboto,
        "Helvetica Neue", Arial, sans-serif;
  overscroll-behavior-y: contain;
}

/* ---------- app frame ---------- */

#app {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  max-width: 34rem;
  margin: 0 auto;
  background: var(--ground);
}

/* AccuMobile's own status-bar teal, in both themes, so the chrome is a
   constant and <meta name="theme-color"> can match it. */
header.bar {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--deep);
  color: #ffffff;
  padding: 0.7rem var(--pad);
  /* Flex, not a fixed set of grid tracks. Back and Job utilities come and go
     with the screen, so the number of children in this bar is 3, 4 or 5; a
     four-track grid dropped the fifth (Sync) onto a second row on exactly the
     screens where both buttons show. Flex just lays out whatever is there. */
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: var(--tap);
  border-bottom: 2px solid var(--brand);
}

/* The title block takes the slack, so the icon buttons stay hard against the
   edges. min-width: 0 is what lets the h1 and .sub ellipsis instead of forcing
   the bar wider than the screen. */
header.bar .titles {
  flex: 1;
  min-width: 0;
}

/* A 2.25rem tap target that a long title is allowed to squeeze is not a tap
   target. The buttons keep their size; the title gives way instead. */
header.bar .iconbtn {
  flex: none;
}

header.bar .brandmark {
  height: 1.5rem;
  width: auto;
  display: block;
}

header.bar h1 {
  font-size: 1.05rem;
  font-weight: 600;
  margin: 0;
  line-height: 1.25;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

header.bar .sub {
  font-size: 0.75rem;
  opacity: 0.85;
  font-weight: 400;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.iconbtn {
  appearance: none;
  background: rgba(255, 255, 255, 0.16);
  border: 0;
  color: inherit;
  font: inherit;
  font-size: 1.1rem;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: var(--radius);
  cursor: pointer;
  display: grid;
  place-items: center;
}

.iconbtn:disabled { opacity: 0.35; cursor: default; }
.iconbtn:active:not(:disabled) { transform: scale(0.9); }
/* Sync drains the queue and reloads the list, which is not instant. */
.iconbtn.busy { opacity: 0.45; pointer-events: none; }

/* display:grid above outranks the user-agent [hidden] rule, so state it. */
.iconbtn[hidden] { display: none; }
.iconbtn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

main {
  flex: 1;
  padding: var(--pad);
  display: grid;
  gap: var(--pad);
  align-content: start;
}

/* ---------- connection + queue status ---------- */

#netbar {
  display: none;
  padding: 0.5rem var(--pad);
  font-size: 0.82rem;
  font-weight: 600;
  text-align: center;
  background: var(--warn-bg);
  color: var(--warn-ink);
  border-bottom: 1px solid var(--warn);
}

#netbar.show { display: block; }

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

.card {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--pad);
  display: grid;
  gap: 0.75rem;
}

.card > h2 {
  margin: 0;
  font-size: 0.72rem;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 700;
}

/* A further circuit on the minor works form: its own certificate, so it is set
   apart from circuit 1 by a rule down the side, not just another card. */
.card.mwcircuit { border-left: 4px solid var(--accent, var(--rule)); }
.card.mwcircuit > h2 { color: var(--ink); }
.card.mwcircuit > h3 {
  margin: 0.5rem 0 0;
  font-size: 0.8rem;
  font-weight: 700;
  color: var(--ink-faint);
}

.rows { display: grid; gap: 0.85rem; }

.row { display: grid; gap: 0.15rem; }

.row dt {
  font-size: 0.68rem;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  font-weight: 700;
}

.row dd { margin: 0; font-size: 1rem; }

.muted { color: var(--ink-soft); }
.small { font-size: 0.85rem; }

/* ---------- buttons ---------- */

/* Primary = MCILMAIL amber with dark teal text. */
.btn {
  appearance: none;
  border: 1px solid var(--brand-dark);
  background: var(--brand);
  color: var(--brand-ink);
  font: inherit;
  font-weight: 600;
  min-height: var(--tap);
  padding: 0.6rem 1rem;
  border-radius: var(--radius);
  cursor: pointer;
  width: 100%;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  text-decoration: none;
}

.btn:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
.btn:disabled { opacity: 0.4; cursor: default; }

/* A tap has to show it landed, or it gets tapped again.
 *
 * `-webkit-tap-highlight-color: transparent` at the top of this file takes away
 * the only feedback Android gives a button by default, and most of what these
 * buttons do -- a status change behind a GPS fix, a job fetched over a van's
 * signal -- takes long enough that a silent button reads as a dead one. So the
 * press is drawn here, and `.busy` keeps it drawn until the work finishes. */
.btn:active:not(:disabled) { transform: scale(0.97); filter: brightness(0.92); }

.btn.busy {
  opacity: 0.75;
  cursor: progress;
  pointer-events: none;   /* belt and braces: the capture guard is the braces */
}
.btn.busy::after {
  content: "";
  width: 0.9em; height: 0.9em;
  border: 2px solid currentColor;
  border-top-color: transparent;
  border-radius: 50%;
  animation: btnspin 700ms linear infinite;
}
@keyframes btnspin { to { transform: rotate(360deg); } }

.btn.secondary {
  background: var(--surface);
  color: var(--accent);
  border: 2px solid var(--accent);
}

.btn.go { background: var(--go); border-color: var(--go); color: #ffffff; }
.btn.stop { background: var(--stop); border-color: var(--stop); color: #ffffff; }

.btn.ghost {
  background: transparent;
  border: 2px solid var(--rule);
  color: var(--ink);
}

.certlink { margin-top: 0.8rem; }

.btnrow { display: grid; gap: 0.6rem; grid-auto-flow: row; }
.btnrow.two { grid-template-columns: 1fr 1fr; }

/* ---------- job list ---------- */

.joblist { display: grid; gap: 0.6rem; }

/* Search and sort sit above the list; "My order" adds a drag handle on the
   left and up/down buttons on the right of each card. The handle takes the
   touch (touch-action none) so dragging it never scrolls the page, and the
   buttons are for anyone who would rather tap than drag with one thumb. */
.listbar { display: grid; grid-template-columns: 1fr auto; gap: 0.5rem; align-items: center; }
.listbar select { width: auto; max-width: 10.5rem; }
.listcount { margin: -0.35rem 0 0; padding-left: 0.1rem; }

.jobrow { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0.4rem; }
.jobrow.reorder { grid-template-columns: 2.75rem minmax(0, 1fr) 2.75rem; }

.jobgrip,
.movebtn {
  appearance: none;
  font: inherit;
  color: var(--ink-soft);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0;
  -webkit-user-select: none;
  user-select: none;
}

.jobgrip { font-size: 1.5rem; line-height: 1; cursor: grab; touch-action: none; }
.jobmove { display: grid; grid-template-rows: 1fr 1fr; gap: 0.4rem; }
.movebtn { min-height: 2.75rem; font-size: 0.95rem; cursor: pointer; }
.jobgrip:active, .movebtn:active { background: var(--info-bg); color: var(--accent); }
.job:active { filter: brightness(0.94); }
.job.busy { opacity: 0.7; pointer-events: none; }
.jobgrip:focus-visible, .movebtn:focus-visible { outline: 3px solid var(--accent); outline-offset: 1px; }

.jobrow.dragging { position: relative; z-index: 5; }
.jobrow.dragging .job,
.jobrow.dragging .jobgrip {
  border-color: var(--accent);
  box-shadow: 0 0.5rem 1.5rem rgba(0, 0, 0, 0.28);
}
.jobrow.dragging .jobgrip { background: var(--info-bg); color: var(--accent); }
body.listdragging { -webkit-user-select: none; user-select: none; cursor: grabbing; }

.job {
  appearance: none;
  text-align: left;
  font: inherit;
  color: inherit;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-left: 4px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.85rem var(--pad);
  cursor: pointer;
  display: grid;
  gap: 0.3rem;
  width: 100%;
}

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

.job[data-status="in_progress"] { border-left-color: var(--go); }
.job[data-status="paused"]      { border-left-color: var(--warn); }
.job[data-status="travelling"],
.job[data-status="on_site"]     { border-left-color: var(--accent); }
.job[data-status="completed"]   { border-left-color: var(--rule); opacity: 0.6; }
.job[data-status="no_access"]   { border-left-color: var(--stop); }

.job .ref {
  font-size: 0.72rem;
  font-weight: 700;
  letter-spacing: 0.05em;
  color: var(--ink-faint);
  font-variant-numeric: tabular-nums;
}

.job .addr { font-weight: 600; line-height: 1.3; }

.job .meta {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  align-items: center;
  margin-top: 0.15rem;
}

.pill {
  font-size: 0.65rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  padding: 0.2rem 0.45rem;
  border-radius: 2px;
  border: 1px solid currentColor;
  white-space: nowrap;
}

.pill.go     { color: var(--go-ink); }
.pill.paused { color: var(--warn-ink); background: var(--warn-bg); }
.pill.stop   { color: var(--stop-ink); }
.pill.plain  { color: var(--ink-faint); }
.pill.emerg  { color: var(--stop-ink); background: var(--stop-bg); }
/* Been here before. Deliberately the accent colour rather than a warning one:
   a repeat visit is information, not a problem. */
.pill.been   { color: var(--accent); background: var(--info-bg); }
/* AM / PM: solid, so the half of the day reads before anything else on the card. */
.pill.slot.am { color: var(--accent-ink); background: var(--accent); border-color: var(--accent); }
.pill.slot.pm { color: var(--surface); background: var(--ink-soft); border-color: var(--ink-soft); }

/* The list cut into AM, PM and any time. The heading is a band with the
   half-day's colour down its edge, readable across a van cab. */
.slotgroup { display: grid; gap: 0.6rem; }
.slotgroup + .slotgroup { margin-top: 0.9rem; }
.slotjobs { display: grid; gap: 0.6rem; }
.slothead {
  display: flex; justify-content: space-between; align-items: center;
  margin: 0; padding: 0.5rem 0.75rem;
  font-size: 1rem; font-weight: 800; letter-spacing: 0.02em;
  background: var(--surface); color: var(--ink);
  border: 1px solid var(--rule); border-left: 6px solid var(--ink-faint);
  border-radius: var(--radius);
}
.slotgroup.am .slothead { border-left-color: var(--accent); }
.slotgroup.pm .slothead { border-left-color: var(--ink-soft); }
.slotcount { font-size: 0.85rem; font-weight: 700; color: var(--ink-faint); }

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

label.field { display: grid; gap: 0.35rem; }

/* Mixed case, never text-transformed. These captions carry units -- kA, mA,
   ms, IΔn -- and uppercase turned "Breaking capacity (kA)" into KA and
   "Max disconnection time (s)" into S on the certificate forms. */
label.field > span {
  font-size: 0.8rem;
  letter-spacing: 0.01em;
  color: var(--ink-faint);
  font-weight: 700;
}

input[type="text"],
input[type="password"],
input[type="number"],
input[type="search"],
select,
textarea {
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.65rem 0.7rem;
  min-height: var(--tap);
  width: 100%;
}

textarea { min-height: 7rem; resize: vertical; line-height: 1.45; }

input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 3px solid var(--accent);
  outline-offset: 1px;
  border-color: var(--accent);
}

.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }

/* A non-label field wrapper, for controls that are not one input. */
div.field { display: grid; gap: 0.35rem; }

/* Same caption treatment as label.field > span, and the same rule about case:
   these carry units and must never be transformed. */
.fieldlabel {
  font-size: 0.8rem;
  letter-spacing: 0.01em;
  color: var(--ink-faint);
  font-weight: 700;
}

/* The caption rule above catches every direct span; a hint is not a caption. */
label.field > span.fieldhint,
.fieldhint {
  font-weight: 400;
  letter-spacing: 0;
  color: var(--ink-soft);
}

/* Two or three choices, side by side, each a full tap target. A dropdown for
   Plastic/Metal is three taps and a scroll for something that is a coin flip. */
.seg { display: grid; grid-auto-flow: column; grid-auto-columns: 1fr; gap: 0.5rem; }

.segbtn {
  font: inherit;
  min-height: var(--tap);
  padding: 0.5rem 0.4rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
  font-weight: 600;
  cursor: pointer;
}

.segbtn[aria-pressed="true"] {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--accent-ink);
}

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

.zechip:empty { display: none; }

/* A warning belongs under the box it is about, not in a banner at the top of a
   form this long -- the engineer reading it is standing at section D. */
.fieldwarn:empty { display: none; }
.fieldwarn > .notice { display: grid; gap: 0.2rem; margin-top: 0.1rem; }
label.field > span.fieldwarn { font-weight: 400; letter-spacing: 0; }
.fieldwarn .notice > span { font-weight: 400; }

label.check {
  display: flex;
  gap: 0.65rem;
  align-items: center;
  min-height: var(--tap);
  cursor: pointer;
}

label.check input { width: 1.35rem; height: 1.35rem; flex: none; accent-color: var(--accent); }

/* ---------- big yes/no ---------- */

.choice { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }

.choice button {
  appearance: none;
  font: inherit;
  font-size: 1.15rem;
  font-weight: 700;
  min-height: 5rem;
  border-radius: var(--radius);
  cursor: pointer;
  border: 2px solid;
}

.choice .yes { border-color: var(--go); color: var(--go-ink); background: var(--surface); }
.choice .no  { border-color: var(--stop); color: var(--stop-ink); background: var(--surface); }
.choice button[aria-pressed="true"].yes { background: var(--go); color: #fff; }
.choice button[aria-pressed="true"].no  { background: var(--stop); color: #fff; }

/* ---------- photos ---------- */

.shots {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
  gap: 0.5rem;
}

.shot {
  position: relative;
  aspect-ratio: 1;
  background: var(--rule-soft);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

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

.shot .queued {
  position: absolute;
  inset: auto 0 0 0;
  background: var(--warn-bg);
  color: var(--warn-ink);
  font-size: 0.6rem;
  font-weight: 700;
  text-align: center;
  padding: 0.15rem;
  letter-spacing: 0.05em;
  text-transform: uppercase;
}

.counter {
  font-size: 0.85rem;
  font-weight: 600;
}

.counter.short { color: var(--stop-ink); }
.counter.met { color: var(--go-ink); }

/* ---------- viewfinder ---------- */

/* The picture fills the screen and the shutter sits under the thumb. An
   engineer holding a torch in the other hand is not going to reach the top of
   a 6.5-inch phone. */
.viewfinder {
  position: relative;
  border-radius: var(--radius);
  overflow: hidden;
  background: #000;
  /* Tall as the screen allows and no taller. An aspect-ratio alone pushed the
     bottom of the picture -- and the strip of shots along it -- under the
     footer, where the shutter lives. */
  height: min(58dvh, 133vw);
  margin-bottom: 0.5rem;
}

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

/* The shutter feedback. There is no sound from a WebView, so the flash and the
   haptic are the whole confirmation that a shot was taken -- and in a dark
   hallway the flash is the one that gets noticed. */
.viewfinder .flash {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

.viewfinder .flash.lit { animation: shutterflash 220ms ease-out; }

@keyframes shutterflash {
  from { opacity: 0.85; }
  to   { opacity: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .viewfinder .flash.lit { animation: none; }
}

.camcount {
  position: absolute;
  top: 0.6rem;
  right: 0.6rem;
  margin: 0;
  min-width: 2.2rem;
  padding: 0.25rem 0.5rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 1.1rem;
  font-weight: 700;
  text-align: center;
  font-variant-numeric: tabular-nums;
}

/* Top-left, opposite the count, out of the way of the thumb that is on the
   shutter. */
.camtorch {
  position: absolute;
  top: 0.6rem;
  left: 0.6rem;
  min-height: 2.4rem;
  padding: 0.3rem 0.8rem;
  border: 0;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font: inherit;
  font-size: 0.85rem;
  font-weight: 700;
  cursor: pointer;
}

.camtorch[aria-pressed="true"] { background: #f5c518; color: #1a1a1a; }
.camtorch[hidden] { display: none; }

/* Shown while the camera is opening, and while the first run works out which
   rear lens is the main one. The shutter is disabled underneath it. */
.camsetting {
  position: absolute;
  inset: 50% 0 auto 0;
  margin: 0;
  transform: translateY(-50%);
  text-align: center;
  color: #fff;
  font-weight: 600;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
}

.camsetting[hidden] { display: none; }

.camerror {
  position: absolute;
  inset: auto 0.6rem 0.6rem;
  margin: 0;
}

/* What has been taken so far, over the bottom of the picture. Scrolls sideways
   rather than growing: eight photos of a board must not push the shutter off
   the screen. */
.viewfinder .strip {
  position: absolute;
  inset: auto 0 0 0;
  display: flex;
  gap: 0.4rem;
  padding: 0.45rem;
  overflow-x: auto;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.62), transparent);
  scrollbar-width: none;
}

.viewfinder .strip:empty { display: none; }
.viewfinder .strip::-webkit-scrollbar { display: none; }

.viewfinder .strip .shot {
  flex: 0 0 auto;
  width: 3.4rem;
  border-color: rgba(255, 255, 255, 0.5);
}

/* A round shutter, the largest thing on the screen. */
.btn.shutter {
  width: 4.6rem;
  height: 4.6rem;
  min-height: 0;
  padding: 0;
  margin: 0 auto 0.6rem;
  border-radius: 50%;
  background: var(--surface);
  border: 5px solid var(--ink);
  box-shadow: inset 0 0 0 4px var(--surface), inset 0 0 0 999px var(--ink);
}

.btn.shutter:active:not(:disabled) { transform: scale(0.93); }
.btn.shutter:disabled { opacity: 0.3; }

/* Drop a bad shot before it is ever queued -- rounding on a wet hand takes a
   picture of the floor, and deleting it from the office later is a job. */
.dropshot {
  position: absolute;
  top: 0.15rem;
  right: 0.15rem;
  width: 1.6rem;
  height: 1.6rem;
  border: 0;
  border-radius: 50%;
  background: rgba(0, 0, 0, 0.62);
  color: #fff;
  font-size: 1.1rem;
  line-height: 1;
  cursor: pointer;
}

/* ---------- signature ---------- */

#sigpad {
  width: 100%;
  height: 11rem;
  background: var(--surface);
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  touch-action: none;
  display: block;
}

.stored { display: grid; grid-template-columns: repeat(auto-fill, minmax(7rem, 1fr)); gap: 0.5rem; }

.sig { margin: 0; display: grid; gap: 0.25rem; }

.sig figcaption {
  display: grid;
  gap: 0.2rem;
  justify-items: center;
  text-align: center;
}

.stored button {
  background: var(--surface);
  border: 2px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.4rem;
  cursor: pointer;
  min-height: 4rem;
}

.stored button[aria-pressed="true"] { border-color: var(--accent); }
.stored img { width: 100%; height: 3rem; object-fit: contain; display: block; }

/* ---------- previous visits ---------- */

.visit .meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.4rem;
  margin: -0.35rem 0 0.2rem;
}

/* A photo from an earlier visit is a link, so it needs the tap target and the
   focus ring that .shot alone does not carry. */
a.shot { display: block; }
a.shot:focus-visible { outline: 3px solid var(--accent); outline-offset: 2px; }

/* ---------- notices ---------- */

.notice {
  border: 1px solid;
  border-left-width: 4px;
  border-radius: var(--radius);
  padding: 0.75rem var(--pad);
  font-size: 0.9rem;
  display: grid;
  gap: 0.35rem;
}

.notice.warn { border-color: var(--warn); background: var(--warn-bg); color: var(--warn-ink); }
.notice.stop { border-color: var(--stop); background: var(--stop-bg); color: var(--stop-ink); }
.notice.info { border-color: var(--accent); background: var(--info-bg); color: var(--accent); }
.notice strong { font-weight: 700; }

/* ---------- steps ---------- */

.steps {
  display: flex;
  gap: 0.3rem;
  padding: 0 var(--pad);
  margin-top: 0.75rem;
}

.steps span {
  flex: 1;
  height: 3px;
  background: var(--rule);
  border-radius: 2px;
}

.steps span.done { background: var(--brand); }

footer.actions {
  position: sticky;
  bottom: 0;
  background: var(--ground);
  border-top: 1px solid var(--rule);
  padding: 0.75rem var(--pad);
  display: grid;
  gap: 0.6rem;
}

.hidden { display: none !important; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  /* No spinner, but the button must still look busy. */
  .btn.busy::after { border-top-color: currentColor; opacity: 0.5; }
}


/* ---------- brand marks ---------- */

/* The lightning mark sits in the header bar beside the title. Fixed height so
   the bar does not jump between screens as the title text changes. */
header.bar .mark {
  height: 18px;
  width: auto;
  flex: none;
  align-self: center;
  margin-right: 0.15rem;
}

.crestwrap {
  display: flex;
  justify-content: center;
  padding: 1.6rem 0 0.4rem;
}

/* The crest is amber line-art on transparent, so it sits on either ground
   without a plate behind it. */
.crest {
  width: 150px;
  height: auto;
  max-width: 55%;
}

/* ------------------------------------------------------------------- EICR */

/* The schedule of inspections is 102 rows. It has to be tappable one-handed,
   in poor light, without the outcome buttons becoming a wall of noise -- so
   the description carries the weight and the outcomes sit under it. */
.sched-row {
  padding: 0.6rem 0;
  border-top: 1px solid var(--rule);
}
.sched-row:first-of-type { border-top: 0; }
.sched-desc { display: flex; gap: 0.5rem; align-items: baseline; }
.sched-desc b {
  flex: 0 0 2.9rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  font-size: 0.85rem;
}
.sched-desc span { flex: 1; font-size: 0.92rem; line-height: 1.35; }
.sched-desc span.muted { color: var(--ink-soft); font-style: italic; }

.sched-outcomes {
  display: flex;
  flex-wrap: wrap;
  margin: 0.45rem 0 0 3.4rem;
}
.oc {
  min-width: 2.5rem;
  min-height: 2.2rem;
  margin: 0 0.3rem 0.3rem 0;
  padding: 0 0.4rem;
  border: 1px solid var(--rule);
  border-radius: 6px;
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.85rem;
  cursor: pointer;
}
.oc.on { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); font-weight: 600; }
/* A dangerous code must not look like an ordinary selection. */
.oc-C1.on, .oc-C2.on {
  background: var(--stop-ink);
  border-color: var(--stop-ink);
  color: var(--surface);
}

.btn.wide {
  display: flex;
  justify-content: space-between;
  align-items: center;
  width: 100%;
  margin-bottom: 0.5rem;
}
.btn.tiny { padding: 0.2rem 0.6rem; font-size: 0.8rem; min-height: 0; }
/* Not full width where it sits beside a caption, as under a signature. */
.sig .btn.tiny { width: auto; justify-self: center; }
.tally {
  font-variant-numeric: tabular-nums;
  color: var(--ink-soft);
  font-size: 0.85rem;
}

.obs-card .obs-head { display: flex; align-items: center; gap: 0.6rem; }
.obs-card .obs-head b { font-variant-numeric: tabular-nums; }
.obs-card .obs-head .btn { margin-left: auto; }
.obs-card p { margin: 0.5rem 0 0; line-height: 1.4; }
.code {
  display: inline-block;
  min-width: 2.1rem;
  padding: 0.1rem 0.35rem;
  border-radius: 4px;
  text-align: center;
  font-weight: 700;
  font-size: 0.8rem;
  background: var(--rule-soft);
  color: var(--ink);
}
.code-C1, .code-C2 { background: var(--stop-ink); color: var(--surface); }

table.circuits { width: 100%; border-collapse: collapse; font-size: 0.88rem; }
table.circuits th, table.circuits td {
  padding: 0.35rem 0.4rem;
  border-bottom: 1px solid var(--rule);
  text-align: left;
}
table.circuits th { color: var(--ink-soft); font-size: 0.78rem; font-weight: 600; }

/* A schedule row that heads indented bullets: no outcome box on the printed
   form, so none here either. */
.sched-row.parent { background: var(--rule-soft); margin: 0 -0.5rem; padding: 0.5rem; }
.sched-row.parent .sched-desc span { font-weight: 600; }

/* Smart fill. Short values use a native datalist; long boilerplate — the
   general-condition paragraph runs past 200 characters — gets tappable chips
   above the box instead, because a dropdown of paragraphs is unreadable on a
   handset held in one hand. */
.chips {
  display: flex;
  flex-wrap: wrap;
  margin: 0.15rem 0 0.4rem;
}
.chip {
  max-width: 100%;
  margin: 0 0.3rem 0.3rem 0;
  padding: 0.3rem 0.6rem;
  border: 1px dashed var(--rule);
  border-radius: 999px;
  background: var(--surface);
  color: var(--ink-soft);
  font: inherit;
  font-size: 0.82rem;
  text-align: left;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
}
.chip:active { background: var(--info-bg); color: var(--ink); }

/* Job utilities tiles. Full-width rows with the state on the right, so the
   whole thing is thumb-reachable and readable at a glance in a van. */
.btn.wide.util { min-height: 3rem; text-align: left; }
.btn.wide.util:disabled { opacity: 0.45; }

/* ---------- certificates the job produces for itself ---------- */
.autocert { display: grid; gap: 0.3rem; padding: 0.7rem 0; border-top: 1px solid var(--rule-soft); }
.autocert:first-of-type { border-top: 0; padding-top: 0.2rem; }
.autocert .btn.tiny { justify-self: start; width: auto; }
.attn { color: var(--warn-ink); }
.src { font-style: normal; font-size: 0.75rem; font-weight: normal; color: var(--ink-faint); margin-left: 0.35rem; }
.src-assumed { color: var(--warn-ink); font-weight: 600; }

/* ----------------------------------------------------------- Domestic EIC */

/* The circuit list doubles as the way into a circuit, so a row is a target. */
table.circuits tr.pick td { cursor: pointer; }
table.circuits tr.on td { background: var(--rule-soft); }
.mark-bad { color: var(--stop-ink); font-weight: 700; }
/* What still stops this circuit being signed, under the form, as it is typed. */
.problems {
  margin: 0.7rem 0 0;
  padding-left: 1.2rem;
  color: var(--stop-ink);
  font-size: 0.88rem;
  line-height: 1.4;
}
.problems:empty { display: none; }
/* Not text-transformed: legends sit beside captions carrying units. */
.subhead {
  margin: 1.2rem 0 0.4rem;
  font-size: 0.9rem;
  font-weight: 600;
  color: var(--ink-soft);
}

/* Certificate viewer: the PDF as page images, since the shell's WebView has
   no PDF viewer. Zoom in doubles the page and scrolls sideways. */
.pdfpages { display: grid; gap: 0.6rem; }
.pdfpages img { display: block; width: 100%; height: auto; background: #fff;
  border: 1px solid var(--rule-soft); border-radius: 4px; }
.pdfpages.wide { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.pdfpages.wide img { width: 200%; max-width: none; }

/* ---------- codes: what PK asked for, and what the engineer did ---------- */
.codeitem {
  display: grid;
  gap: 0.5rem;
  padding: 0.75rem 0;
  border-top: 1px solid var(--rule-soft);
}
.codeitem:first-of-type { border-top: 0; }
.codehead {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: 0.2rem 0.6rem;
  align-items: baseline;
}
.codeno { font-weight: 700; font-variant-numeric: tabular-nums; }
.codedesc { color: var(--ink-soft); font-size: 0.9rem; overflow-wrap: anywhere; }
.codeitem.struck .codeno,
.codeitem.struck .codedesc,
.row.struck dd { text-decoration: line-through; color: var(--ink-faint); }
.codewho {
  margin-left: 0.4rem;
  font-weight: 400;
  font-size: 0.78rem;
  color: var(--ink-faint);
}
.stepper { display: inline-flex; align-items: center; gap: 0.6rem; }
.stepper .movebtn { width: 3rem; font-size: 1.25rem; }
.stepper .movebtn:disabled { opacity: 0.35; }
.stepqty { min-width: 2.5rem; text-align: center; font-weight: 700; font-variant-numeric: tabular-nums; }
.codewhere {
  font: inherit;
  min-height: var(--tap);
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink);
}
.codehint { margin: 0.8rem 0 0.2rem; }
.chip.codechip { border-style: solid; color: var(--ink); white-space: normal; }
.linkbtn {
  appearance: none;
  border: 0;
  background: none;
  padding: 0.4rem 0;
  font: inherit;
  font-size: 0.85rem;
  color: var(--stop-ink);
  text-decoration: underline;
  cursor: pointer;
}
.pickrow {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.6rem;
  width: 100%;
  min-height: var(--tap);
  padding: 0.55rem 0.6rem;
  margin-top: 0.35rem;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
}
.pickrow:active:not(:disabled) { background: var(--info-bg); }
.pickrow:disabled { opacity: 0.45; cursor: default; }

/* The menu. Always at the top, on every screen once signed in, and labelled
   in words as well as the three bars: plenty of the people holding these
   phones would not know the icon on its own means "more in here". */
.menubtn {
  appearance: none;
  flex: none;
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 2.5rem;
  padding: 0 0.8rem;
  border: 2px solid rgba(255, 255, 255, 0.55);
  border-radius: var(--radius);
  background: rgba(255, 255, 255, 0.16);
  color: inherit;
  font: inherit;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}
.menubtn span { font-size: 1.25rem; line-height: 1; }
.menubtn:active { transform: scale(0.95); }
.menubtn[hidden] { display: none; }
.menubtn:focus-visible { outline: 2px solid #fff; outline-offset: 2px; }

/* One big button per choice: a heading word and a line saying what it is.
   Used by the menu and by each question of a new certificate. */
.choices { display: grid; gap: 0.6rem; }
.choice {
  appearance: none;
  display: block;
  width: 100%;
  min-height: 4rem;
  padding: 0.8rem 1rem;
  text-align: left;
  font: inherit;
  color: var(--ink);
  background: var(--surface);
  border: 2px solid var(--rule);
  border-left: 6px solid var(--accent);
  border-radius: var(--radius);
  cursor: pointer;
}
.choice strong { display: block; font-size: 1.1rem; line-height: 1.3; }
.choice span { display: block; margin-top: 0.15rem; font-size: 0.9rem; color: var(--ink-soft); white-space: pre-line; }
.choice.main { border-left-color: var(--go, #129603); }
.choice.quiet { border-left-color: var(--rule); }
.choice:active:not(:disabled) { transform: scale(0.98); background: var(--rule-soft); }
.choice[aria-pressed="true"] { border-color: var(--accent); background: var(--rule-soft); }
.choice:disabled { opacity: 0.5; cursor: default; }
.choice:focus-visible { outline: 3px solid var(--ink); outline-offset: 2px; }
.menuhead { margin: 0.4rem 0 0.5rem; font-size: 0.8rem; font-weight: 700; letter-spacing: 0.04em; color: var(--ink-faint); text-transform: uppercase; }
.asked { font-size: 1.2rem; margin: 0 0 0.75rem; }
