/* ─────────────────────────────────────────────────────────────
   Layout, components, and the redaction bar.
   ───────────────────────────────────────────────────────────── */

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

html,
body {
  margin: 0;
  padding: 0;
}

/* The browser's own `[hidden] { display: none }` is a plain element rule, so ANY
   author rule that sets `display` beats it. Half this stylesheet sets
   `display: flex`, so `hidden` silently stopped working on those elements — the
   conditional contact fields rendered no matter which channels were ticked.
   One global rule, so `hidden` means hidden everywhere. */
[hidden] {
  display: none !important;
}

body {
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-ui);
  font-size: var(--t-md);
  line-height: 1.5;
  -webkit-font-smoothing: antialiased;
}

/* 1.5 is tuned to Archivo's Latin. Sinhala and Tamil stack marks above and
   below the baseline, so the same leading crowds them — this is the whole-app
   counterpart to the hero-title fix below. i18n.js stamps <html lang>. */
html:lang(si) body,
html:lang(ta) body {
  line-height: 1.7;
}

h1,
h2,
h3,
p,
figure {
  margin: 0;
}

button {
  font: inherit;
  color: inherit;
}

a {
  color: inherit;
}

:focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: 2px;
}

.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;
}

/* ── First-run language gate ───────────────────────────────────
   Not a modal over the app — the app has not booted yet. One question, three
   answers, each in its own script. It is the first thing anyone sees, so it is
   plain paper and ink: no rail, no brand copy, nothing to read but the choice. */
.langgate {
  position: fixed;
  inset: 0;
  z-index: 100;
  display: grid;
  place-items: center;
  padding: var(--s5);
  background: var(--paper);
}

.langgate__card {
  width: 100%;
  max-width: 380px;
  padding: var(--s6) var(--s5);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
  text-align: center;
}

.langgate__mark {
  display: block;
  width: 24px;
  height: 3px;
  margin: 0 auto var(--s5);
  background: var(--stamp);
}

.langgate__title {
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* The other two scripts, so the question is legible to someone who reads
   neither English nor the language the browser guessed. */
.langgate__sub {
  margin-top: var(--s2);
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--muted);
}

.langgate__opts {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
  margin-top: var(--s5);
}

/* Big targets: this is the first thing touched, often on a cheap phone. */
.langgate__btn {
  min-height: 52px;
  padding: var(--s3) var(--s4);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: var(--t-md);
  font-weight: 500;
  color: var(--ink);
  cursor: pointer;
  transition: border-color 0.12s ease, background-color 0.12s ease;
}

.langgate__btn:hover {
  border-color: var(--ink);
  background: var(--sunk);
}

/* ── Shell ─────────────────────────────────────────────────── */

.shell {
  display: grid;
  grid-template-columns: var(--rail-w) 1fr;
  min-height: 100vh;
}

/* Grid items default to `min-width: auto`, which means they refuse to shrink
   below their content — a wide table or a long nav pushes the whole page sideways
   instead of scrolling inside its own box. `min-width: 0` lets them contain it.
   .topbar is a grid item too: without it, the Sinhala "Log in / Sign up" button
   (187px, and .btn is nowrap) stretched the shell's only column to 477px on a
   390px phone and scrolled the whole landing sideways. */
.rail,
.topbar,
.content {
  min-width: 0;
}

/* ── Rail ──────────────────────────────────────────────────── */

.rail {
  position: sticky;
  top: 0;
  align-self: start;
  height: 100vh;
  display: flex;
  flex-direction: column;
  gap: var(--s5);
  padding: var(--s5) var(--s4);
  border-right: 1px solid var(--rule);
  background: var(--surface);
}

.rail__mark {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  padding: 0 var(--s2);
}

.rail__mark-stamp {
  width: 9px;
  height: 9px;
  background: var(--stamp);
  border-radius: 1px;
  flex: none;
  transform: translateY(-1px);
}

.rail__mark-text {
  font-family: var(--font-data);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
}

.rail__nav {
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.rail__link {
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s2) var(--s2);
  border: 0;
  border-radius: var(--radius);
  background: none;
  text-align: left;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--muted);
  cursor: pointer;
  text-decoration: none;
  transition: color 0.12s ease, background-color 0.12s ease;
}

.rail__link:hover {
  color: var(--ink);
  background: var(--sunk);
}

.rail__link::before {
  content: "";
  width: 3px;
  height: 14px;
  border-radius: 1px;
  background: transparent;
  flex: none;
}

.rail__link[aria-current="page"] {
  color: var(--ink);
}

.rail__link[aria-current="page"]::before {
  background: var(--stamp);
}

.rail__count {
  margin-left: auto;
  font-family: var(--font-data);
  font-size: var(--t-xs);
  color: var(--muted);
}

.rail__foot {
  margin-top: auto;
  border-top: 1px solid var(--rule);
  padding-top: var(--s4);
}

.theme-toggle {
  display: flex;
  align-items: center;
  gap: var(--s3);
  width: 100%;
  padding: var(--s2);
  border: 0;
  border-radius: var(--radius);
  background: none;
  font-size: var(--t-sm);
  color: var(--muted);
  cursor: pointer;
}

.theme-toggle:hover {
  color: var(--ink);
  background: var(--sunk);
}

/* Language selector. Sits in the public top bar and the member rail foot; both
   are wired to one handler in app.js. Kept quiet — a native select, monospace
   like the rest of the data chrome. */
.lang-select {
  padding: var(--s2) var(--s3);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-family: inherit;
  font-size: var(--t-sm);
  cursor: pointer;
}

.lang-select:hover {
  border-color: var(--muted);
}

.rail__foot .lang-select {
  width: 100%;
}

/* ── Content ───────────────────────────────────────────────── */

.content {
  padding: var(--s7) var(--s6) var(--s8);
  max-width: calc(var(--content-max) + var(--s6) * 2);
  width: 100%;
  /* Centre the content within its column so the slack on a wide monitor splits
     evenly instead of piling up on the right. On narrow screens the content is
     full width, so this is a no-op. Applies to both the rail layout and the
     railless public landing. */
  margin-inline: auto;
}

.view__head {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--rule);
}

