/*
 * Visual system lifted from openui.com: near-black ground, Inter Display for
 * headings with a tight negative track, Geist Mono for anything machine-written,
 * translucent-white hairlines instead of grey borders, generous card radii and
 * pill controls, and a white-on-black primary button.
 *
 * Fonts are vendored rather than pulled from a font CDN. The page promises that
 * nothing about a checked file is retained, and a third-party request on that
 * page would undercut the claim to exactly the audience most likely to check.
 * Latin subsets only — about 96 kB.
 */

@font-face {
  font-family: 'Inter Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/inter-latin-opsz-normal.4b73e2ff3024.woff2") format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

@font-face {
  font-family: 'Inter Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/inter-latin-ext-opsz-normal.084b211fc382.woff2") format('woff2-variations');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304,
    U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0,
    U+2113, U+2C60-2C7F, U+A720-A7FF;
}

@font-face {
  font-family: 'Geist Mono Variable';
  font-style: normal;
  font-display: swap;
  font-weight: 100 900;
  src: url("fonts/geist-mono-latin-wght-normal.a15f9d568fb0.woff2") format('woff2-variations');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC,
    U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215,
    U+FEFF, U+FFFD;
}

:root {
  /* Pure black, which is what openui.com actually paints. Its --background
     token reads #0b0b0f, but nothing renders with it — the hero shell and the
     header surface both sit at rgb(0,0,0), and copying the token instead of the
     painted value leaves the page visibly lighter than the source. */
  --bg: #000000;
  --surface: #131318;
  --surface-2: rgba(255, 255, 255, 0.04);
  --surface-3: rgba(255, 255, 255, 0.07);
  --border: rgba(255, 255, 255, 0.07);
  --border-strong: rgba(255, 255, 255, 0.14);

  --fg: #fafafa;
  --muted: rgba(250, 250, 250, 0.5);
  --faint: rgba(250, 250, 250, 0.32);

  --pass: #4bbf88;
  --pass-bg: rgba(75, 191, 136, 0.1);
  --pass-line: rgba(75, 191, 136, 0.32);
  --warn: #e0a94a;
  --warn-bg: rgba(224, 169, 74, 0.1);
  --warn-line: rgba(224, 169, 74, 0.32);
  --fail: #f0776e;
  --fail-bg: rgba(240, 119, 110, 0.1);
  --fail-line: rgba(240, 119, 110, 0.34);
  --na: rgba(250, 250, 250, 0.45);
  --na-bg: rgba(255, 255, 255, 0.05);
  --na-line: rgba(255, 255, 255, 0.12);

  --font-sans: 'Inter Variable', Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto,
    'Helvetica Neue', Arial, sans-serif;
  --font-mono: 'Geist Mono Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
    'Liberation Mono', monospace;

  --radius-card: 16px;
  --radius-inner: 10px;
  --radius-pill: 999px;

  --shadow-card: 0 4px 4px -2px rgba(0, 0, 0, 0.12), 0 4px 8px -2px rgba(0, 0, 0, 0.1);
}

* { box-sizing: border-box; }

html { background: var(--bg); }

body {
  margin: 0;
  /* No horizontal padding here: the light band runs edge to edge, so the gutter
     lives on .wrap instead. */
  padding: 0 0 96px;
  background: var(--bg);
  color: var(--fg);
  font-family: var(--font-sans);
  font-size: 16px;
  line-height: 1.6;
  font-variation-settings: 'opsz' 16;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
}

/* Inter's display optical size — the heading face on openui.com. */
h1, h2, h3, .display {
  font-variation-settings: 'opsz' 32;
  letter-spacing: -0.02em;
}

a {
  color: var(--fg);
  text-decoration-color: var(--border-strong);
  text-underline-offset: 3px;
}
a:hover { text-decoration-color: var(--fg); }

code, pre, .mono { font-family: var(--font-mono); }

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

/* -------------------------------------------------------------- layout -- */

/* The measure, matching openui.com: a 1200px shell with 32px gutters. The shell
   is wide; the prose inside it is not — .lede, .privacy-note and the section
   paragraphs carry their own ch-based caps so lines stay readable. */
.wrap { max-width: 1200px; margin: 0 auto; padding: 0 32px; }

@media (max-width: 620px) {
  .wrap { padding: 0 20px; }
}

/* A full-bleed section that flips the palette. Every component below is built
   on these variables, so overriding them here re-themes the whole band —
   buttons, inputs, borders and status colours included — without a single
   component needing to know it can appear on a light background. */
.band {
  --bg: #f4f4f6;
  --surface: #ffffff;
  --surface-2: rgba(11, 15, 20, 0.045);
  --surface-3: rgba(11, 15, 20, 0.08);
  --border: rgba(11, 15, 20, 0.12);
  --border-strong: rgba(11, 15, 20, 0.22);

  --fg: #0b0b0f;
  --muted: rgba(11, 11, 15, 0.62);
  /* Heavier than the dark theme's equivalent: the same alpha reads much lighter
     against white, and everything --faint carries here is still real text. */
  --faint: rgba(11, 11, 15, 0.56);

  /* The dark-mode status colours are tuned for a near-black ground and go
     illegible on white, so they are darkened rather than reused. */
  --pass: #14683c;
  --warn: #7a4f00;
  --fail: #a3211a;

  background: var(--bg);
  color: var(--fg);
}

/* -------------------------------------------------------------- navbar -- */

.navbar {
  position: sticky; top: 0; z-index: 50;
  background: var(--bg);
  /* Seamless with the page at rest, with a rule once content is passing
     underneath — the line's job is to separate the bar from what it now
     overlaps, which is nothing until the page moves. Declared transparent
     rather than added on .scrolled so the row never changes height.
     nav.js owns the class. */
  border-bottom: 1px solid transparent;
  transition: border-color .18s ease;
}
.navbar.scrolled { border-bottom-color: var(--border-strong); }

.navbar-inner {
  display: grid;
  grid-template-columns: 1fr auto 1fr;
  align-items: center;
  gap: 16px;
  /* 64px exactly, set as a height rather than padding so the row does not grow
     with whatever is tallest inside it. Wider shell and smaller gutters than
     the content measure, so the brand and the GitHub pill sit near the edges. */
  height: 64px;
  max-width: 1560px; margin: 0 auto;
  padding: 0 16px;
}

.brand {
  display: inline-flex; align-items: center; gap: 10px;
  justify-self: start;
  text-decoration: none; color: var(--fg);
}
.brand-mark {
  flex: none; width: 32px; height: 32px;
  display: grid; place-items: center;
  border-radius: 8px;
  background: var(--fg); color: var(--bg);
}
.brand-mark svg { width: 19px; height: 19px; }
.brand-name {
  font-size: 15px; font-weight: 600; letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 32;
}

.nav-links { display: flex; align-items: center; gap: 2px; }
/* 600, two steps above openui.com's measured 400 — a deliberate choice, not a
   mismatch. It matches the brand rather than sitting under it: the wordmark
   still leads on size and on the mark beside it, so the links can carry the
   same weight without competing. */
/* :not(.btn) because this rule is for plain navigation links, and a button in
   the bar is a button. Without it `.nav-links a` (0,1,1) outranks `.btn-primary`
   (0,1,0) and repaints the pill as a nav link: nav-link padding, a 6px radius,
   and — since `color: var(--fg)` beats the button's `color: var(--bg)` — white
   text on the white fill, i.e. an empty white rectangle. */
.nav-links a:not(.btn) {
  font-size: 15px; line-height: 1; font-weight: 600;
  font-variation-settings: 'opsz' 32;
  letter-spacing: -0.01em;
  padding: 9px 10px; border-radius: 6px;
  color: var(--fg);
  text-decoration: none; white-space: nowrap;
  transition: background .12s ease;
}
.nav-links a:not(.btn):hover { background: var(--surface-2); }

/* The right-hand group: the account link and the call to action.
   `justify-self` is what keeps it there. .nav-drawer is `display: contents`, so
   this wrapper is itself a grid item in the trailing 1fr track, and a grid item
   stretches to fill its track unless told otherwise — which is what made the
   pill run most of the bar's width before it had one. `end` sizes it to its
   contents and pins it to the right edge, opposite the brand. */
.nav-actions {
  justify-self: end;
  display: flex; align-items: center; gap: 16px;
}

/* The account link, and the sign-out button dressed as one. Quiet on purpose:
   the bar has a single call to action and this is not it. */
.nav-auth-link {
  font: inherit; font-size: 14px; font-weight: 500; letter-spacing: -0.01em;
  background: none; border: 0; padding: 0; cursor: pointer;
  color: var(--muted); text-decoration: none; white-space: nowrap;
  transition: color .12s ease;
}
.nav-auth-link:hover { color: var(--fg); }
.nav-auth { display: flex; align-items: center; gap: 10px; margin: 0; }
.nav-user {
  font-size: 13px; color: var(--faint); white-space: nowrap;
  /* An email is longer than a navbar on a laptop. */
  max-width: 22ch; overflow: hidden; text-overflow: ellipsis;
}

/* The wrapper the mobile drawer is built from. `display: contents` dissolves it
   above the breakpoint, so .nav-links is a direct grid item and the desktop row
   is laid out exactly as it was before this element existed. */
.nav-drawer { display: contents; }

/* Hidden above the breakpoint rather than absent: it is in the markup on every
   viewport so the menu works from first paint, not from first script. */
.nav-toggle {
  display: none;
  justify-self: end;
  width: 40px; height: 40px; padding: 0;
  align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--border-strong);
  border-radius: var(--radius-pill);
  color: var(--fg); cursor: pointer;
  transition: background .12s ease;
}
.nav-toggle:hover { background: var(--surface-2); }

