/* ── Body scan (Figma 971:12850 → frames 246:15627…246:15737) ─────────────────
   Three camera screens plus a summary, bolted onto the end of the onboarding
   flow: intro → front scan → side scan → ready. Everything here rides on the
   .ob-* chrome from onboarding.css (grey frame, app bar, title block, Next bar);
   this file only adds the camera layer, the silhouette and the step pips.

   Each screen is ONE page with stages, driven by classes from assets/scan.js:
     (none)  the camera is a 344x240 card at the foot, dimmed 70%
     .wide   the card OPENS to the whole 360x700 screen; dim drops to 50%
     .aim    the body guide rises in
     .sharp  the veil lifts, the picture comes into focus, the top band arrives
     .done   the tick is drawn and Next opens

   The two things the design does that are worth naming, because they are what
   the first version got wrong:

   1. The card is a WINDOW, not a picture that grows. In every frame the photo
      sits at the same place — full screen size, bottom aligned — and the card
      is a hole cut over it. So the opening reveals more of a still composition
      instead of reframing a moving one, and nothing inside it has to move.
   2. Focus is a BACKDROP filter on a veil over the picture, not a filter on the
      picture. The <video> is never re-rasterised, which is what a blur on a
      playing clip costs on a phone. */

/* ── Camera layer ─────────────────────────────────────────────────────────── */
.scan-cam {
  position: absolute; left: 8px; right: 8px; bottom: 0; height: 240px;
  border-radius: 40px; overflow: hidden; z-index: 0;
  background: #2A2422;
  /* iOS will not clip a rasterised child to a parent's radius without one. */
  isolation: isolate;
  transition: left .8s cubic-bezier(.3,.75,.25,1), right .8s cubic-bezier(.3,.75,.25,1),
              height .8s cubic-bezier(.3,.75,.25,1);
  animation: scan-card-in .7s cubic-bezier(.2,.85,.25,1) both;
}
@keyframes scan-card-in { from { opacity: 0; transform: translateY(38px); } }
.scan-screen.wide .scan-cam { left: 0; right: 0; height: 100%; }

/* The composition the window looks at: the full screen, pinned to the foot, so
   it is in the same place whatever size the window is. Only its left edge moves
   — by the 8px the card is inset — and that is the whole of the reframing. */
.scan-inner {
  position: absolute; left: -8px; bottom: 0; width: 360px; height: 700px;
  transition: left .8s cubic-bezier(.3,.75,.25,1);
}
.scan-screen.wide .scan-inner { left: 0; }
.scan-shot {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; object-position: center;
}

/* Node 246:15629/15638/15691 — a flat black over the picture: 70% while the
   card is closed, 50% while it is aiming, gone once it is sharp. */
