/* =============================================================================
   ECOSOUND · COMPONENTS
   Reusable, brandable building blocks. Each maps to tokens.css so a theme
   change flows through automatically. Component markup lives in js/ui.js.
   ============================================================================= */

/* ---- Brand lockup ------------------------------------------------------ */
.ecs-brand {
  display: flex;
  align-items: center;
  gap: var(--ecs-space-3);
}

/* The Echo logo (js/logo.js): inline SVG, coloured from here so one markup
   serves white and ink. Grove on white, Meadow on ink, as the wordmark's ECO. */
.ecs-logo {
  --ecs-logo-fg: var(--ecs-ink);
  --ecs-logo-accent: var(--ecs-forest-500);
  display: block;
  flex: none;
  overflow: visible;
}
.ecs-logo--light {
  --ecs-logo-fg: var(--ecs-on-dark);
  --ecs-logo-accent: var(--ecs-green-400);
}
.ecs-logo__fg     { stroke: var(--ecs-logo-fg); }
.ecs-logo__accent { stroke: var(--ecs-logo-accent); }
.ecs-logo__eco    { fill: var(--ecs-logo-accent); }
.ecs-logo__sound  { fill: var(--ecs-logo-fg); }

/* ---- Buttons ------------------------------------------------------------ */
.ecs-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 9px;
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-base);
  border: none;
  border-radius: var(--ecs-radius-lg);
  padding: 15px;
  cursor: pointer;
  transition: background var(--ecs-dur-fast) var(--ecs-ease),
              color var(--ecs-dur-fast) var(--ecs-ease),
              transform var(--ecs-dur-fast) var(--ecs-ease),
              border-color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-btn:active { transform: translateY(1px); }

.ecs-btn--primary {
  background: var(--ecs-forest-500);
  color: var(--ecs-on-dark);
}
.ecs-btn--primary:hover { background: var(--ecs-forest-600); }

.ecs-btn--bright {
  background: var(--ecs-green-400);
  color: var(--ecs-forest-900);
}
.ecs-btn--bright:hover { background: var(--ecs-green-300); }

/* The site's secondary button: white, one green hairline, ink label. */
.ecs-btn--ghost {
  background: var(--ecs-surface);
  color: var(--ecs-ink);
  border: 1px solid var(--ecs-green-500);
}
.ecs-btn--ghost:hover { background: var(--ecs-green-200); }

/* Quiet inline action — a link that happens to be a button. Pairs with
   .ecs-plus for the site's circled-"+" affordance. */
.ecs-btn--quiet {
  background: transparent;
  color: var(--ecs-green-600);
  border: none;
  padding: 6px 8px;
}
.ecs-btn--quiet:hover { color: var(--ecs-forest-600); }

.ecs-btn--danger {
  background: transparent;
  color: var(--ecs-alert-ink);
  border: 1.5px solid var(--ecs-alert-line);
}
.ecs-btn--danger:hover { background: var(--ecs-alert-bg); }

.ecs-btn:disabled,
.ecs-btn[disabled] {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}

.ecs-btn--ondark {
  background: rgba(255, 255, 255, 0.10);
  color: var(--ecs-on-dark);
}
.ecs-btn--ondark:hover { background: rgba(255, 255, 255, 0.18); }

.ecs-btn--block { width: 100%; }
.ecs-btn--sm { padding: 10px 17px; font-size: var(--ecs-fs-xs); }
.ecs-btn--md { padding: 13px 22px; }
.ecs-btn--lg { padding: 14px 28px; font-size: var(--ecs-fs-md); }
.ecs-btn--nav { padding: 10px 18px; font-size: var(--ecs-fs-sm); }

/* ---- Circled "+" — the site's universal "more" affordance ---------------
   Sits inside links, list bullets and CTAs. Inherits the label's colour, so
   it works on white, on mint and inside a filled button. */
.ecs-plus {
  width: 20px;
  height: 20px;
  border-radius: var(--ecs-radius-pill);
  border: 1.5px solid currentColor;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font: var(--ecs-fw-regular) 13px var(--ecs-font-body);
  line-height: 1;
  flex-shrink: 0;
}
.ecs-plus--sm { width: 17px; height: 17px; font-size: 11px; }
.ecs-btn--primary .ecs-plus { border-color: rgba(255, 255, 255, 0.85); }

/* ---- Cards -------------------------------------------------------------- */
.ecs-card {
  background: var(--ecs-surface);
  border: 1px solid var(--ecs-line);
  border-radius: var(--ecs-radius-2xl);
  padding: var(--ecs-space-6);
}
.ecs-card--dark {
  background: var(--ecs-forest-900);
  border-color: var(--ecs-forest-900);
  color: var(--ecs-on-dark);
}
/* Kept as a name for the "one dark panel per screen" moment; the brand has no
   decorative gradients, so it resolves to the same flat ink as --dark. */
.ecs-card--gradient {
  background: var(--ecs-forest-900);
  border: none;
  color: var(--ecs-on-dark);
}
.ecs-card--elevated { box-shadow: var(--ecs-shadow-card); }

/* ---- Portal type ---------------------------------------------------------
   The app scale from design.md — 36 / 30 / 22 / 16 / 15 / 14 / 13 / 12 / 11 —
   as the few roles a portal screen actually has. Screens carried these
   inline and drifted: page titles at 24, 26 and 28px on neighbouring screens,
   notes at 11.5 and 12.5px. One class per role keeps the next screen on the
   scale. 11px belongs to uppercase labels (kickers, table heads, tags) only. */
.ecs-page-title {
  font: var(--ecs-fw-bold) var(--ecs-fs-h2) / 1.15 var(--ecs-font-display);
  letter-spacing: -0.01em;
  color: var(--ecs-ink);
  margin: 0;
  text-wrap: balance;
  overflow-wrap: anywhere;
}
.ecs-page-sub {
  font: var(--ecs-fw-regular) var(--ecs-fs-sm) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 6px 0 0;
  max-width: 72ch;
}
/* A panel's headline: the quote that just landed, the batch we received. */
.ecs-section-title {
  font: var(--ecs-fw-bold) var(--ecs-fs-title) / 1.25 var(--ecs-font-display);
  letter-spacing: -0.01em;
  color: inherit;
  margin: 0;
}
.ecs-card-title {
  font: var(--ecs-fw-bold) var(--ecs-fs-md) / 1.3 var(--ecs-font-display);
  color: inherit;
  margin: 0;
}
/* On a phone the page title takes the 22px step, so everything titled under it
   steps down to 16 — two 22px headings stacked read as one level. */
@media (max-width: 600px) {
  .ecs-page-title { font-size: var(--ecs-fs-title); }
  .ecs-section-title { font-size: var(--ecs-fs-md); }
}

/* ---- Facts list (key/value rows on chooser cards) ---------------------- */
.ecs-facts { display: flex; flex-direction: column; gap: var(--ecs-space-3); }
.ecs-fact { display: flex; gap: var(--ecs-space-3); align-items: baseline; }
.ecs-fact__k {
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-3xs);
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--ecs-text-faint);
  width: 90px;
  flex-shrink: 0;
}
.ecs-fact__v {
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-sm);
  color: var(--ecs-ink);
}
.ecs-card--dark .ecs-fact__k { color: var(--ecs-on-dark-4); }
.ecs-card--dark .ecs-fact__v { color: var(--ecs-on-dark); }

/* ---- Inputs ------------------------------------------------------------- */
/* Uppercase mono micro-label above the field, as on the site's forms. */
.ecs-label {
  display: block;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-3xs);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
  margin-bottom: 7px;
}
.ecs-input {
  width: 100%;
  font-family: var(--ecs-font-body);
  font-size: var(--ecs-fs-base);
  padding: 11px 13px;
  border-radius: var(--ecs-radius-md);
  border: 1px solid var(--ecs-line-field);
  background: var(--ecs-surface);
  color: var(--ecs-ink);
  outline: none;
  transition: border-color var(--ecs-dur-fast) var(--ecs-ease),
              box-shadow var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-input:focus {
  border-color: var(--ecs-forest-500);
  box-shadow: 0 0 0 3px var(--ecs-green-300);
}
.ecs-input:read-only {
  background: var(--ecs-surface-3);
  color: var(--ecs-text-muted);
}
.ecs-input--mono { font-family: var(--ecs-font-mono); }

/* Code / OTP screen — centred copy + six separate digit boxes. */
.ecs-code-panel { width: 440px; }
.ecs-code-panel__sub {
  font: 400 14px / 1.55 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 0 0 28px;
  text-align: center;
}
.ecs-code-panel__foot {
  text-align: center;
  margin: 0 0 22px;
}
.ecs-code-panel__prompt {
  font: 400 14px / 1.4 var(--ecs-font-body);
  color: var(--ecs-ink);
  margin-bottom: 6px;
}
.ecs-code-panel__actions {
  display: flex;
  justify-content: center;
  align-items: center;
  gap: 8px;
  font: 400 var(--ecs-fs-sm) / 1.4 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-code-panel__actions .ecs-authlink {
  font-weight: 400;
  color: var(--ecs-text-muted);
}
.ecs-code-panel__actions .ecs-authlink:hover { color: var(--ecs-forest-500); }
.ecs-code-panel__wait {
  color: var(--ecs-text-faint);
  font-variant-numeric: tabular-nums;
  cursor: default;
  user-select: none;
}
.ecs-code-panel__dot { color: var(--ecs-text-faint); }
.ecs-otp {
  display: flex;
  justify-content: center;
  gap: 10px;
  margin-bottom: 14px;
}
.ecs-otp__box {
  width: 52px;
  height: 56px;
  box-sizing: border-box;
  text-align: center;
  font-family: var(--ecs-font-mono);
  font-size: 22px;
  font-weight: var(--ecs-fw-semi);
  color: var(--ecs-ink);
  background: var(--ecs-surface);
  border: 1px solid var(--ecs-line-field);
  border-radius: var(--ecs-radius-md);
  outline: none;
  caret-color: var(--ecs-forest-500);
  transition: border-color var(--ecs-dur-fast) var(--ecs-ease),
              box-shadow var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-otp__box:focus {
  border-color: var(--ecs-forest-500);
  box-shadow: 0 0 0 3px var(--ecs-green-300);
}
.ecs-otp__box:disabled {
  opacity: 0.65;
  cursor: wait;
}
.ecs-code-panel__working {
  text-align: center;
  font: 400 13px / 1.4 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 0 0 16px;
}

.ecs-select-wrap { position: relative; }
.ecs-select {
  width: 100%;
  font-family: var(--ecs-font-body);
  font-size: var(--ecs-fs-sm);
  padding: 12px 14px;
  border-radius: var(--ecs-radius-md);
  border: 1.5px solid var(--ecs-line-2);
  background: var(--ecs-surface);
  color: var(--ecs-ink);
  outline: none;
  appearance: none;
  transition: border-color var(--ecs-dur-fast) var(--ecs-ease);
}
/* Fields share one focus treatment (design.md rule 6): the green border and a
   3px halo, as .ecs-input has — not the 6px halo base.css gives controls. */
.ecs-select:focus {
  border-color: var(--ecs-forest-500);
  box-shadow: 0 0 0 3px var(--ecs-focus-halo);
}
.ecs-select-wrap > svg {
  position: absolute;
  right: 14px;
  top: 50%;
  transform: translateY(-50%);
  pointer-events: none;
}

/* ---- Auth (self-service login / register) footer links ----------------- */
.ecs-auth-foot {
  margin-top: 20px;
  font: 400 var(--ecs-fs-sm) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-auth-alt {
  margin-top: 8px;
  font: 400 var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-faint);
}
.ecs-authlink {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  font-weight: var(--ecs-fw-semi);
  color: var(--ecs-green-600);
  cursor: pointer;
  transition: color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-authlink:hover { color: var(--ecs-forest-500); text-decoration: underline; }
.ecs-authlink:disabled {
  color: var(--ecs-text-faint);
  cursor: not-allowed;
  text-decoration: none;
  pointer-events: none;
}

/* Label row with a trailing action (Change email / etc.) — Cursor-style. */
.ecs-field-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 7px;
}
.ecs-field-head .ecs-label { margin-bottom: 0; }
.ecs-field-head .ecs-authlink {
  font-size: var(--ecs-fs-xs);
  font-weight: 400;
  color: var(--ecs-text-muted);
}
.ecs-field-head .ecs-authlink:hover { color: var(--ecs-forest-500); }

.ecs-auth-back {
  display: inline-flex;
  align-items: center;
  gap: 4px;
  margin-top: 20px;
  background: none;
  border: none;
  padding: 0;
  font: 400 var(--ecs-fs-sm) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  cursor: pointer;
  transition: color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-auth-back:hover { color: var(--ecs-forest-500); }

.ecs-signin-fields { margin-top: 22px; }
.ecs-signin-fields > .ecs-input { margin-bottom: 14px; }
.ecs-signin-fields .ecs-pw-wrap { margin-bottom: 14px; }
.ecs-signin-fields .ecs-pw-wrap .ecs-input { margin-bottom: 0; }

/* ---- Password strength meter (optional-password panel) ----------------- */
.ecs-meter { margin: 2px 0 4px; }
.ecs-meter__bar {
  height: 6px;
  border-radius: var(--ecs-radius-pill);
  background: var(--ecs-surface-3);
  overflow: hidden;
}
/* Full width, scaled from the left: a fill that grows by transform stays off
   the layout path, where one that grows by width re-lays the row each step. */
.ecs-meter__fill {
  height: 100%;
  width: 100%;
  transform: scaleX(0);
  transform-origin: left;
  border-radius: var(--ecs-radius-pill);
  background: var(--ecs-line-strong);
  transition: transform var(--ecs-dur-fast) var(--ecs-ease),
              background-color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-meter__fill[data-level="1"],
.ecs-meter__fill[data-level="2"] { background: var(--ecs-alert); }     /* weak  */
.ecs-meter__fill[data-level="3"],
.ecs-meter__fill[data-level="4"] { background: var(--ecs-warn); }      /* fair  */
.ecs-meter__fill[data-level="5"] { background: var(--ecs-forest-500); } /* strong */

.ecs-meter__reqs {
  list-style: none;
  margin: 12px 0 0;
  padding: 0;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 6px 16px;
}
.ecs-meter__reqs li {
  display: flex;
  align-items: center;
  gap: 8px;
  font: 400 var(--ecs-fs-xs) / 1.4 var(--ecs-font-body);
  color: var(--ecs-text-faint);
  transition: color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-req-dot {
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--ecs-line-strong);
  flex: none;
  transition: background-color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-meter__reqs li.ecs-met { color: var(--ecs-forest-500); font-weight: var(--ecs-fw-semi); }
.ecs-meter__reqs li.ecs-met .ecs-req-dot { background: var(--ecs-forest-500); }

/* ---- Two-step sign-up (credential stage) ------------------------------- */
/* The neutral "Continue with email code" button — bordered, icon + label. */
.ecs-setup-codebtn {
  background: var(--ecs-surface);
  color: var(--ecs-ink);
  border: 1.5px solid var(--ecs-line-strong);
}
.ecs-setup-codebtn:hover { background: var(--ecs-surface-3); border-color: var(--ecs-green-500); }
.ecs-setup-codebtn > svg { color: var(--ecs-forest-500); }

/* "or" divider between the code button and the password field. */
.ecs-or {
  display: flex;
  align-items: center;
  gap: 12px;
  margin: 16px 0;
  color: var(--ecs-text-faint);
  font: 500 var(--ecs-fs-xs) / 1 var(--ecs-font-mono);
  text-transform: uppercase;
  letter-spacing: 0.1em;
}
.ecs-or::before,
.ecs-or::after {
  content: '';
  flex: 1;
  height: 1px;
  background: var(--ecs-line);
}

/* Password field with an inline Show/Hide toggle. */
.ecs-pw-wrap { position: relative; }
.ecs-pw-wrap .ecs-input { padding-right: 62px; }
.ecs-pw-toggle {
  position: absolute;
  right: 8px;
  top: 50%;
  transform: translateY(-50%);
  background: none;
  border: none;
  padding: 6px 8px;
  cursor: pointer;
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1 var(--ecs-font-body);
  color: var(--ecs-green-600);
}
.ecs-pw-toggle:hover { color: var(--ecs-forest-500); }

/* "You've been invited to <Org>" banner, shown across the invited sign-up. */
.ecs-invite-banner {
  display: flex;
  gap: 12px;
  align-items: flex-start;
  padding: 14px 16px;
  margin-bottom: 20px;
  border-radius: var(--ecs-radius-md);
  background: var(--ecs-green-100);
  border: 1px solid var(--ecs-line-mint);
}
.ecs-invite-banner__icon { flex: none; line-height: 0; margin-top: 1px; }
.ecs-invite-banner__badge {
  font: 500 var(--ecs-fs-xs) / 1 var(--ecs-font-mono);
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: var(--ecs-forest-500);
  margin-bottom: 4px;
}
.ecs-invite-banner__msg {
  font: 400 var(--ecs-fs-sm) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text);
}

/* ---- Pills / badges / chips -------------------------------------------- */
/* ---- Status tag ----------------------------------------------------------
   Squared, mono, uppercase, tinted background with a coloured dot — the same
   object the client portal and the console both use to say "where is this".
   `.ecs-pill > i` is the dot; tone classes colour both halves. */
.ecs-pill {
  display: inline-flex;
  align-items: center;
  gap: 7px;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-3xs);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  white-space: nowrap;
  padding: 4px 9px;
  border-radius: var(--ecs-radius-tag);
  background: var(--ecs-crumb-bg);
  color: var(--ecs-text-muted);
}
.ecs-pill > i {
  width: 6px;
  height: 6px;
  border-radius: var(--ecs-radius-pill);
  background: currentColor;
  flex-shrink: 0;
}
.ecs-pill--solid { background: var(--ecs-green-200); color: var(--ecs-green-600); }
.ecs-pill--solid > i { background: var(--ecs-forest-500); }
.ecs-pill--bright { background: var(--ecs-green-400); color: var(--ecs-forest-900); }
.ecs-pill--outline {
  background: transparent;
  color: var(--ecs-text-muted);
  border: 1px solid var(--ecs-line-2);
}
.ecs-pill--outline-dark {
  background: transparent;
  color: var(--ecs-on-dark-3);
  border: 1px solid var(--ecs-line-dark-2);
}
.ecs-pill--ok { background: var(--ecs-green-200); color: var(--ecs-green-600); }
.ecs-pill--ok > i { background: var(--ecs-forest-500); }
.ecs-pill--warn { background: var(--ecs-warn-bg-2); color: var(--ecs-warn-ink); }
.ecs-pill--warn > i { background: var(--ecs-warn); }
.ecs-pill--alert { background: var(--ecs-alert-bg); color: var(--ecs-alert-ink); }
.ecs-pill--alert > i { background: var(--ecs-alert); }
.ecs-pill--info { background: var(--ecs-info-bg); color: var(--ecs-info-ink); }
.ecs-pill--info > i { background: var(--ecs-info); }
.ecs-pill--neutral > i { background: var(--ecs-text-faint); }

/* ---- Column header · summary list ----------------------------------------
   Two Carbon/GOV.UK borrowings the whole app reuses: the mono uppercase
   column label above a data table, and the key/value list that answers
   "what did you tell us" without becoming a form. */
.ecs-th {
  font: var(--ecs-th-font);
  letter-spacing: var(--ecs-th-tracking);
  text-transform: uppercase;
  color: var(--ecs-th-ink);
}
.ecs-th--num { text-align: right; }

.ecs-sum { margin: 0; }
.ecs-sum > div {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 12px;
  padding: 9px 0;
  border-bottom: 1px solid var(--ecs-line-3);
}
.ecs-sum > div:last-child { border-bottom: none; }
.ecs-sum dt { font: var(--ecs-fw-regular) var(--ecs-fs-2xs) var(--ecs-font-body); color: var(--ecs-text-muted); }
.ecs-sum dd {
  margin: 0;
  text-align: right;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-ink);
}
.ecs-sum dd.is-good { color: var(--ecs-green-600); }
.ecs-sum dd.is-warn { color: var(--ecs-warn-ink); }
.ecs-sum dd.is-muted { color: var(--ecs-text-faint); }

/* ---- Stat band -----------------------------------------------------------
   One card, N equal cells split by hairlines: uppercase label, the number,
   one line of context. Replaces the old free-floating stat cards. */
.ecs-statband {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: 1fr;
  background: var(--ecs-surface);
  border: 1px solid var(--ecs-line);
}
.ecs-statband > div { padding: 18px 22px; border-right: 1px solid var(--ecs-line-3); }
.ecs-statband > div:last-child { border-right: none; }
.ecs-statband__num {
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-bold);
  font-size: var(--ecs-fs-h2);
  line-height: 1.1;
  color: var(--ecs-ink);
  margin-top: 8px;
}
.ecs-statband__note {
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text-faint);
  margin-top: 4px;
}
.ecs-statband__note.is-good { color: var(--ecs-green-600); }
.ecs-statband__note.is-warn { color: var(--ecs-warn-ink); }
.ecs-statband__note.is-alert { color: var(--ecs-alert-ink); }

.ecs-tag-optional {
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-3xs);
  letter-spacing: 0.10em;
  text-transform: uppercase;
  color: var(--ecs-warn-ink);
  background: var(--ecs-warn-bg-2);
  padding: 4px 9px;
  border-radius: var(--ecs-radius-tag);
}