/* Three bars that fold into an X. Two of them rotate onto the centre line and
   the middle one fades, so the control reads as the same object in both states
   rather than swapping icon for icon. */
.nav-toggle-icon {
  position: relative;
  display: block; width: 18px; height: 12px;
}
.nav-toggle-icon span {
  position: absolute; left: 0;
  display: block; width: 100%; height: 1.5px; border-radius: 2px;
  background: currentColor;
  transition: transform .2s ease, opacity .15s ease;
}
.nav-toggle-icon span:nth-child(1) { top: 0; }
.nav-toggle-icon span:nth-child(2) { top: 5.25px; }
.nav-toggle-icon span:nth-child(3) { top: 10.5px; }

.navbar.menu-open .nav-toggle-icon span:nth-child(1) { transform: translateY(5.25px) rotate(45deg); }
.navbar.menu-open .nav-toggle-icon span:nth-child(2) { opacity: 0; }
.navbar.menu-open .nav-toggle-icon span:nth-child(3) { transform: translateY(-5.25px) rotate(-45deg); }

@media (prefers-reduced-motion: reduce) {
  .nav-toggle-icon span { transition: none; }
}

/* Four links at 600 start crowding the brand below this rather than wrapping.
   From here down the row is brand and burger, and the links move into the
   drawer. */
@media (max-width: 880px) {
  .navbar-inner { grid-template-columns: 1fr auto; }
  .nav-toggle { display: inline-flex; }

  /* Taken out of the grid entirely and anchored to the bar. .navbar is
     position: sticky, which is a positioned ancestor, so this hangs off the
     bottom of it without needing a wrapper to position against. */
  .nav-drawer {
    display: none;
    position: absolute; top: 100%; left: 0; right: 0;
    padding: 10px 16px 18px;
    background: var(--bg);
    border-bottom: 1px solid var(--border-strong);
  }
  .navbar.menu-open .nav-drawer { display: block; }

  .nav-links { flex-direction: column; align-items: stretch; gap: 0; }
  /* Bigger than the desktop target: this is a thumb, not a cursor. */
  .nav-links a:not(.btn) { padding: 13px 12px; font-size: 16px; border-radius: 8px; }

  /* Full width here, unlike the desktop bar. The drawer is a block, so there is
     no track to shrink against and a content-width pill would sit alone against
     the left edge; stretched it reads as the drawer's one action. The base .btn
     centres its own label, which is what makes that look deliberate. */
  /* In the drawer the row becomes a stack: the account link on its own line,
     the button full width under it. */
  .nav-actions { flex-direction: column; align-items: stretch; gap: 10px; }
  .nav-auth, .nav-auth-link { justify-content: center; }
  .nav-user { max-width: none; text-align: center; }

  /* Width rather than display: the base .btn is inline-flex and is declared
     further down the file, so a `display: flex` here would lose to it anyway —
     and stretched inline-flex lays out identically. */
  .nav-cta { width: 100%; height: 44px; margin-top: 4px; }
}

/* The sticky bar would otherwise sit on top of whatever an anchor jumps to. */
:target { scroll-margin-top: 80px; }

/* ---------------------------------------------------------------- hero -- */

.hero { padding: 72px 0 40px; }

.eyebrow {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 12px;
  letter-spacing: 0.02em;
  color: var(--muted);
  background: var(--surface-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  padding: 5px 14px;
  margin: 0 0 26px;
}

h1 {
  font-size: clamp(38px, 6vw, 60px);
  line-height: 1.02;
  font-weight: 700;
  letter-spacing: -0.035em;
  margin: 0 0 22px;
}

/* The two-tone display headline openui.com opens with. */
h1 .dim { color: var(--muted); }

.lede {
  font-size: 19px;
  line-height: 1.55;
  color: var(--muted);
  margin: 0 0 22px;
  max-width: 62ch;
}

/* The hero's job now that the checker is a page of its own: say what the thing
   does, then hand over to it. */
.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin: 30px 0 0; }
/* .btn-lg itself lives with the rest of the button component further down. It
   was declared here and lost every shared property to the base .btn ~430 lines
   below it — same specificity, later wins — so the large button rendered at the
   small size. A modifier has to follow the thing it modifies. */