.view__title {
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.view__sub {
  margin-top: var(--s1);
  font-size: var(--t-sm);
  color: var(--muted);
}

.eyebrow {
  font-family: var(--font-data);
  font-size: var(--t-xs);
  font-weight: 500;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--muted);
}

/* ── Search hero ───────────────────────────────────────────── */

.hero {
  padding: var(--s8) 0 var(--s7);
  max-width: 640px;
}

/* Signed in, the search view is the whole dashboard, so a 640px block hugging
   the left leaves the screen looking empty. Centre it and give it a little more
   room — search reads as an intentional focal point rather than stranded copy.
   The public landing keeps its left-aligned hero (it pairs with the join panel
   on the right), so this is scoped to the rail layout. */
.shell:not(.shell--public) .hero {
  max-width: 720px;
  margin-inline: auto;
}

.hero__title {
  font-size: var(--t-hero);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.12;
  /* Even out the ragged edge rather than leaving one orphaned word on line 3. */
  text-wrap: balance;
}

/* Sinhala and Tamil carry tall ascenders and deep descenders that Latin does
   not, so the Latin-tuned 1.12 collides consecutive lines. They also set with a
   much larger glyph body, so the Latin size reads oversized in the same slot and
   ran the headline to five lines. Loosen the leading, drop the negative tracking
   (that was for Archivo's Latin and only smears these scripts), and step the
   size down so all three languages carry the same optical weight.
   <html lang> is stamped by i18n.js before first render. */
html:lang(si) .hero__title,
html:lang(ta) .hero__title {
  font-size: clamp(24px, 1.3rem + 1.1vw, 34px);
  line-height: 1.45;
  letter-spacing: 0;
  /* The copy authors its own <br>; balancing on top of that re-rags the line
     the translator already chose. */
  text-wrap: initial;
}

/* ── Public welcome note (animated) ────────────────────────────
   A stamped greeting on the public landing. The whole note slides up, the
   stamp presses in, and the text fades after it. Motion is disabled under
   prefers-reduced-motion — the final resting state is the static design. */
/* A pill, not a slab. A full-width stamp-wash box above the H1 out-shouts the
   headline it is introducing — the greeting is the smallest thing on the page,
   so it should read that way and let the title lead. */
.welcome {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s4);
  padding: var(--s1) var(--s3) var(--s1) var(--s1);
  max-width: 100%;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: 999px;
  animation: welcome-in 0.5s cubic-bezier(0.2, 0.7, 0.3, 1) both;
}

.welcome__stamp {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--stamp);
  color: #fff;
  font-size: 11px;
  line-height: 1;
  transform-origin: center;
  animation: welcome-stamp 0.45s cubic-bezier(0.2, 1.5, 0.4, 1) 0.18s both;
}

.welcome__text {
  font-size: var(--t-xs);
  color: var(--muted);
  min-height: 1.25em; /* reserve the line so layout doesn't jump while typing */
}

.welcome__typed b {
  font-weight: 600;
  color: var(--stamp);
}

/* Blinking typewriter caret, in ink until it lands on the stamp-red emphasis. */
.welcome__caret {
  display: inline-block;
  width: 2px;
  height: 1.05em;
  margin-left: 1px;
  vertical-align: text-bottom;
  background: var(--stamp);
  animation: welcome-blink 1s steps(1, end) infinite;
}

@keyframes welcome-in {
  from {
    opacity: 0;
    transform: translateY(10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

@keyframes welcome-stamp {
  from {
    opacity: 0;
    transform: scale(1.7) rotate(-9deg);
  }
  to {
    opacity: 1;
    transform: scale(1) rotate(0);
  }
}

@keyframes welcome-blink {
  0%,
  50% {
    opacity: 1;
  }
  50.01%,
  100% {
    opacity: 0;
  }
}

@media (prefers-reduced-motion: reduce) {
  .welcome,
  .welcome__stamp {
    animation: none;
  }

  /* No typing and no blink — playWelcome() renders the full note at once. */
  .welcome__caret {
    display: none;
  }
}

/* Sits directly under the search field and explains it, so it's tied to the
   field's text column rather than running the full hero width. */
.hero__note {
  margin-top: var(--s3);
  max-width: 62ch;
  font-size: var(--t-xs);
  line-height: 1.6;
  color: var(--muted);
}

/* ── Public landing (signed-out home page) ─────────────────────
   Signed out, the shell drops the rail and runs single-column with a top bar; the
   search view becomes a two-column landing (content left, join panel right). All
   of this is gated by .shell--public (set in app.js) so the signed-in app is
   untouched. */
/* Signed out the shell stacks top bar over content, so it needs explicit rows.
   Without them both rows are auto, and grid's default align-content stretches
   them to fill the shell's min-height: 100vh — on a short page (a one-hit search
   result) that inflated the top bar to ~177px around 59px of content. The bar
   takes its content height; the content column takes the rest. */
.shell--public {
  grid-template-columns: 1fr;
  grid-template-rows: auto 1fr;
}

.shell--public .rail {
  display: none;
}

/* The landing's left column is already sized by the grid (the join panel takes
   the rest), so a second 640px cap inside it just stranded ~190px of dead paper
   between the search bar and the panel. The copy keeps its own ch-based
   measures, so lines stay readable while the search bar fills its column. */
.shell--public .hero {
  max-width: none;
}


/* Sticky so the language switch and the way in stay reachable down a long
   landing. The rail is the signed-in equivalent and is already sticky. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  border-bottom: 1px solid var(--rule);
  background: var(--surface);
}

/* The bar bleeds full width (so its rule spans the viewport) but its contents
   sit on the same centred column as .content — otherwise the brand strands
   itself out in the left margin while the hero starts 300px inboard. */
.topbar__inner {
  display: flex;
  align-items: center;
  justify-content: space-between;
  /* The bar carries translated labels, and Sinhala/Tamil run far longer than the
     English they were laid out against. Wrapping to a second line is the honest
     answer on a narrow phone; the alternative is a page that scrolls sideways. */
  flex-wrap: wrap;
  gap: var(--s2) var(--s4);
  padding: var(--s3) var(--s6);
  max-width: calc(var(--content-max) + var(--s6) * 2);
  margin-inline: auto;
  width: 100%;
  min-width: 0;
}

.topbar__mark {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  text-decoration: none;
  color: inherit;
}

.topbar__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s2) var(--s3);
  min-width: 0;
}

/* The rail toggle is full-width; the top-bar one sizes to its label. */
.theme-toggle--bar {
  width: auto;
}

.hero-wrap {
  display: grid;
  grid-template-columns: 1fr;
  gap: var(--s7);
  align-items: start;
}

/* The same min-width:auto trap as .rail/.content, one level down. These are grid
   items, so they refuse to shrink below their own content and stretch the track
   instead — on a 390px phone the Sinhala landing pushed this to 391 and scrolled
   the page sideways. The track, not the item, decides the width. */
.hero,
.recent,
.join {
  min-width: 0;
}

.shell--public .hero-wrap {
  grid-template-columns: minmax(0, 1fr) 360px;
}

.hero__lede {
  margin-top: var(--s4);
  max-width: 54ch;
  font-size: var(--t-md);
  line-height: 1.65;
  color: var(--muted);
}

.hero__cta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s5);
}