/* Confirmed / status inline chip with dot */
.ecs-confirm-chip {
  display: inline-flex;
  align-items: center;
  gap: 9px;
  background: var(--ecs-green-200);
  border: 1px solid var(--ecs-line-mint);
  border-radius: var(--ecs-radius-md);
  padding: 9px 13px;
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-xs);
  color: var(--ecs-forest-500);
}
.ecs-confirm-chip__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: var(--ecs-forest-500);
}

/* ---- Callout (differentiator / warning banners) ------------------------ */
.ecs-callout {
  display: flex;
  gap: 11px;
  align-items: flex-start;
  border-radius: var(--ecs-radius-lg);
  padding: 13px 15px;
}
.ecs-callout--mint {
  background: var(--ecs-green-100);
  border: 1px solid var(--ecs-line-mint);
  color: var(--ecs-text);
}
.ecs-callout--dark {
  background: rgba(var(--ecs-accent-rgb), 0.12);
  border: 1px solid rgba(var(--ecs-accent-rgb), 0.24);
  color: var(--ecs-on-dark-2);
}
.ecs-callout--warn {
  background: var(--ecs-warn-bg);
  border: 1px solid var(--ecs-warn-line);
  color: var(--ecs-warn-ink-2);
}
.ecs-callout svg { flex-shrink: 0; margin-top: 1px; }
.ecs-callout__body { font-weight: var(--ecs-fw-medium); font-size: var(--ecs-fs-xs); line-height: 1.5; }

/* ---- Checkbox (custom, used in toggles) -------------------------------- */
.ecs-check {
  width: 22px;
  height: 22px;
  border-radius: var(--ecs-radius-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border: 1.5px solid var(--ecs-line-strong);
  background: var(--ecs-surface);
  transition: background var(--ecs-dur-fast) var(--ecs-ease),
              border-color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-check svg { display: none; }
.ecs-check.is-on {
  background: var(--ecs-forest-500);
  border-color: var(--ecs-forest-500);
}
.ecs-check.is-on svg { display: block; }

.ecs-toggle-row {
  display: flex;
  align-items: center;
  gap: var(--ecs-space-3);
  background: var(--ecs-surface);
  border: 1px solid var(--ecs-line);
  border-radius: var(--ecs-radius-lg);
  padding: 15px 18px;
  cursor: pointer;
  transition: border-color var(--ecs-dur-fast) var(--ecs-ease),
              background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-toggle-row:hover { border-color: var(--ecs-line-strong); }
.ecs-toggle-row--stack { align-items: flex-start; }

/* ---- Avatar ------------------------------------------------------------- */
.ecs-avatar {
  border-radius: var(--ecs-radius-md);
  background: var(--ecs-green-500);
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-bold);
  color: var(--ecs-forest-900);
  flex-shrink: 0;
}
.ecs-avatar--dark { background: var(--ecs-forest-900); color: var(--ecs-green-400); }

/* ---- Dropzone ----------------------------------------------------------- */
.ecs-dropzone {
  border: 2px dashed var(--ecs-line-strong);
  border-radius: var(--ecs-radius-2xl);
  background: var(--ecs-surface-2);
  padding: 40px;
  text-align: center;
  cursor: pointer;
  transition: border-color var(--ecs-dur-base) var(--ecs-ease),
              background var(--ecs-dur-base) var(--ecs-ease);
}
.ecs-dropzone:hover { border-color: var(--ecs-green-500); background: var(--ecs-green-100); }
.ecs-dropzone.is-active { border-color: var(--ecs-green-500); background: var(--ecs-green-100); }
.ecs-dropzone__icon {
  width: 54px;
  height: 54px;
  border-radius: var(--ecs-radius-xl);
  background: var(--ecs-green-200);
  display: flex;
  align-items: center;
  justify-content: center;
  margin: 0 auto 14px;
}

/* ---- File row ----------------------------------------------------------- */
.ecs-file {
  display: flex;
  align-items: center;
  gap: 13px;
  border: 1px solid var(--ecs-line-3);
  background: var(--ecs-surface);
  border-radius: var(--ecs-radius-2xl);
  padding: 12px 14px;
}
.ecs-file__meta { font-family: var(--ecs-font-mono); font-size: var(--ecs-fs-2xs); color: var(--ecs-text-muted); }

/* Deployments card: the site list. A row with files expands into nights. */
.ecs-dep-block { display: flex; flex-direction: column; }
.ecs-dep-block.is-open .ecs-dep {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
  border-bottom-color: var(--ecs-green-300);
  background: var(--ecs-green-100);
}
.ecs-file__open,
.ecs-file__static {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 0;
  border: none;
  background: transparent;
  text-align: left;
  color: inherit;
}
.ecs-file__open { cursor: pointer; }
.ecs-file__copy {
  display: flex;
  flex-direction: column;
  gap: 2px;
  flex: 1 1 auto;
  min-width: 0;
}
.ecs-file__label {
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1.3 var(--ecs-font-display);
}
.ecs-file__open:hover .ecs-file__label { color: var(--ecs-forest-500); }
/* A phone gives the recorder line no room beside the count, so "AudioMoth ·
   exact" wrapped on either side of it. The count takes its own line under the
   copy, indented past the chevron (16px + the 10px gap). */
@media (max-width: 600px) {
  .ecs-dep .ecs-file__open { flex-wrap: wrap; row-gap: 2px; }
  .ecs-dep .ecs-night__count { flex-basis: calc(100% - 26px); margin-left: 26px; }
}
.ecs-browse--nested { margin-top: 0; }
.ecs-browse--nested .ecs-browse-scroll {
  border-top: none;
  border-top-left-radius: 0;
  border-top-right-radius: 0;
}
.ecs-browse__hint {
  margin: 0 0 12px;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-file--warn {
  align-items: flex-start;
  border-color: var(--ecs-warn-line);
  background: var(--ecs-warn-bg);
}
.ecs-file__err {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  margin-top: 10px;
  padding: 9px 11px;
  border-radius: var(--ecs-radius-sm);
  background: var(--ecs-surface);
  border: 1px solid var(--ecs-warn-line);
  color: var(--ecs-warn-ink-2);
  font-family: var(--ecs-font-body);
  font-size: var(--ecs-fs-xs);
  font-weight: var(--ecs-fw-medium);
  line-height: 1.45;
}
.ecs-file__err[hidden] { display: none; }
.ecs-file__err svg { flex-shrink: 0; margin-top: 1px; }

/* ---- One-bar upload run (P0-INTAKE) -------------------------------------
   The whole batch in a single readout. Fixed geometry on purpose: every value
   inside is mutated in place ten times a second, so nothing here may reflow
   when a number gets wider. */
.ecs-uprun {
  display: flex;
  align-items: center;
  gap: 16px;
  border: 1px solid var(--ecs-line-3);
  background: var(--ecs-surface);
  border-radius: var(--ecs-radius-xl);
  padding: 16px 18px;
  margin-bottom: 14px;
}
.ecs-upring { position: relative; width: 60px; height: 60px; flex-shrink: 0; }
.ecs-upring__track { fill: none; stroke: var(--ecs-line-3); stroke-width: 5; }
.ecs-upring__fill {
  fill: none;
  stroke: var(--ecs-forest-500);
  stroke-width: 5;
  stroke-linecap: round;
  transition: stroke-dashoffset var(--ecs-dur-base) linear;
}
.ecs-upring__pct {
  position: absolute;
  inset: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-bold);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text);
}
.ecs-uprun__title {
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-sm);
  margin-bottom: 4px;
}
.ecs-uprun__stats {
  display: flex;
  align-items: baseline;
  gap: 10px;
  flex-wrap: wrap;
  font-family: var(--ecs-font-mono);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text-muted);
}
/* Separators come from CSS, so app.js only ever writes the values themselves. */
.ecs-uprun__stats > span:not(:empty) ~ span:not(:empty)::before {
  content: '·';
  margin-right: 10px;
  color: var(--ecs-text-faint);
}
.ecs-uprun__note {
  margin-top: 8px;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-uprun__note[hidden] { display: none; }
.ecs-uprun__digest {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 8px;
  font: var(--ecs-fw-medium) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-warn-ink-2);
}
.ecs-uprun__digest:empty { display: none; }
.ecs-uprun__digest svg { display: block; }
.ecs-uprun__act { display: flex; align-items: center; gap: 8px; flex-shrink: 0; flex-wrap: wrap; }
/* Two controls beside the read-out (Retry failed and Pause) do not fit a 480px
   card next to the ring and its stats, and a flex row would squeeze the stats
   into a column one word wide. The read-out keeps a floor and the controls
   wrap under it instead. */
.ecs-uprun { flex-wrap: wrap; }
.ecs-uprun__body { flex: 1 1 14rem; min-width: 0; }

/* Per-site rows under the ring. The ring is the batch; these say which site is
   in, which is going and which came back with failures. Joined to the ring's
   card rather than a second card, because they are the same readout. Fixed
   tracks for the same reason as the ring: the counts are rewritten at the paint
   rate and nothing may shift sideways when one gains a digit. */
.ecs-upwrap { margin-bottom: 14px; }
.ecs-upwrap > .ecs-uprun { margin-bottom: 0; }
.ecs-upwrap > .ecs-uprun:has(+ div > .ecs-upsites) {
  border-bottom-left-radius: 0;
  border-bottom-right-radius: 0;
}
.ecs-upsites {
  margin: 0;
  padding: 6px 18px 10px;
  list-style: none;
  border: 1px solid var(--ecs-line-3);
  border-top: 0;
  border-radius: 0 0 var(--ecs-radius-xl) var(--ecs-radius-xl);
  background: var(--ecs-surface);
}
.ecs-upsite {
  display: grid;
  grid-template-columns: minmax(4.5rem, 11rem) minmax(40px, 1fr) 7rem 5.5rem;
  align-items: center;
  gap: 12px;
  min-height: 28px;
  border-bottom: 1px solid var(--ecs-line-4);
}
.ecs-upsite:last-child { border-bottom: 0; }
.ecs-upsite__label {
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
  font: var(--ecs-fw-medium) var(--ecs-fs-xs) / 1.3 var(--ecs-font-body);
  color: var(--ecs-ink);
}
.ecs-upsite__bar {
  position: relative;
  height: 4px;
  overflow: hidden;
  background: var(--ecs-line-4);
}
/* Scaled, not resized, for the same reason as .ecs-meter__fill — and this one
   repaints every few hundred milliseconds for the length of an upload. */
