/* /check — the moment bytes become facts.
 *
 * The organising idea: the progress indicator IS the coverage bar. One object,
 * two acts.
 *
 *   Act 1 — the trough fills with undifferentiated ink, scaled to the 4 MB
 *           ceiling, so headroom is visible rather than stated.
 *   Act 2 — it rescales to the document and the ink RESOLVES into structure.
 *
 * Nothing appears that was not already there. The drop target does not get
 * replaced by a result; it becomes one. */

.wrap { max-width: 760px; margin: 0 auto; padding: 0 24px; }

.head { padding: 34px 0 4px; display: flex; flex-direction: column; gap: 9px; }
.head h1 {
  margin: 0; font-size: clamp(26px, 5vw, 32px); font-weight: 600;
  letter-spacing: -0.03em; line-height: 1.15; color: var(--fg);
}
.head p {
  margin: 0; font-size: 15.5px; line-height: 1.6; color: var(--muted);
  max-width: 56ch; text-wrap: pretty;
}

/* --- The instrument ----------------------------------------------------- */

.rig { padding: 26px 0 0; }

/* The byte count — not a percentage, not a spinner. The one number this page
   actually knows. Absent until there is a file to count. */
.tally { display: flex; align-items: baseline; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
[data-phase="idle"] .tally { display: none; }
.tally .n {
  font-size: clamp(34px, 7vw, 46px); font-weight: 600; letter-spacing: -0.035em;
  line-height: 1; font-variant-numeric: tabular-nums; color: var(--fg);
}
.tally .u { font-size: 15px; color: var(--muted); }
.tally .name {
  font-size: 13px; color: var(--faint); margin-left: auto;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 22ch;
}

/* At rest a drop target: tall, dashed, raised, inviting. The moment a file
   lands it compresses into an instrument: short, recessed, hairlined. */
.trough {
  position: relative; display: block; width: 100%; height: 148px;
  border-radius: var(--r-card); background: var(--card); box-shadow: var(--lift);
  outline: 2px dashed var(--line-strong); outline-offset: -2px;
  overflow: hidden; cursor: pointer;
  transition: height 0.42s var(--ease), background 0.3s ease,
              outline-color 0.15s ease, box-shadow 0.3s ease,
              border-radius 0.42s var(--ease);
}
.trough:hover { outline-color: var(--signal-line); }
.trough:focus-within { outline: 2px solid var(--signal); outline-offset: -2px; }
.trough:not([data-gated])[data-over] { outline: 2px solid var(--signal); outline-offset: -2px; background: var(--signal-dim); }

[data-phase]:not([data-phase="idle"]) .trough {
  height: 74px; border-radius: var(--r-control); background: var(--well);
  box-shadow: var(--inset-hi), var(--drop);
  outline: 1px solid var(--line-strong); outline-offset: -1px; cursor: default;
}
/* A refused file leaves the uploader open. The notice says what was wrong, and the next
   thing anyone wants to do is drop a different file — a rail collapsed to 74px reads as
   "closed" and makes them hunt for the reset button to get back to where they started. */
[data-phase="over"] .trough {
  height: 148px; border-radius: var(--r-card); background: var(--card);
  box-shadow: var(--lift); outline: 2px dashed var(--line-strong); outline-offset: -2px;
  cursor: pointer;
}
[data-phase="over"] .trough:hover { outline-color: var(--signal-line); }

[data-phase="resolved"] .trough { cursor: crosshair; }
[data-phase="resolved"] .trough:hover { outline-color: var(--signal-line); }

/* Visually hidden but focusable — the native control carries the keyboard and
   screen-reader behaviour, so nothing here needs a fake button role. */
.trough input {
  position: absolute; width: 1px; height: 1px; opacity: 0;
  top: 50%; left: 50%; pointer-events: none;
}

.invite {
  position: absolute; inset: 0; z-index: 2;
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 7px; padding: 20px; text-align: center; pointer-events: none;
  transition: opacity 0.22s ease;
}
/* An empty byte ribbon: the mark's runs drawn hollow, waiting to be filled. */
.invite .glyph { display: flex; gap: 4px; align-items: center; margin-bottom: 4px; opacity: 0.55; }
.invite .glyph i { display: block; height: 9px; border-radius: 1px; box-shadow: inset 0 0 0 1.5px var(--muted); }
.invite .glyph i:nth-child(1) { width: 26px; }
.invite .glyph i:nth-child(2) { width: 11px; }
.invite .glyph i:nth-child(3) { width: 14px; }
.invite .l { font-size: 16.5px; font-weight: 600; color: var(--fg); }
.invite .s { font-size: 13px; color: var(--muted); max-width: 40ch; line-height: 1.5; }
.trough[data-over] .invite .l { color: var(--signal); }
[data-phase]:not([data-phase="idle"]) .invite { opacity: 0; }

/* Act 1: one solid block of ink, scaled against the ceiling. */
.fill {
  position: absolute; inset: 0 auto 0 0; width: 0; background: var(--bar-ink);
  transition: width 0.34s var(--ease), opacity 0.42s ease;
}

/* Act 2: the same extent, separated into structure. */
.regions { position: absolute; inset: 0; opacity: 0; transition: opacity 0.42s ease; }
/* Extent without a coverage claim — an unsigned document has no covered region,
   and colouring one in would assert something the file does not say. */
.seg.extent { background: var(--bar-ink); opacity: 0.55; }
.tick {
  position: absolute; top: 0; bottom: 0; width: 1px; background: var(--paper);
  opacity: 0; transition: opacity 0.42s ease 0.1s;
}
[data-phase="resolved"] .regions { opacity: 1; }
[data-phase="resolved"] .fill { opacity: 0; }
[data-phase="resolved"] .tick { opacity: 0.75; }

/* The ceiling end-stop. Present only while the scale IS the ceiling. */
.endstop {
  position: absolute; top: 0; bottom: 0; right: 0; width: 2px;
  background: var(--line-strong); opacity: 0; transition: opacity 0.3s ease;
}
[data-phase="reading"] .endstop, [data-phase="analysing"] .endstop, [data-phase="over"] .endstop { opacity: 1; }
[data-phase="over"] .endstop { background: var(--fg); }

/* One pass, left to right — the engine makes exactly one. */
.sweep {
  position: absolute; top: 0; bottom: 0; width: 96px;
  background: linear-gradient(90deg, transparent, var(--signal-dim), transparent);
  opacity: 0; pointer-events: none;
}
[data-phase="analysing"] .sweep { opacity: 1; animation: pass 1s var(--ease) infinite; }
@keyframes pass { from { left: -96px; } to { left: 100%; } }

.cursor {
  position: absolute; top: 0; bottom: 0; width: 1px; background: var(--signal);
  opacity: 0; transition: opacity 0.13s ease; pointer-events: none;
}
[data-phase="resolved"] .trough:hover .cursor { opacity: 1; }

/* A finding, hovered, marks its own extent on the map. */
.marker {
  position: absolute; top: 0; bottom: 0; background: var(--signal-dim);
  box-shadow: inset 1px 0 0 var(--signal), inset -1px 0 0 var(--signal);
  opacity: 0; transition: opacity 0.15s ease; pointer-events: none;
}
.marker[data-on] { opacity: 1; }

/* The right label changes meaning between acts, so it crossfades rather than
   swapping. */
.scale {
  display: flex; justify-content: space-between; align-items: baseline; gap: 12px;
  margin-top: 9px; font-family: var(--mono); font-size: 11.5px;
  font-variant-numeric: tabular-nums; color: var(--faint);
}
.scale .right { position: relative; text-align: right; }
.scale .right span { display: block; transition: opacity 0.3s ease; }
/* Absolutely positioned, so it has no width of its own to wrap inside: left to itself it
   breaks after the digits and drops the word onto the readout below. It is one short label
   and belongs on one line. */
.scale .right .ceiling { position: absolute; inset: 0 0 auto auto; white-space: nowrap; }
[data-phase="resolved"] .scale .right .ceiling { opacity: 0; }
[data-phase="idle"] .scale .right .total,
[data-phase="reading"] .scale .right .total,
[data-phase="analysing"] .scale .right .total,
[data-phase="over"] .scale .right .total { opacity: 0; }

.rig .readout {
  margin: 16px 0 0; min-height: 20px; font-family: var(--mono); font-size: 12.5px;
  letter-spacing: 0.01em; color: var(--muted); overflow-wrap: anywhere;
}
.rig .readout b { font-weight: 400; color: var(--fg); }
.rig .readout .live { color: var(--signal); }
.rig .readout .sep { color: var(--faint); margin: 0 7px; }

.rig .legend { margin-top: 12px; }

/* --- Promise ------------------------------------------------------------- */

.promise {
  margin-top: 30px; border-radius: var(--r-card); background: var(--card);
  box-shadow: var(--lift); overflow: hidden;
}
.promise div {
  display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 14px;
  padding: 11px 16px; font-size: 13px; line-height: 1.5;
}
.promise div + div { border-top: 1px solid var(--line); }
.promise b { font-weight: 600; color: var(--fg); }
.promise span { color: var(--muted); }

/* --- Findings ------------------------------------------------------------ */

.findings { margin-top: 38px; display: flex; flex-direction: column; gap: 26px; }
.section { display: flex; flex-direction: column; gap: 12px; }
.section > h2 { margin: 0; font-size: 17px; font-weight: 600; letter-spacing: -0.01em; color: var(--fg); }
.section > .lead { margin: -3px 0 0; font-size: 13px; color: var(--faint); max-width: 60ch; }

.finding {
  background: var(--card); border-radius: var(--r-card); padding: 16px 18px;
  display: flex; flex-direction: column; gap: 12px; box-shadow: var(--lift);
  transition: box-shadow 0.15s ease;
}
/* Anchored findings advertise themselves as anchored. */
.finding[data-span]:hover, .finding[data-span]:focus-within {
  box-shadow: var(--lift), inset 0 0 0 1px var(--signal-line);
}
.finding[data-span] .statement::after {
  content: "on the map above"; display: block; margin-top: 6px;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: 0.04em;
  color: var(--faint); opacity: 0; transition: opacity 0.15s ease;
}
.finding[data-span]:hover .statement::after,
.finding[data-span]:focus-within .statement::after { opacity: 1; color: var(--signal); }

.statement {
  margin: 0; font-size: 14.5px; line-height: 1.5; color: var(--fg);
  text-wrap: pretty; overflow-wrap: anywhere;
}
.oid {
  display: inline-block; margin: 0; font-family: var(--mono); font-size: 11.5px;
  letter-spacing: 0.02em; color: var(--muted); align-self: flex-start;
}
.oid:hover { color: var(--fg); }

.group-head { display: flex; align-items: baseline; justify-content: space-between; gap: 14px; flex-wrap: wrap; }
.group-head .count { font-size: 12px; color: var(--faint); font-variant-numeric: tabular-nums; }
.occurrence { padding: 13px 0; border-top: 1px solid var(--line); }
.occurrence:first-child { border-top: 0; padding-top: 0; }
.occurrence p { margin: 0; }

.ev { background: var(--well); border-radius: var(--r-control); padding: 11px 13px; }
.ev dl {
  display: grid; grid-template-columns: max-content minmax(0, 1fr);
  gap: 6px 16px; margin: 0; font-size: 12px;
}
.ev dt { font-family: var(--mono); color: var(--faint); }
.ev dd {
  margin: 0; font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--body); overflow-wrap: anywhere; line-height: 1.6;
}