/* The privacy claim also leads the checker page, where it sits under the
   control that takes the file. Here it is a promise made before the click. */
.hero-note {
  margin: 22px 0 0;
  font-size: 12px; line-height: 1.5; color: var(--muted);
  max-width: 78ch;
}

/* ------------------------------------------------------ check page shell -- */

/* The checker is sized to one viewport and does not scroll. Scrolling is taken
   off <body> and given to the two panes inside it, so the frame — navbar,
   dropzone, verdict — is fixed and a result is read in place.

   100dvh rather than 100vh: on mobile Safari the large-viewport unit measures
   the chrome-less height, which is taller than what is actually painted until
   the address bar retracts, and the bottom of the page sits under it. */
body.check-page {
  height: 100dvh;
  overflow: hidden;
  padding: 0;
  /* A column rather than `height: calc(100dvh - 64px)` on the app below: the
     navbar is 64px plus a hairline it only paints once scrolled, and subtracting
     a number that is right on one page and one pixel wrong here is how a
     no-scroll layout quietly starts scrolling. Flex measures it instead. */
  display: flex;
  flex-direction: column;
}

.check-app {
  flex: 1;
  min-height: 0;
  width: 100%;
  max-width: 1560px;
  margin: 0 auto;
  padding: 18px 24px 20px;
  display: grid;
  gap: 24px;
  /* Nothing has been checked yet: one column, capped to a readable width and
     centred in the frame. */
  grid-template-columns: min(640px, 100%);
  grid-template-rows: auto;
  align-content: center;
  justify-content: center;
}

/* A finished check, keyed off the grid of findings rather than off #result
   having any children at all — an error message is one short paragraph and
   belongs under the dropzone in the centred layout, not alone in a pane. */
.check-app:has(#checks) {
  grid-template-columns: minmax(240px, 320px) minmax(0, 1fr);
  grid-template-rows: minmax(0, 1fr);
  align-content: stretch;
  justify-content: stretch;
}

/* min-height on a grid item defaults to auto, which refuses to shrink below the
   content — without this the panes grow the page instead of scrolling. */
.check-app > * { min-height: 0; min-width: 0; }