.ecs-upsite__bar > span {
  display: block;
  width: 100%;
  height: 100%;
  transform-origin: left;
  background: var(--ecs-forest-500);
  transition: transform var(--ecs-dur-base) linear;
}
.ecs-upsite.has-failed .ecs-upsite__bar > span { background: var(--ecs-warn); }
.ecs-upsite__count,
.ecs-upsite__state {
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.3 var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-text-muted);
  white-space: nowrap;
}
.ecs-upsite__count { text-align: right; }
.ecs-upsite__state { overflow: hidden; text-overflow: ellipsis; }
.ecs-upsite.is-uploading .ecs-upsite__state { color: var(--ecs-forest-700); }
.ecs-upsite.has-failed .ecs-upsite__state { color: var(--ecs-warn-ink); }
/* A phone keeps the name, the count and the state; the bar is the one the
   count already says in figures. */
@media (max-width: 600px) {
  .ecs-upsites { padding: 6px 12px 10px; }
  .ecs-upsite { grid-template-columns: minmax(0, 1fr) 7rem 5.5rem; gap: 10px; }
  .ecs-upsite__bar { display: none; }
}
@media (prefers-reduced-motion: reduce) {
  .ecs-upsite__bar > span { transition: none; }
}

/* The read-only grid over the Deployments card. Its cells open recordings
   rather than leave them out, so there is no struck state and no inspect chip. */
.ecs-projgrid { margin-bottom: 12px; }

/* ---- Bulk file selection + removal --------------------------------------- */
.ecs-bulkbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: 10px;
  min-height: 24px;
}
.ecs-bulkbar__count {
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text-muted);
}
.ecs-inline-danger {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  appearance: none;
  border: 0;
  background: none;
  padding: 1px 0;
  cursor: pointer;
  font: var(--ecs-fw-semi) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  text-decoration: underline;
  text-decoration-color: var(--ecs-line-strong);
  text-underline-offset: 3px;
}
.ecs-inline-danger:hover,
.ecs-inline-danger:focus-visible { color: var(--ecs-warn-ink); }
.ecs-tickbox {
  width: 15px;
  height: 15px;
  accent-color: var(--ecs-forest-500);
  cursor: pointer;
  vertical-align: middle;
}
tr.is-picked { background: var(--ecs-green-100); }
.ecs-files-more {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  margin-top: 10px;
  font-family: var(--ecs-font-mono);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text-muted);
}

/* ---- Round icon tile ---------------------------------------------------- */
.ecs-icon-tile {
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border-radius: var(--ecs-radius-md);
}

/* ---- Floating prototype nav pill --------------------------------------- */
.ecs-navpill {
  position: fixed;
  left: 50%;
  bottom: 20px;
  transform: translateX(-50%);
  z-index: 90;
  display: flex;
  align-items: center;
  gap: var(--ecs-space-3);
  background: rgba(var(--ecs-ink-rgb), 0.94);
  border: 1px solid rgba(255, 255, 255, 0.10);
  border-radius: var(--ecs-radius-pill);
  padding: 8px 10px;
  box-shadow: var(--ecs-shadow-pill);
  backdrop-filter: blur(6px);
}
.ecs-navpill__label {
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-on-dark-3);
  padding-left: 8px;
}
.ecs-navpill__sep { width: 1px; height: 20px; background: rgba(255, 255, 255, 0.14); }
.ecs-navpill__btn {
  width: 30px;
  height: 30px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.08);
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-navpill__btn:hover { background: rgba(255, 255, 255, 0.18); }
.ecs-navpill__btn--next { background: var(--ecs-green-400); }
.ecs-navpill__btn--next:hover { background: var(--ecs-green-300); }
.ecs-navpill__btn:disabled { opacity: 0.4; cursor: default; }
.ecs-navpill__dots { display: flex; align-items: center; gap: 6px; }
.ecs-navpill__dot {
  width: 8px;
  height: 8px;
  border-radius: var(--ecs-radius-pill);
  cursor: pointer;
  background: rgba(255, 255, 255, 0.22);
  transition: background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-navpill__dot.is-done { background: rgba(var(--ecs-accent-rgb), 0.5); }
.ecs-navpill__dot.is-active { width: 22px; background: var(--ecs-green-400); }
.ecs-navpill__reset {
  display: flex;
  align-items: center;
  gap: 6px;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-2xs);
  letter-spacing: 0.06em;
  color: var(--ecs-on-dark-3);
  background: none;
  border: none;
  cursor: pointer;
  padding: 0 8px;
  transition: color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-navpill__reset:hover { color: var(--ecs-on-dark); }

/* ---- Folder intake · review + coordinates steps (P0-INTAKE) -------------- */
/* An inset panel, not a card: these steps sit *inside* the uploader card, and
   the paper background is the same one the dropzone uses — a visual "still
   staging, nothing has uploaded". */
.ecs-intake {
  border: 1px solid var(--ecs-line-3);
  border-radius: var(--ecs-radius-xl);
  background: var(--ecs-surface-2);
  padding: var(--ecs-space-5);
}
.ecs-intake code {
  background: var(--ecs-line-4);
  border-radius: var(--ecs-radius-sm);
  padding: 1px 5px;
}

/* Step chrome (P0-CLIENT-UX). Every intake screen opens the same way — where you
   are, what this screen is for, then the folder it is about — so the wording is
   the only thing that changes between them. */
.ecs-stephead { margin-bottom: var(--ecs-space-4); }
.ecs-stephead__eyebrow {
  margin-bottom: 6px;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-3xs);
  letter-spacing: var(--ecs-tracking-wide);
  text-transform: uppercase;
  color: var(--ecs-green-500);
}
.ecs-stephead__title {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 5px;
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-title);
  line-height: 1.25;
  letter-spacing: var(--ecs-tracking-tight);
  color: var(--ecs-ink);
}
.ecs-stephead__sub {
  max-width: 76ch;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.55 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-stephead__sub + .ecs-stephead__sub { margin-top: 5px; }
.ecs-stephead__meta {
  margin-top: 7px;
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  color: var(--ecs-text-muted);
}

/* Panel head: icon tile + title, so the CSV card and the map card read as two
   equal doors to the same table rather than a card and an afterthought. */
.ecs-intake__head {
  display: flex;
  align-items: center;
  gap: 11px;
  margin-bottom: 7px;
}
.ecs-intake__title {
  font: var(--ecs-fw-semi) var(--ecs-fs-md) / 1.3 var(--ecs-font-display);
  color: var(--ecs-ink);
}
.ecs-intake__sub {
  max-width: 72ch;
  margin: 0 0 var(--ecs-space-3);
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.55 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
/* The one line inside a panel that changes as you work (which site the next map
   click places) — quieter than the subtitle above it, and it may reflow. */
.ecs-intake__hint {
  margin: 0 0 10px;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text);
}

/* The first cell of a coordinates row: the site, its recorder under the name,
   and the files this batch takes from it pushed to the end of the cell. The
   label is sized and weighted like `.ecs-site-label` and `.ecs-site-open` on
   step 1, so the same site reads the same on both steps of the wizard; the
   recorder line is `.ecs-site-row__meta` in shape and colour for the same
   reason. Neither is `--ecs-text-faint`: at 11px over the mint of a placing row
   that greys out below 3:1. */
.ecs-coord-site {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--ecs-space-3);
}
.ecs-coord-site__name { min-width: 0; }
.ecs-coord-site__label {
  font: var(--ecs-fw-medium) var(--ecs-fs-xs) / 1.3 var(--ecs-font-display);
  color: var(--ecs-ink);
}
.ecs-coord-site__recorder {
  margin-top: var(--ecs-space-1);
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  color: var(--ecs-text-muted);
}
/* A file count is a number to compare down the column, not prose: mono, tabular
   so the digits line up row to row, and hard against the end of the cell. */
.ecs-coord-site__count {
  flex-shrink: 0;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.4 var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-text-muted);
  white-space: nowrap;
}

/* A control that belongs to the map rather than to the field beside it, parked
   at the far end of the row so the two do not read as one gesture. */
.ecs-paste-row__end { margin-left: auto; }

/* One line over a table: what it holds, and the single control over it. */
.ecs-intake-toolbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ecs-space-3);
  flex-wrap: wrap;
  margin: var(--ecs-space-4) 0 var(--ecs-space-2);
}
.ecs-intake-toolbar__label {
  font: var(--ecs-fw-semi) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  letter-spacing: 0.04em;
  color: var(--ecs-text);
}
.ecs-intake-toolbar__hint {
  margin: 0 0 var(--ecs-space-2);
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}

/* Issues: one row per finding, with its fix on the same row. A warning nobody
   can act on is just a yellow box. */
.ecs-issues__note {
  margin: 0 0 10px;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-warn-ink);
}
.ecs-issues { margin: 0; padding: 0; list-style: none; }
.ecs-issue {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: var(--ecs-space-3);
  padding: 8px 0;
  border-top: 1px solid var(--ecs-warn-line);
}
.ecs-issue:first-child { border-top: none; padding-top: 0; }
.ecs-issue:last-child { padding-bottom: 0; }
.ecs-issue__text {
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-warn-ink-2);
}
.ecs-issue__act {
  display: flex;
  align-items: center;
  gap: 12px;
  flex-wrap: wrap;
  flex-shrink: 0;
}
.ecs-issue-link {
  appearance: none;
  border: 0;
  background: none;
  padding: 1px 0;
  cursor: pointer;
  font: var(--ecs-fw-semi) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-body);
  color: var(--ecs-forest-500);
  text-decoration: underline;
  text-decoration-color: color-mix(in srgb, currentColor 45%, transparent);
  text-underline-offset: 3px;
}
.ecs-issue-link:hover { color: var(--ecs-green-600); }

/* Wide tables (seven columns of site metadata) scroll on their own rather than
   pushing the whole page sideways. */
.ecs-table-scroll { overflow-x: auto; }

.ecs-site-row { border-bottom: 1px solid var(--ecs-line); }
.ecs-site-row:last-child { border-bottom: none; }
.ecs-site-row.is-problem { box-shadow: inset 0 -1px var(--ecs-warn-line); }
.ecs-row-remove-cell {
  width: 30px;
  min-width: 30px;
  padding: 0 2px 0 8px;
  text-align: center;
  vertical-align: middle;
}
.ecs-row-remove {
  display: inline-flex;
  width: 22px;
  height: 22px;
  align-items: center;
  justify-content: center;
  padding: 0;
  border: 1px solid transparent;
  border-radius: 50%;
  background: transparent;
  color: var(--ecs-text-muted);
  cursor: pointer;
}
.ecs-row-remove:hover,
.ecs-row-remove:focus-visible {
  border-color: var(--ecs-warn-line);
  background: var(--ecs-warn-bg);
  color: var(--ecs-warn-ink);
}
.ecs-row-remove.is-restore:hover,
.ecs-row-remove.is-restore:focus-visible {
  border-color: var(--ecs-green-300);
  background: var(--ecs-green-100);
  color: var(--ecs-forest-500);
}
.ecs-site-row__meta {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-wrap: wrap;
  margin-top: 5px;
  font-family: var(--ecs-font-mono);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text-muted);
}
/* Prose under a site name (e.g. merge status) — same face as the table, not data mono. */
.ecs-site-row__note {
  margin-top: 5px;
  padding: 0 8px;
  font-family: var(--ecs-font-body);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text-muted);
}
/* Static site/recorder text that lines up with the inline edit fields above. */
.ecs-site-label {
  padding: 6px 8px;
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-xs);
  line-height: 1.3;
  color: var(--ecs-ink);
}
/* Dropped from the batch: still listed (so it can be put back) but visibly out. */
.ecs-site-row.is-excluded { opacity: 0.55; }
.ecs-site-row.is-excluded .ecs-input { text-decoration: line-through; }
.ecs-site-row.is-placing { background: var(--ecs-green-100); }

/* An editable cell that does not look like a form: the border appears only on
   hover/focus, so a table of six site names still reads as a table. */
.ecs-input--inline {
  margin: 0;
  padding: 6px 8px;
  border-color: transparent;
  background: transparent;
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-xs);
  width: 100%;
  min-width: 90px;
}
.ecs-input--inline:hover { border-color: var(--ecs-line-strong); background: var(--ecs-surface); }
.ecs-input--inline:focus { border-color: var(--ecs-green-500); background: var(--ecs-surface); }
.ecs-input--inline:disabled { cursor: not-allowed; }
/* Edit mode is on: the cells that are now editable have to look it, or a table
   of six site names still reads as a table you can only look at. */
.ecs-input--inline.is-editable { border-color: var(--ecs-line-2); background: var(--ecs-surface); }
/* Coordinates are the one thing in these tables a client is expected to *type*,
   so unlike the borderless site-name cells they carry a resting border: a pair
   of bare "Latitude / Longitude" placeholders reads as two column labels. */
.ecs-input--num {
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-2xs);
  min-width: 96px;
  max-width: 128px;
  border-color: var(--ecs-line-2);
  background: var(--ecs-surface);
}
/* Typed text that does not read as a coordinate — flagged as you type, never
   silently discarded. */
.ecs-input--bad { border-color: var(--ecs-warn-line); background: var(--ecs-warn-bg); }
/* Why a typed coordinate was refused, under the field it was typed into. */
.ecs-coord-problem {
  margin-top: 5px;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.45 var(--ecs-font-body);
  color: var(--ecs-warn-ink);
}

.ecs-coord-pair { display: flex; gap: 6px; }

.ecs-select--sm { padding: 7px 28px 7px 10px; font-size: var(--ecs-fs-2xs); }
.ecs-site-merge { display: inline-block; width: 132px; margin-right: 8px; vertical-align: middle; }
/* Header Edit / Done + row merge·drop — spaced after SIZE. */
.ecs-site-actions {
  padding: 8px 12px 8px 28px;
  text-align: right;
  white-space: nowrap;
  vertical-align: middle;
}
.ecs-site-actions .ecs-btn + .ecs-btn,
.ecs-site-actions .ecs-site-merge + .ecs-btn { margin-left: 8px; }
/* View mode: site name opens the file inspect step. */
.ecs-site-open {
  border: none;
  background: none;
  padding: 0;
  margin: 0;
  cursor: pointer;
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1.3 var(--ecs-font-display);
  color: var(--ecs-forest-500);
  text-align: left;
  text-decoration: none;
}
.ecs-site-open:hover {
  color: var(--ecs-green-600);
  text-decoration: underline;
  text-underline-offset: 3px;
}
.ecs-site-open.is-problem {
  text-decoration: underline;
  text-decoration-color: var(--ecs-warn-line);
  text-decoration-thickness: 1px;
  text-underline-offset: 4px;
}
.ecs-site-row.is-excluded .ecs-site-open { text-decoration: line-through; }

/* Jump-to-files link inside an issue line. */
.ecs-issue__jump {
  border: none;
  background: none;
  padding: 0;
  cursor: pointer;
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-forest-500);
  text-decoration: underline;
}
.ecs-issue__jump:hover { color: var(--ecs-green-600); }
/* Where an issue link lands — a brief tint, so the eye finds the row. */
@keyframes ecs-row-flash {
  0%, 60% { background: var(--ecs-green-200); }
  100% { background: transparent; }
}
.ecs-site-row.is-flash { animation: ecs-row-flash 1.6s var(--ecs-ease); }

