/* ============================================================================
   room.css — Room composite tool: photo input and four-point floor calibration.

   Requires assets/css/keca-tokens.css to be loaded FIRST.

   Deliberately does NOT depend on assets/css/keca-base.css, for the same
   reason plan.css does not: that file carries the viewer pages' element
   defaults and this stylesheet sets its own. room.html links tokens then this
   file, and nothing else.

   COMPATIBILITY LAYER
   ----------------------------------------------------------------------------
   Following the plan.css precedent exactly: every colour in this file goes
   through an --rm-* alias defined once in the block below, mapped from a shared
   token with a fallback. That fallback is the ONLY place a literal colour
   appears here. It exists so that a missing or half-written keca-tokens.css
   produces a plain readable page rather than black-on-black.

   Replace values in keca-tokens.css. Do not edit colours below this block, and
   do not add a literal colour anywhere else in this file.

   MONOCHROME DISCIPLINE
   ----------------------------------------------------------------------------
   There is no hue here beyond the single reserved green pair, and that pair is
   used only for the one PASS state (an accepted calibration). Every other state
   — low confidence, rejected, selected corner, active handle — is distinguished
   by WEIGHT, DASH PATTERN and TEXT, never by colour alone. That is the house
   style and it is a WCAG 1.4.1 requirement.

   THE OVERLAY MUST REGISTER EXACTLY WITH THE PHOTO
   ----------------------------------------------------------------------------
   docs/room-composite-design.md §4.7: if the photo is letterboxed inside its
   container and the overlay is not, every placement is wrong by the letterbox
   ratio — and it looks ALMOST right, which is worse than looking broken.

   This file removes the whole class of bug rather than compensating for it.
   The photograph is a <canvas> in normal flow at width:100%; height:auto, so
   the stage box IS the photo box, with no letterboxing anywhere. The overlay
   <svg> is absolutely positioned to inset:0 of that same box and carries
   preserveAspectRatio="none", so its viewBox maps onto the element box exactly,
   whatever the rounding. There is no object-fit anywhere on this page and there
   must not be.
   ========================================================================= */