.scan-dim { position: absolute; inset: 0; background: #000; opacity: .7; transition: opacity .8s ease; }
.scan-screen.wide .scan-dim { opacity: .5; }
.scan-screen.sharp .scan-dim { opacity: 0; }

/* Node 246:15630/15640 — the out-of-focus state. A 7% warm wash carrying a
   backdrop blur, which is what makes the room soft; both go to nothing when the
   camera focuses. Kept at blur(0) rather than removed so the lift can tween. */
.scan-veil {
  position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background: linear-gradient(180deg, rgba(155,109,69,0.07) 9.27%, rgba(212,184,162,0.07) 88.02%);
  -webkit-backdrop-filter: blur(5.95px); backdrop-filter: blur(5.95px);
  transition: opacity .9s ease, -webkit-backdrop-filter .9s ease, backdrop-filter .9s ease;
}
.scan-screen.sharp .scan-veil {
  opacity: 0; -webkit-backdrop-filter: blur(0); backdrop-filter: blur(0);
}

/* Node 246:15692 — the band the clock and Skip sit on once the room is sharp
   and bright. 515 wide and 273 tall starting 117 above the frame, so what shows
   is its lower half: a 70% warm gradient over a 16px backdrop blur, which is
   both of the things asked for at once — the bar is transparent, and what is
   behind it is blurred AND carries the picture's own colour.
   Figma softens the band's bottom edge with a 22px layer blur; a mask does the
   same to both the fill and the backdrop blur, for none of the repaint cost of
   blurring a live layer every frame. */
.scan-band {
  position: absolute; left: 50%; margin-left: -257px; top: -117px;
  width: 515px; height: 273px; z-index: 2; pointer-events: none;
  background: linear-gradient(180deg, rgba(155,109,69,0.7) 43.13%, rgba(212,184,162,0.7) 88%);
  -webkit-backdrop-filter: blur(16.45px); backdrop-filter: blur(16.45px);
  -webkit-mask-image: linear-gradient(180deg, #000 86%, transparent 100%);
  mask-image: linear-gradient(180deg, #000 86%, transparent 100%);
  opacity: 0; transition: opacity .7s ease;
}
.scan-screen.sharp .scan-band { opacity: 1; }

/* ── Chrome over the camera ───────────────────────────────────────────────────
   The status bar is left alone: main.css already has it sticky at z-index 30, and
   giving it position:relative here would drop it behind the camera. */
.scan-screen > .ob-appbar,
.scan-screen > .ob-title { position: relative; z-index: 3; }
/* Grey until the camera is behind it, then the picture has to show through.
   statusbar-full.svg carries an opaque white plate, which .ob-screen knocks out by
   multiplying it against the status bar's own grey. With no grey left there is
   nothing to multiply against — the bar has z-index 30, so it is its own stacking
   context and the child's blend cannot reach the picture. Blending the BAR instead
   puts the camera in its backdrop: white drops away, the glyphs stay dark, which is
   the #3B3B3B clock the design puts on this band. */
.scan-screen.wide > .status-bar { background: transparent; mix-blend-mode: multiply; }
.scan-screen.wide .ob-appbar .back img { filter: brightness(0) invert(1); }
.scan-screen.wide .ob-appbar .skip { color: #fff; }

.scan-title { text-align: center; position: relative; z-index: 3; }
.scan-title h1 { transition: color .6s ease; }
.scan-screen.wide .scan-title h1 { color: #fff; }
.scan-screen.wide .scan-title p { color: rgba(255,255,255,.82); }
/* Three titles in one slot: the guide copy leaves as the count arrives, and the
   count leaves when there is something better to say than a finished number. */
.scan-title .t-scan,
.scan-title .t-done { position: absolute; left: 0; right: 0; top: 0; opacity: 0; transition: opacity .45s ease; }
.scan-title .t-guide { transition: opacity .45s ease; }
.scan-screen.sharp .scan-title .t-guide { opacity: 0; }
.scan-screen.sharp .scan-title .t-scan { opacity: 1; }
.scan-screen.done .scan-title .t-scan { opacity: 0; }
.scan-screen.done .scan-title .t-done { opacity: 1; }
/* The numeral is OffBit, like every other number in the app. */
.scan-pct { font-family: var(--font-offbit); letter-spacing: 1px; }

/* ── Body guide ───────────────────────────────────────────────────────────────
   Node 246:15641 / 246:15653: 129 wide (front) and 67 (side), from 36.5% of the
   700 box down to 4.85% off its foot, centred. Given as shares rather than the
   design's flat pixels because a phone screen is 100dvh, not the 700 the artboard
   is, and a fixed height would hang off the bottom. */
.scan-sil {
  position: absolute; left: 50%; top: 36.5%; height: 58.65%; width: auto; z-index: 2;
  pointer-events: none;
  opacity: 0; transform: translate(-50%, 14px);
  transition: opacity .7s ease, transform .7s cubic-bezier(.2,.8,.25,1);
}
.scan-screen.aim .scan-sil { opacity: 1; transform: translate(-50%, 0); }
.scan-screen.sharp .scan-sil { opacity: 0; transition-duration: .45s; }

/* The mark, dead centre of the picture — the shared .ob-check, the designer's
   brush drawn along its own stroke, so it is the same tick the sport cards and
   the setup steps use. */
.scan-done-mark { position: absolute; left: 50%; top: 50%; width: 0; height: 0; z-index: 3; }
.scan-done-mark .ob-check { width: 64px; height: 64px; left: -32px; top: -32px; }

/* ── Bottom bar: step pips on the left, Next on the right ─────────────────── */
/* The two ends of the screen read as ONE material.
   The top of this screen is the picture under the band, and assets/scan.js already
   works out what colour that lands on — it is what the OS status bar is told. The
   bar at the foot is given the same value, so the strip above the clock and the
   frame below the camera are literally the same colour instead of a warm band at
   one end and a grey slab at the other. --scan-tint arrives once the clip has
   decoded a frame; until then this is the grey it always was. */
.scan-bottom {
  background: var(--scan-tint, var(--color-neutral-100));
  transition: background-color .6s ease;
}
/* The concave fillet where the camera's rounded foot meets the flat-topped bar is
   painted with the bar's colour (main.css), so it has to follow it too. */
.phone:has(> .tabbar.scan-bottom) { --bar-fill: var(--scan-tint, var(--color-neutral-100)); }
/* Dark footage puts a dark bar at the foot, and Neutral/300 pips on it are a label
   nobody can read. scan.js measures the tone it just wrote and says which way. */
.phone.tint-dark .scan-bottom .scan-steps .st { color: rgba(255,255,255,.58); }
.phone.tint-dark .scan-bottom .scan-steps .st i { background: rgba(255,255,255,.42); color: #1B1B1B; }
/* The step being scanned keeps the red the whole app uses for "this one" — it only
   moves to the pip, because red type on a mid-warm ground is the one pairing here
   that stops being legible. Restated at this weight because the rules above are more
   specific than .scan-steps .st.on and would otherwise grey out the live step. */
.phone.tint-dark .scan-bottom .scan-steps .st.on { color: #fff; }
.phone.tint-dark .scan-bottom .scan-steps .st.on i { background: var(--color-red); color: #fff; }
/* Next is still shut while the count runs, and has to keep looking shut. */
.phone.tint-dark .scan-bottom .ob-next { color: rgba(255,255,255,.5); }
.phone.tint-dark .scan-bottom .ob-next img { filter: brightness(0) invert(1); opacity: .5; }
.phone.tint-dark .scan-bottom .ob-next.ready { color: #fff; }
.phone.tint-dark .scan-bottom .ob-next.ready img { opacity: 1; }
.phone.tint-dark .scan-bottom .home-indicator span { background: rgba(255,255,255,.55); }

.scan-bottom .sb-row { justify-content: space-between; }
.scan-steps { display: flex; align-items: center; gap: 10px; }
.scan-steps .st { display: flex; align-items: center; gap: 6px;
  font-family: var(--font-supreme); font-size: 14px; line-height: 1.4; letter-spacing: -0.42px;
  color: var(--color-neutral-300); }
.scan-steps .st i { flex: none; width: 20px; height: 20px; border-radius: 999px;
  background: var(--color-neutral-300); color: var(--color-neutral-50); font-style: normal;
  font-family: var(--font-supreme); font-size: 14px; letter-spacing: -0.42px;
  display: flex; align-items: center; justify-content: center; }
.scan-steps .st.on { color: var(--color-red); }
.scan-steps .st.on i { background: var(--color-red); }

/* ── Ready screen ─────────────────────────────────────────────────────────────
   The list is setup.css's .sc-list/.sc-row, unchanged — same white tray, 4px
   padding, grey rows. Only the entrance timing differs, so nothing is redefined
   here beyond the left-aligned title the rest of the .ob-* pages share. */
.scan-ready .ob-title { padding-bottom: 32px; }

/* Nothing scrolls on these screens, so the heading's fade-and-blur strip is a
   gradient over nothing — the same rule the other short pages get. */
.scan-screen .ob-title::after { content: none; }

@media (prefers-reduced-motion: reduce) {
  .scan-cam, .scan-inner, .scan-dim, .scan-veil, .scan-band, .scan-sil,
  .scan-title .t-scan, .scan-title .t-done, .scan-title .t-guide { transition: none; }
  .scan-cam { animation: none; }
}