/* ---- the site × night grid (intake step 1) -------------------------------
   A field season is a rectangle, so the review step is one: a row per recorder
   site, a column per survey night, a count in every cell. Density is the point —
   twelve recorders over six weeks has to fit on a laptop — so the chrome is
   hairlines and one mint ramp, and everything else is the numbers. */
.ecs-matrix-head { align-items: flex-start; margin-bottom: var(--ecs-space-3); }
.ecs-matrix-head__text { min-width: 0; }
.ecs-matrix-head__kicker {
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) / 1.4 var(--ecs-font-mono);
  letter-spacing: var(--ecs-tracking-wide);
  text-transform: uppercase;
  color: var(--ecs-text-muted);
}
.ecs-matrix-head__sub {
  margin: 5px 0 0;
  max-width: 64ch;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.55 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}

/* Past a dozen nights the grid is wider than the card. It scrolls inside its own
   frame rather than pushing the page sideways, and the row header column stays
   put — a count with no site name against it is not a fact. */
.ecs-matrix-wrap {
  overflow-x: auto;
  border: 1px solid var(--ecs-line);
  background: var(--ecs-surface);
}
.ecs-matrix {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-variant-numeric: tabular-nums;
}
.ecs-matrix__empty {
  margin: 0;
  padding: var(--ecs-space-6) 0;
  text-align: center;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}

/* The two sticky columns of the frame: the corner labels the row axis and the
   row header carries the site. Both sit above the cells that scroll under them. */
.ecs-matrix__corner,
.ecs-matrix__rowh {
  position: sticky;
  left: 0;
  z-index: 2;
  width: 240px;
  min-width: 240px;
  padding: 8px 12px;
  text-align: left;
  vertical-align: top;
  background: var(--ecs-surface);
  border-right: 1px solid var(--ecs-line);
}
.ecs-matrix__corner {
  z-index: 3;
  vertical-align: bottom;
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) / 1.4 var(--ecs-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
  border-bottom: 1px solid var(--ecs-line);
}

/* Column head: the night on two lines, its tick, and what it holds. */
.ecs-matrix__colh {
  min-width: 56px;
  padding: 8px 6px 6px;
  text-align: center;
  vertical-align: bottom;
  background: var(--ecs-surface);
  border-bottom: 1px solid var(--ecs-line);
  border-left: 1px solid var(--ecs-line-4);
}
.ecs-matrix__colday,
.ecs-matrix__colmon,
.ecs-matrix__coltot {
  display: block;
  white-space: nowrap;
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) / 1.35 var(--ecs-font-mono);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.ecs-matrix__colday { color: var(--ecs-ink); }
.ecs-matrix__colmon { color: var(--ecs-text-muted); }
.ecs-matrix__coltot {
  letter-spacing: 0;
  text-transform: none;
  color: var(--ecs-text-muted);
}
/* Recordings we could not read a time out of are a real bucket, not a gap, and
   they are the one column a client should look at twice. */
.ecs-matrix__colh.is-unknown { background: var(--ecs-warn-bg); }
.ecs-matrix__colh.is-unknown .ecs-matrix__colday,
.ecs-matrix__colh.is-unknown .ecs-matrix__colmon { color: var(--ecs-warn-ink); }

/* Week groups: the fold belongs to the group, so the affordance does too. */
.ecs-matrix__grouph {
  padding: 6px;
  text-align: center;
  background: var(--ecs-surface);
  border-bottom: 1px solid var(--ecs-line-4);
  border-left: 1px solid var(--ecs-line-4);
}
.ecs-matrix__week {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  max-width: 100%;
  padding: 2px 7px;
  border: 1px solid var(--ecs-line-2);
  border-radius: var(--ecs-radius-sm);
  background: var(--ecs-green-100);
  cursor: pointer;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-mono);
  letter-spacing: 0.04em;
  color: var(--ecs-green-600);
}
.ecs-matrix__weeklabel { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ecs-matrix__week:hover { border-color: var(--ecs-green-500); background: var(--ecs-green-200); }

/* One tick for a row and a column both, so the two read as the same decision.
   A row's keeps the incumbent `ecs-row-remove` name — it is the same control. */
.ecs-matrix__tick,
.ecs-matrix__rowh .ecs-row-remove {
  display: inline-flex;
  width: 16px;
  height: 16px;
  flex: none;
  align-items: center;
  justify-content: center;
  margin: 5px 0 0;
  padding: 0;
  border: 1px solid var(--ecs-line-field);
  border-radius: var(--ecs-radius-md);
  background: var(--ecs-surface);
  color: var(--ecs-surface);
  cursor: pointer;
  transition: background var(--ecs-dur-toggle) var(--ecs-ease), border-color var(--ecs-dur-toggle) var(--ecs-ease);
}
.ecs-matrix__rowh .ecs-row-remove { margin: 0; }
.ecs-matrix__tick[aria-checked='true'],
.ecs-matrix__rowh .ecs-row-remove[aria-checked='true'] {
  border-color: var(--ecs-forest-500);
  background: var(--ecs-forest-500);
}
/* Partly in: a column some sites kept and some did not. The dash is the one
   mark that is neither the tick nor the empty box, so the third state reads as
   a third state and not as a half-painted one of the other two. */
.ecs-matrix__tick[aria-checked='mixed'] {
  border-color: var(--ecs-forest-500);
  background: var(--ecs-surface);
  color: var(--ecs-forest-500);
}
.ecs-matrix__tick[aria-checked='mixed']:hover,
.ecs-matrix__tick[aria-checked='mixed']:focus-visible {
  border-color: var(--ecs-green-600);
  background: var(--ecs-green-100);
  color: var(--ecs-green-600);
}
/* The row's tick inherits the old remove control's amber hover, which would
   flip a ticked row from forest to warning on the way past it. Both states say
   what they say on hover too, in the one hue this region is allowed. */
.ecs-matrix__tick:hover,
.ecs-matrix__tick:focus-visible,
.ecs-matrix__rowh .ecs-row-remove:hover,
.ecs-matrix__rowh .ecs-row-remove:focus-visible {
  border-color: var(--ecs-green-600);
  background: var(--ecs-green-100);
  color: var(--ecs-green-100);
}
.ecs-matrix__tick[aria-checked='true']:hover,
.ecs-matrix__tick[aria-checked='true']:focus-visible,
.ecs-matrix__rowh .ecs-row-remove[aria-checked='true']:hover,
.ecs-matrix__rowh .ecs-row-remove[aria-checked='true']:focus-visible {
  background: var(--ecs-green-600);
  color: var(--ecs-surface);
}

/* Row header: the site, what recorded it, what it raised, and (in edit mode)
   the controls that used to live off the right edge of a seven-column table. */
.ecs-matrix__rowtop { display: flex; align-items: center; gap: 8px; }
.ecs-matrix__rowmeta,
.ecs-matrix__chips {
  display: flex;
  align-items: center;
  gap: 6px;
  flex-wrap: wrap;
  margin-top: 4px;
  padding-left: 24px;
}
.ecs-matrix__recorder,
.ecs-matrix__folder {
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  color: var(--ecs-text-muted);
}
.ecs-matrix__rowh .ecs-site-actions { padding: 8px 0 2px 24px; text-align: left; }
.ecs-matrix__row.is-quiet .ecs-matrix__rowh { color: var(--ecs-text-muted); }

/* A finding that belongs to one site belongs on that site's row: rust for a file
   we cannot use as sent, amber for one that will work but is worth a look. */
.ecs-matrix__chip {
  padding: 1px 6px;
  border: 1px solid;
  border-radius: var(--ecs-radius-sm);
  cursor: pointer;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-mono);
  white-space: nowrap;
}
.ecs-matrix__chip.is-warn {
  border-color: var(--ecs-warn-line);
  background: var(--ecs-warn-bg);
  color: var(--ecs-warn-ink);
}
.ecs-matrix__chip.is-alert {
  border-color: var(--ecs-alert-line);
  background: var(--ecs-alert-bg);
  color: var(--ecs-alert-ink);
}
.ecs-matrix__chip:hover { border-color: currentColor; }

/* The cells. The mint ramp is the whole chart: five steps of one hue scaled by
   bytes, so a fat night reads at a glance without a second colour anywhere. */
.ecs-matrix__c {
  padding: 0;
  border-bottom: 1px solid var(--ecs-line-3);
  border-left: 1px solid var(--ecs-line-4);
}
/* A night this recorder never saw is hatched, not blank: an empty cell and a
   cell holding nothing are different facts and must not look alike. */
.ecs-matrix__c.is-empty {
  background-image: repeating-linear-gradient(45deg, transparent 0 5px, var(--ecs-line) 5px 6px);
}
.ecs-matrix__cell {
  position: relative;
  display: block;
  width: 100%;
  min-width: 56px;
  height: 34px;
  padding: 0 8px;
  border: 0;
  cursor: pointer;
  text-align: right;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 34px var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-ink);
  transition: background var(--ecs-dur-toggle) var(--ecs-ease);
}
.ecs-matrix__cell.is-h1 { background: var(--ecs-green-100); }
.ecs-matrix__cell.is-h2 { background: color-mix(in srgb, var(--ecs-green-200) 55%, var(--ecs-green-100)); }
.ecs-matrix__cell.is-h3 { background: var(--ecs-green-200); }
.ecs-matrix__cell.is-h4 { background: color-mix(in srgb, var(--ecs-green-300) 55%, var(--ecs-green-200)); }
.ecs-matrix__cell.is-h5 { background: var(--ecs-green-300); }
/* Left out: struck through and off the ramp entirely, so a glance down a column
   tells included from excluded before any number is read. */
.ecs-matrix__cell.is-out {
  background: var(--ecs-paper);
  color: var(--ecs-text-muted);
  text-decoration: line-through;
}
.ecs-matrix__cell:hover { outline: 1px solid var(--ecs-green-500); outline-offset: -1px; }
/* Drawn inside the box: the grid scrolls sideways, and a ring outside a cell
   would be clipped at the sticky row header and painted over by the next cell.
   Rows, tabs, segments and the file-list scroller sit flush against their
   neighbours for the same reason. Edge inward: the halo, then the ring. */
.ecs-matrix__cell:focus-visible,
.ecs-tab:focus-visible,
.ecs-seg__btn:focus-visible,
.ecs-recent__row:focus-visible,
.ecs-wlist__body:focus-visible {
  outline: var(--ecs-focus-width) solid var(--ecs-focus-ring);
  outline-offset: calc((var(--ecs-focus-offset) + var(--ecs-focus-width)) * -1);
  box-shadow: inset 0 0 0 var(--ecs-focus-offset) var(--ecs-focus-halo);
}
/* A dropped site's cells still carry the numbers the client needs to decide
   whether to put it back, so they stay readable: text-faint on the excluded
   ground is 2.5:1, which is not a number anyone reads off a 34px cell. */
.ecs-matrix__cell:disabled { cursor: not-allowed; color: var(--ecs-text-muted); }
.ecs-matrix__cell:disabled .ecs-matrix__peek { display: none; }
.ecs-matrix__n { font-variant-numeric: tabular-nums; }
/* Open-this-cell, kept out of the way until the pointer or the keyboard is on
   the cell — a magnifier in all five hundred of them at rest is noise. */