/* Both readings. Same type, same weight, same surface, fixed order. */
.readings {
  display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
  padding-top: 12px; border-top: 1px solid var(--line);
}
.readings h3 { margin: 0 0 3px; font-size: 11.5px; font-weight: 500; color: var(--muted); }
.readings p { margin: 0; font-size: 12.5px; line-height: 1.45; color: var(--body); }

.summary {
  display: grid; grid-template-columns: max-content minmax(0, 1fr); margin: 0;
  background: var(--card); border-radius: var(--r-card); overflow: hidden; box-shadow: var(--lift);
}
.summary dt, .summary dd { margin: 0; padding: 9px 16px; font-size: 12.5px; }
.summary dt { color: var(--muted); white-space: nowrap; }
.summary dd {
  font-family: var(--mono); font-variant-numeric: tabular-nums;
  color: var(--fg); overflow-wrap: anywhere;
}
.summary dd.plain { font-family: var(--sans); color: var(--body); }
.summary .r { grid-column: 1 / -1; height: 1px; background: var(--line); }

/* --- Notices ------------------------------------------------------------- */

/* A transport failure is not a finding: ordinary type, ordinary surface, no
   colour, no icon. */
.notice {
  margin-top: 30px; background: var(--card); border-radius: var(--r-card);
  padding: 19px 21px; box-shadow: var(--lift);
  display: flex; flex-direction: column; gap: 9px;
}
.notice h2 { margin: 0; font-size: 16px; font-weight: 600; color: var(--fg); }
.notice p { margin: 0; font-size: 14px; line-height: 1.55; color: var(--body); max-width: 58ch; }
.notice .hint { font-size: 13px; color: var(--muted); }
.notice .row { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 5px; }
.notice code {
  font-family: var(--mono); font-size: 12px; color: var(--fg);
  background: var(--card-2); padding: 1px 5px; border-radius: 4px;
}

