/*
 * Camera-first Identify surface — SPRINT2 WS-D.
 *
 * Mobile-first. The base rules are written for 320px and the only additions
 * above that are more room, never a different structure, so nothing has to be
 * unwound at a breakpoint.
 *
 * Colour: every value comes from theme/assets/css/tokens.css, whose pairings
 * are contrast-verified there. Two rules are re-checked here because this
 * surface introduces new pairings:
 *   --dx-ink on --dx-surface .......... 14.30:1  (body copy, headings)
 *   --dx-ink-soft on --dx-surface ......  6.88:1  (supporting copy)
 *   --dx-on-solid on --dx-coral-solid ..  4.55:1  (shutter, primary action)
 *   #ffffff on the viewfinder scrim ... the scrim is #2a2731 at 72% over a
 *      live video frame; the prompt text sits on the OPAQUE panel below the
 *      viewfinder instead, so no text is ever judged against moving pixels.
 *
 * Motion: every transition is inside @media (prefers-reduced-motion:
 * no-preference). The default state of this file is motionless.
 */

/*
 * WS-J defect 2: the two measured reserves the viewfinder cap is built from.
 * They live here, next to the rule that consumes them, rather than as numbers
 * buried inside a calc().
 *   --dx-cam-head-reserve     the tallest the head block gets (measured 118px
 *                             at 320px, where the h1 wraps to three lines)
 *   --dx-cam-shutter-reserve  the shutter row, which must never be covered
 */
.dx-cam {
  --dx-cam-head-reserve: 120px;
  --dx-cam-shutter-reserve: 72px;
  display: grid;
  gap: var(--dx-s-4);
  margin-bottom: var(--dx-s-6);
}

.dx-cam__head h1 {
  margin: 0 0 var(--dx-s-1);
  color: var(--dx-ink);
  font-size: var(--dx-t-h1);
  line-height: var(--dx-lh-tight);
}

.dx-cam__lede {
  margin: 0;
  max-width: 34ch;
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-body);
  line-height: var(--dx-lh-snug);
}

.dx-cam__stage {
  display: grid;
  gap: var(--dx-s-3);
}

/* ---- Viewfinder -------------------------------------------------------
 * 3:4 on phones: a plush is taller than it is wide, and a portrait frame is
 * what a one-handed user is already holding.
 */
/*
 * WS-J defect 2 — the shutter must be ON SCREEN and TAPPABLE at every width.
 *
 * The aspect ratio alone decides the viewfinder's height, so on a short phone
 * the surface grew taller than the viewport and pushed the shutter under (or
 * past) the fixed bottom bar: measured in Chrome before this fix, shutter
 * 627..699 in a 640px viewport at 320, 669..741 in 667px at 375, and at 390 a
 * tap on the geometric centre of the shutter landed on the bottom navigation
 * instead of the camera.
 *
 * The viewfinder is the only elastic element on the surface, so it is what
 * yields. The reserve below is the measured sum of everything that must stay
 * in view with it — the site header, the page's top padding, the head block at
 * its tallest (118px measured at 320px, where the h1 wraps to three lines),
 * the two grid gaps above and below the stage, the 72px shutter row, and the
 * bar's own reserved strip. `min-height` keeps the frame usable if a viewport
 * is shorter still: below that point the page scrolls rather than shrinking
 * the viewfinder into uselessness.
 */
/*
 * WS-K: `width: 100%` is load-bearing, not cosmetic.
 *
 * With `aspect-ratio` and an AUTO width, WS-J's `max-height` made the frame's
 * WIDTH follow its clamped height: measured at 320px the viewfinder came out
 * 153px wide inside a 288px column (231->173 at 375, 358->326 at 390).
 * Two consequences, one visual and one functional:
 *   - the preview became a sliver in the middle of the column;
 *   - the containing block for the absolutely positioned `.dx-cam__start` was
 *     no longer definite before that child was laid out, which is the root of
 *     the button's unstable box (see `.dx-cam__start` below).
 * Pinning the width makes the width definite FIRST; the ratio then derives the
 * height, and `max-height` clamps it. Same cap, no circular dependency.
 */
