/* Vision Scribe Research — shared stylesheet.
 *
 * Five colours, chosen for contrast first and checked against WCAG AA. Bone
 * (#EBEBDF) is the page; Prussian blue (#14213D) is every deliberate mark on
 * it — nav brand, headings, primary buttons, focus rings — at 13.3:1 on bone.
 * Body copy is plain black and button labels are plain white, because at
 * those sizes a tinted near-black reads as a printing error rather than as a
 * choice.
 *
 * The other three have rules, because two of them fail as body text:
 *
 *   --charcoal  #58595F  5.81:1 on bone. Body copy where black is too hard.
 *   --airforce  #628395  3.36:1 on bone. FAILS body text. Hairlines, rules,
 *                        icon strokes and text at 24px+ only.
 *   --celadon   #ABD9BF  a panel colour, never a text colour. Prussian on it
 *                        is 10.2:1; charcoal on it is 4.46:1 and fails, so a
 *                        celadon panel carries Prussian text and nothing else.
 *
 * Light on purpose: the one thing on screen that must be trusted for its
 * colour is the camera preview, and dark chrome around it biases how bright
 * the speaker thinks their own lighting is.
 */

:root {
  --bone:       #EBEBDF;   /* page */
  --bone-deep:  #dedecf;   /* wells, chips, hovers */
  --surface:    #ffffff;   /* cards, panels, inputs */
  --line:       #d3d3c2;
  --line-soft:  #e2e2d4;

  --indigo:     #14213D;   /* primary — Prussian blue, 13.3:1 on bone */
  --indigo-dk:  #0c1527;   /* hover / pressed */
  --indigo-tint:#dde2ea;   /* active nav, focus ring */

  --airforce:   #628395;   /* rules and strokes only — 3.36:1 fails as text */
  --celadon:    #abd9bf;   /* panel ground; Prussian text on it, nothing else */
  --celadon-tint:#d6ebe0;  /* the same panel, quieter */

  --ink:        #000000;   /* body copy */
  --ink-soft:   #33333a;
  --charcoal:   #58595F;   /* body copy — 5.81:1 on bone, 6.98:1 on white */
  /* Was #6c6c66, which is 4.40:1 on bone and misses AA by a hair. The app
     puts muted text on bone all over (the progress line, every hint under a
     control), so it becomes charcoal: same role, one of the five palette
     colours, and it passes. */
  --muted:      #58595F;
  --on-indigo:  #ffffff;   /* text on an indigo field */

  --ok:         #2e6b4a;
  --ok-tint:    #deeae2;
  --bad:        #9e2b25;
  --bad-tint:   #f2dedd;
  --warn:       #74580f;   /* 5.40:1 on warn-tint */
  --warn-tint:  #efe7cd;

  --well:       #101a31;   /* behind video/canvas, before the first frame */
  --radius:     12px;
  --shadow:     0 1px 2px rgba(20, 33, 61, .06),
                0 8px 24px -12px rgba(20, 33, 61, .28);
}

* { box-sizing: border-box; }

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: "Segoe UI", system-ui, -apple-system, sans-serif;
  -webkit-font-smoothing: antialiased;
}

/* Anything the browser would otherwise paint its default blue. nav has its
   own rule below and wins on specificity. */
a { color: var(--indigo); text-underline-offset: 2px; }
a:hover { color: var(--indigo-dk); }

/* --- nav -------------------------------------------------------------- */
nav {
  display: flex; gap: 4px; padding: 10px 18px;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  align-items: center;
}
nav .brand {
  font-weight: 700; margin-right: 16px; color: var(--indigo);
  letter-spacing: -.015em;
}
nav a {
  color: var(--ink-soft); text-decoration: none; padding: 6px 12px;
  border-radius: 8px;
}
nav a:hover { background: var(--bone); }
nav a.active { background: var(--indigo-tint); color: var(--indigo); }
/* The profile-gated tabs before there is a profile. Explicit because `nav` is
   a flex container and a hidden flex item that kept a display would still take
   its place in the row. */
nav a[hidden] { display: none; }