.ecs-matrix__peek {
  position: absolute;
  top: 3px;
  left: 3px;
  display: inline-flex;
  width: 17px;
  height: 17px;
  align-items: center;
  justify-content: center;
  border: 1px solid var(--ecs-line-2);
  background: var(--ecs-surface);
  color: var(--ecs-forest-500);
  opacity: 0;
  transition: opacity var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-matrix__cell:hover .ecs-matrix__peek,
.ecs-matrix__cell:focus-visible .ecs-matrix__peek { opacity: 1; }
.ecs-matrix__peek:hover { border-color: var(--ecs-green-500); background: var(--ecs-green-100); }

/* Row totals down the right, column totals along the bottom — the grid closes
   its own arithmetic, so no client has to take the sticky bar on trust. */
.ecs-matrix__toth,
.ecs-matrix__rangeh {
  padding: 8px 12px;
  white-space: nowrap;
  vertical-align: bottom;
  background: var(--ecs-surface);
  border-bottom: 1px solid var(--ecs-line);
  border-left: 1px solid var(--ecs-line);
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) / 1.4 var(--ecs-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
}
.ecs-matrix__toth { text-align: right; }
.ecs-matrix__rangeh { text-align: left; }
.ecs-matrix__tot,
.ecs-matrix__range {
  padding: 6px 12px;
  white-space: nowrap;
  vertical-align: middle;
  border-bottom: 1px solid var(--ecs-line-3);
  border-left: 1px solid var(--ecs-line);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  color: var(--ecs-ink);
}
.ecs-matrix__tot { text-align: right; }
.ecs-matrix__range { font-weight: var(--ecs-fw-regular); color: var(--ecs-text-muted); }
.ecs-matrix__sub { display: block; font-weight: var(--ecs-fw-regular); color: var(--ecs-text-muted); }
.ecs-matrix__foot .ecs-matrix__corner,
.ecs-matrix__foott {
  padding: 8px 12px;
  text-align: right;
  white-space: nowrap;
  background: var(--ecs-green-100);
  border-top: 1px solid var(--ecs-line-strong);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  color: var(--ecs-ink);
}
.ecs-matrix__foot .ecs-matrix__corner {
  text-align: left;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  font-size: var(--ecs-fs-3xs);
  color: var(--ecs-text-muted);
  border-bottom: 0;
}

/* On a phone a 240px frozen column is most of the screen and the cells scroll
   in a slot too narrow to compare anything in. The row header gives up its
   width first; the grid still scrolls inside its own frame, so the page never
   does. */
@media (max-width: 560px) {
  .ecs-matrix__corner,
  .ecs-matrix__rowh {
    width: 148px;
    min-width: 148px;
    padding: 8px;
  }
  .ecs-matrix__rowmeta,
  .ecs-matrix__chips { padding-left: 0; }
  .ecs-matrix__rowh .ecs-site-actions { padding-left: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .ecs-matrix__tick,
  .ecs-matrix__rowh .ecs-row-remove,
  .ecs-matrix__cell,
  .ecs-matrix__peek { transition: none; }
}

/* File inspect: fixed panel; nights accordion scrolls inside, not the page. */
.ecs-intake--files {
  display: flex;
  flex-direction: column;
  /* Topbar + back + flowbody padding — panel fills the rest; scroll stays inside. */
  height: calc(100dvh - 148px);
  min-height: 360px;
  max-height: calc(100dvh - 148px);
  margin-bottom: 0;
  padding-bottom: 0;
  overflow: hidden;
}
.ecs-inspect-head { flex-shrink: 0; padding-bottom: 12px; }
.ecs-intake--files .ecs-intake-bar {
  position: relative;
  flex-shrink: 0;
  margin: 0 calc(var(--ecs-space-5) * -1) 0;
  border-radius: 0 0 var(--ecs-radius-xl) var(--ecs-radius-xl);
}

/* Uploaded-files browser: site list → nights → files (batch edit + view). */
.ecs-browse { margin-top: 10px; }
.ecs-browse__summary {
  margin: 0 0 8px;
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  color: var(--ecs-text-muted);
}
.ecs-browse-head__meta {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 4px;
}
.ecs-browse-scroll {
  max-height: min(52vh, 560px);
  overflow-x: hidden;
  overflow-y: auto;
  border: 1px solid var(--ecs-line);
  border-radius: var(--ecs-radius-lg);
  background: var(--ecs-surface, #fff);
}
.ecs-browse-site {
  display: flex;
  align-items: center;
  gap: 8px;
  border-bottom: 1px solid var(--ecs-line-4);
  padding-right: 8px;
}
.ecs-browse-site:last-child { border-bottom: none; }
.ecs-browse-site__remove {
  display: flex;
  flex: 0 0 auto;
  padding-left: 8px;
}
.ecs-browse-site__head {
  display: flex;
  align-items: center;
  gap: 10px;
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  padding: 12px 4px 12px 8px;
  border: none;
  background: transparent;
  cursor: pointer;
  text-align: left;
  color: var(--ecs-text);
}
.ecs-browse-site__head:hover { background: var(--ecs-green-100); }
.ecs-browse-site__label {
  flex: 1 1 auto;
  min-width: 0;
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1.3 var(--ecs-font-display);
}

/* Collapsed-by-default survey nights. */
.ecs-night { border-bottom: 1px solid var(--ecs-line-4); }
.ecs-night:last-child { border-bottom: none; }
.ecs-night__head {
  display: flex;
  align-items: center;
  gap: 10px;
  width: 100%;
  margin: 0;
  padding: 12px 4px;
  border: none;
  background: transparent;
  cursor: pointer;
  text-align: left;
  color: var(--ecs-text);
}
.ecs-night__head:hover { background: var(--ecs-green-100); }
.ecs-night__chev {
  display: inline-flex;
  flex-shrink: 0;
  color: var(--ecs-text-muted);
}
.ecs-night__label {
  flex: 1 1 auto;
  min-width: 0;
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1.3 var(--ecs-font-display);
}
.ecs-night__count {
  flex-shrink: 0;
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.3 var(--ecs-font-mono);
  color: var(--ecs-text-muted);
}
.ecs-night-files {
  padding: 0 0 10px 26px;
}
.ecs-file-row { border-bottom: 1px solid var(--ecs-line-4); }
.ecs-file-row.is-mtime td:first-child { box-shadow: inset 3px 0 0 var(--ecs-warn-line); }
/* File an issue jump landed on — tint the whole row so it reads immediately. */
.ecs-file-row.is-focus {
  background: var(--ecs-warn-bg);
}
.ecs-file-row.is-focus td:first-child { box-shadow: inset 3px 0 0 var(--ecs-warn-ink); }

/* ---- windowed file list (js/screens.js `windowedFileList`) -----------------
   One row per recording, and only the visible ones in the DOM. Every dimension
   the JS also needs is a custom property written into the markup — the row
   height above all — so the arithmetic that decides which rows to draw and the
   rule that gives them their height can never drift apart.
   A grid, not a table: the header and the rows share one track list, which is
   what lets the rows be absolutely positioned under a spacer and still line up. */
.ecs-wlist {
  --ecs-wlist-cols: 1fr;
  --ecs-wlist-row: 32px;
  --ecs-wlist-h: 60vh;
  margin-top: var(--ecs-space-3);
  border: 1px solid var(--ecs-line);
  background: var(--ecs-surface);
}
.ecs-wlist__head,
.ecs-wlist__row {
  display: grid;
  grid-template-columns: var(--ecs-wlist-cols);
  align-items: center;
}
/* Stuck to the top *inside* the scroller: outside it the header would be a
   scrollbar wider than the rows, and every column would sit off its heading. */
.ecs-wlist__head {
  position: sticky;
  top: 0;
  z-index: 1;
  height: 28px;
  border-bottom: 1px solid var(--ecs-line);
  background: var(--ecs-surface-2);
  font: var(--ecs-th-font);
  letter-spacing: var(--ecs-th-tracking);
  text-transform: uppercase;
  color: var(--ecs-th-ink);
}
.ecs-wlist__body {
  position: relative;
  height: var(--ecs-wlist-h);
  overflow-x: hidden;
  overflow-y: auto;
  /* A list this long swallows the wheel; the page behind it should not move. */
  overscroll-behavior: contain;
}
/* The spacer is as tall as the whole list, so the scrollbar tells the truth
   about how much is behind it; the window rides on top of it. */
.ecs-wlist__run { position: relative; width: 100%; }
.ecs-wlist__win {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  will-change: transform;
}
.ecs-wlist__row {
  height: var(--ecs-wlist-row);
  border-bottom: 1px solid var(--ecs-line-4);
  transition: background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-wlist__row:hover { background: var(--ecs-surface-2); }
.ecs-wlist__row.is-picked { background: var(--ecs-surface-pick); }
/* Left out of the batch: still listed, because it is still in the plan and
   putting the night back brings it straight home. Struck through on the paper
   tint the review grid marks its excluded cells with — muted, but still text a
   client has to be able to read, so not faded below the contrast floor. */
.ecs-wlist__row.is-out { background: var(--ecs-paper); color: var(--ecs-text-muted); }
.ecs-wlist__row.is-out .ecs-wlist__name {
  text-decoration: line-through;
  color: var(--ecs-text-muted);
}
/* Where an issue jump lands — a rule down the edge, not a wash: with the issue
   as the scope every row on screen is one of its files. */
.ecs-wlist__row#ecs-inspect-focus { box-shadow: inset 2px 0 0 var(--ecs-warn-ink); }
.ecs-wlist__cell {
  min-width: 0;
  padding: 0 var(--ecs-space-2);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}
.ecs-wlist__cell.is-right { text-align: right; }
.ecs-wlist__cell--tick,
.ecs-wlist__cell--act {
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 0;
}
.ecs-wlist__name {
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1 var(--ecs-font-mono);
  color: var(--ecs-ink);
}
.ecs-wlist__mono {
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1 var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-text-muted);
}
/* Where the recording time came from. A time read out of the filename is the
   normal case and stays plain text — drawn as a chip it would be an identical
   bordered box a thousand rows deep, saying nothing. Only the file's own date,
   which is the one worth doubting, is marked. */
.ecs-wchip {
  display: inline-block;
  max-width: 100%;
  overflow: hidden;
  padding: 2px 0;
  border: 1px solid transparent;
  border-radius: var(--ecs-radius-tag);
  /* 12px, not 11px: this is a data cell the client reads, the same value the
     table it replaces set in 12px mono. 11px is for the kickers and the column
     headings above it. */
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.3 var(--ecs-font-mono);
  color: var(--ecs-text-muted);
  text-overflow: ellipsis;
  white-space: nowrap;
}
.ecs-wchip.is-weak {
  padding: 2px 6px;
  border-color: var(--ecs-warn-line);
  background: var(--ecs-warn-bg);
  color: var(--ecs-warn-ink);
}
/* The remove control surfaces on hover and focus but is never taken out of the
   flow: `display:none` here would make the tab order lie about what is there. */
.ecs-wlist__cell--act .ecs-row-remove {
  opacity: 0;
  transition: opacity var(--ecs-dur-fast) var(--ecs-ease);
}
/* `:focus`, not `:focus-visible` — a control a keyboard has reached has to be
   on screen, and the only way to reach this one with a mouse is to hover it. */
.ecs-wlist__row:hover .ecs-row-remove,
.ecs-wlist__cell--act .ecs-row-remove:focus { opacity: 1; }
/* Empty is a state of the list, not a replacement for it: the header stays, so
   the filter above can be corrected against the columns it was filtering. */
.ecs-wlist__empty {
  padding: var(--ecs-space-6) var(--ecs-space-4);
  text-align: center;
}
.ecs-wlist__empty-note {
  margin: 0;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-wlist__empty[hidden] { display: none; }
/* 60vh is the component's own height, and what a list dropped into a flowing
   page wants. The inspect panel is already pinned to the viewport, so inside it
   the list flexes instead — 60vh on top of a fixed panel pushes the step footer
   off the bottom of a laptop screen, and the panel clips it. */
.ecs-intake--files .ecs-wlist {
  display: flex;
  flex-direction: column;
  flex: 1 1 auto;
  min-height: 0;
}
.ecs-intake--files .ecs-wlist__body {
  flex: 1 1 auto;
  height: auto;
  min-height: 132px;
}
.ecs-intake--files .ecs-inspect-sel { flex-shrink: 0; }

/* ---- the inspect step's own chrome: breadcrumb, toolbar, selection bar ---- */
.ecs-inspect-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ecs-space-3);
  flex-wrap: wrap;
  margin-top: var(--ecs-space-2);
}
.ecs-inspect-crumb {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  min-width: 0;
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.4 var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-text-muted);
}
.ecs-inspect-crumb__site { font-weight: var(--ecs-fw-semi); color: var(--ecs-ink); }
.ecs-inspect-crumb__sep { color: var(--ecs-text-faint); }
.ecs-inspect-crumb__count { color: var(--ecs-text); }
.ecs-inspect-crumb__out {
  padding: 1px 6px;
  border: 1px solid var(--ecs-warn-line);
  background: var(--ecs-warn-bg);
  color: var(--ecs-warn-ink);
  font-size: var(--ecs-fs-3xs);
  letter-spacing: 0.06em;
  text-transform: uppercase;
}
.ecs-inspect-steps { display: flex; gap: var(--ecs-space-2); flex-shrink: 0; }
.ecs-inspect-tools {
  display: flex;
  align-items: center;
  gap: var(--ecs-space-2);
  flex-wrap: wrap;
  margin-top: var(--ecs-space-3);
}
.ecs-inspect-tools__end { margin-left: auto; }
.ecs-inspect-find {
  display: flex;
  align-items: center;
  gap: 6px;
  flex: 1 1 220px;
  min-width: 180px;
  max-width: 340px;
  padding: 0 8px;
  border: 1px solid var(--ecs-line-field);
  border-radius: var(--ecs-radius-md);
  background: var(--ecs-surface);
}
.ecs-inspect-find:focus-within {
  border-color: var(--ecs-forest-500);
  box-shadow: 0 0 0 3px var(--ecs-green-300);
}
.ecs-inspect-find__input {
  flex: 1 1 auto;
  min-width: 0;
  height: var(--ecs-ctl-h-sm);
  border: none;
  outline: none;
  background: transparent;
  font: var(--ecs-fw-regular) var(--ecs-fs-xs) var(--ecs-font-body);
  color: var(--ecs-ink);
}
.ecs-inspect-find__clear {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--ecs-text-muted);
  cursor: pointer;
}
.ecs-inspect-find__clear:hover:not(:disabled) { color: var(--ecs-ink); }
.ecs-inspect-find__clear:disabled { opacity: 0.3; cursor: default; }
.ecs-inspect-toggle {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: var(--ecs-ctl-h-sm);
  padding: 0 11px;
  border: 1px solid var(--ecs-line-field);
  border-radius: var(--ecs-radius-ctl);
  background: var(--ecs-surface);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
  cursor: pointer;
  transition: background var(--ecs-dur-fast) var(--ecs-ease),
              color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-inspect-toggle:hover { background: var(--ecs-surface-2); color: var(--ecs-ink); }
.ecs-inspect-toggle.is-on {
  border-color: var(--ecs-green-300);
  background: var(--ecs-green-200);
  color: var(--ecs-green-600);
}
.ecs-inspect-scoped {
  display: flex;
  align-items: baseline;
  gap: 6px;
  flex-wrap: wrap;
  margin: var(--ecs-space-3) 0 0;
  font: var(--ecs-fw-regular) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-warn-ink);
}
.ecs-inspect-shown {
  margin: var(--ecs-space-2) 0 0;
  font: var(--ecs-fw-regular) var(--ecs-fs-3xs) / 1.4 var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
}
/* Appears under the list once anything is ticked. Kept in the DOM either way,
   so the keyboard that ticked a row can reach the button that acts on it. */
.ecs-inspect-sel {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ecs-space-3);
  flex-wrap: wrap;
  margin-top: var(--ecs-space-2);
  padding: 8px var(--ecs-space-3);
  border: 1px solid var(--ecs-green-300);
  background: var(--ecs-green-100);
}
.ecs-inspect-sel[hidden] { display: none; }
.ecs-inspect-sel__note {
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) / 1.5 var(--ecs-font-body);
  color: var(--ecs-ink);
}
.ecs-inspect-sel__acts { display: flex; align-items: center; gap: var(--ecs-space-2); }

/* Motion off: the row tint and the reveal of the remove control both go, and
   the control stays where the tab order says it is — visible, not faded in. */
@media (prefers-reduced-motion: reduce) {
  .ecs-wlist__row,
  .ecs-inspect-toggle { transition: none; }
  .ecs-wlist__cell--act .ecs-row-remove { opacity: 1; transition: none; }
}

/* No hover to reveal it with: on a touch screen the remove control would simply
   never appear, so it is drawn from the start there. */
@media (hover: none) {
  .ecs-wlist__cell--act .ecs-row-remove { opacity: 1; }
}

/* A phone cannot hold six columns. The four fixed tracks come to 388px on their
   own, which leaves the filename — the column the whole screen is about — at
   zero and pushes the remove control past the clipped edge. Below 600px the two
   the client can live without go: what a file is called, when it was recorded,
   and the control that takes it out. The time-source chip is not lost with them
   — the “read from the file date” warning still jumps here with those files as
   the filter. */
@media (max-width: 600px) {
  .ecs-intake--files .ecs-wlist__head,
  .ecs-intake--files .ecs-wlist__row {
    grid-template-columns: 30px minmax(0, 1fr) 146px 34px;
  }
  .ecs-intake--files .ecs-wlist__head > :nth-child(4),
  .ecs-intake--files .ecs-wlist__head > :nth-child(5),
  .ecs-intake--files .ecs-wlist__row > :nth-child(4),
  .ecs-intake--files .ecs-wlist__row > :nth-child(5) { display: none; }
}

/* The project page's night lists (`browseNightList`): the same component over
   uploaded files. Sized off the card they sit in, not the viewport: on a
   laptop the Deployments card is a 480px middle column, and a viewport rule
   would hand it all seven tracks and leave the filename at zero. The checksum
   and the status go first — the name, the time and, in a draft, the control
   that removes the file are what the list is for — and in a narrower card the
   size follows them. */
.ecs-night-files { container-type: inline-size; }
.ecs-night-files .ecs-wlist { margin-top: 0; }
@container (max-width: 640px) {
  .ecs-wlist--night .ecs-wlist__head,
  .ecs-wlist--night .ecs-wlist__row { grid-template-columns: minmax(0, 1fr) 146px 84px; }
  .ecs-wlist--night.is-draft .ecs-wlist__head,
  .ecs-wlist--night.is-draft .ecs-wlist__row { grid-template-columns: 30px minmax(0, 1fr) 146px 84px 34px; }
  .ecs-wlist--night .ecs-wlist__cell.is-wide { display: none; }
}
@container (max-width: 400px) {
  .ecs-wlist--night .ecs-wlist__head,
  .ecs-wlist--night .ecs-wlist__row { grid-template-columns: minmax(0, 1fr) 132px; }
  .ecs-wlist--night.is-draft .ecs-wlist__head,
  .ecs-wlist--night.is-draft .ecs-wlist__row { grid-template-columns: 30px minmax(0, 1fr) 132px 34px; }
  .ecs-wlist--night .ecs-wlist__cell.is-right { display: none; }
}
@media (max-width: 600px) {
  .ecs-night-files { padding-left: 0; }
}

/* Sticky step footer: what the batch is, and the one thing to do next. Pinned to
   the bottom of the panel so it stays reachable with twenty sites on screen. */