.hero__cta-note {
  font-size: var(--t-sm);
  color: var(--muted);
}

/* A hairline above the section separates it from the search block, so the
   landing reads as two parts (look someone up / how this works) rather than
   one undifferentiated column of grey text. */
.how {
  margin-top: var(--s7);
  padding-top: var(--s5);
  border-top: 1px solid var(--rule);
}

/* The rule above spans the column (it divides the landing), but the steps keep a
   readable measure — the hero no longer caps its own width, so without max-width
   the step copy ran past 100 characters a line. */
.how__steps {
  margin-top: var(--s4);
  display: flex;
  flex-direction: column;
  gap: var(--s4);
  max-width: 70ch;
  padding: 0;
  list-style: none;
}

/* The rule runs down the gutter between the numbers, tying the three steps
   into one sequence. It stops at the last number rather than trailing off. */
.how__step {
  position: relative;
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  font-size: var(--t-sm);
  color: var(--muted);
}

.how__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 11px;
  top: 24px;
  bottom: calc(var(--s4) * -1);
  width: 1px;
  background: var(--rule);
}

.how__step b {
  color: var(--ink);
  font-weight: 600;
}

.how__num {
  flex: none;
  display: grid;
  place-items: center;
  width: 23px;
  height: 23px;
  border-radius: 50%;
  border: 1px solid var(--rule);
  background: var(--surface);
  font-family: var(--font-data);
  font-size: var(--t-xs);
  line-height: 1;
  color: var(--stamp);
}

/* This is a caution, so it wears the ochre the rest of the app already uses for
   one. The dashed grey box read as a placeholder and said "ignore me" — the
   opposite of what a defamation warning is for (rule 7: load-bearing, not
   cosmetic). Quiet, but unmistakably a notice. */
.disclaimer {
  margin-top: var(--s6);
  max-width: 62ch;
  padding: var(--s3) var(--s4);
  background: var(--ochre-wash);
  border: 0;
  border-left: 2px solid var(--ochre);
  border-radius: 0 var(--radius) var(--radius) 0;
  font-size: var(--t-xs);
  line-height: 1.6;
  color: var(--muted);
}

/* Right-column join / sign-in panel. */
.join {
  position: sticky;
  top: var(--s5);
}

/* --shadow-lift is for things that actually float (the drawer, the modals). The
   join panel rests on the paper beside the hero, so it takes the card shadow and
   reads as part of the page rather than as a dialog someone forgot to dismiss. */
.join__card {
  padding: var(--s5);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-card);
}

/* A stamp tab on the card's top edge — the same mark as the rail and top bar,
   tying the panel to the registry vocabulary. */
.join__card::before {
  content: "";
  display: block;
  width: 24px;
  height: 3px;
  margin-bottom: var(--s4);
  background: var(--stamp);
}

.join__title {
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.join__lede {
  margin-top: var(--s2);
  margin-bottom: var(--s5);
  padding-bottom: var(--s4);
  border-bottom: 1px solid var(--rule);
  font-size: var(--t-sm);
  color: var(--muted);
}

.join__rules {
  margin-top: var(--s3);
  font-size: var(--t-xs);
  line-height: 1.6;
  color: var(--muted);
}

/* "Add your report" banner over anonymous search results. */
.joinbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s4);
  margin-bottom: var(--s5);
  padding: var(--s3) var(--s4);
  background: var(--stamp-wash);
  border: 1px solid var(--rule);
  border-left: 3px solid var(--stamp);
  border-radius: var(--radius);
  font-size: var(--t-sm);
}

.joinbar b {
  font-weight: 600;
}

/* The kind picker sits above the field it configures. Quiet by design — it is a
   keyboard hint, not a filter, and must not compete with the search itself. */
.searchkind {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s1);
  margin-top: var(--s5);
}

.searchkind__btn {
  padding: var(--s1) var(--s3);
  min-height: 32px;
  border: 1px solid transparent;
  border-radius: 999px;
  background: none;
  font-size: var(--t-xs);
  font-weight: 500;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.12s ease, background-color 0.12s ease, border-color 0.12s ease;
}

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

.searchkind__btn[aria-pressed="true"] {
  background: var(--surface);
  border-color: var(--rule);
  color: var(--ink);
}

/* The picker already provides the gap above the field. */
.searchkind + .searchbar {
  margin-top: var(--s2);
}

@media (max-width: 800px) {
  /* Finger-sized, like every other tap target on a phone. */
  .searchkind__btn {
    min-height: 40px;
    padding: var(--s2) var(--s3);
  }
}

/* The page's one job is to look someone up, so the field is sized like the
   headline's equal rather than like a filter box: taller, ink-bordered at rest,
   and it lifts on focus instead of only changing colour. */
.searchbar {
  display: flex;
  align-items: center;
  gap: var(--s2);
  margin-top: var(--s5);
  padding: var(--s1);
  padding-left: var(--s4);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
  transition: border-color 0.12s ease, box-shadow 0.12s ease;
}

.searchbar:hover {
  border-color: var(--muted);
}

