/* Shared visual system for the data-entry web app -- same design tokens as
   the dashboard (build_dashboard.py / dashboard_shell.html) so the whole
   tool, including the live dashboard embedded at /dashboard, reads as one
   cohesive, professional product. Palette: blue -> teal, modern/government-
   appropriate, built around a Thai-first web font. */
:root {
  color-scheme: light;
  --surface-1: #ffffff; --surface-2: #f7f9fb; --page: #f3f6f8;
  /* --text-muted darkened from #98a2b3 (2.58:1 on white -- fails WCAG AA, which
     needs 4.5:1 for body text). #667085 measures ~5.3:1, so hint text, table
     captions and empty states are now readable for users with reduced vision,
     which matters here because many farmers using this are older. */
  --text-primary: #101828; --text-secondary: #475467; --text-muted: #667085;
  --grid: #e4e9ee; --baseline: #cfd7de; --border: rgba(16,24,40,0.12);

  /* Minimum comfortable hit area. 44px is the long-standing accessibility
     floor (WCAG 2.5.5 / Apple HIG) -- the previous 38px buttons and 19px text
     links were genuinely hard to tap accurately on a phone. */
  --tap-min: 44px;

  /* brand ramp: blue -> teal */
  --primary: #2563eb; --primary-dark: #1a46b3; --primary-light: #e8f0fe;
  --accent: #0f9e8e; --accent-light: #e3f7f3;
  /* The same teal, deep enough to carry white text. White on --accent measures
     3.33:1 and fails WCAG AA (4.5:1); this measures 5.5:1. Used ONLY as the far
     stop of gradients that have white text over them -- --accent itself is
     unchanged, because as a border, an icon or a pale tint it has nothing on top
     of it and is not a contrast problem. */
  --accent-deep: #0b6f64;
  /* Same story for the green: white on --good (#0f9e5e) measures 3.46:1. Used
     where white text sits ON the colour -- the tri-toggle's checked "ไม่พบ" state.
     --critical already passes at 4.53:1 and is left alone. */
  --good-deep: #0a7a48;

  /* dashboard series colors (kept as the same variable NAMES the dashboard
     templates already reference, values refreshed to match the new palette) */
  --s1: #2563eb; /* blue - domestic */
  --s2: #f0883e; /* orange - export */
  --s3: #0f9e8e; /* teal - import */
  --s4: #e0a336; /* amber - packing */

  --good: #0f9e5e; --warning: #e0a336; --serious: #ea7a4d; --critical: #dc3545;

  --radius-sm: 7px; --radius-md: 11px; --radius-lg: 16px;
  /* Two-layer shadows: a tight contact shadow for the edge plus a wide soft one
     for the lift. A single blur reads as either sharp or muddy, never as depth. */
  --shadow-sm: 0 1px 2px rgba(16,24,40,0.05), 0 1px 1px rgba(16,24,40,0.03);
  --shadow-md: 0 1px 2px rgba(16,24,40,0.05), 0 6px 16px -4px rgba(16,24,40,0.10);
  --shadow-lg: 0 2px 4px rgba(16,24,40,0.05), 0 18px 40px -8px rgba(16,24,40,0.16);
  --shadow-menu: 0 4px 8px -2px rgba(16,24,40,0.08), 0 20px 44px -10px rgba(16,24,40,0.22);

  /* The primary action reads as one gradient across the whole product: the brand
     blue falling into the brand teal, the same ramp the logo mark uses. */
  --grad-primary: linear-gradient(135deg, #2f6ff0 0%, #2563eb 45%, #1d63c9 100%);
  /* The dashboard button. Deliberately NOT --grad-primary: see .nav-link-cta. */
  --grad-cta: linear-gradient(135deg, #1e4fa3 0%, #173d80 100%);
  --grad-cta-hover: linear-gradient(135deg, #1a4691 0%, #12326b 100%);
  --grad-primary-hover: linear-gradient(135deg, #2a63d8 0%, #1f57cf 45%, #1a4fae 100%);
  --ring-primary: 0 0 0 3px rgba(37,99,235,.18);
  --ease-out: cubic-bezier(.16,1,.3,1);
}
* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0; padding: 0; color: var(--text-primary);
  font-family: "Noto Sans Thai", "Sarabun", system-ui, -apple-system, "Segoe UI", sans-serif;
  -webkit-font-smoothing: antialiased;
  font-size: 14.5px; line-height: 1.55;
  background:
    radial-gradient(1100px 560px at -8% -12%, rgba(37,99,235,0.07), transparent 60%),
    radial-gradient(900px 520px at 108% -6%, rgba(15,158,142,0.08), transparent 55%),
    var(--page);
  background-attachment: fixed, fixed, fixed;
  min-height: 100vh;
}

@keyframes fadeInUp { from { opacity: 0; transform: translateY(10px); } to { opacity: 1; transform: none; } }
@keyframes fadeIn { from { opacity: 0; } to { opacity: 1; } }
.panel, .auth-panel, .stat-tile, .empty-state { animation: fadeInUp .4s cubic-bezier(.16,1,.3,1) both; }
.sheet-card { animation: fadeInUp .4s cubic-bezier(.16,1,.3,1) both; }
.card-grid .sheet-card:nth-child(2) { animation-delay: .03s; }
.card-grid .sheet-card:nth-child(3) { animation-delay: .06s; }
.card-grid .sheet-card:nth-child(4) { animation-delay: .09s; }
.card-grid .sheet-card:nth-child(n+5) { animation-delay: .12s; }
@media (prefers-reduced-motion: reduce) {
  /* Honour the OS "reduce motion" setting everywhere, not just on the few
     classes listed before -- motion can trigger nausea/vertigo for some users,
     and the swimming-fish decorations added below are exactly the kind of
     continuous movement that setting exists for. */
  *, *::before, *::after {
    animation-duration: .001ms !important; animation-iteration-count: 1 !important;
    transition-duration: .001ms !important; scroll-behavior: auto !important;
  }
}
a { color: var(--primary); text-decoration: none; }
a:hover { text-decoration: underline; }

/* ---------------- focus visibility (keyboard users) ----------------
   Previously only three components had a focus style, so anyone navigating by
   Tab -- keyboard-only users, screen-reader users, and anyone whose mouse just
   died -- could not tell where they were on the page. :focus-visible shows the
   ring for keyboard focus but NOT on mouse click, so it costs pointer users
   nothing. */
:focus-visible {
  outline: 3px solid var(--primary);
  outline-offset: 2px;
  border-radius: 4px;
}
.btn-primary:focus-visible { outline-color: var(--primary-dark); outline-offset: 3px; }

/* Skip link: lets keyboard/screen-reader users jump straight past the nav to
   the content instead of tabbing through every menu item on every page. */
.skip-link {
  position: absolute; left: -9999px; top: 0; z-index: 100;
  background: var(--primary); color: #fff; padding: 12px 20px;
  border-radius: 0 0 var(--radius-sm) 0; font-weight: 700; font-size: 14.5px;
}
.skip-link:focus { left: 0; text-decoration: none; }

/* Screen-reader-only text: conveys context to assistive tech without changing
   the visual design (e.g. naming an icon-only button). */

/* ---------------- top navigation ---------------- */
.topbar {
  display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 24px;
  /* Frosted rather than solid, so content scrolling underneath stays faintly
     visible and the bar reads as a layer above the page instead of a lid on it.
     The rgba background is the fallback wherever backdrop-filter is unsupported,
     and it is opaque enough on its own -- the blur is an enhancement, never
     load-bearing for legibility. */
  background: rgba(255,255,255,0.82);
  -webkit-backdrop-filter: saturate(180%) blur(14px);
  backdrop-filter: saturate(180%) blur(14px);
  border-bottom: 1px solid var(--border); position: sticky; top: 0; z-index: 30;
  box-shadow: var(--shadow-sm);
  /* Belt-and-braces against the nav overflow fixed below: even if a future
     menu item is too wide to wrap, it must never make the whole page
     scroll sideways. */
  max-width: 100%;
}
.topbar .brand {
  font-weight: 800; font-size: 14.5px; color: var(--text-primary);
  display: flex; align-items: center; gap: 9px; white-space: nowrap;
}
.topbar .brand .logo-mark {
  width: 30px; height: 30px; border-radius: 9px; flex-shrink: 0;
  background: linear-gradient(135deg, var(--primary), var(--accent-deep));
  display: flex; align-items: center; justify-content: center;
  color: #fff; font-size: 15px; box-shadow: var(--shadow-sm);
}
/* flex-wrap is load-bearing: an admin sees ~10 nav items, and in Thai those
   labels are long enough to need roughly 1400px. Without wrapping, every
   viewport between the 860px hamburger breakpoint and ~1700px got a
   horizontal scrollbar on the ENTIRE PAGE -- so on a normal laptop the whole
   form could be dragged sideways. Wrapping lets the bar grow one row taller
   instead, which costs nothing and hides nothing. */
.topbar nav { display: flex; flex-wrap: wrap; gap: 4px 2px; font-size: 13.5px; align-items: center; justify-content: flex-end; }
.topbar nav a.nav-link {
  color: var(--text-secondary); text-decoration: none; padding: 7px 11px; border-radius: 8px;
  display: inline-flex; align-items: center; gap: 6px; white-space: nowrap; transition: background .12s, color .12s;
}
.topbar nav a.nav-link:hover { background: var(--surface-2); color: var(--text-primary); text-decoration: none; }
.topbar nav a.nav-link.active { background: var(--primary-light); color: var(--primary-dark); font-weight: 700; }
.topbar nav a.nav-link .badge-count {
  background: var(--critical); color: #fff; font-size:11.5px; font-weight: 700; border-radius: 999px;
  padding: 1px 6px; line-height: 1.5;
}
.nav-toggle {
  display: none; border: 1px solid var(--border); background: var(--surface-1); border-radius: 8px;
  /* 44px: this is the ONLY way to reach the menu on a phone, so it must not be
     the hardest thing on the page to hit. */
  width: var(--tap-min); height: var(--tap-min); align-items: center; justify-content: center;
  cursor: pointer; flex-shrink: 0;
}
.nav-toggle span, .nav-toggle span::before, .nav-toggle span::after {
  content: ''; display: block; width: 18px; height: 2px; background: var(--text-primary); position: relative; transition: .15s;
}
.nav-toggle span::before { position: absolute; top: -6px; }
.nav-toggle span::after { position: absolute; top: 6px; }

.container { max-width: 1120px; margin: 0 auto; padding: 30px 22px 70px; }
.page-title { font-size: 23px; font-weight: 800; margin: 0 0 6px; color: var(--text-primary); letter-spacing: -0.01em; }
/* A heading for a section WITHIN a page. It used to be .page-title with an inline
   font-size, so a page could have four "page titles" and the document outline said
   nothing about its structure. */
.section-title { font-size: 16px; font-weight: 800; margin: 0 0 6px; color: var(--text-primary); letter-spacing: -0.01em; }
.page-sub { font-size: 13.5px; color: var(--text-secondary); margin: 0 0 26px; line-height: 1.65; max-width: 900px; }

.flash { padding: 11px 16px; border-radius: var(--radius-sm); font-size: 13.5px; margin-bottom: 14px; border: 1px solid transparent; }
.flash.error { background: #fef2f2; border-color: #fecaca; color: #b42318; }
.flash.success { background: #ecfdf5; border-color: #a7f3d0; color: #067647; }
/* flash(..., 'warning') is used for "saved, but read this" outcomes -- an attachment
   that could not be matched to a farm yet, a list that had to be truncated. base.html
   renders it as class="flash warning"; without this rule it came out unstyled. */
.flash.warning { background: #fffbeb; border-color: #fde68a; color: #92400e; }

.group-block { margin-bottom: 30px; }
.group-title { font-size: 12.5px; font-weight: 800; color: var(--text-secondary); text-transform: uppercase; letter-spacing: .04em; margin-bottom: 12px; }
.card-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 14px; }
.sheet-card {
  background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 16px 18px;
  display: flex; flex-direction: column; gap: 9px; box-shadow: var(--shadow-sm); transition: box-shadow .15s, transform .15s;
}
.sheet-card:hover { box-shadow: var(--shadow-md); transform: translateY(-3px); border-color: rgba(37,99,235,0.25); }
.sheet-card .icon-badge {
  width: 38px; height: 38px; border-radius: 10px; display: flex; align-items: center; justify-content: center;
  font-size: 18px; background: linear-gradient(135deg, var(--primary-light), var(--accent-light)); flex-shrink: 0;
}
.sheet-card .name { font-size: 13.5px; font-weight: 700; color: var(--text-primary); }
.sheet-card .count { font-size: 12px; color: var(--text-muted); }
.sheet-card .actions { display: flex; gap: 8px; margin-top: 4px; }

/* ---------------- home welcome banner ---------------- */
.welcome-banner {
  display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap;
  /* The teal stop is #0b6f64, not --accent (#0f9e8e). White text measured 3.33:1
     against the brand accent -- under the 4.5 AA floor -- and this banner carries
     the greeting, the date and the record count, all in white. The stop is
     deepened just enough to clear AA across the whole sweep while staying the same
     hue as the accent, so the banner still reads as blue falling into teal. */
  background: linear-gradient(120deg, var(--primary) 0%, var(--primary-dark) 60%, #0b6f64 130%);
  color: #fff; border-radius: var(--radius-lg); padding: 22px 26px; margin-bottom: 24px;
  box-shadow: var(--shadow-md); position: relative; overflow: hidden;
}
.welcome-banner::before {
  content: ''; position: absolute; width: 220px; height: 220px; border-radius: 50%;
  background: rgba(255,255,255,0.08); top: -100px; right: -40px;
}
.welcome-banner .hi { font-size: 18px; font-weight: 800; position: relative; z-index: 1; }
/* Was rgba(255,255,255,0.85), which measured 2.82:1 over the gradient. Fully
   opaque white is the same design intent -- secondary by size, not by fading the
   text until it stops being readable. */
.welcome-banner .today { font-size: 12.5px; color: #fff; margin-top: 3px; position: relative; z-index: 1; }
.welcome-banner .stat-pill {
  /* A translucent WHITE pill lightened the gradient under white text down to
     2.73:1. Tinting toward black instead darkens the backdrop, so the same glassy
     look now carries its label at AA. */
  background: rgba(8,32,50,0.30); border: 1px solid rgba(255,255,255,0.30); border-radius: 999px;
  padding: 8px 16px; font-size: 12.5px; font-weight: 700; white-space: nowrap; position: relative; z-index: 1;
  backdrop-filter: blur(2px);
}
.welcome-banner .banner-wave { position: absolute; left: 0; right: 0; bottom: 0; width: 100%; height: 34px; z-index: 0; pointer-events: none; }

.btn-primary, .btn-secondary, .btn-danger {
  border-radius: var(--radius-sm); padding: 11px 19px; font-size: 14.5px; font-weight: 700; cursor: pointer;
  font-family: inherit; border: 1px solid transparent; text-decoration: none; display: inline-flex;
  align-items: center; justify-content: center; gap: 7px;
  /* Raised from 38px: see --tap-min. Buttons are the single most-tapped thing
     in the app, so they get the full comfortable target. */
  min-height: var(--tap-min);
  transition: background .12s, border-color .12s, box-shadow .12s, transform .05s;
}
.btn-primary { background: var(--primary); color: #fff; box-shadow: var(--shadow-sm); }
.btn-primary:hover { background: var(--primary-dark); text-decoration: none; box-shadow: var(--shadow-md); transform: translateY(-1px); }
.btn-primary:active { transform: translateY(1px); box-shadow: var(--shadow-sm); }
.btn-secondary { background: var(--surface-1); color: var(--text-secondary); border-color: var(--border); }
.btn-secondary:hover { background: var(--surface-2); color: var(--text-primary); text-decoration: none; transform: translateY(-1px); box-shadow: var(--shadow-sm); }
.btn-danger { background: #fff; color: var(--critical); border-color: #fecaca; }
.btn-danger:hover { background: #fef2f2; text-decoration: none; }
button.btn-primary, button.btn-secondary, button.btn-danger { border: 1px solid transparent; }
button.btn-secondary { border-color: var(--border); }

.panel { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 20px 22px; box-shadow: var(--shadow-sm); }
.form-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 15px 20px; }
@media (max-width: 700px) { .form-grid { grid-template-columns: 1fr; } }
/* The browser's own [hidden] rule is `display:none` at USER-AGENT priority, so
   any author rule that sets `display` -- .form-field{display:flex} right below,
   for one -- silently beats it and the element stays on screen with hidden=true.
   That has now bitten twice: the admin add-forms all showed at once, and both
   farm panels on the entry form rendered together while reporting hidden. Fixed
   once, here, rather than per template. */
[hidden] { display: none !important; }

.form-field { display: flex; flex-direction: column; gap: 6px; }
.form-field label { font-size: 12.5px; font-weight: 700; color: var(--text-secondary); }
/* Matches by EXCLUSION rather than listing each type: the old selector spelled
   out text/number/password/date/file, so the moment a field used a more
   appropriate type (tel, email, search...) it silently lost all styling and
   collapsed to a ~21px unstyled box. Checkboxes and radios are excluded
   because they must keep their native compact size -- their tap target comes
   from the surrounding label row instead. */
/* :where() around the exclusions so they add NO specificity. Written as three :not()
   selectors this rule scored higher than every later rule that tries to adjust a field,
   which is why the dropdown chevron rule below could set a background but not a
   padding -- the combobox reserved no room for its own arrow while the <select> did. */
.form-field input:where(:not([type=checkbox]):not([type=radio]):not([type=hidden])),
.form-field select, .form-field textarea {
  /* 16px is deliberate, not a style choice: iOS Safari ZOOMS THE WHOLE PAGE in
     whenever you focus an input whose font-size is under 16px. At the previous
     13.5px, every tap on every field made the page jump and the user had to
     pinch back out -- on the public farmer form that was 22 fields' worth of
     that. It also simply reads better for older users. */
  padding: 11px 13px; border: 1px solid var(--border); border-radius: var(--radius-sm);
  font-size: 16px; font-family: inherit; min-height: var(--tap-min);
  /* background-COLOR, not the `background` shorthand: the shorthand also resets
     background-image, and this rule outranks the dropdown-chevron rule below -- so the
     combobox lost its chevron while the <select> kept one, which is the difference this
     was all meant to remove. */
  background-color: var(--surface-1); color: var(--text-primary);
  transition: border-color .12s, box-shadow .12s;
  width: 100%;
}
.form-field textarea { min-height: 88px; line-height: 1.6; }
/* Native date/time inputs carry an internal picker whose intrinsic height makes them render a
   couple of pixels taller than a text input or a <select> with the SAME padding -- so a date box
   sitting next to a "— เลือก —" dropdown in the same grid row ended up visibly taller, its bottom
   edge out of line. Pin the single-line date/time family to the exact height the others compute to
   (padding 11+11 + border 2 + 16px line = 48), so every box on a row is the same size. */
.form-field input[type="date"],
.form-field input[type="datetime-local"],
.form-field input[type="time"],
.form-field input[type="month"] {
  height: 48px;
}
/* ---- one appearance for every dropdown ----
   A native <select> draws its own arrow, at its own size and position, so it did not match
   the in-page combobox sitting next to it on the same row. Both draw the same chevron now,
   from the same SVG, and both reserve the same room for it.

   Why one is still a <select>: its list is a CLOSED set. The laboratory chosen on the
   specimen row decides which disease panel appears, matched by exact name, so free text
   there would mean a laboratory no panel matches. The combobox is for lists where typing
   something not on the list is legitimate -- a species, a province, a specimen type. */
.form-field select,
.combo [data-combo-input] {
  appearance: none; -webkit-appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8' fill='none' stroke='%23667085' stroke-width='1.8' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M1 1.5 6 6.5 11 1.5'/%3E%3C/svg%3E");
  background-repeat: no-repeat;
  background-position: right 13px center;
  padding-right: 36px;
}
/* The combo's toggle is a hit area over that chevron, not a second glyph. */
.combo-toggle { width: 34px; height: 100%; top: 0; right: 0; transform: none; font-size: 0; }
.combo-toggle:focus-visible { outline: 2px solid var(--primary); outline-offset: -2px; }
.form-field input:focus, .form-field select:focus, .form-field textarea:focus {
  outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light);
}
/* Required-field marker that doesn't depend on the reader noticing a "*" */
.form-field label .req { color: var(--critical); font-weight: 700; margin-left: 2px; }
.form-field .field-hint { font-size: 12.5px; color: var(--text-muted); margin-top: 2px; line-height: 1.5; }
/* Live feedback under the อำเภอ box from geo_cascade.js: "this district is not in
   จังหวัด X". A caution, not an error -- the value is still accepted, so it must
   not look like a validation failure that blocked the save. */
.form-field .geo-note { font-size: 12.5px; color: var(--warning); margin-top: 3px; line-height: 1.5; font-weight: 600; }
.form-section-title { font-size: 13px; font-weight: 800; margin: 24px 0 12px; color: var(--text-primary); border-top: 1px solid var(--grid); padding-top: 18px; }
.disease-negpos { display: grid; grid-template-columns: 1fr 70px 70px; align-items: center; gap: 8px; font-size: 12.5px; }
.disease-negpos input { width: 100%; padding: 6px 8px; border: 1px solid var(--border); border-radius: 6px; font-size: 12.5px; }

.disease-category-block { border: 1px solid var(--grid); border-radius: var(--radius-md); padding: 14px 16px; margin-bottom: 12px; background: var(--surface-2); }
.disease-category-title { font-size: 12.5px; font-weight: 800; color: var(--primary-dark); text-transform: uppercase; letter-spacing: .02em; margin-bottom: 10px; }
.disease-flag-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px,1fr)); gap: 8px 16px; }
.disease-flag-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; font-size: 13px; padding: 3px 0; }
.disease-flag-item .dname { flex: 1; min-width: 0; overflow-wrap: anywhere; }
.tri-toggle { display: inline-flex; border: 1px solid var(--border); border-radius: 8px; overflow: hidden; flex-shrink: 0; }
.tri-toggle input { position: absolute; opacity: 0; width: 0; height: 0; }
.tri-toggle label {
  padding: 4px 9px; font-size:11.5px; font-weight: 700; cursor: pointer; color: var(--text-muted);
  background: #fff; border-right: 1px solid var(--border); user-select: none; line-height: 1.6;
}
.tri-toggle label:last-child { border-right: none; }
.tri-toggle input:focus-visible + label { outline: 2px solid var(--primary); outline-offset: -2px; }
.tri-toggle input:checked + label { color: #fff; }
.tri-toggle input[value=""]:checked + label { background: var(--text-muted); }   /* 4.97:1 with white */
.tri-toggle input[value="0"]:checked + label { background: var(--good-deep); }   /* white on --good was 3.46:1 */
.tri-toggle input[value="1"]:checked + label { background: var(--critical); }
.tri-toggle label.opt-neg:hover, .tri-toggle label.opt-pos:hover { background: var(--page); }

/* One laboratory's extra fields (ชื่อเชื้อที่แยกได้, ระดับความรุนแรง, "อื่นๆ" lists):
   a titled strip below the specimen table, shown only when that laboratory is
   actually being used -- see [data-lab-extra] in entry_form.html. */
.disease-category-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; flex-wrap: wrap; margin-bottom: 4px; }
.disease-category-head .disease-category-title { margin-bottom: 0; }
/* Search box that narrows a laboratory's disease rows by name -- shown only when
   that laboratory has more than eight of them (see entry_form.html). */

/* Repeatable "อื่นๆ (ระบุเพิ่มเติม)" list widget (kind='other_list' fields,
   see entry_form_other_list.html + entry_form.html's other-list-widget
   script): one row per off-checklist finding, each with its own name box +
   .tri-toggle result (reuses the exact same tri-toggle markup/CSS as the
   fixed disease checklist above, for a consistent look) + remove button. */
.other-list-row { display: flex; align-items: center; gap: 10px; margin-bottom: 8px; }
.other-list-row:last-child { margin-bottom: 0; }
.other-list-name {
  flex: 1; min-width: 0; padding: 8px 12px; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: #fff;
}
.other-list-name:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.other-list-remove {
  flex-shrink: 0; width: 28px; height: 28px; display: inline-flex; align-items: center; justify-content: center;
  border: 1px solid var(--border); border-radius: 50%; background: var(--surface-1); color: var(--text-muted);
  font-size: 16px; line-height: 1; cursor: pointer; padding: 0;
}
.other-list-remove:hover { background: var(--critical-light, #fef2f2); color: var(--critical); border-color: var(--critical); }
.other-list-add-btn { margin-top: 2px; font-size: 12.5px; padding: 6px 14px; }

/* The per-disease result row (entry_form.html's laboratory section): name, the
   three-state .tri-toggle answer, and -- only when the answer is พบเชื้อ and the
   submission has more than one pool -- which pools it was found in. One row is one
   complete answer, which is why the pool x disease grid this replaced is gone. */
/* Shown on every row of a specimen that was split across laboratories: one
   collection, several submissions. Without it, three rows naming the same ชนิดตัวอย่าง
   read as three separate collections. */
/* One specimen = one card. A quiet surface with a soft shadow and a thin accent down the
   left edge, so a stack of them reads as distinct records rather than one grey slab. */
.specimen-card {
  position: relative;
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 14px 16px 14px 18px; margin-bottom: 14px;
  background: var(--surface-1); box-shadow: var(--shadow-sm);
  transition: box-shadow .15s, border-color .15s;
}
.specimen-card::before {
  content: ''; position: absolute; left: 0; top: 0; bottom: 0; width: 3px;
  border-radius: var(--radius-md) 0 0 var(--radius-md);
  background: var(--primary); opacity: .55;
}
.specimen-card:hover { box-shadow: var(--shadow-md); }
/* The field row: aligned to the TOP so every label shares one line even when a neighbour
   (การแบ่งพูล) is taller. */
.specimen-head { display: flex; gap: 12px 14px; flex-wrap: wrap; align-items: flex-start; }
/* Row-level actions, in a footer bar rather than adrift among the inputs. A hairline rule
   sets it apart from the fields above; the destructive one sits furthest right. */
.specimen-actions {
  display: flex; justify-content: flex-end; gap: 8px; flex-wrap: wrap;
  margin-top: 14px; padding-top: 12px; border-top: 1px solid var(--border);
}
.specimen-actions [data-role="add-lab"] { margin-right: auto; }

.same-collection-note {
  font-size: 11.5px; color: var(--primary-dark); background: var(--primary-light);
  border-radius: 999px; padding: 3px 10px; margin-bottom: 10px; display: inline-block;
}
/* Says the disease picker is narrowed to the specimen's host, and carries the way out. Quiet
   -- it only appears when the narrowing actually hid something. */
.dis-host-note {
  display: flex; align-items: center; gap: 10px; flex-wrap: wrap;
  margin: 2px 0 8px; font-size: 12px; color: var(--text-secondary);
}
/* A specimen taken out of another one -- ขี้กุ้ง from the same shrimp. Different colour
   from .same-collection-note on purpose: that one means "one collection, several
   laboratories", this one means "one collection, several kinds of specimen", and reading
   them as the same thing is what produces a double count. */
.derived-note { background: var(--accent-light, #fff7ed); color: var(--accent-dark, #9a3412); }
.lab-result-wrap { margin-top: 12px; padding-top: 10px; border-top: 1px dashed var(--border); }
/* The bulk answer for a laboratory panel, and what is still outstanding. The panel is
   14 diseases at the median and 29 at the worst, and 94% of reports answer every one of
   them the same way. */
/* The empty state before a laboratory is chosen (and the "no diseases listed" case). A
   soft dashed placeholder reads as "waiting for input" rather than as a bare sentence that
   looks like something failed to load. */
.lab-result-hint {
  font-size: 12.5px; color: var(--text-muted); line-height: 1.6; text-align: center;
  padding: 16px 14px; margin-bottom: 8px;
  border: 1px dashed var(--border); border-radius: var(--radius-sm);
  background: var(--surface-2);
}
/* One chip per pool. The number is always readable; the ANSWER is the colour and the
   border, and the accessible name spells it out because colour alone must not carry it.
   Clicking cycles ไม่พบเชื้อ -> พบเชื้อ -> ไม่ได้ตรวจ: three states in the width of one, which
   is what lets 62 pools fit on a row at all. */
/* ---- pick the disease, then answer its pools ---- */
.dis-pick { display: flex; gap: 8px; align-items: flex-end; flex-wrap: wrap; margin-bottom: 10px; }
.dis-pick-field { min-width: 240px; flex: 1; }
.dis-other-field { min-width: 200px; flex: 1; }
/* Folded blocks tile into columns; an OPEN one spans the row so its pool strip has the full
   width. One column of twenty folded lines came to 680px -- taller than the 430px grid this
   replaced, which would have made the picker cost space rather than save it. */
.dis-blocks { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 4px; align-items: start; }
.dis-block.is-open, .dis-blocks > .lab-result-hint { grid-column: 1 / -1; }
/* Folded, this is the ENTIRE record of one disease, so the line carries the whole answer.
   Twenty of them stacked is the panel -- taller than one line and the picker would cost more
   room than the grid it replaced. */
.dis-block { border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-1); }
.dis-block.is-open { background: var(--surface-2); }
/* AFTER .is-open on purpose: equal specificity, so source order decides, and an open positive
   was losing its red tint to the open state. Which one is positive is the more important fact. */
.dis-block.is-pos { border-color: var(--critical); background: var(--critical-light, #fef2f2); }
/* The whole row opens the block: the name and the badge are what a mouse aims at, and a
   14px triangle is not. The caret stays a real button so a keyboard has something to reach. */
.dis-head { display: flex; align-items: center; gap: 8px; padding: 5px 8px; cursor: pointer; }
.dis-head:hover { background: rgba(16, 24, 40, 0.03); border-radius: var(--radius-sm); }
/* DRAWN, not typed. As the character ▾ it came out as a dash in the department's font, which
   reads as a control that is broken rather than one that is closed. */
.dis-toggle {
  border: none; background: none; cursor: pointer; padding: 0;
  width: 16px; height: 16px; flex-shrink: 0; position: relative;
  color: var(--text-muted);
}
.dis-toggle::before {
  content: ''; position: absolute; top: 50%; left: 50%;
  width: 0; height: 0;
  border-left: 5px solid currentColor;
  border-top: 4px solid transparent;
  border-bottom: 4px solid transparent;
  transform: translate(-50%, -50%);
  transition: transform 120ms ease;
}
.dis-block.is-open .dis-toggle::before { transform: translate(-50%, -50%) rotate(90deg); }
.dis-toggle:focus-visible, .dis-remove:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.dis-name { font-size: 12.5px; font-weight: 700; min-width: 120px; flex: 1; }
.dis-remove {
  border: none; background: none; cursor: pointer; padding: 0 4px;
  font-size: 15px; line-height: 1; color: var(--text-muted);
}
.dis-remove:hover { color: var(--critical); }
/* Its own surface, inset under the header, so an open block reads as one thing with a
   detail area rather than as two loose rows. */
.dis-pools {
  margin: 0 8px 8px 26px; padding: 8px 10px;
  display: flex; flex-direction: column; gap: 8px;
  background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-sm);
}
.dis-block.is-pos .dis-pools { border-color: rgba(220, 53, 69, 0.28); }
/* Colour never carries the answer alone: each swatch has its word beside it. The swatches are
   NOT numbered -- numbered samples sat directly above the pool numbers, so the legend's "1"
   and pool 1 were the same object twice. */
.pool-legend { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; font-size: 11px; color: var(--text-muted); }
.pool-legend-lead { font-weight: 600; }
.pool-key-item { display: inline-flex; align-items: center; gap: 4px; }
.pool-key {
  width: 12px; height: 12px; border-radius: 3px; flex-shrink: 0;
  border: 1px solid var(--border); background: var(--surface-1);
}
.pool-key.is-neg { border-color: var(--good-deep, #0a7a48); background: #e7f6ee; }
.pool-key.is-pos { border-color: var(--critical); background: var(--critical); }
.pool-key.is-nt { border-style: dashed; border-color: var(--text-muted); background: var(--surface-2); }
.dis-bulk { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.dis-bulk-lead { font-size: 11px; color: var(--text-muted); }
/* Per-disease pool division: a compact "ตัว/พูล" for THIS disease, above its pool strip. */
.dis-pool-layout { display: flex; align-items: center; gap: 8px; flex-wrap: wrap; margin-bottom: 10px; }
.dis-pool-lab { font-size: 12px; color: var(--text-secondary); font-weight: 600; }
.dis-pool-per { width: 68px; height: 32px; padding: 0 8px; border: 1px solid var(--border);
  border-radius: 7px; font: inherit; font-size: 13px; background: var(--surface); color: var(--text-primary);
  font-variant-numeric: tabular-nums; }
.dis-pool-per:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.dis-pool-per-unit { font-size: 12px; color: var(--text-muted); }
.dis-pool-count { font-size: 12px; color: var(--text-secondary); font-weight: 600; font-variant-numeric: tabular-nums; }
/* An action on THIS block, so it looks like a button. As underlined links these read as
   navigation, which is the one thing they are not. */
.btn-chip {
  border: 1px solid var(--border); background: var(--surface-1); cursor: pointer;
  padding: 3px 9px; border-radius: 999px; font-size: 11.5px; color: var(--text-secondary);
}
.btn-chip:hover { border-color: var(--primary); color: var(--primary); }
.btn-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }

/* Per-disease parasite intensity (+/++/+++), shown on a positive parasite finding. One chip is
   chosen at a time; the chosen grade is filled so it reads at a glance. */
.dis-severity {
  display: flex; align-items: center; gap: 6px; flex-wrap: wrap;
  padding: 5px 8px 7px; border-top: 1px dashed var(--grid);
}
.dis-sev-lead { font-size: 11px; color: var(--text-muted); }
.dis-sev-chip {
  border: 1px solid var(--border); background: var(--surface-1); cursor: pointer;
  min-width: 34px; padding: 3px 9px; border-radius: 999px; font-size: 12px; font-weight: 700;
  color: var(--text-secondary);
}
.dis-sev-chip:hover { border-color: var(--critical); color: var(--critical); }
.dis-sev-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.dis-sev-chip.is-on { background: var(--critical); border-color: var(--critical); color: #fff; }
.dis-sev-clear {
  border: 0; background: none; cursor: pointer; font-size: 11px; color: var(--text-muted);
  text-decoration: underline; padding: 3px 4px;
}
.dis-sev-clear:hover { color: var(--critical); }

/* AMR (เชื้อดื้อยา) antibiogram. One isolate per card; one chip per antibiotic that cycles
   ไม่ได้ทดสอบ -> ไว(S) -> กึ่ง(I) -> ดื้อ(R). The RESULT is the colour + the letter, and the
   accessible name spells it out because colour alone must not carry it. */
.amr-isolate {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 12px 14px; margin-bottom: 10px; background: var(--surface-1);
}
.amr-head { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-bottom: 10px; }
.amr-no { font-size: 12.5px; font-weight: 700; color: var(--text-secondary); white-space: nowrap; }
.amr-bac { flex: 1 1 220px; min-width: 180px; padding: 6px 10px; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-1); }
.amr-bac:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
.amr-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.amr-chip {
  display: inline-flex; align-items: center; gap: 6px; cursor: pointer;
  padding: 4px 8px 4px 10px; border-radius: var(--radius-sm); font-size: 12px;
  border: 1px solid var(--border); background: var(--surface-2); color: var(--text-secondary);
}
.amr-chip .amr-res { font-weight: 700; min-width: 14px; text-align: center; }
.amr-chip:hover { filter: brightness(0.97); }
.amr-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.amr-chip.is-none { border-style: dashed; }
.amr-chip.is-s { border-color: var(--good-deep, #0a7a48); background: #e7f6ee; color: var(--good-deep, #0a7a48); }
.amr-chip.is-i { border-color: var(--warning, #e0a336); background: #fdf4e3; color: #9a6b12; }
.amr-chip.is-r { border-color: var(--critical); background: var(--critical); color: #fff; }

/* Histopathology by organ: one row per organ = name | finding note (| remove, for a custom row).
   No severity grade -- the department dropped it; the note carries whatever detail matters. */
.histo-row {
  display: grid; grid-template-columns: minmax(120px, 1.4fr) minmax(160px, 2.6fr) auto;
  align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--grid);
}
.histo-row:last-child { border-bottom: 0; }
.histo-organ { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.histo-organ-in, .histo-finding {
  width: 100%; padding: 6px 10px; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-1);
}
.histo-organ-in:focus, .histo-finding:focus { outline: 2px solid var(--primary); outline-offset: -1px; }
@media (max-width: 640px) {
  .histo-row { grid-template-columns: 1fr auto; grid-auto-rows: auto; }
  .histo-finding { grid-column: 1 / -1; }
}

/* The one-line intro under an optional lab section's title (AMR, histopath, water, and the
   per-activity switch). Was an inline style repeated on each; shared so they stay in step. */
.lab-hint { margin: 0 0 10px; font-size: 12.5px; }
/* Repeated one-line hint and count on the admin registry pages (species, specimen types, symptoms)
   and the report detail. Factored out of inline styles so the pages stay in step. */
.registry-hint { margin: 6px 0 0; font-size: 12px; }
.registry-count { font-size: 12px; color: var(--text-muted); }

/* Water quality by parameter: one row per parameter = name | value | unit (| remove, custom row).
   The unit rides beside the value so a reading reads as "DO  5.2  mg/L". */
.water-row {
  /* The unit column is a FIXED width, not `auto`: an auto unit column is as wide as its text, so
     "mg/L" ate more of the row than "°C" and an empty unit (pH) ate none -- which made the value
     box a different width on every line. Fixed here, every value box is the same size. */
  display: grid; grid-template-columns: minmax(130px, 1.6fr) minmax(120px, 1fr) 3rem auto;
  align-items: center; gap: 8px; padding: 6px 0; border-bottom: 1px solid var(--grid);
}
.water-unit { text-align: left; }
.water-row:last-child { border-bottom: 0; }
.water-param { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.water-unit { font-size: 12px; color: var(--text-muted); white-space: nowrap; }
.water-param-in, .water-unit-in, .water-value {
  width: 100%; padding: 6px 10px; font-size: 13px;
  border: 1px solid var(--border); border-radius: var(--radius-sm); background: var(--surface-1);
}
.water-unit-in { max-width: 90px; }
.water-param-in:focus, .water-unit-in:focus, .water-value:focus {
  outline: 2px solid var(--primary); outline-offset: -1px;
}
@media (max-width: 640px) {
  .water-row { grid-template-columns: 1fr auto; grid-auto-rows: auto; }
  .water-value { grid-column: 1 / -1; }
}
.pool-chips { display: flex; flex-wrap: wrap; gap: 4px; }
.pool-chip {
  min-width: 28px; height: 26px; padding: 0 5px; cursor: pointer;
  font-size: 12px; font-variant-numeric: tabular-nums;
  border-radius: var(--radius-sm); border: 1px solid var(--border);
  background: var(--surface-1); color: var(--text-secondary);
}
/* Filled soft green, so ไม่พบเชื้อ is unmistakably a RESULT and cannot be read as the dashed
   grey ไม่ได้ตรวจ beside it. Kept a tint rather than the solid fill พบเชื้อ wears, so a positive
   still shouts loudest across a strip of negatives. */
.pool-chip.is-neg { border-color: var(--good-deep, #0a7a48); background: #e7f6ee; color: var(--good-deep, #0a7a48); }
.pool-chip.is-pos { border-color: var(--critical); background: var(--critical); color: #fff; font-weight: 700; }
/* Dashed, filled grey, and dimmed: this pool was never tested for this disease. It must not
   read as a result AND must not read as faint -- ไม่ได้ตรวจ mistaken for ไม่พบเชื้อ is the exact
   confusion the SOP’s blank cell exists to prevent, so it is unmistakable rather than quiet.
   Same shape as the others, so the strip still reads as a strip. */
.pool-chip.is-nt {
  border-style: dashed; border-color: var(--text-muted);
  background: var(--surface-2); color: var(--text-muted);
}
.pool-chip:hover { filter: brightness(0.96); }
.pool-chip:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
/* On touch, a chip is a control where a mis-tap CHANGES A RESULT, so it gets the projects
   own --tap-min rather than the 26px that fits a mouse. Kept small on desktop: 62 of them
   on one row is the case that decided the size. */
@media (max-width: 700px) {
  .pool-chip { min-width: var(--tap-min); height: var(--tap-min); font-size: 13px; }
}
/* Only the bad state has words. A green "✓ 3 พูล" beside a box holding 3, labelled พบเชื้อ
   and followed by "จาก 14 พูล", was the same fact said a fourth time. */
.dpool-note { font-size: 11px; }
.dpool-note.is-bad { color: var(--critical); }
.lab-test-line { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; margin-top: 10px; font-size: 12.5px; color: var(--text-secondary); }
.lab-test-total { font-size: 12.5px; color: var(--text-secondary); margin: 2px 0 10px; }
.form-actions { margin-top: 24px; display: flex; gap: 10px; }

.data-table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
.data-table th, .data-table td { text-align: left; padding: 10px 12px; border-bottom: 1px solid var(--grid); }
.data-table th { color: var(--text-secondary); font-weight: 700; font-size: 11.5px; background: var(--surface-2); text-transform: uppercase; letter-spacing: .02em; }
.data-table th:first-child { border-top-left-radius: var(--radius-sm); }
.data-table th:last-child { border-top-right-radius: var(--radius-sm); }
.data-table tr:hover td { background: var(--primary-light); }
.data-table tr:last-child td { border-bottom: none; }

/* status/role badges (used in admin_users, admin_registrations, farmer_reports_list) */
.badge { display: inline-flex; align-items: center; gap: 4px; padding: 3px 10px; border-radius: 999px; font-size: 11.5px; font-weight: 700; white-space: nowrap; }
.badge-good { background: #ecfdf5; color: #067647; }
/* The "ปิดใช้งาน / disabled" badge, and the small table-row action button. Shared by the
   admin registry pages (species / specimen types / antibiotics) so the same look is not
   pasted inline into each. */
.badge-off { background: #f1f3f6; color: var(--text-secondary); }
.btn-xs { font-size: 11.5px; padding: 5px 10px; min-height: 0; }
.badge-warn { background: #fffaeb; color: #b54708; }
.badge-bad { background: #fef2f2; color: #b42318; }
.badge-neutral { background: var(--surface-2); color: var(--text-secondary); }
.badge-brand { background: var(--primary-light); color: var(--primary-dark); }

.empty-state { text-align: center; padding: 46px 20px; color: var(--text-muted); }
.empty-state .title { font-size: 14px; font-weight: 700; color: var(--text-secondary); margin-bottom: 4px; }
.empty-state .sub { font-size: 12.5px; }

.dropzone-mini {
  border: 2px dashed var(--border); border-radius: var(--radius-lg); background: var(--surface-1); padding: 32px; text-align: center;
}
.stat-row { display: grid; grid-template-columns: repeat(4,1fr); gap: 12px; margin-bottom: 22px; }
@media (max-width: 700px) { .stat-row { grid-template-columns: repeat(2,1fr); } }
.stat-tile { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 14px 16px; box-shadow: var(--shadow-sm); }
.stat-tile .v { font-size: 21px; font-weight: 800; color: var(--text-primary); }
.stat-tile .l { font-size: 11.5px; color: var(--text-muted); }

/* ---------------- auth / public pages (login, register, farmer report) ---------------- */
.auth-wrap { max-width: 420px; margin: 48px auto 0; }
.auth-hero { text-align: center; margin-bottom: 22px; }
.logo-mark-lg {
  /* Sized for the drawn fish mark (see login.html) rather than an emoji
     glyph, so it needs real padding and no font-size dependency. */
  width: 78px; height: 78px; border-radius: 22px;
  background: linear-gradient(135deg, var(--primary), var(--accent-deep));
  display: flex; align-items: center; justify-content: center; color: #fff;
  box-shadow: 0 10px 24px rgba(37,99,235,.32);
  flex-shrink: 0; position: relative; z-index: 2;
}
.auth-hero .logo-mark-lg { margin: 0 auto 14px; }
.auth-hero .title { font-size: 19px; font-weight: 800; color: var(--text-primary); margin-bottom: 6px; }
.auth-hero .sub { font-size: 13px; color: var(--text-secondary); line-height: 1.6; }
.auth-panel { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-lg); padding: 26px 26px; box-shadow: var(--shadow-md); }
.auth-footnote { text-align: center; font-size: 12.5px; margin-top: 16px; color: var(--text-secondary); }

/* ---------------- landing (login) two-column hero ---------------- */
.landing-shell {
  max-width: 980px; margin: 40px auto 0; display: grid; grid-template-columns: 1.05fr 1fr;
  border-radius: var(--radius-lg); overflow: hidden; box-shadow: var(--shadow-lg); background: var(--surface-1);
  animation: fadeInUp .45s cubic-bezier(.16,1,.3,1) both;
}
.landing-brand {
  background: linear-gradient(155deg, var(--primary) 0%, var(--primary-dark) 55%, var(--accent-deep) 130%);
  color: #fff; padding: 46px 40px; position: relative; overflow: hidden;
  display: flex; flex-direction: column; justify-content: center;
}
.landing-brand::before, .landing-brand::after {
  content: ''; position: absolute; border-radius: 50%; background: rgba(255,255,255,0.08);
}
.landing-brand::before { width: 280px; height: 280px; top: -110px; right: -80px; }
.landing-brand::after { width: 190px; height: 190px; bottom: -70px; left: -50px; background: rgba(255,255,255,0.06); }
.landing-brand .logo-mark-lg { background: rgba(255,255,255,0.16); box-shadow: none; margin: 0 0 16px; }
/* An <h1> now (it was a <div>, so the page had no heading element at all), which is why
   the margin is reset here -- the size and colour were already set. */
.landing-brand .title { color: #fff; font-size: 21px; text-align: left; margin: 0; }
.landing-brand .sub { color: #fff; text-align: left; }   /* was 0.88 alpha -- 2.92:1 */
.landing-form { padding: 46px 42px; display: flex; flex-direction: column; justify-content: center; }

/* Contact block in the brand panel (replaces the old feature-bullet list) --
   same spacing/opacity rhythm as .landing-features so it reads as part of
   the same hero, just address/phone/email lines instead of feature bullets. */
.landing-contact { margin: 22px 0 0; display: flex; flex-direction: column; gap: 9px; position: relative; z-index: 1; }
.landing-contact div { display: flex; align-items: flex-start; gap: 9px; font-size: 12.5px; color: #fff; line-height: 1.55; }   /* was 0.85 alpha -- 2.82:1 */
.landing-contact .ico { flex-shrink: 0; }

/* Farmer/officer access tabs -- two equally-weighted entry points on the
   landing page's right (form) column, switched with a small segmented
   control rather than one being the "main" form and the other a footnote. */
.access-tabs { display: flex; gap: 6px; padding: 4px; background: var(--surface-2); border-radius: 999px; margin-bottom: 22px; }
.access-tab {
  flex: 1; border: none; background: transparent; border-radius: 999px; padding: 9px 14px; font-size: 13px;
  font-weight: 700; color: var(--text-secondary); cursor: pointer; font-family: inherit; transition: background .12s, color .12s;
}
.access-tab:hover { color: var(--text-primary); }
.access-tab.active { background: var(--surface-1); color: var(--primary-dark); box-shadow: var(--shadow-sm); }
.access-panel-head { margin-bottom: 18px; }
.access-panel-title { font-size: 16px; font-weight: 800; color: var(--text-primary); margin-bottom: 6px; }
.access-panel-sub { font-size: 12.5px; color: var(--text-secondary); line-height: 1.6; }
.access-actions { display: flex; flex-direction: column; gap: 10px; }
@media (max-width: 860px) {
  .landing-shell { grid-template-columns: 1fr; max-width: 440px; margin-top: 24px; }
  .landing-brand { padding: 34px 28px 24px; text-align: center; align-items: center; }
  .landing-brand .logo-mark-lg { margin: 0 auto 14px; }
  .landing-brand .title, .landing-brand .sub { text-align: center; }
  .landing-form { padding: 32px 26px; }
}

/* Back to 860px. Raising this to 1024 pushed a 946px desktop window into the
   hamburger drawer, and a stacked full-width drawer is the wrong shape for a
   window that has room for a real menu bar. The width the bar needs is bought
   back below instead, by dropping the username label. */
@media (max-width: 860px) {
  .topbar { flex-wrap: wrap; }
  .nav-toggle { display: flex; }
  .topbar nav {
    display: none; width: 100%; flex-direction: column; align-items: stretch; gap: 2px;
    order: 3; margin-top: 10px; padding-top: 10px; border-top: 1px solid var(--grid);
  }
  .topbar nav.open { display: flex; }
  .topbar nav a.nav-link { width: 100%; }
}

/* ================================================================
   NAV MENUS
   ================================================================
   Twelve sibling links wrapped the bar onto three rows and gave a reader no
   hierarchy: daily work and once-a-year configuration looked identical. They
   are grouped into <details> menus now -- chosen over a JS dropdown because a
   <details> opens, closes and takes keyboard focus on its own, so the nav keeps
   working with scripting disabled.
   ================================================================ */
.topbar nav { gap: 4px; }

.nav-menu { position: relative; }
.nav-menu > summary {
  list-style: none; cursor: pointer; user-select: none;
  display: inline-flex; align-items: center; gap: 6px;
  padding: 7px 11px; border-radius: 8px; white-space: nowrap;
  font-size: 13.5px; color: var(--text-secondary);
  transition: background .12s, color .12s;
}
.nav-menu > summary::-webkit-details-marker,
.nav-menu > summary::marker { display: none; content: ''; }
.nav-menu > summary:hover { background: var(--surface-2); color: var(--text-primary); }
.nav-menu > summary:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 1px; box-shadow: var(--ring-primary);
}
/* A menu whose page you are on has to look selected while shut, or the only
   way to know where you are is to open every menu. */
.nav-menu.active > summary { background: var(--primary-light); color: var(--primary-dark); font-weight: 700; }

.nav-caret {
  width: 0; height: 0; flex-shrink: 0;
  border-left: 3.5px solid transparent; border-right: 3.5px solid transparent;
  border-top: 4.5px solid currentColor; opacity: .55;
  transition: transform .16s var(--ease-out);
}
.nav-menu[open] > summary .nav-caret { transform: rotate(180deg); }
.nav-menu[open] > summary { background: var(--surface-2); color: var(--text-primary); }

.nav-menu-panel {
  position: absolute; top: calc(100% + 6px); left: 0; z-index: 40;
  min-width: 208px; padding: 6px;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-md); box-shadow: var(--shadow-menu);
  display: flex; flex-direction: column; gap: 1px;
  animation: navMenuIn .16s var(--ease-out) both;
}
.nav-menu-panel-right { left: auto; right: 0; }
@keyframes navMenuIn { from { opacity: 0; transform: translateY(-5px); } to { opacity: 1; transform: none; } }

.nav-item {
  display: flex; align-items: center; gap: 8px; justify-content: space-between;
  padding: 8px 10px; border-radius: 7px; font-size: 13.5px;
  color: var(--text-secondary); text-decoration: none; white-space: nowrap;
  transition: background .1s, color .1s;
}
.nav-item:hover { background: var(--surface-2); color: var(--text-primary); text-decoration: none; }
.nav-item.active { background: var(--primary-light); color: var(--primary-dark); font-weight: 700; }
.nav-item-danger { color: #b42318; }
.nav-item-danger:hover { background: #fef2f2; color: #912018; }

.nav-menu-meta {
  display: flex; flex-direction: column; gap: 1px;
  padding: 7px 10px 9px; margin-bottom: 3px;
  border-bottom: 1px solid var(--grid);
  font-size: 13px; font-weight: 700; color: var(--text-primary);
}
.nav-menu-meta span { font-size: 11.5px; font-weight: 500; color: var(--text-muted); }

/* Counts roll up to the summary: a badge inside a closed menu is a badge nobody
   sees, which defeats the point of having one. */
.nav-menu > summary .badge-count,
.nav-item .badge-count {
  background: var(--critical); color: #fff; font-size:11.5px; font-weight: 700;
  border-radius: 999px; padding: 1px 6px; line-height: 1.5; flex-shrink: 0;
}

.nav-menu-user > summary { gap: 7px; padding-left: 6px; }
.nav-avatar {
  width: 25px; height: 25px; border-radius: 50%; flex-shrink: 0;
  background: #2563eb;
  background: var(--grad-primary, linear-gradient(135deg, #2f6ff0 0%, #2563eb 45%, #1d63c9 100%));
  color: #fff;
  display: flex; align-items: center; justify-content: center;
  font-size: 11.5px; font-weight: 700; letter-spacing: .01em;
}
.nav-user-name { font-weight: 600; color: var(--text-primary); max-width: 130px; overflow: hidden; text-overflow: ellipsis; }
/* Wherever the bar is shown but space is tight, the avatar alone identifies the
   account and buys back ~90px -- which is what keeps six groups plus the online
   chip on ONE row at 946px, instead of one item dropping to a second. The full
   name is still inside the menu. Starts at the drawer breakpoint so there is no
   width where neither rule applies. */
@media (min-width: 861px) and (max-width: 1180px) {
  .nav-user-name { display: none; }
}

/* A divider before the account cluster -- NOT a flex spacer. A grow:1 spacer
   ate the whole remaining line, which pushed the account menu onto a second row
   the moment the online chip appeared. `.topbar nav` is already
   justify-content:flex-end, so the right edge needs no help. */
.nav-sep {
  width: 1px; height: 20px; flex: 0 0 auto;
  background: var(--grid); margin: 0 6px;
}

/* The dashboard is what most people open the system for, so it is a filled
   button rather than one more text link. */
/* Selector matched to `.topbar nav a.nav-link:hover` on purpose. At the plain
   `.nav-link-cta` specificity the generic hover rule outranked this one and won
   the BACKGROUND, while `color:#fff !important` survived -- so hovering turned the
   button into white text on the near-white hover tint. The `!important` on colour
   made it worse by holding the text white after its backdrop was gone. Any rule
   that owns both halves of a colour pair has to outrank whatever else can set
   either half. */
.topbar nav a.nav-link-cta {
  /* Navy, not the brand blue used for every other primary action. Two reasons:
     it separates "go to the dashboard" from the blue tint that marks the page you
     are on, and it measures 7.77:1 against white at its lightest stop where the
     brand blue only reaches 4.51 -- barely over the 4.5 AA floor.
     Solid colour FIRST, gradient second. If the token is ever missing -- a stale
     cached stylesheet against newer HTML is enough -- `background:
     var(--undefined)` computes to nothing while `color:#fff !important` still
     applies, and this button became white text on the white topbar: present,
     clickable, invisible. Same reasoning wherever #fff sits on a token below. */
  background: #1e4fa3;
  background: var(--grad-cta, linear-gradient(135deg, #1e4fa3 0%, #173d80 100%));
  color: #fff !important; font-weight: 700;
  box-shadow: 0 1px 2px rgba(16,24,40,.06), 0 2px 8px -2px rgba(23,61,128,.45);
}
.topbar nav a.nav-link-cta:hover {
  background: #173d80;
  background: var(--grad-cta-hover, linear-gradient(135deg, #1a4691 0%, #12326b 100%));
  color: #fff !important; transform: translateY(-1px);
}
/* And when the dashboard IS the current page: .active also sets a pale background
   with dark text, which would strand the white label the same way. */
.topbar nav a.nav-link-cta.active {
  background: #173d80;
  background: var(--grad-cta-hover, linear-gradient(135deg, #1a4691 0%, #12326b 100%));
  color: #fff !important;
}
.topbar nav a.nav-link-cta:active { transform: translateY(0); }

@media (max-width: 860px) {
  /* Stacked in the hamburger drawer: a floating panel makes no sense when the
     nav is already a full-width column, so the menus open inline. */
  .nav-sep { display: none; }
  .nav-menu { width: 100%; }
  .nav-menu > summary { width: 100%; justify-content: space-between; }
  .nav-menu-panel {
    position: static; min-width: 0; box-shadow: none; border: 0;
    border-left: 2px solid var(--grid); border-radius: 0;
    margin: 2px 0 4px 10px; padding: 0 0 0 8px; animation: none;
  }
  /* The account row was justify-content:space-between inherited from the desktop
     summary, which threw the avatar hard left, the caret hard right and left the
     username floating in the middle of the row. In a stacked drawer it reads as
     a normal left-aligned row. */
  .nav-menu-user > summary { justify-content: flex-start; gap: 9px; }
  .nav-menu-user > summary .nav-caret { margin-left: auto; }
  .nav-user-name { display: inline; }
  /* The online chip is a chip, not a form field: stretched to full width it
     looked like a disabled input rather than a status badge. */
  .topbar nav .online-chip { align-self: flex-start; width: auto; }
  .nav-link-cta { width: 100%; justify-content: center; }
}

/* ================================================================
   PREMIUM PASS
   ================================================================
   Declared after the component rules above so it settles them. The aim is depth
   and a single confident accent, not decoration: nothing here changes what any
   control does or where it sits.
   ================================================================ */

/* ---- cards ---- */
/* The lift on hover is 2px, not the previous 3px, and it comes with a real
   shadow change. A big jump with a flat shadow reads as the card sliding rather
   than rising. */
.sheet-card {
  border-radius: var(--radius-lg);
  padding: 17px 19px;
  transition: box-shadow .2s var(--ease-out), transform .2s var(--ease-out), border-color .2s;
}
.sheet-card:hover {
  transform: translateY(-2px);
  box-shadow: var(--shadow-lg);
  border-color: rgba(37,99,235,0.28);
}
/* A hairline of brand colour along the top edge, revealed on hover. Cheaper than
   a coloured border (which would shift the layout) and it points the eye at the
   card you are about to click. */
.sheet-card { position: relative; overflow: hidden; }
.sheet-card::before {
  content: ''; position: absolute; inset: 0 0 auto 0; height: 2px;
  background: var(--grad-primary); opacity: 0;
  transition: opacity .2s var(--ease-out);
}
.sheet-card:hover::before { opacity: 1; }

.sheet-card .icon-badge {
  border-radius: 11px;
  background: linear-gradient(135deg, #e8f0fe 0%, #e3f7f3 100%);
  box-shadow: inset 0 0 0 1px rgba(37,99,235,.10);
}
.sheet-card .name { font-size: 14px; letter-spacing: -.005em; }
.sheet-card .count { font-variant-numeric: tabular-nums; }

/* A card must never break a button's label mid-word. At ~1085px the old
   240px minimum produced four ~245px columns, and "+ บันทึกใหม่" split across two
   lines inside the button. The buttons now refuse to wrap internally and the row
   wraps instead, so they either sit side by side or stack cleanly. */
.card-grid { grid-template-columns: repeat(auto-fill, minmax(262px, 1fr)); }
.sheet-card .actions { flex-wrap: wrap; }
.sheet-card .actions .btn-primary,
.sheet-card .actions .btn-secondary { white-space: nowrap; flex: 0 1 auto; }

/* ---- panels ---- */
.panel { border-radius: var(--radius-lg); }

/* ---- primary action ---- */
/* One gradient for the primary action across the whole product, matching the
   logo mark's ramp. The shadow is tinted with the button's own hue -- a neutral
   grey shadow under a saturated button looks like dirt. */
.btn-primary {
  background: #2563eb;
  background: var(--grad-primary, linear-gradient(135deg, #2f6ff0 0%, #2563eb 45%, #1d63c9 100%));
  box-shadow: 0 1px 2px rgba(16,24,40,.06), 0 2px 8px -2px rgba(37,99,235,.42);
}
.btn-primary:hover {
  background: #1f57cf;
  background: var(--grad-primary-hover, linear-gradient(135deg, #2a63d8 0%, #1f57cf 45%, #1a4fae 100%));
  box-shadow: 0 2px 4px rgba(16,24,40,.07), 0 8px 20px -4px rgba(37,99,235,.48);
  transform: translateY(-1px);
}
.btn-primary:active { transform: translateY(0); box-shadow: 0 1px 2px rgba(16,24,40,.08); }
.btn-primary:focus-visible, .btn-secondary:focus-visible, .btn-danger:focus-visible {
  outline: 2px solid var(--primary); outline-offset: 2px; box-shadow: var(--ring-primary);
}
.btn-secondary:active, .btn-danger:active { transform: translateY(0); }

/* ---- stat tiles ---- */
/* The number is the content; it was set two steps too small to act like it. */
.stat-tile {
  border-radius: var(--radius-lg); padding: 15px 17px;
  position: relative; overflow: hidden;
}
.stat-tile::before {
  content: ''; position: absolute; inset: 0 auto 0 0; width: 3px;
  background: var(--grad-primary); opacity: .85;
}
.stat-tile .v {
  font-size: 25px; letter-spacing: -.02em; line-height: 1.2;
  font-variant-numeric: tabular-nums;
}
.stat-tile .l { font-size: 11.5px; font-weight: 600; letter-spacing: .01em; }

/* ---- type ---- */
.page-title { letter-spacing: -.022em; }
.group-title { letter-spacing: .05em; }

/* ---- links in body copy ---- */
/* An underline that thickens rather than appears: the text does not reflow and
   the hover still reads. */
a { text-underline-offset: 2px; }

/* Respecting reduced motion is not optional for the lifts and the menu slide --
   for a vestibular-sensitive reader those are the two most provocative things
   on the page. */
@media (prefers-reduced-motion: reduce) {
  .sheet-card, .btn-primary, .btn-secondary, .nav-link-cta { transition: none !important; }
  .sheet-card:hover, .btn-primary:hover, .btn-secondary:hover, .nav-link-cta:hover { transform: none !important; }
  .nav-menu-panel { animation: none !important; }
}

/* ================================================================
   เอกลักษณ์สัตว์น้ำ (aquatic visual identity)
   ================================================================
   The system is about aquatic animal disease surveillance, so the visual
   language leans on water and the species actually tracked here (shrimp,
   fish, shellfish, crab). Everything below is inline SVG/CSS -- no image
   files, no external requests -- so it stays fast, scales crisply on any
   screen, and re-colours itself from the theme variables.

   All decorative artwork must carry aria-hidden="true" in the markup: it
   adds nothing for a screen-reader user and would otherwise just be noise
   between them and the actual content. */

/* ---- water background for public/landing pages ---- */

/* ---- swimming fish band ---- */
.aqua-swim {
  position: absolute; inset-inline: 0; pointer-events: none; overflow: hidden;
  line-height: 0; z-index: 0;
}
/* The creature spans are the positioned elements -- the inline `top:` the
   _aqua.html macro sets has to land on something absolutely positioned to have
   any effect. It previously sat on a static span while the SVG inside was the
   absolute one, so the vertical placement was silently ignored. */
.aqua-swim > span { position: absolute; line-height: 0; }
.aqua-swim svg { display: block; opacity: .55; }
/* Travel is expressed in PERCENT of the band, not vw. With vw the distance was
   tied to the viewport, so inside a narrow panel (the landing brand card) the
   creatures spent nearly the whole cycle off-view and never appeared at all. */
.aqua-fish-1 { animation: swim-right 26s linear infinite; }
.aqua-fish-2 { animation: swim-right 34s linear infinite; animation-delay: -11s; }
.aqua-fish-3 { animation: swim-left  30s linear infinite; animation-delay: -17s; }
@keyframes swim-right {
  from { left: -18%; transform: none; }
  to   { left: 112%; transform: none; }
}
@keyframes swim-left {
  from { left: 112%; transform: scaleX(-1); }
  to   { left: -18%; transform: scaleX(-1); }
}

/* ---- rising bubbles ---- */
.aqua-bubble {
  position: absolute; bottom: -12px; border-radius: 50%;
  background: radial-gradient(circle at 32% 28%, rgba(255,255,255,.9), rgba(255,255,255,.15));
  border: 1px solid rgba(255,255,255,.5);
  animation: bubble-rise linear infinite;
}
@keyframes bubble-rise {
  from { transform: translateY(0) scale(.7); opacity: 0; }
  15%  { opacity: .75; }
  to   { transform: translateY(-190px) scale(1.05); opacity: 0; }
}

/* ---- wave divider between sections ---- */
.aqua-wave { display: block; width: 100%; height: 34px; line-height: 0; }
.aqua-wave path { fill: var(--primary); opacity: .10; }

/* ---- species icon chip (shrimp / fish / shellfish / crab) ---- */

/* ---- aquatic empty state ---- */
.empty-state .aqua-empty { width: 108px; height: 74px; margin: 0 auto 12px; display: block; opacity: .75; }
.empty-state .aqua-empty .body { fill: var(--primary); opacity: .22; }
.empty-state .aqua-empty .line { stroke: var(--primary); opacity: .35; }

/* ---- section heading with a small fish marker ---- */
.aqua-heading { display: flex; align-items: center; gap: 9px; }
.aqua-heading svg { width: 22px; height: 22px; flex-shrink: 0; opacity: .8; }

/* ---- decorative fish watermark inside gradient hero panels ---- */
.aqua-watermark {
  position: absolute; right: -18px; bottom: -14px;
  width: 190px; height: 190px; opacity: .10; pointer-events: none;
  transform: rotate(-8deg);
}
.aqua-watermark svg { width: 100%; height: 100%; }
/* The watermark's colour is decided HERE, not by the macro's argument -- _aqua.html's
   watermark() passes none, so this rule is the single place it is set. */
.aqua-watermark path, .aqua-watermark circle { fill: #fff; }

/* On small screens the artwork competes with the content it decorates, so
   the large watermark steps aside while the subtler background stays. */
@media (max-width: 640px) {
  .aqua-watermark { width: 120px; height: 120px; opacity: .07; }
  .aqua-swim { opacity: .6; }
}

/* ================================================================
   ปรับให้ใช้งานบนมือถือได้จริง (mobile usability)
   ================================================================ */

/* Text links inside body copy sit inside 44px-tall rows on touch devices so
   they can be hit reliably, without changing how they look. */
/* Inline text links get a taller hit area on every viewport where the layout
   is already stacked -- not gated on (pointer: coarse), because that query
   misses touch laptops and any browser that reports a mouse while the user is
   actually using a finger. Padding-only, so nothing moves visually. */
.auth-footnote a, .page-sub a, .field-hint a, .farmer-track-link a, .farmer-form-foot a {
  display: inline-block; padding: 11px 2px;
}
@media (pointer: coarse) {
  .data-table td a.btn-secondary, .data-table td a.btn-primary { min-height: var(--tap-min); }
}

/* Wide data tables scroll inside their own panel instead of forcing the whole
   page sideways, and keep a visible edge cue that there is more to see. */

@media (max-width: 640px) {
  .container { padding: 20px 15px 60px; }
  .page-title { font-size: 20px; }
  .page-sub { font-size: 14px; }
  .form-grid { grid-template-columns: 1fr; gap: 14px; }
  /* Full-width buttons are far easier to hit one-handed on a phone. */
  .form-actions { flex-direction: column; }
  .form-actions .btn-primary, .form-actions .btn-secondary, .form-actions .btn-danger { width: 100%; }
  .data-table { font-size: 13.5px; }
  .data-table th, .data-table td { padding: 12px 10px; }
  .stat-row { grid-template-columns: 1fr 1fr; }
}

/* ================================================================
   หน้าแจ้งรายงานของเกษตรกร (public farmer form)
   ================================================================
   Designed for the least technical audience of the whole system, most of them
   on a phone: one column, generous targets, numbered steps so a long form
   still feels finishable, and plain-language hints under the fields that
   people most often get stuck on. */

.farmer-form-wrap { max-width: 720px; }

.farmer-hero {
  position: relative; overflow: hidden;
  background: linear-gradient(135deg, var(--primary) 0%, var(--primary-dark) 55%, var(--accent-deep) 130%);
  color: #fff; border-radius: var(--radius-lg) var(--radius-lg) 0 0;
  padding: 34px 28px 30px; text-align: center;
}
.farmer-hero-inner { position: relative; z-index: 2; }
.farmer-hero-icon {
  width: 68px; height: 68px; margin: 0 auto 14px; border-radius: 20px;
  background: rgba(255,255,255,.18); border: 1px solid rgba(255,255,255,.28);
  display: flex; align-items: center; justify-content: center;
  /* The icon inside inherits this, so it cannot become a different
     white from the text beside it. */
  color: #fff;
}
.farmer-hero-title { font-size: 24px; font-weight: 800; margin: 0 0 10px; letter-spacing: -.01em; }
.farmer-hero-sub { font-size: 15px; line-height: 1.7; margin: 0; color: #fff; }   /* was .94 alpha */

.farmer-track-link { text-align: center; font-size: 14.5px; margin: 16px 0 22px; color: var(--text-secondary); }

.farmer-form {
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-lg); padding: 26px 26px 30px; box-shadow: var(--shadow-md);
}

/* Numbered steps: turns one intimidating wall of ~14 fields into three short,
   obviously-finite sections. */
.form-step { padding-bottom: 26px; margin-bottom: 26px; border-bottom: 1px solid var(--grid); }
.form-step:last-of-type { border-bottom: none; margin-bottom: 8px; padding-bottom: 6px; }
.form-step-head { display: flex; align-items: center; gap: 11px; margin-bottom: 18px; }
.form-step-num {
  width: 32px; height: 32px; flex-shrink: 0; border-radius: 50%;
  background: linear-gradient(135deg, var(--primary), var(--accent-deep)); color: #fff;
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 15px;
}
.form-step-title { font-size: 17px; font-weight: 800; margin: 0; color: var(--text-primary); }

.symptom-fieldset { border: 1px solid var(--grid); border-radius: var(--radius-md); padding: 16px; margin-top: 18px; background: var(--surface-2); }
.symptom-fieldset legend { font-size: 14.5px; font-weight: 700; padding: 0 8px; color: var(--text-primary); }
.legend-hint { font-weight: 400; color: var(--text-muted); font-size: 13px; }
.symptom-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(215px, 1fr)); gap: 8px; }

/* Whole row is the tap target, not just the 13px checkbox itself -- the single
   biggest source of mis-taps on a checklist like this. */
.symptom-option {
  display: flex; align-items: center; gap: 11px; cursor: pointer;
  padding: 11px 13px; border-radius: var(--radius-sm); background: var(--surface-1);
  border: 1px solid var(--border); min-height: var(--tap-min);
  font-size: 14.5px; line-height: 1.4; transition: border-color .12s, background .12s;
}
.symptom-option:hover { border-color: var(--primary); background: var(--primary-light); }
.symptom-option input { width: 24px; height: 24px; flex-shrink: 0; accent-color: var(--primary); margin: 0; }
.symptom-option:has(input:checked) { border-color: var(--primary); background: var(--primary-light); }

.photo-field { margin-top: 18px; padding: 16px; border: 2px dashed var(--border); border-radius: var(--radius-md); background: var(--surface-2); }
.photo-field input[type=file] { background: var(--surface-1); cursor: pointer; }

.btn-lg { font-size: 16.5px; padding: 15px 30px; min-height: 54px; width: 100%; }
.farmer-form-foot { text-align: center; font-size: 13.5px; color: var(--text-muted); margin: 14px 0 0; }

@media (max-width: 640px) {
  .farmer-hero { padding: 26px 20px 24px; border-radius: var(--radius-md) var(--radius-md) 0 0; }
  .farmer-hero-title { font-size: 21px; }
  .farmer-hero-sub { font-size: 14.5px; }
  .farmer-form { padding: 20px 17px 24px; border-radius: var(--radius-md); }
  .symptom-grid { grid-template-columns: 1fr; }
  .form-step-title { font-size: 16px; }
}

/* ---------------- หน้าขอบคุณหลังส่งรายงาน (thanks page) ---------------- */
.thanks-hero {
  position: relative; overflow: hidden; text-align: center; color: #fff;
  background: linear-gradient(135deg, var(--good) 0%, var(--accent-deep) 70%, var(--primary) 140%);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0; padding: 34px 26px 30px;
}
.thanks-hero-inner { position: relative; z-index: 2; }
.thanks-check {
  width: 76px; height: 76px; margin: 0 auto 14px; border-radius: 50%;
  background: rgba(255,255,255,.18); border: 1px solid rgba(255,255,255,.3);
  display: flex; align-items: center; justify-content: center;
}
.thanks-title { font-size: 23px; font-weight: 800; margin: 0 0 8px; }
.thanks-sub { font-size: 15px; margin: 0; color: rgba(255,255,255,.94); line-height: 1.6; }

/* The tracking number is the single most important thing on this page -- if
   the user loses it they cannot follow up -- so it gets size, contrast, and a
   selectable monospace-ish treatment rather than sitting in body copy. */
.tracking-card {
  background: var(--surface-1); border: 2px solid var(--primary);
  border-radius: var(--radius-md); padding: 22px; text-align: center; margin: 18px 0;
  box-shadow: var(--shadow-md);
}
.tracking-label { font-size: 13.5px; color: var(--text-secondary); margin-bottom: 6px; }
.tracking-code {
  font-size: 34px; font-weight: 800; letter-spacing: .06em; color: var(--primary-dark);
  user-select: all; line-height: 1.2; margin-bottom: 10px;
}
.tracking-hint { font-size: 13.5px; color: var(--text-secondary); margin: 0; line-height: 1.7; }

.next-steps { background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-md); padding: 20px 22px; margin-bottom: 18px; }
.next-steps-title { font-size: 16px; font-weight: 800; margin: 0 0 14px; }
.next-steps-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 14px; }
.next-steps-list li { display: flex; gap: 12px; align-items: flex-start; font-size: 14.5px; line-height: 1.6; }
.step-dot {
  width: 28px; height: 28px; flex-shrink: 0; border-radius: 50%;
  background: var(--primary-light); color: var(--primary-dark);
  display: flex; align-items: center; justify-content: center; font-weight: 800; font-size: 13.5px;
}
.thanks-contact { text-align: center; font-size: 13.5px; color: var(--text-secondary); margin-top: 18px; line-height: 1.7; }

@media (max-width: 640px) {
  .tracking-code { font-size: 28px; }
  .thanks-title { font-size: 20px; }
}

/* ---------------- หน้าตรวจสอบสถานะ (status lookup) ---------------- */
.status-hero {
  position: relative; overflow: hidden; text-align: center; color: #fff;
  background: linear-gradient(135deg, var(--primary) 0%, var(--accent-deep) 120%);
  border-radius: var(--radius-lg) var(--radius-lg) 0 0; padding: 30px 26px 26px;
}
.status-hero-inner { position: relative; z-index: 2; }
.status-result { margin-top: 18px; }
.status-result-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; flex-wrap: wrap; margin-bottom: 20px; }
.status-result-code { font-size: 21px; font-weight: 800; color: var(--primary-dark); letter-spacing: .03em; }
.status-result-meta { font-size: 13px; color: var(--text-muted); margin-top: 3px; }

/* Progress track -- answers "how far along is my report?" without the user
   having to interpret a status label they may not recognise. */
.status-track { list-style: none; display: flex; margin: 0 0 20px; padding: 0; counter-reset: s; }
.status-track-step { flex: 1; text-align: center; position: relative; font-size: 12.5px; color: var(--text-muted); }
.status-track-step::before {
  content: ''; position: absolute; top: 15px; left: -50%; width: 100%; height: 3px;
  background: var(--grid); z-index: 0;
}
.status-track-step:first-child::before { display: none; }
.status-track-step.done::before { background: var(--good); }
.status-track-dot {
  position: relative; z-index: 1; display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; margin: 0 auto 7px; border-radius: 50%;
  background: var(--surface-2); border: 2px solid var(--grid);
  font-weight: 800; font-size: 13px; color: var(--text-muted);
}
.status-track-step.done .status-track-dot { background: var(--good); border-color: var(--good); color: #fff; }
.status-track-step.done .status-track-label { color: var(--text-primary); font-weight: 700; }
.status-track-label { display: block; line-height: 1.4; padding: 0 3px; }

.status-detail-grid { display: grid; grid-template-columns: repeat(auto-fit,minmax(180px,1fr)); gap: 12px; font-size: 14.5px; }
.sd-label { display: block; font-size: 12.5px; color: var(--text-muted); margin-bottom: 2px; }

.officer-note { margin-top: 18px; background: var(--accent-light); border-left: 4px solid var(--accent); border-radius: var(--radius-sm); padding: 14px 16px; }
.officer-note-head { font-size: 13.5px; font-weight: 800; color: var(--text-primary); margin-bottom: 6px; }
.officer-note-body { font-size: 14.5px; line-height: 1.65; margin: 0; color: var(--text-primary); }
.officer-note-time { font-size: 12.5px; color: var(--text-muted); margin-top: 8px; }

/* ---------------- flash messages ---------------- */
.flash { display: flex; align-items: flex-start; gap: 10px; font-size: 14.5px; line-height: 1.6; }
.flash-icon { flex-shrink: 0; font-size: 16px; line-height: 1.4; }

@media (max-width: 640px) {
  .status-track-label { font-size: 11.5px; }
  .status-track-dot { width: 28px; height: 28px; }
  .status-track-step::before { top: 13px; }
}

/* Aquatic icon inside the activity-card badge: the badge is a fixed 38px
   square, so the icon is centred rather than left-aligned in it. */
.sheet-card .icon-badge svg, .aqua-heading svg { display: block; }
.group-title.aqua-heading { display: flex; align-items: center; gap: 8px; }
.group-title.aqua-heading span { line-height: 1.3; }

/* ================================================================
   ตารางข้อมูลบนมือถือ (responsive data tables)
   ================================================================
   Measured problem this solves: at 375px the admin tables rendered
   930-983px wide inside a 343px panel -- 2.7-2.9x the screen. Reading a row
   meant scrolling sideways far enough that the row's first column (the thing
   that says WHICH activity/user the row is about) had left the screen.

   Below 720px each row becomes a self-contained card and each cell shows its
   column name beside its value, so a row is read top-to-bottom with no
   horizontal scrolling at all. Column names come from the <th> via the small
   script in base.html.

   Above 720px nothing changes -- the normal table is the better tool when
   there is room for it. */
/* Breakpoint set at 820px, not 720px, so iPad-portrait (768px) -- a very
   common device for officers in the field -- gets the card layout too.
   Measured: at 768px the activity-admin table still needed 238px of
   horizontal scrolling, because its cells contain form controls with their
   own minimum widths. */
@media (max-width: 820px) {
  .data-table.has-stacked-labels,
  .data-table.has-stacked-labels tbody,
  .data-table.has-stacked-labels tr,
  .data-table.has-stacked-labels td {
    display: block;
    width: 100%;
  }
  /* Header row is redundant once every cell carries its own label. */
  .data-table.has-stacked-labels thead { display: none; }

  .data-table.has-stacked-labels tr {
    background: var(--surface-1);
    border: 1px solid var(--border);
    border-radius: var(--radius-md);
    padding: 6px 4px;
    margin-bottom: 12px;
    box-shadow: var(--shadow-sm);
  }
  .data-table.has-stacked-labels tr:last-child { margin-bottom: 0; }
  .data-table.has-stacked-labels tr:hover td { background: transparent; }

  .data-table.has-stacked-labels td {
    display: flex;
    align-items: flex-start;
    justify-content: space-between;
    gap: 14px;
    padding: 9px 12px;
    border-bottom: 1px solid var(--grid);
    text-align: right;
    white-space: normal;
    min-height: 0;
  }
  .data-table.has-stacked-labels td:last-child { border-bottom: none; }

  .data-table.has-stacked-labels td::before {
    content: attr(data-label);
    flex: 0 0 40%;
    text-align: left;
    font-size: 12.5px;
    font-weight: 700;
    color: var(--text-muted);
    line-height: 1.5;
  }
  /* An action cell (buttons) or an unlabelled cell reads better full-width. */
  .data-table.has-stacked-labels td:not([data-label])::before { content: none; }
  .data-table.has-stacked-labels td:not([data-label]) { justify-content: flex-start; }

  /* Cells holding controls or button groups: let them use the full width
     rather than being squeezed into the 60% value column. */
  .data-table.has-stacked-labels td:has(input),
  .data-table.has-stacked-labels td:has(select),
  .data-table.has-stacked-labels td:has(.btn-primary),
  .data-table.has-stacked-labels td:has(.btn-secondary),
  .data-table.has-stacked-labels td:has(.btn-danger) {
    flex-direction: column;
    align-items: stretch;
    text-align: left;
    gap: 7px;
  }
  .data-table.has-stacked-labels td:has(input)::before,
  .data-table.has-stacked-labels td:has(select)::before,
  .data-table.has-stacked-labels td:has(.btn-primary)::before,
  .data-table.has-stacked-labels td:has(.btn-secondary)::before,
  .data-table.has-stacked-labels td:has(.btn-danger)::before {
    flex: none;
  }
  .data-table.has-stacked-labels td input,
  .data-table.has-stacked-labels td select { width: 100%; min-width: 0; }

  /* Rows of action buttons stack full-width so they meet the tap-target size. */
  .data-table.has-stacked-labels td > div[style*="display:flex"] { flex-direction: column; }
  .data-table.has-stacked-labels td .btn-secondary,
  .data-table.has-stacked-labels td .btn-danger,
  .data-table.has-stacked-labels td .btn-primary { width: 100%; }

  /* The scroll-shadow hint is meaningless once nothing scrolls sideways. */
}

/* ---------------- form controls that live inside data tables ----------------
   These sit directly in <td> rather than in a .form-field, so they never
   picked up the shared input styling: measured 18-30px tall with 11-12.5px
   text, i.e. below the tap-target floor AND below the 16px that stops iOS
   zooming the page on focus. */
.cell-input {
  font-family: inherit; font-size: 14px;
  padding: 8px 10px; min-height: 38px; width: 100%; max-width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-1); color: var(--text-primary);
  transition: border-color .12s, box-shadow .12s;
}
.cell-input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px var(--primary-light); }
.cell-input-title { font-weight: 800; min-width: 200px; }
.cell-input-num { max-width: 90px; }

.cell-btn { padding: 8px 14px; font-size: 13px; min-height: 38px; white-space: nowrap; }

/* On touch screens everything grows to the full comfortable target and the
   font reaches the 16px iOS-zoom threshold. Desktop keeps the denser sizing,
   where a mouse makes 38px perfectly precise. */
@media (max-width: 820px), (pointer: coarse) {
  .cell-input { font-size: 16px; min-height: var(--tap-min); padding: 11px 12px; }
  .cell-input-num { max-width: 100%; }
  .cell-btn { min-height: var(--tap-min); font-size: 14.5px; padding: 11px 16px; }
}

/* Nav links: fine for a mouse at 35px, but bumped to the full target on any
   touch device (touch laptops and tablets in landscape included, which the
   width-based breakpoint alone would miss). */
@media (pointer: coarse) {
  .topbar nav a.nav-link { min-height: var(--tap-min); padding: 11px 13px; }
}

/* ---------------- last tap-target gaps found by the viewport audit ---------------- */

/* Login page audience tabs: 38px. These are the very first choice a visitor
   makes ("am I a farmer or an officer?"), so they get the full target. */
.access-tab { min-height: var(--tap-min); }

/* Bare checkboxes that are NOT wrapped in a label (e.g. the permission grid on
   /admin/users) render at the browser default 13x13px. Scaling the control
   itself keeps the layout while making it hittable and visible. */
input[type=checkbox]:not(.symptom-option input),
input[type=radio]:not(.symptom-option input) {
  /* 24px meets WCAG 2.5.8 (AA) minimum target size. These sit in dense
     permission grids where a full 44px box would break the layout; each one
     has an associated <label>, which is clickable and gives the larger
     practical target. */
  width: 24px; height: 24px; accent-color: var(--primary); flex-shrink: 0;
}
@media (pointer: coarse) {
  input[type=checkbox]:not(.symptom-option input),
  input[type=radio]:not(.symptom-option input) { width: 24px; height: 24px; }
}

/* File inputs render as a ~21px native control. Padding gives the row a real
   target without hiding the OS-native "choose file" button, which people
   recognise. */
input[type=file] {
  min-height: var(--tap-min); padding: 11px 12px; width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  /* 16px, not 15px: anything under 16 makes iOS Safari zoom the page when the
     control is focused -- the same rule as every other input here. */
  background: var(--surface-1); font-size: 16px; cursor: pointer;
}

/* ---------------- กลับหน้าหลัก (back-to-home, present on every page) ----------------
   Deliberately a real, always-visible control rather than relying on the nav
   bar: on phones the nav is collapsed behind the hamburger, and on the public
   farmer pages there is no nav at all -- so someone who opened a link
   directly had no route back other than the browser Back button. */
.back-home {
  display: inline-flex; align-items: center; gap: 7px;
  min-height: var(--tap-min); padding: 10px 18px; margin-bottom: 16px;
  background: var(--surface-1); color: var(--text-secondary);
  border: 1px solid var(--border); border-radius: 999px;
  font-size: 14px; font-weight: 700; text-decoration: none;
  box-shadow: var(--shadow-sm);
  transition: background .12s, color .12s, transform .05s, box-shadow .12s;
}
.back-home:hover {
  background: var(--primary-light); color: var(--primary-dark);
  border-color: transparent; text-decoration: none;
  transform: translateX(-2px); box-shadow: var(--shadow-md);
}
.back-home:active { transform: translateX(0); }

/* Public header: same shape as the signed-in bar, minus the menu. */
.topbar-public { justify-content: space-between; }
.brand-link { text-decoration: none; }
.brand-link:hover { text-decoration: none; opacity: .85; }
.btn-home { flex-shrink: 0; white-space: nowrap; }

@media (max-width: 640px) {
  .back-home { padding: 10px 15px; font-size: 13.5px; margin-bottom: 13px; }
  .topbar-public .brand span:last-child {
    /* Keep the brand from pushing the หน้าแรก button off a narrow screen. */
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 48vw;
  }
}

/* ---------- pagination ----------
   Used by the entry list, which now fetches one bounded page from SQLite
   instead of loading the whole table. Page links wrap on narrow screens and
   every target meets the 44px tap floor set by --tap-min. */
.pager {
  display: flex; flex-wrap: wrap; gap: 6px; align-items: center;
  justify-content: center; margin: 18px 0 4px;
}
.pager-btn {
  display: inline-flex; align-items: center; justify-content: center;
  min-width: var(--tap-min); min-height: var(--tap-min);
  padding: 0 12px; border-radius: var(--radius-sm);
  border: 1px solid var(--border); background: var(--surface-1);
  color: var(--text-secondary); font-size: 13.5px; font-weight: 600;
  text-decoration: none; transition: background .15s, color .15s, border-color .15s;
}
.pager-btn:hover {
  background: var(--primary-light); color: var(--primary-dark);
  border-color: transparent; text-decoration: none;
}
.pager-btn.current {
  background: var(--primary); border-color: var(--primary);
  color: #fff; cursor: default;
}
.pager-gap { color: var(--text-muted); padding: 0 2px; }

/* =====================================================================
   MOTION LAYER
   ---------------------------------------------------------------------
   Life and depth without noise. Three rules held throughout:
     1. Idle motion is SLOW and small (4-7s cycles, a few px). Anything
        faster reads as a glitch on a page people stare at all day.
     2. Motion on interaction is FAST (120-220ms) so the UI feels
        responsive rather than laggy.
     3. Everything here is covered by the global prefers-reduced-motion
        override near the top of this file -- no separate opt-out needed.
   ===================================================================== */

/* ---- idle: aquatic drift -------------------------------------------- */
/* Applied to decorative creature icons. Two variants at different speeds so
   a grid of cards doesn't pulse in lockstep, which looks mechanical. */
@keyframes aqua-drift {
  0%, 100% { transform: translateY(0) rotate(0deg); }
  50%      { transform: translateY(-3px) rotate(-3deg); }
}
@keyframes aqua-drift-alt {
  0%, 100% { transform: translateY(-1px) rotate(1.5deg); }
  50%      { transform: translateY(-4px) rotate(-2deg); }
}
.icon-badge > svg { animation: aqua-drift 5.5s ease-in-out infinite; will-change: transform; }
.card-grid .sheet-card:nth-child(even) .icon-badge > svg { animation: aqua-drift-alt 6.8s ease-in-out infinite; }
.card-grid .sheet-card:nth-child(3n) .icon-badge > svg { animation-delay: -1.8s; }
.card-grid .sheet-card:nth-child(4n) .icon-badge > svg { animation-delay: -3.1s; }

/* Hero creatures breathe a little more, since they are the focal point. */
@keyframes aqua-hero {
  0%, 100% { transform: translateY(0) rotate(0deg) scale(1); }
  50%      { transform: translateY(-6px) rotate(-4deg) scale(1.03); }
}
.farmer-hero-icon > svg, .logo-mark-lg > svg { animation: aqua-hero 6s ease-in-out infinite; }

/* ---- idle: water ---------------------------------------------------- */
/* The wave divider drifts sideways. Two stacked paths at different speeds
   give parallax, which is what stops it looking like a sliding image. */
/* Units, not percentages: on an SVG path a percentage resolves against the
   path's own bounding box, which slid the wave entirely out of view and left a
   flat bar. 1200 user units is exactly two wave periods, so this loops
   seamlessly against the double-width path the wave macro emits. */
@keyframes wave-drift-a { from { transform: translateX(0); }        to { transform: translateX(-1200px); } }
@keyframes wave-drift-b { from { transform: translateX(-1200px); }  to { transform: translateX(0); } }
.aqua-wave { overflow: hidden; }
.aqua-wave path:nth-child(1) { animation: wave-drift-a 22s linear infinite; }
.aqua-wave path:nth-child(2) { animation: wave-drift-b 31s linear infinite; }

/* Slow caustic shimmer across gradient hero panels -- like light on water. */
@keyframes caustic {
  0%   { transform: translateX(-40%) skewX(-12deg); opacity: 0; }
  40%  { opacity: .5; }
  100% { transform: translateX(140%) skewX(-12deg); opacity: 0; }
}
.welcome-banner, .thanks-hero, .status-hero, .landing-brand { position: relative; overflow: hidden; }
.welcome-banner::after, .thanks-hero::after, .status-hero::after, .landing-brand::after {
  content: ''; position: absolute; top: 0; bottom: 0; width: 38%;
  background: linear-gradient(100deg, transparent, rgba(255,255,255,.16), transparent);
  animation: caustic 9s ease-in-out infinite; pointer-events: none; z-index: 1;
}

/* ---- interaction: cards -------------------------------------------- */
.sheet-card {
  transition: transform .18s cubic-bezier(.16,1,.3,1), box-shadow .18s ease, border-color .18s ease;
}
.sheet-card:hover {
  transform: translateY(-4px);
  box-shadow: var(--shadow-lg);
  border-color: rgba(37,99,235,.35);
}
/* The card's creature reacts to the hover -- a small, deliberate "hello". */
.sheet-card:hover .icon-badge > svg { animation: aqua-hero 1.6s ease-in-out infinite; }
.icon-badge { transition: transform .18s cubic-bezier(.16,1,.3,1), background .18s ease; }
.sheet-card:hover .icon-badge { transform: scale(1.08) rotate(-4deg); }

/* ---- interaction: buttons ------------------------------------------ */
.btn-primary, .btn-secondary, .btn-danger, .pager-btn, .back-home, .access-tab {
  transition: transform .14s cubic-bezier(.16,1,.3,1), box-shadow .14s ease,
              background .14s ease, color .14s ease, border-color .14s ease;
}
.btn-primary:hover, .btn-danger:hover { transform: translateY(-2px); box-shadow: var(--shadow-md); }
/* Press feedback: the button settles back down, which reads as a real press. */
.btn-primary:active, .btn-secondary:active, .btn-danger:active,
.pager-btn:active, .back-home:active { transform: translateY(1px) scale(.985); }

/* ---- interaction: nav --------------------------------------------- */
.nav-link { position: relative; }
.nav-link::after {
  content: ''; position: absolute; left: 10px; right: 10px; bottom: 4px; height: 2px;
  border-radius: 2px; background: currentColor; opacity: .9;
  transform: scaleX(0); transform-origin: left; transition: transform .2s cubic-bezier(.16,1,.3,1);
}
.nav-link:hover::after, .nav-link.active::after { transform: scaleX(1); }

/* ---- interaction: tables ------------------------------------------ */
.data-table tbody tr { transition: background .13s ease; }
.data-table tbody tr:hover { background: var(--primary-light); }

/* ---- arrival: flash messages -------------------------------------- */
@keyframes flash-in {
  from { opacity: 0; transform: translateY(-8px) scale(.99); }
  to   { opacity: 1; transform: none; }
}
.flash { animation: flash-in .3s cubic-bezier(.16,1,.3,1) both; }

/* ---- arrival: the farmer's tracking number ------------------------- */
/* This number is the one thing a farmer must not miss, so it gets a single
   attention-drawing entrance and then holds still. */
@keyframes stamp-in {
  0%   { opacity: 0; transform: scale(.86); }
  60%  { opacity: 1; transform: scale(1.04); }
  100% { opacity: 1; transform: scale(1); }
}
.tracking-code { animation: stamp-in .5s cubic-bezier(.16,1,.3,1) both; animation-delay: .15s; }

/* Progress track dots fill in one after another, so a farmer can see the
   sequence rather than three static circles. */
@keyframes dot-pop { from { opacity: 0; transform: scale(.6); } to { opacity: 1; transform: none; } }
.status-track-step .status-track-dot { animation: dot-pop .34s cubic-bezier(.16,1,.3,1) both; }
.status-track-step:nth-child(2) .status-track-dot { animation-delay: .1s; }
.status-track-step:nth-child(3) .status-track-dot { animation-delay: .2s; }

/* ---- landing: the two entry points -------------------------------- */
.access-tab:hover { transform: translateY(-1px); }
.access-panel { animation: fadeInUp .32s cubic-bezier(.16,1,.3,1) both; }

/* ---- focus stays instant ------------------------------------------ */
/* Deliberately NOT animated: a keyboard user tabbing quickly needs the focus
   ring to appear the instant focus lands, with no transition to sit through. */
:focus-visible { transition: none !important; }

/* ---- landing brand panel -------------------------------------------
   Motion here comes from the rising bubbles plus the caustic shimmer in the
   motion layer above. An earlier version also had a wave band pinned to the
   bottom; it was removed because three stacked white washes read as a flat
   grey slab, and the swimming creatures inside it kept colliding with the
   contact details. Bubbles give the same "underwater" feel with no layout risk. */
.landing-brand > .logo-mark-lg,
.landing-brand > .title,
.landing-brand > .sub,
.landing-brand > .landing-contact { position: relative; z-index: 2; }

/* ---- notification recipient picker ---------------------------------
   The composer used to offer only whole role groups, which meant an alert
   about two farms was pushed at every officer in the country. These styles
   support choosing named individuals instead. */
.recipient-block {
  border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 14px 16px; margin-bottom: 14px; background: var(--surface-2);
}
.recipient-head { display: flex; gap: 10px; align-items: flex-start; margin-bottom: 12px; }
.recipient-num {
  flex-shrink: 0; width: 24px; height: 24px; border-radius: 50%;
  background: var(--primary); color: #fff; font-size: 12.5px; font-weight: 700;
  display: flex; align-items: center; justify-content: center;
}
.recipient-head b { font-size: 13.5px; }
.recipient-hint { font-size: 11.5px; color: var(--text-muted); line-height: 1.55; margin: 2px 0 0; }

.recipient-tools { display: flex; gap: 8px; align-items: center; flex-wrap: wrap; margin-bottom: 10px; }
.recipient-tools input[type="search"] {
  flex: 1 1 220px; min-width: 0;
}
.recipient-count {
  font-size: 12px; font-weight: 600; color: var(--text-muted);
  padding: 4px 10px; border-radius: 999px; background: var(--surface-1);
  border: 1px solid var(--border); white-space: nowrap;
}
.recipient-count.has-selection {
  color: #fff; background: var(--accent); border-color: transparent;
}

/* Scrolls rather than growing without limit -- this list is as long as the
   staff roster. max-height is in rem so it scales with the user's font size. */
.person-list {
  max-height: 17rem; overflow-y: auto;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background: var(--surface-1);
}
.person-row {
  display: flex; gap: 10px; align-items: center;
  padding: 9px 12px; cursor: pointer; font-weight: 400;
  border-bottom: 1px solid var(--grid);
  min-height: var(--tap-min);
  transition: background .12s ease;
}
.person-row:last-child { border-bottom: none; }
.person-row:hover { background: var(--primary-light); }
.person-row:has(input:checked) { background: var(--accent-light); }
.person-row input { flex-shrink: 0; }
.person-main { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.person-name { font-size: 13px; font-weight: 600; color: var(--text-primary); }
.person-meta { font-size:11.5px; color: var(--text-muted); }

/* history table: who an alert was aimed at */
.named-to .named-list { font-size:11.5px; color: var(--text-muted); margin-top: 3px; max-width: 260px; }

/* Inbox marker for an alert addressed to this person by name. */
.personal-flag {
  display: inline-flex; align-items: center; gap: 4px;
  font-size:11.5px; font-weight: 700; color: var(--accent);
  background: var(--accent-light); border-radius: 999px; padding: 2px 9px;
}

/* ---- destructive-action zone --------------------------------------
   Deliberately separated from the save button and visually distinct, so
   retiring an account cannot be mistaken for an ordinary edit. */
.danger-zone { border-color: rgba(220,53,69,.35); background: #fffafa; }
.danger-zone-title {
  display: flex; align-items: center; gap: 8px;
  font-size: 14px; font-weight: 800; color: var(--critical); margin-bottom: 10px;
}
.danger-zone-note { font-size: 12.5px; color: var(--text-secondary); line-height: 1.65; margin: 0 0 14px; }
.danger-keep {
  background: var(--surface-1); border: 1px solid var(--grid);
  border-radius: var(--radius-sm); padding: 12px 14px; margin-bottom: 16px;
}
.danger-keep-head { font-size: 12.5px; font-weight: 700; color: var(--good); margin-bottom: 8px; }
.danger-keep-list { margin: 0; padding-left: 20px; font-size: 12.5px; color: var(--text-secondary); line-height: 1.85; }
.danger-keep-list code {
  background: var(--surface-2); border: 1px solid var(--grid);
  border-radius: 4px; padding: 1px 5px; font-size: 11.5px;
}
.danger-zone code {
  background: var(--surface-2); border: 1px solid var(--grid);
  border-radius: 4px; padding: 1px 5px; font-size: 12px;
}
@media (prefers-color-scheme: dark) { .danger-zone { background: rgba(220,53,69,.06); } }

/* Inline explanation where an action button would otherwise sit (e.g. "this is
   your own account", "kept for historical reference"). */
.cell-note {
  display: inline-flex; align-items: center;
  font-size: 11.5px; color: var(--text-muted); font-style: italic;
  padding: 0 4px; white-space: nowrap;
}
/* Marks a retired account inside a historical recipient list. */
.retired-mark { color: var(--text-muted); font-style: italic; }

/* ---- "who is online" indicator ------------------------------------
   Presence is inferred from recent activity (see db.touch_user_seen), so the
   pulsing dot is doing real work here: it signals "live figure, refreshed as
   people move around" rather than a fixed number. */
@keyframes online-pulse {
  0%, 100% { transform: scale(1);   opacity: 1; }
  50%      { transform: scale(1.5); opacity: .45; }
}
.online-dot {
  width: 8px; height: 8px; border-radius: 50%; flex-shrink: 0;
  background: #34d399;
  box-shadow: 0 0 0 3px rgba(52, 211, 153, .22);
  animation: online-pulse 2.4s ease-in-out infinite;
}
/* Styled for the LIGHT topbar (--surface-1), to match the account menu beside it.
   An earlier version used white text on a translucent-white background, which
   was invisible here -- that styling only works on the blue gradient panels. */
.online-chip {
  display: inline-flex; align-items: center; gap: 7px;
  padding: 5px 11px; border-radius: 999px;
  font-size: 12px; font-weight: 700; white-space: nowrap;
  color: #0b7a5c; background: var(--accent-light);
  border: 1px solid rgba(15, 158, 142, .32);
}
.online-chip-link { text-decoration: none; transition: background .15s ease, border-color .15s ease; }
.online-chip-link:hover,
.online-chip-link.active {
  background: #cdefe7; border-color: rgba(15, 158, 142, .55);
  color: #0b7a5c; text-decoration: none;
}

/* Headline figure on the admin online page. */
.online-head { display: flex; align-items: baseline; gap: 10px; }
.online-head .online-dot { align-self: center; }
.online-big { font-size: 34px; font-weight: 800; color: var(--primary-dark); line-height: 1; }
.online-unit { font-size: 13.5px; color: var(--text-secondary); font-weight: 600; }

/* ---- data-completeness report --------------------------------------
   Summary tiles for the traceability gap (rows with a result but no farm
   registration number). Colour only carries meaning here: red = a gap that
   blocks analysis, green = nothing outstanding. */
.dq-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 14px;
}
.dq-stat {
  background: var(--surface-2); border: 1px solid var(--grid);
  border-radius: var(--radius-md); padding: 14px 16px;
}
.dq-num { font-size: 26px; font-weight: 800; color: var(--text-primary); line-height: 1.15; }
.dq-lbl { font-size: 11.5px; color: var(--text-muted); margin-top: 4px; line-height: 1.45; }
.dq-stat.dq-bad { background: #fff5f5; border-color: rgba(220,53,69,.32); }
.dq-stat.dq-bad .dq-num { color: var(--critical); }
.dq-stat.dq-good { background: #f2fbf6; border-color: rgba(15,158,94,.3); }
.dq-stat.dq-good .dq-num { color: var(--good); }

/* ---------------- in-page combobox (public report form's species field) ----------------
   A <datalist>/<select> popup is drawn by the OS, and an embedded browser places it from
   the page's reported window origin -- which puts it across the screen. This list is part
   of the page, so it cannot be mispositioned, and it can carry group headings and a
   filter that a native popup cannot. */
.combo { position: relative; }
.combo-toggle {
  position: absolute; border: none; background: none; cursor: pointer; padding: 0;
  color: transparent;   /* the chevron is drawn by the input's background, see above */
}
.combo-panel {
  position: absolute; z-index: 40; left: 0; right: 0; top: calc(100% + 4px);
  max-height: 280px; overflow-y: auto;
  background: var(--surface-1); border: 1px solid var(--border);
  border-radius: var(--radius-sm); box-shadow: var(--shadow-md);
  padding: 4px 0;
}
.combo-panel[hidden] { display: none; }
.combo-group {
  font-size: 11px; font-weight: 800; color: var(--text-muted);
  text-transform: uppercase; letter-spacing: .03em; padding: 8px 12px 4px;
}
.combo-group[hidden] { display: none; }
.combo-opt {
  display: block; width: 100%; text-align: left; border: none; background: none;
  font: inherit; font-size: 13.5px; color: var(--text-primary);
  padding: 7px 12px; cursor: pointer;
}
.combo-opt[hidden] { display: none; }
.combo-opt:hover, .combo-opt.is-active { background: var(--primary-light); }
.combo-opt-other { color: var(--primary-dark); font-weight: 700; }
.combo-empty { font-size: 12.5px; color: var(--text-muted); padding: 8px 12px; line-height: 1.5; }
.combo-empty[hidden] { display: none; }

/* "แยกมาจากตัวอย่างอื่น" / "พูลไม่เท่ากัน": a checkbox that reveals a second control,
   rather than a dropdown whose only option is "no". */
.split-check { display: inline-flex; align-items: center; gap: 6px; font-size: 12px;
  color: var(--text-secondary); font-weight: 400; margin: 0; cursor: pointer; }
.split-check input { margin: 0; flex-shrink: 0; }

/* The even pool case: one number and its unit, so the common layout is two keystrokes
   instead of the "12x5, 4, 3" notation. */

/* Symptom checkboxes on the public form, grouped (see DISEASE_SUSPECT_GROUPS): 30 of
   them flat was a wall of boxes, and the groups are the order a farmer looks at a pond
   rather than any laboratory taxonomy. */
.symptom-group { margin-bottom: 14px; }
.symptom-group:last-of-type { margin-bottom: 0; }
.symptom-group-title {
  font-size: 12px; font-weight: 800; color: var(--primary-dark);
  letter-spacing: .02em; margin-bottom: 7px;
}

/* ---- small utilities ----
   Each of these replaced the same inline declaration pasted into 8-17 different
   templates. Pasted styling is how pages drift apart: one copy gets adjusted and the
   rest silently do not. */
.hint { font-size: 11.5px; color: var(--text-muted); }
.muted { color: var(--text-muted); }
/* Wide content scrolls inside its own box; without it a table pushes the whole page
   sideways on a phone. */
.scroll-x { overflow-x: auto; }
/* The first section title on a page has no divider above it -- there is nothing to
   divide it from. */
.form-section-title.first { margin-top: 0; border-top: none; padding-top: 0; }
/* ---- colour-coded form sections ----
   Each kind of data wears one accent colour, so an officer can tell which box they are in at a
   glance instead of every section reading as the same grey panel. A section sets one colour on a
   CSS var; the heading wears it as a left bar + text colour, and a boxed section (a .panel) wears
   the same left border with a faint tint. Light theme only, which is all this app ships. */
.form-section-title.sec-title {
  border-left: 3px solid var(--sec, var(--primary)); padding-left: 10px;
  color: var(--sec-deep, var(--text-primary));
}
.panel.sec-box {
  border-left: 3px solid var(--sec, var(--primary));
  background: var(--sec-tint, var(--surface-1));
}
.sec-farm   { --sec: #0a7a48; --sec-deep: #0a7a48; --sec-tint: #eff8f2; }
.sec-animal { --sec: #0d9488; --sec-deep: #0f766e; --sec-tint: #e9f6f4; }
.sec-lab    { --sec: #4f46e5; --sec-deep: #4338ca; --sec-tint: #eeeffe; }
.sec-water  { --sec: #0284c7; --sec-deep: #036aa1; --sec-tint: #e9f4fc; }
.sec-histo  { --sec: #7c3aed; --sec-deep: #6d28d9; --sec-tint: #f3edfd; }
.sec-amr    { --sec: #d97706; --sec-deep: #b45309; --sec-tint: #fdf4e6; }
.other-detail-input { margin-top: 6px; }
/* A numeric column. Named for what it holds, not for the alignment -- so a table cannot
   half-adopt it. */
.num { text-align: right; }
/* A form field that takes the whole grid row (a long list, a note). */
.span-all { grid-column: 1 / -1; }
/* A button or link at row scale, inside a table cell. */
.btn-sm { padding: 4px 10px; font-size: 11.5px; }
/* A compact button in a toolbar row -- the same three declarations were pasted onto
   buttons in two files. */
.btn-compact { font-size: 12px; padding: 6px 12px; min-height: 0; }
/* The label beside a permission checkbox. Four copies across the two user forms, which is
   how one of them ends up a different weight from the others. */
.check-label { margin: 0; font-size: 12.5px; font-weight: 600; color: var(--text-secondary); }
.row-actions { display: flex; gap: 6px; flex-wrap: wrap; }

/* The version line. Deliberately the quietest thing on the page: it answers "what are you
   running?" for a support conversation without competing with the content above it. */
.build-line {
  margin: 28px 0 0; text-align: right;
  font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums;
}
/* The precise git-describe string next to the human version -- present for support, dimmed so
   it does not compete with the version number a person actually reads. */
.build-rev { font-family: var(--mono, ui-monospace, monospace); opacity: 0.8; }

/* ============================================================
   แผนที่วางแผนเฝ้าระวัง (ลับ, admin_bigboss) — surveillance_map.html
   Province-level bubble map + planning table, rendered client-side
   from per-farm records. Offline: uses static/th_provinces.json only.
   ============================================================ */
.nav-item-secret { color: var(--accent-deep); font-weight: 600; }
.secret-tag {
  display: inline-block; margin-left: 8px; padding: 2px 9px; border-radius: 999px;
  font-size: 12px; font-weight: 600; vertical-align: middle;
  background: #fdecef; color: var(--critical); border: 1px solid rgba(220,53,69,0.28);
}

/* summary stat strip -- equal-width responsive cells so nothing stretches oddly */
.smap-summary {
  display: grid; grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 12px; margin: 6px 0 16px;
}
.smap-stat {
  display: flex; flex-direction: column; gap: 3px; padding: 12px 16px; min-width: 0;
  background: var(--accent-light); border: 1px solid var(--border); border-radius: var(--radius-md);
}
.smap-stat-num { font-size: 26px; font-weight: 700; color: var(--accent-deep); font-variant-numeric: tabular-nums; line-height: 1.1; }
.smap-stat-lab { font-size: 12.5px; color: var(--text-secondary); line-height: 1.35; }
.smap-stat-filter { justify-content: center; background: var(--surface-2); }
.smap-stat-val { font-size: 15px; font-weight: 600; color: var(--text-primary); }

/* filter bar */
.smap-filters { margin-bottom: 16px; display: flex; flex-direction: column; gap: 12px; }
.smap-filter-row { display: flex; align-items: center; gap: 10px; flex-wrap: wrap; }
.smap-filter-label { font-size: 13px; font-weight: 600; color: var(--text-secondary); min-width: 78px; }
.smap-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.smap-chip {
  padding: 5px 12px; border-radius: 999px; cursor: pointer; font-size: 13px;
  background: var(--surface-2); border: 1px solid var(--border); color: var(--text-secondary);
  transition: background .12s, color .12s, border-color .12s;
}
.smap-chip:hover { border-color: var(--accent); color: var(--accent-deep); }
.smap-chip.active { background: var(--accent); border-color: var(--accent); color: #fff; }
.smap-select {
  padding: 7px 10px; border-radius: var(--radius-sm, 8px); border: 1px solid var(--border);
  background: var(--surface-1); color: var(--text-primary); font-size: 13px; min-width: 260px; max-width: 100%;
}
.btn-sm { padding: 5px 12px; font-size: 13px; }

/* two-column layout: map + table */
.smap-layout { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(0, 1fr); gap: 16px; align-items: start; }
@media (max-width: 900px) { .smap-layout { grid-template-columns: 1fr; } }
.smap-mapwrap, .smap-tablewrap { padding: 16px 18px; }
.smap-map-head, .smap-table-head { display: flex; justify-content: space-between; align-items: baseline; gap: 10px; margin-bottom: 10px; flex-wrap: wrap; }
.smap-map-head b, .smap-table-head b { font-size: 15px; }
.smap-legend, .smap-hint { font-size: 12px; color: var(--text-muted); }
.smap-legend-dot { display: inline-block; width: 10px; height: 10px; border-radius: 50%; margin-right: 5px; vertical-align: middle;
  background: var(--accent); opacity: 0.55; border: 1px solid var(--accent-deep); }

.smap-map { position: relative; width: 100%; min-height: 300px; }
.smap-map-loading { position: absolute; inset: 0; display: flex; align-items: center; justify-content: center;
  color: var(--text-muted); font-size: 14px; }
.smap-svg { width: 100%; height: auto; max-height: 76vh; display: block; }
.smap-prov-shape { fill: var(--surface-2); stroke: var(--baseline); stroke-width: 0.7; vector-effect: non-scaling-stroke; }
.smap-prov-shape.sel { fill: var(--accent-light); stroke: var(--accent); stroke-width: 1.4; }
.smap-bub-c { fill: var(--accent); fill-opacity: 0.42; stroke: var(--accent-deep); stroke-width: 1; vector-effect: non-scaling-stroke; }
.smap-bubble:hover .smap-bub-c { fill-opacity: 0.62; }
.smap-bubble.sel .smap-bub-c { fill: var(--serious); fill-opacity: 0.6; stroke: #b4441f; }
.smap-bub-t { fill: #08302b; font-size: 15px; font-weight: 700; text-anchor: middle; pointer-events: none;
  paint-order: stroke; stroke: #fff; stroke-width: 3px; stroke-linejoin: round; }
.smap-caveat { margin: 10px 0 0; font-size: 11.5px; color: var(--text-muted); line-height: 1.5; }

/* planning table */
.smap-table-scroll { max-height: 76vh; overflow: auto; }
.smap-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.smap-table thead th { position: sticky; top: 0; background: var(--surface-2); z-index: 1;
  text-align: left; padding: 8px 10px; font-size: 12.5px; color: var(--text-secondary); border-bottom: 1px solid var(--border); }
.smap-table th.num, .smap-table td.num { text-align: right; white-space: nowrap; }
.smap-table td { padding: 7px 10px; border-bottom: 1px solid var(--grid); vertical-align: middle; }
.smap-row { cursor: pointer; }
.smap-row:hover { background: var(--accent-light); }
.smap-row.active { background: var(--accent-light); box-shadow: inset 3px 0 0 var(--accent); }
.smap-prov { font-weight: 600; }
.smap-topsp { color: var(--text-secondary); font-size: 12.5px; }
.smap-empty { text-align: center; color: var(--text-muted); padding: 20px; }
/* count cell doubles as a mini bar chart */
.smap-table td.num { position: relative; }
.smap-bar { position: absolute; right: 42px; top: 50%; transform: translateY(-50%);
  height: 8px; background: var(--accent); opacity: 0.28; border-radius: 3px; max-width: 90px; }
.smap-bar-n { position: relative; font-variant-numeric: tabular-nums; font-weight: 600; }

/* drill-down farm list */
.smap-farms { margin-top: 16px; padding: 16px 18px; }
.smap-farms-head { display: flex; justify-content: space-between; align-items: center; margin-bottom: 8px; }
.smap-farms-head b { font-size: 15px; }
.smap-farms-scroll { max-height: 420px; }
.smap-sp-tag { display: inline-block; padding: 1px 8px; margin: 1px 2px; border-radius: 999px;
  background: var(--accent-light); color: var(--accent-deep); font-size: 12px; border: 1px solid var(--border); }
.smap-muted { color: var(--text-muted); }
.smap-reg { font-variant-numeric: tabular-nums; white-space: nowrap; font-weight: 600; color: var(--accent-deep); }
.smap-select-year { min-width: 130px; }
/* สอ. (export) vs ทบ. registration badge in the drill-down farm list */
.smap-badge { display: inline-block; padding: 0 6px; border-radius: 4px; font-size: 11px; font-weight: 700;
  vertical-align: middle; line-height: 17px; }
.smap-badge-exp { background: #fff3e6; color: #b4571a; border: 1px solid rgba(180,87,26,0.3); }
.smap-badge-pending { background: #fff6e6; color: #9a6800; border: 1px dashed rgba(224,163,54,0.55); }
.smap-badge-tb { background: var(--surface-2); color: var(--text-muted); border: 1px solid var(--border); }
.smap-export-toggle { display: inline-flex; align-items: center; gap: 6px; font-size: 13px; color: var(--text-secondary);
  cursor: pointer; user-select: none; }
.smap-export-toggle input { width: 15px; height: 15px; accent-color: var(--serious); cursor: pointer; }
.smap-stat-exp { background: #fff3e6; }
.smap-stat-exp .smap-stat-num { color: #b4571a; }
.smap-stat-pending { background: var(--surface-2); }

/* farm register: search box + pager */
.farm-search { display: flex; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
.farm-search input[type="search"] {
  flex: 1 1 320px; min-width: 0; height: 40px; padding: 0 12px;
  border: 1px solid var(--border); border-radius: 8px; font: inherit; font-size: 14px;
  background: var(--surface); color: var(--text-primary);
}
.farm-search input[type="search"]:focus { outline: 2px solid var(--accent); outline-offset: 1px; border-color: var(--accent); }
.pager { display: flex; align-items: center; justify-content: center; gap: 14px; margin: 16px 0 4px; }
.pager-info { font-size: 13px; color: var(--text-secondary); font-variant-numeric: tabular-nums; }

/* farm detail: export (สอ.) registration verification block */
.export-verify { padding: 16px 18px; }
.ev-none { margin: 0; color: var(--text-secondary); line-height: 1.6; }
.ev-status { display: flex; align-items: center; flex-wrap: wrap; gap: 12px 16px; margin-bottom: 4px; }
.ev-num { display: flex; align-items: baseline; gap: 8px; }
.ev-num label { font-size: 12.5px; color: var(--text-muted); }
.ev-num span { font-size: 17px; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--text-primary); }
.ev-badge { display: inline-block; padding: 2px 11px; border-radius: 999px; font-size: 13px; font-weight: 600; }
.ev-ok { background: #e6f6ee; color: var(--good-deep); border: 1px solid rgba(15,158,94,0.3); }
.ev-pending { background: #fff6e6; color: #9a6800; border: 1px solid rgba(224,163,54,0.4); }
.ev-meta { width: 100%; font-size: 12.5px; color: var(--text-secondary); line-height: 1.6; }
.ev-form { margin-top: 14px; padding-top: 14px; border-top: 1px solid var(--grid); }
.ev-form-title { font-weight: 600; margin-bottom: 10px; }
.ev-dates { display: flex; gap: 14px; flex-wrap: wrap; max-width: 460px; }
.ev-dates .form-field { flex: 1 1 180px; }
.ev-actions { display: flex; gap: 10px; margin-top: 12px; flex-wrap: wrap; }
.ev-revoke { color: var(--critical); }
.ev-hint { margin: 10px 0 0; font-size: 12px; color: var(--text-muted); line-height: 1.6; }

/* ============================================================
   Executive analytics dashboards (.xdash)
   The secret production / water dashboards. A titled band, a
   scorecard, systematic section ticks, and ONE hero -- the
   correlation view that is the whole point -- so a page of
   external reference data reads with the weight of the main
   dashboard. Scoped to .xdash; reuses the app tokens.
   ============================================================ */
.xdash .page-head, .xdash > header { margin-bottom: 0; }
.xd-head { margin-bottom: 22px; padding-bottom: 18px; border-bottom: 1px solid var(--border); }
.xd-eyebrow {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11px; font-weight: 800; letter-spacing: .085em; text-transform: uppercase;
  color: var(--primary); background: var(--primary-light);
  border-radius: 999px; padding: 4px 12px; margin-bottom: 11px;
}
.xd-head-row { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; flex-wrap: wrap; }
.xd-head h1 { font-size: 26px; letter-spacing: -.015em; margin: 0; line-height: 1.15; }
.xd-lead { font-size: 13.5px; color: var(--text-secondary); margin: 9px 0 0; max-width: 82ch; line-height: 1.6; }
.xd-lead b { color: var(--text-primary); }

.xd-scorecard { display: grid; grid-template-columns: repeat(auto-fit, minmax(158px, 1fr)); gap: 14px; margin: 20px 0 26px; }
.xd-stat {
  background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-md);
  padding: 15px 18px; box-shadow: var(--shadow-sm);
  transition: box-shadow .15s var(--ease-out), transform .15s var(--ease-out);
}
.xd-stat:hover { box-shadow: var(--shadow-md); transform: translateY(-1px); }
.xd-stat .k { font-size: 10.5px; font-weight: 700; letter-spacing: .045em; text-transform: uppercase; color: var(--text-muted); margin-bottom: 8px; }
.xd-stat .v { font-size: 27px; font-weight: 800; letter-spacing: -.02em; line-height: 1; color: var(--text-primary); font-variant-numeric: tabular-nums; }
.xd-stat .v small { font-size: 12.5px; font-weight: 600; color: var(--text-secondary); letter-spacing: 0; }
.xd-stat.is-accent { border-color: #cfe0fb; }
.xd-stat.is-accent .v { color: var(--primary); }

.xdash section { margin-bottom: 26px; }
.xdash section > h2 { position: relative; padding-left: 15px; font-size: 17px; letter-spacing: -.01em; margin-top: 0; }
.xdash section > h2::before { content: ""; position: absolute; left: 0; top: 2px; bottom: 2px; width: 4px; border-radius: 2px; background: var(--primary); opacity: .85; }

/* the hero: the correlation, given a tinted frame and a badge */
.xdash section.xd-hero {
  background: linear-gradient(180deg, #fbfdff 0%, #f1f6fe 100%);
  border: 1px solid #d7e6fb; border-radius: var(--radius-lg);
  padding: 22px 24px 24px; box-shadow: 0 2px 18px rgba(37,99,235,.06);
}
.xdash section.xd-hero > h2 { padding-left: 0; }
.xdash section.xd-hero > h2::before { display: none; }
.xd-hero-badge {
  display: inline-flex; align-items: center; gap: 7px;
  font-size: 11px; font-weight: 800; letter-spacing: .07em; text-transform: uppercase;
  color: var(--primary); background: rgba(37,99,235,.09);
  border-radius: 999px; padding: 4px 12px; margin-bottom: 11px;
}
.xd-corr-figure { display: inline-flex; align-items: baseline; gap: 8px; font-variant-numeric: tabular-nums; }
.xd-corr-figure .r { font-size: 30px; font-weight: 800; letter-spacing: -.02em; color: var(--primary); line-height: 1; }
.xd-corr-figure .lbl { font-size: 13px; color: var(--text-secondary); }

.xdash .xd-controls { display: flex; gap: 22px; flex-wrap: wrap; align-items: flex-end; }
.xdash .xd-controls .filter-group { min-width: 170px; }
/* .filter-group / .fg-title are defined in the dashboard shell, which these pages do
   not use (they extend base.html) -- so the label rendered inline beside its select.
   Restated here, scoped, so a control reads as label-over-input like everywhere else. */
.xdash .filter-group { display: flex; flex-direction: column; }
.xdash .filter-group label.fg-title {
  font-size: 11px; font-weight: 700; letter-spacing: .045em; text-transform: uppercase;
  color: var(--text-muted); margin-bottom: 7px;
}
.xdash .filter-group select {
  appearance: none; -webkit-appearance: none;
  height: 38px; width: 100%;
  border: 1px solid var(--border); border-radius: var(--radius-sm);
  background-color: var(--surface-1);
  background-image: url("data:image/svg+xml;charset=UTF-8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%23667085' stroke-width='1.7' stroke-linecap='round' stroke-linejoin='round' fill='none'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 11px center;
  padding: 0 32px 0 11px; font-size: 13px; font-family: inherit; color: var(--text-primary);
  cursor: pointer; box-shadow: var(--shadow-sm);
}
.xdash .filter-group select:hover { border-color: var(--text-muted); }
.xdash .filter-group select:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; box-shadow: var(--ring-primary); }

/* A tile whose value is a word or a date, not a figure -- it must not shout like one. */
.xd-stat.is-text .v { font-size: 17px; line-height: 1.25; letter-spacing: 0; }

/* ranked correlation list: the page reading the data for you */
.wq-rank { display: flex; flex-direction: column; gap: 6px; margin: 4px 0 12px; }
.wq-rank-row {
  display: grid; grid-template-columns: minmax(120px, 1.15fr) minmax(60px, 1fr) 52px minmax(170px, 1.5fr);
  align-items: center; gap: 12px; width: 100%; text-align: left;
  background: var(--surface-1); border: 1px solid var(--border); border-radius: var(--radius-sm);
  padding: 9px 13px; font-family: inherit; font-size: 12.5px; color: var(--text-secondary);
  cursor: pointer; transition: border-color .12s, box-shadow .12s, transform .12s;
}
.wq-rank-row:hover { border-color: var(--primary); box-shadow: var(--shadow-sm); transform: translateX(2px); }
.wq-rank-row:focus-visible { outline: 2px solid var(--primary); outline-offset: 1px; }
.wq-rank-row.is-on { border-color: var(--primary); background: var(--primary-light); }
.wq-rank-row.is-top { border-color: #cfe0fb; }
.wq-rank-name { font-weight: 600; color: var(--text-primary); }
.wq-rank-crown {
  font-size: 10px; font-weight: 800; letter-spacing: .04em; color: var(--primary);
  background: rgba(37,99,235,.10); border-radius: 999px; padding: 2px 7px; margin-right: 5px;
}
.wq-rank-bar { height: 7px; border-radius: 999px; background: var(--grid); overflow: hidden; }
.wq-rank-bar i { display: block; height: 100%; border-radius: 999px; }
.wq-rank-r { text-align: right; font-weight: 800; font-variant-numeric: tabular-nums; font-size: 13.5px; }
.wq-rank-txt { color: var(--text-muted); font-size: 11.5px; }
.wq-caveat {
  font-size: 11.5px; color: var(--text-secondary); line-height: 1.6;
  background: rgba(224,163,54,.09); border: 1px solid rgba(224,163,54,.32);
  border-radius: var(--radius-sm); padding: 9px 12px;
}
@media (max-width: 760px) {
  .wq-rank-row { grid-template-columns: 1fr 46px; gap: 6px 10px; }
  .wq-rank-bar { grid-column: 1 / -1; }
  .wq-rank-txt { grid-column: 1 / -1; }
}

/* colour scale beside the water map's parameter picker */
.wq-scale { min-width: 190px; }
.wq-scale-label { font-size: 11.5px; font-weight: 700; color: var(--text-secondary); margin-bottom: 6px; }
.wq-scale-bar { height: 10px; border-radius: 999px; border: 1px solid var(--border); }
.wq-scale-ends {
  display: flex; justify-content: space-between; margin-top: 4px;
  font-size: 11px; color: var(--text-muted); font-variant-numeric: tabular-nums;
}

@media (max-width: 700px) {
  .xd-head h1 { font-size: 22px; }
  .xdash section.xd-hero { padding: 16px 15px 18px; border-radius: var(--radius-md); }
}

/* Chart tooltip, shared by every page that draws a chart. It was pasted inline into each
   secret dashboard with literal colours, which meant two copies to keep in step and two
   places the palette could not reach. dashboard_shell.html defines its own copy for the main
   dashboard, which does not load this file. */
.chart-tooltip {
  position: fixed; pointer-events: none; z-index: 50;
  background: var(--text-primary); color: #fff;
  font-size: 12px; line-height: 1.5; padding: 6px 9px; border-radius: 6px;
  box-shadow: var(--shadow-md);
  opacity: 0; transition: opacity .08s ease;
}
.chart-tooltip.show { opacity: 1; }

/* Visible to a screen reader, not on screen -- for labels that would be redundant to sighted
   users but leave a control unnamed without them. */
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* Investigation attachment preview: an in-page modal (an iframe) that renders an
   image or PDF on demand. Markup + behaviour live in templates/investigations.html;
   the classes are here so component styling stays in one place. */
.inv-preview-btn { font-size: 12px; margin-left: 6px; padding: 2px 8px; }
.inv-modal {
  position: fixed; inset: 0; z-index: 1000; display: none;
  background: rgba(15, 23, 42, 0.66); padding: 3vh 3vw;
}
.inv-modal.open { display: block; }
.inv-modal-box {
  background: var(--surface, #fff); border-radius: 12px; height: 94vh;
  max-width: 1000px; margin: 0 auto; display: flex; flex-direction: column;
  box-shadow: 0 18px 50px rgba(0, 0, 0, 0.4); overflow: hidden;
}
.inv-modal-head {
  display: flex; align-items: center; gap: 12px; padding: 10px 14px;
  border-bottom: 1px solid var(--border, #e2e8f0);
}
.inv-modal-name {
  font-weight: 600; overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; flex: 1;
}
.inv-modal-frame { flex: 1; width: 100%; border: 0; background: #f1f5f9; }
