/* ============================================================
   sp-design.css — shared editorial design system for Twelve
   Digital products. Consumed via <link href="/shared/sp-design.css">
   (Caddy serves /shared in prod; the Deno dev server serves it too
   — see packages/deno-server/server.ts).

   Contents:
     1. @font-face (Inter)
     2. Design tokens (:root custom properties)
     3. Base reset + document defaults
     4. Layout helpers (.sp-header, main, .sp-foot)
     5. Type helpers (.sp-title/.sp-subtitle/.sp-label-mini/.sp-mono)
     6. Section head + flat row primitives
     7. Buttons (.sp-btn variants) + inputs (.sp-field/.sp-input/…)
     8. Segmented control (.sp-segment) + counter chip (.sp-chip)
     9. Progress rail, checklist helpers, FAB, bottom sheet
    10. Review stats, notice strip, signature wrap, meter
    11. Empty states, photo grid, misc utilities
   ============================================================ */

/* ---------- 1. Fonts ---------- */
@font-face {
  font-family: 'Inter';
  font-weight: 400;
  font-style: normal;
  font-display: swap;
  src: url('/shared/fonts/Inter-Regular.ttf') format('truetype');
}
@font-face {
  font-family: 'Inter';
  font-weight: 600 700;
  font-style: normal;
  font-display: swap;
  src: url('/shared/fonts/Inter-Bold.ttf') format('truetype');
}

/* ---------- 2. Tokens ---------- */
:root {
  --ink: #1C2036;
  --ink-2: #2F3555;
  /* Pressed fill for the dark buttons. A touch device has no hover to borrow,
     so the press needs a shade of its own, clearly apart from --ink-2. */
  --ink-3: #454C7C;
  --paper: #F5F1E8;
  --surface: #FBF9F2;
  --muted: #6B6458;
  /* Secondary text that still has to be read. 4.6:1 on the paper ground.
     It used to be #9A9287, which measures 2.7:1 — under the AA line for body
     text, and it was carrying every field hint, every step label and every
     measured unit in the app. */
  --faint: #736B5B;
  /* Purely decorative marks: the wordmark serial, the step numerals, the
     disclosure chevrons, input placeholders. Nothing here says anything the
     reader has to take in. */
  --faint-soft: #9A9287;
  --rule: #E2DBC7;
  --rule-strong: #CFC6AE;
  --accent: #B14A28;
  --accent-ink: #7C3316;
  --accent-press: #C9603C;

  --ok: #2F6B3A;
  --ok-bg: #E8EFDB;
  --warn: #B17812;
  --warn-bg: #F6EBC6;
  --crit: #9B2914;
  --crit-bg: #F3D9CE;
  --info: #4B5A78;
  --info-bg: #DFE3EC;

  --radius: 10px;
  --radius-sm: 6px;
  --radius-lg: 14px;

  /* Press feedback. Every control tints on touch-down and shrinks a little;
     see section 13 for why hover cannot do this job on a phone. */
  --press: rgba(28,32,54,.10);
  --press-soft: rgba(28,32,54,.055);
  --press-crit: rgba(155,41,20,.10);
  --press-scale: .97;
  --ease-out: cubic-bezier(.2,.8,.2,1);

  --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, 'Liberation Mono', monospace;

  --page-max: 520px;
  --page-px: 20px;
  /* Field padding and border, as tokens: the date-input rule in section 7 has
     to restate them, and a padding change that forgot it reopened a bug. */
  --field-px: 13px;
  --field-py: 11px;
  --field-border: 1px;
  --header-h: 52px;
  /* What the header actually occupies, notch included. The bar itself was
     `height: var(--header-h)` with the inset added as padding, which under
     border-box left the title colliding with the bottom rule — mobile/build.mjs
     carried an `!important` override to undo that. One token instead: the bar
     is this tall, `main` starts this far down, and the sticky progress rail
     sticks to this, not to 52px (it used to slide 62px up under the notch). */
  --header-total: calc(var(--header-h) + env(safe-area-inset-top, 0));
  /* Height reserved by .sp-actionbar: 44px button + 10px above + 10px below. */
  --actionbar-h: calc(65px + env(safe-area-inset-bottom, 0));
}

/* ---------- 3. Reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
body {
  font-family: var(--font-ui);
  font-size: 16px;
  line-height: 1.45;
  color: var(--ink);
  background: var(--paper);
  -webkit-tap-highlight-color: transparent;
  -webkit-font-smoothing: antialiased;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}
main {
  flex: 1 0 auto;
  width: 100%;
  max-width: var(--page-max);
  margin: 0 auto;
  padding: 16px var(--page-px) 0;
}
/* The fixed header is out of flow, so on a page that has one the first line of
   content would start underneath it. Scoped to the sibling rather than set on
   `main` outright: the landing and login pages share this stylesheet and have
   no header to clear. */
.sp-header ~ main { padding-top: calc(var(--header-total) + 16px); }
button, input, textarea, select { font: inherit; color: inherit; }
button { background: transparent; border: 0; padding: 0; cursor: pointer; }
a { color: inherit; }

/* Controls behave like controls, not like text. Without this a long press on
   "Mangel" starts a text selection and raises the callout menu -- an inspector
   wearing gloves triggers it constantly -- and a double tap on a button waits
   out the browser's zoom timer before anything happens. Inputs are excluded:
   selecting what you typed has to keep working. */
button, .sp-btn, .sp-iconbtn, .sp-chip, label.sp-btn, a.sp-btn,
.sp-row, .sp-device-tab, .sp-step-no, .sp-sheet-grip {
  -webkit-touch-callout: none;
  -webkit-user-select: none;
  user-select: none;
  touch-action: manipulation;
}

/* ---------- 4. Layout shell ---------- */
/* Fixed, not sticky. A sticky header is part of the document, so the rubber
   band carries it down with everything else and leaves a strip of paper above
   it — a native navigation bar stays exactly where it is while the content
   gives underneath. Fixed elements sit on the visual viewport and the overscroll
   does not reach them. `main` reserves the height below. */
.sp-header {
  position: fixed; top: 0; left: 0; right: 0; z-index: 40;
  background: var(--paper);
  border-bottom: 1px solid var(--rule);
  height: var(--header-total);
  display: flex; align-items: center;
  padding: 0 var(--page-px);
  padding-top: env(safe-area-inset-top, 0);
  gap: 12px;
}
.sp-header-inner {
  max-width: var(--page-max);
  width: 100%;
  margin: 0 auto;
  display: flex; align-items: center; gap: 12px;
  height: var(--header-h);
}
.sp-wordmark {
  font-weight: 600;
  font-size: 15px;
  letter-spacing: -0.01em;
  color: var(--ink);
  flex: 1;
  min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* Out of the way while the screen's own big title is still on screen. It
   keeps its space so the header never reflows, and it never hides on the home
   screen, where the wordmark is the title. */
.sp-wordmark #header-title-text.is-under {
  opacity: 0;
  transform: translateY(5px);
}
.sp-wordmark #header-title-text {
  transition: opacity .18s ease, transform .18s ease;
}
@media (prefers-reduced-motion: reduce) {
  .sp-wordmark #header-title-text { transition: none; }
}
.sp-wordmark .sp-wm-serial {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint-soft);
  letter-spacing: 0.04em;
  margin-right: 8px;
  text-transform: uppercase;
}
/* 44x44, Apple's minimum. It was 36, which is what made the back arrow and the
   settings gear feel like they had to be aimed at. The extra 8px reach past
   each glyph is taken out of the header's own 20px side padding via the
   negative margins below, so the icons stay optically on the screen edge and
   the header box does not grow -- nothing overflows, nothing pans. */
