/* Terminus interface tokens, 2026-09-25 redesign.
 *
 * THE ONE RULE THIS FILE ENFORCES: colour is reserved for findings. The
 * interface itself is black, white and grey.
 *
 * The shipped map already carries this reasoning for cartography -- "Chrome is
 * NEVER a semantic hue", tiles/map.html:96 -- because a control tinted the
 * flood blue collides with a real flood polygon the moment one draws. This
 * file extends the same rule to the whole interface. A realtor scanning a
 * report should be able to say: every coloured thing on this screen is the
 * property telling me something. A blue button breaks that promise, so there
 * are no blue buttons.
 *
 * WHAT THAT BUYS. Four severities have to be separable at a glance, on a
 * phone, in sunlight, by somebody who is not looking carefully. They can only
 * be separable if nothing competes with them. So the accent is ink.
 */

:root {
  color-scheme: light;

  /* ---- Interface. Neutral by rule, see the note above. ---------------- */
  --paper:      #ffffff;   /* the sheet itself */
  --ground:     #f4f5f7;   /* behind the sheet */
  --sunk:       #eceef1;   /* inside a well: inputs, code, empty states */
  --ink:        #14161a;   /* body and headings */
  --ink-2:      #454b56;   /* secondary text that is still content */
  --muted:      #6b7280;   /* captions, sources, metadata */
  --rule:       #e2e5ea;   /* hairlines between rows */
  --rule-hard:  #c9cdd4;   /* a border that has to be seen */
  --focus:      #14161a;   /* focus ring. Ink, like everything else. */

  /* ---- Findings. The only hues on the page. --------------------------- */
  /* ACT: this costs money, a permit, an inspection or a disclosure. */
  --act:        #a8301a;
  --act-soft:   #fdf0ed;
  --act-edge:   #f0c5ba;
  /* NOTE: true and worth knowing, but it does not by itself create a duty. */
  --note:       #8a5a06;
  --note-soft:  #fdf5e7;
  --note-edge:  #edd6a8;
  /* CLEAR: the layer answered, and the answer is that there is nothing here.
     This is a FINDING. It is the product's whole thesis that it is not the
     same as silence, so it gets a colour of its own. */
  --clear:      #2c6a45;
  --clear-soft: #eef6f1;
  --clear-edge: #bcdcc9;
  /* UNVERIFIED: the layer could not answer. It gets NO HUE, deliberately.
     A fourth colour beside three others is read as a fourth kind of answer,
     and this is not an answer at all. It is grey plus a hatch, so it reads as
     a hole in the page rather than as a value. See --hatch below. */
  --unsure:     #6b7280;
  --unsure-soft:#f1f2f4;
  --unsure-edge:#cfd3d9;

  /* The texture that marks an unanswered layer.
     I CLAIMED MORE FOR THIS THAN IT DELIVERS. The design note said the
     unanswered tier differs "three ways at once: hue, label and texture".
     A reviewer measured the stripe at 1.17:1 against its own ground, and I
     recomputed it from these tokens and got 1.165:1. At that ratio it is
     not a second channel; it is a suggestion of one.
     Raised to carry a visible difference. It is still a SECONDARY cue: the
     label and the absence of hue do the work, and the texture supports
     them. It is not a substitute for either, and no state is distinguished
     by texture alone. */
  --hatch: repeating-linear-gradient(
    45deg,
    transparent 0 5px,
    rgb(107 114 128 / 0.38) 5px 7px
  );

  /* ---- Type. ---------------------------------------------------------- */
  /* Geist is the display and text face. It is loaded from a CDN in this
     PROTOTYPE only; shipping means vendoring the woff2 next to the map's
     existing tiles/fonts, because this product must render with no third
     party in the path. The stack below is a real fallback, not a formality:
     Segoe UI Variable on Windows and SF on macOS both hold the design. */
  --font: "Geist", ui-sans-serif, -apple-system, "Segoe UI Variable Text",
          "Segoe UI", system-ui, sans-serif;
  /* Figures, parcel ids, coordinates and money. Tabular by default so a
     column of dollar amounts lines up on the decimal. */
  --mono: "Geist Mono", ui-monospace, "Cascadia Mono", "SF Mono", Menlo,
          Consolas, monospace;

  /* A 1.25 ramp with real gaps. The shipped page runs 10, 11, 12, 13, 15, 16,
     18 -- seven steps inside 1.8x, which is why nothing on it has a hierarchy.
     Nothing here is below 12px, and no interactive text is below 13px. */
  --t-micro:  12px;   /* source lines, legal, table captions */
  --t-small:  14px;   /* secondary UI text, controls */
  --t-body:   16px;   /* the default. Findings prose reads at this size. */
  --t-lead:   18px;   /* a finding's answer */
  --t-h3:     22px;
  --t-h2:     28px;
  --t-h1:     36px;
  --t-display: 46px;  /* the address on the client sheet, and nothing else */

  /* ---- Space. 4px base. ----------------------------------------------- */
  --s1: 4px;  --s2: 8px;  --s3: 12px; --s4: 16px;
  --s5: 24px; --s6: 32px; --s7: 48px; --s8: 72px;

  /* ---- Shape. Two radii, and the rule for which is which. -------------
     Controls -- anything you click or type into -- are 8px.
     Containers -- cards, panels, the sheet -- are 12px.
     Nothing is a pill and nothing is square. */
  --r-control: 8px;
  --r-card:    12px;

  /* ---- Elevation. One shadow, tinted to the ground, never black. ------
     And never paired with a visible border: a hairline plus a 32px blur is
     the generated-UI signature the detector flags on the shipped page. */
  --lift: 0 1px 2px rgb(20 22 26 / 0.05), 0 8px 24px -12px rgb(20 22 26 / 0.18);
  /* For anything that ALSO carries a visible border. A hairline paired with a
     wide diffuse blur is the generated-UI signature the detector flags on the
     shipped page (`1px border + 32px shadow blur`). An element that floats
     over a photograph needs the defined edge, so it takes the tight shadow
     and gives up the glow. */
  --lift-tight: 0 2px 6px rgb(20 22 26 / 0.16);

  /* Text that sits on a dark scrim over a photograph. The scrim is dark in
     BOTH themes, so this cannot be --paper: in dark mode --paper is #16181c
     and the label renders dark on dark at a 1.0:1 ratio. Measured. */
  --on-scrim: #f6f7f8;

  /* ---- Motion. Feedback only. No decoration, nothing perpetual. ------- */
  --quick: 120ms cubic-bezier(0.2, 0, 0.2, 1);
  --ease:  220ms cubic-bezier(0.16, 1, 0.3, 1);
}