/* The ring lives on the bar, so the inner input can drop its own outline
   without the field ever going unfocusable-looking. */
.searchbar:focus-within {
  border-color: var(--ink);
  box-shadow: 0 0 0 3px var(--stamp-wash);
}

.searchbar__input {
  flex: 1;
  min-width: 0;
  padding: var(--s3) 0;
  min-height: 44px;
  border: 0;
  background: none;
  color: var(--ink);
  font-family: var(--font-data);
  font-size: var(--t-lg);
  letter-spacing: 0.02em;
}

.searchbar__input::placeholder {
  color: var(--muted);
  letter-spacing: 0;
  font-family: var(--font-ui);
  font-size: var(--t-md);
}

.searchbar__input:focus {
  outline: none;
}

/* A labelled button, not a bare glyph. "→" alone doesn't say what it does, and
   it was the smallest target on the page's primary action. */
.searchbar .btn {
  min-height: 44px;
  padding-inline: var(--s4);
  font-size: var(--t-sm);
}

.stats {
  margin-top: var(--s5);
  max-width: 70ch;
  font-size: var(--t-sm);
  color: var(--muted);
}

.stats b {
  font-family: var(--font-data);
  font-weight: 500;
  color: var(--ink);
}

/* ── Buttons ───────────────────────────────────────────────── */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--s2);
  padding: var(--s2) var(--s4);
  min-height: 36px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
  font-size: var(--t-sm);
  font-weight: 500;
  cursor: pointer;
  white-space: nowrap;
  transition: background-color 0.12s ease, border-color 0.12s ease,
    color 0.12s ease;
}

.btn:hover {
  border-color: var(--ink);
}

.btn--primary {
  background: var(--stamp);
  border-color: var(--stamp);
  color: #fff;
}

.btn--primary:hover {
  filter: brightness(1.08);
  border-color: var(--stamp);
}

/* Weightier than a plain .btn, quieter than the stamp. For an action that
   matters but isn't the one red thing in its column — see .hero__cta. */
.btn--outline {
  border-color: var(--ink);
  color: var(--ink);
}

.btn--outline:hover {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.btn--ghost {
  border-color: transparent;
  background: none;
  color: var(--muted);
}

.btn--ghost:hover {
  color: var(--ink);
  background: var(--sunk);
  border-color: transparent;
}

.btn--danger {
  color: var(--stamp);
  border-color: var(--rule);
}

.btn--danger:hover {
  border-color: var(--stamp);
  background: var(--stamp-wash);
}

.btn--sm {
  min-height: 30px;
  padding: var(--s1) var(--s3);
  font-size: var(--t-xs);
}

.btn--icon {
  padding: var(--s2);
  min-width: 36px;
}

/* ── Redaction bar — the signature ─────────────────────────── */

.redact {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  font-family: var(--font-data);
  font-size: var(--t-sm);
  letter-spacing: 0.04em;
  white-space: nowrap;
}

.redact__clear {
  color: var(--ink);
}

/* The inked block. A real fill, not dot characters. */
.redact__bar {
  display: inline-block;
  height: 0.95em;
  border-radius: 1px;
  background: var(--ink);
  opacity: 0.82;
  vertical-align: middle;
  /* width is set inline per-value, proportional to hidden digit count */
}

:root[data-theme="dark"] .redact__bar {
  opacity: 0.62;
}

.redact--lg {
  font-size: var(--t-md);
}

/* ── Chips: channel + status ───────────────────────────────── */

/* A group of channel chips: wraps as a block, never scattered by the cell. */
.chips {
  display: inline-flex;
  flex-wrap: wrap;
  gap: var(--s1);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--s1);
  padding: 2px var(--s2);
  border-radius: 2px;
  border: 1px solid var(--rule);
  font-family: var(--font-data);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  white-space: nowrap;
}

.chip__dot {
  width: 5px;
  height: 5px;
  border-radius: 50%;
  background: currentColor;
  flex: none;
}

/* ── Channel glyphs ────────────────────────────────────────────
   The one place brand colour is allowed. The stamp-red rule ("sparingly, only
   where a human has made a judgement") governs CHROME — buttons, nav, REPORTED.
   A channel mark is not chrome and not a judgement; it is part of the record,
   and WhatsApp green reads faster than the word does. So colour stays inside
   the glyph: the chip's border, fill, and text are untouched, and nothing here
   competes with the stamp for "this is the action."

   Paths are drawn with currentColor, so each brand is one `color` below and
   themes can override it. */
.ico {
  width: 12px;
  height: 12px;
  flex: none;
  fill: currentColor;
}

.chip--channel .ico {
  /* Optical: the mark sits a touch high against uppercase mono. */
  margin-top: -1px;
}

.ico--whatsapp {
  color: #25d366;
}
.ico--viber {
  color: #7360f2;
}
.ico--telegram {
  color: #26a5e4;
}
.ico--facebook {
  color: #0866ff;
}
.ico--instagram {
  color: #ff0069;
}
.ico--youtube {
  color: #ff0000;
}

/* X and TikTok are black-on-white brands, so they cannot be a fixed hex — on
   the dark theme a #000 glyph is an invisible hole. They take the page's ink. */
.ico--x,
.ico--tiktok {
  color: var(--ink);
}

/* call / sms / email have no brand to be faithful to. Ink, so the coloured
   marks stay meaningful rather than becoming decoration. */
.ico--call,
.ico--sms,
.ico--email {
  color: var(--muted);
}

.chip--confirmed {
  color: var(--stamp);
  border-color: var(--stamp);
  background: var(--stamp-wash);
}

.chip--reported {
  color: var(--ochre);
  border-color: var(--ochre);
  background: var(--ochre-wash);
}

.chip--watchlist {
  color: var(--muted);
}

.chip--removed {
  color: var(--muted);
  text-decoration: line-through;
}

/* A member's own record, held back from search. Ochre — it is a caution, not a
   failure: nothing is wrong, it is simply not public yet. */
.chip--pending {
  color: var(--ochre);
  border-color: var(--ochre);
  background: var(--ochre-wash);
}

/* Refused. Stamp-red, because a human made this judgement — the same rule that
   gives REPORTED its colour. */