.sp-iconbtn {
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--muted);
  border-radius: var(--radius-sm);
}
.sp-header-inner > .sp-iconbtn:first-child { margin-left: -10px; }
.sp-header-inner > .sp-iconbtn:last-child { margin-right: -10px; }
.sp-iconbtn svg { width: 20px; height: 20px; }
.sp-iconbtn[hidden] { display: none; }

.sp-foot {
  flex-shrink: 0;
  max-width: var(--page-max);
  margin: 40px auto 0;
  padding: 16px var(--page-px) calc(24px + env(safe-area-inset-bottom, 0));
  border-top: 1px solid var(--rule);
  font-size: 11px;
  color: var(--faint);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  display: flex; flex-wrap: wrap;
  gap: 16px;
  width: 100%;
}
.sp-foot a { color: var(--muted); text-decoration: none; }
.sp-foot .sp-foot-spacer { flex: 1; }

/* ---------- 5. Type helpers ---------- */
.sp-title {
  font-size: 24px;
  line-height: 1.1;
  letter-spacing: -0.015em;
  font-weight: 600;
  margin: 0 0 4px;
}
.sp-subtitle {
  color: var(--muted);
  font-size: 14px;
  margin: 0 0 20px;
}
.sp-label-mini {
  font-size: 11px;
  font-weight: 500;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  color: var(--muted);
}
.sp-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; }

/* ---------- 6. Section head + flat row ---------- */
.sp-section-head {
  display: flex; align-items: baseline; gap: 2px;
  padding: 14px 0 8px;
}
/* The count is pushed to the far end; a help button sits with its heading. */
.sp-section-head > .sp-section-meta { margin-left: auto; }
.sp-section-head > .sp-help { align-self: center; }
/* The rule separates one section from the next, so the first section on a screen
   sits flush: a page title is not a section, and above it there is nothing at all.
   `~` matches when ANY earlier sibling qualifies, so this follows what is actually
   rendered -- hide the welcome block and its rule disappears with it, which
   `:first-child` never did (the block stays a DOM child while display:none). */
:not(.sp-hidden):not(.sp-sr):not(.sp-page-intro):not(.sp-title):not(.sp-subtitle) ~ .sp-section-head {
  border-top: 1px solid var(--rule);
  margin-top: 12px;
}
.sp-section-head h3 {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 0;
}
.sp-section-meta {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
  letter-spacing: 0.04em;
}

.sp-list { list-style: none; padding: 0; margin: 0; }
.sp-row {
  display: flex; align-items: center; gap: 10px;
  border-bottom: 1px solid var(--rule);
  padding: 14px 0;
  min-height: 60px;
}
.sp-row:last-child { border-bottom: 0; }
/* The chevron used to be a sibling of this button: 16px of "tap me" that was
   not tappable, sitting between the row and its edit action. It lives inside
   the button now, so the whole left-hand region opens the row.
   The negative margin/padding pair nets to zero width -- it only gives the
   press highlight a body to fill, and the 8px it reaches left stay inside the
   page's own 20px gutter. */