:root[data-theme="dark"] {
  color-scheme: dark;
  --paper:      #16181c;
  --ground:     #0e1013;
  --sunk:       #1e2127;
  --ink:        #e8eaed;
  --ink-2:      #b3b9c4;
  --muted:      #8b929e;
  --rule:       #2a2e36;
  --rule-hard:  #3d434d;
  --focus:      #e8eaed;

  /* Lifted for a dark ground, same three meanings, same separation. */
  --act:        #f08a70;
  --act-soft:   #2a1714;
  --act-edge:   #5b2a20;
  --note:       #e0aa4e;
  --note-soft:  #261d0e;
  --note-edge:  #56401a;
  --clear:      #6cbd8e;
  --clear-soft: #10201a;
  --clear-edge: #244e39;
  --unsure:     #8b929e;
  --unsure-soft:#1c1f25;
  --unsure-edge:#383d46;

  --hatch: repeating-linear-gradient(
    45deg,
    transparent 0 5px,
    rgb(139 146 158 / 0.40) 5px 7px
  );

  --lift: 0 1px 2px rgb(0 0 0 / 0.4), 0 8px 24px -12px rgb(0 0 0 / 0.6);
  --lift-tight: 0 2px 6px rgb(0 0 0 / 0.5);
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    color-scheme: dark;
    --paper:#16181c; --ground:#0e1013; --sunk:#1e2127;
    --ink:#e8eaed; --ink-2:#b3b9c4; --muted:#8b929e;
    --rule:#2a2e36; --rule-hard:#3d434d; --focus:#e8eaed;
    --act:#f08a70; --act-soft:#2a1714; --act-edge:#5b2a20;
    --note:#e0aa4e; --note-soft:#261d0e; --note-edge:#56401a;
    --clear:#6cbd8e; --clear-soft:#10201a; --clear-edge:#244e39;
    --unsure:#8b929e; --unsure-soft:#1c1f25; --unsure-edge:#383d46;
    --hatch: repeating-linear-gradient(45deg, transparent 0 5px,
             rgb(139 146 158 / 0.40) 5px 7px);
    --lift: 0 1px 2px rgb(0 0 0 / 0.4), 0 8px 24px -12px rgb(0 0 0 / 0.6);
    --lift-tight: 0 2px 6px rgb(0 0 0 / 0.5);
  }
}

/* ===================================================================== */
/* Base                                                                  */
/* ===================================================================== */

