@import url('/assets/fonts/fonts.css');

/* ===========================================================================
   iTailor.

   One skin: warm parchment and bottle green, carried over from the fashion
   pages of the parent site so the product looks like the thing that was sold.
   Every colour goes through a custom property, so the palette is in one place.

   Hand-written rather than generated: there is no build step in this project,
   and the stylesheet a browser receives is the one in the repository.
   =========================================================================== */

:root {
  /* --- palette -------------------------------------------------------- */
  --f-bg: #f3efe6;
  --f-bg-elev: #fffdf8;
  --f-bg-sunk: #e9e3d6;
  --f-ink: #16130d;
  --f-ink-2: #4b443a;
  --f-ink-3: #8b8174;
  --f-line: rgba(22, 19, 13, 0.14);
  --f-line-strong: rgba(22, 19, 13, 0.42);
  --f-accent: #1e5c47;
  --f-accent-soft: rgba(30, 92, 71, 0.1);
  --f-accent-ink: #f7f4ed;
  --f-gold: #8a6413;
  --f-shadow: 0 24px 60px rgba(60, 48, 26, 0.16);
  --f-shadow-sm: 0 2px 8px rgba(60, 48, 26, 0.08);

  /* Confidence bands. Not a traffic light: the low band is outlined rather
     than filled, because a wall of red on a first capture reads as failure
     when it usually means "check these three with a tape". */
  --f-high: #1e5c47;
  --f-high-soft: rgba(30, 92, 71, 0.12);
  --f-medium: #8a6413;
  --f-medium-soft: rgba(138, 100, 19, 0.13);
  --f-low: #9a3412;
  --f-low-soft: rgba(154, 52, 18, 0.09);

  /* --- type ----------------------------------------------------------- */
  --f-sans: 'Inter', system-ui, -apple-system, 'Segoe UI', sans-serif;
  --f-grotesk: 'Archivo', 'Inter', system-ui, sans-serif;
  --f-serif: 'Newsreader', Georgia, 'Times New Roman', serif;
  --f-mono: 'JetBrains Mono', ui-monospace, 'Cascadia Mono', Consolas, monospace;

  --f-radius: 0.75rem;
  --f-radius-sm: 0.5rem;
}

/* --------------------------------------------------------------------------
   Reset — only what is actually needed.
   -------------------------------------------------------------------------- */

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

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