.ecs-intake-bar {
  position: sticky;
  bottom: 0;
  z-index: 4; /* over the site grid's sticky row heads (z 2 and 3) as they scroll under it */
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--ecs-space-4);
  flex-wrap: wrap;
  margin: var(--ecs-space-4) calc(var(--ecs-space-5) * -1) calc(var(--ecs-space-5) * -1);
  padding: 14px var(--ecs-space-5);
  border-top: 1px solid var(--ecs-line);
  border-radius: 0 0 var(--ecs-radius-xl) var(--ecs-radius-xl);
  background: var(--ecs-surface);
}
.ecs-intake-bar__sum {
  display: flex;
  flex-direction: column;
  gap: 3px;
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-medium);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-text);
}
.ecs-intake-bar__note {
  font-family: var(--ecs-font-body);
  font-weight: var(--ecs-fw-regular);
  color: var(--ecs-text-muted);
}
.ecs-intake-bar__cta { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* Slim dropzone for the coordinate CSV — the folder one above it is the hero. */
.ecs-dropzone--slim {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  flex-wrap: wrap;
  padding: 18px;
}

.ecs-paste-row {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-top: var(--ecs-space-3);
}
.ecs-paste-row .ecs-input--inline { border-color: var(--ecs-line-strong); background: var(--ecs-surface); }

/* Progressive disclosure: the long explanation stays one click away instead of
   being a wall on first read (privacy tiers, CSV format). `ecs-privacy-help` is
   the same thing under its original name — the organiser still uses it. */
.ecs-disclose,
.ecs-privacy-help {
  margin-top: var(--ecs-space-4);
  padding-top: var(--ecs-space-3);
  border-top: 1px solid var(--ecs-line);
}
.ecs-disclose > summary,
.ecs-privacy-help > summary {
  cursor: pointer;
  font: var(--ecs-fw-semi) var(--ecs-fs-xs) / 1.4 var(--ecs-font-display);
  color: var(--ecs-forest-500);
}

/* ---- Leaflet map (vendored; loaded only on the coordinates step) --------- */
.ecs-map {
  height: 340px;
  border-radius: var(--ecs-radius-lg);
  border: 1px solid var(--ecs-line);
  background: var(--ecs-line-4);
  overflow: hidden;
  z-index: 0; /* keep Leaflet's stacking context under the sticky step bar */
}
/* Leaflet's own container, created by js/app.js inside the [data-preserve] box
   above. The box belongs to the render, this node belongs to the map: Leaflet
   writes classes and inline styles onto it, and a paint that synced them back to
   the markup would strip its own map apart. */
.ecs-map__canvas { height: 100%; }
/* A site is selected: the next map click places its pin. */
.ecs-map__canvas.is-placing { cursor: crosshair; }
.ecs-map:has(> .ecs-map__canvas.is-placing) { border-color: var(--ecs-green-500); }
.ecs-map .leaflet-container { font-family: var(--ecs-font-body); }

/* CSS-drawn pin, so no marker PNGs need vendoring alongside leaflet.css. */
.ecs-mappin {
  width: 26px;
  height: 26px;
  border-radius: 50% 50% 50% 0;
  transform: rotate(-45deg);
  border: 2px solid var(--ecs-on-dark);
  box-shadow: 0 4px 10px -3px rgba(var(--ecs-ink-rgb), 0.7);
  display: flex;
  align-items: center;
  justify-content: center;
}
.ecs-mappin > span {
  transform: rotate(45deg);
  font-family: var(--ecs-font-mono);
  font-weight: var(--ecs-fw-bold);
  font-size: var(--ecs-fs-2xs);
  color: var(--ecs-on-dark);
}
.ecs-mappin.is-selected { border-color: var(--ecs-forest-900); }

/* Heat sites (the results map): a glow in the activity's tint, wider and
   deeper the busier the site, and a round numbered dot over it. The tiles go
   grey under them, so the green reads as data and not as a park. */
.ecs-map--results .leaflet-tile-pane { filter: grayscale(1) contrast(0.9) brightness(1.06); }
.ecs-heat-glow {
  --heat: var(--ecs-green-500);
  width: 100%; height: 100%;
  border-radius: 50%;
  background: radial-gradient(
    closest-side,
    color-mix(in srgb, var(--heat) 78%, transparent) 0%,
    color-mix(in srgb, var(--heat) 46%, transparent) 38%,
    color-mix(in srgb, var(--heat) 14%, transparent) 72%,
    transparent 100%
  );
}
.ecs-heat-dot {
  --heat: var(--ecs-green-500);
  display: flex; align-items: center; justify-content: center;
  width: 24px; height: 24px;
  border-radius: 50%;
  border: 2px solid var(--ecs-on-dark);
  background: var(--heat);
  box-shadow: 0 2px 6px -2px rgba(var(--ecs-ink-rgb), 0.6);
}
.ecs-heat-dot > span { font: var(--ecs-fw-bold) var(--ecs-fs-2xs)/1 var(--ecs-font-mono); color: var(--ecs-on-dark); }
.ecs-heat-dot.is-selected { border-color: var(--ecs-forest-900); box-shadow: 0 0 0 2px var(--ecs-on-dark); }
.ecs-heat-glow.is-1, .ecs-heat-dot.is-1 { --heat: var(--ecs-green-350); }
.ecs-heat-glow.is-2, .ecs-heat-dot.is-2 { --heat: var(--ecs-green-400); }
.ecs-heat-glow.is-3, .ecs-heat-dot.is-3 { --heat: var(--ecs-green-500); }
.ecs-heat-glow.is-4, .ecs-heat-dot.is-4 { --heat: var(--ecs-forest-500); }
.ecs-heat-glow.is-5, .ecs-heat-dot.is-5 { --heat: var(--ecs-green-600); }
.ecs-heat-dot.is-1 > span, .ecs-heat-dot.is-2 > span { color: var(--ecs-ink); }

/* ===========================================================================
   BATCH ORGANISER (P0-CORRECT)
   The correction workspace for a submitted batch. Almost everything is already
   here — the site rows, the bulk bar, the map — so this adds only what the
   workspace needs on top: its toolbar, the drag-drop affordance, and the
   block that keeps a lost save's summary readable.
   =========================================================================== */

/* Site filter + search, on one line until the screen is too narrow for it. */
.ecs-org-toolbar {
  display: flex;
  align-items: center;
  gap: 10px;
  flex-wrap: wrap;
  margin-bottom: 10px;
}
.ecs-org-toolbar #ecs-org-search { flex: 1; min-width: 160px; max-width: 320px; }

/* A file selection dragged over a site row. Deliberately loud: dropping audio
   onto the wrong recorder is the mistake this whole feature exists to fix. */
.ecs-site-row[data-org-drop] { transition: background var(--ecs-dur-fast) var(--ecs-ease); }
.ecs-site-row.is-droptarget {
  background: var(--ecs-green-100);
  box-shadow: inset 0 0 0 2px var(--ecs-forest-500);
}
/* Rows are draggable, so say so on hover without hijacking the text cursor in
   the inline rename field. */
.ecs-file-row[data-org-row] { cursor: grab; }
.ecs-file-row[data-org-row] input,
.ecs-file-row[data-org-row] select { cursor: auto; }

/* What the person had pending when a save came back 409. Monospaced and boxed
   because it is a record to work from, not a sentence to skim. */
.ecs-org-pending {
  padding: 10px 12px;
  border-radius: var(--ecs-radius-sm);
  background: var(--ecs-line-4);
  font-family: var(--ecs-font-mono);
  font-size: var(--ecs-fs-2xs);
  line-height: 1.6;
  color: var(--ecs-text);
  overflow-wrap: anywhere;
}

/* The column-mapping preview can be much wider than the card. It scrolls
   itself rather than pushing the page sideways. */
.ecs-callout .ecs-table-scroll { max-height: 320px; }

@media (max-width: 720px) {
  .ecs-org-toolbar #ecs-org-search { max-width: none; }
}

/* ===========================================================================
   PAYG QUOTE PROGRESS + POST-SUBMISSION TIMELINE (P0-CORRECT)
   Two small pieces for the wait between confirming a folder and having a price,
   and for what replaces the Submit button once paying has already submitted.
   =========================================================================== */

/* An INDETERMINATE bar, on purpose. Detection has no percentage on the server,
   so there is none to show here; this says "still working" and nothing more.
   A determinate-looking bar creeping to 90% and stopping is the pattern this
   deliberately avoids. */
.ecs-quote-wait {
  position: relative;
  height: 4px;
  border-radius: 0;
  background: var(--ecs-line-3);
  overflow: hidden;
}
.ecs-quote-wait__bar {
  position: absolute;
  inset: 0 auto 0 0;
  width: 34%;
  border-radius: 0;
  background: var(--ecs-forest-500);
  animation: ecs-quote-slide 1.6s var(--ecs-ease) infinite;
}
@keyframes ecs-quote-slide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(330%); }
}
/* Motion off: a stalled sliver reads as a hung job. Fill the track instead and
   let the words carry the "still working". */
@media (prefers-reduced-motion: reduce) {
  .ecs-quote-wait__bar {
    animation: none !important;
    width: 100%;
    opacity: 0.35;
  }
}

/* Where the batch is, and where it goes next. */
.ecs-timeline {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}
.ecs-timeline__step {
  position: relative;
  display: grid;
  grid-template-columns: 22px 1fr;
  grid-template-areas: "dot label" "line note";
  column-gap: 10px;
  padding-bottom: 14px;
}
.ecs-timeline__step:last-child { padding-bottom: 0; }
/* The connector, drawn from the dot down to the next one. */
.ecs-timeline__step:not(:last-child)::before {
  content: "";
  position: absolute;
  left: 6px;
  top: 18px;
  bottom: 0;
  width: 2px;
  background: var(--ecs-line-3);
}
.ecs-timeline__dot {
  grid-area: dot;
  width: 14px;
  height: 14px;
  margin-top: 3px;
  border-radius: 50%;
  border: 2px solid var(--ecs-line-2, var(--ecs-line-3));
  background: var(--ecs-surface, #fff);
}
.ecs-timeline__label {
  grid-area: label;
  font-family: var(--ecs-font-display);
  font-weight: var(--ecs-fw-semi);
  font-size: var(--ecs-fs-sm);
  color: var(--ecs-text-muted);
}
.ecs-timeline__note {
  grid-area: note;
  font-family: var(--ecs-font-body);
  font-size: var(--ecs-fs-2xs);
  line-height: 1.6;
  color: var(--ecs-text-faint);
}
.ecs-timeline__step.is-done .ecs-timeline__dot { background: var(--ecs-forest-500); border-color: var(--ecs-forest-500); }
.ecs-timeline__step.is-done .ecs-timeline__label { color: var(--ecs-text); }
/* Where the batch is right now — the only ring, so it is findable at a glance. */
.ecs-timeline__step.is-now .ecs-timeline__dot {
  border-color: var(--ecs-forest-500);
  box-shadow: 0 0 0 3px var(--ecs-green-100);
}
.ecs-timeline__step.is-now .ecs-timeline__label { color: var(--ecs-text); }
.ecs-timeline__step.is-now .ecs-timeline__note { color: var(--ecs-text-muted); }

/* Delivered result cards: four compact files, with the bundle as the primary
   action. The layout wraps before filenames become unreadable. */
.ecs-results-card__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 12px;
  margin-bottom: 14px;
}
.ecs-results-card__title {
  font: 600 16px var(--ecs-font-display);
  color: var(--ecs-text);
}
.ecs-results-card__sub {
  margin-top: 3px;
  font: 400 12px/1.5 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-result-files {
  display: flex;
  flex-direction: column;
  gap: 8px;
}
.ecs-result-file {
  display: grid;
  grid-template-columns: 46px minmax(0, 1fr) auto;
  align-items: center;
  gap: 12px;
  padding: 10px 12px;
  border: 1px solid var(--ecs-line);
  border-radius: var(--ecs-radius-sm);
  background: var(--ecs-surface-2);
}
.ecs-result-file__type {
  font: 700 var(--ecs-fs-3xs) var(--ecs-font-mono);
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ecs-forest-500);
}
.ecs-result-file__body {
  display: flex;
  min-width: 0;
  flex-direction: column;
  gap: 2px;
}
.ecs-result-file__body strong {
  font: 600 13px var(--ecs-font-display);
  color: var(--ecs-text);
}
.ecs-result-file__body span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-mono);
  color: var(--ecs-text-muted);
}
@media (max-width: 600px) {
  .ecs-results-card__head { flex-direction: column; }
  .ecs-result-file { grid-template-columns: 38px minmax(0, 1fr); }
  .ecs-result-file > :last-child { grid-column: 1 / -1; justify-self: stretch; }
}

/* =============================================================================
   RESULTS EXPLORER  (P1-RESULTS)

   The client's own reviewed data: five tabs of figures, charts and tables over
   one delivery. It borrows the portal's existing vocabulary wholesale — squared
   corners, hairline separation, mono micro-labels, one green — because this is
   the screen where a client checks a number against the CSV they downloaded,
   and it should look like the same document.

   Numbers are set in tabular figures everywhere they are compared down a column.
   That is not a detail: a column of proportional digits does not line up, and a
   table nobody can scan is a table nobody trusts.
   ============================================================================= */

.ecs-results-back { margin-bottom: 14px; }

.ecs-results-meta {
  display: flex; align-items: center; gap: 12px; flex-wrap: wrap;
  margin-bottom: 8px;
}
.ecs-results-head {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap; margin: 0 0 18px;
}
.ecs-results-head__id { min-width: 0; }
.ecs-results-head__sub {
  font: 400 13px var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 6px 0 0;
}
.ecs-results-head__act { display: flex; align-items: center; gap: 10px; flex-shrink: 0; }
.ecs-results-expired {
  /* The one place expiry is stated on this page. It is a fact about the LINK,
     never about the results — which is why it is a quiet outline pill and not a
     warning. */
  white-space: normal;
  text-align: center;
}

/* ---- tabs -----------------------------------------------------------------
   A single hairline runs the width of the work column and the active tab sits
   on a 2px green rule through it. Colour is not the only carrier: the active tab
   also gains ink-weight text and carries aria-selected. */
.ecs-tabs {
  display: flex;
  gap: 2px;
  border-bottom: 1px solid var(--ecs-line);
  margin-bottom: 20px;
  overflow-x: auto;
  scrollbar-width: none;
}
.ecs-tabs::-webkit-scrollbar { display: none; }
.ecs-tab {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 2px solid transparent;
  margin-bottom: -1px;
  padding: 11px 15px;
  cursor: pointer;
  display: inline-flex; align-items: center; gap: 7px;
  white-space: nowrap;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
  transition: color var(--ecs-dur-fast) var(--ecs-ease),
              border-color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-tab:hover { color: var(--ecs-ink); }
.ecs-tab.is-active {
  color: var(--ecs-ink);
  border-bottom-color: var(--ecs-green-500);
}
.ecs-tab__count {
  font-size: var(--ecs-fs-2xs);
  font-variant-numeric: tabular-nums;
  letter-spacing: 0.04em;
  padding: 2px 5px;
  background: var(--ecs-surface-3);
  color: var(--ecs-text-muted);
}
.ecs-tab.is-active .ecs-tab__count {
  background: var(--ecs-green-200);
  color: var(--ecs-green-600);
}

/* ---- the two-column figure grid ------------------------------------------ */
.ecs-results-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr);
  gap: 18px;
  margin-bottom: 18px;
  align-items: start;
}

/* The Nights pair shares its three rows — headline, plot, note — so the two
   cards stay level when one headline wraps or one note runs longer. The
   species key lives above the pair, not inside a card, for the same reason:
   a legend that wraps must not push one plot down. */
.ecs-results-grid--aligned {
  align-items: stretch;
  grid-template-rows: auto auto auto;
  row-gap: 0;
}
.ecs-results-grid--aligned > .ecs-chart-card {
  display: grid;
  grid-row: span 3;
  grid-template-rows: subgrid;
  row-gap: 0;
  min-width: 0;
}