.dx-cam__viewfinder {
  position: relative;
  display: grid;
  overflow: hidden;
  width: 100%;
  aspect-ratio: 3 / 4;
  max-height: calc(
    100dvh
    - var(--dx-header-h)
    - var(--dx-s-7)
    - var(--dx-cam-head-reserve)
    - var(--dx-s-4)
    - var(--dx-s-3)
    - var(--dx-cam-shutter-reserve)
    - var(--dx-bottomnav-clearance)
  );
  min-height: 180px;
  place-items: center;
  border-radius: var(--dx-r-lg);
  background: var(--dx-surface-sunk);
}

.dx-cam__video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  background: var(--dx-ink);
}

/* Framing guide. Decoration is not allowed on this surface, so this is the
 * one drawn element: it exists to tell the user where to put the plush. */
.dx-cam__frame {
  position: absolute;
  inset: 12% 10%;
  border: 2px dashed rgba(255, 255, 255, 0.72);
  border-radius: var(--dx-r-lg);
  pointer-events: none;
}

[data-dx-cam-stage="idle"] .dx-cam__frame,
[data-dx-cam-stage="permission_denied"] .dx-cam__frame,
[data-dx-cam-stage="unavailable"] .dx-cam__frame {
  display: none;
}

/*
 * WS-K regression fix (introduced by WS-J's cap).
 *
 * This button had NO insets, so it was placed at its static position and
 * centred only by the container's `place-items: center`. That resolution needs
 * a definite containing-block size; under the cap the frame's width was
 * derived FROM its clamped height, so the first layout pass centred the button
 * against one width and the next relayout (any hover, any repaint) centred it
 * against another. Measured at 390px: top 380 / left 101 / h 48 before a
 * pointer existed, top 261 / left 16 / h 57 (text wrapped) the instant a real
 * pointer touched the page -- a 119px jump of the primary control, so a trusted
 * click at the measured centre landed on the video and the camera never opened.
 *
 * `inset: 0` + `margin: auto` + `fit-content` sizing centres the button from
 * its own four resolved offsets instead of from the container's alignment
 * property, so the box is the same in every layout pass. `width: 100%` on the
 * frame removes the cause; this removes the fragility.
 */
.dx-cam__start {
  position: absolute;
  inset: 0;
  width: fit-content;
  height: fit-content;
  max-width: calc(100% - (2 * var(--dx-s-4)));
  margin: auto;
  min-height: 48px;
}

[data-dx-cam-stage="idle"] .dx-cam__video {
  opacity: 0;
}

/* ---- Adaptive prompt ---------------------------------------------------
 * One instruction, one reason, nothing else. This is the "SHORT TO OPERATE"
 * rule made physical: the instruction is the largest text on the surface
 * after the page title.
 */
.dx-cam__prompt {
  display: grid;
  gap: var(--dx-s-1);
  padding: var(--dx-s-3) var(--dx-s-4);
  border-radius: var(--dx-r-md);
  background: var(--dx-sky-wash);
}

.dx-cam-prompt__instruction {
  margin: 0;
  color: var(--dx-ink);
  font-size: var(--dx-t-h3);
  font-weight: 800;
  line-height: var(--dx-lh-snug);
}

.dx-cam-prompt__why,
.dx-cam-prompt__count {
  margin: 0;
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-small);
}

.dx-cam-prompt__skip {
  justify-self: start;
  min-height: 44px;
}

.dx-cam__hint {
  margin: 0;
  padding: var(--dx-s-2) var(--dx-s-3);
  border-radius: var(--dx-r-sm);
  background: var(--dx-sunny-wash);
  color: var(--dx-sunny-ink);
  font-size: var(--dx-t-small);
  font-weight: 700;
}

.dx-cam__status {
  margin: 0;
  min-height: 1.25rem;
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-small);
}