.btn {
  display: inline-flex; align-items: center; padding: 8px 16px; border: 0;
  border-radius: var(--r-control); background: var(--signal); color: var(--signal-ink);
  font: inherit; font-size: 13.5px; font-weight: 590; cursor: pointer;
}
.btn:hover { background: var(--signal-press); }
.btn:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }
.btn.ghost { background: var(--card-2); color: var(--fg); }

.again {
  margin-top: 26px; font-size: 13px; color: var(--faint);
  display: flex; flex-wrap: wrap; gap: 8px; align-items: baseline;
}
.again button {
  border: 0; background: none; padding: 0; font: inherit; font-size: 13px;
  color: var(--body); cursor: pointer; border-radius: 3px;
}
.again button:hover { color: var(--signal); }
.again button:focus-visible { outline: 2px solid var(--signal); outline-offset: 3px; }

@media (max-width: 560px) {
  .wrap { padding: 0 16px; }
  .readings { grid-template-columns: 1fr; gap: 13px; }
  .promise div { grid-template-columns: minmax(0, 1fr); gap: 2px; }
  .tally .name { margin-left: 0; flex-basis: 100%; max-width: none; }
}

@media print { .promise, .again, .trough { display: none !important; } }

/* --- The gate ------------------------------------------------------------
 *
 * What the uploader looks like when there is no test capacity. It covers the trough
 * rather than removing it: the page still shows what it is and what it does, and says
 * when it can do it.
 *
 * The texture is the system's own stipple, unchanged — `--bar-ink` dots at 4px over
 * `--well`. Its documented meaning is absence of ink, which is what this state is. It is
 * the one place that vocabulary appears outside the coverage bar, and it appears before
 * any file exists, so it cannot be read as a claim about a document.
 *
 * Ships visible, in markup, and is removed once the page has asked. A cached page that
 * opened by default would invite a file we would then refuse. */