/* The Nights tab's species control: one row above the two charts it scopes,
   on the page rather than inside either card. */
.ecs-results-scope {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 12px 18px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.ecs-results-scope .ecs-select { min-width: 240px; }
.ecs-results-scope .ecs-seg { flex-shrink: 0; }

/* The species key for both charts. Full width, so seven names wrap once
   above the pair instead of inside the narrower card. */
.ecs-results-key { margin: -2px 0 14px; }
.ecs-results-key .ecs-chart-key { padding-top: 0; }
.ecs-results-grid--aligned .ecs-chart__note:empty { margin: 0; }

/* ---- a figure card -------------------------------------------------------- */
.ecs-chart-card { padding: 17px 19px 19px; margin-bottom: 18px; }
.ecs-results-grid > .ecs-chart-card { margin-bottom: 0; }
.ecs-chart__head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: 12px; flex-wrap: wrap;
  margin-bottom: 14px;
}
.ecs-chart__head .ecs-th { margin: 0; }
/* A chart card's right side: its headline in words, then the Download
   button. One group, so the head still reads title left, figure right, and
   wraps as one line under the title on a phone. */
.ecs-chart__aside {
  display: flex; align-items: center; gap: 6px;
  min-width: 0;
}

/* ---- Download (every chart card) -------------------------------------------
   A quiet icon tile at the end of the head, and its small menu. The negative
   margin keeps the 28px tile from growing a head whose line is 15px. */