body {
  margin: 0;
  /*
   * `dvh`, not `vh`.
   *
   * On a phone `100vh` is the viewport with the browser chrome *hidden* — a
   * height the page does not actually have while the address bar is showing.
   * Every full-height page therefore overflows by the height of that bar on
   * first paint, which is what produces the small unexplained scroll on a page
   * that plainly fits, and it shifts as the bar hides and returns on scroll.
   * `dvh` tracks the real viewport. The fallback line keeps browsers without it
   * on the old behaviour rather than on nothing.
   */
  min-height: 100vh;
  min-height: 100dvh;
  background: var(--f-bg);
  color: var(--f-ink);
  font-family: var(--f-sans);
  font-size: 16px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  overflow-x: hidden;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd {
  margin: 0;
}

ul, ol {
  margin: 0;
  padding: 0;
  list-style: none;
}

img, svg, video {
  max-width: 100%;
  display: block;
}

a {
  color: inherit;
  text-decoration-color: var(--f-line-strong);
  text-underline-offset: 0.2em;
}

a:hover {
  text-decoration-color: currentColor;
}

::selection {
  background: var(--f-accent);
  color: var(--f-accent-ink);
}

/* A single, consistent focus ring. Never removed — a representative working
   through a twenty-four field review form with the keyboard has to be able to
   see where they are. */
:focus-visible {
  outline: 2px solid var(--f-accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* --------------------------------------------------------------------------
   Layout
   -------------------------------------------------------------------------- */

.f-container {
  max-width: 78rem;
  margin-inline: auto;
  padding-inline: 1.25rem;
}

.f-container--wide {
  max-width: 110rem;
}

@media (min-width: 768px) {
  .f-container {
    padding-inline: 2.5rem;
  }
}

.f-section {
  padding-block: 3.5rem;
  border-top: 1px solid var(--f-line);
  background: var(--f-bg);
}

@media (min-width: 768px) {
  .f-section {
    padding-block: 5.5rem;
  }
}

.f-section--sunk {
  background: var(--f-bg-sunk);
}

.f-section--flush {
  border-top: 0;
}

.f-stack > * + * {
  margin-top: var(--gap, 1rem);
}

.f-row {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.f-row--between {
  justify-content: space-between;
}

.f-grid {
  display: grid;
  gap: 1.25rem;
}

@media (min-width: 640px) {
  .f-grid--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (min-width: 900px) {
  .f-grid--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  .f-grid--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
}

.f-spacer { flex: 1 1 auto; }

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

.f-eyebrow {
  font-family: var(--f-mono);
  font-size: 0.625rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: var(--f-ink-3);
  margin: 0;
}

.f-eyebrow--accent { color: var(--f-accent); }

.f-h1 {
  font-family: var(--f-grotesk);
  font-weight: 800;
  line-height: 1.0;
  letter-spacing: -0.025em;
  font-size: clamp(2rem, 5vw, 4rem);
}

.f-h2 {
  font-family: var(--f-grotesk);
  font-weight: 800;
  line-height: 1.06;
  letter-spacing: -0.02em;
  font-size: clamp(1.6rem, 3.4vw, 2.6rem);
}

.f-h3 {
  font-family: var(--f-grotesk);
  font-weight: 700;
  line-height: 1.2;
  letter-spacing: -0.012em;
  font-size: 1.1rem;
}

.f-lede {
  font-family: var(--f-serif);
  font-style: italic;
  font-size: clamp(1.05rem, 1.8vw, 1.3rem);
  color: var(--f-accent);
  line-height: 1.45;
}

.f-prose {
  font-family: var(--f-serif);
  font-size: 1.0625rem;
  line-height: 1.7;
  color: var(--f-ink-2);
  max-width: 62ch;
}

.f-prose + .f-prose { margin-top: 1rem; }

.f-muted { color: var(--f-ink-2); }
.f-faint { color: var(--f-ink-3); }
.f-small { font-size: 0.8125rem; }
.f-tiny  { font-size: 0.75rem; }
.f-mono  { font-family: var(--f-mono); }
.f-num   { font-variant-numeric: tabular-nums; font-feature-settings: 'tnum' 1; }

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

/* --------------------------------------------------------------------------
   Surfaces
   -------------------------------------------------------------------------- */

/* Hairlines are box-shadow rings rather than borders throughout, so a card
   never shifts by a pixel when it gains or loses one. */
.f-card {
  background: var(--f-bg-elev);
  border-radius: var(--f-radius);
  box-shadow: 0 0 0 1px var(--f-line);
  padding: 1.5rem;
}

.f-card--tight { padding: 1rem 1.15rem; }
.f-card--flush { padding: 0; overflow: hidden; }

.f-card--link {
  display: block;
  text-decoration: none;
  transition: transform 160ms ease, box-shadow 160ms ease;
}

.f-card--link:hover {
  transform: translateY(-2px);
  box-shadow: 0 0 0 1px var(--f-line-strong), var(--f-shadow-sm);
}

.f-panel {
  background: var(--f-bg-sunk);
  border-radius: var(--f-radius);
  padding: 1.25rem;
}

.f-divider {
  height: 1px;
  background: var(--f-line);
  border: 0;
  margin-block: 1.5rem;
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.f-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 0;
  border-radius: 999px;
  padding: 0.8rem 1.5rem;
  font-family: var(--f-grotesk);
  font-weight: 700;
  font-size: 0.875rem;
  line-height: 1;
  cursor: pointer;
  text-decoration: none;
  background: var(--f-accent);
  color: var(--f-accent-ink);
  transition: transform 140ms ease, opacity 140ms ease, box-shadow 140ms ease;
  white-space: nowrap;
}

.f-btn:hover { transform: translateY(-1px); }
.f-btn:active { transform: translateY(0); }

.f-btn[disabled],
.f-btn[aria-disabled='true'] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.f-btn--ghost {
  background: transparent;
  color: var(--f-ink);
  box-shadow: 0 0 0 1px var(--f-line-strong);
}

.f-btn--quiet {
  background: transparent;
  color: var(--f-ink-2);
  box-shadow: 0 0 0 1px var(--f-line);
}

.f-btn--danger {
  background: transparent;
  color: var(--f-low);
  box-shadow: 0 0 0 1px rgba(154, 52, 18, 0.35);
}

.f-btn--small {
  padding: 0.5rem 0.95rem;
  font-size: 0.78rem;
}

.f-btn--block { width: 100%; }

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

.f-field { display: block; }
.f-field + .f-field { margin-top: 1.15rem; }

.f-label {
  display: block;
  font-family: var(--f-mono);
  font-size: 0.625rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  color: var(--f-ink-3);
  margin-bottom: 0.45rem;
}

.f-input,
.f-select,
.f-textarea {
  width: 100%;
  font: inherit;
  color: var(--f-ink);
  background: var(--f-bg-elev);
  border: 0;
  border-radius: var(--f-radius-sm);
  box-shadow: 0 0 0 1px var(--f-line);
  padding: 0.7rem 0.85rem;
  transition: box-shadow 140ms ease;
}

.f-textarea { min-height: 6rem; resize: vertical; }

.f-select {
  appearance: none;
  background-image: linear-gradient(45deg, transparent 50%, var(--f-ink-3) 50%),
                    linear-gradient(135deg, var(--f-ink-3) 50%, transparent 50%);
  background-position: calc(100% - 1.1rem) 1.15rem, calc(100% - 0.75rem) 1.15rem;
  background-size: 0.35rem 0.35rem, 0.35rem 0.35rem;
  background-repeat: no-repeat;
  padding-right: 2.25rem;
}

.f-input:focus,
.f-select:focus,
.f-textarea:focus {
  outline: 0;
  box-shadow: 0 0 0 2px var(--f-accent);
}

.f-input[aria-invalid='true'],
.f-select[aria-invalid='true'],
.f-textarea[aria-invalid='true'] {
  box-shadow: 0 0 0 2px var(--f-low);
}

.f-hint {
  margin-top: 0.4rem;
  font-size: 0.8125rem;
  color: var(--f-ink-3);
}

.f-error {
  margin-top: 0.4rem;
  font-size: 0.8125rem;
  color: var(--f-low);
  font-weight: 500;
}

.f-check {
  display: flex;
  gap: 0.65rem;
  align-items: flex-start;
  cursor: pointer;
}

.f-check input {
  margin-top: 0.22rem;
  accent-color: var(--f-accent);
  width: 1rem;
  height: 1rem;
  flex: none;
}

/* --------------------------------------------------------------------------
   Chrome — the top bar
   -------------------------------------------------------------------------- */

.f-nav {
  position: sticky;
  top: 0;
  z-index: 50;
  background: color-mix(in srgb, var(--f-bg) 92%, transparent);
  backdrop-filter: blur(16px);
  border-bottom: 1px solid var(--f-line);
}

.f-nav__inner {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  height: 4rem;
}

.f-brand {
  display: inline-flex;
  align-items: baseline;
  gap: 0.5rem;
  font-family: var(--f-grotesk);
  font-weight: 800;
  letter-spacing: -0.02em;
  font-size: 1.05rem;
  text-decoration: none;
}

.f-brand__mark { color: var(--f-accent); }

.f-navlinks {
  display: none;
  gap: 1.35rem;
  align-items: center;
}

@media (min-width: 860px) {
  .f-navlinks { display: flex; }
}

.f-navlink {
  font-size: 0.875rem;
  color: var(--f-ink-2);
  text-decoration: none;
  padding-block: 0.25rem;
  border-bottom: 2px solid transparent;
}

.f-navlink:hover { color: var(--f-ink); }

.f-navlink[aria-current='page'] {
  color: var(--f-ink);
  border-bottom-color: var(--f-accent);
  font-weight: 500;
}

.f-footer {
  border-top: 1px solid var(--f-line);
  padding-block: 2.5rem;
  color: var(--f-ink-3);
  font-size: 0.8125rem;
}

/* --------------------------------------------------------------------------
   Flash messages
   -------------------------------------------------------------------------- */

.f-flash {
  border-radius: var(--f-radius-sm);
  padding: 0.85rem 1.1rem;
  font-size: 0.9rem;
  box-shadow: 0 0 0 1px var(--f-line);
  background: var(--f-bg-elev);
}

.f-flash + .f-flash { margin-top: 0.6rem; }

.f-flash--success {
  background: var(--f-high-soft);
  box-shadow: 0 0 0 1px rgba(30, 92, 71, 0.3);
  color: #123a2c;
}

.f-flash--error {
  background: var(--f-low-soft);
  box-shadow: 0 0 0 1px rgba(154, 52, 18, 0.3);
  color: #7a2a0e;
}

.f-flash--info {
  background: var(--f-medium-soft);
  box-shadow: 0 0 0 1px rgba(138, 100, 19, 0.28);
  color: #6b4d0f;
}

/* --------------------------------------------------------------------------
   Tables
   -------------------------------------------------------------------------- */

.f-tablewrap { overflow-x: auto; }

.f-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}

.f-table th {
  text-align: left;
  font-family: var(--f-mono);
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--f-ink-3);
  font-weight: 400;
  padding: 0.6rem 0.85rem;
  border-bottom: 1px solid var(--f-line);
  white-space: nowrap;
}

.f-table td {
  padding: 0.7rem 0.85rem;
  border-bottom: 1px solid var(--f-line);
  vertical-align: middle;
}

.f-table tbody tr:last-child td { border-bottom: 0; }
.f-table tbody tr:hover { background: color-mix(in srgb, var(--f-bg-sunk) 55%, transparent); }

/* --------------------------------------------------------------------------
   Badges and confidence
   -------------------------------------------------------------------------- */

.f-badge {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  border-radius: 999px;
  padding: 0.2rem 0.6rem;
  font-family: var(--f-mono);
  font-size: 0.625rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  box-shadow: 0 0 0 1px var(--f-line);
  white-space: nowrap;
}

.f-badge--high   { background: var(--f-high-soft);   color: var(--f-high);   box-shadow: 0 0 0 1px rgba(30, 92, 71, 0.28); }
.f-badge--medium { background: var(--f-medium-soft); color: var(--f-medium); box-shadow: 0 0 0 1px rgba(138, 100, 19, 0.28); }
.f-badge--low    { background: transparent;          color: var(--f-low);    box-shadow: 0 0 0 1px rgba(154, 52, 18, 0.42); }
.f-badge--accent { background: var(--f-accent); color: var(--f-accent-ink); box-shadow: none; }

.f-meter {
  --value: 0;
  width: 3.5rem;
  height: 0.3rem;
  border-radius: 999px;
  background: var(--f-line);
  overflow: hidden;
  flex: none;
}

.f-meter::after {
  content: '';
  display: block;
  height: 100%;
  width: calc(var(--value) * 100%);
  background: currentColor;
  border-radius: inherit;
}

.f-meter--high   { color: var(--f-high); }
.f-meter--medium { color: var(--f-medium); }
.f-meter--low    { color: var(--f-low); }

/* --------------------------------------------------------------------------
   Stat tiles
   -------------------------------------------------------------------------- */

.f-stat__value {
  font-family: var(--f-grotesk);
  font-weight: 800;
  font-size: clamp(1.7rem, 3vw, 2.3rem);
  line-height: 1;
  letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums;
}

.f-stat__label {
  margin-top: 0.5rem;
  font-family: var(--f-mono);
  font-size: 0.625rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: var(--f-ink-3);
}

/* --------------------------------------------------------------------------
   Empty states
   -------------------------------------------------------------------------- */

.f-empty {
  text-align: center;
  padding: 3.5rem 1.5rem;
  color: var(--f-ink-3);
}

.f-empty__title {
  font-family: var(--f-grotesk);
  font-weight: 700;
  font-size: 1.05rem;
  color: var(--f-ink);
  margin-bottom: 0.4rem;
}

/* --------------------------------------------------------------------------
   Print — the measurement docket
   -------------------------------------------------------------------------- */

@media print {
  :root { --f-bg: #fff; --f-bg-elev: #fff; --f-bg-sunk: #fff; }

  .f-nav, .f-footer, .f-noprint { display: none !important; }

  body { background: #fff; font-size: 11pt; }

  .f-card {
    box-shadow: none;
    border: 1px solid #bbb;
    break-inside: avoid;
  }

  .f-table th, .f-table td { border-bottom: 1px solid #ccc; }

  a[href]::after { content: ''; }
}

/* --------------------------------------------------------------------------
   The camera stage

   Dark, edge to edge, and deliberately unlike the rest of the application. The
   person using this is standing several feet away reading it at a glance, not
   sitting at a desk — so contrast beats elegance, and the video gets the room.
   -------------------------------------------------------------------------- */

.f-cam {
  width: 100vw;
  max-width: 100vw;
  height: 100dvh;
  max-height: 100dvh;
  margin: 0;
  padding: 0;
  border: 0;
  background: #0c0b09;
  color: #f6f2ea;
  display: grid;
  grid-template-rows: 1fr auto;
  overflow: hidden;
}

.f-cam::backdrop { background: rgba(6, 5, 4, 0.92); }

.f-cam__frame {
  position: relative;
  overflow: hidden;
  background: #000;
  display: grid;
  place-items: center;
  min-height: 0;
}

/* The picture fills the frame, and `data-fit` decides how.

   `contain` alone was wrong on a phone. Plenty of cameras hand back a landscape
   stream however firmly portrait is asked for, and a 16:9 picture letterboxed
   into a 9:19.5 frame uses under a third of it — the rest is black bar above and
   below, the body lands in a strip across the middle, and the person is left
   trying to fill an outline drawn at a scale the picture cannot reach.

   It costs accuracy as well as dignity: the silhouette is read from a mask about
   256 samples across the *whole* frame, so a body squeezed into the middle of a
   letterboxed landscape picture is described by half as many samples as one in a
   portrait picture, and every girth is a width measured off it.

   So `camera.js` compares the two aspects each time either can have changed and
   crops rather than letterboxes whenever the picture is the wider of the two —
   which keeps the full height, the axis a standing body needs. `contain` stays
   for the other direction, where cropping would take the head and the feet. */
.f-cam__video,
.f-cam__overlay,
.f-cam__still {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* Video and guide are one picture in two layers: they share an aspect ratio, so
   one rule keeps them registered to the pixel. Changing this selector without
   the other would slide the outline off the body. */
.f-cam__frame[data-fit='cover'] .f-cam__video,
.f-cam__frame[data-fit='cover'] .f-cam__overlay {
  object-fit: cover;
}

/* The frame that was just taken, held over the live view while it is read.

   `contain`, always, and no longer part of the rule above. The still used to be
   the whole video frame and therefore the same shape as it, so one `data-fit`
   governed all three layers. The shutter now crops the still to the body, which
   makes it a different shape from the video — and `cover` on a still narrower
   than the frame scales it to fill the width and takes the top and bottom off.
   That is the head and the feet: the two ends of the one measurement everything
   else is scaled against, cropped out of the picture used to confirm it. */
.f-cam__still { background: #000; object-fit: contain; }
.f-cam__still[data-mirrored='true'] { transform: scaleX(-1); }

/* Only the display is mirrored. Landmarks, the guide and the captured still
   all stay in the camera's own coordinates — see camera.js. */
.f-cam__frame[data-mirrored='true'] .f-cam__video,
.f-cam__frame[data-mirrored='true'] .f-cam__overlay {
  transform: scaleX(-1);
}

/* The shutter blink. Pure feedback — it is the only moment in the flow that
   needs no words, and a person across the room sees it without reading. */
.f-cam__fx {
  position: absolute;
  inset: 0;
  background: #fff;
  opacity: 0;
  pointer-events: none;
}

/* Two overlaid animations: the frame goes white to the edges, and a hard ring
   collapses inwards behind it. One of them is legible on a phone held at arm's
   length, one from three metres away, and it is not always the same one. */
.f-cam__fx.is-firing { animation: f-cam-shutter 560ms cubic-bezier(0.2, 0, 0.1, 1); }

@keyframes f-cam-shutter {
  0%   { opacity: 0;    box-shadow: inset 0 0 0 0 rgba(120, 225, 170, 0); }
  6%   { opacity: 1;    box-shadow: inset 0 0 0 1.5rem rgba(120, 225, 170, 0.95); }
  38%  { opacity: 0.28; box-shadow: inset 0 0 0 0.75rem rgba(120, 225, 170, 0.8); }
  100% { opacity: 0;    box-shadow: inset 0 0 0 0 rgba(120, 225, 170, 0); }
}

@media (prefers-reduced-motion: reduce) {
  .f-cam__fx.is-firing { animation-duration: 220ms; }
}

/* --- the between-photographs card ------------------------------------------
   A screen rather than a banner. This is the only point in the flow where the
   person has to do something between shots, and they are reading it from across
   the room, so it takes the whole frame and waits. */

.f-cam__notice {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  padding: 1.5rem;
  background: rgba(8, 7, 6, 0.72);
  backdrop-filter: blur(2px);
  z-index: 2;
}

.f-cam__notice[hidden] { display: none; }

.f-cam__notice-card {
  max-width: min(34rem, 92%);
  text-align: center;
  padding: 1.75rem 1.75rem 1.5rem;
  border-radius: 1rem;
  background: rgba(20, 18, 15, 0.95);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16), 0 1.5rem 4rem rgba(0, 0, 0, 0.5);
}

.f-cam__notice[data-tone='good'] .f-cam__notice-card {
  box-shadow: 0 0 0 1px rgba(120, 225, 170, 0.55), 0 1.5rem 4rem rgba(0, 0, 0, 0.5);
}

/* The turn card sits low against a thinner scrim, because the outline for the
   next view is drawn behind it and half the point is to see the shape you are
   about to have to make. */
.f-cam__notice[data-tone='turn'] {
  align-items: end;
  background: linear-gradient(to top, rgba(8, 7, 6, 0.9) 30%, rgba(8, 7, 6, 0.25));
  backdrop-filter: none;
}

.f-cam__notice[data-tone='turn'] .f-cam__notice-card {
  box-shadow: 0 0 0 1px rgba(240, 190, 90, 0.55), 0 1.5rem 4rem rgba(0, 0, 0, 0.5);
}

.f-cam__notice-eyebrow {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 0.625rem;
  letter-spacing: 0.28em;
  text-transform: uppercase;
  color: rgba(246, 242, 234, 0.55);
}

.f-cam__notice[data-tone='good'] .f-cam__notice-eyebrow { color: rgb(150, 235, 190); }
.f-cam__notice[data-tone='turn'] .f-cam__notice-eyebrow { color: rgb(240, 200, 120); }

.f-cam__notice-headline {
  margin: 0.6rem 0 0;
  font-size: clamp(1.35rem, 4.5vw, 2rem);
  font-weight: 700;
  line-height: 1.15;
}

.f-cam__notice-lead {
  margin: 0.7rem 0 0;
  font-size: 0.95rem;
  line-height: 1.5;
  color: rgba(246, 242, 234, 0.74);
}

.f-cam__notice-count {
  margin: 0.9rem 0 0;
  font-size: clamp(2.5rem, 9vh, 4rem);
  font-weight: 800;
  line-height: 1;
  font-variant-numeric: tabular-nums;
}

.f-cam__notice-count[hidden] { display: none; }

.f-cam__notice-card .f-btn { margin-top: 1rem; }
.f-cam__notice-card .f-btn[hidden] { display: none; }

.f-cam__spinner {
  width: 1.5rem;
  height: 1.5rem;
  margin: 1.1rem auto 0;
  border-radius: 999px;
  border: 2px solid rgba(255, 255, 255, 0.2);
  border-top-color: rgba(255, 255, 255, 0.85);
  animation: f-cam-spin 800ms linear infinite;
}

.f-cam__spinner[hidden] { display: none; }

@keyframes f-cam-spin { to { transform: rotate(360deg); } }

/* --- the instruction, on the picture ---------------------------------------
   The panel below is read by somebody standing at the phone. This is read by
   the person being photographed, from three metres, over their own shoulder in
   a mirrored preview — so it is set at roughly four times the panel's size,
   given a scrim rather than a shadow so it survives a bright wall behind it,
   and pinned to the top of the frame, which is the only band a standing figure
   does not occupy. */

.f-cam__coach {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  padding: calc(0.6rem + env(safe-area-inset-top)) 1rem 1.4rem;
  text-align: center;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(8, 7, 6, 0.86) 30%, rgba(8, 7, 6, 0));
}

.f-cam__coach[hidden] { display: none; }

.f-cam__coach-line {
  margin: 0;
  font-size: clamp(1.25rem, 5.4vw, 1.9rem);
  font-weight: 700;
  line-height: 1.2;
  text-wrap: balance;
  color: #fff;
  text-shadow: 0 0.08em 0.4em rgba(0, 0, 0, 0.8);
}

.f-cam__coach[data-tone='ready'] .f-cam__coach-line { color: rgb(150, 235, 190); }
.f-cam__coach[data-tone='close'] .f-cam__coach-line { color: rgb(247, 208, 132); }

.f-cam__coach-tally {
  margin: 0.3rem 0 0;
  font-family: var(--f-mono);
  font-size: clamp(0.8rem, 3.2vw, 1rem);
  letter-spacing: 0.08em;
  color: rgba(246, 242, 234, 0.78);
  text-shadow: 0 0.08em 0.4em rgba(0, 0, 0, 0.8);
}

.f-cam__coach-tally[hidden] { display: none; }

/* --- the spirit level ------------------------------------------------------
   A phone leaning back on a table photographs a standing body along a diagonal,
   which shortens the recovered stature and moves every derived girth with it.
   Nothing in the picture shows that, and nobody standing across the room can be
   told it in words they will act on — so it is drawn as the one instrument
   everybody can already read. */

.f-cam__level {
  position: absolute;
  left: 50%;
  /* Below the coach line and above the card that explains it — the prop-up
     screen is a notice, and a bubble hidden behind the words describing it is
     the one arrangement that cannot work. */
  top: 22%;
  z-index: 3;
  transform: translateX(-50%);
  display: grid;
  justify-items: center;
  gap: 0.5rem;
  pointer-events: none;
}

.f-cam__level[hidden] { display: none; }

.f-cam__level-well {
  position: relative;
  width: 7rem;
  height: 7rem;
  border-radius: 999px;
  background: rgba(12, 11, 9, 0.72);
  box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.35);
}

/* The target ring the bubble has to sit inside — the tolerance, drawn. */
.f-cam__level-well::after {
  content: '';
  position: absolute;
  inset: 38%;
  border-radius: 999px;
  box-shadow: 0 0 0 2px rgba(255, 255, 255, 0.45);
}

.f-cam__level-bubble {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 1.6rem;
  height: 1.6rem;
  border-radius: 999px;
  background: rgb(247, 208, 132);
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.45);
  transition: transform 120ms linear, background-color 160ms linear;
}

.f-cam__level[data-ok='true'] .f-cam__level-bubble { background: rgb(120, 225, 170); }
.f-cam__level[data-ok='true'] .f-cam__level-well { box-shadow: inset 0 0 0 2px rgba(120, 225, 170, 0.9); }

.f-cam__level-read {
  margin: 0;
  font-family: var(--f-mono);
  font-size: 0.8rem;
  letter-spacing: 0.06em;
  color: rgba(246, 242, 234, 0.9);
  text-shadow: 0 0.08em 0.4em rgba(0, 0, 0, 0.85);
}

/* The ring is the fastest signal there is — no words, no focus needed, visible
   in peripheral vision while somebody is holding a pose and looking straight
   ahead. Thick enough to survive being seen from across a room. */
.f-cam__frame[data-tone='ready']  { box-shadow: inset 0 0 0 8px rgba(120, 225, 170, 0.85); }
.f-cam__frame[data-tone='close']  { box-shadow: inset 0 0 0 8px rgba(240, 190, 90, 0.6); }

/* The countdown numeral.

   Positioned outright rather than left to find its own static position. It used
   to be `position: absolute` with no offsets at all, which makes its placement
   depend on how the browser resolves the static position of an abspos child of a
   grid container that is centring its items — a corner of the specification that
   is genuinely ambiguous between engines, and one whose failure mode is a numeral
   painted somewhere nobody is looking. It was reported missing on a run where the
   diary proves it was set: `beat` at 32.0, 33.0 and 34.0 seconds, one second
   apart, on a screen the person says never showed a number.

   Everything it needs is now stated: it fills the frame, centres its own text,
   sits above the video and the guide, and is opaque on its own account rather
   than by virtue of an animation having run. The animation scales it and nothing
   else, so a numeral is visible whether or not the animation is honoured, and
   `prefers-reduced-motion` cannot take it away. */
.f-cam__count {
  /* Fixed, not absolute.

     The diary reported this numeral's box arriving at a viewport top of −100,
     −263 and −291 pixels on three different captures: the camera frame extends
     above the visible viewport on a phone, because `100dvh` and what is actually
     on screen part company while the address bar is animating. Centred in the
     frame, the numeral was therefore centred on a rectangle whose top edge was
     off the screen, and it drifted upward by however far. A dialog is in the top
     layer, so `fixed` is measured against the viewport itself — which is where
     the person's eyes are. */
  position: fixed;
  inset: 0;
  z-index: 5;
  display: grid;
  place-items: center;
  margin: 0;
  font-size: clamp(5rem, 22vh, 12rem);
  font-weight: 800;
  line-height: 1;
  opacity: 1;
  color: rgba(255, 255, 255, 0.96);
  text-shadow: 0 0.15em 0.5em rgba(0, 0, 0, 0.75), 0 0 1.5rem rgba(0, 0, 0, 0.6);
  pointer-events: none;
}

.f-cam__count[hidden] { display: none; }

/* A ring closing around the numeral, one second per beat.

   The numeral answers "how long"; the ring answers "is this thing running", from
   the far side of a room where a single glyph is small and a moving edge is not.
   It is drawn with a conic gradient masked to a ring, so it costs no element and
   no script — the animation drives one custom property. */
.f-cam__count::before {
  content: '';
  position: absolute;
  width: min(46vw, 34vh);
  aspect-ratio: 1;
  border-radius: 50%;
  background: conic-gradient(rgba(120, 225, 170, 0.95) var(--sweep, 0turn), rgba(255, 255, 255, 0.14) 0);
  mask: radial-gradient(closest-side, transparent 78%, #000 79%);
  -webkit-mask: radial-gradient(closest-side, transparent 78%, #000 79%);
  animation: f-cam-sweep 1s linear;
}

@keyframes f-cam-sweep {
  from { --sweep: 1turn; }
  to   { --sweep: 0turn; }
}

@property --sweep {
  syntax: '<angle>';
  inherits: false;
  initial-value: 0turn;
}

/* One beat per numeral, restarted on the change rather than on the frame — see
   `stage.setCountdown`. The last second is the one that needed it: a numeral
   that does not move is indistinguishable from a camera that has stopped. */
.f-cam__count.is-ticking { animation: f-cam-tick 900ms cubic-bezier(0.2, 0, 0.2, 1); }

/* Scale only. Opacity belongs to the element, not to the animation — a numeral
   that is only visible while an animation is running is a numeral that
   disappears the moment anything declines to run it. */
@keyframes f-cam-tick {
  0%   { transform: scale(1.35); }
  18%  { transform: scale(1); }
  100% { transform: scale(0.96); }
}

@media (prefers-reduced-motion: reduce) {
  .f-cam__count.is-ticking { animation: none; }
}

.f-cam__banner {
  position: absolute;
  left: 50%;
  bottom: 1.25rem;
  transform: translateX(-50%);
  max-width: min(38rem, 88%);
  padding: 0.7rem 1.1rem;
  border-radius: 999px;
  font-size: 0.9rem;
  text-align: center;
  background: rgba(20, 18, 15, 0.92);
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.16);
}

.f-cam__banner[data-tone='good'] {
  background: rgba(20, 64, 47, 0.94);
  box-shadow: 0 0 0 1px rgba(120, 225, 170, 0.5);
}

/* On a phone the panel is a strip of controls and nothing else.

   It used to take up to 42dvh, and everything in it — the lead, the instruction,
   the seven check pills — was written for somebody within arm's reach of the
   screen. In the flow this product exists for there is nobody there: the phone
   is propped on a table and the only reader is three metres away. So on a small
   screen that space goes to the picture, where it does measurable good — a body
   photographed across 85% of the frame instead of 58% is a body the pose model
   and the silhouette both read more finely — and the words move onto the frame
   at a size that carries (see `.f-cam__coach`).

   From 48rem up, which is a laptop with a representative sitting at it, the full
   panel comes back exactly as it was. */
.f-cam__panel {
  padding: 0.6rem 0.9rem calc(0.6rem + env(safe-area-inset-bottom));
  background: #141210;
  box-shadow: 0 -1px 0 rgba(255, 255, 255, 0.12);
}

.f-cam__panel .f-cam__lead,
.f-cam__panel .f-cam__instruction,
.f-cam__panel .f-cam__checks { display: none; }

.f-cam__controls {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

/* Thumb-sized, because these are pressed standing up and one-handed. */
.f-cam__controls .f-btn { min-height: 2.75rem; }

.f-cam .f-btn--ghost[data-on='true'] {
  box-shadow: 0 0 0 1px rgba(120, 225, 170, 0.8);
  color: rgb(150, 235, 190);
}

.f-cam__controls .f-btn[hidden] { display: none; }

@media (min-width: 48rem) {
  .f-cam__panel {
    padding: 1rem 1.25rem calc(1rem + env(safe-area-inset-bottom));
    max-height: 42dvh;
    overflow-y: auto;
  }

  .f-cam__panel .f-cam__lead,
  .f-cam__panel .f-cam__instruction,
  .f-cam__panel .f-cam__checks { display: revert; }

  .f-cam__panel .f-cam__checks { display: flex; }
  .f-cam__controls { margin-top: 1rem; gap: 0.75rem; }

  /* The panel already carries the instruction at a desk. On the frame it only
     needs to be a heading, not a placard. */
  .f-cam__coach { padding-bottom: 2rem; }
  .f-cam__coach-line { font-size: 1.35rem; }
}

.f-cam__lead { margin: 0.35rem 0 0; font-size: 0.9rem; color: rgba(246, 242, 234, 0.72); }

/* The one line somebody actually reads from across the room. */
.f-cam__instruction {
  margin: 0.9rem 0 0;
  font-size: clamp(1.05rem, 2.6vw, 1.35rem);
  font-weight: 600;
  min-height: 1.6em;
}

.f-cam__checks {
  display: flex;
  flex-wrap: wrap;
  gap: 0.4rem;
  margin: 0.85rem 0 0;
  padding: 0;
  list-style: none;
}

.f-cam__check {
  font-size: 0.72rem;
  padding: 0.3rem 0.6rem 0.3rem 1.5rem;
  border-radius: 999px;
  position: relative;
  background: rgba(255, 255, 255, 0.07);
  color: rgba(246, 242, 234, 0.62);
}

.f-cam__check::before {
  content: '·';
  position: absolute;
  left: 0.6rem;
  font-weight: 700;
}

.f-cam__check[data-ok='true'] {
  background: rgba(120, 225, 170, 0.16);
  color: rgb(150, 235, 190);
}

.f-cam__check[data-ok='true']::before { content: '✓'; }

.f-cam .f-btn--quiet,
.f-cam .f-btn--ghost {
  color: #f6f2ea;
  box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.28);
  background: transparent;
}

/* Landscape on a phone leaves almost no height — give the video what there is
   and let the panel become a narrow column beside it. */
@media (orientation: landscape) and (max-height: 32rem) {
  .f-cam {
    grid-template-rows: 1fr;
    /* `auto` on a phone, where the panel is now a strip of buttons and sizing it
       to a fixed column would steal a fifth of the frame to show nothing. */
    grid-template-columns: 1fr auto;
  }

  .f-cam__panel { max-height: none; box-shadow: -1px 0 0 rgba(255, 255, 255, 0.12); }
  .f-cam__controls { flex-direction: column; align-items: stretch; height: 100%; justify-content: center; }
}

@media (orientation: landscape) and (max-height: 32rem) and (min-width: 48rem) {
  .f-cam { grid-template-columns: 1fr minmax(16rem, 22rem); }
  .f-cam__controls { flex-direction: row; height: auto; }
}

/* --------------------------------------------------------------------------
   Motion
   -------------------------------------------------------------------------- */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
}

/* The second-opinion card, arrived at by fragment straight after a check has
   run. A reload that lands silently is indistinguishable from a reload that did
   nothing, which is precisely how this control used to feel. */
#second-opinion:target {
  box-shadow: 0 0 0 2px var(--f-accent);
  animation: f-noticed 2.4s ease-out;
}

@keyframes f-noticed {
  0%, 40% { background: var(--f-high-soft); }
  100%    { background: var(--f-bg-elev); }
}

@media (prefers-reduced-motion: reduce) {
  #second-opinion:target { animation: none; }
}

/* The captured photograph, in the slot it will occupy on the page.

   `object-fit: contain` explicitly, and the canvas told to fill its box, rather
   than the intrinsic-size-plus-max-width dance that was here. That dance is
   correct on paper and fragile in practice: a canvas is a replaced element whose
   intrinsic size is its bitmap, and `max-height: 100%` only clamps it when the
   grid row it sits in has a definite height to be a percentage of. Where it did
   not resolve, the canvas rendered at full bitmap height and the box clipped it.

   That was invisible while the still was the whole camera frame, because a body
   filling half of it had empty room above and below to lose. The shutter now
   crops to the body, so the body *is* the picture — and the first thing the clip
   took was the feet, which is the end of the one span everything is scaled
   against. */
[data-slot] figure {
  position: relative;
  overflow: hidden;
  margin: 1rem 0 0;
  border-radius: var(--f-radius-sm);
  background: var(--f-bg-sunk);
  aspect-ratio: 3 / 4;
}

/* Absolutely positioned, which is the whole point of it.

   This was a grid item at `width: 100%; height: 100%`, and that is circular in a
   way that only bites once the canvas gets big. `place-items: center` leaves the
   grid column `auto`-sized, an auto column is sized from its content, and a
   percentage width against a column that has no definite size falls back to the
   element's *intrinsic* width — which for a captured still is now 740 real
   pixels. The column grew to 740, the card was 350 wide, `overflow: hidden` took
   the difference off every edge, and the photograph appeared cropped on all four
   sides.

   Against a positioned ancestor with a definite size there is no circularity to
   resolve: `inset: 0` gives the box the container's own dimensions, and
   `object-fit` does the fitting. */
[data-slot] figure canvas {
  position: absolute;
  inset: 0;
  display: block;
  width: 100%;
  height: 100%;
  object-fit: contain;
}

/* The one-time model download, made watchable.

   Twenty seconds with a percentage on it is a download; twenty seconds without
   one is a broken page, and from three metres away they look identical. The
   camera buttons are disabled until this clears, because the alternative — which
   is what used to happen — is somebody propping the phone up, walking across the
   room, and standing in front of a viewfinder that says "Finding you…" while
   nine megabytes arrive. */
.f-loading {
  margin-bottom: 1.5rem;
  padding: 1rem 1.15rem;
  border: 1px solid var(--f-line);
  border-radius: var(--f-radius-sm);
  background: var(--f-bg-sunk);
}

.f-loading[hidden] { display: none; }

.f-loading__row {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}

.f-loading__label { margin: 0; font-size: 0.9rem; font-weight: 500; }
.f-loading__value { margin: 0; font-size: 0.9rem; color: var(--f-accent); font-variant-numeric: tabular-nums; }

.f-loading__track {
  margin-top: 0.6rem;
  height: 0.4rem;
  border-radius: 999px;
  background: var(--f-line);
  overflow: hidden;
}

.f-loading__bar {
  display: block;
  height: 100%;
  width: var(--progress, 0%);
  border-radius: inherit;
  background: var(--f-accent);
  transition: width 220ms ease-out;
}

/* No length reported, so no percentage invented — it travels instead. A bar
   stalled at a made-up 60% is worse than one that is honestly just moving. */
.f-loading[data-indeterminate='true'] .f-loading__bar {
  width: 35%;
  animation: f-loading-slide 1.3s ease-in-out infinite;
}

@keyframes f-loading-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(300%); }
}

@media (prefers-reduced-motion: reduce) {
  .f-loading[data-indeterminate='true'] .f-loading__bar { animation: none; width: 100%; opacity: 0.5; }
}

.f-btn[data-loading='true'] { opacity: 0.55; cursor: progress; }