main { max-width: 980px; margin: 0 auto; padding: 24px 16px 60px; }
h1 { font-size: 24px; margin: 8px 0 16px; font-weight: 600;
     letter-spacing: -.015em; color: var(--indigo); }

/* --- cards ------------------------------------------------------------ */
.cards { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 16px; }
.card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 14px 18px; min-width: 130px;
  box-shadow: var(--shadow);
}
.card .big { font-size: 26px; font-weight: 700; color: var(--indigo); }
/* A stat that carries a status says so in its own colour. Spelled out here
   because `.card .big` outranks a bare `.warn` on the same element. */
.card .big.ok { color: var(--ok); }
.card .big.warn { color: var(--warn); }
.card .big.bad { color: var(--bad); }
.card .label { color: var(--muted); font-size: 12px; margin-top: 2px; }

/* --- controls --------------------------------------------------------- */
button {
  background: var(--indigo); border: 0; color: var(--on-indigo);
  padding: 10px 20px; font-size: 15px; border-radius: 9px; cursor: pointer;
  font-family: inherit; font-weight: 500;
  transition: background .12s ease;
}
button:hover { background: var(--indigo-dk); }
/* Disabled controls are exempt from the contrast rule, which is not a reason
   to make them unreadable: white on the old #b9b9ad was 1.98:1. */
button:disabled { background: var(--bone-deep); color: #3f3f3a;
                  border: 1px solid var(--line); cursor: default; }
button.secondary {
  background: var(--bone); color: var(--indigo);
  border: 1px solid var(--line);
}
button.secondary:hover { background: var(--bone-deep); }
button.danger { background: var(--bad); color: #fff;
                padding: 3px 10px; font-size: 12px; }