.check-app:has(#checks) .check-input,
.check-app:has(#checks) .result {
  height: 100%;
  overflow-y: auto;
  /* Room for the scrollbar so an overflowing pane does not reflow its own
     content the moment it appears. */
  scrollbar-gutter: stable;
}

/* ------------------------------------------------------------ dropzone -- */

.dropzone {
  /* It is a <label> wrapping the file input, so it needs taking out of inline
     flow — otherwise the padding and border fragment across line boxes. */
  display: block;
  border: 1px dashed var(--border-strong);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  padding: 64px 24px;
  text-align: center;
  cursor: pointer;
  transition: border-color .14s ease, background .14s ease;
  outline-offset: 3px;
}
/* Pulled down with the ground: these were picked against #0b0b0f, and on pure
   black the same values read as a much larger jump than a hover should. */
.dropzone:hover, .dropzone:focus-visible { border-color: rgba(255, 255, 255, 0.34); background: #101014; }
.dropzone.dragging { border-color: var(--fg); border-style: solid; background: #121217; }
.dropzone.busy { cursor: progress; opacity: .55; }

.dropzone-title { display: block; font-size: 20px; font-weight: 600; letter-spacing: -0.02em; margin: 0 0 8px; }
.dropzone-sub { display: block; font-size: 14px; color: var(--muted); margin: 0; }

/* The label is the drop target; the input itself only needs to stay reachable
   by the keyboard, so it is hidden visually rather than with display:none. */
.dropzone input[type='file'] {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap;
}
.dropzone:has(input:focus-visible) { border-color: var(--fg); }

/* The privacy claim, deliberately quiet: it belongs near the thing it is about
   and it is not a warning. Small, no border, no background.

   It lives inside the form, so it is hidden along with it once a check runs —
   outside, it was left stranded between a hidden form and the result,
   explaining the handling of a file that had already been handled.

   The one place on the page with no ch-based cap, and deliberately — capping it
   for readability would stack it four lines deep under the drop zone, which is
   the prominence this text was just demoted out of. Running the full measure
   keeps it to a couple of lines of fine print.

   --muted, not --faint. The demotion comes from size, position and losing the
   panel; pushing the colour down as well would put a claim about what happens
   to someone's file at roughly 3:1, under AA. Same reasoning as .wants legend
   below. */
.privacy-note {
  margin: 14px 0 0;
  font-size: 12px; line-height: 1.5; color: var(--muted);
}

/* -------------------------------------------------------------- preview -- */

/* Built client-side from the chosen File. The server never sends the image
   back, so this costs no bandwidth and keeps nothing on disk. */
.preview {
  display: flex; align-items: center; gap: 18px; margin: 0;
  border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface); box-shadow: var(--shadow-card);
  padding: 16px 20px;
}
.preview[hidden] { display: none; }

.preview img {
  flex: none; width: 92px; height: 92px;
  object-fit: cover; border-radius: var(--radius-inner);
  background: var(--surface-2); border: 1px solid var(--border);
}
/* A format the browser cannot decode still gets checked server-side, so the
   caption stands on its own rather than leaving a broken image behind. */
.preview.no-thumbnail img { display: none; }

/* In the split layout the preview is the left rail: the file the findings are
   about, standing beside them at a size worth looking at rather than as a
   92px chip. Same element, turned on its side. */
.check-app:has(#checks) .preview {
  flex-direction: column; align-items: stretch; gap: 14px;
  padding: 14px;
}
.check-app:has(#checks) .preview img {
  width: 100%; height: auto; max-height: 46vh;
  object-fit: contain;
}

.preview figcaption { min-width: 0; }
.preview-name {
  display: block; font-weight: 600; letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 32;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.preview-meta {
  display: block; margin-top: 4px;
  font-family: var(--font-mono); font-size: 12px; color: var(--faint);
}

/* ------------------------------------------------------- processing state -- */

/* hx-indicator flips .htmx-request on #analysing for the life of the request.
   The element is display:none until then, so nothing reserves space. */
.analysing { display: none; margin-top: 14px; }
.analysing.htmx-request { display: block; }

.analysing-inner {
  display: flex; align-items: center; gap: 16px;
  border: 1px solid var(--border); border-radius: var(--radius-card);
  background: var(--surface); padding: 22px 24px;
}

.spinner {
  flex: none; width: 20px; height: 20px; border-radius: 50%;
  border: 2px solid var(--border-strong); border-top-color: var(--fg);
  animation: spin .7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.analysing-text { min-width: 0; }

/* Each step holds the line for a quarter of the cycle. Not real progress — it
   names what the server is doing, in the order it does it.

   The animation is declared on the .htmx-request state rather than on .step,
   so it begins the moment the indicator appears. Declared on .step it would be
   bound to the document timeline and already be at an arbitrary point in the
   cycle by the time anyone uploads anything — usually mid-fade, sometimes in
   the gap between two steps, showing nothing at all. */
.analysing-steps { display: grid; }
.analysing-steps .step {
  grid-area: 1 / 1;
  font-size: 15px; font-weight: 600; letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 32;
  opacity: 0;
}
.analysing.htmx-request .step { animation: step-cycle 6s linear infinite; }
.analysing.htmx-request .step:nth-child(2) { animation-delay: 1.5s; }
.analysing.htmx-request .step:nth-child(3) { animation-delay: 3s; }
.analysing.htmx-request .step:nth-child(4) { animation-delay: 4.5s; }

@keyframes step-cycle {
  0%    { opacity: 0; transform: translateY(3px); }
  3%    { opacity: 1; transform: none; }
  22%   { opacity: 1; transform: none; }
  25%   { opacity: 0; transform: translateY(-3px); }
  100%  { opacity: 0; transform: translateY(-3px); }
}

.analysing-sub {
  display: block; margin-top: 3px;
  font-family: var(--font-mono); font-size: 12px; color: var(--faint);
}

.analysing-track {
  height: 2px; margin-top: 2px; overflow: hidden;
  background: var(--border); border-radius: var(--radius-pill);
}
.analysing-bar {
  display: block; height: 100%; width: 34%; border-radius: var(--radius-pill);
  background: var(--fg); opacity: .7;
  animation: sweep 1.6s ease-in-out infinite;
}
@keyframes sweep {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(390%); }
}

/* Someone who asked not to be moved gets a static indicator, not a stack of
   invisible steps — the first line stays put and the bar holds still. */
@media (prefers-reduced-motion: reduce) {
  .spinner, .analysing-bar, .analysing.htmx-request .step { animation: none; }
  .analysing-steps .step:first-child { opacity: 1; }
  .analysing-bar { width: 100%; opacity: .35; }
}

/* -------------------------------------------------------------- result -- */

/* The right-hand pane. It is empty until a check runs, and in the centred
   layout an error message needs separating from the form above it. */
.result:not(:empty) { margin-top: 20px; }
.check-app:has(#checks) .result { margin-top: 0; }

.verdict {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  padding: 18px 20px;
  position: relative;
  overflow: hidden;
}
/* A single hairline of colour rather than a wash — the palette stays monochrome. */
.verdict::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
}
.verdict.marked::before   { background: var(--pass); }
.verdict.fragile::before  { background: var(--warn); }
.verdict.unmarked::before { background: var(--fail); }
.verdict.error::before    { background: var(--fail); }
.verdict.error h2         { color: var(--fail); }
.verdict:focus { outline: none; }

/* The tally sits on the headline's baseline rather than under the prose: on a
   pane that has to fit a viewport it is the one number worth reading first, and
   it costs no extra row up here. */
.verdict-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 18px; flex-wrap: wrap; margin-bottom: 10px;
}
.verdict-head h2 { margin: 0; }
.verdict-tally {
  font-family: var(--font-mono); font-size: 11px; color: var(--faint);
  white-space: nowrap;
}

/* The error partial has no .verdict-head — it is a headline and one sentence —
   so the gap lives on the heading and is taken back inside the head row. */
.verdict h2 {
  margin: 0 0 10px;
  font-size: clamp(21px, 2.2vw, 27px);
  line-height: 1.12;
  font-weight: 700;
  letter-spacing: -0.03em;
}
.verdict.marked h2   { color: var(--pass); }
.verdict.fragile h2  { color: var(--warn); }
.verdict.unmarked h2 { color: var(--fail); }

/* The pane is wide; a sentence is not. These caps are what keep the wide layout
   from turning findings into single long lines. */
.verdict .lead { margin: 0 0 10px; font-size: 16px; line-height: 1.5; color: var(--fg); max-width: 62ch; }
.verdict .explainer { margin: 0 0 12px; font-size: 13.5px; color: var(--muted); max-width: 70ch; }
.verdict .reasons {
  margin: 0 0 12px; padding: 0; list-style: none;
  font-size: 13.5px; color: var(--muted);
  border-top: 1px solid var(--border); padding-top: 12px;
}
.verdict .reasons li { margin-bottom: 5px; padding-left: 18px; position: relative; max-width: 76ch; }
.verdict .reasons li:last-child { margin-bottom: 0; }
.verdict .reasons li::before {
  content: ''; position: absolute; left: 2px; top: .62em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--faint);
}
/* Whatever ends the card ends it flush — which of the three it is depends on
   the verdict, and on the error partial it is the lead. */
.verdict > :last-child { margin-bottom: 0; }

/* The caveat: a "no" here is a statement about the file, not about the picture.
   Bordered and full-size rather than fine print, because it is the single thing
   a reader is most likely to get wrong and least likely to work out alone. */
.verdict .caveat {
  margin: 14px 0 0; padding: 13px 15px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-inner);
  background: var(--surface-2);
  font-size: 14px; line-height: 1.55; color: var(--fg);
  max-width: 76ch;
}

/* ------------------------------------------------------- plain answers -- */

/* Three questions, three sentences. This is the page now; the ten checks are
   evidence behind a disclosure. */
.answers { display: grid; gap: 8px; margin-top: 12px; }

.answer {
  display: flex; align-items: flex-start; gap: 13px;
  border: 1px solid var(--border);
  border-radius: var(--radius-inner);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  padding: 12px 15px;
}

/* A tick, a dash or a cross, drawn rather than set in a font — a glyph that has
   not loaded yet is a blank where the answer should be. Colour carries the same
   meaning, so the shapes are what make it survive being colour-blind or
   printed: they differ in form, not only in hue. */
.answer-mark {
  flex: none; position: relative;
  width: 20px; height: 20px; margin-top: 1px;
  border-radius: 50%;
  border: 1.5px solid currentColor;
}
.answer-mark::before, .answer-mark::after {
  content: ''; position: absolute; background: currentColor; border-radius: 1px;
}
.answer.pass { color: var(--pass); }
.answer.warn { color: var(--warn); }
.answer.fail { color: var(--fail); }
.answer.na   { color: var(--na); }