.ecs-chartdl { position: relative; flex-shrink: 0; margin: -7px -7px -7px 0; }
.ecs-chartdl__btn {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0;
  background: transparent; border: 1px solid transparent;
  border-radius: var(--ecs-radius-md);
  color: var(--ecs-text-muted); cursor: pointer;
  transition: background var(--ecs-dur-fast) var(--ecs-ease), color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-chartdl__btn:hover,
.ecs-chartdl__btn.is-open {
  background: var(--ecs-surface-2);
  border-color: var(--ecs-line);
  color: var(--ecs-ink);
}
.ecs-chartdl__btn:focus-visible {
  outline: var(--ecs-focus-width) solid var(--ecs-focus-ring);
  outline-offset: 1px;
}
.ecs-chartmenu {
  position: absolute; top: calc(100% + 6px); right: 0; z-index: 60;
  width: 264px; background: var(--ecs-surface);
  border: 1px solid var(--ecs-line-2); border-radius: var(--ecs-radius-md);
  box-shadow: 0 22px 44px -24px rgba(var(--ecs-ink-rgb), 0.45);
  padding: 6px; animation: ecs-fade-up var(--ecs-dur-base) var(--ecs-ease-out);
}
.ecs-chartmenu__item {
  display: grid; grid-template-columns: 36px minmax(0, 1fr); align-items: baseline; gap: 8px;
  width: 100%; padding: 9px 10px;
  background: transparent; border: none; border-radius: 0;
  font: inherit; text-align: left; cursor: pointer;
  transition: background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-chartmenu__item:hover,
.ecs-chartmenu__item:focus-visible { background: var(--ecs-surface-2); }
.ecs-chartmenu__item:focus-visible { outline: var(--ecs-focus-width) solid var(--ecs-focus-ring); outline-offset: -2px; }
.ecs-chartmenu__fmt {
  font: var(--ecs-fw-semi) var(--ecs-fs-2xs) var(--ecs-font-mono);
  letter-spacing: 0.04em;
  color: var(--ecs-ink);
}
.ecs-chartmenu__what {
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-chartmenu__err {
  margin: 6px 0 0; padding: 8px 10px;
  background: var(--ecs-alert-bg);
  font: 400 var(--ecs-fs-2xs)/1.45 var(--ecs-font-body);
  color: var(--ecs-alert);
}
/* The card's own headline, in words. Every chart states its peak or its total
   here so the page still says something when the picture is skipped. */
.ecs-chart__meta {
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  /* This line states the card's headline in words — it is the fallback for the
     picture beside it, so it reads at full contrast, not as a caption. */
  color: var(--ecs-text-muted);
  white-space: nowrap;
}
.ecs-chart__note {
  font: 400 var(--ecs-fs-2xs)/1.55 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 12px 0 0;
}
.ecs-chart__link {
  appearance: none; background: none; border: none; padding: 0; cursor: pointer;
  display: inline-block; margin-top: 13px;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  letter-spacing: 0.04em;
  color: var(--ecs-green-600);
}
.ecs-chart__link:hover { text-decoration: underline; text-underline-offset: 3px; }

/* ---- the charts themselves ------------------------------------------------
   An SVG scales with its box, so a plot drawn for 700px and dropped into a
   1004px card renders its 10px axis labels at 14px. Each chart carries its own
   design width (--ecs-chart-w, set inline by charts.js) and is allowed at most a
   fifth of upscale on top of it; below its design width it scrolls rather than
   shrinking its type into illegibility. */
.ecs-chart-svg {
  display: block;
  width: 100%;
  height: auto;
  max-width: calc(var(--ecs-chart-w, 640px) * 1.2);
}
.ecs-chart-scroll { overflow-x: auto; }
/* The portal draws its column charts a second time at a phone's width; that
   drawing shows only on phones (portal.css). */
.ecs-chart-svg.is-phone { display: none; }
.ecs-chart-bar { fill: var(--ecs-green-500); }
/* The busiest night or hour, in the brand ink. One step of green was invisible
   at a twelve-pixel bar width — a highlight nobody can see is decoration, and
   the card head names the peak in words regardless. */
.ecs-chart-bar.is-max { fill: var(--ecs-forest-900); }
.ecs-chart-bar.is-zero { fill: var(--ecs-line-2); }
.ecs-chart-grid { stroke: var(--ecs-line-3); stroke-width: 1; }
.ecs-chart-axis-line { stroke: var(--ecs-line-2); stroke-width: 1; }
.ecs-chart-axis {
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  /* Not --ecs-text-faint: at 2.8:1 on white it fails even the 3:1 a graphical
     label has to clear, and an axis nobody can read is an axis that is not
     there. */
  fill: var(--ecs-text-muted);
}
.ecs-chart-empty {
  font: 400 13px var(--ecs-font-body);
  fill: var(--ecs-text-muted);
}

/* ---- ranked bars (species composition) ------------------------------------
   HTML rather than SVG: the label and the value are the message, and real text
   reflows, stays selectable and keeps its size whatever the card width. Only the
   bar is drawn, and it is hidden from the reading order because the number it
   represents is written out right beside it. */
.ecs-rank { list-style: none; margin: 0; padding: 0; }
.ecs-rank__row {
  display: grid;
  grid-template-columns: minmax(0, 8fr) minmax(64px, 7fr) auto;
  align-items: center;
  gap: 12px;
  padding: 7px 0;
  border-bottom: 1px solid var(--ecs-line-4);
}
.ecs-rank__row:last-child { border-bottom: none; }
.ecs-rank__label {
  min-width: 0;
  font: 400 var(--ecs-fs-xs) var(--ecs-font-body);
  color: var(--ecs-ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ecs-rank__label em {
  display: block;
  font: italic 400 var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ecs-rank__track { display: block; height: 11px; background: var(--ecs-line-4); }
.ecs-rank__bar { display: block; height: 100%; background: var(--ecs-green-500); }
.ecs-rank__value {
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-ink);
  white-space: nowrap;
}
/* The summed tail is a real figure, but it is not a species. */
.ecs-rank__row.is-other .ecs-rank__label,
.ecs-rank__row.is-other .ecs-rank__value { color: var(--ecs-text-muted); }
.ecs-rank__row.is-other .ecs-rank__bar { background: var(--ecs-text-faint); }

/* ---- stacked columns (activity per night, by species) ---------------------
   The parts take the series tokens in rank order; the white between them is a
   gap in the geometry (charts.js), not a stroke. */
.ecs-chart-seg.is-s1 { fill: var(--ecs-series-1); }
.ecs-chart-seg.is-s2 { fill: var(--ecs-series-2); }
.ecs-chart-seg.is-s3 { fill: var(--ecs-series-3); }
.ecs-chart-seg.is-s4 { fill: var(--ecs-series-4); }
.ecs-chart-seg.is-s5 { fill: var(--ecs-series-5); }
.ecs-chart-seg.is-s6 { fill: var(--ecs-series-6); }
.ecs-chart-seg.is-other { fill: var(--ecs-series-other); }
.ecs-chart-seg:hover { fill-opacity: 0.8; }

/* A card's own controls and key, on one row between its head and its plot:
   the key reads left to right in stack order, the switch keeps the right. */
.ecs-chart__tools {
  display: flex; align-items: flex-start; justify-content: flex-end;
  gap: 10px 18px;
  margin: -2px 0 14px;
}
.ecs-chart-key {
  flex: 1; min-width: 0;
  display: flex; flex-wrap: wrap; gap: 6px 14px;
  list-style: none; margin: 0; padding: 4px 0 0;
}
.ecs-chart-key__item {
  display: inline-flex; align-items: center; gap: 6px;
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text);
  white-space: nowrap;
}
.ecs-chart-key__sw { width: 10px; height: 10px; flex-shrink: 0; border-radius: 2px; }
.ecs-chart-key__sw.is-s1 { background: var(--ecs-series-1); }
.ecs-chart-key__sw.is-s2 { background: var(--ecs-series-2); }
.ecs-chart-key__sw.is-s3 { background: var(--ecs-series-3); }
.ecs-chart-key__sw.is-s4 { background: var(--ecs-series-4); }
.ecs-chart-key__sw.is-s5 { background: var(--ecs-series-5); }
.ecs-chart-key__sw.is-s6 { background: var(--ecs-series-6); }
.ecs-chart-key__sw.is-other { background: var(--ecs-series-other); }
.ecs-seg--sm .ecs-seg__btn { padding: 5px 10px; }

/* ---- night × hour grid (the Overview's activity by night) -----------------
   A table whose cells are tinted by count, in the intake grid's make: mono
   numerals, a sticky night column, a frame that scrolls rather than squeezing.
   Deeper than the intake grid's Mint, because here the tint IS the reading:
   five steps of the one green from Mint to Ink Green, darker as busier, the
   two deepest with white numerals (5.0:1 and 6.8:1). A zero cell is the
   faintest grey and holds no numeral, so the few busy hours stand out from the
   many quiet ones. The cells are parted by a 1px white border each, which is
   paint over the tint rather than a gap in the table, so the sticky head and
   column never show the rows scrolling through between them. */
.ecs-heat-scroll {
  overflow: auto;
  max-height: 352px;
  border: 1px solid var(--ecs-line);
  background: var(--ecs-surface);
}
.ecs-heat {
  width: 100%;
  border-collapse: separate;
  border-spacing: 0;
  font-variant-numeric: tabular-nums;
}
/* Read by a screen reader, never seen. Not .ecs-sr-only: that is an
   inline-block, and an inline-block caption is wrapped in an anonymous table
   row, which opens a blank line-height band under the hours. */
.ecs-heat__caption {
  height: 1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.ecs-heat__corner,
.ecs-heat__colh {
  position: sticky; top: 0; z-index: 2;
  height: 30px; padding: 0 2px;
  background: var(--ecs-surface);
  border-bottom: 1px solid var(--ecs-line);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  color: var(--ecs-text-muted);
  text-align: center;
  white-space: nowrap;
}
.ecs-heat__corner {
  left: 0; z-index: 3;
  padding: 0 10px;
  text-align: left;
  font-size: var(--ecs-fs-3xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-right: 1px solid var(--ecs-line);
}
.ecs-heat__colh.is-total {
  padding: 0 10px;
  text-align: right;
  font-size: var(--ecs-fs-3xs);
  text-transform: uppercase;
  letter-spacing: 0.08em;
  border-left: 1px solid var(--ecs-line);
}
.ecs-heat__rowh {
  position: sticky; left: 0; z-index: 1;
  padding: 0 10px;
  background: var(--ecs-surface);
  border-right: 1px solid var(--ecs-line);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  color: var(--ecs-ink);
  text-align: left;
  white-space: nowrap;
}
.ecs-heat__c {
  height: 26px; min-width: 30px; padding: 0 2px;
  border: 1px solid var(--ecs-surface);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  color: var(--ecs-ink);
  text-align: center;
  white-space: nowrap;
}
.ecs-heat__total {
  padding: 0 10px;
  border-left: 1px solid var(--ecs-line);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  color: var(--ecs-ink);
  text-align: right;
  white-space: nowrap;
}
.ecs-heat__c.is-0 { background: var(--ecs-line-4); }
.ecs-heat__c.is-1, .ecs-heat-key__sw.is-1 { background: var(--ecs-green-200); }
.ecs-heat__c.is-2, .ecs-heat-key__sw.is-2 { background: var(--ecs-green-350); }
.ecs-heat__c.is-3, .ecs-heat-key__sw.is-3 { background: var(--ecs-green-400); }
.ecs-heat__c.is-4, .ecs-heat-key__sw.is-4 { background: var(--ecs-forest-500); }
.ecs-heat__c.is-5, .ecs-heat-key__sw.is-5 { background: var(--ecs-green-600); }
.ecs-heat__c.is-4,
.ecs-heat__c.is-5 { color: var(--ecs-on-dark); }
.ecs-heat-scroll:focus-visible {
  outline: var(--ecs-focus-width) solid var(--ecs-focus-ring);
  outline-offset: 2px;
}

/* Under the grid: its key on the left, its note beside it. */
.ecs-heat-foot {
  display: flex; align-items: center; justify-content: space-between;
  gap: 8px 20px; flex-wrap: wrap;
  margin-top: 12px;
}
.ecs-heat-foot .ecs-chart__note { margin: 0; flex: 1 1 260px; }
.ecs-heat-key { display: inline-flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.ecs-heat-key__label {
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) var(--ecs-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
}
.ecs-heat-key__steps {
  display: inline-flex; flex-wrap: wrap; gap: 4px 10px;
  list-style: none; margin: 0; padding: 0;
}
.ecs-heat-key__steps li {
  display: inline-flex; align-items: center; gap: 5px;
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-text-muted);
}
.ecs-heat-key__sw { width: 12px; height: 12px; display: block; }

/* ---- data tables ---------------------------------------------------------- */
.ecs-table { width: 100%; border-collapse: collapse; }
.ecs-table th,
.ecs-table td { text-align: left; vertical-align: baseline; }
.ecs-table thead th {
  padding: 0 12px 9px;
  border-bottom: 1px solid var(--ecs-line);
  font: var(--ecs-th-font);
  letter-spacing: var(--ecs-th-tracking);
  text-transform: uppercase;
  color: var(--ecs-th-ink);
  white-space: nowrap;
}
.ecs-table tbody td,
.ecs-table tbody th {
  padding: 10px 12px;
  border-bottom: 1px solid var(--ecs-line-4);
  font: 400 var(--ecs-fs-sm) var(--ecs-font-body);
  color: var(--ecs-text);
}
.ecs-table tbody tr:last-child td,
.ecs-table tbody tr:last-child th { border-bottom: none; }
.ecs-table tbody tr { transition: background var(--ecs-dur-fast) var(--ecs-ease); }
.ecs-table tbody tr:hover { background: var(--ecs-surface-2); }
.ecs-table th:first-child,
.ecs-table td:first-child { padding-left: 0; }
.ecs-table th:last-child,
.ecs-table td:last-child { padding-right: 0; }
.ecs-table--dense tbody td,
.ecs-table--dense tbody th { padding: 8px 12px; }

.ecs-table tfoot th,
.ecs-table tfoot td {
  padding: 10px 12px;
  border-top: 1px solid var(--ecs-line-2);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
}

.ecs-td-name { font-weight: var(--ecs-fw-medium); color: var(--ecs-ink) !important; }
.ecs-td-num {
  text-align: right;
  font-family: var(--ecs-font-mono) !important;
  font-size: var(--ecs-fs-xs) !important;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.ecs-td-num.is-warn { color: var(--ecs-warn-ink); }
.ecs-td-num.is-total { color: var(--ecs-ink); font-weight: var(--ecs-fw-medium); }
/* A grid of mostly-zero cells should read as texture, not as data. */
.ecs-td-zero { color: var(--ecs-line-strong); }
.ecs-td-code {
  font: 400 var(--ecs-fs-xs) var(--ecs-font-mono) !important;
  color: var(--ecs-text-muted) !important;
  white-space: nowrap;
}
.ecs-td-latin em { font-style: italic; color: var(--ecs-text-muted); }
.ecs-td-range,
.ecs-td-site { white-space: nowrap; }
.ecs-td-act { text-align: right; white-space: nowrap; }
.ecs-td-chips { line-height: 1.9; }

.ecs-table-foot {
  font: 400 var(--ecs-fs-2xs)/1.6 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 14px 0 0;
  padding-top: 12px;
  border-top: 1px solid var(--ecs-line-4);
}
.ecs-table-more {
  display: flex; justify-content: center;
  margin-top: 16px; padding-top: 14px;
  border-top: 1px solid var(--ecs-line-4);
}

.ecs-rowlink {
  appearance: none; background: none; border: none; padding: 0; cursor: pointer;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  letter-spacing: 0.03em;
  color: var(--ecs-green-600);
  white-space: nowrap;
}
.ecs-rowlink:hover { text-decoration: underline; text-underline-offset: 3px; }
/* A whole column of them is a stripe, so inside a row's name cell the link
   wears the table's own ink and only turns green under the pointer. */
.ecs-td-name .ecs-rowlink {
  font: var(--ecs-fw-medium) var(--ecs-fs-xs) var(--ecs-font-body);
  letter-spacing: 0;
  color: var(--ecs-ink);
}
.ecs-td-name .ecs-rowlink:hover { color: var(--ecs-green-600); }

/* A hash or uuid the client quotes back to us: shown short, selectable, and
   carrying the whole value in its title. */
.ecs-mono-id {
  font-family: var(--ecs-font-mono);
  font-size: var(--ecs-fs-2xs);
  letter-spacing: 0.02em;
  cursor: help;
}

/* ---- taxon chips ----------------------------------------------------------- */
.ecs-chip-tag {
  display: inline-block;
  padding: 2px 7px;
  margin-right: 5px;
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-body);
  background: var(--ecs-green-200);
  color: var(--ecs-green-600);
  white-space: nowrap;
}
/* The reviewer wrote a doubt. It stays visible as a doubt — the "?" carries it
   for anyone who cannot see the colour. */
.ecs-chip-tag.is-uncertain { background: var(--ecs-warn-bg-2); color: var(--ecs-warn-ink); }
.ecs-chip-tag.is-quiet { background: var(--ecs-surface-3); color: var(--ecs-text-muted); }

/* ---- provenance (the dark card) -------------------------------------------- */
.ecs-prov { padding: 18px 20px 20px; }
.ecs-prov__eyebrow { color: var(--ecs-on-dark-4); margin-bottom: 9px; }
.ecs-prov__body {
  font: 400 var(--ecs-fs-xs)/1.6 var(--ecs-font-body);
  color: var(--ecs-on-dark-2);
  margin: 0 0 14px;
}
.ecs-prov__facts { margin-bottom: 16px; }
.ecs-prov__facts > div { border-bottom-color: var(--ecs-line-dark); }
.ecs-prov__facts dt { color: var(--ecs-on-dark-4); }
.ecs-prov__facts dd { color: var(--ecs-on-dark); }
.ecs-prov .ecs-mono-id { color: var(--ecs-green-400); }
.ecs-prov .ecs-pill--outline {
  background: transparent;
  border-color: var(--ecs-line-dark-2);
  color: var(--ecs-on-dark-2);
}

/* ---- sites ----------------------------------------------------------------- */
.ecs-sites-grid {
  display: grid;
  grid-template-columns: minmax(0, 1.4fr) minmax(0, 1fr);
  gap: 18px;
  align-items: start;
}
.ecs-map--results { height: 460px; }
.ecs-map-fallback {
  display: flex; align-items: center; justify-content: center;
  min-height: 200px; padding: 24px;
  text-align: center;
  font: 400 13px/1.6 var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
/* No max-height: a fixed one cut the last site off mid-row with nothing to say
   so. The list is as tall as the survey it describes, and the page scrolls. */
.ecs-sites-grid__list { margin-bottom: 0; }

.ecs-site-list { list-style: none; margin: 0; padding: 0; }
.ecs-site-item {
  display: grid;
  grid-template-columns: 24px minmax(0, 1fr) auto;
  gap: 12px;
  align-items: start;
  padding: 12px 0;
  border-bottom: 1px solid var(--ecs-line-4);
  cursor: pointer;
  transition: background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-site-item:last-child { border-bottom: none; }
.ecs-site-item:hover { background: var(--ecs-surface-2); }
/* The number is the tie to the pin on the map. Square, like everything else the
   brand draws, and never the only way to tell two sites apart. */
.ecs-site-item__pin {
  width: 22px; height: 22px;
  display: inline-flex; align-items: center; justify-content: center;
  background: var(--ecs-forest-500);
  color: var(--ecs-on-dark);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
}
.ecs-site-item__pin.is-empty {
  background: transparent;
  border: 1px dashed var(--ecs-line-strong);
  color: var(--ecs-text-faint);
}
.ecs-site-item__body { min-width: 0; display: block; }
.ecs-site-item__head {
  display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin-bottom: 3px;
}
.ecs-site-item__head strong {
  font: var(--ecs-fw-medium) var(--ecs-fs-xs) var(--ecs-font-display);
  color: var(--ecs-ink);
}
.ecs-site-item__stats {
  display: block;
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-site-item__note {
  display: block;
  margin-top: 3px;
  font: 400 var(--ecs-fs-2xs)/1.5 var(--ecs-font-body);
  /* What the delivered GeoJSON did with this position is the one fact a client
     cannot get anywhere else on this screen. It is not a caption. */
  color: var(--ecs-text-muted);
}
.ecs-site-item__act { align-self: center; }

.ecs-results-note {
  font: 400 var(--ecs-fs-2xs)/1.6 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 14px 4px 0;
  max-width: 78ch;
}

/* ---- states that are not the data ------------------------------------------ */
.ecs-results-empty {
  font: 400 var(--ecs-fs-sm) var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 0;
  padding: 10px 0;
}
.ecs-results-state {
  text-align: center;
  padding: 44px 28px 40px;
  max-width: 560px;
  margin: 8px auto 0;
}
.ecs-results-state__icon {
  display: inline-flex; align-items: center; justify-content: center;
  width: 46px; height: 46px;
  margin-bottom: 16px;
  background: var(--ecs-green-100);
  border: 1px solid var(--ecs-line-mint);
}
.ecs-results-state__title {
  font: var(--ecs-fw-bold) var(--ecs-fs-title) var(--ecs-font-display);
  letter-spacing: var(--ecs-tracking-tight);
  margin: 0 0 9px;
}
.ecs-results-state__body {
  font: 400 var(--ecs-fs-sm)/1.65 var(--ecs-font-body);
  color: var(--ecs-text-muted);
  margin: 0 auto 20px;
  max-width: 52ch;
}
.ecs-results-state__acts { display: flex; gap: 8px; justify-content: center; flex-wrap: wrap; }

/* The batch page's Results card grows a second action beside the expiry pill. */
.ecs-results-card__act { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }

/* ---- responsive ------------------------------------------------------------ */
@media (max-width: 1100px) {
  .ecs-results-grid,
  .ecs-sites-grid { grid-template-columns: minmax(0, 1fr); }
  .ecs-results-grid > .ecs-chart-card { margin-bottom: 0; }
  /* One column: the shared rows would pile the two cards on top of each
     other. They stack as ordinary cards again. */
  .ecs-results-grid--aligned { grid-template-rows: none; row-gap: 18px; }
  .ecs-results-grid--aligned > .ecs-chart-card {
    display: block;
    grid-row: auto;
  }
  .ecs-sites-grid__list { max-height: none; }
  .ecs-map--results { height: 360px; }
}
@media (max-width: 760px) {
  .ecs-results-head { align-items: flex-start; }
  /* Below this width the charts are scaled down far enough that their axis
     labels stop being legible, so the plot keeps its own minimum and scrolls
     inside the card rather than shrinking the type. The page itself never
     scrolls sideways. */
  .ecs-chart-scroll { overflow-x: auto; }
  .ecs-chart-scroll .ecs-chart-svg { min-width: var(--ecs-chart-w, 520px); }
  /* A meta line naming a species and its peak is wider than a phone's card;
     it wraps under the title rather than pushing the page sideways. */
  .ecs-chart__meta { white-space: normal; }
  .ecs-tab { padding: 10px 12px; }
  /* Five tabs do not fit 390px, so the strip scrolls. The fade is the only
     thing telling a reader there is more of it to the right. */
  .ecs-tabs {
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
    mask-image: linear-gradient(to right, #000 calc(100% - 22px), transparent);
  }
  .ecs-site-item { grid-template-columns: 24px minmax(0, 1fr); }
  .ecs-site-item__act { grid-column: 2; justify-self: start; margin-top: 4px; }
}

/* ---- the detections filter bar (P1-RESULTS) --------------------------------
   Four controls and an export, on one line at desktop width. It sits INSIDE the
   card it filters rather than above it, because the thing it narrows is the
   table directly underneath and the two should move together. */
.ecs-filter {
  display: flex; align-items: flex-end; justify-content: space-between;
  gap: 16px; flex-wrap: wrap;
  padding-bottom: 15px; margin-bottom: 4px;
  border-bottom: 1px solid var(--ecs-line);
}
.ecs-filter__fields {
  display: flex; align-items: flex-end; gap: 12px; flex-wrap: wrap;
  flex: 1; min-width: 0;
}
.ecs-filter__field { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
.ecs-filter__field--search { flex: 1; min-width: 180px; }
.ecs-filter__label {
  font: var(--ecs-fw-medium) var(--ecs-fs-3xs) var(--ecs-font-mono);
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ecs-text-muted);
}
.ecs-filter__search { position: relative; display: block; }
.ecs-filter__search > svg {
  position: absolute; left: 10px; top: 50%; transform: translateY(-50%);
  pointer-events: none;
}
.ecs-filter__search .ecs-input { padding-left: 32px; }
.ecs-filter__acts { display: flex; align-items: center; gap: 8px; flex-shrink: 0; }
.ecs-input--sm {
  padding: 7px 10px;
  font-size: var(--ecs-fs-2xs);
  margin: 0;
}

/* A segmented control, not a fourth select: four mutually exclusive options,
   one of them always on — which is exactly the state a select hides. */
.ecs-seg {
  display: inline-flex;
  border: 1px solid var(--ecs-line-field);
  border-radius: var(--ecs-radius-ctl);
  overflow: hidden;
}
.ecs-seg__btn {
  appearance: none; background: var(--ecs-surface); border: none; cursor: pointer;
  padding: 7px 11px;
  border-right: 1px solid var(--ecs-line-3);
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
  transition: background var(--ecs-dur-fast) var(--ecs-ease),
              color var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-seg__btn:last-child { border-right: none; }
.ecs-seg__btn:hover { background: var(--ecs-surface-2); color: var(--ecs-ink); }
.ecs-seg__btn.is-on {
  background: var(--ecs-green-200);
  color: var(--ecs-green-600);
  font-weight: var(--ecs-fw-medium);
}

/* The count line when the card head is only a count — no title, because the tab
   above it already named the table. */
.ecs-chart__head--count { justify-content: flex-end; margin: 13px 0 12px; }

/* ---- map pin tones (P1-RESULTS) -------------------------------------------
   Used by the dashboard, where one map carries every batch. Never the only
   carrier: the pin is numbered, its tooltip names the batch, and the list
   beside the map repeats both. */
.ecs-mappin.is-tone-ok { background: var(--ecs-forest-500); }
.ecs-mappin.is-tone-warn { background: var(--ecs-warn); }
.ecs-mappin.is-tone-neutral { background: var(--ecs-text-faint); }
.ecs-mappin.is-tone-alert { background: var(--ecs-alert); }

@media (max-width: 760px) {
  .ecs-filter { align-items: stretch; }
  .ecs-filter__fields { gap: 10px; }
  .ecs-filter__field { flex: 1 1 140px; }
  .ecs-filter__acts { justify-content: flex-end; }
}

/* ---- the home's dashboard band (P1-RESULTS) --------------------------------
   Reuses the explorer's card, grid and chart vocabulary wholesale. The home and
   the explorer are the same client reading the same data at two zoom levels, and
   they should look like it. */
.ecs-map--home { height: 320px; }

/* The legend under the map. A tint on a 26px pin is not a label, so the colours
   are spelled out directly beneath the thing that uses them. */
.ecs-legend {
  display: flex; align-items: center; gap: 16px; flex-wrap: wrap;
  margin-top: 12px; padding-top: 11px;
  border-top: 1px solid var(--ecs-line-4);
}
.ecs-legend__item {
  display: inline-flex; align-items: center; gap: 7px;
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
}
.ecs-statusdot {
  width: 9px; height: 9px; flex-shrink: 0;
  border-radius: var(--ecs-radius-pill);
  background: var(--ecs-dot-idle);
}
.ecs-statusdot.is-ok { background: var(--ecs-forest-500); }
.ecs-statusdot.is-warn { background: var(--ecs-warn); }
.ecs-statusdot.is-neutral { background: var(--ecs-text-faint); }
.ecs-statusdot.is-alert { background: var(--ecs-alert); }

/* Recent activity: a dot, a sentence, a date. Deliberately not a timeline —
   these are unrelated events across unrelated batches, and a connecting rail
   would imply a sequence that is not there. */
.ecs-recent { list-style: none; margin: 0; padding: 0; }
.ecs-recent__row {
  appearance: none;
  background: none;
  border: none;
  border-bottom: 1px solid var(--ecs-line-4);
  width: 100%;
  text-align: left;
  display: grid;
  grid-template-columns: 9px minmax(0, 1fr) auto;
  align-items: center;
  gap: 10px;
  padding: 10px 0;
  cursor: pointer;
  transition: background var(--ecs-dur-fast) var(--ecs-ease);
}
.ecs-recent li:last-child .ecs-recent__row { border-bottom: none; }
.ecs-recent__row:hover { background: var(--ecs-surface-2); }
.ecs-recent__text {
  min-width: 0;
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-body);
  color: var(--ecs-text-muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.ecs-recent__text strong {
  font: var(--ecs-fw-medium) var(--ecs-fs-2xs) var(--ecs-font-mono);
  color: var(--ecs-green-600);
}
.ecs-recent__when {
  font: 400 var(--ecs-fs-2xs) var(--ecs-font-mono);
  font-variant-numeric: tabular-nums;
  color: var(--ecs-text-faint);
  white-space: nowrap;
}

/* The batch table's "Results ready →" is now a control, not a caption. It keeps
   the chip's type so the column still reads as one thing. */
.ecs-chip--btn {
  appearance: none; background: none; border: none; padding: 0; cursor: pointer;
  text-align: left;
}
.ecs-chip--btn:hover { text-decoration: underline; text-underline-offset: 3px; }