*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--font);
  font-size: var(--t-body);
  line-height: 1.55;
  font-feature-settings: "ss01", "cv01";
  -webkit-font-smoothing: antialiased;
}

h1, h2, h3, h4 { margin: 0; font-weight: 600; letter-spacing: -0.015em; }
h1 { font-size: var(--t-h1); line-height: 1.15; }
h2 { font-size: var(--t-h2); line-height: 1.2; }
h3 { font-size: var(--t-h3); line-height: 1.3; }
p  { margin: 0 0 var(--s3); max-width: 68ch; }
p:last-child { margin-bottom: 0; }

a { color: inherit; text-underline-offset: 3px; text-decoration-thickness: 1px; }

:where(button, a, input, select, textarea, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--r-control);
}

/* MONO IS FOR IDENTIFIERS AND COLUMNS, NOT FOR PROSE.
   A parcel id and a column of dollar amounts want the fixed advance: they
   are compared with each other, and the eye needs the digits to line up.
   A figure sitting inside a sentence does not. Geist Mono's advance is wide
   enough that "0.13 ac" set inline reads as "0 . 13", with the decimal
   floating between two gaps. So prose figures take the text face with
   tabular digits, which keeps them aligned without the spacing. */
.num, .mono { font-family: var(--mono); font-variant-numeric: tabular-nums; }
.fig { font-variant-numeric: tabular-nums; }

/* ===================================================================== */
/* Controls                                                              */
/* ===================================================================== */

.btn {
  font: inherit;
  font-size: var(--t-small);
  font-weight: 500;
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  /* 40px tall. A realtor uses this standing at a kerb, on a phone, one
     handed. 44px is the Apple target and 40 plus the 2px focus offset is
     the practical floor; nothing here is smaller. */
  min-height: 40px;
  padding: 0 var(--s4);
  border-radius: var(--r-control);
  border: 1px solid var(--rule-hard);
  background: var(--paper);
  color: var(--ink);
  cursor: pointer;
  transition: background var(--quick), border-color var(--quick),
              transform var(--quick);
}
.btn:hover { background: var(--sunk); }
.btn:active { transform: translateY(1px); }

.btn--primary {
  background: var(--ink);
  border-color: var(--ink);
  /* Ink fill needs paper text. In dark mode --ink is light, so the label
     takes --paper and the pair inverts together. Never a hard-coded white. */
  color: var(--paper);
}
.btn--primary:hover { background: var(--ink-2); border-color: var(--ink-2); }

.btn--quiet { border-color: transparent; background: transparent; color: var(--ink-2); }
.btn--quiet:hover { background: var(--sunk); color: var(--ink); }

.field {
  display: block;
  width: 100%;
  font: inherit;
  min-height: 44px;
  padding: 0 var(--s4);
  border-radius: var(--r-control);
  border: 1px solid var(--rule-hard);
  background: var(--paper);
  color: var(--ink);
}
.field::placeholder { color: var(--muted); }
.field:focus { outline: 2px solid var(--focus); outline-offset: -1px; border-color: var(--focus); }

.label {
  display: block;
  font-size: var(--t-small);
  font-weight: 500;
  color: var(--ink-2);
  margin-bottom: var(--s2);
}

/* ===================================================================== */
/* The severity ladder                                                   */
/* ===================================================================== */
/*
 * Four states, and the realtor has to tell them apart without reading.
 *
 *   act       something here costs money, a permit or a disclosure
 *   note      true and worth knowing, no duty attached
 *   clear     the layer answered, and there is nothing here
 *   unsure    the layer did not answer. NOT a finding of nothing.
 *
 * `clear` and `unsure` are the pair this product exists to keep apart, so
 * they differ by hue AND by texture AND by label, three ways at once.
 */

.sev { --sev-ink: var(--muted); --sev-soft: var(--unsure-soft); --sev-edge: var(--unsure-edge); }
.sev--act   { --sev-ink: var(--act);   --sev-soft: var(--act-soft);   --sev-edge: var(--act-edge); }
.sev--note  { --sev-ink: var(--note);  --sev-soft: var(--note-soft);  --sev-edge: var(--note-edge); }
.sev--clear { --sev-ink: var(--clear); --sev-soft: var(--clear-soft); --sev-edge: var(--clear-edge); }
.sev--unsure{ --sev-ink: var(--unsure);--sev-soft: var(--unsure-soft);--sev-edge: var(--unsure-edge); }

.chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  font-size: var(--t-micro);
  font-weight: 500;
  line-height: 1;
  padding: 5px var(--s2) 5px 7px;
  border-radius: var(--r-control);
  border: 1px solid var(--sev-edge);
  background: var(--sev-soft);
  color: var(--sev-ink);
  white-space: nowrap;
}
.chip::before {
  content: "";
  width: 7px; height: 7px;
  border-radius: 2px;
  background: var(--sev-ink);
  flex: none;
}
/* THE UNANSWERED STATE'S SECOND CHANNEL IS A SHAPE, NOT A TINT.
   The first version leaned on the hatch, which measured 1.17:1 and was
   therefore not a channel at all. A DASHED border is unambiguous at any
   contrast, survives a monochrome laser, and survives every form of colour
   blindness, because it is a difference in form rather than in hue.
   Order of strength, and the page should be readable if any one is lost:
     1. the label, which says "Not answered" in words
     2. the dashed edge
     3. the absence of a hue the other three tiers have
     4. the hatch, at 1.60:1 light and 1.94:1 dark, which is a hint */
.sev--unsure .chip {
  border-style: dashed;
  background-image: var(--hatch);
}
.sev--unsure .chip::before {
  background: transparent;
  border: 1.5px dashed var(--unsure);
  border-radius: 1px;
}

/* A CHIP ON EVERY ROW IS NOT A HIERARCHY. The first build put a filled chip
   beside all four severities, and a page with thirteen amber chips reads as
   thirteen alarms. Only the two tiers that ask the reader to DO something
   keep the fill: `act`, which attaches a duty, and `unsure`, which attaches
   an unchecked subject. `note` and `clear` drop to a dot and a label on the
   page ground, so they recede and let the other two carry the page. The
   spine on the left still colours all four, so nothing loses its rank. */
.sev--note .chip, .sev--clear .chip {
  background: transparent;
  border-color: transparent;
  color: var(--muted);
  padding-left: 0;
}

/* ===================================================================== */
/* Finding rows                                                          */
/* ===================================================================== */
/*
 * ANSWER FIRST. The shipped page puts the layer name in the heading slot and
 * the answer in a right-hand column, so a realtor scanning it reads a list of
 * government dataset names. Here the answer is the heading and the layer is
 * the caption, because the answer is the thing being bought.
 */

.finding {
  display: grid;
  grid-template-columns: 3px 1fr;
  gap: 0 var(--s4);
  padding: var(--s4) 0;
  border-bottom: 1px solid var(--rule);
}
.finding:last-child { border-bottom: 0; }

.finding__spine {
  border-radius: 2px;
  background: var(--sev-ink);
  /* An unanswered layer gets a broken spine rather than a solid one. */
}
.sev--unsure .finding__spine { background: transparent; background-image: var(--hatch); }
.sev--clear  .finding__spine { opacity: 0.55; }

.finding__answer {
  font-size: var(--t-lead);
  font-weight: 600;
  line-height: 1.3;
  letter-spacing: -0.01em;
  margin: 0;
}
.sev--act .finding__answer   { color: var(--act); }
.sev--unsure .finding__answer { color: var(--unsure); font-style: italic; }

.finding__layer {
  font-size: var(--t-small);
  color: var(--muted);
  margin: 2px 0 0;
}
.finding__detail { margin: var(--s2) 0 0; color: var(--ink-2); }

.finding__note {
  margin: var(--s3) 0 0;
  padding: var(--s3) var(--s4);
  border-radius: var(--r-control);
  background: var(--sunk);
  color: var(--ink-2);
  font-size: var(--t-small);
}

.finding__source {
  margin: var(--s3) 0 0;
  font-size: var(--t-micro);
  color: var(--muted);
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1) var(--s3);
}

/* ===================================================================== */
/* Layout helpers                                                        */
/* ===================================================================== */

.card {
  background: var(--paper);
  border-radius: var(--r-card);
  border: 1px solid var(--rule);
  padding: var(--s5);
}

.stack > * + * { margin-top: var(--s4); }
.wrap { width: min(100% - 2rem, 1180px); margin-inline: auto; }

.section-title {
  font-size: var(--t-small);
  font-weight: 600;
  color: var(--ink-2);
  margin: 0 0 var(--s3);
  padding-bottom: var(--s2);
  border-bottom: 1px solid var(--rule);
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: var(--s3);
}
.section-title span { font-weight: 400; color: var(--muted); font-size: var(--t-micro); }

.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0;
  margin: -1px; overflow: hidden; clip-path: inset(50%); white-space: nowrap;
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    transition-duration: 1ms !important;
    scroll-behavior: auto !important;
  }
}