button.danger:hover { background: #7f221d; }

label { display: block; font-size: 13px; color: var(--muted);
        margin: 14px 0 5px; letter-spacing: .01em; }
input[type=text], input[type=email], input[type=password], textarea {
  width: 100%; background: var(--surface); border: 1px solid var(--line);
  color: var(--ink); border-radius: 9px; padding: 10px 12px; font-size: 15px;
  font-family: inherit;
}
input:focus, textarea:focus {
  outline: none; border-color: var(--indigo);
  box-shadow: 0 0 0 3px var(--indigo-tint);
}

/* --- status ----------------------------------------------------------- */
.ok    { color: var(--ok); }
.bad   { color: var(--bad); }
.warn  { color: var(--warn); }
.muted { color: var(--muted); }

table { border-collapse: collapse; width: 100%; font-size: 13px; }
th, td { text-align: left; padding: 7px 8px;
         border-bottom: 1px solid var(--line-soft); }
th { color: var(--muted); font-weight: 600; }
code, .mono { font-family: "SF Mono", Consolas, monospace; }
.phones {
  font-family: "SF Mono", Consolas, monospace; background: var(--surface);
  padding: 10px 12px; border-radius: 10px; border: 1px solid var(--line);
  line-height: 1.9;
}
.phones span { background: var(--bone); border: 1px solid var(--line);
               border-radius: 5px; padding: 2px 6px; margin-right: 4px; }

/* --- capture webapp additions ----------------------------------------- */
.wrap { max-width: 900px; margin: 0 auto; padding: 24px 16px 60px; }

/* --- sign in (login.html) ---------------------------------------------- */
/* One centred column since the pitch moved to home.html: brand, card, terms.
   Same ground as the landing so the two pages do not change colour under a
   visitor crossing between them. */
body.signin {
  background:
    radial-gradient(1000px 520px at 8% -10%, rgba(20, 33, 61, .07) 0%,
                    transparent 60%),
    var(--bone);
  min-height: 100vh;
}
.signin-wrap { max-width: 440px; margin: 0 auto; padding: 7vh 22px 56px; }
.signin-wrap .auth-card { max-width: none; margin: 0; }
.back-home {
  display: inline-block; margin: 0 0 22px; font-size: 22px; font-weight: 700;
  letter-spacing: -.02em; color: var(--indigo); text-decoration: none;
}
.back-home span { font-weight: 300; color: var(--charcoal); }
.back-home:hover { color: var(--indigo-dk); }

.auth-card {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: 16px; padding: 28px; box-shadow: var(--shadow);
  /* The indigo edge is the one place the brand colour touches the form, so
     the eye lands on the card rather than on the paragraph beside it. */
  border-top: 3px solid var(--indigo);
}
.auth-card h2 { margin: 0; font-size: 20px; font-weight: 600;
                color: var(--indigo); }
.auth-card .card-sub { font-size: 13px; line-height: 1.5; margin: 8px 0 4px; }
.auth-card button { width: 100%; }

/* --- landing (home.html) ---------------------------------------------- */
/* The public page. Anchor tags rather than <button>, so `.btn` repeats the
   button rules above for links; everything else here is layout. No animation
   and no transition beyond the hover the buttons already had: a research page
   that moves while you read it is asking to be trusted less.

   Colour discipline, since this page is where the palette is most visible:
   Prussian carries headings and buttons, charcoal carries body copy, air force
   blue appears only as hairlines, and the celadon panel takes Prussian text.
   Nothing on this page puts text on air force blue. */
body.home { background: var(--bone); color: var(--charcoal); }

.btn {
  display: inline-block; background: var(--indigo); color: var(--on-indigo);
  padding: 12px 22px; border-radius: 9px; font-size: 15px; font-weight: 500;
  text-decoration: none;
}
.btn:hover { background: var(--indigo-dk); color: var(--on-indigo); }
.btn.secondary {
  background: transparent; color: var(--indigo);
  border: 1px solid var(--airforce);
}
.btn.secondary:hover { background: var(--bone-deep); color: var(--indigo); }
.btn:focus-visible, .site-head a:focus-visible, .privacy-more a:focus-visible,
.site-foot a:focus-visible {
  outline: 2px solid var(--indigo); outline-offset: 3px; border-radius: 4px;
}

.site-head {
  display: flex; align-items: center; gap: 24px;
  max-width: 1080px; margin: 0 auto; padding: 20px 28px;
}
.head-brand {
  font-size: 19px; font-weight: 700; letter-spacing: -.02em;
  color: var(--indigo); text-decoration: none;
}
.head-brand span { font-weight: 300; color: var(--charcoal); }
/* This is a <nav>, so the app's nav rule above would give it a white bar and
   a bottom border. Undo those three here rather than dropping the element:
   the landmark is what a screen reader needs to skip the header. */
.site-head .head-links {
  display: flex; gap: 20px; margin-left: auto;
  background: transparent; border-bottom: 0; padding: 0;
}
.site-head .head-links a {
  color: var(--charcoal); text-decoration: none; font-size: 14.5px;
  padding: 6px 2px; border-radius: 0;
}
.site-head .head-links a:hover { color: var(--indigo); background: none; }
.head-cta { padding: 9px 18px; font-size: 14.5px; }

.hero {
  max-width: 1080px; margin: 0 auto; padding: 72px 28px 64px;
}
.hero h1 {
  font-size: 54px; line-height: 1.06; letter-spacing: -.035em;
  color: var(--indigo); margin: 0 0 20px; max-width: 16ch; font-weight: 700;
}
.hero-sub {
  font-size: 18px; line-height: 1.6; color: var(--charcoal);
  max-width: 56ch; margin: 0 0 32px;
}
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 0; }

.band {
  max-width: 1080px; margin: 0 auto; padding: 48px 28px;
  border-top: 1px solid var(--airforce);
}
.band > h2 {
  font-size: 13px; text-transform: uppercase; letter-spacing: .14em;
  color: var(--indigo); margin: 0 0 28px; font-weight: 600;
}
.cards {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 20px;
}
.card {
  background: var(--surface); border: 1px solid var(--line-soft);
  border-radius: var(--radius); padding: 24px 22px;
}
.card h3 {
  font-size: 17px; line-height: 1.3; color: var(--indigo);
  margin: 0 0 10px; font-weight: 600;
}
.card p {
  font-size: 15px; line-height: 1.58; color: var(--charcoal); margin: 0;
}

/* Celadon carries Prussian text and nothing else: charcoal on celadon is
   4.46:1 and misses AA, so this panel raises the body copy to Prussian
   rather than tinting the panel until charcoal passes. */