.sp-row-body {
  flex: 1; min-width: 0;
  display: flex; align-items: center; gap: 10px;
  text-align: left;
  margin: -8px; padding: 8px;
  border-radius: var(--radius-sm);
}
.sp-row-text { flex: 1; min-width: 0; }
.sp-row-title {
  display: block;
  font-size: 16px;
  font-weight: 500;
  color: var(--ink);
  line-height: 1.25;
  margin-bottom: 2px;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.sp-row-sub {
  display: block;
  font-size: 13px;
  color: var(--muted);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* A sub-line carrying a state chip has to wrap. One line with an ellipsis cuts
   off whatever sits last, and on a due row or a defect row the last thing is
   the urgency — the one part of the row worth reading. */
.sp-row-sub.is-wrap {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 8px;
  overflow: visible;
  white-space: normal;
}
.sp-row-chev {
  width: 16px; height: 16px;
  color: var(--faint-soft);
  flex-shrink: 0;
}
.sp-row-edit,
.sp-row-del {
  width: 44px; height: 44px;
  display: inline-flex; align-items: center; justify-content: center;
  color: var(--faint);
  border-radius: var(--radius-sm);
  opacity: .55;
}
.sp-row-del svg, .sp-row-edit svg { width: 16px; height: 16px; }

/* ---------- 7. Buttons + inputs ---------- */
.sp-btn {
  display: inline-flex; align-items: center; justify-content: center;
  gap: 8px;
  font-size: 15px;
  font-weight: 500;
  height: 44px;
  padding: 0 16px;
  border-radius: var(--radius);
  transition: background-color .18s ease, color .18s ease, border-color .18s ease,
              transform .18s var(--ease-out);
  text-decoration: none;
  min-width: 0;
}
.sp-btn-primary { background: var(--ink); color: var(--paper); }
.sp-btn-secondary {
  background: transparent;
  color: var(--ink);
  border: 1px solid var(--rule-strong);
}
.sp-btn-accent { background: var(--accent); color: #FFF8F1; }
/* Filled red, for the one button in a sheet that deletes something. */
.sp-btn-destructive { background: var(--crit); color: #FFF6F3; }
/* Smaller type, same 44pt reach as every other field control. */
.sp-btn-sm { height: 44px; padding: 0 12px; font-size: 13px; }
.sp-btn-block { width: 100%; }
/* 44, not 40. These are the "Abbrechen" and "Löschen" of the app -- the last
   row of a sheet and the corner of the signature pad, both places a thumb
   reaches badly. */
.sp-btn-ghost { color: var(--muted); height: 44px; padding: 0 10px; font-size: 13px; }
.sp-btn-danger { color: var(--crit); }
.sp-btn-primary:disabled,
.sp-btn-secondary:disabled,
.sp-btn[disabled] {
  background: transparent;
  color: var(--faint);
  border: 1px dashed var(--rule-strong);
  cursor: not-allowed;
}
/* Nothing to press: no tint, no shrink, and taps do not reach it at all. */
.sp-btn:disabled, .sp-btn[disabled] { pointer-events: none; }

.sp-field { display: block; margin-bottom: 16px; }
.sp-field-label {
  display: block;
  font-size: 11px;
  text-transform: uppercase;
  letter-spacing: 0.08em;
  font-weight: 500;
  color: var(--muted);
  margin-bottom: 6px;
}
.sp-input, .sp-textarea, .sp-select {
  display: block; width: 100%;
  background: var(--surface);
  border: var(--field-border) solid var(--rule);
  border-radius: var(--radius);
  padding: var(--field-py) var(--field-px);
  font-size: 16px;   /* 16px: below that iOS zooms the page on focus */
  color: var(--ink);
  transition: border-color .15s ease, box-shadow .15s ease;
  outline: none;
}
.sp-input::placeholder, .sp-textarea::placeholder { color: var(--faint-soft); }
.sp-input:focus, .sp-textarea:focus, .sp-select:focus {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px rgba(28,32,54,.12);
}
.sp-textarea { resize: vertical; min-height: 72px; }
.sp-select {
  appearance: none;
  background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%236B6458' stroke-width='2'><path d='M6 9l6 6 6-6'/></svg>");
  background-repeat: no-repeat;
  background-position: right 12px center;
  padding-right: 36px;
}
/* Date and time inputs are the one control the box-sizing reset does not
   reach: WebKit forces `box-sizing: content-box` on them from the UA sheet and
   an author `!important` does not win — measured in the app, all three of
   `box-sizing: border-box`, the same with `!important`, and `max-width: 100%`
   left the field at 428px inside a 400px column. `width: 100%` therefore means
   100% *plus* padding and border, and on the summary screen that pushed the
   Prüfdatum field 8px past the right edge, which is what let the whole app
   scroll sideways. Take the padding and border off the width instead. */
.sp-input[type="date"],
.sp-input[type="time"],
.sp-input[type="datetime-local"],
.sp-input[type="month"],
.sp-input[type="week"] {
  width: calc(100% - 2 * (var(--field-px) + var(--field-border)));
}

.sp-field-hint {
  font-size: 12px;
  color: var(--muted);
  margin-top: 6px;
  line-height: 1.4;
}

/* ---------- 8. Segmented + chip ---------- */
/* A track with an inset thumb, the shape iOS has used since 13. The old
   version was a flush row of cells whose selected one filled corner to corner:
   the choice read as a state of the whole control rather than as one of three
   things picked out of it. The colour coding -- green OK, red Mangel -- is the
   product's and stays. */
.sp-segment {
  display: flex;
  gap: 2px;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 3px;
}
.sp-segment > button {
  flex: 1;
  height: 38px;
  font-size: 13px;
  font-weight: 500;
  color: var(--muted);
  border-radius: 7px;
  position: relative;
  transition: background-color .18s ease, color .18s ease, transform .18s var(--ease-out);
}
/* Reach past the thumb into the track's own padding: 38 drawn, 44 tapped. */
.sp-segment > button::after {
  content: '';
  position: absolute;
  top: -3px; bottom: -3px; left: 0; right: 0;
}
/* Hairline between two unselected cells only -- next to the thumb it would be
   a second edge drawn beside an edge that is already there. */
.sp-segment > button + button::before {
  content: '';
  position: absolute; left: -2px; top: 8px; bottom: 8px;
  width: 1px;
  background: var(--rule-strong);
  transition: opacity .18s ease;
}
.sp-segment > button.is-active::before,
.sp-segment > button.is-active + button::before { opacity: 0; }
.sp-segment > button.is-active {
  background: var(--ink); color: var(--paper);
  box-shadow: 0 1px 2px rgba(28,32,54,.18);
}
.sp-segment > button.is-active.is-ok { background: var(--ok); color: #fff; }
.sp-segment > button.is-active.is-warn { background: var(--warn); color: #fff; }
.sp-segment > button.is-active.is-crit { background: var(--crit); color: #fff; }
.sp-segment > button.is-active.is-muted { background: var(--info); color: #fff; }
/* The most-tapped control in the app — OK / Mangel / N/A. The track carries
   the 44pt: 38px cell + 3px padding top and bottom + the two hairlines. */
.sp-segment-sm > button { height: 38px; font-size: 13px; }

.sp-chip {
  display: inline-flex; align-items: center; gap: 6px;
  font-family: var(--font-mono);
  font-size: 11px;
  padding: 3px 8px;
  border-radius: 999px;
  letter-spacing: 0.02em;
  background: var(--info-bg);
  color: var(--info);
  white-space: nowrap;
  flex-shrink: 0;
}
.sp-chip.is-ok { background: var(--ok-bg); color: var(--ok); }
.sp-chip.is-warn { background: var(--warn-bg); color: var(--warn); }
.sp-chip.is-crit { background: var(--crit-bg); color: var(--crit); }
.sp-chip.is-neutral { background: rgba(0,0,0,.05); color: var(--muted); }

/* ---------- 9. Progress, checklist, FAB, sheet ---------- */
.sp-progress {
  position: sticky;
  top: var(--header-total);
  z-index: 35;
  background: var(--paper);
  padding: 12px 0 10px;
  border-bottom: 1px solid var(--rule);
  margin: -24px calc(-1 * var(--page-px)) 16px;
  padding-left: var(--page-px);
  padding-right: var(--page-px);
}
.sp-progress-top {
  display: flex; align-items: baseline; justify-content: space-between;
  margin-bottom: 8px;
  font-size: 13px;
}
.sp-progress-label { font-size: 13px; color: var(--muted); }
.sp-progress-label strong { color: var(--ink); font-weight: 500; margin-right: 6px; }
.sp-progress-count {
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
  letter-spacing: 0.04em;
}
.sp-progress-bar { height: 2px; background: var(--rule); border-radius: 2px; overflow: hidden; }
.sp-progress-bar > span { display: block; height: 100%; background: var(--ink); transition: width .25s ease; }

/* Equipment tabs inside the sticky rail: jump straight to a piece of equipment
   instead of stepping through the ones in between. Scrolls sideways, because a
   playground can have more equipment than fits a phone. */
.sp-device-tabs {
  display: flex;
  gap: 6px;
  margin-top: 10px;
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
  /* Reaching the last tab must not hand the swipe on to the page (or, in the
     app shell, to the back gesture). */
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  /* Bleed to the screen edges so the last tab does not look clipped. */
  margin-left: calc(-1 * var(--page-px));
  margin-right: calc(-1 * var(--page-px));
  padding: 2px var(--page-px);
}
.sp-device-tabs::-webkit-scrollbar { display: none; }
.sp-device-tab {
  flex: 0 0 auto;
  max-width: 46vw;
  min-height: 44px;              /* a thumb-sized target, like every other control */
  display: flex; flex-direction: column; align-items: flex-start; justify-content: center;
  gap: 1px;
  padding: 5px 10px;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius-sm);
  background: var(--surface);
  scroll-snap-align: center;
  transition: background-color .18s ease, border-color .18s ease, transform .18s var(--ease-out);
}
.sp-device-tab-name {
  font-size: 13px;
  line-height: 1.2;
  color: var(--ink);
  max-width: 100%;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.sp-device-tab-meta {
  font-family: var(--font-mono);
  font-size: 10px;
  letter-spacing: 0.04em;
  color: var(--faint);
  white-space: nowrap;
}
.sp-device-tab.is-done { border-color: var(--ok); }
.sp-device-tab.is-done .sp-device-tab-meta { color: var(--ok); }
.sp-device-tab.is-crit { border-color: var(--crit); }
.sp-device-tab.is-crit .sp-device-tab-meta { color: var(--crit); }
/* Last, so the current tab reads as current whatever its state. */
.sp-device-tab.is-current { background: var(--ink); border-color: var(--ink); }
.sp-device-tab.is-current .sp-device-tab-name { color: var(--paper); }
.sp-device-tab.is-current .sp-device-tab-meta { color: var(--rule-strong); }

.sp-category {
  padding: 18px 0 4px;
  border-top: 1px solid var(--rule);
}
.sp-category:first-child { border-top: 0; padding-top: 4px; }
/* The head wraps as a whole rather than squeezing the title into a column.
   "Konstruktion & Stabilität" next to a chip and an action left three words
   stacked over two lines beside a half-empty row. */
.sp-category-head {
  display: flex; flex-wrap: wrap; align-items: baseline; justify-content: space-between;
  margin-bottom: 10px;
  gap: 4px 12px;
}
.sp-category-title {
  font-size: 13px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
  flex: 1 1 100%;
}
/* Once the title has a line of its own, the state sits under it, left-aligned
   with everything else on the screen. */
.sp-category-head > .sp-category-right { margin-left: -2px; }
.sp-category-right { display: inline-flex; align-items: center; gap: 10px; flex: 1 1 auto; justify-content: space-between; }
.sp-category-all-ok {
  font-size: 12px;
  color: var(--muted);
  text-decoration: underline;
  text-underline-offset: 2px;
  white-space: nowrap;
  /* Looks like a text link, taps like a button. No negative margin: the 8px
     it pushed past the content edge were enough to let the whole app pan
     sideways. */
  min-height: 44px; min-width: 44px; padding: 0 2px 0 10px;
  display: inline-flex; align-items: center; justify-content: flex-end;
}
.sp-check-item {
  padding: 12px 0;
  border-top: 1px dashed var(--rule);
}
.sp-check-item:first-child { border-top: 0; }
.sp-check-label {
  display: block;
  font-size: 14px;
  line-height: 1.35;
  color: var(--ink);
  margin-bottom: 8px;
}
.sp-check-defect {
  margin-top: 10px;
  padding: 12px;
  border-left: 2px solid var(--crit);
  background: rgba(155,41,20,.04);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.sp-check-defect > .sp-input { margin-top: 8px; }
.sp-defect-photo { margin-top: 8px; width: 100%; gap: 8px; }

/* A defect an earlier inspection left open, shown on the row it belongs to.
   Amber rather than red: it is not this inspection's finding yet, it is what
   the last one wrote down. The band sits above the segmented control so the
   inspector reads the history before they grade the item. */
.sp-check-carry {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: 0 0 10px;
  padding: 8px 10px;
  border-left: 2px solid var(--warn);
  background: rgba(177, 120, 18, .07);
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.sp-carry-note {
  flex: 1 1 100%;
  font-size: 13px;
  line-height: 1.35;
  color: var(--muted);
}
.sp-carry-adopt { margin-left: auto; }
/* Answered OK: the band is a record of what was, not an offer. */
.sp-check-carry.is-closing { border-left-color: var(--ok); background: rgba(47, 107, 58, .05); }
.sp-carry-state { margin-left: auto; font-size: 13px; color: var(--ok); }

.sp-measure-rule {
  margin: 0 0 8px;
  font-size: 12px;
  line-height: 1.4;
  color: var(--muted);
}

/* A mini label with a control beside it. */
.sp-label-row { display: flex; align-items: center; gap: 2px; margin-bottom: 6px; }
.sp-label-row .sp-help { margin: -12px 0 -12px 2px; }

/* Measured values on a checklist item. Two-up where there is room, because
   they come in pairs — what was measured and what it has to reach. */
.sp-measures {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: 8px;
  margin: 0 0 10px;
}
/* A column, so that a label wrapping onto two lines pushes its own field down
   rather than leaving the pair standing at different heights. */
.sp-measure { display: flex; flex-direction: column; }
.sp-measure .sp-measure-field { margin-top: auto; }
.sp-measure-label {
  display: block;
  font-size: 11px;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--faint);
  margin-bottom: 4px;
}
.sp-measure-field { position: relative; }
.sp-measure-field > .sp-input { padding-right: 42px; }
.sp-measure-unit {
  position: absolute;
  right: 12px;
  top: 50%;
  transform: translateY(-50%);
  font-size: 13px;
  color: var(--faint);
  pointer-events: none;
}
.sp-measure-verdict {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
  margin: -2px 0 10px;
}

/* The one sentence a screen leads with: what the state of this thing is, and
   therefore why the button below it is the button. Bigger than a hint, quieter
   than a title, and it carries its own colour when the news is bad. */
.sp-lede {
  margin: 18px 0 0;
  font-size: 15px;
  line-height: 1.45;
  color: var(--ink);
}
.sp-lede.is-crit { color: var(--crit); }
.sp-lede.is-warn { color: var(--warn); }
.sp-lede .sp-lede-sub { display: block; margin-top: 2px; font-size: 13px; color: var(--muted); }

/* An explanation, not a warning.
   The amber .sp-notice is for something the reader has to act on. Everything
   else -- what a section is for, where a file goes, what a severity means --
   is editorial text, and editorial text does not belong in a tinted box with
   a coloured border. A rule and space carry it (see docs/design-system.md). */
.sp-note {
  margin: 6px 0 16px;
  padding-left: 12px;
  border-left: 2px solid var(--rule-strong);
  font-size: 13px;
  line-height: 1.5;
  color: var(--muted);
}
.sp-note strong { color: var(--ink); font-weight: 500; }
.sp-note p { margin: 0 0 8px; }
.sp-note p:last-child { margin-bottom: 0; }
/* Directly under a section head, the rule aligns with the heading above it. */
.sp-section-head + .sp-note { margin-top: -2px; }

/* A button that reads as a link inside a sentence. Underlined, because the
   sentence around it is the only thing telling the reader it can be pressed. */
.sp-linkish {
  background: none; border: 0; padding: 0;
  font: inherit; color: var(--ink);
  text-decoration: underline; text-underline-offset: 3px;
  cursor: pointer;
}
.sp-linkish:active { opacity: .6; }

/* A question mark that opens an explanation. It is a control, not an
   ornament: 44pt of target around a 15px glyph, sitting in the section head
   next to what it explains. */
.sp-help {
  flex: 0 0 auto;
  width: 44px; height: 44px;
  margin: -10px -12px -10px 0;
  display: inline-flex; align-items: center; justify-content: center;
  background: none; border: 0; cursor: pointer;
  color: var(--faint);
  font: 500 15px/1 var(--font-ui);
}
.sp-help::before {
  content: '?';
  display: inline-flex; align-items: center; justify-content: center;
  width: 20px; height: 20px;
  border: 1px solid currentColor;
  border-radius: 50%;
}
@media (hover: hover) and (pointer: fine) { .sp-help:hover { color: var(--ink); } }
.sp-help:active { color: var(--ink); }

/* The body of a help sheet: a definition list of terms and what they mean. */
.sp-help-list { margin: 4px 0 0; }
.sp-help-list dt {
  font-size: 14px; font-weight: 500; color: var(--ink);
  margin-top: 14px;
}
.sp-help-list dt:first-child { margin-top: 0; }
.sp-help-list dd {
  margin: 3px 0 0;
  font-size: 13px; line-height: 1.5; color: var(--muted);
}

/* A switch. One setting, on or off, and the whole row is the target — a 20px
   checkbox is not a 44pt one, whatever the label next to it says. */
.sp-switch-row {
  display: flex;
  align-items: center;
  gap: 16px;
  min-height: 44px;
  padding: 10px 0;
  cursor: pointer;
}
.sp-switch-text { flex: 1; }
.sp-switch-title { display: block; font-size: 14px; color: var(--ink); }
.sp-switch-sub { display: block; margin-top: 3px; font-size: 12px; line-height: 1.4; color: var(--muted); }
.sp-switch {
  appearance: none;
  -webkit-appearance: none;
  flex: 0 0 auto;
  width: 51px;
  height: 31px;
  border-radius: 999px;
  background: var(--rule-strong);
  position: relative;
  transition: background .18s ease;
  cursor: pointer;
}
.sp-switch::after {
  content: '';
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #fff;
  box-shadow: 0 1px 3px rgba(0,0,0,.2);
  transition: transform .18s ease;
}
.sp-switch:checked { background: var(--ok); }
.sp-switch:checked::after { transform: translateX(20px); }
.sp-switch:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }

/* A list row that is a statement rather than a destination: no chevron, no
   press state, and it must not look tappable. */
.sp-row-static { cursor: default; }
.sp-row-static:active { background: none; transform: none; }

/* The mark at the end of a row says what the row does. A chevron promises a
   screen behind it; a pencil promises a form. */
.sp-row-mark {
  flex-shrink: 0;
  width: 16px; height: 16px;
  color: var(--faint-soft);
}
.sp-row-mark svg { width: 100%; height: 100%; display: block; }

/* An open defect. Not a list row: it carries four pieces of information and a
   named action, and squeezing that into a row with an unlabelled tick left the
   most important part -- what is wrong -- truncated at forty characters. */
.sp-defect {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 8px 12px;
  padding: 14px 0;
  border-top: 1px solid var(--rule);
}
.sp-defect:first-child { border-top: 0; }
.sp-defect-main { flex: 1 1 200px; min-width: 0; }
.sp-defect-head {
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px;
  margin: 0;
}
.sp-defect-device { font-size: 14px; font-weight: 500; color: var(--ink); }
.sp-defect-item { margin: 4px 0 0; font-size: 13px; line-height: 1.4; color: var(--muted); }
.sp-defect-note { margin: 4px 0 0; font-size: 13px; line-height: 1.4; color: var(--ink); }
.sp-defect-since {
  margin: 6px 0 0;
  font-family: var(--font-mono);
  font-size: 11px;
  color: var(--faint);
}
.sp-defect-done { flex: 0 0 auto; align-self: center; }

/* Bottom action bar.
   Replaces the old floating .sp-fab. A fixed circular button sat on top of the
   right-hand end of every full-width control it scrolled past — measured on the
   inspection checklist, it swallowed taps meant for "N/A" at three of four
   scroll positions and for "Weiter" at the fourth. An opaque bar cannot do
   that: the screen reserves its height as padding, so no content is ever
   rendered underneath it. Screens using this must set `padding-bottom:
   var(--actionbar-h)`. */
.sp-actionbar {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 30;
  background: var(--paper);
  border-top: 1px solid var(--rule);
  padding: 10px var(--page-px) calc(10px + env(safe-area-inset-bottom, 0));
}
.sp-actionbar-inner {
  max-width: var(--page-max);
  margin: 0 auto;
  display: flex; gap: 10px;
}
.sp-actionbar .sp-btn { flex: 1; height: 44px; }
.sp-actionbar .sp-btn svg { width: 20px; height: 20px; }

.sp-sheet-backdrop {
  position: fixed; inset: 0;
  background: rgba(28,32,54,.35);
  z-index: 90;
  opacity: 0;
  animation: spBackdrop .16s ease-out forwards;
}
@keyframes spBackdrop { to { opacity: 1; } }
/* A sheet that appears with an animation and vanishes without one reads as a
   crash. Both halves leave the way they arrived. */
.sp-sheet-backdrop.is-closing { animation: spBackdropOut .18s ease-in forwards; }
@keyframes spBackdropOut { from { opacity: 1; } to { opacity: 0; } }
.sp-sheet {
  position: fixed; left: 0; right: 0; bottom: 0;
  z-index: 91;
  background: var(--paper);
  border-top-left-radius: var(--radius-lg);
  border-top-right-radius: var(--radius-lg);
  box-shadow: 0 -8px 24px rgba(28,32,54,.15);
  padding: 8px var(--page-px) calc(24px + env(safe-area-inset-bottom, 0));
  max-width: var(--page-max);
  margin: 0 auto;
  transform: translateY(100%);
  animation: spSheet .28s var(--ease-out) forwards;
  /* A drag never scrolls the page behind the sheet. */
  overscroll-behavior: contain;
  touch-action: pan-y;
}
@keyframes spSheet { to { transform: translateY(0); } }
.sp-sheet.is-closing { animation: spSheetOut .2s ease-in forwards; }
@keyframes spSheetOut { from { transform: translateY(0); } to { transform: translateY(100%); } }
/* Under the finger while it is being dragged: no animation, JS owns the
   transform, and it snaps back or leaves on release. */
.sp-sheet.is-dragging { animation: none; transition: none; }
.sp-sheet.is-settling { animation: none; transition: transform .22s var(--ease-out); }

/* The grip was a 4px decoration announcing a gesture the sheet did not have.
   It is a real handle now: 40x4 of ink inside a 44pt-tall drag strip that runs
   the width of the sheet, and pulling it down dismisses. */
.sp-sheet-grip {
  display: block;
  width: 100%; height: 34px;
  margin: 0 0 8px;
  position: relative;
}
.sp-sheet-grip::before {
  content: '';
  position: absolute; top: 14px; left: 50%;
  width: 40px; height: 5px;
  margin-left: -20px;
  background: var(--rule-strong);
  border-radius: 3px;
  transition: background-color .18s ease;
}
.sp-sheet.is-dragging .sp-sheet-grip::before { background: var(--muted); }
.sp-sheet-title { font-size: 17px; font-weight: 600; margin: 0 0 4px; }
.sp-sheet-sub { font-size: 13px; color: var(--muted); margin: 0 0 16px; }
.sp-sheet-actions { display: flex; flex-direction: column; gap: 8px; margin-top: 16px; }
/* Subscription offer — the same block on the upgrade sheet and in Settings. */
.sp-offer { margin-top: 4px; }
.sp-offer-title { font-size: 17px; font-weight: 600; letter-spacing: -.01em; margin: 0 0 4px; color: var(--ink); }
.sp-offer-text { font-size: 14px; color: var(--muted); margin: 0 0 14px; line-height: 1.5; }
.sp-offer-plans { display: flex; flex-direction: column; gap: 8px; }
.sp-offer-plans .sp-btn-stack:first-child { background: var(--ink); color: var(--paper); border-color: var(--ink); }
.sp-offer-fine { font-size: 12px; color: var(--faint); line-height: 1.5; margin: 14px 0 8px; }
.sp-offer-links { display: flex; flex-direction: column; gap: 8px; margin: 0 0 4px; }
.sp-offer-link { justify-content: space-between; text-decoration: none; }
.sp-offer-link svg { flex: none; opacity: .6; }

/* A sheet choice with a title and a one-line meta underneath. */
.sp-btn-stack { height: auto; min-height: 52px; padding: 9px 16px; flex-direction: column; align-items: flex-start; gap: 2px; }
.sp-btn-stack-title { font-size: 15px; font-weight: 500; }
.sp-btn-stack-meta { font-family: var(--font-mono); font-size: 11px; letter-spacing: .02em; opacity: .7; }

/* ---------- 10. Review stats, notice, signature, meter ---------- */
.sp-stats {
  display: flex; flex-wrap: wrap; align-items: baseline;
  gap: 6px 14px;
  font-family: var(--font-mono);
  font-size: 13px;
  margin: 0 0 20px;
  padding: 0;
  line-height: 1.3;
}
.sp-stats .sp-stats-item { display: inline-flex; align-items: baseline; gap: 6px; margin: 0; }
.sp-stats dt {
  font-family: var(--font-ui);
  font-size: 10px;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  font-weight: 500;
  margin: 0;
}
.sp-stats dd { margin: 0; color: var(--ink); font-variant-numeric: tabular-nums; }
.sp-stats dd.is-crit { color: var(--crit); font-weight: 600; }
.sp-stats dd.is-warn { color: var(--warn); font-weight: 600; }
.sp-stats dd.is-ok { color: var(--ok); font-weight: 600; }

.sp-notice {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 10px 14px;
  border-left: 2px solid var(--warn);
  background: var(--warn-bg);
  color: var(--warn);
  font-size: 13px;
  margin-bottom: 20px;
  border-radius: 0 var(--radius-sm) var(--radius-sm) 0;
}
.sp-notice.is-crit { border-color: var(--crit); background: var(--crit-bg); color: var(--crit); }
/* Good news is a notice too — a repair confirmed since the last inspection.
   Without this it inherited the amber of a warning and read as a problem. */
.sp-notice.is-ok { border-color: var(--ok); background: var(--ok-bg); color: var(--ok); }

.sp-sig-wrap {
  position: relative;
  border: 1px solid var(--rule-strong);
  background: var(--surface);
  border-radius: var(--radius);
  padding: 8px 10px 4px;
}
.sp-sig-wrap canvas { width: 100%; display: block; border-bottom: 1px solid var(--rule); }
/* An instruction, not a label: sentence case, and readable. Uppercase 11px at
   2.7:1 was the quietest thing on a screen whose whole job is to be signed. */
.sp-sig-hint {
  position: absolute;
  top: 14px; left: 14px;
  font-size: 13px;
  color: var(--faint);
  pointer-events: none;
}
.sp-sig-foot { display: flex; justify-content: space-between; align-items: center; padding-top: 6px; }
.sp-sig-foot span {
  font-family: var(--font-mono);
  font-size: 10px;
  color: var(--faint);
  letter-spacing: 0.04em;
  text-transform: uppercase;
}

.sp-meter {
  font-size: 12px;
  color: var(--muted);
  margin: -6px 0 16px;
  display: flex; align-items: center; gap: 6px;
}
.sp-meter .sp-meter-val { font-family: var(--font-mono); color: var(--ink); }
.sp-meter a { color: var(--accent); text-decoration: none; border-bottom: 1px solid currentColor; }

/* ---------- 11. Empty states, photos, utilities ---------- */
/* An empty list is a position in the chain Auftraggeber -> Spielplatz ->
   Spielgeraet -> Inspektion, not a void. It says what belongs here and offers
   the step, left-aligned like every other field in this form -- a centred
   island with a grey icon disc belonged to a different product. The count in
   the section head already reports "empty", so the text never repeats it. */
.sp-empty { padding: 2px 0 4px; text-align: left; }
.sp-empty-text { font-size: 14px; color: var(--muted); margin: 0 0 14px; max-width: 34ch; }
.sp-empty .sp-btn { width: 100%; }

/* First-run chain. These four steps are a hard dependency order -- an
   inspection cannot start before equipment exists, the app refuses -- so the
   numbering carries information rather than decoration. Same register
   vocabulary as the rest of the app: mono numerals, uppercase labels, hairline
   rules. Only the step you are on carries ink, and only it explains itself. */
.sp-steps { list-style: none; margin: 18px 0 0; padding: 0; }
.sp-step {
  display: grid;
  grid-template-columns: 26px 1fr;
  gap: 10px;
  padding: 13px 0;
  border-top: 1px solid var(--rule);
}
.sp-step:first-child { border-top: 0; padding-top: 0; }
.sp-step-no {
  font-family: var(--font-mono);
  font-size: 11px;
  letter-spacing: 0.04em;
  color: var(--faint-soft);
  line-height: 1.6;
}
.sp-step-label {
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0;
  line-height: 1.6;
}
.sp-step-text { font-size: 14px; color: var(--muted); margin: 6px 0 0; max-width: 34ch; }
.sp-step-text.is-lead { margin-top: 0; color: var(--ink-2); }
.sp-step.is-current .sp-step-no,
.sp-step.is-current .sp-step-label { color: var(--ink); }
.sp-step.is-current .sp-btn { width: 100%; margin-top: 12px; }

.sp-photo-grid { display: flex; flex-wrap: wrap; gap: 8px; }
/* 84, not 68. A 68px tile of a crack in a beam is not a photo you can check
   before filing it, and it left the delete badge nowhere to sit. Four across a
   large phone, three across a small one. */
.sp-photo {
  position: relative;
  width: 84px; height: 84px;
  border-radius: var(--radius-sm);
  border: 1px solid var(--rule);
  /* No overflow:hidden -- the delete badge's reach spills past the tile. */
}
.sp-photo img {
  width: 100%; height: 100%;
  object-fit: cover; display: block;
  border-radius: inherit;
}
.sp-photo-del {
  position: absolute; top: 3px; right: 3px;
  width: 30px; height: 30px;
  background: rgba(28,32,54,.78); color: #fff;
  border-radius: 999px;
  font-size: 17px; line-height: 1;
  display: inline-flex; align-items: center; justify-content: center;
  transition: background-color .18s ease, transform .18s var(--ease-out);
}
/* The badge reads at 30px and takes taps at 44. Anything bigger drawn on a
   thumbnail hides the defect it is a badge for. */
.sp-photo-del::after {
  content: '';
  position: absolute; top: 50%; left: 50%;
  width: 44px; height: 44px;
  transform: translate(-50%, -50%);
}
/* Which defect a photo documents, when it was taken from one. */
.sp-photo-tag {
  position: absolute; left: 0; right: 0; bottom: 0;
  border-bottom-left-radius: var(--radius-sm);
  border-bottom-right-radius: var(--radius-sm);
  padding: 4px 6px;
  font-family: var(--font-mono);
  font-size: 9px; letter-spacing: .02em;
  color: #fff; background: rgba(28,32,54,.72);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}

.sp-inline-actions { display: flex; gap: 10px; margin-top: 12px; }
.sp-hidden { display: none !important; }
.sp-sr {
  position: absolute !important; width: 1px; height: 1px;
  padding: 0; margin: -1px; overflow: hidden;
  clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

/* Small screens */
@media (max-width: 380px) {
  :root { --page-px: 16px; }
}

/* ---------- Screen cross-fade (opt-in via .screen.active) ---------- */
.screen { display: none; }
.screen.active { display: block; animation: spFade 140ms ease-out; }
@keyframes spFade { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: translateY(0); } }

/* ---------- 12. Report preview (screen-report) ---------- */
/* The finished PDF is shown in-app before it is handed to the share sheet, so
   the export never feels like the app dumped a file at the user and quit. */
/* Pages are rendered onto a canvas rather than embedded as a file. WKWebView's
   built-in PDF view fits the *width* of whatever frame it is given, which in a
   phone-sized frame drops the reader into the top left corner of page one with
   no sign that pages two and three exist. */
.sp-report-viewer {
  position: relative;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--paper);
  overflow: hidden;
  margin-bottom: 8px;
}
.sp-report-stage {
  /* Tall enough that a whole A4 page is readable, short enough that the send
     button stays on screen. Was 64vh/560px, which left the pager and the send
     button crammed into the last 100px above the home indicator on a 956pt
     iPhone -- measured: pager bottom 882, send button 898-950, viewport 956. */
  height: min(58vh, 500px);
  overflow: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  display: flex;
  align-items: flex-start;
  /* `safe` so a zoomed page that is wider than the stage can still be scrolled
     back to its left edge — plain centring clips the overflowing start. */
  justify-content: safe center;
  padding: 10px;
}
.sp-report-page {
  display: block;
  background: #fff;
  border-radius: 2px;
  box-shadow: 0 1px 6px rgba(28, 32, 54, 0.18);
  /* The canvas is sized in JS to the chosen zoom; a max-width rule here would
     silently squash it and blur the text. */
  max-width: none;
}
.sp-report-pager {
  display: flex;
  align-items: center;
  gap: 4px;
  margin: 4px 0 18px;
}
/* 44x44 is Apple's minimum touch target. The width was already 44; the height
   came from .sp-btn-ghost and was 32, which is what made these hard to hit --
   they sit low on the screen, right where the thumb is least accurate. Scoped
   to the pager so the ghost buttons elsewhere keep their compact height. */
.sp-report-pager .sp-report-step,
.sp-report-pager .sp-report-zoom {
  height: 44px;
}
.sp-report-pager .sp-report-step {
  min-width: 44px;
  font-size: 20px;
  line-height: 1;
}
.sp-report-page-label {
  font-family: var(--font-mono);
  font-size: 13px;
  color: var(--muted);
  min-width: 92px;
  text-align: center;
}
.sp-report-pager .sp-report-zoom { margin-left: auto; }
.sp-report-fallback {
  position: absolute; inset: 0;
  display: flex; flex-direction: column;
  align-items: center; justify-content: center;
  gap: 10px;
  padding: 24px;
  text-align: center;
  background: var(--surface);
}
.sp-report-fallback .sp-report-fallback-icon { color: var(--faint); }
.sp-report-fallback p { margin: 0; font-size: 14px; color: var(--muted); line-height: 1.5; }
.sp-report-actions { display: flex; flex-direction: column; gap: 10px; }

/* ============================================================
   13. Pointer states — hover, press, focus
   ------------------------------------------------------------
   Every interactive style in this file used to be a :hover rule, and the app
   ships as a phone app. Two things follow from that, and both were visible on
   device:

   1. Nothing acknowledged a touch. `-webkit-tap-highlight-color: transparent`
      in the reset removes WebKit's own grey flash, and with no :active rule
      to replace it a tap produced no feedback at all until whatever it started
      finished — on the checklist, an IndexedDB write and a full re-render of
      up to 35 items later.
   2. What feedback there was, was wrong. WebKit applies :hover on touch-down
      and leaves it applied until something else is tapped, so the last control
      an inspector touched kept a highlight that meant nothing.

   So: hover is gated behind a pointer that can actually hover, and press is
   what a finger gets. Both blocks sit at the end of the file so they win
   against the base rules without needing !important.
   ============================================================ */

@media (hover: hover) and (pointer: fine) {
  .sp-iconbtn:hover { color: var(--ink); background: var(--press-soft); }
  .sp-foot a:hover { color: var(--ink); }
  .sp-row-body:hover { background: var(--press-soft); }
  .sp-row-del:hover { color: var(--crit); opacity: 1; background: var(--press-crit); }
  .sp-row-edit:hover { color: var(--ink); opacity: 1; background: var(--press-soft); }
  .sp-btn-primary:hover { background: var(--ink-2); }
  .sp-btn-secondary:hover { background: var(--press-soft); border-color: var(--ink); }
  .sp-btn-accent:hover { background: var(--accent-ink); }
  .sp-btn-destructive:hover { background: #85220F; }
  .sp-btn-ghost:hover { color: var(--ink); }
  .sp-btn-danger:hover { background: var(--press-crit); }
  .sp-segment > button:hover { color: var(--ink); }
  .sp-segment > button.is-active:hover { color: var(--paper); }
  .sp-device-tab:hover { border-color: var(--muted); }
  .sp-category-all-ok:hover { color: var(--ok); }
  .sp-photo-del:hover { background: var(--crit); }
}

/* --- Press ---------------------------------------------------------------
   The shrink is deliberately small: 3% is what iOS uses on a filled button,
   enough to read as "under my finger" at arm's length and not enough to make
   a list jump. Press is instant (transition-duration 0) and release fades
   back over the element's own transition — the asymmetry is what makes a
   button feel solid rather than laggy. */

.sp-btn:active,
.sp-iconbtn:active,
.sp-row-body:active,
.sp-row-edit:active,
.sp-row-del:active,
.sp-segment > button:active,
.sp-device-tab:active,
.sp-category-all-ok:active,
.sp-photo-del:active,
.sp-offer-link:active { transition-duration: 0s; }

/* Filled buttons darken by swapping the fill; outlined ones take a tint. */
.sp-btn-primary:active { background: var(--ink-3); transform: scale(var(--press-scale)); }
.sp-btn-accent:active { background: var(--accent-press); transform: scale(var(--press-scale)); }
.sp-btn-destructive:active { background: #7E1F0E; transform: scale(var(--press-scale)); }
.sp-btn-secondary:active { background: var(--press); border-color: var(--ink); transform: scale(var(--press-scale)); }
.sp-btn-ghost:active { background: var(--press); color: var(--ink); }
.sp-btn-danger:active { background: var(--press-crit); color: var(--crit); }
/* The import control is a <label>, not a <button> — it still has to press. */
label.sp-btn-secondary:active { background: var(--press); border-color: var(--ink); transform: scale(var(--press-scale)); }

/* Rows and tabs tint without shrinking: a list that flexes under the thumb
   reads as broken, not as responsive. */
.sp-row-body:active { background: var(--press); }
.sp-iconbtn:active { background: var(--press); color: var(--ink); }
.sp-row-edit:active { background: var(--press); color: var(--ink); opacity: 1; }
.sp-row-del:active { background: var(--press-crit); color: var(--crit); opacity: 1; }
.sp-category-all-ok:active { color: var(--ok); }
.sp-photo-del:active { background: var(--crit); transform: scale(.9); }

/* Checklist cells and equipment tabs — the two controls an inspection is
   actually made of. Tapping an already-active cell is a no-op in app.js, so
   the active cell dims rather than tinting. */
.sp-segment > button:active { background: var(--press); color: var(--ink); }
.sp-segment > button.is-active:active { opacity: .72; background: var(--ink); color: var(--paper); }
.sp-segment > button.is-active.is-ok:active { background: var(--ok); color: #fff; }
.sp-segment > button.is-active.is-warn:active { background: var(--warn); color: #fff; }
.sp-segment > button.is-active.is-crit:active { background: var(--crit); color: #fff; }
.sp-segment > button.is-active.is-muted:active { background: var(--info); color: #fff; }
.sp-device-tab:active { background: var(--press); border-color: var(--muted); }
.sp-device-tab.is-current:active { background: var(--ink-3); border-color: var(--ink-3); }

/* --- Focus ---------------------------------------------------------------
   Inputs already draw a focus ring. Buttons drew none, which left anyone on a
   hardware keyboard or Full Keyboard Access with no idea where they were.
   :focus-visible, so a finger tap never leaves a ring behind. */
.sp-btn:focus-visible,
.sp-iconbtn:focus-visible,
.sp-row-body:focus-visible,
.sp-row-edit:focus-visible,
.sp-row-del:focus-visible,
.sp-segment > button:focus-visible,
.sp-device-tab:focus-visible,
.sp-category-all-ok:focus-visible,
.sp-photo-del:focus-visible,
.sp-sheet-grip:focus-visible {
  outline: 2px solid var(--ink);
  outline-offset: 2px;
}

/* --- Toast ---------------------------------------------------------------
   Confirmations that used to be an alert() the user had to dismiss. A save
   that worked does not deserve a modal. */
.sp-toast {
  position: fixed;
  left: 50%;
  bottom: calc(28px + env(safe-area-inset-bottom, 0));
  transform: translate(-50%, 12px);
  z-index: 95;
  width: max-content;
  max-width: min(88vw, 420px);
  background: var(--ink);
  color: var(--paper);
  padding: 12px 16px;
  border-radius: var(--radius);
  font-size: 14px;
  line-height: 1.35;
  box-shadow: 0 8px 24px rgba(28,32,54,.28);
  opacity: 0;
  animation: spToastIn .22s var(--ease-out) forwards;
  pointer-events: none;
}
@keyframes spToastIn { to { opacity: 1; transform: translate(-50%, 0); } }
.sp-toast.is-leaving { animation: spToastOut .2s ease-in forwards; }
@keyframes spToastOut { from { opacity: 1; transform: translate(-50%, 0); } to { opacity: 0; transform: translate(-50%, 8px); } }
.sp-toast.is-crit { background: var(--crit); color: #FFF6F3; }
.sp-toast.is-ok { background: var(--ok); color: #fff; }

/* --- Reduced motion ------------------------------------------------------
   Honour the system setting: keep every state change, drop the movement. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-delay: 0ms !important;
    transition-duration: .01ms !important;
    scroll-behavior: auto !important;
  }
  .sp-btn:active,
  .sp-photo-del:active,
  label.sp-btn-secondary:active { transform: none; }
  .screen.active { animation: none; }
}

/* ============================================================
   14. Wide screens — iPad, and a phone held sideways
   ------------------------------------------------------------
   Everything above is written for a 520px column, because that is what a
   phone gives you. On an iPad the same column sat in the middle of a 1032pt
   screen with half the display empty on either side and every screen's
   content stacked into its top third. The app is shipped as universal
   (TARGETED_DEVICE_FAMILY = "1,2"), so that is a layout an inspector can
   actually get.

   The column widens in two steps and the content that can run side by side
   does: form fields two-up, and the checklist — the screen an inspection is
   spent on — in as many columns as fit. Nothing here changes the phone.
   ============================================================ */

@media (min-width: 700px) {
  :root { --page-max: 760px; --page-px: 28px; }

  /* Full-width buttons are a phone idea: they exist because 520px is about as
     wide as a button should ever be. At 760 they read as banners. */
  .sp-inline-actions .sp-btn-block,
  .sp-empty .sp-btn,
  .sp-report-actions .sp-btn,
  form > .sp-btn-block { width: auto; min-width: 260px; }
  .sp-inline-actions { flex-wrap: wrap; }

  /* Two fields per row. The hidden id input is display:none and takes no
     cell; the submit button spans both columns and keeps its own width. */
  #client-form, #site-form, #device-form, #settings-form {
    display: grid;
    grid-template-columns: 1fr 1fr;
    column-gap: 24px;
  }
  #client-form > .sp-btn, #site-form > .sp-btn,
  #device-form > .sp-btn, #settings-form > .sp-btn {
    grid-column: 1 / -1;
    justify-self: start;
    margin-top: 4px;
  }

  /* An equipment tab may be up to 46vw on a phone, where 46vw is 200px. On an
     iPad that is 475px and three tabs fill the screen. */
  .sp-device-tab { max-width: 240px; }

  /* Room for a whole report page instead of a letterbox. */
  .sp-report-stage { height: min(64vh, 760px); }
  .sp-photo { width: 104px; height: 104px; }

  /* The action bar spans the screen; the button inside it should not. */
  .sp-actionbar-inner { justify-content: center; }
  .sp-actionbar .sp-btn { flex: 0 1 320px; }

  /* A sheet the width of an iPad is a wall. Keep it thumb-sized, lift it off
     the bottom edge and round all four corners — the shape iOS gives a sheet
     once there is room around it. */
  .sp-sheet {
    max-width: 460px;
    left: 0; right: 0;
    bottom: 24px;
    border-radius: var(--radius-lg);
    padding-bottom: 20px;
  }
}

@media (min-width: 1000px) {
  :root { --page-max: 940px; }

  /* The checklist in columns. Each cell is a pass condition and its three
     buttons, which needs about 330px; the grid takes as many as fit and the
     category head spans the row above them. */
  .sp-category {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
    column-gap: 32px;
  }
  .sp-category-head { grid-column: 1 / -1; }

  .sp-title { font-size: 28px; }
  .sp-photo { width: 120px; height: 120px; }
}
