/* ═══════════════════════════════════════════════════════════════════════════
   PoolCrew surface layer
   ───────────────────────────────────────────────────────────────────────────
   Meesha 2026-09-18, on the Stop-screen prototype: "everything from the font
   styles to how you had it organized to the treatment on the copy, the
   hierarchy, the proportions, everything looks so good on that prototype...
   it should have its own CSS and brand guideline rules or whatever."

   WHAT THIS IS, AND WHAT IT DELIBERATELY IS NOT.

   It is NOT a second brand. brand/tokens.css stays the single source of truth
   for colour and type VALUES, and nothing here invents a hex that is not
   derived from it. Forking the palette per app is exactly the drift the
   suite-wide component work was written to stop, and PoolCrew would be the
   fourth place a cerulean is decided.

   What it IS: the layer that decides how those tokens are APPLIED here --
   density, row rhythm, card treatment, control sizing, where emphasis goes.
   That is genuinely app-specific and always has been. PoolBooks is a desk
   tool read on a large screen; PoolCrew is read one-handed, outdoors, in
   sunlight, by someone wearing gloves. The same tokens should not produce the
   same density on both, and pretending otherwise is what made this app look
   like a form rather than a field tool.

   So: shared vocabulary, app-specific application. Loaded AFTER styles.css so
   it wins on equal specificity, and scoped to the surfaces it restyles rather
   than resetting anything globally.

   Anything added here must use a token or a value derived from one. If a
   value is needed that tokens.css does not name, that is a gap in the token
   set and belongs there -- see the lesson about a token set missing a member
   people actually need generating its own drift.
   ═══════════════════════════════════════════════════════════════════════════ */