/* ---- Controls ----------------------------------------------------------
 * WS-K: the shutter row is PINNED above the reserved bottom strip.
 *
 * WS-J capped the viewfinder so the shutter would stay clear of the fixed bar.
 * Measured, that arithmetic cannot succeed on the short phones: at 320x640 the
 * space above the reserved strip is 289px, and the live camera state needs the
 * frame plus a 101px adaptive prompt plus the 72px shutter row plus two gaps --
 * 197px before the frame gets a single pixel. Even at the 180px floor the
 * shutter landed at y=560..632 under a bar starting at y=576 (56px overlap, and
 * a centre tap navigated to "/"). No cap value fixes that; the row has to stop
 * depending on how tall everything above it happens to be.
 *
 * So it is sticky, offset by the SAME token every page reserves at the bottom
 * (--dx-bottomnav-clearance = bar + safe area + the raised control's overhang).
 * The shutter is therefore on screen and clear of both the bar and the raised
 * circle at every width and in every state, whatever the head, the prompt or a
 * future hint measures. The opaque background is what makes it read as a tray
 * rather than as text overlapping text when it does lift off its flow position.
 *
 * The bar only exists below 1100px, so above that the row goes back into flow
 * (see the 1100px block below).
 */
.dx-cam__controls {
  position: sticky;
  bottom: var(--dx-bottomnav-clearance);
  z-index: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--dx-s-4);
  border-radius: var(--dx-r-pill);
  background: var(--dx-surface);
}

.dx-cam__shutter {
  display: grid;
  flex: 0 0 auto;
  width: 72px;
  height: 72px;
  place-items: center;
  border: 0;
  border-radius: var(--dx-r-pill);
  background: var(--dx-coral-solid);
  color: var(--dx-on-solid);
  cursor: pointer;
}

.dx-cam__shutter-ring {
  display: block;
  width: 56px;
  height: 56px;
  border: 3px solid var(--dx-on-solid);
  border-radius: var(--dx-r-pill);
}

.dx-cam__shutter[disabled] {
  background: var(--dx-line-strong);
  color: var(--dx-ink-soft);
  cursor: not-allowed;
}

.dx-cam__shutter:focus-visible,
.dx-cam__file:focus-within {
  outline: var(--dx-focus);
  outline-offset: var(--dx-focus-offset);
}

/* ---- Captured strip ----------------------------------------------------- */
.dx-cam__strip {
  display: flex;
  gap: var(--dx-s-2);
  margin: 0;
  padding: 0;
  overflow-x: auto;
  list-style: none;
}

.dx-cam-strip__item {
  display: grid;
  gap: var(--dx-s-1);
  flex: 0 0 88px;
}

.dx-cam-strip__item img {
  width: 88px;
  height: 88px;
  border-radius: var(--dx-r-sm);
  object-fit: cover;
  background: var(--dx-surface-sunk);
}

.dx-cam-strip__retake {
  min-height: 44px;
  border: 1px solid var(--dx-line-strong);
  border-radius: var(--dx-r-sm);
  background: var(--dx-surface);
  color: var(--dx-accent-ink);
  font-size: var(--dx-t-small);
  font-weight: 800;
  cursor: pointer;
}

/* ---- Fallbacks ---------------------------------------------------------- */
.dx-cam__fallbacks {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--dx-s-3);
}

.dx-cam__file {
  display: inline-flex;
  min-height: 44px;
  align-items: center;
  padding: 0 var(--dx-s-4);
  border: 1px solid var(--dx-line-strong);
  border-radius: var(--dx-r-pill);
  color: var(--dx-accent-ink);
  font-weight: 800;
  cursor: pointer;
}

.dx-cam__file-input {
  position: absolute;
  width: 1px;
  height: 1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
}

.dx-cam__privacy {
  margin: 0;
  color: var(--dx-ink-faint);
  font-size: var(--dx-t-micro);
}

/* ---- Result surface -----------------------------------------------------
 * The heading carries the whole verdict; the band tint is reinforcement, not
 * the message, so the surface still reads correctly in greyscale.
 */
.dx-cam__result {
  display: grid;
  gap: var(--dx-s-4);
}

.dx-cam-result__head {
  padding: var(--dx-s-4);
  border-radius: var(--dx-r-md);
  background: var(--dx-surface-sunk);
}