/* A grid laid over the trough, not in place of it. The uploader still looks like itself —
   the invitation is still there, just quietened — because a reader needs to recognise what
   is unavailable before "unavailable" means anything. Faint on purpose: at rest this is a
   surface condition, not an announcement. */
.gate {
  position: absolute; inset: 0; z-index: 4; pointer-events: none;
  background-image: radial-gradient(circle at center, var(--bar-ink) 0.8px, transparent 1px);
  background-size: 5px 5px;
  opacity: 0;
  transition: opacity 0.24s var(--ease), background-image 0.18s ease;
}
.trough[data-gated] { cursor: not-allowed; outline-color: var(--line); }
.trough[data-gated] .invite { opacity: 0.4; }
.trough[data-gated] .gate { opacity: 0.28; }

/* Reaching for it says so. Only while a file is actually over the trough, so the colour is
   always an answer to something the reader just did and never a state the page sits in. */
.trough[data-gated][data-over] .gate {
  opacity: 0.85;
  background-image: radial-gradient(circle at center, var(--blocked-ink) 0.95px, transparent 1.15px);
}
.trough[data-gated][data-over] {
  outline: 2px solid color-mix(in oklab, var(--blocked-ink) 70%, transparent);
  outline-offset: -2px;
  background: color-mix(in oklab, var(--blocked-ink) 7%, var(--card));
}

/* --- What the wait says, above the uploader ------------------------------------------
 *
 * Outside the trough, because a message printed on the thing it is about has nowhere
 * legible to sit and competes with the texture underneath it. */
.waiting {
  display: flex; flex-wrap: wrap; align-items: center; gap: 4px 10px;
  margin: 0 0 12px; color: var(--blocked-ink);
}

/* Stroked from `currentColor`, so the glyph takes the amber of the line it belongs to and
   nothing has to name the colour twice. */
.waiting .icon { display: block; width: 18px; height: 18px; flex: none; }
.waiting .icon [stroke] {
  stroke: currentColor; stroke-width: 1.75;
  stroke-linecap: square; stroke-linejoin: miter; fill: none;
}
/* Amber, matching the grid it explains. This is the one line on the page allowed a colour,
   and it is allowed one because it describes the reader's own situation rather than
   anything about a document. */
.waiting .l { font-size: 14px; font-weight: 500; color: var(--blocked-ink); }

/* Sans with tabular figures, not mono. Mono is reserved for a value you would copy,
   compare, or paste into a hex editor; a wait is a description. */
.waiting .wait { font-size: 13px; color: var(--muted); }
.waiting .clock {
  font-size: 20px; font-weight: 600; letter-spacing: -0.02em;
  font-variant-numeric: tabular-nums; color: var(--blocked-ink);
}
.waiting .clock:empty { display: none; }
.waiting .why { flex-basis: 100%; font-size: 13px; line-height: 1.5; color: var(--muted); max-width: 62ch; }

/* Once a file is in flight the gate has nothing to say, and the trough has a job. */
[data-phase]:not([data-phase="idle"]) .gate { opacity: 0; visibility: hidden; }