/* Tick: two strokes meeting at the low point. */
.answer.pass .answer-mark::before {
  width: 2px; height: 5px; left: 6px; top: 8px; transform: rotate(-45deg);
}
.answer.pass .answer-mark::after {
  width: 2px; height: 9px; left: 10px; top: 4px; transform: rotate(35deg);
}
/* Dash, for "partly". */
.answer.warn .answer-mark::before,
.answer.na .answer-mark::before {
  width: 9px; height: 2px; left: 4.5px; top: 8px;
}
/* Cross. */
.answer.fail .answer-mark::before,
.answer.fail .answer-mark::after {
  width: 10px; height: 2px; left: 4px; top: 8px;
}
.answer.fail .answer-mark::before { transform: rotate(45deg); }
.answer.fail .answer-mark::after  { transform: rotate(-45deg); }

.answer-text { min-width: 0; }
.answer-question {
  margin: 0; font-size: 14px; font-weight: 600; letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 32;
  color: var(--fg);
}
.answer-body { margin: 3px 0 0; font-size: 14px; line-height: 1.5; color: var(--muted); max-width: 76ch; }
.answer-issues {
  margin: 9px 0 0; padding: 0; list-style: none;
  font-size: 13.5px; line-height: 1.5; color: var(--muted);
}
.answer-issues li { margin-bottom: 5px; padding-left: 16px; position: relative; max-width: 76ch; }
.answer-issues li:last-child { margin-bottom: 0; }
.answer-issues li::before {
  content: ''; position: absolute; left: 2px; top: .6em;
  width: 4px; height: 4px; border-radius: 50%; background: currentColor;
}

/* -------------------------------------------------------------- to do -- */

.todo {
  margin-top: 12px; padding: 13px 15px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-inner);
  background: var(--surface-2);
  max-width: 82ch;
}
.todo strong {
  display: block; font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--faint); margin-bottom: 8px; font-weight: 500;
}
.todo p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--fg); }

/* Each download is its own form, so the row lays out the forms, not the buttons.
   It sits in the left rail under the preview, where the column is narrow — hence
   wrapping, and stretched buttons so a wrapped row does not look ragged. */
.result-actions { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; margin: 14px 0 0; }
.result-actions:empty { display: none; }
.result-actions form { margin: 0; flex: 1 1 auto; }
.result-actions form .btn { width: 100%; }
.result-actions > .btn { flex: 1 1 100%; }

.btn {
  /* inline-flex on the base, not just on .btn-lg: half these buttons are <a>,
     and an inline anchor takes horizontal padding only, so the two element
     types sat at different heights in the same row. */
  display: inline-flex; align-items: center; justify-content: center;
  font: inherit; font-size: 14px; font-weight: 500; letter-spacing: -0.01em;
  border: 1px solid var(--border-strong);
  background: var(--surface-2);
  color: var(--fg);
  border-radius: var(--radius-pill);
  padding: 10px 20px;
  text-decoration: none; white-space: nowrap;
  cursor: pointer;
  transition: background .14s ease, border-color .14s ease, color .14s ease;
}
.btn:hover {
  background: var(--surface-3);
  /* Mixed off --fg rather than the hardcoded rgba(255,255,255,.24) this
     replaces: the same button appears in the light band, where a white edge is
     invisible. Same weight as before against the dark ground. */
  border-color: color-mix(in srgb, var(--fg) 28%, transparent);
  text-decoration: none;
}
.btn-primary {
  background: var(--fg); border-color: var(--fg); color: var(--bg); font-weight: 600;
}
/* Mixed toward the page ground rather than faded with opacity. Opacity took the
   label down with the fill, so the solid button dimmed like a disabled one;
   mixing moves the fill only and leaves the text at full contrast. Both --fg
   and --bg flip inside .band, so this stays correct dark-on-light too. */
.btn-primary:hover {
  background: color-mix(in srgb, var(--fg) 85%, var(--bg));
  border-color: color-mix(in srgb, var(--fg) 85%, var(--bg));
}
.btn-quiet { background: transparent; border-color: transparent; color: var(--muted); }
.btn-quiet:hover { color: var(--fg); background: var(--surface-2); border-color: var(--border); }

/* The hero pair, and any other call to action that leads a section. */
.btn-lg { font-size: 15px; padding: 13px 26px; }

/* Keyboard users get the same affordance the pointer gets. Offset so the ring
   sits outside the pill instead of tracing its inner edge. */
.btn:focus-visible {
  outline: 2px solid var(--fg);
  outline-offset: 2px;
}

.section-heading {
  font-family: var(--font-mono);
  font-size: 12px; letter-spacing: 0.04em; text-transform: uppercase; color: var(--faint);
  margin: 22px 0 10px;
}

/* -------------------------------------------------------------- signer -- */

/* Who signed, on one card above the check list. The values are read straight
   out of the manifest, so the card is styled as evidence — mono for anything
   machine-written — rather than as a badge of approval. */
.signer {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  padding: 24px 26px;
}

.signer-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; padding-bottom: 18px;
  border-bottom: 1px solid var(--border);
}
.signer-name {
  display: block; font-size: 19px; font-weight: 600; letter-spacing: -0.02em;
  font-variation-settings: 'opsz' 32;
  overflow-wrap: anywhere;
}
.signer-role {
  display: block; margin-top: 3px;
  font-family: var(--font-mono); font-size: 11px; color: var(--faint);
}

/* auto-fit rather than a fixed column count: the panel sits in a 1200px shell
   on a desktop and in a phone's width on a phone, and the field list is short
   enough that either extreme looks wrong with a hardcoded number. */
.signer-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(210px, 1fr));
  gap: 18px 30px;
  margin: 20px 0 0;
}
.signer-item.wide { grid-column: 1 / -1; }
.signer-item dt {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--faint); margin-bottom: 5px;
}
.signer-item dd { margin: 0; font-size: 14px; color: var(--fg); }
.signer-item dd.mono { font-family: var(--font-mono); font-size: 12px; color: var(--muted); }
/* Certificate serials and manifest labels are long unbroken strings; without
   this they push the card wider than the page. */
.signer-item dd.break { overflow-wrap: anywhere; }
.signer-qualifier { display: block; margin-top: 3px; font-size: 12px; color: var(--faint); }

.signer-caveat {
  margin: 20px 0 0; padding-top: 16px;
  border-top: 1px solid var(--border);
  font-size: 13px; color: var(--faint); max-width: 78ch;
}

/* -------------------------------------------------------------- checks -- */

/* Two columns of five rather than one stack of ten. Stacked, the tenth check is
   below the fold on any laptop, and not scrolling is the whole point of this
   page. align-items: start so opening one row grows that card alone instead of
   stretching the one beside it to match. */