:root {
  /* Application constants for THIS app, derived from brand tokens. Named so a
     reader can see the intent rather than a magic number. */
  --crew-row-min:     44px;                     /* one-handed tap target */
  --crew-row-pad:     0.68rem;                  /* vertical rhythm of a data row */
  --crew-rule:        var(--tf-ink-200, #cce3ec);
  --crew-label:       var(--text-muted, #456f83);
  --crew-value:       var(--text-strong, #062840);
  --crew-action:      var(--tf-cerulean, #1f7fa4);
}

/* ── Data rows ─────────────────────────────────────────────────────────────
   A row is LABEL (quiet, left) and VALUE (loud, right). The app had these the
   other way round in weight: both at --fs-sm, the value in --text-muted, so
   the fact the row exists to carry was the faintest thing in it. */
.ss-propdetail-row {
  min-height: var(--crew-row-min);
  padding: var(--crew-row-pad) 0;
  border-bottom: 1px solid var(--crew-rule);
  gap: 1rem;
}
.ss-propdetails-body > .ss-propdetail-row:last-child,
.ss-propdetail-row:last-child { border-bottom: 0; }

.ss-propdetail-label {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 500;
  color: var(--crew-label);
}
.ss-propdetail-value {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 650;
  color: var(--crew-value);
  text-align: right;
}
.ss-propdetail-value.is-absent {
  font-weight: 500;
  font-style: italic;
  color: var(--crew-label);
}
.ss-profile-known { display: flex; align-items: center; gap: 0.75rem; }

/* ── "Change" ──────────────────────────────────────────────────────────────
   Gold is the CTA colour and this is not a call to action; it is a quiet
   affordance on a row of settled facts. Eleven gold underlines down one panel
   read as eleven things demanding attention. Cerulean, no underline, and a
   real tap target. */
.ss-profile-edit {
  padding: 0.35rem 0.1rem;
  min-height: 32px;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--crew-action);
  text-decoration: none;
}
.ss-profile-edit:active { opacity: 0.6; }

/* ── Disclosure summaries ──────────────────────────────────────────────────
   "Property details" / "Pool details" are section labels, so they get the
   same small-caps treatment as every other section header in the app rather
   than looking like body copy with a triangle. */
.ss-propdetails-summary {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-height: var(--crew-row-min);
  padding: 0;
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crew-label);
}
.ss-propdetails-summary::before {
  content: "";
  width: 9px; height: 9px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(-45deg);
  transition: transform 0.2s ease;
  flex: none;
}
.ss-propdetails[open] > .ss-propdetails-summary::before { transform: rotate(45deg); }

/* ── Tri-state gates ───────────────────────────────────────────────────────
   Yes / No / Not sure was a fixed 190px box, so "Not sure" wrapped to two
   lines and dragged every row taller. The control now takes the full width
   under its own label, which gives each cell room for one line. */
.ss-propdetail-row:has(.ss-gate-seg) { display: block; }
.ss-propdetail-row:has(.ss-gate-seg) .ss-propdetail-label { margin-bottom: 0.45rem; }
.ss-gate-seg { width: 100%; display: flex; }
.ss-gate-seg .seg-btn {
  flex: 1;
  min-height: 40px;
  padding: 0 0.35rem;
  font-size: var(--fs-sm, 0.8125rem);
  white-space: nowrap;
}

/* ── Yard sign ─────────────────────────────────────────────────────────────
   Four full-size buttons on their own line for a field that is set once a
   season. Same segmented treatment, same single line. */
/* nowrap and a stretched parent are both required: the existing rule sets
   flex-wrap:wrap, which put the fourth button on its own line, and the
   stacked row was only sizing to its content (265px of an available 366). */
.ss-propdetail-row-stack { align-items: stretch; }
.ss-ys-actions { display: flex; flex-wrap: nowrap; gap: 0; width: 100%; }
.ss-ys-actions .btn {
  flex: 1;
  min-height: 40px;
  padding: 0 0.35rem;
  font-size: var(--fs-sm, 0.8125rem);
  white-space: nowrap;
  border-radius: 0;
  border-right-width: 0;
}
.ss-ys-actions .btn:first-child { border-radius: 9px 0 0 9px; }
.ss-ys-actions .btn:last-child  { border-radius: 0 9px 9px 0; border-right-width: 1.5px; }

/* ═══ PASS TWO — the card shell, section headers, checklist rows, chemistry ══ */

/* ── Cards ────────────────────────────────────────────────────────────────
   The prototype's cards read as separate objects on a warm ground; these read
   as one continuous sheet, because a 1px --border at 10% opacity with no
   shadow and a 0.6rem gap does not separate anything. Slightly larger radius,
   a real (very soft) shadow, and more air between cards than inside them --
   which is what makes a stack scan as a list of things rather than as a wall. */
.ss-section {
  margin: 0 0.9rem 0.75rem;
  border-radius: 14px;
  border: 1px solid var(--crew-rule);
  box-shadow: 0 1px 2px rgba(6, 40, 64, 0.06), 0 4px 14px rgba(6, 40, 64, 0.05);
}

/* ── Section headers ──────────────────────────────────────────────────────
   The title is a LABEL for the card, not a heading competing with the content
   inside it. Small caps, tracked, muted -- the same treatment the disclosure
   summaries got in pass one, so every level of section header in the app now
   agrees with itself. */
.ss-section-header {
  min-height: var(--crew-row-min);
  padding: 0.7rem 1rem;
  border-bottom-color: var(--crew-rule);
}
.ss-section-title {
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crew-label);
}
.ss-section-sum {
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 650;
  color: var(--crew-label);
}

/* ── Checklist rows ───────────────────────────────────────────────────────
   0.55rem of padding on a row carrying a 24px control gives a ~34px target,
   under the 44px minimum this app is built around, and the rows ran into each
   other. Same rhythm as the data rows in pass one. */
.cl-row {
  min-height: var(--crew-row-min);
  padding: var(--crew-row-pad) 0;
  gap: 0.7rem;
  border-bottom-color: var(--crew-rule);
}
.cl-label { font-size: var(--fs-base, 0.9375rem); font-weight: 600; }
.cl-group-title {
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 750;
  letter-spacing: 0.06em;
  color: var(--crew-label);
  margin: 0.9rem 0 0.15rem;
}

/* ── Chemistry ────────────────────────────────────────────────────────────
   The label was cerulean and bold, which made every field name compete with
   the reading it labels AND with the one real action on the card. A field
   label is a label. The VALUE is what he is here to read. */
.chem-label {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 650;
  color: var(--crew-value);
}
.chem-field { padding-bottom: 0.15rem; }
.chem-field-head { margin-bottom: 0.4rem; }
.chem-input {
  font-weight: 700;
  color: var(--crew-value);
}

/* ═══ PASS THREE — the flag form ═══════════════════════════════════════════ */

/* ── Equipment + urgency: two tap-to-cycle chips (2026-10-19) ─────────────
   Meesha's stop-screen-reorg prototype. Each chip has a FIXED width so the
   row does not jump while it cycles: the equipment chip takes the remaining
   space and truncates, the urgency chip is a set width. Dashed border = tap
   to cycle; unset reads muted; the caption below says what the choice means. */
.ss-cycle-row { display: flex; gap: 0.5rem; align-items: stretch; margin: 0.15rem 0 0.35rem; }
.ss-cycle-chip {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  min-height: var(--crew-row-min);
  padding: 0.45rem 0.8rem;
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 650;
  color: var(--crew-value);
  background: var(--tf-ink-050, #f2fafc);
  border: 1.5px dashed var(--crew-action);
  border-radius: 999px;
  cursor: pointer;
  user-select: none;
  -webkit-tap-highlight-color: transparent;
}
.ss-cycle-chip > span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.ss-cycle-chip.unset { color: var(--crew-label); background: #fff; border-color: var(--crew-rule); font-weight: 600; }
.ss-eq-chip { flex: 1 1 0; min-width: 0; }
.ss-sev-chip { flex: 0 0 7.25rem; width: 7.25rem; }
.ss-sev-chip.sev-urgent { color: var(--status-danger-text, #9b2c2c); border-color: var(--status-danger-text, #9b2c2c); }
.ss-sev-chip.sev-watch_it { color: var(--status-warning-text, #8a5a12); border-color: var(--status-warning-text, #8a5a12); }
.ss-cycle-chip .ss-spark { flex: none; }
.ss-cycle-caption { margin: 0.1rem 0 0.35rem; }

/* ── Field labels and helper text in the flag form ────────────────────────
   The hint now sits directly under the question it explains, so it reads as
   an answer to that question rather than as a caption on whatever happens to
   be above it. */
.ss-field-label {
  display: block;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--crew-value);
  margin-bottom: 0.15rem;
}
.ss-internal-hint {
  font-size: var(--fs-sm, 0.8125rem);
  color: var(--crew-label);
  margin: 0.15rem 0 0.7rem;
  line-height: 1.4;
}

.ss-eq-fields { display: grid; gap: 0.45rem; margin-bottom: 0.5rem; }
.ss-eq-add-btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.35rem;
  min-height: var(--crew-row-min); font: inherit; font-weight: 650;
  color: var(--crew-action); background: #fff;
  border: 1.5px solid var(--crew-rule); border-radius: 10px; cursor: pointer;
}

/* ── Photo slots ──────────────────────────────────────────────────────────
   Three squares, always three. The row was a flex line that grew as photos
   were added and simply stopped offering the Add button at the cap, so the
   limit was only discoverable by reaching it. */
.ss-photo-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.55rem;
  flex-wrap: nowrap;
}
.ss-photo-add,
.ss-photo-ghost {
  /* BOTH dimensions. .ss-photo-add sets width AND height to 80px; naming only
     width left height at 80, and aspect-ratio:1 then resolved the width back
     to 80 from it. Third time today a later rule lost to a property it did
     not name. */
  width: auto;
  height: auto;
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  border-radius: 11px;
  font-size: var(--fs-xs, 0.75rem);
  color: var(--crew-label);
}
.ss-photo-ghost {
  border: 1.5px dashed var(--tf-ink-300, #a8c8d8);
  background: var(--tf-ink-050, #f2fafc);
}
/* handlePhotoInput now gives each thumbnail this class instead of inline
   styles, so it can simply fill its grid cell. */
.ss-photo-thumb {
  width: 100%;
  aspect-ratio: 1;
  object-fit: cover;
  border-radius: 11px;
  display: block;
}
.ss-photo-thumb-wrap {
  position: relative;
  display: block;
  width: 100%;
  aspect-ratio: 1;
}
/* The tag on a visit photo: Before / After / Equipment / Other. A dashed outline is a guess from the order the
   photos were taken, until Geoff taps it. */
.ss-photo-tag {
  position: absolute; left: 6px; top: 6px; min-height: 36px; padding: 0 12px;
  border: 0; border-radius: 18px; font: inherit; font-size: var(--fs-xs); font-weight: 700;
  background: rgba(6, 40, 64, .86); color: #fff; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-photo-tag.before { background: var(--tf-cerulean, #0b4f6c); }
.ss-photo-tag.after { background: var(--status-success, #2d7a4f); }
.ss-photo-tag.guess { background: rgba(6, 40, 64, .55); border: 2px dashed #fff; font-weight: 600; }

/* ── "Worth a look today" ─────────────────────────────────────────────────
   Sits on its own above the Visit card, outside any card, so it cannot be
   collapsed away. The dashed rule STAYS: Meesha put it there to make these
   stand out, and flattening it was my mistake -- the point of this block is
   to be noticed, and a block that matches everything around it is not.
   Strengthened rather than removed: a solid accent rule reads at a glance in
   sunlight where a dashed hairline does not. */
.ss-brief-flags {
  margin: 0 0.9rem 0.75rem;
  padding: 0.6rem 0 0.6rem 0.85rem;
  border: 0;
  border-left: 3px solid var(--tf-cerulean, #1f7fa4);
  background: var(--tf-ink-050, #f2fafc);
  border-radius: 0 10px 10px 0;
}
.ss-brief-flags .ss-block-label {
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crew-action);
  margin-bottom: 0.3rem;
}
.ss-brief-flag {
  /* display:block, not the base rule's flex. The bullet is absolutely
     positioned here, so the flex container and its 0.45rem gap are dead
     styling that only misleads the next person to edit this. */
  display: block;
  position: relative;
  padding: 0.22rem 0.85rem 0.22rem 0.9rem;
  font-size: var(--fs-base, 0.9375rem);
  line-height: 1.4;
  color: var(--crew-value);
}
.ss-brief-flag::before {
  content: "";
  position: absolute;
  left: 0.28rem;
  top: 0.72rem;
  /* margin-top: 0 is the fix. The base rule sets margin-top: 0.48rem to nudge
     the bullet down when it was a flex item, and a margin still applies to an
     absolutely positioned box -- so top and margin ADDED, putting the dot
     7.7px below where this rule says it is and a clear step under the first
     line. Measured at 19.2px against a first-line centre of 14px. Fourth time
     a later rule has lost to a property it did not name; naming every one of
     them is the only thing that has ever worked. */
  margin: 0;
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: var(--tf-cerulean, #1f7fa4);
}

/* ── Equipment actions sheet ──────────────────────────────────────────────
   Everything you can do to one unit, in one place. Each action routes to a
   path that already exists -- notably "Replaced with a new one" opens the
   same editor rather than getting its own write, because entering a different
   model already asks whether it is a correction or a replacement, and that
   question is the only thing that can tell them apart. */
.eq-sheet-head {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin-bottom: 0.9rem;
}
.eq-sheet-head .contact-sheet-name { flex: 1; margin: 0; }
.eq-sheet-action {
  display: block;
  width: 100%;
  min-height: var(--crew-row-min);
  margin-bottom: 0.5rem;
}
/* Destructive, and the only one here that is. Solid red per the documented
   Danger role: this is confirm-grade, not a quiet affordance. */
.eq-sheet-remove {
  background: var(--status-danger, #DC2626);
  color: #fff;
  border: 0;
}
.eq-sheet-note {
  margin: 0.6rem 0 0;
  font-size: var(--fs-sm, 0.8125rem);
  line-height: 1.45;
  color: var(--crew-label);
}

/* ═══ PASS FOUR — the guided step card, and the row icons ═════════════════ */

/* ── The step card ────────────────────────────────────────────────────────
   It was a card inside a card: its own border, its own shadow and a 16px
   radius sitting inside the Visit card's, with 1.75rem of padding on top of
   the Visit card's own. That double frame is what read as misaligned. It is
   the Visit card's content, so it drops the frame and shares the padding. */
.gv-step-card {
  border: 0;
  border-radius: 12px;
  box-shadow: none;
  background: var(--tf-ink-050, #f2fafc);
  padding: 1.25rem 1rem 1.1rem;
}
/* A display serif at --fs-2xl for "Before photo" competes with the customer's
   name for the largest thing on screen. It is a step title, not a headline. */
.gv-step-label {
  font-family: inherit;
  font-size: var(--fs-lg, 1.0625rem);
  font-weight: 750;
  color: var(--crew-value);
  margin: 0 0 0.35rem;
}
.gv-step-guidance {
  font-size: var(--fs-base, 0.9375rem);
  color: var(--crew-label);
  line-height: 1.45;
  margin: 0 0 0.9rem;
}
.gv-step-icon { color: var(--crew-action); }
.gv-photo-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  width: 100%;
  min-height: 48px;
  margin-bottom: 0.5rem;
  cursor: pointer;
}
.gv-done-btn { width: 100%; min-height: var(--crew-row-min); }

/* ── Row icons: rounded squares ───────────────────────────────────────────
   Meesha prefers the squares to the circles, and the important half is that
   they now agree: the checklist row pair and the sheet mic were two shapes
   for one idea. Squares everywhere, at the 44px target. */
.cl-mic-btn,
.cl-cam-btn,
.text-edit-mic {
  width: 40px;
  height: 40px;
  border-radius: 11px;
  border: 1.5px solid var(--crew-rule);
  color: var(--crew-action);
}
.text-edit-mic { width: 44px; height: 44px; }


/* ═══ TYPE — the prototype's scale, mapped onto the brand tokens ══════════
   Meesha wants the prototype's sizing and proportions for every text role.
   Its values are 12.5 / 13 / 13.5 / 14.5 / 15 / 19 / 21px, and they land on
   the token scale almost exactly:

     prototype 13px   -> --fs-xs    13px   labels, hints, statuses
     prototype 14.5px -> --fs-sm    14px   field labels, values, button text
     prototype 15px   -> --fs-base  15px   body, inputs
     prototype 19/21  -> --fs-lg    20px   readings, the customer's name

   So this is not a new scale, it is the existing one applied at app density
   instead of website density. --fs-md (17px) does not appear: tokens.css
   names it "website body copy, long-form reading", and using it as the app's
   body size is what made everything feel heavy.

   Roles, in one place, so a new surface has somewhere to look rather than
   picking a size. */

/* Headings: a card's label, not a headline competing with its own contents. */
.ss-section-title,
.ss-propdetails-summary,
.cl-group-title,
.ss-block-label {
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 750;
  letter-spacing: 0.06em;
  text-transform: uppercase;
}

/* Statuses: the count or state on a card header. Quiet, never bold. */
.ss-section-sum { font-size: var(--fs-xs, 0.8125rem); font-weight: 650; }

/* Field labels: the question, not the answer. */
.ss-field-label,
.chem-label,
.ss-propdetail-label { font-size: var(--fs-sm, 0.875rem); }
.ss-field-label, .chem-label { font-weight: 700; }

/* Saved and historical data: the answer is the loud half of its row, and the
   date under it is the quietest thing on the card. */
.ss-propdetail-value { font-size: var(--fs-sm, 0.875rem); font-weight: 650; }
.chem-hint, .ss-internal-hint, .ss-guided-hint, .gv-action-hint,
.ss-share-hint, .chem-cadence-note {
  font-size: var(--fs-xs, 0.8125rem);
  line-height: 1.4;
  color: var(--crew-label);
}

/* Inputs: a reading is the biggest thing on its row, because it is the thing
   being read back. Text inputs stay at body size. */
.chem-input { font-size: var(--fs-lg, 1.25rem); font-weight: 700; }
.ss-input, .ss-text-input, .ss-textarea, .field-input {
  font-size: var(--fs-base, 0.9375rem);
}
/* Placeholder is the same size as the text it stands in for, one step
   quieter. Sizing it differently makes a field jump when he types. */
.chem-input::placeholder { color: var(--tf-ink-400, #7fa3b5); font-weight: 650; }
.ss-input::placeholder,
.ss-text-input::placeholder,
.ss-textarea::placeholder,
.field-input::placeholder { color: var(--tf-ink-400, #7fa3b5); }

/* Button labels. */
.btn { font-size: var(--fs-base, 0.9375rem); font-weight: 700; }
.btn-sm { font-size: var(--fs-sm, 0.875rem); }

/* NO DISPLAY SERIF IN THE APP. Libre Baskerville is the brand's voice on the
   website and on documents a customer receives; inside a field tool it reads
   as decoration on a form, and at --fs-2xl it made "Before photo" the largest
   thing on the screen. The five app rules that reach for it are overridden
   here rather than edited in styles.css, so the website and the sent
   documents are untouched. */
.gv-step-label,
.gv-finish-label,
.gv-review-confirm-title,
.pro-name { font-family: inherit; }

/* ══════════════════════════════════════════════════════════════════════════
   CHEMISTRY: the mic and the shock sheet
   Two shapes replaced here (2026-09-18). The mic was a full-width outlined
   button, which is a third mic treatment in one app; it is now the SAME round
   control the checklist rows and the text editor use, with the words beside
   it. The shock flow was a <details> whose summary carried a sentence, which
   does not read as tappable; it is now a row that opens a sheet.
   Every property a rule cares about is named, because a later rule that names
   only some of them is how the calculate button lost its border twice.
   ══════════════════════════════════════════════════════════════════════════ */
/* The recording state lived on .chem-mic-btn and still does, so the pulse
   keeps working; only the resting shape changed. */
/* width: auto here collapsed the 40px square to 21px, because .cl-mic-btn's
   width is a property this rule NAMES and therefore wins on. Measured, not
   read: the same failure as the calculate button's border, twice. */
.chem-mic-btn {
  width: 40px;
  height: 40px;
  min-height: 0;
  margin-bottom: 0;
  flex: 0 0 auto;
}

.chem-shock-open {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  width: 100%;
  margin-top: 0.85rem;
  padding: var(--crew-row-pad, 0.75rem 0.85rem);
  min-height: var(--crew-row-min, 44px);
  text-align: left;
  font-family: inherit;
  background: var(--tf-ink-050, #f2fafc);
  border: 1px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  color: var(--tf-cerulean, #1f6f9c);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.chem-shock-open:active { opacity: 0.7; }
.chem-shock-open-text { flex: 1 1 auto; min-width: 0; }
.chem-shock-open-text b {
  display: block;
  font-size: var(--fs-base, 15px);
  font-weight: 700;
  color: var(--tf-ocean, #17475f);
}
.chem-shock-open-text span {
  display: block;
  font-size: var(--fs-xs, 13px);
  font-weight: 500;
  color: var(--text-muted, #5b7183);
}
.chem-shock-chev { flex: 0 0 auto; color: var(--text-muted, #5b7183); }

/* Inside the sheet the options get room, so they stack instead of competing
   for one row's width. */
#chem-shock .chem-shock-btns {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
}
#chem-shock .chem-shock-btn { flex: 1 1 30%; min-width: 92px; }
#chem-shock #chem-shock-calc {
  width: 100%;
  margin-top: 1rem;
}

/* ══════════════════════════════════════════════════════════════════════════
   PASS FIVE — the arrival step, Navigate in the customer card, the player
   ══════════════════════════════════════════════════════════════════════════ */

/* The guided container sat flush to the Visit card's edge while .cl-body
   above it carries 1rem of horizontal padding, so the step progress row
   hung further left than the checklist rows it follows. Same inset. */
#ss-guided-container { padding-left: 1rem; padding-right: 1rem; }
.gv-progress-row { margin-bottom: 0.45rem; }

/* Before photo: two pale-blue buttons on a pale-blue card, so neither read as
   the one to tap. The action is filled in the signature cyan (the documented
   in-panel primary); the fallback keeps a quiet outline. Gold is not used --
   the FAB already owns the one gold button on this screen.
   Every property is named: a base .btn-soft rule that sets background and
   colour separately is exactly how this loses half its declarations. */
.gv-photo-btn.btn-soft {
  background: var(--tf-ocean, #1a9fc4);
  border: 1.5px solid var(--tf-ocean, #1a9fc4);
  color: #fff;
  font-weight: 700;
}
.gv-photo-btn.btn-soft:active { opacity: 0.85; }
.gv-done-btn.btn-ghost {
  background: transparent;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  color: var(--crew-label, #5b7183);
  font-weight: 600;
}

/* Navigate now lives in the customer card, under the address. */
.ss-navigate-wrap { margin-top: 0.7rem; }
.ss-navigate-wrap .ss-navigate-btn { width: 100%; }
.ss-navigate-wrap .ss-omw-status { margin: 0.45rem 0 0; }

/* The play button was --tf-cerulean, which is the DEEP navy in this token set,
   not the cyan its name suggests. The prototype's is the lighter pool-water
   blue. */
.ss-brief-play { background: var(--tf-ocean, #1a9fc4); }

/* The picker is type-ahead now, so a row needs to say which ones came off
   today's route. */
.quote-picker-row-tag {
  flex: 0 0 auto;
  align-self: center;
  font-size: var(--fs-xs, 13px);
  font-weight: 700;
  color: var(--tf-ocean, #1a9fc4);
  background: var(--tf-ink-050, #f2fafc);
  border-radius: 999px;
  padding: 2px 8px;
}

/* A lead reads as a lead, not as a customer he forgot about. Amber rather than
   the ocean cyan of the Today tag, because tapping it WRITES -- it creates the
   customer record -- and that deserves to look different from picking someone
   who is already in the book.
   --status-warning-text on --status-warning-soft is the pair tokens.css
   documents at 4.94:1, i.e. AA. Not a hand-picked amber, and not --tf-saffron
   on --tf-sand, which is nowhere near AA at this size. */
.quote-picker-row-tag-lead {
  color: var(--status-warning-text);
  background: var(--status-warning-soft);
}

/* ── The quote sheet's photo strip is the Visit page's photo row ──────────
   Meesha: "The photos should default to 3 placeholders like everywhere else
   ... bring that consistency of design into here." It was a 58px scrolling
   flex line whose only content on a fresh quote was one small camera icon,
   so nothing said photos belonged here. It now carries .ss-photo-row and
   .ss-photo-add and inherits that design rather than resembling it.
   Every property the old flex rules set is named again here, because a rule
   that overrides only some of them is how the mic ended up 21px wide. */
.quote-photo-strip.ss-photo-row {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.55rem;
  overflow-x: visible;
  padding: 2px 0 6px;
}
.quote-cam.ss-photo-add {
  flex: 0 0 auto;
  width: auto;
  height: auto;
  aspect-ratio: 1;
  border: 1.5px dashed var(--border, #cce3ec);
  border-radius: 11px;
  background: var(--tf-sand, #f5ead8);
  color: var(--crew-label, #5b7183);
  flex-direction: column;
  gap: 0.3rem;
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 600;
}
.quote-photo-strip .quote-photo-cell {
  flex: 0 0 auto;
  width: auto;
  height: auto;
  aspect-ratio: 1;
  border-radius: 11px;
}
/* "See all" opens a second row of the same three-up grid, not a wrapped
   scroller, so both rows read as one set. */
.quote-photo-overflow {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 0.55rem;
  overflow-x: visible;
}

/* One material for the whole photo row. .ss-photo-add was --tf-sand (warm
   beige) while .ss-photo-ghost was --tf-ink-050 (pale blue), so a row of
   three slots rendered in two materials and read as two different controls
   sitting together. Same ground for all three; the Add tile is the one with
   a solid cyan edge and cyan label, the empty ones stay dashed and muted.
   This lands on the Visit page and the quote sheet at once, which is the
   point -- they are now literally the same row, not two that resemble
   each other. */
.ss-photo-add,
.quote-cam.ss-photo-add {
  background: var(--tf-ink-050, #f2fafc);
  border: 1.5px solid var(--tf-ocean, #1a9fc4);
  color: var(--tf-ocean, #1a9fc4);
  font-weight: 700;
}
.ss-photo-ghost {
  border: 1.5px dashed var(--tf-ink-300, #a8c8d8);
  background: var(--tf-ink-050, #f2fafc);
  color: var(--crew-label, #5b7183);
  font-weight: 600;
}

/* The reason beside a disabled Send is guidance, not an error. It was
   danger red, which is the same register the removed inline warning used:
   it tells him he did something wrong before he has done anything. */
.q-send-why.q-intent-warn {
  color: var(--text-muted, #5b7183);
  font-weight: 600;
}

/* ══════════════════════════════════════════════════════════════════════════
   THE QUOTE SHEET, ON THE VISIT PAGE'S STYLES
   Meesha: "everything on this sheet should follow the new styles that we're
   implementing for the visit page ... do not break any functionality or any
   layout but make this look modern and clean."

   Scoped to #quote-sheet on purpose. Every rule below could be argued for
   app-wide, and several should eventually be, but a sheet is a bounded
   surface I can see all of; widening these to every sheet in the same pass
   is how a type change becomes an unreviewed reflow on nine screens.

   The type scale is remapped rather than restated. --fs-md is 17px, the old
   inflated root, and it is what made "Cancel" render a size larger and a
   weight lighter than the two buttons directly above it -- the mismatch in
   the screenshot. It is read by ~40 rules across the app, so retiring it
   globally is its own reviewed sweep; redefining it ON THIS SHEET moves
   every consumer inside these walls at once and cannot touch anything
   outside them.
   ══════════════════════════════════════════════════════════════════════════ */
#quote-sheet {
  --fs-md: var(--fs-base, 0.9375rem);
}

/* ── Buttons: one shape, three registers ──────────────────────────────────
   The design system's roles, applied as written. Gold FINISHES the thing
   (Send to office is the only gold on this sheet, and the FAB is not on
   screen behind it). Cyan-tinted soft is the secondary. Transparent with an
   edge is the way out. They were previously gold / bare-white / bare-white,
   so the two escapes were indistinguishable from each other. */
#quote-sheet .btn,
#quote-sheet .contact-cancel-btn {
  font-family: inherit;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  border-radius: 12px;
  min-height: var(--crew-row-min, 44px);
  line-height: 1.2;
}
#quote-sheet .contact-cancel-btn {
  font-weight: 600;
  padding: 0.7rem 1rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  color: var(--crew-label, #5b7183);
  background: transparent;
}

/* ── Cards: the pale ground, not the warm beige ───────────────────────────
   .q-intent was --tf-sand, which is the warm beige used for marketing
   surfaces. Next to the white line-item cards it read as a different
   material rather than a grouped question. Same ground and hairline the
   Visit page's cards use. */
#quote-sheet .q-intent,
#quote-sheet .quote-rates {
  background: var(--tf-ink-050, #f2fafc);
  border: 1px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
}
#quote-sheet .q-item {
  border: 1px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
}

/* ── Labels, questions, helper text ───────────────────────────────────────
   The same three registers the Visit page uses: the question at base weight
   750, helper at sm in the muted colour, section eyebrows uppercased at xs. */
#quote-sheet .q-intent-q {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 750;
  color: var(--crew-value, #17475f);
}
#quote-sheet .q-intent-sub,
#quote-sheet .q-intent-note,
#quote-sheet .settings-sublabel,
#quote-sheet .quote-rate-hint,
#quote-sheet .sheet-sub {
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 500;
  color: var(--crew-label, #5b7183);
}
#quote-sheet .sheet-title {
  font-family: inherit;
  font-size: var(--fs-lg, 1.25rem);
  font-weight: 750;
  color: var(--crew-value, #17475f);
}
#quote-sheet .quote-ph-t,
#quote-sheet .quote-rates-t {
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crew-label, #5b7183);
}

/* ── The segmented control and the job-type tiles ─────────────────────────
   Both are pick-one controls and were drawn two ways: a hairline box with
   a hard cyan fill, beside tiles with a shadow ring. One treatment. */
#quote-sheet .settings-segmented {
  border: 1px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  overflow: hidden;
}
#quote-sheet .seg-btn {
  font-family: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 600;
  min-height: var(--crew-row-min, 44px);
  color: var(--crew-label, #5b7183);
  background: #fff;
}
#quote-sheet .seg-btn + .seg-btn { border-left: 1px solid var(--crew-rule, #dde7ef); }
#quote-sheet .seg-btn[aria-pressed="true"] {
  background: var(--tf-ocean, #1a9fc4);
  color: #fff;
  font-weight: 700;
}
#quote-sheet .q-cat-btn {
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
}
#quote-sheet .q-cat-btn.is-on {
  border-color: var(--tf-ocean, #1a9fc4);
  background: var(--tf-ink-050, #f2fafc);
  /* The inset ring doubled the border into a 2.5px edge on the selected tile
     only, so picking one nudged the grid. The border colour is the state. */
  box-shadow: none;
}
#quote-sheet .q-cat-label {
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
}
#quote-sheet .q-cat-hint {
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 500;
  color: var(--crew-label, #5b7183);
}

/* The ownership note, now behind the customer card's kebab. */
.ss-sheet-hint {
  font-size: var(--fs-sm, 0.8125rem);
  color: var(--crew-label, #5b7183);
  line-height: 1.45;
  margin: 0 0 0.6rem;
}
#ss-cust-menu-sheet textarea {
  width: 100%;
  min-height: 84px;
  font: inherit;
  font-size: var(--fs-base, 0.9375rem);
  padding: 0.65rem 0.75rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  color: var(--crew-value, #17475f);
  resize: vertical;
}
#ss-cust-menu-sheet textarea:focus {
  outline: none;
  border-color: var(--tf-ocean, #1a9fc4);
}
.ss-own-send {
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  margin-top: 0.7rem;
  border-radius: 12px;
  font-weight: 700;
}

/* ── Chemistry: the card, not a beige box inside a white card ─────────────
   Meesha, against the prototype: "make this card look a lot more like the
   prototype because it's still looking very beige and not as nice."
   .chem-entry painted --tf-sand and its own 12px radius INSIDE the white
   Chemistry section card, so the readings sat in a second, warmer card with
   a second copy of the word CHEMISTRY at the top of it. It is the section's
   content, so it drops the box and shares the card. */
.chem-entry {
  margin: 0 0 0.2rem;
  background: transparent;
  border-radius: 0;
  /* Top padding is not optional here: dropping the inner card's own padding
     left the first field's label sitting on the header's bottom rule. */
  padding: 0.9rem 1rem;
}

/* The mic, now in the section header beside the count. Icon only: the label
   stays in the DOM because chemToggleMic writes the recording state into it,
   so it is hidden from sight rather than deleted. */
/* The header is space-between, so with four children everything spreads
   evenly and the mic floats mid-row. margin-right:auto groups the title and
   the mic on the left and pushes the count and the chevron, which ARE a
   pair, to the right where they belong. */
.ss-chem-mic-slot {
  display: inline-flex;
  align-items: center;
  margin-left: 0.55rem;
  margin-right: auto;
}
.ss-chem-mic-slot:empty { margin: 0; }
.chem-mic-btn {
  width: 36px;
  height: 36px;
  min-height: 0;
  margin: 0;
  flex: 0 0 auto;
  border-radius: 10px;
}
.chem-mic-text {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ══════════════════════════════════════════════════════════════════════════
   PROPERTY & EQUIPMENT: rows, not stacked fragments
   Meesha, twice: "the layout in this card is completely broken ... it also
   needs closer alignment with the prototype."

   Everything in here was a .ss-det-kv, which is a COLUMN: label on one line,
   value on the next, no separator, full bleed. So "Getting in" had a bare
   underlined Add floating at the far right above an italic "Nothing on file
   yet", "Gates" sat above its own sentence, and the equipment entries were
   four stacked lines with two kebabs drifting beside them. Nothing lined up
   with anything because nothing shared a column.

   The prototype is a list of ROWS: muted label left, the value or the action
   right, a hairline between. That is what this does. Layout only -- no
   markup, no ids, no handlers change.
   ══════════════════════════════════════════════════════════════════════════ */
.ss-section-prop #ss-prop-body { padding: 0 1rem 0.5rem; }

/* The label/value row. grid, not flex: the value column must be allowed to
   shrink and wrap rather than push the label off, which is what a flex row
   with a long gates sentence does. */
.ss-section-prop .ss-det-kv {
  display: grid;
  grid-template-columns: auto 1fr;
  align-items: baseline;
  column-gap: 0.9rem;
  row-gap: 0.15rem;
  padding: 0.7rem 0;
  border-bottom: 1px solid var(--crew-rule, #dde7ef);
  min-height: var(--crew-row-min, 44px);
}
.ss-section-prop .ss-det-kv:last-child { border-bottom: 0; }
.ss-section-prop .ss-det-kv > b {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 500;
  color: var(--crew-label, #5b7183);
  text-transform: none;
  letter-spacing: 0;
}
.ss-section-prop .ss-det-kv > span {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 650;
  color: var(--crew-value, #17475f);
  text-align: right;
  min-width: 0;
}
/* Specificity, not order: `.ss-section-prop .ss-det-kv > span` is (0,2,1) and
   this was (0,2,0), so "Nothing on file yet." rendered as a bold dark VALUE.
   Matching the same shape puts it back to a muted empty state. */
.ss-section-prop .ss-det-kv > span.ss-det-empty {
  font-weight: 500;
  font-style: normal;
  color: var(--crew-label, #5b7183);
}

/* "Getting in" carries an Edit/Add action as well as a value, so its head is
   the row and the value sits under it, right-aligned to the same edge. */
.ss-section-prop .ss-det-kv-edit {
  grid-template-columns: 1fr auto;
  align-items: center;
}
.ss-section-prop .ss-det-kv-edit > .ss-det-kv-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
}
.ss-section-prop .ss-det-kv-edit > span {
  grid-column: 1 / -1;
  text-align: left;
}
/* The action is a target, not a link in a sentence. A bordered pill, which is
   what the prototype uses for every action in this card. */
.ss-section-prop .ss-det-editlink {
  padding: 0.35rem 0.8rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 999px;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--tf-ocean, #1a9fc4);
  background: #fff;
  text-decoration: none;
  min-height: 34px;
}
.ss-section-prop .ss-det-editlink::after { content: none; }

/* Equipment: an uppercase type eyebrow, the unit under it, the kebab right.
   It was inheriting the label/value row above, which put the type and the
   model in two different columns and left the kebab absolutely positioned
   over whichever one was longer. */
.ss-section-prop .ss-eq-kv {
  display: grid;
  grid-template-columns: 1fr auto;
  grid-template-areas: "type kebab" "val kebab";
  align-items: center;
  column-gap: 0.5rem;
  row-gap: 0.1rem;
  position: static;
  padding: 0.7rem 0;
  padding-right: 0;
}
.ss-section-prop .ss-eq-kv > b {
  grid-area: type;
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crew-label, #5b7183);
}
.ss-section-prop .ss-eq-kv > span {
  grid-area: val;
  text-align: left;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 650;
  color: var(--crew-value, #17475f);
}
.ss-section-prop .ss-eq-kebab {
  grid-area: kebab;
  position: static;
  align-self: center;
  width: 40px;
  height: 40px;
  border: 0;
  background: transparent;
  color: var(--crew-label, #5b7183);
  font-size: 18px;
  border-radius: 10px;
}

/* The two disclosure panels were rendering as tall empty blocks: a summary
   row and then a body with padding whether or not it had anything in it. */
.ss-section-prop .ss-propdetails {
  border-top: 1px solid var(--crew-rule, #dde7ef);
  padding: 0;
  margin: 0;
}
.ss-section-prop .ss-propdetails-summary {
  min-height: var(--crew-row-min, 44px);
  display: flex;
  align-items: center;
  padding: 0.7rem 0;
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--crew-label, #5b7183);
}
.ss-section-prop .ss-propdetails:not([open]) > .ss-propdetails-body { display: none; }
.ss-section-prop .ss-propdetails-body { padding: 0 0 0.5rem; }
.ss-section-prop #ss-gate-section:empty,
.ss-section-prop #ss-profile-section:empty { display: none; }

/* ── Shocking a pool is not good news ────────────────────────────────────
   Meesha: "it genuinely deserves an amber because it's not a good thing
   when we have to shock a pool." It was the neutral pale ground every other
   row uses, which reads as one more option rather than the thing you reach
   for when the water has gone green. Amber, not red: this is a condition to
   act on, not an error he has made. */
.chem-shock-open {
  background: var(--status-warning-bg, #fdf4e3);
  border-color: var(--status-warning-border, #e8c98a);
}
.chem-shock-open .chem-shock-open-text b { color: var(--status-warning-text, #7a5312); }
.chem-shock-open > svg:first-child { color: var(--status-warning-text, #7a5312); }

/* The notes mic was a filled GOLD square, which is the finishing-action
   colour: it read as the most important thing in the card, louder than
   "Add note" beside it, and it is the fourth mic shape in an app that now
   has one. Same rounded square as the checklist rows, the text editor and
   chemistry. The recording state keeps its own rule and still wins. */
.ss-notes-mic-btn {
  width: 44px;
  height: 44px;
  min-width: 44px;
  min-height: 44px;
  border-radius: 11px;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  background: #fff;
  color: var(--crew-action, #1a9fc4);
}

/* The outcome word on a completed checklist row. Text, not a dead button:
   it reports, it is not something to press. */
.cl-done-note {
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 600;
  color: var(--crew-label, #5b7183);
  padding-right: 0.15rem;
}

/* ── The disclosure arrow was drawing TWICE ───────────────────────────────
   styles.css puts a literal glyph in ::before (content: '▸ ', and '▾ ' under
   `.ss-propdetails[open] > .ss-propdetails-summary::before`). The rule above
   replaced it with a rotated CSS box and set content to "" -- but only on the
   PLAIN selector, which is (0,1,1). The [open] one is (0,2,2) and wins, so an
   expanded panel rendered the glyph AND the rotated box on top of each other,
   which is the mark Meesha saw beside POOL DETAILS. Closed panels looked fine,
   which is why it survived. Same failure as every other one today: the
   override has to name the property on the same selector that sets it. */
.ss-propdetails[open] > .ss-propdetails-summary::before { content: ""; }

/* ── The rule above the yard-sign buttons ────────────────────────────────
   The label lives in a .ss-propdetail-row and the four buttons are a sibling
   OUTSIDE it, so the row's bottom border drew between a label and its own
   control. The separator belongs after the pair, not through it. */
.ss-propdetail-row-stack { border-bottom: 0; padding-bottom: 0.25rem; }
.ss-ys-actions { margin: 0.15rem 0 0.5rem; border-bottom: 0; }

/* ── Value vs action ─────────────────────────────────────────────────────
   Meesha: "can't really differentiate between the entry liquid and the call
   to action which is the word change because everything looks the same and
   is the same size." They were both --fs-sm at similar weight, separated
   only by colour, so a row read as two words rather than a fact and a
   control. The value stays text and grows; Change becomes the same bordered
   pill this card already uses for Add, which is what makes it read as
   something to press rather than more of the sentence. */
.ss-propdetail-value {
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
}
.ss-profile-edit {
  flex: none;
  padding: 0.3rem 0.75rem;
  min-height: 32px;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 999px;
  background: #fff;
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  letter-spacing: 0.02em;
  color: var(--tf-ocean, #1a9fc4);
  text-decoration: none;
}

/* Change is a pencil now, so the pill becomes a square target rather than a
   wide one wrapped around a 15px icon. */
.ss-profile-edit {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  min-height: 34px;
  padding: 0;
  border-radius: 10px;
}

/* Narration mute, on the step card. Quiet by design: it is an escape from
   something the app is doing, not one of the step's actions, so it sits
   below them at the weight of the Back link. */
/* In the Visit card's header now, beside the status. Same grouping trick as
   the chemistry mic: the header is space-between, so margin-right:auto keeps
   the title and this control together on the left and pushes the status and
   the chevron, which are a pair, to the right. */
.ss-gv-mute-slot { display: inline-flex; align-items: center; margin-right: auto; }
.ss-gv-mute-slot:empty { margin: 0; }
.gv-mute-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  margin: 0 0 0 0.55rem;
  padding: 0 0.6rem;
  min-height: 32px;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 999px;
  background: #fff;
  font: inherit;
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  color: var(--crew-label, #5b7183);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.gv-mute-btn[aria-pressed="true"] { color: var(--tf-ocean, #1a9fc4); }
.gv-mute-btn:active { opacity: 0.6; }

/* ══════════════════════════════════════════════════════════════════════════
   THE TEXT EDITOR SHEET
   One sheet, used by every text field in the app, so everything here lands
   everywhere at once. Meesha's list, in order.

   IT APPEARS INSTEAD OF SLIDING. `hidden` is a binary, so the card was
   simply there on the next frame. A transform transition on the card and an
   opacity one on the scrim give it the prototype's rise. Driven off
   :not([hidden]) rather than a class, so nothing in the JS has to change and
   every other caller of this sheet inherits it. Honoured for people who ask
   for less motion.

   THE ORANGE BOX. The focus ring came from the browser's default accent, not
   from us -- nothing set a focus colour on this textarea at all, so Chrome
   drew its own. Cyan, explicitly.

   TAP TO RECORD WAS BELOW THE FOLD. The textarea was rows="10" in a card
   that caps its own height, so the mic sat past the bottom edge and needed a
   scroll to reach. The card is a flex column now: the textarea takes the
   space that is left and never more, so the mic and Cancel are always on
   screen no matter how tall the phone or how open the keyboard.
   ══════════════════════════════════════════════════════════════════════════ */
/* An ANIMATION, not a transition. [hidden] is display:none, and a transition
   cannot run from display:none -- the element simply exists at its final
   position on the next frame, which is exactly the "it just appears" Meesha
   described. Measured: the card's transform was already the identity matrix
   120ms after the sheet opened. An animation fires on reveal instead. */
@keyframes tfp-sheet-rise {
  from { transform: translateY(100%); }
  to   { transform: translateY(0); }
}
@keyframes tfp-sheet-fade {
  from { opacity: 0; }
  to   { opacity: 1; }
}
/* EVERY sheet rises, not just the editor. Meesha, on the customer kebab:
   "that is just like jumping out instead of sliding nicely from the bottom."
   She had already asked for this to be universal -- "this sheet should be
   universal across all of them, so one change should change it everywhere"
   -- and scoping the animation to #text-edit-sheet was me not taking that
   literally enough. On .contact-sheet, so every sheet in the app inherits
   it and the next one written does too. */
.contact-sheet:not([hidden]) > .contact-sheet-card {
  animation: tfp-sheet-rise 0.26s cubic-bezier(0.22, 0.7, 0.3, 1) both;
}
.contact-sheet:not([hidden]) > .contact-sheet-bg {
  animation: tfp-sheet-fade 0.26s ease both;
}
@media (prefers-reduced-motion: reduce) {
  .contact-sheet:not([hidden]) > .contact-sheet-card,
  .contact-sheet:not([hidden]) > .contact-sheet-bg { animation: none; }
}
#text-edit-sheet .contact-sheet-card {
  max-height: 88vh;
  gap: 0.5rem;
}

.text-edit-grab {
  width: 38px;
  height: 4px;
  border-radius: 2px;
  background: var(--crew-rule, #dde7ef);
  margin: -0.35rem auto 0.35rem;
  flex: none;
}
.text-edit-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  padding-bottom: 0.6rem;
  border-bottom: 1px solid var(--crew-rule, #dde7ef);
  flex: none;
}
#text-edit-title {
  margin: 0;
  text-align: left;
  font-size: var(--fs-lg, 1.25rem);
  font-weight: 750;
  color: var(--crew-value, #17475f);
}
.text-edit-save {
  flex: none;
  min-height: 40px;
  padding: 0 1.1rem;
  border-radius: 12px;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
}

/* flex: 1 1 auto WITH min-height: 0 -- without the min-height a flex item
   refuses to shrink below its content, which is how a rows="10" textarea
   pushed the mic off the bottom of a capped card in the first place. */
.text-edit-input {
  flex: 1 1 auto;
  min-height: 120px;
  height: auto;
  resize: none;
  font: inherit;
  font-size: var(--fs-base, 0.9375rem);
  line-height: 1.5;
  padding: 0.8rem 0.9rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  color: var(--crew-value, #17475f);
  background: #fff;
}
.text-edit-input:focus,
.text-edit-input:focus-visible,
#text-edit-sheet:not([hidden]) .text-edit-input {
  outline: none;
  border-color: var(--tf-ocean, #1a9fc4);
  box-shadow: 0 0 0 3px rgba(26, 159, 196, 0.16);
}

.text-edit-microw {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex: none;
  padding-top: 0.15rem;
}
.text-edit-miclabel {
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 600;
  color: var(--crew-label, #5b7183);
}
.text-edit-cancel {
  flex: none;
  min-height: 44px;
  border: 0;
  background: transparent;
  color: var(--crew-label, #5b7183);
  font-weight: 600;
  font-size: var(--fs-sm, 0.8125rem);
}

/* ══════════════════════════════════════════════════════════════════════════
   ROW ACTIONS: one control, two registers
   Meesha: "we have several different kinds on one page and sheet ... I want
   to make sure we look at the pattern of when we use which kind of button or
   chip or control."

   One stop screen was showing four treatments for the same job -- the action
   at the right-hand end of a list row:

     Add             outline pill, cyan       (Property & equipment)
     Change          outline pill, cyan       (Pool details)
     Ask / Mark done grey filled, square-ish  (Departure checklist)
     Placed          GOLD filled              (yard sign)
     Declined        bare white outline       (yard sign)

   Gold is the finishing colour and the FAB already owns the only gold on
   this screen, so a yard sign was reading as the most important thing on it.
   Grey filled is a register we use nowhere else. And a bare white outline
   next to a filled button is the shape the design system explicitly warns
   against, because --border sits at 10% opacity.

   So: ONE row action, the outline pill, plus ONE recommended variant, filled
   cyan, for the case where a row genuinely has a preferred answer (the yard
   sign's camera -- taking the photo is what builds the habit, DECISIONS
   Entry 23). Two registers on rows instead of four, and the recommended one
   is the app's own signature colour rather than the commit gold.
   ══════════════════════════════════════════════════════════════════════════ */
.cl-action-btn,
.cl-actions .btn-secondary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 36px;
  padding: 0 0.9rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 999px;
  background: #fff;
  color: var(--tf-ocean, #1a9fc4);
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  white-space: nowrap;
}
.cl-action-btn:active,
.cl-actions .btn-secondary:active { opacity: 0.7; }

/* The recommended answer on a row. Filled, in the signature cyan, never gold. */
.cl-cam-primary,
.cl-actions .btn-primary {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.35rem;
  min-height: 36px;
  padding: 0 0.9rem;
  border: 1.5px solid var(--tf-ocean, #1a9fc4);
  border-radius: 999px;
  background: var(--tf-ocean, #1a9fc4);
  color: #fff;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  white-space: nowrap;
}

/* And the same pill for the two that were already close, so all of them are
   literally the same declarations rather than three lookalikes. */
.ss-section-prop .ss-det-editlink {
  min-height: 36px;
  padding: 0 0.9rem;
  font-size: var(--fs-sm, 0.8125rem);
}

/* ── The avatar ──────────────────────────────────────────────────────────
   Meesha: "it's a white G and it's really tiny ... that measly little G."
   It was --fs-xs (13px) in white on the gold, which is about 2:1 contrast
   and reads as a smudge rather than a letter. The prototype has a large
   dark initial on the gold. Deep cerulean on --cta-bg is roughly 5:1, so
   this is a legibility fix as much as a styling one. */
.avatar-initials {
  font-size: var(--fs-lg, 1.25rem);
  font-weight: 750;
  color: var(--tf-cerulean, #0b4f6c);
  letter-spacing: 0;
}
.avatar-btn { box-shadow: inset 0 0 0 1.5px rgba(11, 79, 108, 0.12); }

/* The action's own label inside a sheet that is titled for the customer. */
.ss-sheet-label {
  margin: 0 0 0.2rem;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 750;
  color: var(--crew-value, #17475f);
}

/* ══════════════════════════════════════════════════════════════════════════
   THE HEADER BADGES
   Meesha: "too small for the numbers ... right now it's cutting off the
   numbers." The message badge is a 15px circle carrying 13px digits inside
   a 1.5px border on each side, which leaves about 12px of usable height for
   a 13px glyph -- so "9+" clips. Both dimensions move: the pill grows and
   the digits shrink, because a badge that is big enough for "9+" at 13px
   would be a disc bigger than the icon it sits on.
   ══════════════════════════════════════════════════════════════════════════ */
.header-msg-badge {
  top: 2px;
  right: 2px;
  min-width: 19px;
  height: 19px;
  padding: 0 5px;
  font-size: 11px;
  font-weight: 800;
  line-height: 16px;      /* the border box is 19px: 16 + 1.5 top + 1.5 bottom */
  letter-spacing: 0;
}

/* .ss-record-line lived here. Its markup is gone: the "Customer record"
   link went to the same screen the NAME now links to, so the card was
   spending two rows and two tap targets on one destination. The kebab it
   carried moved up beside the name (.ss-name-row). */

/* The confirm sheet's own two buttons. */
.confirm-sheet-card { gap: 0.55rem; }
.confirm-sheet-go {
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  border-radius: 12px;
  font-weight: 700;
  background: var(--tf-ocean, #1a9fc4);
  border: 1.5px solid var(--tf-ocean, #1a9fc4);
  color: #fff;
}
/* Destructive gets the danger colour, so "Remove it" never looks like the
   safe default. */
.confirm-sheet-go.is-danger {
  background: var(--status-danger, #b3261e);
  border-color: var(--status-danger, #b3261e);
  color: #fff;
}

/* ── Narration: the speaker alone ────────────────────────────────────────
   Meesha, after seeing it in place: "we can get rid of the label narration
   on and just have the little speaker." The words stay for a screen reader
   and as the tooltip -- the icon is the state, and a slashed speaker is
   universally understood. */
.gv-mute-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.gv-mute-btn {
  width: 34px;
  padding: 0;
  gap: 0;
}

/* ── Complete visit: a green that belongs to the palette ─────────────────
   --status-success is #16a34a, a saturated signal green built for a status
   dot, not for a 48px slab across the foot of the screen. Meesha: "that
   green cannot possibly be in the color palette ... find a much better
   green that is not so annoying." --status-success-text (#157a3a) is the
   palette's own darker member of the same family, chosen for legibility
   rather than alarm, and it holds far more than 4.5:1 against white. Same
   hue, same token set, a great deal calmer at this size. */
.ss-complete-btn.active {
  background: var(--status-success-text, #157a3a);
  color: #fff;
}

/* ── The FAB sits ABOVE the action bar, as in the prototype ──────────────
   First attempt reserved room for it INSIDE the bar, which stopped the
   overlap and cost the Complete visit button 86px of width. The prototype
   floats the FAB clear of the bar entirely, which is both less intrusive
   and gives the bar its full width back.
   Scoped to the stop screen with :has(), because that is the only screen
   with an action bar -- lifting it everywhere would leave an odd gap on
   Today. The bar measures 81px, so 5.5rem clears it with air to spare. */
/* 7.5rem, measured rather than guessed: 5.5rem left the disc 17px into the
   bar. The bar occupies 105px of the viewport bottom once scrolled to the
   end, so 120px clears it with air.

   THE MENU IS DERIVED FROM THE FAB, not given its own number. Both used to
   carry hand-computed constants (fab 1rem, menu 5.25rem = 1 + 3.75 + 0.5),
   correct as a pair and with nothing tying them together -- so moving the
   FAB up on this screen and not the menu put "Flag an issue" and "Charge or
   quote" underneath the disc. Measured before the fix: the menu's bottom
   edge sat 36px BELOW the FAB's. One variable now, one place to change. */
:root {
  --crew-fab-bottom: 1rem;
  --crew-fab-size: 3.75rem;   /* matches .log-lead-fab width/height */
  --crew-fab-gap: 0.625rem;
}
body:has(#screen-stop:not([hidden])) { --crew-fab-bottom: 7.5rem; }
.log-lead-fab {
  bottom: calc(var(--crew-fab-bottom) + env(safe-area-inset-bottom));
}
.fab-menu {
  bottom: calc(var(--crew-fab-bottom) + var(--crew-fab-size) + var(--crew-fab-gap)
               + env(safe-area-inset-bottom));
}

/* The urgency helper explains what PICKING one of these does, so it belongs
   under the question rather than stranded below the last option, where it
   reads as a footnote to "Urgent". */
.ss-issue-form .ss-field-label + .ss-internal-hint + .ss-sev-list { margin-top: 0.6rem; }

/* ── The equipment question in the flag form ─────────────────────────────
   "Which equipment?" sat hard against the checkbox decision above it, and
   its helper line floated in the middle of a gap. Group the label with its
   own helper, and put the air ABOVE the question instead of inside it. */
.ss-issue-form .ss-field-label { margin-top: 1.35rem; }
.ss-issue-form .ss-field-label + .ss-internal-hint { margin-top: 0.15rem; margin-bottom: 0.7rem; }

/* ── Complete visit is as wide as everything else ────────────────────────
   Measured: it rendered 255px while Navigate and Calculate dose, the two
   comparable buttons sitting inside cards, are both 272px. The 42px
   overflow kebab beside it was the entire difference. The bar is a column
   now, so Complete gets the full content width and the exits sit under it
   as a quiet line rather than competing with it. */
/* Complete visit and its kebab, side by side, spanning the CARD EDGES.
   This carried a 1rem inset so the pair matched Navigate and Calculate dose
   -- buttons that live INSIDE a card, and so are inset by that card's own
   padding. The reasoning was sound and the neighbour changed underneath it:
   what now sits directly above the bar is a stack of collapsed section
   CARDS, so the pair read as narrower than everything above it (measured:
   group 50..325 against cards 34..341). The action bar is a PEER of those
   cards, not content inside one, so it lines up with their edges.
   (Meesha, 2026-09-18.) */
.ss-action-bar-inner {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.6rem;
  /* Neither padding NOR margin here: .ss-action-bar already carries the
     same 0.9rem horizontal inset the cards use, so anything added at this
     level double-counts it. Measured that the hard way -- a 0.9rem margin
     put the pair at 49 against the cards' 34. */
  padding: 0;
}
.ss-complete-btn { flex: 1 1 auto; min-width: 0; }
.ss-overflow-btn {
  flex: 0 0 auto;
  width: 46px;
  min-height: 48px;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  color: var(--crew-label, #5b7183);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-overflow-btn:active { opacity: 0.6; }

/* The exits, as a sheet. Full-width targets instead of a 42px dot. */
.ss-overflow-sheet .ss-overflow-item {
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  color: var(--crew-value, #17475f);
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
}
.ss-overflow-sheet .ss-overflow-danger {
  color: var(--status-danger, #b3261e);
  border-color: var(--status-danger-border, #e7b4b0);
}

/* ── The equipment picker: four, then an expander ────────────────────────
   Single column, one line each. The old two-column grid wrapped long names
   onto two lines and left a ragged last row, which is what made a list of
   six units look like a wall. */
.ss-eq-pick {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
  margin-top: 0.15rem;
}
.ss-eq-pick-btn {
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  padding: 0.6rem 0.85rem;
  text-align: left;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 650;
  color: var(--crew-value, #17475f);
  /* One line. A wrapped equipment name is how the grid got ragged. */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-eq-pick-btn.active {
  border-color: var(--tf-ocean, #1a9fc4);
  background: var(--tf-ink-050, #f2fafc);
  color: var(--tf-ocean-800, #0b4f6c);
}
/* "Something else" is the escape, not another unit. */
.ss-eq-pick-other {
  font-weight: 600;
  color: var(--crew-label, #5b7183);
}
.ss-eq-pick-more {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
.ss-eq-more-btn {
  align-self: flex-start;
  margin-top: 0.5rem;
  padding: 0.3rem 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--tf-ocean, #1a9fc4);
  text-decoration: underline;
  cursor: pointer;
}

/* ── Add equipment sheet ─────────────────────────────────────────────────
   Four type chips plus an expander, matching the prototype: the first four
   cover nearly every add, and eight at once wrap into a block on a phone. */
.add-eq-card { max-height: 90vh; overflow-y: auto; gap: 0.35rem; }
.add-eq-scan {
  width: 100%;
  min-height: 48px;
  margin-top: 0.3rem;
  border-radius: 12px;
  font-weight: 700;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
}
.add-eq-scan-hint { text-align: center; margin: 0.35rem 0 0; }
.add-eq-divider {
  height: 1px;
  background: var(--crew-rule, #dde7ef);
  margin: 0.9rem 0 0.2rem;
}
.add-eq-types {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.25rem 0 0;
}
.add-eq-more-types { display: contents; }
.add-eq-more-types[hidden] { display: none; }
.add-eq-type {
  flex: 1 1 22%;
  min-width: 78px;
  min-height: 42px;
  padding: 0 0.5rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
  white-space: nowrap;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.add-eq-type.is-on {
  background: var(--tf-ocean, #1a9fc4);
  border-color: var(--tf-ocean, #1a9fc4);
  color: #fff;
}
.add-eq-more-btn {
  align-self: flex-start;
  margin: 0.15rem 0 0.4rem;
  padding: 0.3rem 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--tf-ocean, #1a9fc4);
  text-decoration: underline;
  cursor: pointer;
}
/* required / optional ride with the label rather than becoming their own
   line, so the form stays four rows instead of eight. */
.add-eq-req, .add-eq-opt {
  font-weight: 600;
  color: var(--crew-label, #5b7183);
}
/* Non-breaking space after the middot: a plain trailing space collapses at
   the end of a ::before, so it rendered "Brand \00b7required". */
.add-eq-req::before, .add-eq-opt::before { content: "\00b7\00a0"; }
.add-eq-card .ss-text-input { width: 100%; margin-bottom: 0.2rem; }
.add-eq-actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 0.9rem;
}
.add-eq-actions .btn {
  flex: 1;
  min-height: var(--crew-row-min, 44px);
  border-radius: 12px;
  font-weight: 700;
}
.add-eq-another {
  border: 1.5px solid var(--crew-rule, #dde7ef);
  background: #fff;
  color: var(--crew-value, #17475f);
}

/* The way in, at the foot of the equipment list. */
.ss-eq-add-btn {
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  margin-top: 0.6rem;
  border: 1.5px dashed var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--tf-ocean, #1a9fc4);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}

/* ── "How bad is it?" stops wrapping ─────────────────────────────────────
   Three options, each a bold label over a description, squeezed into a
   three-column row: every one wrapped to three lines and the row grew
   taller than the question. They are a pick-one list, so they get the same
   full-width row treatment as the severity list in Flag an issue, which
   reads cleanly at any label length. Wrapping is now impossible rather
   than merely unlikely, which is the difference that matters when a label
   changes later. */
#chem-shock .chem-shock-btns {
  display: flex;
  flex-direction: column;
  gap: 0.45rem;
}
#chem-shock .chem-shock-btn {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
  flex: none;
  width: 100%;
  min-width: 0;
  min-height: var(--crew-row-min, 44px);
  padding: 0.6rem 0.85rem;
  text-align: left;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
}
#chem-shock .chem-shock-btn .chem-shock-sub {
  display: inline;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 500;
  color: var(--crew-label, #5b7183);
  opacity: 1;
  white-space: nowrap;
}
#chem-shock .chem-shock-btn.selected {
  background: var(--tf-ocean, #1a9fc4);
  border-color: var(--tf-ocean, #1a9fc4);
  color: #fff;
}
#chem-shock .chem-shock-btn.selected .chem-shock-sub { color: rgba(255,255,255,0.85); }
#chem-shock .chem-shock-label {
  text-align: left;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 750;
  color: var(--crew-value, #17475f);
  margin: 0.9rem 0 0.1rem;
}

/* The status circle is a real target now, not decoration beside a button. */
.cl-status-btn {
  padding: 0;
  border: 2px solid var(--crew-rule, #dde7ef);
  border-radius: 50%;
  background: #fff;
  color: var(--status-success-text, #157a3a);
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  -webkit-tap-highlight-color: transparent;
}
.cl-status-btn:active { opacity: 0.6; }
.cl-row-done .cl-status-btn {
  border-color: var(--status-success-text, #157a3a);
  background: var(--status-success-text, #157a3a);
  color: #fff;
}

/* ── Gate and yard-sign chips: tap through every state ───────────────────
   Two tri-state segmented controls plus a four-button yard-sign row took
   seven rows of the panel to ask three questions. Three chips now.

   The state is a WORD, not a fill. A filled/empty pair cannot express "not
   checked" at all, and colour alone would leave him guessing which of the
   two absences he is looking at -- and "no gate" versus "nobody looked" is
   exactly the pair that must never be confused on a pool safety gate. */
.ss-gate-row { border-bottom: 0; padding-bottom: 0.15rem; min-height: 0; }
.ss-propdetail-hint {
  font-size: var(--fs-sm, 0.8125rem);
  color: var(--crew-label, #5b7183);
  text-align: right;
}
.ss-gate-chips {
  display: flex;
  gap: 0.5rem;
  padding-bottom: 0.7rem;
  border-bottom: 1px solid var(--crew-rule, #dde7ef);
}
.ss-gate-chip {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 2px;
  min-height: 52px;
  padding: 0.5rem 0.7rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  text-align: left;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-gate-chip-name {
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
}
.ss-gate-chip-val {
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 600;
  color: var(--crew-label, #5b7183);
  /* One line: a wrapped value makes the two chips different heights. */
  max-width: 100%;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ss-gate-chip[data-state="yes"] {
  border-color: var(--tf-ocean, #1a9fc4);
  background: var(--tf-ink-050, #f2fafc);
}
.ss-gate-chip[data-state="yes"] .ss-gate-chip-val { color: var(--tf-ocean, #1a9fc4); }
/* Unknown must not look like an answer. Dashed reads as an empty slot, the
   same language the photo placeholders use. */
.ss-gate-chip[data-state="unknown"] { border-style: dashed; }
.ss-gate-chip[data-state="unknown"] .ss-gate-chip-val { font-style: italic; }
.ss-gate-chip:active { opacity: 0.7; }
.ss-ys-chip { flex: 1 1 100%; }

/* Show more, under the photo grid. */
.cv-photo-more {
  display: block;
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  margin-top: 0.6rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--tf-ocean, #1a9fc4);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cv-photo-more:disabled { opacity: 0.55; cursor: default; }

/* ── The sheet header: close left, primary right ─────────────────────────
   Platform convention on both iOS and Android, so it needs no learning,
   and it puts the way out in thumb reach at the top rather than below a
   box that scrolls. Named generically (.sheet-x) so every other sheet can
   adopt the same header rather than each inventing a footer Cancel. */
.text-edit-head { gap: 0.6rem; }
.text-edit-head #text-edit-title { flex: 1 1 auto; min-width: 0; }
.sheet-x {
  flex: 0 0 auto;
  width: 38px;
  height: 38px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 0;
  border-radius: 10px;
  background: transparent;
  color: var(--crew-label, #5b7183);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.sheet-x:active { opacity: 0.6; }

/* Every sheet header is now close-left / title / primary-right. */
.eq-sheet-head { display: flex; align-items: center; gap: 0.6rem; }
.eq-sheet-head .contact-sheet-name { flex: 1 1 auto; min-width: 0; text-align: left; margin: 0; }
.text-edit-head .contact-sheet-name { flex: 1 1 auto; min-width: 0; text-align: left; margin: 0; }
/* The X is the LAST thing in the header, hard against the right edge, with
   the primary to its left. Meesha: "the X should be in the upper right
   always, that's the established pattern." Right for a PWA -- upper-right
   close is the web dialog convention, where upper-left is the iOS-native
   one I had followed. */
.sheet-x { margin-right: -0.35rem; }

/* ── Pool details rows: the row is the control ───────────────────────────
   The pencil is gone. A pencil beside every value is a settings-screen
   idiom from a decade ago, and it put a second hit target on a row whose
   whole job is to be tapped. The value carries the affordance now -- the
   interactive colour plus a chevron -- and the button IS the row, so the
   target is the full width instead of 34px. */
.ss-propdetail-tap {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  column-gap: 0.75rem;
  width: 100%;
  text-align: left;
  border: 0;
  border-bottom: 1px solid var(--crew-rule, #dde7ef);
  background: transparent;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-propdetail-tap:active { background: var(--tf-ink-050, #f2fafc); }
.ss-propdetail-tap .ss-propdetail-value {
  text-align: right;
  color: var(--tf-ocean, #1a9fc4);
}
.ss-propdetail-tap .ss-propdetail-value.is-absent { color: var(--crew-label, #5b7183); }
.ss-propdetail-chev { flex: none; color: var(--crew-label, #5b7183); }

/* The escape from an open editor is a text link, not the pencil's pill --
   it inherited that class and rendered as a stray round button floating
   under the options. */
.ss-profile-edit-cancel {
  align-self: flex-start;
  margin-top: 0.35rem;
  padding: 0.25rem 0;
  border: 0;
  background: transparent;
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  color: var(--crew-label, #5b7183);
  cursor: pointer;
}

/* Pool size: a value that looks like the field it is, edited in place. */
.ss-poolsize-row { align-items: center; }
.ss-poolsize-pill {
  justify-self: end;
  min-height: 40px;
  padding: 0 0.9rem;
  border: 1.5px solid var(--crew-rule, #dde7ef);
  border-radius: 12px;
  background: #fff;
  font: inherit;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-poolsize-pill:active { opacity: 0.7; }
.ss-poolsize-edit { display: flex; align-items: center; gap: 0.4rem; justify-content: flex-end; }
.ss-poolsize-input {
  width: 6.5rem;
  min-height: 40px;
  padding: 0 0.6rem;
  border: 1.5px solid var(--tf-ocean, #1a9fc4);
  border-radius: 12px;
  font: inherit;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 700;
  color: var(--crew-value, #17475f);
  text-align: right;
}
.ss-poolsize-input:focus { outline: none; box-shadow: 0 0 0 3px rgba(26,159,196,0.16); }
.ss-poolsize-unit { font-size: var(--fs-sm, 0.8125rem); color: var(--crew-label, #5b7183); }

/* Open row: the header stays tappable (that is the close), options below. */
.ss-propdetail-open { border-bottom: 1px solid var(--crew-rule, #dde7ef); }
.ss-propdetail-open .ss-propdetail-row { border-bottom: 0; }
.ss-propdetail-tap.is-open .ss-propdetail-chev { transform: rotate(90deg); }
.ss-propdetail-chev { transition: transform 0.18s ease; }
.ss-propdetail-options { padding: 0 0 0.75rem; }

/* ── Log a contact sheet (2026-09-18) ────────────────────────────────────
   Was an inline panel hanging off the customer card, which pushed the whole
   stop down and put its own Cancel below the fold. Now the shared sheet
   pattern: title left, X upper right, one primary at the bottom. */
.log-contact-card { padding-bottom: 1.1rem; }
.log-contact-lede {
  margin: 0 0 0.9rem;
}
.log-contact-note { min-height: 7rem; resize: vertical; }
/* A quiet text control, not a button: it reveals a field that 0 of 1,942
   logged contacts have ever used, so it must not compete with Log it. */
.log-contact-linktoggle {
  display: block;
  text-align: left;
  margin-top: 0.45rem;
  padding: 0.3rem 0;
  background: none;
  border: 0;
  font: inherit;
  font-size: var(--fs-sm);
  font-weight: 600;
  color: var(--tf-ocean, #1b6b8f);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.log-contact-linktoggle:active { opacity: 0.6; }
.log-contact-linkinput { width: 100%; margin-top: 0.5rem; }
.log-contact-cad { margin-top: 1.1rem; }
.log-contact-cad .ss-internal-hint { margin-top: 0.15rem; margin-bottom: 0.55rem; }
.log-contact-cbrow {
  display: flex;
  align-items: flex-start;
  gap: 0.55rem;
  padding: 0.5rem 0;
  font-size: var(--fs-sm);
  color: var(--app-text, #16323f);
  line-height: 1.35;
  cursor: pointer;
}
.log-contact-cbrow + .log-contact-cbrow {
  border-top: 1px solid var(--app-border-soft);
}
.log-contact-cbrow input[type="checkbox"] {
  flex: none;
  width: 1.15rem;
  height: 1.15rem;
  margin: 0.05rem 0 0;
  accent-color: var(--tf-ocean, #1b6b8f);
}
.log-contact-status {
  margin: 0.55rem 0 0;
  font-size: var(--fs-xs);
  color: var(--app-text-muted, #5c7480);
  min-height: 1em;
}
.log-contact-actions { margin-top: 0.5rem; }

/* ── Sold for today (sql/758) ────────────────────────────────────────────
   Jobs the customer bought that ride on this visit. Deliberately NOT a
   .cl-row: a checklist step is part of the service and a job is a separate
   thing they paid for, and folding them into one list is how the job stops
   looking like money. It borrows the checklist's ROUND tick, because that is
   the mark-done control this app already has and a second shape for the same
   gesture is drift. */
.ss-sold-body { padding: 0.15rem 1rem 0.9rem; }
.ss-sold-row {
  display: flex;
  align-items: center;
  gap: 0.7rem;
  min-height: var(--crew-row-min, 44px);
  padding: 0.55rem 0;
  border-bottom: 1px solid var(--crew-rule, #e3eef3);
}
.ss-sold-row:last-of-type { border-bottom: 0; }
.ss-sold-tick {
  flex: none;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  border: 2px solid var(--tf-saffron, #c9a96e);
  background: #fff;
  color: #fff;
  display: grid;
  place-items: center;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ss-sold-tick:active { opacity: 0.6; }
.ss-sold-row.is-done .ss-sold-tick {
  background: var(--crew-ok, #1e7a52);
  border-color: var(--crew-ok, #1e7a52);
}
.ss-sold-row.is-nottoday .ss-sold-tick {
  border-color: var(--crew-rule, #e3eef3);
  border-style: dashed;
}
.ss-sold-label { flex: 1 1 auto; min-width: 0; }
.ss-sold-title {
  display: block;
  font-size: var(--fs-base, 0.9375rem);
  font-weight: 650;
  line-height: 1.3;
}
.ss-sold-row.is-nottoday .ss-sold-title { color: var(--crew-label, #5b7183); }
.ss-sold-state,
.ss-sold-owed {
  display: block;
  font-size: var(--fs-xs, 0.8125rem);
  color: var(--crew-label, #5b7183);
  margin-top: 0.1rem;
}
.ss-sold-owed { font-weight: 650; }
/* The quiet register of the row-action pattern: it is the alternative answer,
   not a destructive one, so it never takes gold. */
.ss-sold-skip {
  flex: none;
  background: none;
  border: 0;
  padding: 0.4rem 0 0.4rem 0.5rem;
  font: inherit;
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 700;
  color: var(--crew-label, #5b7183);
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.ss-sold-skip:active { opacity: 0.6; }
.ss-sold-warn {
  margin: 0.75rem 0 0;
  padding: 0.65rem 0.75rem;
  border-radius: 10px;
  background: var(--crew-warn-bg, #fdf1dc);
  border: 1px solid var(--crew-warn-line, #ecd9ae);
  font-size: var(--fs-xs, 0.8125rem);
  line-height: 1.5;
  color: var(--crew-warn-text, #6b4e12);
}
.ss-sold-warn b { font-weight: 800; }

/* ── Today: a job on this visit is a LINK now, not a control ─────────────
   Dashed, so it reads as a pointer to somewhere rather than a thing to press
   here. The answer lives on the stop screen (sql/758). */
.stop-openjob-row--ro {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  width: 100%;
  min-height: var(--crew-row-min, 44px);
  padding: 0.6rem 0.7rem;
  border: 1px dashed var(--crew-rule, #e3eef3);
  border-radius: 11px;
  background: var(--crew-surface-2, #f7fafb);
  color: inherit;
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.stop-openjob-row--ro:active { opacity: 0.65; }
.stop-openjob-todo {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  background: var(--crew-warn-bg, #fdf1dc);
  color: var(--crew-warn-text, #6b4e12);
  font-size: 0.625rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  vertical-align: 0.1em;
}
.stop-openjob-go {
  flex: none;
  color: var(--crew-chevron, #a8c8d8);
  font-size: 1.2rem;
  line-height: 1;
}

/* ── The approve sheet's "what they will see" folds ──────────────────────
   Same disclosure treatment as the report-note section beside them, so the
   sheet reads as one stack rather than a new pattern bolted on. */
.summary-seen-list {
  list-style: none;
  margin: 0.55rem 0 0;
  padding: 0;
}
.summary-seen-list li {
  position: relative;
  padding: 0.3rem 0 0.3rem 0.95rem;
  font-size: var(--fs-sm, 0.875rem);
  line-height: 1.45;
}
.summary-seen-list li::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0.72em;
  width: 6px;
  height: 6px;
  border-radius: 50%;
  background: var(--tf-saffron, #c9a96e);
}
.summary-seen-list li.is-job { font-weight: 700; }
.summary-seen-list li.is-job::before { background: var(--tf-ocean, #1a9fc4); }
.summary-seen-sold {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.05rem 0.35rem;
  border-radius: 4px;
  background: var(--crew-tint, #e4f7fc);
  color: var(--tf-ocean-800, #0e6f8f);
  font-size: 0.5875rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  vertical-align: 0.15em;
}
.summary-seen-empty {
  font-size: var(--fs-xs, 0.8125rem);
  color: var(--crew-label, #5b7183);
  padding-left: 0;
}
.summary-seen-list li.summary-seen-empty::before { content: none; }
.summary-seen-rd {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 0.75rem;
  padding: 0.4rem 0;
  font-size: var(--fs-sm, 0.875rem);
  border-bottom: 1px solid var(--crew-rule, #e3eef3);
}
.summary-seen-rd:last-child { border-bottom: 0; }
.summary-seen-rdv { font-weight: 700; white-space: nowrap; }
.summary-seen-readings { margin-top: 0.4rem; }

/* ── Also on this visit: the colour now means the WORK, not the money ────
   The chip was hard-coded green because it only ever said PAID, so a job
   nobody had done yet sat on a finished stop looking finished. Green is
   reserved for done; anything still open is amber, which is the same
   register the open-job warning on the stop screen uses. Overrides the
   base in styles.css by SOURCE ORDER at equal specificity -- this file
   loads second and carries no !important. */
.stop-addon-chip {
  background: var(--crew-warn-bg, #fdf1dc);
  border-color: var(--crew-warn-line, #ecd9ae);
  text-decoration: none;
  color: inherit;
  -webkit-tap-highlight-color: transparent;
}
.stop-addon-chip.is-done {
  background: var(--crew-ok-bg, #e9f5ee);
  border-color: var(--crew-ok-line, #bcdcca);
}
.stop-addon-chip--link:active { opacity: 0.65; }
.stop-addon-chip-state {
  display: inline-block;
  margin-left: 0.4rem;
  padding: 0.1rem 0.4rem;
  border-radius: 999px;
  font-size: 0.625rem;
  font-weight: 800;
  letter-spacing: 0.05em;
  vertical-align: 0.1em;
  color: var(--crew-warn-text, #6b4e12);
  background: #fff;
  border: 1px solid var(--crew-warn-line, #ecd9ae);
}
.stop-addon-chip.is-done .stop-addon-chip-state {
  color: var(--crew-ok, #1e7a52);
  border-color: var(--crew-ok-line, #bcdcca);
}
/* The amount is a fact about the invoice and must not take the work's
   colour, or it starts reading as a status too. */
.stop-addon-chip-amt { color: var(--crew-label, #5b7183); }
.stop-addon-chip.is-done .stop-addon-chip-amt { color: var(--crew-ok, #1e7a52); }
.stop-addon-heading {
  color: var(--crew-label, #5b7183);
}

/* ── The report the customer got ────────────────────────────────────────
   A link out to a different document on a different origin, so it reads as
   a link rather than as another action on this page. */
.ss-report-link-wrap { margin: 0 0.9rem 0.75rem; }
.ss-report-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  min-height: var(--crew-row-min, 44px);
  padding: 0.7rem 0.9rem;
  border: 1px solid var(--crew-rule, #e3eef3);
  border-radius: 12px;
  background: var(--crew-surface-2, #f7fafb);
  font-size: var(--fs-sm, 0.875rem);
  font-weight: 700;
  color: var(--tf-ocean-800, #0e6f8f);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}
.ss-report-link:active { opacity: 0.65; }
.ss-report-link svg { flex: none; }

/* ── A visit-history row that opens that visit's report ─────────────────
   The chevron label is what makes it obvious the row goes somewhere; a row
   that merely looked tappable would be a guess. Rows with no report stay
   exactly as they were, which is how the absence stays honest. */
/* The service name IS the link. No trailing call to action: the row already
   names the visit, so the word you want to open is the word you tap. */
.cv-history-slug--link {
  color: var(--tf-ocean-800, #0e6f8f);
  font-weight: 700;
  text-decoration: underline;
  text-underline-offset: 2px;
  -webkit-tap-highlight-color: transparent;
}
.cv-history-slug--link:active { opacity: 0.65; }

/* A row revealed by cvCollapse(). The wrapper exists only to be hidden, so it
   must not introduce a box of its own. */
.cv-more-row { display: contents; }
/* The merged equipment card's inner heading. Quieter than the card title:
   the service log is a different KIND of thing from the equipment, but not a
   different card. */
.cv-subsection-title {
  margin: 1rem 0 0.35rem;
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 750;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--crew-label, #5b7183);
}

/* ── The name IS the link ────────────────────────────────────────────────
   Dotted underline, the same affordance PoolBooks uses on .cr-name-link. It
   and a separate "Customer record" line went to one destination, so the
   card spent two rows and two targets on the same place. */
.ss-name-row {
  display: flex;
  align-items: center;
  gap: 0.15rem;
  min-width: 0;
}
.ss-hero-name-tap {
  border-bottom: 1px dotted var(--crew-rule, #a8c8d8);
  text-decoration: none;
}
.ss-hero-name-tap:active { opacity: 0.65; }
/* The kebab is a quiet sibling of the name, not a fourth contact icon: it
   corrects the RECORD, where the icons on the right reach the PERSON. */
.ss-name-row .stop-contact-icon {
  width: 30px;
  height: 30px;
  min-width: 0;
  flex: none;
  border: 0;
  background: transparent;
  color: var(--crew-label, #5b7183);
}
.ss-name-row .stop-contact-icon svg { width: 15px; height: 15px; }

/* ── A customer's name, as a link to their record ───────────────────────
   Dotted underline, the same affordance PoolBooks uses (.cr-name-link), so
   one gesture means one thing across both apps. It inherits type from
   whatever it sits in, which is what lets a 20px heading and a 13px row
   label share it. */
.cust-name-link {
  color: inherit;
  font: inherit;
  text-decoration: none;
  border-bottom: 1px dotted var(--crew-rule, #a8c8d8);
  -webkit-tap-highlight-color: transparent;
}
.cust-name-link:active { opacity: 0.65; }

/* ── Texting is off for this customer ───────────────────────────────────
   Dimmed and struck, not hidden: the control still has to be FINDABLE, or
   he hunts for a button that is not there and concludes the app is broken.
   It stays tappable on purpose -- the tap is what explains why. */
.stop-contact-icon.is-optedout {
  opacity: 0.45;
  position: relative;
}


/* ── Chemistry history table ────────────────────────────────────────────
   Numbers, not a chart: he is in sun deciding what to dose now, and a chart
   makes him interpret when he needs to read. */
.chem-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm, 0.875rem); }
.chem-table th {
  font-size: 0.625rem;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  font-weight: 800;
  color: var(--crew-label, #5b7183);
  text-align: right;
  padding: 0.4rem 0.35rem;
  border-bottom: 1px solid var(--crew-rule, #e3eef3);
  white-space: nowrap;
}
.chem-table td {
  padding: 0.5rem 0.35rem;
  text-align: right;
  border-bottom: 1px solid var(--crew-rule, #e3eef3);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.chem-table tr:last-child td { border-bottom: 0; }
/* The table is transposed: dates down the rows, metrics across. The date is
   the row label now, and it is the one left-aligned column. */
.chem-table th.chem-date,
.chem-table td.chem-date { text-align: left; font-weight: 700; padding-left: 0; white-space: nowrap; }
/* Trend is a summary ROW at the foot, one cell under each metric. It
   describes a metric across the window, so it sits on whichever axis the
   metrics are on -- the same reason it stopped being inline with a value. */
.chem-table td.chem-trend-c { text-align: right; }
.chem-trend-row td {
  border-bottom: 0;
  border-top: 1.5px solid var(--crew-rule, #e3eef3);
  padding-top: 0.45rem;
}
/* The visit count grows and the metric count does not, so the axis that can
   overflow is the one that scrolls. Inside its own box, never the page. */
.chem-scroll { overflow-x: auto; -webkit-overflow-scrolling: touch; }
.chem-trend { font-weight: 800; font-size: 0.9rem; line-height: 1; }
.chem-t-flat { color: var(--crew-label, #5b7183); }
.chem-t-up   { color: var(--crew-warn-text, #6b4e12); }
.chem-t-down { color: var(--tf-ocean-800, #0e6f8f); }
.chem-v { font-weight: 650; }
.chem-lo { color: var(--crew-danger, #9b2c2c); font-weight: 800; }
.chem-hi { color: var(--crew-warn-text, #6b4e12); font-weight: 800; }
.chem-na { color: var(--crew-rule, #b9c9d2); }
.cv-chem-foot {
  margin: 0.6rem 0 0;
  font-size: var(--fs-xs, 0.8125rem);
  line-height: 1.5;
  color: var(--crew-label, #5b7183);
}

/* The "Last visit" strip that lived here is gone. Every reading field
   already carries the last value as its placeholder, so it was a fourth way
   of saying the same thing. History lives on the Customer Record. */

/* ── Name sits closer to its address ────────────────────────────────────
   The two are one thought -- who this is and where they are -- and the row
   was carrying 0.5rem below it plus the height the 30px kebab adds to a
   ~30px name, so the address drifted away from the name it belongs to.
   (Meesha, 2026-09-19.) */
.ss-hero-row { margin-bottom: 0.1rem; }
/* The kebab no longer sets the row's height. align-self keeps it centred on
   the FIRST line of a name that wraps, instead of stretching the row. */
.ss-name-row { align-items: baseline; }
.ss-name-row .stop-contact-icon { align-self: center; height: 26px; width: 26px; }
.ss-address { margin-top: 0; }

/* ── The Reviewed chip opens what they wrote ────────────────────────────
   A button, not a label, because it goes somewhere. Keeps the chip's own
   look so nothing about the record's appearance changes. */
.cv-review-open {
  border: 0;
  font: inherit;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.cv-review-open:active { opacity: 0.65; }
.cv-review-item + .cv-review-item {
  margin-top: 0.9rem;
  padding-top: 0.9rem;
  border-top: 1px solid var(--crew-rule, #e3eef3);
}
.cv-review-meta {
  margin: 0 0 0.3rem;
  font-size: var(--fs-xs, 0.8125rem);
  color: var(--crew-label, #5b7183);
}
.cv-review-stars { color: var(--tf-saffron, #c9a96e); letter-spacing: 0.08em; margin-right: 0.35rem; }
.cv-review-text {
  margin: 0;
  font-size: var(--fs-base, 0.9375rem);
  line-height: 1.5;
}

/* ── The day carousel slides ────────────────────────────────────────────
   The row used to re-render in place on every tap, so the day you touched
   jumped into the centre slot. It now moves only when the selection would
   fall off the end, and when it does it travels one day-width rather than
   cutting. */
.carousel-strip { will-change: transform; }
/* The selected day grows into its state instead of appearing in it. Colour
   and weight are already carried by .selected in styles.css; this only adds
   the motion. */
.carousel-day {
  transition: background-color 140ms ease, transform 140ms ease;
}
.carousel-day.selected { transform: scale(1.04); }
.carousel-day:active { transform: scale(0.96); }

/* Someone who has asked for less motion gets none of it: the row still
   works, it simply cuts. */
/* The slide itself is already skipped in JS when this is set -- the row cuts
   to the new window instead. This only covers the day chips. No !important:
   nothing in this file uses one, and nothing later re-declares these. */
@media (prefers-reduced-motion: reduce) {
  .carousel-day { transition: none; }
  .carousel-day.selected, .carousel-day:active { transform: none; }
}

/* ── The checklist tick that is NOT a control ────────────────────────────
   Most checklist ticks are real buttons: the circle IS how you record the
   step. Two kinds are not -- the photo step and the review step, whose act
   is the camera or the Ask button beside them -- and they rendered as a
   pixel-identical ring. Meesha tapped one and nothing happened, which is
   the affordance lying about itself.

   They keep the same footprint so the labels stay aligned, and lose every
   cue that says "press me": no ring, no fill, just the mark. A tappable
   one still reads as a control because it is the only one with an outline.

   `span.cl-status` rather than `.cl-status:not(.cl-status-btn)` -- the
   element type is the actual invariant here (the renderer emits a <span>
   for exactly these), and it outranks the base .cl-status in styles.css
   without needing the class to exist. The done rule carries the extra
   class so it still beats `.cl-row-done .cl-status`. */
span.cl-status {
  border: 0;
  background: none;
}
.cl-row-done span.cl-status {
  background: none;
  color: var(--status-success-text, #157a3a);
}

/* ── Messages on the customer record ────────────────────────────────────
   Read-only. Every row is part of one tap target that opens the real
   thread, so nothing here looks individually pressable: the link at the
   foot is the only control. */
.cv-msg-row {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 0.5rem;
  align-items: baseline;
  padding: 0.5rem 0;
  border-bottom: 1px solid var(--crew-rule, #e3eef3);
  font-size: var(--fs-sm, 0.875rem);
  line-height: 1.45;
}
.cv-msg-row:last-of-type { border-bottom: 0; }
.cv-msg-who {
  font-weight: 750;
  font-size: var(--fs-xs, 0.75rem);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--crew-label, #5b7183);
  white-space: nowrap;
}
/* Inbound is the half worth finding at a glance: it is the customer asking
   for something. Weight, not colour -- colour here would compete with the
   out-of-range reds two cards up. */
.cv-msg-row.is-in .cv-msg-who { color: var(--tf-ocean-800, #0e6f8f); }
.cv-msg-row.is-in .cv-msg-body { font-weight: 600; }
.cv-msg-body {
  color: var(--crew-ink, #123448);
  /* A long text must not stretch the card; two lines is enough to know
     which message this is, and the thread has the rest. */
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}
.cv-msg-when {
  font-size: var(--fs-xs, 0.75rem);
  color: var(--crew-label, #5b7183);
  white-space: nowrap;
}
.cv-msg-open {
  display: inline-block;
  margin-top: 0.6rem;
  font-size: var(--fs-sm, 0.875rem);
  font-weight: 700;
  color: var(--tf-ocean-800, #0e6f8f);
  text-decoration: underline;
  text-underline-offset: 3px;
}

/* ── Equipment brand picker (sql/807) ────────────────────────────────────
   Replaced a free-text brand box on the two capture surfaces. Built from the
   shapes this app already uses rather than new ones: the chips borrow
   .add-eq-type's treatment and the expander IS .add-eq-more-btn, the same
   control the Type row two fields above already uses. */
.bp { display: block; }
.bp-chips { display: flex; flex-wrap: wrap; gap: 0.4rem; margin: 0.15rem 0 0.35rem; }
/* display:contents so revealed chips JOIN the existing flex row instead of
   starting a second one -- copied from .add-eq-more-types, same reason. */
.bp-rest { display: contents; }
.bp-rest[hidden] { display: none; }
.bp-chip {
  flex: 0 1 auto;
  min-height: var(--touch-min, 2.5rem);
  padding: 0.45rem 0.8rem;
  border: 1.5px solid var(--border);
  border-radius: 999px;
  background: #fff;
  color: var(--text-body);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.bp-chip.is-on { background: var(--tf-cerulean); border-color: var(--tf-cerulean); color: #fff; }
/* Dashed, so "Other" reads as the escape hatch rather than an eleventh brand. */
.bp-chip.bp-other { border-style: dashed; }
.bp-other-wrap { margin-top: 0.35rem; }
.bp-other-wrap .ss-text-input { width: 100%; }
.bp-sug { display: flex; flex-wrap: wrap; gap: 0.35rem; margin-top: 0.35rem; }
.bp-sug-btn {
  min-height: 2.2rem;
  padding: 0.3rem 0.65rem;
  border-radius: 999px;
  border: 1.5px solid var(--tf-cerulean);
  background: rgba(26, 159, 196, 0.08);
  color: var(--tf-cerulean);
  font-size: var(--fs-xs);
  font-weight: 700;
  cursor: pointer;
}

/* ── Standing-day picker in the charge sheet (2026-09-29) ──────────────────
   One row per weekday, two slots each. Built from the app's own tokens; the
   "on" state uses the same cyan the sheet uses for a chosen row elsewhere, so
   a picked slot reads as picked without a second visual language. */
.nc-day-grid { display: flex; flex-direction: column; gap: var(--space-2); margin: var(--space-3) 0; }
.nc-day-row { display: grid; grid-template-columns: 1fr auto auto; gap: var(--space-2); align-items: center; }
.nc-day-name { font-size: var(--fs-sm); color: var(--text-primary); font-weight: 600; }
.nc-day-slot {
  min-height: var(--crew-row-min);
  padding: 0 var(--space-3);
  border: 1px solid var(--app-border-soft, #e6edf1);
  border-radius: var(--radius-md);
  background: var(--tf-white, #fff);
  color: var(--text-muted);
  font-family: var(--font-sans);
  font-size: var(--fs-sm);
  font-weight: 600;
  cursor: pointer;
}
.nc-day-slot.on {
  background: var(--tf-ocean, #1a9fc4);
  border-color: var(--tf-ocean, #1a9fc4);
  color: var(--tf-white, #fff);
}

/* "Not right" on a briefing flag.
   A quiet text button, not a destructive-looking X: answering a model's guess
   is an ordinary thing to do, and a red control would read as though he were
   deleting a record. He is not -- the flag is regenerated daily and stored
   nowhere, and what persists is the task it raises for the office.
   Inline after the text so a long flag wraps around it rather than pushing it
   off the row, and padded out to a real tap target on a phone held one-handed
   at a pool. */
.ss-brief-flag-x {
  appearance: none;
  border: 0;
  background: none;
  margin-left: 0.4rem;
  padding: 0.35rem 0.25rem;
  font: inherit;
  font-size: var(--fs-xs, 0.8125rem);
  font-weight: 650;
  color: var(--crew-action, #1f7fa4);
  text-decoration: underline;
  text-underline-offset: 2px;
  cursor: pointer;
  white-space: nowrap;
  /* Own stacking context for the hit-area pseudo below. The parent .ss-brief-flag
     is already position:relative for its bullet, so without this the ::after
     would size against the whole flag row instead of the button. */
  position: relative;
}
/* The visible button is 29px tall, which is right for the row and wrong for a
   thumb. Grow the HIT AREA rather than the control: padding big enough to clear
   the 44px guidance would push every flag row apart and make a quiet advisory
   look like a toolbar. Measured at 45px after this. */
.ss-brief-flag-x::after {
  content: "";
  position: absolute;
  inset: -8px -6px;
}
.ss-brief-flag-x:disabled { opacity: 0.5; }

/* photos already filed for this guided step, so a photo taken here is visible where it was taken */
.gv-step-photos { display: flex; gap: 0.5rem; flex-wrap: wrap; justify-content: center; margin: 0.25rem 0 0.75rem; }
.gv-step-photo { width: 5.5rem; height: 5.5rem; object-fit: cover; border-radius: 12px; }
.gv-photo-pick { width: 100%; min-height: var(--crew-row-min); justify-content: center; margin-top: 0.5rem; }

/* the one header summary that is an invitation, not a count */
.ss-section-sum.ss-sum-action { color: var(--crew-accent, #0b4f6c); background: var(--crew-accent-soft, #c8eef8); border-radius: 999px; padding: 0.15rem 0.65rem; font-weight: 700; }

.ss-notes-textarea-row { min-width: 0; }
.ss-notes-textarea-row .ss-notes-ta { box-sizing: border-box; width: auto; flex: 1 1 0; min-width: 0; }
.ss-section-header { scroll-margin-top: 0.5rem; }

/* Photo step: one row of compact icon buttons instead of three full-width ones */
.gv-photo-actions { display: flex; gap: 0.75rem; justify-content: center; align-items: flex-start; margin: 0.5rem 0 0.25rem; }
.gv-icon-btn { display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 0.2rem; min-width: 4.25rem; min-height: 3.5rem; padding: 0.4rem 0.6rem; border-radius: 14px; border: 1px solid var(--crew-rule, #a8c8d8); background: transparent; color: var(--crew-accent, #0b4f6c); font: inherit; font-size: var(--fs-xs, 0.75rem); font-weight: 650; cursor: pointer; }
.gv-icon-btn svg { width: 22px; height: 22px; }
.gv-icon-btn.gv-icon-primary { background: var(--crew-accent, #0b4f6c); color: #fff; border-color: transparent; }
.gv-icon-btn.gv-icon-next { background: var(--crew-ok-soft, #e4f4e6); color: var(--crew-ok, #2d6a3a); border-color: transparent; }

/* With the keyboard up, pin every sheet to the VISIBLE area (top and height from visualViewport) instead of
   the layout viewport, so the header and its close button can never sit above the visible top. */
.kb-open .contact-sheet { top: var(--vvtop, 0px); bottom: auto; height: var(--vvh, 100%); }
.kb-open .contact-sheet .contact-sheet-card,
.kb-open #text-edit-sheet .contact-sheet-card { max-height: calc(var(--vvh, 100vh) - 0.5rem); }

/* ── Stop screen reorganization (2026-10-18) ─────────────────────────────
   Sparkle: the system did this for him (briefing line, drafted note, photo guess). */
.ss-spark { color: var(--tf-ocean-800, #0e6f8f); flex: none; margin-right: 0.4rem; vertical-align: -2px; }
.ss-brief-flag { display: flex; align-items: baseline; }
.ss-brief-flag::before { content: none; }
.ss-brief-flag span { flex: 1; }
#ss-ask-body { padding: 0 0.9rem 0.8rem; }
#ss-poolsize-host { padding: 0.55rem 0.9rem 0; }
#ss-ask-body { padding-top: 0.6rem; }

/* Full-size photo viewer. Swipe is scroll-snap; arrows only on a pointer device. */
.pv-host { position: fixed; inset: 0; z-index: 1000; background: rgba(6, 40, 64, .94); display: flex; align-items: center; justify-content: center; }
.pv-host[hidden] { display: none; }
.pv-car { display: flex; width: 100%; height: 100%; overflow-x: auto; scroll-snap-type: x mandatory; scrollbar-width: none; }
.pv-car::-webkit-scrollbar { display: none; }
.pv-slide { flex: 0 0 100%; scroll-snap-align: center; display: flex; align-items: center; justify-content: center; padding: calc(3.5rem + env(safe-area-inset-top)) 0.75rem 3rem; }
.pv-slide img { max-width: 100%; max-height: 100%; object-fit: contain; border-radius: 12px; }
.pv-close { position: absolute; top: calc(0.9rem + env(safe-area-inset-top)); right: 0.9rem; z-index: 2; width: 44px; height: 44px; border-radius: 50%; border: 0; background: #fff; color: var(--tf-ink-900, #062840); display: flex; align-items: center; justify-content: center; }
.pv-count { position: absolute; bottom: 1rem; left: 0; right: 0; text-align: center; color: #fff; font-size: var(--fs-sm, 0.875rem); }
.pv-host.pv-single .pv-count, .pv-host.pv-single .pv-nav { display: none; }
.pv-nav { display: none; position: absolute; top: 50%; transform: translateY(-50%); z-index: 2; width: 44px; height: 44px; border-radius: 50%; border: 0; background: #fff; color: var(--tf-ink-900, #062840); font-size: 24px; cursor: pointer; }
.pv-prev { left: 1rem; } .pv-next { right: 1rem; }
@media (hover: hover) and (pointer: fine) { .pv-nav { display: block; } .pv-host.pv-single .pv-nav { display: none; } }
.ss-flagged-list { margin: 0 0 0.9rem; display: flex; flex-direction: column; gap: 0.35rem; }
.ss-flagged-item { display: flex; align-items: baseline; gap: 0.45rem; font-size: var(--fs-sm, 0.875rem); color: var(--crew-value); }
.ss-flagged-sev { font-weight: 700; flex: none; }
.ss-flagged-desc { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── House + Pool property photos (At the property card, sql/909) ──────────
   Two squares in the same three-wide photo grid as every other photo row, so
   they read as the same control. Empty: the Add tile's solid cyan edge with a
   camera and the label. Filled: the photo, its name under it, and a small
   camera in the corner to retake. */
.ss-prop-row { padding-top: 0.7rem; }
.ss-prop-tile {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
}
.ss-prop-cam {
  aspect-ratio: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 0.3rem;
  border-radius: 11px;
  background: var(--tf-ink-050, #f2fafc);
  border: 1.5px solid var(--tf-ocean, #1a9fc4);
  color: var(--tf-ocean, #1a9fc4);
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  cursor: pointer;
}
.ss-prop-cam.is-filled {
  position: absolute;
  top: 6px;
  right: 6px;
  width: 44px;
  height: 44px;
  aspect-ratio: auto;
  border-radius: 50%;
  border-color: #fff;
  background: rgba(6, 40, 64, .55);
  color: #fff;
}
.ss-prop-name {
  font-size: var(--fs-xs, 0.75rem);
  font-weight: 700;
  color: var(--crew-label, #5b7183);
  text-align: center;
}
/* A guess the AI made from the Pool photo. Dashed, like every other guess on
   this screen, until Geoff taps it. */
.ss-shape-suggest {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  min-height: 44px;
  margin-top: 0.55rem;
  padding: 0.35rem 0.85rem;
  border: 1.5px dashed var(--tf-ocean, #1a9fc4);
  border-radius: 999px;
  background: #fff;
  color: var(--crew-value, #17475f);
  font: inherit;
  font-size: var(--fs-sm, 0.8125rem);
  font-weight: 700;
  cursor: pointer;
}
.ss-shape-suggest .ss-spark { color: var(--tf-ocean, #1a9fc4); }