.band.privacy { border-top: 0; padding: 48px 28px 64px; }
.privacy-inner {
  background: var(--celadon); border-radius: var(--radius);
  padding: 40px 36px; color: var(--indigo);
}
.privacy-inner h2 {
  font-size: 26px; line-height: 1.25; letter-spacing: -.02em;
  color: var(--indigo); margin: 0 0 24px; font-weight: 700; max-width: 24ch;
}
.privacy-list {
  list-style: none; padding: 0; margin: 0;
  display: grid; grid-template-columns: 1fr 1fr; gap: 20px 40px;
}
.privacy-list li {
  font-size: 15.5px; line-height: 1.58; color: var(--indigo);
  border-top: 1px solid rgba(20, 33, 61, .22); padding-top: 14px;
}
/* An odd number of points would otherwise leave the last one stranded in the
   left column with a gap beside it. Let it run the full width instead. */
.privacy-list li:last-child:nth-child(odd) { grid-column: 1 / -1; }
.privacy-more { margin: 28px 0 0; font-size: 15px; }
.privacy-more a { color: var(--indigo); font-weight: 600; }

.site-foot {
  max-width: 1080px; margin: 0 auto; padding: 28px;
  border-top: 1px solid var(--airforce);
  display: flex; flex-wrap: wrap; gap: 8px 24px; justify-content: space-between;
  font-size: 14px; color: var(--charcoal);
}
.site-foot p { margin: 0; }
.site-foot a { color: var(--indigo); }
/* Decorative, but it is still a painted glyph, so it holds body contrast
   rather than sitting at air force blue's 3.36:1. */
.foot-sep { color: var(--charcoal); margin: 0 8px; }

@media (max-width: 860px) {
  .cards, .privacy-list { grid-template-columns: 1fr; }
  .hero { padding: 48px 22px 44px; }
  .hero h1 { font-size: 38px; }
  .hero-sub { font-size: 16.5px; }
  .band { padding: 36px 22px; }
  .privacy-inner { padding: 28px 22px; }
  .site-head { flex-wrap: wrap; gap: 12px 18px; padding: 16px 22px; }
  .head-links { margin-left: 0; order: 3; width: 100%; }
  .head-cta { margin-left: auto; }
}

/* --- camera ----------------------------------------------------------- */
.row { display: flex; gap: 24px; flex-wrap: wrap; align-items: flex-start; }
#camwrap { position: relative; width: 480px; }
#cam, #overlay { width: 480px; height: 360px; border-radius: 10px;
                 display: block; }
/* Placeholder colour belongs to the video ONLY. The overlay canvas sits on
   top of it, so giving the canvas a background paints an opaque sheet over
   the picture - the camera works, the box draws, and you see black. */
#cam { object-fit: cover; background: var(--well); }
#overlay { position: absolute; left: 0; top: 0; background: transparent;
           pointer-events: none; }
#camwrap.rec #cam { outline: 3px solid var(--bad); }

/* The recording canvas (index.html). #stage carries the lower-face crop that
   MediaRecorder captures and uploads; it is kept off screen, because what the
   speaker needs to see is their own face with the green box on it, not the
   crop in isolation. Shrunk to a transparent pixel rather than display:none —
   a canvas that is not composited can stop producing frames, and captureStream
   needs every one of them. */
#camwrap #stage {
  position: absolute; left: 0; top: 0; width: 1px; height: 1px;
  opacity: 0; pointer-events: none;
}
/* Rejection toast (index.html). Fixed to the bottom of the viewport, because
   the speaker's eyes are on the camera and the prompt, not on the status line
   under the button. [hidden] needs the explicit display:none — the display
   below would otherwise override the attribute's default style. */
#toast {
  position: fixed; left: 50%; bottom: 24px; transform: translateX(-50%);
  z-index: 20; max-width: min(560px, calc(100vw - 32px));
  padding: 12px 18px; border-radius: var(--radius);
  background: var(--bad); color: #fff; font-size: 15px; line-height: 1.4;
  box-shadow: var(--shadow); text-align: center;
}
/* Green for a message that reports something that worked — the deletion
   confirmation on login.html. Red is the default because the other caller,
   the rejected-take toast, is telling the speaker something went wrong. */