.dx-cam-result__head--strong { background: var(--dx-mint-wash); }
.dx-cam-result__head--possible { background: var(--dx-sky-wash); }
.dx-cam-result__head--weak { background: var(--dx-sunny-wash); }
.dx-cam-result__head--none { background: var(--dx-surface-sunk); }
/* A ruled-out identification is an honest negative, not an error: it takes a
 * calm sunk surface, never an alarm colour. */
.dx-cam-result__head--contradicted { background: var(--dx-surface-sunk); }

/* The canonical verdict, one plain sentence. Sits under the lede as the
 * standing conclusion in a collector's own words. */
.dx-cam-result__verdict {
  margin: var(--dx-s-2) 0 0;
  color: var(--dx-ink);
  font-size: var(--dx-t-body);
  font-weight: 700;
  line-height: var(--dx-lh-snug);
}

/* Evidence strength as a single word. A chip, not a meter -- it carries no
 * number and reads correctly in greyscale because the word is the message. */
.dx-cam-result__strength {
  display: inline-flex;
  align-items: center;
  align-self: start;
  margin: 0;
  padding: 2px var(--dx-s-2);
  border-radius: var(--dx-r-pill, 999px);
  background: var(--dx-surface-sunk);
  color: var(--dx-ink);
  font-size: var(--dx-t-small);
  font-weight: 800;
  letter-spacing: 0.01em;
}

.dx-cam-result__strength--strong { background: var(--dx-mint-wash); color: var(--dx-ink); }
.dx-cam-result__strength--likely { background: var(--dx-sky-wash); color: var(--dx-ink); }
.dx-cam-result__strength--possible { background: var(--dx-sky-wash); color: var(--dx-ink); }
.dx-cam-result__strength--uncertain { background: var(--dx-sunny-wash); color: var(--dx-sunny-ink); }

/* The honest next-action list. Same visual grammar as "why we think so". */
.dx-cam-result__next {
  padding: var(--dx-s-3);
  border: 1px dashed var(--dx-line);
  border-radius: var(--dx-r-md);
  background: var(--dx-surface-sunk);
}

.dx-cam-result__next .dx-cam-result__why-label {
  margin-top: 0;
}

.dx-cam-result__title {
  margin: 0 0 var(--dx-s-1);
  color: var(--dx-ink);
  font-size: var(--dx-t-h2);
  line-height: var(--dx-lh-tight);
}

.dx-cam-result__title:focus-visible {
  outline: var(--dx-focus);
  outline-offset: var(--dx-focus-offset);
}

.dx-cam-result__lede,
.dx-cam-result__note {
  margin: 0;
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-body);
  line-height: var(--dx-lh-snug);
}

.dx-cam-result__list {
  display: grid;
  gap: var(--dx-s-3);
}

.dx-cam-result__card {
  display: grid;
  gap: var(--dx-s-3);
  padding: var(--dx-s-3);
  border: 1px solid var(--dx-line);
  border-radius: var(--dx-r-md);
  background: var(--dx-surface);
}

.dx-cam-result__card--lead {
  border-style: dashed;
}

/*
 * The single confident answer is marked as the best match with a solid coral
 * edge, so it is never mistaken for one of the peer "compare these"
 * alternatives or for a dashed, unconfirmed lead. The distinction is a border,
 * not a colour fill, so it still reads in greyscale and at high contrast.
 */
.dx-cam-result__card--best {
  border-color: var(--dx-coral-solid);
  border-left-width: 4px;
}

/*
 * Recoverable failure panel. A stalled or failed check renders here instead of
 * leaving a spinner running. Calm sunk surface — this is an interruption to
 * recover from, not an alarm. Its buttons inherit the shared 44px result-action
 * target defined further down, so Retry is as tappable as any other control.
 */
.dx-cam-result__error {
  display: grid;
  gap: var(--dx-s-3);
  padding: var(--dx-s-4);
  border: 1px solid var(--dx-line);
  border-radius: var(--dx-r-md);
  background: var(--dx-surface-sunk);
}