.checks {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  align-items: start;
  gap: 8px;
  margin-top: 12px;
}

.check {
  border: 1px solid var(--border);
  border-radius: var(--radius-inner);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  overflow: hidden;
}

.check > summary {
  display: flex; align-items: center; gap: 11px;
  padding: 9px 13px; cursor: pointer; list-style: none;
  transition: background .12s ease;
}
.check > summary::-webkit-details-marker { display: none; }
.check > summary:hover { background: var(--surface-2); }

.check .num {
  font-family: var(--font-mono); font-size: 11px; color: var(--faint);
  min-width: 1.6em;
}
.check .heading { flex: 1; min-width: 0; }
/* Both lines are held to one line closed, so every card in the grid is exactly
   the same height and the ten read as a scoreboard rather than a ragged list.
   Nothing is lost: opening the row unwraps both and adds the evidence under
   them, and the pill has already said the part that matters at a glance. */
.check .title,
.check .summary-line {
  display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.check .title {
  font-size: 14px; font-weight: 600; letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 32;
}
.check .summary-line {
  margin-top: 2px; font-size: 12.5px; line-height: 1.4; color: var(--muted);
}
.check[open] .title,
.check[open] .summary-line { white-space: normal; }

.pill {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase; font-weight: 500;
  border-radius: var(--radius-pill); padding: 4px 11px; white-space: nowrap;
  border: 1px solid transparent;
}
.pill.pass { background: var(--pass-bg); color: var(--pass); border-color: var(--pass-line); }
.pill.warn { background: var(--warn-bg); color: var(--warn); border-color: var(--warn-line); }
.pill.fail { background: var(--fail-bg); color: var(--fail); border-color: var(--fail-line); }
.pill.na   { background: var(--na-bg);   color: var(--na);   border-color: var(--na-line); }

/* An open row takes the full width. Stacked into a half-width cell the
   explanation, example and finding run to a very tall card standing next to an
   empty one; across both columns the same content is a couple of paragraphs. */
.check[open] { grid-column: 1 / -1; }

.check-body { padding: 0 13px 15px calc(13px + 1.6em + 11px); font-size: 13.5px; }
/* The remediation text quotes IPTC newscode URLs, which are one unbreakable
   token wider than a column of this grid. The card clips its own overflow, so
   without this the end of the URL is simply not there. */
.check-body p { margin: 0 0 10px; color: var(--muted); max-width: 78ch; overflow-wrap: anywhere; }
.check-body .fix {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-inner); padding: 14px 16px; margin: 14px 0 0;
  max-width: 82ch;
}
.check-body .fix strong {
  display: block; font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--faint); margin-bottom: 8px; font-weight: 500;
}
.check-body .fix p { color: var(--fg); margin: 0; }
.check-body .weight {
  font-family: var(--font-mono); font-size: 11px; color: var(--faint); margin: 14px 0 0;
}

/* The standing explanation of what the check asks, and a worked example. Below
   `.check-body p` on purpose: the question needs to win the colour back from
   the muted default, and at equal specificity that is decided by order. */
.check-body .guide { margin: 0 0 14px; }
.check-body .guide-asks {
  color: var(--fg);
  font-weight: 600; letter-spacing: -0.01em;
  font-variation-settings: 'opsz' 32;
}
.check-body .guide-example {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-inner); padding: 12px 14px; margin-top: 12px;
  max-width: 82ch;
}
.check-body .guide-example p:last-child { margin-bottom: 0; }
.check-body .guide-code {
  margin: 10px 0 0; padding: 11px 12px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 11.5px; line-height: 1.55; color: var(--muted);
  /* The snippets are wrapped to a sensible width in copy.py, but an IPTC
     newscode URL is longer than a column even so — wrap it rather than scroll
     sideways inside an already-nested box. */
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* What this file did, as opposed to what the check is for. The two labels are
   the whole point: unlabelled, the standing explanation and the finding read as
   one continuous paragraph and the reader cannot tell which is about them. */
.check-body .finding { margin: 0; }
.check-body .finding p:last-child { margin-bottom: 0; }
.check-body .guide-example strong,
.check-body .finding strong {
  display: block; font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--faint); margin-bottom: 8px; font-weight: 500;
}

/* --------------------------------------------------------------- drawers -- */

/* The certificate panel and the raw manifest: evidence rather than answer, and
   both long. Collapsed they are two rows at the foot of the pane, which is what
   lets the findings above them fit a screen. */
/* One drawer now, holding everything technical. It was two side by side when
   the ten checks were the page; they are the evidence behind the answer. */
.result-extras { margin-top: 12px; }

.drawer {
  border: 1px solid var(--border);
  border-radius: var(--radius-inner);
  background: var(--surface);
  overflow: hidden;
}
.drawer > summary {
  cursor: pointer; list-style: none;
  padding: 10px 13px;
  font-family: var(--font-mono); font-size: 12px; color: var(--muted);
  transition: color .12s ease, background .12s ease;
}
.drawer > summary::-webkit-details-marker { display: none; }
.drawer > summary::before { content: '› '; color: var(--faint); }
.drawer[open] > summary::before { content: '⌄ '; }
.drawer > summary:hover { color: var(--fg); background: var(--surface-2); }
.drawer-body { padding: 2px 13px 14px; }
.drawer-heading {
  font-family: var(--font-mono);
  font-size: 11px; letter-spacing: 0.04em; text-transform: uppercase;
  color: var(--faint); font-weight: 500;
  margin: 20px 0 10px;
}
.drawer-form { margin: 16px 0 0; }

/* The signer partial is a card in the downloadable report, where it stands
   alone. Inside a drawer it is already in one, so it drops its own frame rather
   than nesting a second border 13px inside the first. */
.drawer .signer {
  border: 0; border-radius: 0; background: none; box-shadow: none; padding: 0;
}

.raw-json {
  margin: 0;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: var(--radius-inner); padding: 14px;
  overflow: auto; max-height: 340px;
  font-size: 12px; line-height: 1.55; color: var(--muted);
}

/* ------------------------------------------------------------- capture -- */

/* The band supplies its own separation, so it takes vertical padding rather
   than the hairline rule the dark sections use. */
.capture { margin-top: 88px; padding: 72px 0; }
.explainer {
  margin-top: 88px; padding-top: 34px; border-top: 1px solid var(--border);
}
.capture h2, .explainer h2 {
  font-size: 24px; font-weight: 600; letter-spacing: -0.025em; margin: 0 0 14px;
}
.capture h2 { font-size: 30px; letter-spacing: -0.03em; }
.capture p, .explainer p { color: var(--muted); max-width: 68ch; }

/* ----------------------------------------------------- account pages -- */