:root {
  --rm-bg:            var(--keca-bg, #f7f7f5);
  --rm-surface:       var(--keca-surface, #ffffff);
  --rm-surface-alt:   var(--keca-surface-alt, #efefec);
  --rm-surface-sunk:  var(--keca-surface-sunk, #e0e0dc);

  --rm-ink:           var(--keca-text, #121211);
  --rm-ink-muted:     var(--keca-text-muted, #5a5a56);
  /* Fallback darkened to meet WCAG AA 4.5:1 if tokens.css fails to load —
     the same reasoning as plan.css's --pl-ink-faint. */
  --rm-ink-faint:     var(--keca-text-faint, #5c5c58);
  --rm-ink-invert:    var(--keca-text-invert, #ffffff);

  --rm-rule:          var(--keca-rule, #c9c9c4);
  --rm-rule-strong:   var(--keca-rule-strong, #121211);
  --rm-rule-faint:    var(--keca-rule-faint, #e0e0dc);

  --rm-focus:         var(--keca-focus, #121211);
  --rm-focus-halo:    var(--keca-focus-halo, #ffffff);
  --rm-alert:         var(--keca-alert, #121211);
  --rm-warn:          var(--keca-warn, #5a5a56);

  /* The one non-neutral pair, reserved for a verified/PASS state. */
  --rm-pass:          var(--green, #3E7C55);
  --rm-pass-bg:       var(--green-bg, #E7F0E4);

  --rm-font-display:  var(--keca-font-display, "Montserrat", Arial, sans-serif);
  --rm-font-body:     var(--keca-font-body, "Nunito Sans", "Segoe UI", Arial, sans-serif);
  --rm-font-mono:     var(--keca-font-mono, ui-monospace, Consolas, monospace);

  --rm-s1: var(--keca-s-1, 0.25rem);
  --rm-s2: var(--keca-s-2, 0.5rem);
  --rm-s3: var(--keca-s-3, 0.75rem);
  --rm-s4: var(--keca-s-4, 1rem);
  --rm-s5: var(--keca-s-5, 1.5rem);
  --rm-s6: var(--keca-s-6, 2rem);

  --rm-track-display: var(--keca-tracking-display, 0.14em);
  --rm-track-label:   var(--keca-tracking-label, 0.09em);
  --rm-tap:           var(--keca-tap-min, 44px);

  /* Overlay ink. The grid must read on a dark slate floor and on a white
     terrazzo one, so every line is drawn twice: a wide light halo beneath and
     a thin dark line on top. These two are the only overlay colours. */
  --rm-overlay-ink:   var(--keca-rule-strong, #121211);
  --rm-overlay-halo:  var(--keca-surface, #ffffff);
}

/* ---------------------------------------------------------------------------
   BASE
   ------------------------------------------------------------------------ */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  padding: 0 var(--rm-s4) var(--rm-s6);
  background: var(--rm-bg);
  color: var(--rm-ink);
  font-family: var(--rm-font-body);
  font-size: 1rem;
  line-height: 1.55;
}

/* Square corners. Everywhere. Always. */
input, select, textarea, button, table, td, th, details,
.region, .banner, .card, .badge, .btn {
  border-radius: 0;
}

h1, h2, h3, h4, .region-h, .panel-sub, .app-brand, label, summary, th, .btn, .badge {
  font-family: var(--rm-font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--rm-track-label);
  line-height: 1.2;
}

h1 { font-size: 1.5rem; letter-spacing: var(--rm-track-display); margin: 0 0 var(--rm-s2); }
h2 { font-size: 1.05rem; margin: 0 0 var(--rm-s3); }
h3 { font-size: 0.8rem; font-weight: 500; letter-spacing: var(--rm-track-display);
     color: var(--rm-ink-muted); margin: 0 0 var(--rm-s2); }

p { margin: 0 0 var(--rm-s3); }
p:last-child { margin-bottom: 0; }

strong { font-weight: 700; }

.mono {
  font-family: var(--rm-font-mono);
  font-size: 0.92em;
  text-transform: none;
  letter-spacing: 0;
}

.sr-only {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

[hidden] { display: none !important; }

.skip-link {
  position: absolute;
  left: -9999px;
  top: 0;
  z-index: 100;
  background: var(--rm-ink);
  color: var(--rm-ink-invert);
  padding: var(--rm-s2) var(--rm-s4);
  font-family: var(--rm-font-display);
  text-transform: uppercase;
  letter-spacing: var(--rm-track-label);
}
.skip-link:focus { left: var(--rm-s2); top: var(--rm-s2); }

:focus-visible {
  outline: var(--keca-focus-width, 2px) solid var(--rm-focus);
  outline-offset: var(--keca-focus-offset, 2px);
}

/* ---------------------------------------------------------------------------
   HEADER / FOOTER
   ------------------------------------------------------------------------ */

.app-head {
  border-bottom: 2px solid var(--rm-rule-strong);
  padding: var(--rm-s5) 0 var(--rm-s3);
  margin-bottom: var(--rm-s4);
}
.app-head-id { display: flex; align-items: baseline; gap: var(--rm-s3); flex-wrap: wrap; }
.app-brand {
  margin: 0;
  font-size: 0.75rem;
  letter-spacing: var(--rm-track-display);
  color: var(--rm-ink-muted);
}
.app-title { margin: 0; }

/* THE WAY BACK TO kecausa.com (2026-09-21). This tool is also served inside the
   main site at kecausa.com/specifier/, and nothing in it led back out: the nav
   below only reaches the other tools. The same rule as plan.css against the
   --rm-* tokens, so the two headers keep behaving the same way. First in
   .app-head-id, on the brand's own baseline; the 44px target is on its own
   box, as .app-nav a does it. */
.site-exit {
  display: inline-flex;
  align-items: center;
  min-height: var(--rm-tap);
  font-family: var(--rm-font-display);
  font-weight: 500;
  font-size: var(--keca-fs-micro, 0.6875rem);
  letter-spacing: 0.08em;
  text-transform: none;
  white-space: nowrap;
  color: var(--rm-ink-muted);
  text-decoration: underline;
  text-decoration-color: var(--rm-rule);
  text-underline-offset: 0.35em;
}
.site-exit:hover { color: var(--rm-ink); text-decoration-color: var(--rm-ink); }
.site-exit::after {
  content: "";
  flex: 0 0 1px;
  width: 1px;
  height: 1.25rem;
  margin-left: var(--rm-s3);
  background: var(--rm-rule);
}
@media print { .site-exit { display: none !important; } }

.app-nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rm-s3) var(--rm-s4);
  margin: var(--rm-s3) 0 var(--rm-s2);
  font-size: 0.8rem;
}
/* MEASURED AT 375 px: these four links were plain inline anchors — 67-91 px
   wide and 18.8 px TALL, with no padding and no min-height. They are the only
   route off this page, and 18.8 px is below the 24 px WCAG 2.5.8 minimum and
   well below a thumb. plan.css:824 already solves exactly this for the
   identical .app-nav on the plan tool by giving the anchor a --pl-tap
   min-height and inline-flex centring; this is the same rule against --rm-tap,
   so the two headers now behave the same way. The rendered text does not move:
   the extra height is centred around it. */
.app-nav a {
  color: var(--rm-ink);
  text-underline-offset: 0.18em;
  display: inline-flex;
  align-items: center;
  min-height: var(--rm-tap);
}
.app-nav a:hover { color: var(--rm-ink); text-decoration-thickness: 2px; }
.app-sub {
  margin: 0;
  max-width: 68ch;
  font-size: 0.9rem;
  color: var(--rm-ink-muted);
}

.app-foot {
  margin-top: var(--rm-s6);
  padding-top: var(--rm-s4);
  border-top: 1px solid var(--rm-rule);
  font-size: 0.82rem;
  color: var(--rm-ink-muted);
  max-width: 78ch;
}

/* ---------------------------------------------------------------------------
   BUTTONS
   ------------------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--rm-s2);
  min-height: var(--rm-tap);
  padding: var(--rm-s2) var(--rm-s4);
  border: 1px solid var(--rm-rule-strong);
  background: var(--rm-rule-strong);
  color: var(--rm-ink-invert);
  font-size: 0.72rem;
  text-decoration: none;
  cursor: pointer;
  transition: background .15s ease, color .15s ease, border-color .15s ease;
}
.btn:hover { background: var(--rm-ink); border-color: var(--rm-ink); }

.btn--ghost { background: transparent; color: var(--rm-ink); }
.btn--ghost:hover { background: var(--rm-surface-alt); color: var(--rm-ink); }

/* Compact on a desktop, where the pointer is a pixel. On a touch screen these
   are still real controls — "Clear all four" throws away a calibration — so
   they take the full --keca-tap-min target rather than a thumb-sized guess. */
/* 0.66rem was 10.6 px — below --keca-fs-micro, the 11 px floor this project
   set as the smallest step on the scale. "Clear all four" and "Forget this
   calibration" are destructive; they should not be the smallest words here. */
.btn--small { min-height: 36px; padding: var(--rm-s1) var(--rm-s3); font-size: var(--keca-fs-micro, 0.6875rem); }
@media (pointer: coarse) {
  .btn--small { min-height: var(--rm-tap); padding-inline: var(--rm-s4); }
}

.btn[disabled], .btn[aria-disabled="true"] {
  background: transparent;
  color: var(--rm-ink-faint);
  border-color: var(--rm-rule);
  cursor: not-allowed;
}

.btn-row {
  display: flex;
  flex-wrap: wrap;
  gap: var(--rm-s2);
  margin: var(--rm-s3) 0 0;
}

/* ---------------------------------------------------------------------------
   PRIVACY BANNER — the load-bearing promise of this page
   ------------------------------------------------------------------------ */

.privacy {
  border: 2px solid var(--rm-rule-strong);
  background: var(--rm-surface);
  padding: var(--rm-s3) var(--rm-s4);
  margin: 0 0 var(--rm-s4);
  max-width: 78ch;
}
.privacy-title {
  font-family: var(--rm-font-display);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: var(--rm-track-label);
  font-size: 0.72rem;
  margin: 0 0 var(--rm-s2);
}
.privacy p { font-size: 0.86rem; margin: 0 0 var(--rm-s2); }
.privacy p:last-child { margin: 0; }
/* MEASURED AT 375 px: this disclosure was a 13 px-tall, 10.9 px-text target —
   the smallest hit area on the page, and it is the control that opens the
   answer to "what exactly do you read from my photograph". A design firm
   deciding whether to upload a picture of a client's property should not have
   to hit a 13 px line to find out. Raised to the --keca-fs-micro floor (11 px,
   the smallest step the token scale defines) and given a full tap target. */
.privacy summary {
  font-size: var(--keca-fs-micro, 0.6875rem);
  color: var(--rm-ink-muted);
  cursor: pointer;
  margin-top: var(--rm-s2);
  /* Padding, NOT display:flex. A <summary> is display:list-item, and that is
     what draws the disclosure triangle — switching it to flex silently removes
     the one visual cue that this line opens. Padding reaches the same target
     and keeps the marker. */
  padding-block: 0.8rem;
  min-height: var(--rm-tap);
  line-height: 1.6;
}
.privacy ul { margin: var(--rm-s2) 0 0; padding-left: 1.1rem; font-size: 0.82rem; }
.privacy li { margin-bottom: var(--rm-s1); }

/* ---------------------------------------------------------------------------
   LAYOUT
   ------------------------------------------------------------------------ */

.layout {
  display: grid;
  gap: var(--rm-s5);
  grid-template-columns: 1fr;
  align-items: start;
}

@media (min-width: 1024px) {
  .layout {
    grid-template-columns: minmax(0, 1fr) 26rem;
    gap: var(--rm-s5);
  }
  .col-stage { position: sticky; top: var(--rm-s3); }
}

.col-stage, .col-panel { min-width: 0; }
.col-panel { display: flex; flex-direction: column; gap: var(--rm-s4); }

.region {
  border: 1px solid var(--rm-rule);
  background: var(--rm-surface);
  padding: var(--rm-s4);
}
.region-h {
  display: flex;
  align-items: center;
  gap: var(--rm-s3);
  font-size: 0.72rem;
  color: var(--rm-ink-muted);
  margin: 0 0 var(--rm-s3);
}
.region-h::after {
  content: "";
  flex: 1 1 auto;
  height: 1px;
  background: var(--rm-rule);
}
.region-h .step-no {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem; height: 1.5rem;
  border: 1px solid var(--rm-rule-strong);
  color: var(--rm-ink);
  font-size: 0.7rem;
  flex: 0 0 auto;
}

.panel-note {
  font-size: 0.8rem;
  color: var(--rm-ink-muted);
  margin: var(--rm-s3) 0 0;
}

/* ---------------------------------------------------------------------------
   THE STAGE — photo + overlay, registered exactly (see file header)
   ------------------------------------------------------------------------ */

/* The wrap exists so the magnifier can sit over the photo without being a
   child of the photo's own box. */
.stage-wrap { position: relative; }

.stage {
  position: relative;
  border: 1px solid var(--rm-rule-strong);
  background: var(--rm-surface-sunk);
  overflow: hidden;
}
.stage.is-dropping { outline: 3px dashed var(--rm-rule-strong); outline-offset: -6px; }

.stage-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--rm-s3);
  min-height: 46vh;
  padding: var(--rm-s5) var(--rm-s4);
  text-align: center;
  color: var(--rm-ink-muted);
}
.stage-empty p { max-width: 34ch; font-size: 0.88rem; }

/* HOW THE PHOTO IS FITTED, and why it is done this way.

   The canvas is a plain block at width:100%; height:auto, so its box always has
   the photo's exact aspect ratio and the figure's box is exactly the canvas's
   box. Nothing is letterboxed, so the overlay at inset:0 registers exactly.

   To stop a tall portrait photo filling three screens on a phone, the HEIGHT is
   capped by capping the figure's WIDTH — room-calibrate.js's fitStage() sets
   `max-width` in pixels to (cap height x aspect ratio) whenever the photo or
   the window changes. Capping the height directly would need object-fit or a
   max-height on a width:100% canvas, and both reintroduce the letterbox this
   page must not have. */
.stage-figure {
  position: relative;
  margin: 0 auto;
  display: block;
  max-width: 100%;
}

.photo-holder { display: block; line-height: 0; }
.photo-canvas {
  display: block;
  width: 100%;
  height: auto;
  /* No object-fit. See the file header — the canvas box IS the photo box. */
}

/* TOUCH SCROLLING OVER THE PHOTO — measured at 375 px.
   This was `touch-action: none` across the whole overlay. The overlay covers
   the entire photo, and the photo is as tall as its own aspect ratio makes it:
   a landscape test shot measured 349 x 262 px, and a portrait phone photo
   (3:4) comes out about 349 x 465 px. On a 375 x 667 iPhone SE that is 70 % of
   the viewport with every touch gesture swallowed — a one-finger swipe that
   starts anywhere on the picture did not scroll the page, so a designer whose
   photo filled the screen could not get past it to reach steps 2, 3 and 4 at
   all. plan.css:425 documents this exact trap being found and fixed on the
   plan drawing; the room stage still had it.

   Same resolution as the plan tool. `pan-y pinch-zoom` lets the browser take a
   vertical swipe and scroll the page, while horizontal drags and every tap
   still reach JavaScript. touch-action intersects down the tree, so the corner
   handles below opt back out to `none` and can be dragged in any direction.

   What this costs, stated plainly: placing a brand-new corner and sliding it
   in ONE unbroken vertical gesture no longer works, because at pointerdown
   there is no handle under the finger yet and the browser claims the vertical
   pan. Tap to place, then drag the handle to refine — which is exactly what
   the instructions under the photo already tell people to do. */
.overlay {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  display: block;
  cursor: crosshair;
  touch-action: pan-y pinch-zoom;
}
/* The transparent 22-unit grab circle behind each corner marker. Dragging a
   placed corner must work in every direction, including straight up and down,
   so the handle takes the gesture back off the browser. */
.overlay .handle,
.overlay .handle-hit { touch-action: none; }
.overlay:focus-visible {
  outline: var(--keca-focus-width, 2px) solid var(--rm-focus);
  outline-offset: -3px;
}

/* --- overlay ink ---------------------------------------------------------
   Every stroke is painted twice: a wide light halo first, then a thin dark
   line. That is what keeps a 1 m grid legible on both a dark slate floor and a
   white terrazzo one without introducing a colour.
   ---------------------------------------------------------------------- */

.overlay path, .overlay line, .overlay circle { vector-effect: non-scaling-stroke; }

.halo   { fill: none; stroke: var(--rm-overlay-halo); stroke-opacity: .75; }
.stroke { fill: none; stroke: var(--rm-overlay-ink); }

#g-grid-halo     { stroke-width: 2.75px; }
#g-grid          { stroke-width: 1px; stroke-opacity: .85; }

#g-rect-halo     { stroke-width: 6px; }
#g-rect          { stroke-width: 2.5px; }

#g-horizon-halo  { stroke-width: 4px; }
#g-horizon       { stroke-width: 1.5px; stroke-dasharray: 10 7; }

#g-uprights-halo { stroke-width: 5px; }
#g-uprights      { stroke-width: 2px; stroke-dasharray: 6 4; }

.cap-line { stroke: var(--rm-overlay-ink); stroke-width: 2px; }

/* --- corner handles --------------------------------------------------- */

.handle-hit  { fill: transparent; stroke: none; }
.handle-halo { fill: var(--rm-overlay-halo); fill-opacity: .85; stroke: none; }
.handle-dot  { fill: var(--rm-overlay-ink); stroke: var(--rm-overlay-halo); stroke-width: 1.5px; }
.handle-cross { stroke: var(--rm-overlay-ink); stroke-width: 1px; stroke-opacity: .6; }

/* The active corner is marked by WEIGHT and a ring, never by colour. */
.handle.is-active .handle-dot { stroke-width: 3px; }
.handle.is-active .handle-halo { fill-opacity: 1; }

.handle-label,
.dim-label {
  fill: var(--rm-overlay-ink);
  stroke: var(--rm-overlay-halo);
  stroke-width: 3px;
  stroke-linejoin: round;
  paint-order: stroke fill;
  font-family: var(--rm-font-display);
  font-weight: 600;
  letter-spacing: 0.06em;
  pointer-events: none;
}
.dim-label { font-family: var(--rm-font-mono); font-weight: 400; letter-spacing: 0; }

.cursor-line { stroke: var(--rm-overlay-ink); stroke-width: 1.5px; stroke-dasharray: 5 4; }
.cursor-dot  { fill: none; stroke: var(--rm-overlay-ink); stroke-width: 2px; }

/* --- the magnifier ---------------------------------------------------- */

.loupe {
  position: absolute;
  top: var(--rm-s2);
  right: var(--rm-s2);
  width: 128px;
  height: 128px;
  border: 2px solid var(--rm-rule-strong);
  background: var(--rm-surface);
  pointer-events: none;
  z-index: 3;
}
.loupe--left { right: auto; left: var(--rm-s2); }

/* --- the running instruction ------------------------------------------ */

.step-line {
  border: 1px solid var(--rm-rule);
  border-top: none;
  background: var(--rm-surface);
  padding: var(--rm-s3) var(--rm-s4);
  font-size: 0.88rem;
  min-height: 3.4rem;
  display: flex;
  align-items: center;
}
.step-line[data-level="error"] {
  border-left: 4px solid var(--rm-alert);
  font-weight: 600;
}
.step-line[data-level="warn"] { border-left: 4px solid var(--rm-warn); }
.step-line[data-level="ok"] {
  border-left: 4px solid var(--rm-pass);
  background: var(--rm-pass-bg);
}

.stage-help {
  font-size: 0.8rem;
  color: var(--rm-ink-muted);
  margin: var(--rm-s3) 0 0;
  max-width: 72ch;
}
.stage-help kbd {
  font-family: var(--rm-font-mono);
  font-size: 0.85em;
  border: 1px solid var(--rm-rule);
  background: var(--rm-surface-alt);
  padding: 0 0.3em;
}

.photo-meta {
  font-family: var(--rm-font-mono);
  font-size: 0.72rem;
  color: var(--rm-ink-faint);
  margin: var(--rm-s2) 0 0;
  word-break: break-word;
}

/* ---------------------------------------------------------------------------
   FIELDS
   ------------------------------------------------------------------------ */

.field { margin: 0 0 var(--rm-s4); }
.field:last-child { margin-bottom: 0; }

.field > label,
.field-legend {
  display: block;
  font-size: 0.7rem;
  color: var(--rm-ink-muted);
  margin: 0 0 var(--rm-s1);
}

input[type="number"],
input[type="text"],
select {
  width: 100%;
  min-height: var(--rm-tap);
  padding: var(--rm-s2) var(--rm-s3);
  border: 1px solid var(--rm-rule);
  background: var(--rm-surface);
  color: var(--rm-ink);
  font-family: var(--rm-font-body);
  font-size: 1rem;   /* 16px: anything smaller makes iOS zoom on focus */
}
input:focus, select:focus { border-color: var(--rm-rule-strong); }

.input-unit { position: relative; display: block; }
.input-unit input { padding-right: 3rem; }
.input-unit .unit {
  position: absolute;
  right: var(--rm-s3);
  top: 50%;
  transform: translateY(-50%);
  font-family: var(--rm-font-mono);
  font-size: 0.8rem;
  color: var(--rm-ink-faint);
  pointer-events: none;
}

.field-help {
  font-size: 0.78rem;
  color: var(--rm-ink-muted);
  margin: var(--rm-s1) 0 0;
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--rm-s3);
}
.field-grid .field { margin: 0; }

.checks { display: flex; flex-direction: column; gap: var(--rm-s1); }
.check {
  display: flex;
  align-items: center;
  gap: var(--rm-s2);
  min-height: var(--rm-tap);
  cursor: pointer;
  font-family: var(--rm-font-body);
  font-size: 0.88rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--rm-ink);
}
.check input { width: 1.15rem; height: 1.15rem; accent-color: var(--rm-ink); flex: 0 0 auto; }

/* File inputs are styled as labels so they hit the 44 px target and match the
   rest of the button set. The real <input type="file"> stays in the DOM and
   keeps its accessible name — it is visually hidden, not display:none, so it
   remains focusable and reachable by a screen reader. */
.file-input {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}
.file-input:focus-visible + .btn {
  outline: var(--keca-focus-width, 2px) solid var(--rm-focus);
  outline-offset: var(--keca-focus-offset, 2px);
}

/* `display: contents` so the wrapper does not become a flex item of its own —
   the label still lays out as a sibling button in .btn-row. */
.pick { display: contents; }

/* The camera button only makes sense on a device that has one attached to the
   browser's file picker. On a mouse-and-keyboard machine `capture` is ignored
   and the button would just be a second, confusing "choose a file".
   This hides the INPUT along with its label: a focusable file input whose only
   accessible name comes from a display:none <label> is a screen-reader trap. */
.only-touch { display: none; }
@media (pointer: coarse) {
  .pick.only-touch { display: contents; }
}

/* ---------------------------------------------------------------------------
   READOUT
   ------------------------------------------------------------------------ */

.result-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--rm-s3);
  margin: 0 0 var(--rm-s3);
}

.badge {
  display: inline-flex;
  align-items: center;
  padding: var(--rm-s1) var(--rm-s3);
  border: 1px solid var(--rm-rule-strong);
  font-size: 0.66rem;
  white-space: nowrap;
}
.badge[data-state="ok"] {
  background: var(--rm-pass-bg);
  border-color: var(--rm-pass);
  color: var(--rm-ink);
}
/* Low confidence and rejected are told apart by border weight and by the word
   in the badge, not by hue. */
.badge[data-state="low-confidence"] { border-width: 1px; border-style: dashed; }
.badge[data-state="rejected"] {
  background: var(--rm-rule-strong);
  color: var(--rm-ink-invert);
}

.readout {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.84rem;
}
.readout th,
.readout td {
  border-bottom: 1px solid var(--rm-rule-faint);
  padding: var(--rm-s2) 0;
  text-align: left;
  vertical-align: top;
}
.readout th {
  font-size: 0.66rem;
  color: var(--rm-ink-muted);
  font-weight: 500;
  width: 52%;
  padding-right: var(--rm-s3);
}
.readout td {
  font-family: var(--rm-font-mono);
  font-size: 0.86rem;
  color: var(--rm-ink);
}
.readout tr:last-child th,
.readout tr:last-child td { border-bottom: none; }
.readout .row-note {
  display: block;
  font-family: var(--rm-font-body);
  font-size: 0.72rem;
  font-weight: 400;
  text-transform: none;
  letter-spacing: 0;
  color: var(--rm-ink-faint);
  margin-top: 2px;
}

.messages { list-style: none; margin: var(--rm-s3) 0 0; padding: 0; }
.msg {
  border-left: 4px solid var(--rm-rule);
  padding: var(--rm-s2) 0 var(--rm-s2) var(--rm-s3);
  margin: 0 0 var(--rm-s2);
  font-size: 0.84rem;
}
.msg:last-child { margin-bottom: 0; }
.msg--error { border-left-color: var(--rm-alert); border-left-width: 5px; font-weight: 600; }
.msg--warn  { border-left-color: var(--rm-warn); border-left-style: dashed; }
.msg--ok    { border-left-color: var(--rm-pass); }
.msg--info  { border-left-color: var(--rm-rule); }

/* ---------------------------------------------------------------------------
   DISCLOSURE — §9.3, stated on the page as well as on any export
   ------------------------------------------------------------------------ */

.disclosure {
  border: 1px solid var(--rm-rule-strong);
  border-left-width: 5px;
  background: var(--rm-surface-alt);
  padding: var(--rm-s4);
  margin-top: var(--rm-s5);
  max-width: 78ch;
  font-size: 0.86rem;
}
.disclosure h2 { font-size: 0.72rem; }

details.sub {
  border-top: 1px solid var(--rm-rule-faint);
  margin-top: var(--rm-s3);
  padding-top: var(--rm-s3);
}
details.sub > summary {
  cursor: pointer;
  font-size: 0.68rem;
  color: var(--rm-ink-muted);
  min-height: 2rem;
  display: flex;
  align-items: center;
}
details.sub > summary::marker { color: var(--rm-ink-faint); }
.sub-body { padding-top: var(--rm-s3); }

.tile-result {
  font-family: var(--rm-font-mono);
  font-size: 0.78rem;
  color: var(--rm-ink);
  margin: var(--rm-s3) 0 0;
  min-height: 1.2em;
}

.session-note {
  font-size: 0.78rem;
  color: var(--rm-ink-muted);
  margin: var(--rm-s3) 0 0;
}

/* ---------------------------------------------------------------------------
   PHONE — the client reviews this from one. 375 px is the target width.
   ------------------------------------------------------------------------ */

@media (max-width: 420px) {
  body { padding-inline: var(--rm-s3); }

  .app-head { padding-top: var(--rm-s4); }
  h1 { font-size: 1.25rem; }
  .app-nav { gap: var(--rm-s3); font-size: 0.76rem; }

  .region { padding: var(--rm-s3); }
  .privacy { padding: var(--rm-s3); }

  /* Two number fields side by side still clear 44 px at 375 px, and keeping
     them on one line is what makes "width × depth" read as one measurement. */
  .field-grid { gap: var(--rm-s2); }

  .loupe { width: 104px; height: 104px; }

  .step-line { font-size: 0.84rem; padding: var(--rm-s3); }
  .btn { padding-inline: var(--rm-s3); }
}

@media (prefers-reduced-motion: reduce) {
  * { transition: none !important; }
}

/* ---------------------------------------------------------------------------
   PRINT — a calibration readout is worth putting in a project file. The photo
   prints; the controls do not.
   ------------------------------------------------------------------------ */

@media print {
  body { padding: 0; background: var(--rm-surface); }
  .app-nav, .btn, .btn-row, .no-print, .loupe { display: none !important; }
  .layout { display: block; }
  .region { border: none; padding: 0 0 var(--rm-s4); page-break-inside: avoid; }
  .col-stage { position: static; }
  .stage { overflow: visible; }
}

/* ==========================================================================
   PHONE TYPE FLOOR — measured on this page at 375 px
   --------------------------------------------------------------------------
   Placed AFTER the max-width: 420px block above on purpose: that block shrinks
   .app-nav to 0.76rem (12.2 px) and .step-line to 0.84rem (13.4 px) on exactly
   the screen this audit targets, so a floor declared before it would lose.

   Everything listed is running text or a recovered NUMBER, measured under
   14 px at 375 px:

       .readout th        10.6 px  which quantity each row reports
       details.sub summary 10.9 px "What to use as an anchor"
       .photo-meta        11.5 px  the working-copy size and EXIF finding
       .readout .row-note 11.5 px  "standing is about 1.5-1.7 m"
       .app-nav a         12.2 px
       .field-help        12.5 px  including "Measure it. Do not use a
                                   remembered standard size"
       .session-note      12.5 px
       .tile-result       12.5 px
       .stage-help        12.8 px  the whole keyboard interaction reference
       .app-foot          13.1 px
       .privacy ul        13.1 px  exactly what is read from the photo
       .msg               13.4 px  the calibration warnings and rejections
       .step-line         13.4 px  the current step and the verdict
       .readout td        13.8 px  camera height, field of view, focal length
       .disclosure        13.8 px  "Read before specifying"
       .privacy p         13.8 px  "Your photograph never leaves this device"

   The privacy promise and the standing disclosure are the two blocks a design
   firm actually has to read before pointing a camera at a client's property.
   Both were the smallest running text on the page.
   ========================================================================== */
@media (max-width: 767px) {
  .privacy p,
  .privacy ul,
  .field-help,
  .stage-help,
  .photo-meta,
  .session-note,
  .tile-result,
  .step-line,
  .msg,
  .readout,
  .readout td,
  .readout .row-note,
  .disclosure,
  .app-foot,
  .sub-body ul { font-size: var(--keca-fs-base, 0.875rem); }

  .app-nav { font-size: var(--keca-fs-base, 0.875rem); }

  /* Row labels and the sub-disclosure summaries are short, so they take the
     11 px micro step of the token scale rather than the full 14 px — but they
     were below even that. */
  .readout th,
  details.sub > summary { font-size: var(--keca-fs-micro, 0.6875rem); }
}

/* ================= HEIGHT CONFIRMATION =================
   Deliberately the loudest thing in the results block. It is the only defence
   against a uniformly mis-measured reference rectangle, which every automated
   gate passes cleanly (condition 1.00000, residual 1e-13, status ok) while
   silently scaling every dimension. */
.height-confirm {
  border: 2px solid var(--paper);
  background: var(--surface);
  padding: 16px 18px;
  margin: 0 0 18px;
}
.height-confirm-q {
  font-family: var(--head-font, 'Montserrat', sans-serif);
  font-size: 15px;
  line-height: 1.45;
  margin: 0 0 6px;
  color: var(--paper);
}
.height-confirm-hint {
  margin: 0 0 14px;
  font-size: 13px;
  line-height: 1.5;
  color: var(--paper-muted);
}
.height-confirm-actions { display: flex; flex-wrap: wrap; gap: 8px; }
.height-confirm .btn { min-height: 44px; }
.height-confirm .btn-primary { background: var(--accent); color: #fff; border-color: var(--accent); }
.height-confirm-verdict {
  margin: 14px 0 0; padding: 11px 13px; font-size: 13.5px; line-height: 1.5;
}
.height-confirm-verdict[data-state="ok"]   { background: var(--green-bg); color: var(--green); }
.height-confirm-verdict[data-state="warn"] { background: var(--warn-bg, #f3e7e4); color: var(--warn, #9c3b2a); }
.height-confirm[data-answered="no"] { border-color: var(--warn, #9c3b2a); }

@media (max-width: 420px) {
  .height-confirm-actions .btn { width: 100%; }
}