.dx-cam-result__image {
  width: 100%;
  max-height: 220px;
  border-radius: var(--dx-r-sm);
  object-fit: contain;
  background: var(--dx-surface-sunk);
}

.dx-cam-result__name {
  margin: 0;
  color: var(--dx-ink);
  font-size: var(--dx-t-h3);
}

.dx-cam-result__meta {
  margin: 0;
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-small);
}

.dx-cam-result__why-label {
  margin: var(--dx-s-2) 0 var(--dx-s-1);
  color: var(--dx-ink);
  font-size: var(--dx-t-small);
  font-weight: 800;
}

.dx-cam-result__why {
  margin: 0;
  padding-left: var(--dx-s-5);
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-small);
  line-height: var(--dx-lh-snug);
}

.dx-cam-result__caution {
  padding: var(--dx-s-2) var(--dx-s-3);
  border-radius: var(--dx-r-sm);
  background: var(--dx-sunny-wash);
}

.dx-cam-result__caution .dx-cam-result__why {
  color: var(--dx-sunny-ink);
}

.dx-cam-result__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--dx-s-2);
}

.dx-cam-result__actions .dx-btn {
  min-height: 44px;
}

/* ---- Manual fallback disclosure ---------------------------------------- */
.dx-identify-manual {
  margin-bottom: var(--dx-s-6);
  padding: var(--dx-s-3) var(--dx-s-4);
  border: 1px solid var(--dx-line);
  border-radius: var(--dx-r-md);
  background: var(--dx-surface);
}

.dx-identify-manual__summary {
  min-height: 44px;
  display: flex;
  align-items: center;
  color: var(--dx-accent-ink);
  font-size: var(--dx-t-body);
  font-weight: 800;
  cursor: pointer;
}

.dx-identify-manual__summary:focus-visible {
  outline: var(--dx-focus);
  outline-offset: var(--dx-focus-offset);
}

.dx-identify-manual__lede {
  margin: 0 0 var(--dx-s-3);
  color: var(--dx-ink-soft);
  font-size: var(--dx-t-small);
}

/* ---- 390px and up: the viewfinder can afford to be squarer -------------- */
@media (min-width: 390px) {
  .dx-cam__viewfinder { aspect-ratio: 4 / 5; }
  .dx-cam-strip__item,
  .dx-cam-strip__item img { flex-basis: 96px; width: 96px; }
}

/* ---- Tablet and desktop: camera left, results right --------------------
 * Below 900px the two stack, which is the same DOM order, so nothing about
 * the reading sequence changes with width.
 */
@media (min-width: 900px) {
  .dx-cam {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    align-items: start;
    gap: var(--dx-s-6);
  }

  .dx-cam__head { grid-column: 1 / -1; }
  .dx-cam__resume { grid-column: 1 / -1; }
  .dx-cam__viewfinder { aspect-ratio: 1 / 1; }
  .dx-cam__result { position: sticky; top: calc(var(--dx-header-h) + var(--dx-s-4)); }
  .dx-cam-result__card { grid-template-columns: 140px minmax(0, 1fr); }
  .dx-cam-result__image { max-height: 160px; }
}

/* The bottom bar is display:none from 1100px (tokens.css zeroes its whole
 * reserve there), so the pinned shutter tray has nothing to clear and goes
 * back into normal flow rather than hovering over the desktop preview. */
@media (min-width: 1100px) {
  .dx-cam__controls {
    position: static;
    background: none;
  }
}

@media (min-width: 1280px) {
  .dx-cam { gap: var(--dx-s-7); }
  .dx-cam__viewfinder { aspect-ratio: 4 / 3; }
}

@media (prefers-reduced-motion: no-preference) {
  .dx-cam__shutter,
  .dx-cam-strip__retake,
  .dx-cam__file {
    transition: transform var(--dx-dur) var(--dx-ease), box-shadow var(--dx-dur) var(--dx-ease);
  }

  .dx-cam__shutter:not([disabled]):active { transform: scale(0.94); }
}

@media (prefers-contrast: more) {
  .dx-cam__frame { border-color: #ffffff; border-style: solid; }
  .dx-cam-result__card { border-color: var(--dx-ink); }
}