.chip--rejected {
  color: var(--stamp);
  border-color: var(--stamp);
  background: var(--stamp-wash);
}

/* ── Recovery notice ───────────────────────────────────────────
   A member with no phone on their account cannot get back in if they forget
   their password. Ochre, like every other caution here — and one line with one
   button, because a banner that nags gets dismissed rather than acted on. */
.recover {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-bottom: var(--s5);
  padding: var(--s3) var(--s4);
  background: var(--ochre-wash);
  border-left: 2px solid var(--ochre);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.recover__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background: var(--ochre);
  color: var(--paper);
  font-family: var(--font-data);
  font-size: 11px;
  line-height: 1;
}

.recover__text {
  flex: 1;
  min-width: 0;
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--ink);
}

/* ── Flag a listing ────────────────────────────────────────────
   The one affordance on a row the viewer does not own. Deliberately quiet: it
   must be findable by the person named in the record without inviting everyone
   else to press it. */
.rowflag {
  display: inline-grid;
  place-items: center;
  width: 30px;
  height: 30px;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: none;
  color: var(--muted);
  font-size: var(--t-sm);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease, background-color 0.12s ease;
}

.rowflag:hover {
  color: var(--stamp);
  border-color: var(--rule);
  background: var(--surface);
}

@media (max-width: 800px) {
  .rowflag {
    width: 40px;
    height: 40px;
  }
}

/* ── Moderation queue ──────────────────────────────────────────
   Cards, not table rows: a moderator is reading one submission at a time and
   deciding on it, not scanning a ledger. Identifiers here are UNMASKED (the
   route is role-gated) because "is this the right person?" is the question. */
.modlist {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

.modcard {
  padding: var(--s5);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
}

.modcard__head {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  font-family: var(--font-data);
  font-size: var(--t-xs);
  color: var(--muted);
}

.modcard__ref {
  font-weight: 500;
  color: var(--ink);
}

.modcard__date {
  margin-left: auto;
}

.modcard__name {
  margin-top: var(--s3);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}

.modcard__fields {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2) var(--s5);
  margin-top: var(--s3);
}

.modcard__field {
  display: flex;
  flex-direction: column;
  gap: 2px;
  min-width: 0;
}

.modcard__notes {
  margin-top: var(--s4);
  padding: var(--s3);
  background: var(--sunk);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--muted);
}

/* What the accused actually said. Leads visually — it is the reason this record
   is in front of a moderator at all. */
.modcard__flags {
  margin-top: var(--s4);
  padding: var(--s3) var(--s4);
  background: var(--stamp-wash);
  border-left: 3px solid var(--stamp);
  border-radius: 0 var(--radius) var(--radius) 0;
}

.modcard__flag {
  margin-top: var(--s2);
  font-size: var(--t-sm);
  line-height: 1.6;
}

.modcard__actions {
  display: flex;
  justify-content: flex-end;
  gap: var(--s2);
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
}

/* A count that means "someone is waiting on you". */
.rail__count--alert:not(:empty) {
  color: var(--stamp);
}

.filter__count {
  margin-left: var(--s2);
  font-family: var(--font-data);
  color: var(--muted);
}

/* ── Table (registry ledger) ───────────────────────────────── */

.table-wrap {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow-x: auto;
}

/* Pagination controls, sat under whichever table they page. */
.pager {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: var(--s4);
  margin-top: var(--s3);
}

.pager__status {
  font-family: var(--font-data);
  font-size: var(--t-sm);
  color: var(--muted);
}

.pager__btn[disabled] {
  opacity: 0.4;
  cursor: not-allowed;
}

.table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
}

.table th {
  padding: var(--s3) var(--s4);
  text-align: left;
  font-family: var(--font-data);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  border-bottom: 1px solid var(--rule);
  white-space: nowrap;
}

.table td {
  padding: var(--s3) var(--s4);
  border-bottom: 1px solid var(--rule);
  vertical-align: middle;
}

.table tbody tr:last-child td {
  border-bottom: 0;
}

.table tbody tr {
  cursor: pointer;
  transition: background-color 0.1s ease;
}

.table tbody tr:hover {
  background: var(--sunk);
}

.table tbody tr.is-archived {
  opacity: 0.55;
}

.table__num {
  font-family: var(--font-data);
  text-align: right;
  white-space: nowrap;
}

.table__name {
  font-weight: 500;
}

.table__actions {
  text-align: right;
  white-space: nowrap;
}

.table__actions .btn {
  margin-left: var(--s1);
}

.arrears {
  font-family: var(--font-data);
  font-weight: 500;
}

.arrears--severe {
  color: var(--stamp);
}

.overdue {
  font-family: var(--font-data);
  color: var(--muted);
}

/* ── Filters ───────────────────────────────────────────────── */

.filters {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--s2);
  margin-bottom: var(--s4);
}

/* A generated group of filters. `display: contents` keeps the buttons as direct
   flex children of .filters, so the wrapper doesn't disturb the layout. */
.filters__set {
  display: contents;
}

.filters__sep {
  width: 1px;
  height: 18px;
  background: var(--rule);
  margin: 0 var(--s2);
}

/* inline-flex so the channel glyph sits on the label's centre line rather than
   the text baseline, which drops it a couple of pixels. */
.filter {
  display: inline-flex;
  align-items: center;
  gap: var(--s2);
  padding: var(--s1) var(--s3);
  min-height: 30px;
  border: 1px solid var(--rule);
  border-radius: 2px;
  background: var(--surface);
  font-family: var(--font-data);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--muted);
  cursor: pointer;
  transition: color 0.12s ease, border-color 0.12s ease;
}

.filter .ico {
  width: 14px;
  height: 14px;
}

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

.filter[aria-pressed="true"] {
  color: var(--ink);
  border-color: var(--ink);
}

/* ── Cards / flagged list ──────────────────────────────────── */

.card-list {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
  gap: var(--s2);
  margin-top: var(--s3);
}

.card {
  display: flex;
  align-items: center;
  gap: var(--s4);
  width: 100%;
  padding: var(--s3) var(--s4);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  cursor: pointer;
  text-align: left;
  transition: border-color 0.12s ease;
}