/* allauth renders plain HTML forms — no classes of its own worth hooking, so
   these style the elements directly inside the card rather than by class. */
.auth { max-width: 420px; margin: 0 auto; padding: 64px 0 40px; }
.auth-card {
  background: var(--surface); border: 1px solid var(--border);
  border-radius: var(--radius-card); padding: 28px 26px;
}
.auth-card h1 { font-size: 24px; letter-spacing: -0.025em; margin: 0 0 8px; }
.auth-card p { color: var(--muted); font-size: 14.5px; }
.auth-card form { margin: 18px 0 0; }
.auth-card label { display: block; font-size: 13px; color: var(--muted); margin: 0 0 6px; }
.auth-card input[type='email'],
.auth-card input[type='text'],
.auth-card input[type='password'] {
  font: inherit; font-size: 15px; width: 100%; padding: 11px 14px;
  margin: 0 0 14px;
  background: var(--surface-2); color: var(--fg);
  border: 1px solid var(--border-strong); border-radius: var(--radius-inner);
}
.auth-card input:focus {
  outline: none; border-color: var(--fg); background: var(--surface-3);
}
.auth-card button[type='submit'] {
  /* Same pill as everywhere else, without needing allauth to emit .btn. */
  font: inherit; font-size: 14px; font-weight: 600; letter-spacing: -0.01em;
  display: inline-flex; align-items: center; justify-content: center;
  width: 100%; padding: 11px 20px;
  background: var(--fg); color: var(--bg);
  border: 1px solid var(--fg); border-radius: var(--radius-pill);
  cursor: pointer; transition: background .14s ease, border-color .14s ease;
}
.auth-card button[type='submit']:hover {
  background: color-mix(in srgb, var(--fg) 85%, var(--bg));
  border-color: color-mix(in srgb, var(--fg) 85%, var(--bg));
}
/* "Remember me" is the one checkbox allauth renders, and it arrives as a bare
   input under its own block label — so the label goes inline with it rather
   than sitting above it like the text fields. */
.auth-card input[type='checkbox'] {
  width: 16px; height: 16px; margin: 0 8px 0 0;
  accent-color: var(--fg); vertical-align: middle;
}
.auth-card label:has(input[type='checkbox']),
.auth-card input[type='checkbox'] + label {
  display: inline-flex; align-items: center; gap: 8px; margin: 0 0 16px;
}

.auth-card a { color: var(--fg); text-decoration-color: var(--border-strong); }
.auth-card ul { padding-left: 18px; color: var(--muted); font-size: 13.5px; }
.auth-card .errorlist, .auth-messages {
  list-style: none; padding: 10px 12px; margin: 0 0 16px;
  background: var(--fail-bg); border: 1px solid var(--fail-line);
  border-radius: var(--radius-inner);
  color: var(--fail); font-size: 13.5px;
}
.auth-note {
  margin: 16px 0 0; font-size: 12.5px; line-height: 1.55; color: var(--faint);
  text-align: center;
}

/* ------------------------------------------------- the explanation page -- */

/* No selling above it, so the hero is a heading and a standfirst rather than a
   full first screen. */
.hero-compact { padding: 56px 0 8px; }
.hero-compact h1 { font-size: clamp(32px, 4.6vw, 52px); margin: 0 0 18px; }

/* One check. An <article> because each is self-contained and quotable on its
   own — which is also why every one carries an id: a link to a single check is
   the thing someone actually wants to send. */
.check-entry {
  padding: 26px 0;
  border-top: 1px solid var(--border);
}
.check-entry:first-of-type { border-top: 0; padding-top: 6px; }
.check-entry h3 {
  font-size: 19px; font-weight: 600; letter-spacing: -0.02em;
  margin: 0 0 12px; max-width: 62ch;
  /* The sticky navbar would otherwise land on top of a heading jumped to. */
  scroll-margin-top: 88px;
}
.check-entry > p { margin: 0; }

/* Same box the result panel puts an example in — declared separately because
   that one is scoped to .check-body, and reaching into it from here would tie
   two layouts together that have no reason to move as one. */
.check-entry .guide-example {
  background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius-inner); padding: 14px 16px; margin-top: 16px;
  max-width: 82ch;
}
.check-entry .guide-example p { margin: 0; font-size: 14.5px; }
.check-entry .guide-example-label {
  font-family: var(--font-mono);
  font-size: 10px; letter-spacing: 0.06em; text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 8px !important;
}
.check-entry .guide-code {
  margin: 12px 0 0; padding: 12px 13px;
  background: var(--bg); border: 1px solid var(--border);
  border-radius: 8px;
  font-size: 11.5px; line-height: 1.55; color: var(--muted);
  white-space: pre-wrap; overflow-wrap: anywhere;
}

/* The checker page's only outbound links, under the left rail. Quiet on
   purpose — the page has one job and this is not it — but present even after a
   result has replaced the form that carried the privacy note. */
.check-footnote {
  display: flex; flex-wrap: wrap; gap: 4px 18px;
  margin: 14px 0 0; font-size: 12px; line-height: 1.5;
}
.check-footnote a { color: var(--faint); text-decoration-color: transparent; }
.check-footnote a:hover { color: var(--muted); text-decoration-color: var(--border-strong); }

/* The site's only real internal navigation, now that the navbar carries one
   button. Kept in the footer where a reader who reached the bottom is deciding
   where to go next. */
.footer-links { display: flex; flex-wrap: wrap; gap: 6px 22px; margin: 0 0 18px !important; }
.footer-links a { color: var(--fg); text-decoration-color: var(--border-strong); }

.capture-form { margin-top: 22px; }
.capture-form input[type='email'] {
  font: inherit; font-size: 15px; padding: 11px 18px; width: min(360px, 100%);
  border: 1px solid var(--border-strong); border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--fg);
}
.capture-form input[type='email']::placeholder { color: var(--faint); }
.capture-form input[type='email']:focus {
  outline: none; border-color: rgba(255, 255, 255, 0.4); background: var(--surface-3);
}

.wants { border: 0; padding: 0; margin: 18px 0; display: flex; flex-wrap: wrap; gap: 8px 22px; }
/* --muted, not --faint: this is a form label asking a real question, and at
   --faint it lands around 2.8:1 on either background — under AA in both themes. */
.wants legend { font-size: 13px; color: var(--muted); padding: 0; margin-bottom: 10px; }
.wants label { font-size: 14px; display: flex; align-items: center; gap: 8px; color: var(--muted); }
.wants input[type='checkbox'] { accent-color: var(--fg); }

.capture-status { font-size: 14px; margin: 14px 0 0; min-height: 1.5em; color: var(--muted); }
.capture-status.ok { color: var(--pass); }
.capture-status.error { color: var(--fail); }