#toast.ok { background: var(--ok); }
#toast[hidden] { display: none; }
#verdict { margin-top: 12px; font-size: 15px; min-height: 40px; }
.meter { font-family: "SF Mono", Consolas, monospace; letter-spacing: -1px; }
.pill { display: inline-block; font-size: 11px; padding: 2px 8px;
        border-radius: 10px; background: var(--bone-deep);
        color: var(--ink-soft); }
.pill.ok  { background: var(--ok-tint); color: var(--ok); }
.pill.bad { background: var(--bad-tint); color: var(--bad); }
video.thumb { width: 200px; border-radius: 8px; background: var(--well); }
.error  { color: var(--bad); font-size: 13px; min-height: 18px;
          margin-top: 10px; }
.notice { color: var(--ok); font-size: 13px; min-height: 18px;
          margin-top: 10px; }

/* Corpus prompt panel on the capture page. The sentence is the one thing on
   the page the speaker actually reads, so it is sized to be legible from
   arm's length at the camera. */
#sentence {
  font-size: 26px; line-height: 1.45; background: var(--surface);
  padding: 18px 22px; border-radius: var(--radius);
  border: 1px solid var(--line); min-height: 70px; color: var(--ink);
}

/* Consent prompt (consent.html). Long-form text, so it gets a reading
   measure rather than the full page width. */
.consent { max-width: 62ch; margin: 18px 0 22px; }
.consent h2 {
  font-size: 12px; margin: 22px 0 5px; color: var(--indigo);
  font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
}
.consent p { margin: 0; font-size: 15px; line-height: 1.62;
             color: var(--ink); }
/* Sections whose body runs to more than one paragraph — the transfer and
   licensing terms, and retention. Without this they set solid and the second
   paragraph reads as a continuation of the first. */
.consent p + p { margin-top: .9em; }
.checkline {
  display: flex; align-items: center; gap: 8px; font-size: 14px;
  color: var(--ink); margin: 0;
}
.checkline input { width: 16px; height: 16px; accent-color: var(--indigo); }
.blank-state { max-width: 52ch; margin: 12vh auto 0; text-align: center; }
.blank-state h1 { font-size: 22px; }

/* hCaptcha widget on the login form. The iframe is a fixed size chosen in
   login.html from how much room this box has, so there is nothing to lay out
   here beyond spacing it. No max-width: the widget does not reflow, and
   squeezing it clips the logo instead of shrinking it. */
.captcha { margin-top: 16px; }
.captcha:empty { display: none; }

/* --- profile form (profile.html) --------------------------------------- */
/* Long form, mostly optional. Grouped into fieldsets so the required name at
   the top is not visually the same weight as twenty checkboxes below it. */
.form-section {
  background: var(--surface); border: 1px solid var(--line);
  border-radius: var(--radius); padding: 4px 20px 20px;
  box-shadow: var(--shadow); margin: 0 0 18px; max-width: 62ch;
}
.form-section h2 {
  font-size: 12px; margin: 20px 0 4px; color: var(--indigo);
  font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
}
.form-section .hint {
  font-size: 13px; line-height: 1.5; color: var(--muted); margin: 6px 0 0;
}
/* Two columns where the option labels are short enough for it, one where the
   box is narrow. Checkbox and radio rows share the layout: what differs
   between them is how many can be on, which is not a visual question. */
.optgrid {
  display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 16px; margin-top: 6px;
}
@media (max-width: 620px) { .optgrid { grid-template-columns: 1fr; } }
.optgrid .checkline { padding: 4px 0; }
.optgrid .checkline input { flex: none; }
/* The US-only accent regions, revealed under their parent select. Indented
   so the dependency reads without a sentence explaining it. */
.subfield {
  margin-left: 14px; padding-left: 14px;
  border-left: 2px solid var(--line-soft);
}
.subfield[hidden] { display: none; }
/* A message about one field, sitting between its label and its box. Unlike
   .error it has no min-height and no reserved margin: it appears and
   disappears while somebody is typing, and a permanent 18px gap under every
   label to hold the space would be worse than the shift it prevents. */