.card:hover {
  border-color: var(--ink);
}

/* The per-registry search box: an in-screen filter over the member's own rows. */
.registry-search {
  margin-bottom: var(--s4);
}

.registry-search__input {
  max-width: 24rem;
}

/* A lead the viewer cannot open (another member's related / same-name record).
   Reads as a card, but not as a button — no pointer, no hover lift. */
.card--static {
  cursor: default;
}

.card--static:hover {
  border-color: var(--rule);
}

.card__main {
  flex: 1;
  min-width: 0;
  display: flex;
  align-items: center;
  gap: var(--s4);
  flex-wrap: wrap;
}

.card__label {
  font-weight: 500;
}

.card__meta {
  font-size: var(--t-xs);
  color: var(--muted);
  font-family: var(--font-data);
}

/* ── Search results ────────────────────────────────────────── */

.result-group + .result-group {
  margin-top: var(--s6);
}

.result-group__head {
  display: flex;
  align-items: baseline;
  gap: var(--s3);
  margin-bottom: var(--s3);
}

.crosslink {
  display: flex;
  align-items: flex-start;
  gap: var(--s3);
  padding: var(--s4);
  margin-bottom: var(--s5);
  border: 1px solid var(--stamp);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--stamp-wash);
  font-size: var(--t-sm);
}

.crosslink__title {
  font-weight: 600;
  color: var(--stamp);
}

.crosslink__body {
  margin-top: var(--s1);
}

.empty {
  padding: var(--s7) var(--s5);
  text-align: center;
  border: 1px dashed var(--rule);
  border-radius: var(--radius);
  color: var(--muted);
  font-size: var(--t-sm);
}

/* ── "Not on file" ─────────────────────────────────────────────
   The most consequential screen here, and the one that must not look like an
   .empty. A dashed grey box saying "nothing found" reads as "this person is
   safe" to someone holding cash — and a scammer who is new has no history by
   definition, so the registry is quietest about exactly the people it can least
   afford to be quiet about. This wears the ochre the app uses for every other
   caution, and is left-aligned like something to be read rather than centred
   like a shrug. */
.notfound {
  padding: var(--s5);
  background: var(--ochre-wash);
  border: 1px solid var(--ochre);
  border-left-width: 3px;
  border-radius: var(--radius);
  max-width: 62ch;
}

.notfound__flag {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
}

.notfound__mark {
  flex: none;
  display: grid;
  place-items: center;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background: var(--ochre);
  color: var(--paper);
  font-family: var(--font-data);
  font-size: var(--t-sm);
  line-height: 1;
}

/* The server's own words — see notOnFile() in ui.js. */
.notfound__lede {
  margin-top: var(--s3);
  font-size: var(--t-md);
  color: var(--ink);
}

.notfound__why {
  margin-top: var(--s3);
  font-size: var(--t-sm);
  line-height: 1.6;
  color: var(--muted);
}

.notfound__cta {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: var(--s3);
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--ochre);
}

.notfound__cta-note {
  font-size: var(--t-sm);
  color: var(--muted);
}

.empty__title {
  color: var(--ink);
  font-weight: 500;
  margin-bottom: var(--s1);
}

/* ── Detail view ───────────────────────────────────────────── */

.detail__back {
  margin-bottom: var(--s4);
}

.detail__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--s4);
  padding-bottom: var(--s5);
  border-bottom: 1px solid var(--rule);
}

.detail__name {
  font-size: var(--t-lg);
  font-weight: 600;
  letter-spacing: -0.01em;
}

/* A record can carry many channels — these must wrap, not run off the screen. */
.detail__chips {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s2);
  margin-top: var(--s3);
}

.detail__actions {
  display: flex;
  gap: var(--s2);
  flex: none;
}

.detail__grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
  gap: var(--s5);
  padding: var(--s5) 0;
  border-bottom: 1px solid var(--rule);
}

.field__label {
  font-family: var(--font-data);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: var(--s2);
}

.field__value {
  font-family: var(--font-data);
  font-size: var(--t-md);
}

.field__value--ui {
  font-family: var(--font-ui);
}

.detail__section {
  padding: var(--s5) 0;
  border-bottom: 1px solid var(--rule);
}

.detail__section:last-child {
  border-bottom: 0;
}

.detail__section-title {
  margin-bottom: var(--s4);
}

/* Report history — a ledger, not a decorative timeline */
.history {
  list-style: none;
  margin: 0;
  padding: 0;
}

.history__row {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: var(--s4);
  padding: var(--s3) 0;
  border-top: 1px solid var(--rule);
  font-size: var(--t-sm);
}

.history__date {
  font-family: var(--font-data);
  font-size: var(--t-xs);
  color: var(--muted);
  padding-top: 2px;
}

/* Both are spans inside one grid cell — without `block` the note runs straight
   on from the action: "Reported to Platform (Meta)Impersonating a lender." */
.history__what {
  display: block;
  font-weight: 500;
}

.history__note {
  display: block;
  color: var(--muted);
  margin-top: 2px;
}

.channel-list {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.channel-row {
  display: flex;
  align-items: center;
  gap: var(--s4);
  padding: var(--s3);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--surface);
}

/* ── Drawer ────────────────────────────────────────────────── */

.scrim {
  position: fixed;
  inset: 0;
  background: rgba(10, 11, 13, 0.45);
  z-index: 40;
}

.drawer {
  position: fixed;
  top: 0;
  right: 0;
  bottom: 0;
  width: min(440px, 100vw);
  z-index: 50;
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border-left: 1px solid var(--rule);
  box-shadow: var(--shadow-lift);
  animation: slide-in 0.18s ease;
}

@keyframes slide-in {
  from {
    transform: translateX(16px);
    opacity: 0;
  }
}

.drawer__head,
.modal__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s3);
  padding: var(--s4) var(--s5);
  border-bottom: 1px solid var(--rule);
}

.drawer__title,
.modal__title {
  font-size: var(--t-md);
  font-weight: 600;
}

.drawer__body {
  flex: 1;
  overflow-y: auto;
  padding: var(--s5);
}