.legal { font-size: 14px; }

/* The three questions, spelled out on the landing page in the same order the
   result gives them. */
.plain-list { margin: 0 0 20px; padding: 0; list-style: none; max-width: 72ch; }
.plain-list li {
  position: relative; padding-left: 18px; margin-bottom: 12px;
  color: var(--muted);
}
.plain-list li::before {
  content: ''; position: absolute; left: 2px; top: .68em;
  width: 5px; height: 5px; border-radius: 50%; background: var(--faint);
}
.plain-list strong { color: var(--fg); font-weight: 600; }

/* "This is not an AI detector." Said once, plainly, where someone arriving with
   the wrong expectation will hit it before they reach the checker. */
.callout {
  padding: 16px 18px; margin: 0 0 20px;
  border: 1px solid var(--border-strong); border-radius: var(--radius-card);
  background: var(--surface);
  max-width: 72ch;
}
.callout strong { color: var(--fg); }

/* ----------------------------------------------------------------- faq -- */

.faq { margin-top: 88px; padding-top: 34px; border-top: 1px solid var(--border); }
.faq h2 { font-size: 24px; font-weight: 600; letter-spacing: -0.025em; margin: 0 0 20px; }

/* One card holding the whole set, hairlines between — the same construction as
   .checks, so the two accordions on the page behave and read alike. */
.faq-list {
  border: 1px solid var(--border);
  border-radius: var(--radius-card);
  background: var(--surface);
  box-shadow: var(--shadow-card);
  overflow: hidden;
  max-width: 90ch;
}
.faq-item + .faq-item { border-top: 1px solid var(--border); }

.faq-item > summary {
  display: flex; align-items: center; justify-content: space-between; gap: 18px;
  padding: 18px 22px; cursor: pointer; list-style: none;
  transition: background .12s ease;
}
.faq-item > summary::-webkit-details-marker { display: none; }
.faq-item > summary:hover { background: var(--surface-2); }
.faq-item > summary:focus-visible { outline: 2px solid var(--fg); outline-offset: -2px; }

.faq-question {
  font-size: 16px; font-weight: 600; letter-spacing: -0.015em;
  font-variation-settings: 'opsz' 32;
  max-width: 70ch;
}

/* A plus that becomes a minus. Two bars, one rotated away on open — cheaper
   than a glyph and it does not depend on a font that may still be loading. */
.faq-marker {
  flex: none; position: relative; width: 12px; height: 12px;
}
.faq-marker::before, .faq-marker::after {
  content: ''; position: absolute; left: 0; top: 50%;
  width: 12px; height: 1.5px; border-radius: 2px;
  background: var(--muted);
  transition: transform .18s ease, background .12s ease;
}
.faq-marker::after { transform: rotate(90deg); }
.faq-item[open] .faq-marker::after { transform: rotate(0deg); }
.faq-item[open] .faq-marker::before,
.faq-item[open] .faq-marker::after { background: var(--fg); }

.faq-answer { padding: 0 22px 22px; }
.faq-answer p { margin: 0; font-size: 15px; color: var(--muted); max-width: 76ch; }

@media (prefers-reduced-motion: reduce) {
  .faq-marker::before, .faq-marker::after { transition: none; }
}

/* -------------------------------------------------------------- footer -- */

.site-footer {
  margin-top: 72px; padding-top: 26px;
  font-size: 13px; color: var(--faint);
}
.site-footer .wrap { border-top: 1px solid var(--border); padding-top: 26px; }
.site-footer p { margin: 0 0 12px; max-width: 72ch; }
.site-footer a { color: var(--muted); }

/* ------------------------------------------------- narrow / short screens -- */

/* Below the navbar's own breakpoint the fixed-height app screen has nowhere
   left to put a second column, and a viewport-sized frame on a phone is mostly
   browser chrome. The page gives scrolling back to the document here: the
   no-scroll layout is a promise about a laptop, not about a handset. */
@media (max-width: 880px) {
  body.check-page { height: auto; overflow: visible; padding-bottom: 64px; }

  .check-app,
  .check-app:has(#checks) {
    height: auto;
    grid-template-columns: minmax(0, 100%);
    grid-template-rows: auto;
    padding: 18px 20px 24px;
  }
  .check-app:has(#checks) .check-input,
  .check-app:has(#checks) .result {
    height: auto; overflow: visible;
  }
  /* Back to a chip: full width, the image would push the findings a screen
     down before they are reached. */
  .check-app:has(#checks) .preview { flex-direction: row; align-items: center; }
  .check-app:has(#checks) .preview img { width: 92px; height: 92px; object-fit: cover; }

  /* Stacked, the rail comes first — which puts "Download report" above the
     verdict it is a report of. The rail is dissolved so its children become
     grid items in their own right, and the buttons are ordered to the end:
     picture, findings, then what to do about them.

     `order` cannot reach into .check-input from here — #rail-actions is its
     child and #result is its sibling — so display: contents is what makes the
     two orderable against each other at all. */
  .check-app:has(#checks) .check-input { display: contents; }
  .check-app:has(#checks) #rail-actions { order: 1; margin-top: 0; }

  .checks { grid-template-columns: minmax(0, 1fr); }
  .dropzone { padding: 44px 20px; }
}

/* The answer is sized to fit a 768px viewport outright. Below that it tightens
   rather than drops anything: every line here is either the answer, the thing
   people misread the answer as, or what to do about it, and there is no honest
   way to shorten that list.

   Past the point where tightening runs out, the pane scrolls on its own — never
   the page. The frame stays put whatever happens. */
@media (min-width: 881px) and (max-height: 767px) {
  .verdict { padding: 14px 18px; }
  .verdict .lead { font-size: 15px; margin-bottom: 8px; }
  .verdict .caveat { margin-top: 10px; padding: 10px 13px; font-size: 13.5px; }
  .answers { gap: 6px; margin-top: 10px; }
  .answer { padding: 10px 14px; }
  .answer-body { font-size: 13.5px; }
  .todo { margin-top: 10px; padding: 11px 14px; }
  .result-extras { margin-top: 10px; }
}
@media (min-width: 881px) and (max-height: 640px) {
  .check-app:has(#checks) .preview img { max-height: 30vh; }
}

@media (max-width: 620px) {
  .hero { padding-top: 56px; }
  .lede { font-size: 17px; }
  .verdict { padding: 20px 18px; }
  .check > summary { padding: 13px 14px; gap: 10px; }
  .check-body { padding: 0 14px 16px 14px; }
  .signer { padding: 20px 18px; }
  .faq-item > summary { padding: 16px; gap: 12px; }
  .faq-answer { padding: 0 16px 18px; }
  .hero-actions .btn-lg { flex: 1 1 auto; justify-content: center; }
}