.field-error {
  color: var(--bad); font-size: 13px; line-height: 1.4; margin: 2px 0 6px;
}
.field-error[hidden] { display: none; }
/* Filled from the account and not editable. Styled as settled rather than
   broken — the value is still selectable and copyable. */
input[readonly] { background: var(--bone-deep); color: var(--ink-soft); }
select {
  width: 100%; background: var(--surface); border: 1px solid var(--line);
  color: var(--ink); border-radius: 9px; padding: 10px 12px; font-size: 15px;
  font-family: inherit;
}
select:focus {
  outline: none; border-color: var(--indigo);
  box-shadow: 0 0 0 3px var(--indigo-tint);
}
input[type=number] {
  width: 120px; background: var(--surface); border: 1px solid var(--line);
  color: var(--ink); border-radius: 9px; padding: 10px 12px; font-size: 15px;
  font-family: inherit;
}
.form-actions {
  display: flex; gap: 10px; align-items: center; max-width: 62ch;
  margin-top: 4px;
}

/* --- delete my data (profile.html) ------------------------------------- */
/* Set apart from the form above it and given the warning colour, because it
   is the one control on the page that cannot be undone by pressing it again.
   Not a primary button: nobody should land on this by aiming for Save. */
.danger-zone {
  max-width: 62ch; border: 1px solid var(--line);
  border-left: 3px solid var(--bad);
  border-radius: var(--radius); background: var(--surface);
  padding: 18px 20px; margin: 26px 0 0;
}
.danger-zone h2 {
  font-size: 12px; margin: 0 0 6px; color: var(--bad);
  font-weight: 700; text-transform: uppercase; letter-spacing: .08em;
}
.danger-zone p { font-size: 13px; line-height: 1.55; color: var(--muted);
                 margin: 0 0 14px; }
button.destructive {
  background: var(--bad-tint); color: var(--bad);
  border: 1px solid var(--bad); font-size: 14px; padding: 8px 16px;
}
button.destructive:hover { background: var(--bad); color: #fff; }

/* The confirmation. A native <dialog>, so the browser supplies the focus trap
   and Escape, rather than this reimplementing both badly. */
dialog.modal {
  border: 1px solid var(--line); border-radius: 16px; padding: 26px 28px;
  max-width: 44ch; width: calc(100% - 32px);
  background: var(--surface); color: var(--ink); box-shadow: var(--shadow);
}
dialog.modal::backdrop { background: rgba(16, 26, 49, .45); }
dialog.modal h2 { margin: 0 0 12px; font-size: 20px; color: var(--indigo);
                  font-weight: 600; }
dialog.modal p { font-size: 14.5px; line-height: 1.55; margin: 0 0 10px; }
dialog.modal ol { font-size: 14.5px; line-height: 1.55; margin: 0 0 14px;
                  padding-left: 22px; }
dialog.modal li { margin: 4px 0; }
dialog.modal .final { color: var(--bad); font-weight: 600; }
.modal-actions {
  display: flex; gap: 10px; justify-content: flex-end; margin-top: 18px;
}
@media (max-width: 480px) {
  .modal-actions { flex-direction: column-reverse; }
  .modal-actions button { width: 100%; }
}

/* --- consent terms on the sign-in page ---------------------------------- */
/* Collapsed by default: the four facts above it are what someone skims, and
   the full document under them is what they read if they mean to. Open, it
   reuses the .consent rules the consent page uses, so the words are set the
   same way in both places. */
.terms { margin-top: 22px; border-top: 1px solid var(--line); }
.terms > summary {
  cursor: pointer; padding: 12px 0; font-size: 13.5px; font-weight: 600;
  color: var(--indigo); list-style-position: inside;
}
.terms > summary:hover { color: var(--indigo-dk); }
.terms > summary:focus-visible {
  outline: 2px solid var(--indigo); outline-offset: 3px; border-radius: 4px;
}
/* Long enough to need its own scroll rather than pushing the sign-in form
   down a screen and a half on a phone. */
.terms .consent {
  max-height: 48vh; overflow-y: auto; margin: 0 0 18px;
  padding-right: 12px;
}
.terms .consent h2:first-child { margin-top: 0; }