.drawer__foot,
.modal__foot {
  display: flex;
  justify-content: flex-end;
  gap: var(--s2);
  padding: var(--s4) var(--s5);
  border-top: 1px solid var(--rule);
}

/* ── Modal ─────────────────────────────────────────────────── */

.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  z-index: 50;
  width: min(460px, calc(100vw - var(--s6)));
  max-height: calc(100vh - var(--s7));
  display: flex;
  flex-direction: column;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow-lift);
}

.modal__body {
  padding: var(--s5);
  overflow-y: auto;
}

.modal__lede {
  font-size: var(--t-sm);
  color: var(--muted);
  margin-bottom: var(--s5);
}

.modal__subject {
  padding: var(--s3) var(--s4);
  margin-bottom: var(--s5);
  background: var(--sunk);
  border-radius: var(--radius);
}

/* ── Forms ─────────────────────────────────────────────────── */

.form {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

.form__row {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: var(--s3);
}

.form__group {
  display: flex;
  flex-direction: column;
  gap: var(--s2);
}

.label {
  font-family: var(--font-data);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--muted);
}

.input,
.select,
.textarea {
  width: 100%;
  padding: var(--s2) var(--s3);
  min-height: 38px;
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-data);
  font-size: var(--t-sm);
  transition: border-color 0.12s ease;
}

.input:focus,
.select:focus,
.textarea:focus {
  outline: none;
  border-color: var(--ink);
}

.textarea {
  min-height: 84px;
  resize: vertical;
  font-family: var(--font-ui);
  line-height: 1.5;
}

.select {
  font-family: var(--font-ui);
  cursor: pointer;
}

.hint {
  font-size: var(--t-xs);
  color: var(--muted);
}

.error {
  font-size: var(--t-xs);
  color: var(--stamp);
}

.error:empty {
  display: none;
}

.input[aria-invalid="true"] {
  border-color: var(--stamp);
}

.segmented {
  display: flex;
  gap: 0;
  padding: 3px;
  background: var(--sunk);
  border-radius: var(--radius);
}

.segmented__btn {
  flex: 1;
  padding: var(--s2);
  border: 0;
  border-radius: 2px;
  background: none;
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--muted);
  cursor: pointer;
}

.segmented__btn[aria-pressed="true"] {
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.08);
}

.checks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3);
}

.check {
  display: flex;
  align-items: center;
  gap: var(--s2);
  font-size: var(--t-sm);
  cursor: pointer;
}

.check input {
  accent-color: var(--stamp);
  width: 15px;
  height: 15px;
}

/* Roomier than the chip's 12px — this is a tap target being labelled, not a
   dense table cell. */
.check .ico {
  width: 16px;
  height: 16px;
  margin-right: calc(var(--s1) * -1);
}

/* ── Toast ─────────────────────────────────────────────────── */

.toast {
  position: fixed;
  left: 50%;
  bottom: var(--s5);
  transform: translateX(-50%);
  z-index: 60;
  display: flex;
  align-items: center;
  gap: var(--s3);
  padding: var(--s3) var(--s4);
  background: var(--ink);
  color: var(--paper);
  border-radius: var(--radius);
  font-size: var(--t-sm);
  box-shadow: var(--shadow-lift);
  animation: toast-in 0.16s ease;
}

@keyframes toast-in {
  from {
    transform: translate(-50%, 8px);
    opacity: 0;
  }
}

/* ── Responsive ────────────────────────────────────────────── */

@media (max-width: 800px) {
  .shell {
    grid-template-columns: 1fr;
  }

  /* The rail wraps rather than scrolling sideways. With only four destinations,
     a hidden scroll strip would bury nav items for no reason. */
  .rail {
    position: static;
    height: auto;
    flex-direction: row;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--s2) var(--s3);
    padding: var(--s3) var(--s4);
    border-right: 0;
    border-bottom: 1px solid var(--rule);
    overflow-x: visible;
  }

  .rail__nav {
    flex-direction: row;
    flex-wrap: wrap;
    gap: var(--s1);
  }

  /* Finger-sized targets. 30px is fine for a mouse and a miss on a phone. */
  .rail__link {
    min-height: 40px;
    padding: var(--s2) var(--s3);
  }

  .btn {
    min-height: 44px;
  }

  .btn--sm {
    min-height: 36px;
    padding: var(--s2) var(--s3);
  }

  .filter {
    min-height: 36px;
  }

  .theme-toggle {
    min-height: 40px;
    min-width: 40px;
    justify-content: center;
  }

  .rail__link::before,
  .rail__count {
    display: none;
  }

  .rail__link[aria-current="page"] {
    background: var(--sunk);
  }

  .rail__foot {
    margin-top: 0;
    margin-left: auto;
    border-top: 0;
    padding-top: 0;
  }

  .theme-toggle span {
    display: none;
  }

  .content {
    padding: var(--s5) var(--s4) var(--s7);
  }

  .hero {
    padding: var(--s5) 0 var(--s6);
  }

  .hero__title {
    font-size: 24px;
  }

  /* Landing stacks: search/content first, then the join panel below it. */
  .shell--public .hero-wrap {
    grid-template-columns: 1fr;
    gap: var(--s6);
  }

  .join {
    position: static;
  }

  .topbar__inner {
    padding: var(--s3) var(--s4);
  }

  .joinbar {
    flex-direction: column;
    align-items: flex-start;
  }

  .view__head,
  .detail__head {
    flex-direction: column;
    align-items: flex-start;
  }

  .form__row {
    grid-template-columns: 1fr;
  }

  /* Tables reflow into stacked cards */
  .table-wrap {
    border: 0;
    background: none;
    overflow: visible;
  }

  .table thead {
    display: none;
  }

  .table,
  .table tbody,
  .table tr,
  .table td {
    display: block;
    width: 100%;
  }

  .table tr {
    margin-bottom: var(--s2);
    padding: var(--s3) var(--s4);
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
  }

  /* A cell can hold several channel chips. Let them wrap under the label rather
     than pushing the card sideways. */
  .table td {
    display: flex;
    align-items: center;
    justify-content: space-between;
    flex-wrap: wrap;
    gap: var(--s2) var(--s4);
    padding: var(--s2) 0;
    border-bottom: 0;
    text-align: left;
  }

  /* Value side of a card row: hugs the right, wraps within itself. */
  .table td .chips {
    justify-content: flex-end;
    flex: 1;
  }

  .table td::before {
    content: attr(data-label);
    font-family: var(--font-data);
    font-size: 11px;
    font-weight: 500;
    letter-spacing: 0.1em;
    text-transform: uppercase;
    color: var(--muted);
    flex: none;
  }

  .table td:empty {
    display: none;
  }

  .table__num,
  .table__actions {
    text-align: left;
  }

  .table__actions .btn {
    margin-left: 0;
    margin-right: var(--s1);
  }

  .history__row {
    grid-template-columns: 1fr;
    gap: var(--s1);
  }
}

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

/* ── Print — redactions must survive paper ─────────────────── */

@media print {
  .rail,
  .detail__actions,
  .table__actions,
  .filters,
  .toast,
  .drawer,
  .modal,
  .scrim {
    display: none !important;
  }

  .shell {
    grid-template-columns: 1fr;
  }

  .redact__bar {
    background: #000 !important;
    opacity: 1 !important;
    -webkit-print-color-adjust: exact;
    print-color-adjust: exact;
  }
}

/* ── Auth ──────────────────────────────────────────────────
   Sign-in / sign-up. The registry is not public: identifiers are masked so they
   cannot be harvested, and leaving the app open to anonymous callers would give
   away by the back door what the screens are careful not to show. */

.auth {
  min-height: 100vh;
  display: grid;
  place-items: center;
  padding: var(--s5);
  background: var(--paper);
}

.auth[hidden] {
  display: none;
}

.auth__card {
  width: 100%;
  max-width: 380px;
}

.auth__mark {
  display: flex;
  align-items: baseline;
  gap: var(--s2);
  margin-bottom: var(--s6);
}

.auth__title {
  font-size: var(--t-xl);
  font-weight: 600;
  letter-spacing: -0.02em;
  line-height: 1.15;
}

.auth__lede {
  margin-top: var(--s3);
  margin-bottom: var(--s6);
  font-size: var(--t-sm);
  color: var(--muted);
}

.auth__form {
  display: flex;
  flex-direction: column;
  gap: var(--s4);
}

.auth__submit {
  margin-top: var(--s2);
  width: 100%;
  min-height: 44px;
}

.auth__alt {
  margin-top: var(--s5);
  padding-top: var(--s4);
  border-top: 1px solid var(--rule);
  font-size: var(--t-sm);
  color: var(--muted);
  text-align: center;
}

/* Two rules stack when both the sign-up/sign-in switch and the mobile/email
   switch are showing; the second only needs the space, not another line. */
.auth__alt + .auth__alt {
  margin-top: var(--s3);
  padding-top: 0;
  border-top: 0;
}

/* Resend / change number — a pair of quiet actions under the code field. */
.auth__meta {
  display: flex;
  justify-content: space-between;
  gap: var(--s3);
  margin-top: var(--s3);
  font-size: var(--t-sm);
}

.auth__meta .auth__switch:disabled {
  color: var(--muted);
  text-decoration: none;
  cursor: default;
}

/* A number the member is reading off an SMS and retyping — mono, so it lines up
   with the rest of the app's data and 0/O cannot be confused. */
.input--data {
  font-family: var(--font-data);
  letter-spacing: 0.02em;
}

/* The code is six characters and nothing else. Wide tracking and a bigger size
   make it legible to someone comparing it against a text on a small screen. */
.input--code {
  font-size: var(--t-lg);
  letter-spacing: 0.4em;
  text-align: center;
}

.input--code::placeholder {
  letter-spacing: 0.4em;
}

.auth__switch {
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-weight: 500;
  color: var(--stamp);
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 2px;
}

.auth__back {
  display: inline-block;
  margin-bottom: var(--s4);
  border: 0;
  background: none;
  padding: 0;
  font: inherit;
  font-size: var(--t-sm);
  color: var(--muted);
  cursor: pointer;
}

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

/* The public sign-in button sits where the officer's name/sign-out otherwise would. */
.rail__signin {
  align-self: flex-start;
}

.auth__error {
  padding: var(--s3) var(--s4);
  margin-bottom: var(--s4);
  border: 1px solid var(--stamp);
  border-left-width: 3px;
  border-radius: var(--radius);
  background: var(--stamp-wash);
  color: var(--stamp);
  font-size: var(--t-sm);
}

.auth__error:empty {
  display: none;
}

/* Recessed rather than dashed-and-centred: it's a credential to copy, so it
   reads like the app's other data (sunk fill, mono, left-aligned) instead of
   centring into a two-line wrap. */
.auth__demo {
  margin-top: var(--s4);
  padding: var(--s2) var(--s3);
  border: 0;
  border-radius: var(--radius);
  background: var(--sunk);
  font-family: var(--font-data);
  font-size: var(--t-xs);
  line-height: 1.6;
  color: var(--muted);
  text-align: left;
  overflow-wrap: anywhere;
}

/* The signed-in officer, at the foot of the rail. */
.rail__user {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--s2);
  padding: var(--s2);
  margin-bottom: var(--s1);
}

.rail__user-name {
  font-size: var(--t-xs);
  color: var(--muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A full-screen spinner would be noise. A quiet line is enough. */
.loading {
  padding: var(--s6);
  text-align: center;
  color: var(--muted);
  font-size: var(--t-sm);
}

/* ── Footer: the link out to the documentation ──────────────────
   Quiet — a member is here to search, not to read. But it is the only route
   from the app to the docs, and the only path Google has to crawl from `/`
   (noindex, FOLLOW) to the pages that can actually rank. */
.appfoot {
  margin-top: var(--s8);
  padding-top: var(--s5);
  border-top: 1px solid var(--rule);
}

.appfoot__nav {
  display: flex;
  flex-wrap: wrap;
  gap: var(--s3) var(--s5);
  font-size: var(--t-sm);
}

.appfoot__nav a {
  color: var(--muted);
  text-decoration: none;
}

.appfoot__nav a:hover {
  color: var(--ink);
}
