/* ------------------------------------------------------------------ *\
   Tree Survey Register
   Mobile-first. The public tree page is the priority surface.
\* ------------------------------------------------------------------ */

:root {
  --green-900: #14361f;
  --green-700: #1f6b3a;
  --green-600: #2a8049;
  --green-100: #e8f3ec;
  --ink: #16211b;
  --ink-soft: #5b6b61;
  --line: #dfe5e0;
  --bg: #f6f7f8;              /* neutral, not tinted: green is an accent here */
  --surface: #ffffff;
  --danger: #b3261e;
  --warn-bg: #fff4e0;
  --warn-line: #e9c37a;
  --ok-bg: #e7f5ec;
  --radius: 14px;
  --shadow: 0 1px 2px rgba(20, 54, 31, .06), 0 6px 20px rgba(20, 54, 31, .06);
}

/* The operating system's preference - unless the reader has overruled it
   with the switch in the menu, which is what the :not() is for. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --green-700: #4fae74;
    --green-600: #58bd7f;
    --green-100: #16301f;
    --ink: #e8efe9;
    --ink-soft: #9fb0a5;
    --line: #2a3630;
    --bg: #101612;
    --surface: #17201b;
    --warn-bg: #2f2717;
    --warn-line: #5c4a24;
    --ok-bg: #15301f;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 6px 20px rgba(0, 0, 0, .25);
  }
}

/* The same, chosen rather than guessed. */
:root[data-theme="dark"] {
    --green-700: #4fae74;
    --green-600: #58bd7f;
    --green-100: #16301f;
    --ink: #e8efe9;
    --ink-soft: #9fb0a5;
    --line: #2a3630;
    --bg: #101612;
    --surface: #17201b;
    --warn-bg: #2f2717;
    --warn-line: #5c4a24;
    --ok-bg: #15301f;
    --shadow: 0 1px 2px rgba(0, 0, 0, .3), 0 6px 20px rgba(0, 0, 0, .25);
  }


* { box-sizing: border-box; }

/**
 * The browser's own [hidden] rule is display:none, but any class that sets a
 * display (.btn is inline-flex) overrides it. This makes `hidden` reliable.
 */
[hidden] { display: none !important; }

html {
  -webkit-text-size-adjust: 100%;
  /**
   * Always reserve room for the scrollbar. Without this, a page that is short
   * in one language but long in another gains/loses the scrollbar, and the
   * centred content jumps sideways by half its width when switching.
   */
  scrollbar-gutter: stable;
}

/**
 * Page background.
 *
 * Three layers: a mesh of coloured light, a repeating leaf motif, and a large
 * tree illustration anchored to the bottom-right corner. All of it sits behind
 * solid cards, so no body text or table row is ever read against it, and the
 * whole lot is dropped when printing.
 */
body {
  margin: 0;
  color: var(--ink);
  font: 16px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  overflow-wrap: break-word;
  position: relative;

  background-color: var(--bg);
  background-image:
    radial-gradient(760px 520px at 84% -8%, var(--mesh-1), transparent 62%),
    radial-gradient(680px 520px at 12% 4%, var(--mesh-2), transparent 60%),
    radial-gradient(820px 620px at 96% 78%, var(--mesh-3), transparent 62%),
    radial-gradient(760px 560px at -6% 92%, var(--mesh-4), transparent 60%),
    var(--leaf-pattern);
  background-repeat: no-repeat, no-repeat, no-repeat, no-repeat, repeat;
  background-size: auto, auto, auto, auto, 300px 300px;
  /* Stays put while long tables scroll past it. */
  background-attachment: fixed, fixed, fixed, fixed, fixed;
}

/* The tree itself: decorative, so it is a pseudo-element and never in the DOM. */
body::before {
  content: '';
  position: fixed;
  right: -40px;
  bottom: -30px;
  width: min(46vw, 560px);
  height: min(46vw, 560px);
  background: var(--tree-art) no-repeat right bottom / contain;
  opacity: var(--art-opacity);
  pointer-events: none;
  z-index: -1;
}

:root {
  /* Emerald, teal, warm amber and sage - enough variety to feel alive. */
  --mesh-1: rgba(72, 186, 122, .52);
  --mesh-2: rgba(96, 200, 195, .42);
  --mesh-3: rgba(240, 194, 104, .46);
  --mesh-4: rgba(31, 107, 58, .34);
  --art-opacity: .17;

  --leaf-pattern: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300' viewBox='0 0 300 300'%3E%3Cg fill='none' stroke='%231f6b3a' stroke-opacity='.09' stroke-width='1.4' stroke-linecap='round'%3E%3Cpath d='M48 30c-16 13-16 37 0 50 16-13 16-37 0-50z'/%3E%3Cpath d='M48 35v40'/%3E%3Cpath d='M212 78c-18 10-23 32-11 48 18-10 23-32 11-48z'/%3E%3Cpath d='M209 83l-7 41'/%3E%3Cpath d='M120 176c-16 13-16 37 0 50 16-13 16-37 0-50z'/%3E%3Cpath d='M120 181v40'/%3E%3Cpath d='M254 214c-13 11-13 30 0 41 13-11 13-30 0-41z'/%3E%3Cpath d='M254 218v34'/%3E%3C/g%3E%3C/svg%3E");

  /* A stylised broadleaf: trunk, a few boughs, layered canopy. */
  --tree-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520' viewBox='0 0 520 520'%3E%3Cg fill='none' stroke='%231f6b3a' stroke-width='7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M260 500V300'/%3E%3Cpath d='M260 360l-62-58M260 330l64-60M260 400l-46-42M260 300l40-40'/%3E%3C/g%3E%3Cg fill='%231f6b3a' fill-opacity='.5'%3E%3Ccircle cx='260' cy='210' r='86'/%3E%3Ccircle cx='168' cy='252' r='66'/%3E%3Ccircle cx='352' cy='250' r='70'/%3E%3Ccircle cx='206' cy='158' r='58'/%3E%3Ccircle cx='318' cy='160' r='54'/%3E%3Ccircle cx='260' cy='120' r='48'/%3E%3C/g%3E%3C/svg%3E");
}

/* The operating system's preference - unless the reader has overruled it
   with the switch in the menu, which is what the :not() is for. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --mesh-1: rgba(79, 174, 116, .30);
    --mesh-2: rgba(64, 158, 152, .26);
    --mesh-3: rgba(196, 150, 74, .24);
    --mesh-4: rgba(42, 128, 73, .32);
    --art-opacity: .20;

    --leaf-pattern: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300' viewBox='0 0 300 300'%3E%3Cg fill='none' stroke='%234fae74' stroke-opacity='.11' stroke-width='1.4' stroke-linecap='round'%3E%3Cpath d='M48 30c-16 13-16 37 0 50 16-13 16-37 0-50z'/%3E%3Cpath d='M48 35v40'/%3E%3Cpath d='M212 78c-18 10-23 32-11 48 18-10 23-32 11-48z'/%3E%3Cpath d='M209 83l-7 41'/%3E%3Cpath d='M120 176c-16 13-16 37 0 50 16-13 16-37 0-50z'/%3E%3Cpath d='M120 181v40'/%3E%3Cpath d='M254 214c-13 11-13 30 0 41 13-11 13-30 0-41z'/%3E%3Cpath d='M254 218v34'/%3E%3C/g%3E%3C/svg%3E");

    --tree-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520' viewBox='0 0 520 520'%3E%3Cg fill='none' stroke='%234fae74' stroke-width='7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M260 500V300'/%3E%3Cpath d='M260 360l-62-58M260 330l64-60M260 400l-46-42M260 300l40-40'/%3E%3C/g%3E%3Cg fill='%234fae74' fill-opacity='.5'%3E%3Ccircle cx='260' cy='210' r='86'/%3E%3Ccircle cx='168' cy='252' r='66'/%3E%3Ccircle cx='352' cy='250' r='70'/%3E%3Ccircle cx='206' cy='158' r='58'/%3E%3Ccircle cx='318' cy='160' r='54'/%3E%3Ccircle cx='260' cy='120' r='48'/%3E%3C/g%3E%3C/svg%3E");
  }
}

/* The same, chosen rather than guessed. */
:root[data-theme="dark"] {
    --mesh-1: rgba(79, 174, 116, .30);
    --mesh-2: rgba(64, 158, 152, .26);
    --mesh-3: rgba(196, 150, 74, .24);
    --mesh-4: rgba(42, 128, 73, .32);
    --art-opacity: .20;

    --leaf-pattern: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300' viewBox='0 0 300 300'%3E%3Cg fill='none' stroke='%234fae74' stroke-opacity='.11' stroke-width='1.4' stroke-linecap='round'%3E%3Cpath d='M48 30c-16 13-16 37 0 50 16-13 16-37 0-50z'/%3E%3Cpath d='M48 35v40'/%3E%3Cpath d='M212 78c-18 10-23 32-11 48 18-10 23-32 11-48z'/%3E%3Cpath d='M209 83l-7 41'/%3E%3Cpath d='M120 176c-16 13-16 37 0 50 16-13 16-37 0-50z'/%3E%3Cpath d='M120 181v40'/%3E%3Cpath d='M254 214c-13 11-13 30 0 41 13-11 13-30 0-41z'/%3E%3Cpath d='M254 218v34'/%3E%3C/g%3E%3C/svg%3E");

    --tree-art: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='520' height='520' viewBox='0 0 520 520'%3E%3Cg fill='none' stroke='%234fae74' stroke-width='7' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M260 500V300'/%3E%3Cpath d='M260 360l-62-58M260 330l64-60M260 400l-46-42M260 300l40-40'/%3E%3C/g%3E%3Cg fill='%234fae74' fill-opacity='.5'%3E%3Ccircle cx='260' cy='210' r='86'/%3E%3Ccircle cx='168' cy='252' r='66'/%3E%3Ccircle cx='352' cy='250' r='70'/%3E%3Ccircle cx='206' cy='158' r='58'/%3E%3Ccircle cx='318' cy='160' r='54'/%3E%3Ccircle cx='260' cy='120' r='48'/%3E%3C/g%3E%3C/svg%3E");
  }


/* On a phone the illustration would sit under the content, so retire it. */
@media (max-width: 700px) {
  body::before { display: none; }
}

h1, h2, h3 { line-height: 1.25; margin: 0; }

a { color: var(--green-700); }

code {
  font-family: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  font-size: .9em;
  background: var(--green-100);
  padding: .1em .35em;
  border-radius: 4px;
}

.muted { color: var(--ink-soft); }
.mono { font-family: ui-monospace, Menlo, Consolas, monospace; }
.inline { display: inline; }

.card {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 1rem 1.1rem;
}

/* ---------------------------------------------------------------- Buttons */

.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  padding: .7rem 1.05rem;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--surface);
  color: var(--ink);
  font: inherit;
  font-weight: 550;
  text-decoration: none;
  cursor: pointer;
  min-height: 44px;           /* comfortable touch target */
  transition: background .12s, border-color .12s;
}
.btn:hover { border-color: var(--green-600); }
.btn--primary { background: var(--green-700); border-color: var(--green-700); color: #fff; }
.btn--primary:hover { background: var(--green-600); border-color: var(--green-600); }
.btn--ghost { background: transparent; }
.btn--danger { background: var(--danger); border-color: var(--danger); color: #fff; }
.btn--small { padding: .45rem .8rem; min-height: 38px; font-size: .9rem; }
.btn--block { width: 100%; }

/* An icon standing on its own. The label stays in the markup for screen
   readers and comes back as a tooltip on hover; only the ink is dropped.
   `gap: 0` matters: without it the button still reserves the space between
   icon and label, and the icon sits visibly off-centre. */
.btn--icon { padding: 0; width: 38px; gap: 0; }
.btn--icon .btn-label,
.is-iconified .btn-label {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}
.btn.is-disabled { opacity: .45; pointer-events: none; }

/* ---------------------------------------------------------------- Alerts */

.alert {
  margin: 0 0 1rem;
  padding: .75rem 1rem;
  border-radius: 10px;
  border: 1px solid var(--line);
  background: var(--surface);
}
.alert--error { background: #fdecea; border-color: #f0b4ae; color: #7a1d17; }
.alert--ok { background: var(--ok-bg); border-color: var(--green-600); }
.alert--warn { background: var(--warn-bg); border-color: var(--warn-line); }

/* The operating system's preference - unless the reader has overruled it
   with the switch in the menu, which is what the :not() is for. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .alert--error { background: #3a1a17; border-color: #7a332c; color: #f5c8c3; }
}

/* The same, chosen rather than guessed. */
[data-theme="dark"] .alert--error { background: #3a1a17; border-color: #7a332c; color: #f5c8c3; }


.hint { font-size: .85rem; color: var(--ink-soft); margin: .5rem 0 0; }
/* Live filtering: dim the results briefly while the new set is fetched. */
#results {
  display: flex;
  flex-direction: column;
  gap: .55rem;
  transition: opacity .12s ease;
}
#results.is-loading { opacity: .55; }
.results-count { margin: 0; }
.table-hint { margin: 0 0 0 .15rem; }

/* ---------------------------------------------------------- Language toggle */

.lang-toggle {
  display: inline-flex;
  align-items: stretch;
  border: 1px solid var(--line);
  border-radius: 999px;
  overflow: hidden;
  background: var(--surface);
  font-size: .82rem;
  line-height: 1;
  flex: none;
}

/* Visible to screen readers only - the flags carry no visible label. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.lang-option {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  /* Sized to sit beside the two icon buttons rather than outweigh them: the
     same height, but narrower halves and lighter type, so the header reads as
     one row of peers instead of a pair of squares next to a slab. */
  padding: .45rem .38rem;
  min-height: 38px;
  color: var(--ink-soft);
  text-decoration: none;
  transition: background .12s, opacity .12s;
}

.lang-code {
  font-size: .65rem;
  font-weight: 600;
  letter-spacing: .03em;
}
.lang-option + .lang-option { border-left: 1px solid var(--line); }
.lang-option:hover { background: var(--green-100); color: var(--green-700); }
.lang-option.is-active {
  background: var(--green-700);
  color: #fff;
  cursor: default;
}

/* Over the photo on the public page, where there is no header bar. */
.lang-toggle--floating {
  position: absolute;
  top: .7rem;
  right: .7rem;
  z-index: 5;
  background: rgba(255, 255, 255, .94);
  backdrop-filter: blur(6px);
  border-color: rgba(0, 0, 0, .12);
  box-shadow: 0 1px 6px rgba(0, 0, 0, .18);
}
/* The operating system's preference - unless the reader has overruled it
   with the switch in the menu, which is what the :not() is for. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .lang-toggle--floating {
    background: rgba(23, 32, 27, .94);
    border-color: rgba(255, 255, 255, .16);
  }
}

/* The same, chosen rather than guessed. */
[data-theme="dark"] .lang-toggle--floating {
    background: rgba(23, 32, 27, .94);
    border-color: rgba(255, 255, 255, .16);
  }


.login-lang, .notfound-lang {
  display: flex;
  justify-content: center;
  margin-bottom: .9rem;
}
.notfound-lang { margin-top: 1rem; }

/* ================================================================ *
   PUBLIC TREE PAGE
 * ================================================================ */

.tree-page {
  max-width: 640px;
  margin: 0 auto;
  padding: 0 0 3rem;
  display: flex;
  flex-direction: column;
  gap: .85rem;
}

/* Shown only to a signed-in admin, who reaches this page from the tree list. */
.back-bar {
  display: block;
  padding: .8rem 1rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  color: var(--green-700);
  font-weight: 600;
  font-size: .92rem;
  text-decoration: none;
}
.back-bar:hover { background: var(--green-100); }

.tree-hero {
  position: relative;              /* anchors the floating language toggle */
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  box-shadow: var(--shadow);
  margin-bottom: .25rem;
}

.tree-photo {
  display: block;
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  background: var(--green-100);
}

.tree-photo--empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .55rem;
  color: var(--ink-soft);
  font-size: .95rem;
  border-bottom: 1px solid var(--line);
}

.tree-hero-body { padding: 1.1rem 1.15rem 1.25rem; }

.tree-id {
  margin: 0 0 .25rem;
  font-size: .8rem;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.tree-id strong { color: var(--ink); font-family: ui-monospace, Menlo, Consolas, monospace; letter-spacing: .02em; }

.tree-hero h1 { font-size: 1.7rem; margin: 0 0 .15rem; }

.tree-botanical {
  margin: 0;
  font-style: italic;
  color: var(--ink-soft);
  font-size: 1.02rem;
}

.chips { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .85rem; }

.chip {
  display: inline-block;
  padding: .3rem .7rem;
  border-radius: 999px;
  background: var(--green-100);
  color: var(--green-700);
  font-size: .82rem;
  font-weight: 600;
  border: 1px solid transparent;
}
.chip--condition[data-value*="poor"],
.chip--condition[data-value*="dead"],
.chip--condition[data-value*="dying"] { background: #fdecea; color: #a3261d; }
.chip--condition[data-value*="fair"],
.chip--condition[data-value*="moderate"] { background: var(--warn-bg); color: #8a5a12; }

/* The operating system's preference - unless the reader has overruled it
   with the switch in the menu, which is what the :not() is for. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .chip--condition[data-value*="poor"],
  :root:not([data-theme="light"]) .chip--condition[data-value*="dead"],
  :root:not([data-theme="light"]) .chip--condition[data-value*="dying"] { background: #3a1a17; color: #f0b0a8; }
  :root:not([data-theme="light"]) .chip--condition[data-value*="fair"],
  :root:not([data-theme="light"]) .chip--condition[data-value*="moderate"] { background: #33291a; color: #e0bd7e; }
}

/* The same, chosen rather than guessed. */
[data-theme="dark"] .chip--condition[data-value*="poor"],
[data-theme="dark"] .chip--condition[data-value*="dead"],
[data-theme="dark"] .chip--condition[data-value*="dying"] { background: #3a1a17; color: #f0b0a8; }
[data-theme="dark"] .chip--condition[data-value*="fair"],
[data-theme="dark"] .chip--condition[data-value*="moderate"] { background: #33291a; color: #e0bd7e; }


.tree-page .card { margin: 0 .6rem; }

.tree-page .card h2 {
  font-size: .78rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--green-700);
  margin-bottom: .6rem;
}

.detail-list { margin: 0; }

.detail-row {
  display: flex;
  justify-content: space-between;
  align-items: baseline;
  gap: 1.25rem;
  padding: .5rem 0;
  border-top: 1px solid var(--line);
}
.detail-row:first-child { border-top: 0; padding-top: 0; }

.detail-row dt { color: var(--ink-soft); flex: 0 1 auto; font-size: .93rem; }
.detail-row dd { margin: 0; text-align: right; font-weight: 550; flex: 1 1 auto; }

/* ---------------------------------------------------------------- Map */

.map-card { display: flex; flex-direction: column; gap: .7rem; }

.map-heading {
  display: flex;
  align-items: center;
  gap: .4rem;
  margin-bottom: 0 !important;
}

.map-coords {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .6rem;
  flex-wrap: wrap;
}
.map-coords code {
  font-size: .85rem;
  background: var(--green-100);
  padding: .35rem .55rem;
  border-radius: 6px;
}

/* Anchored from the tree list, so leave room below the sticky header. */
#map { scroll-margin-top: 1rem; }

/* --- overview map of every tree --- */

.map-panel { display: flex; flex-direction: column; gap: .5rem; }
.map-hint { margin: 0 0 0 .15rem; }

.all-map {
  height: min(70vh, 720px);
  width: 100%;
  border-radius: var(--radius);
  border: 1px solid var(--line);
  box-shadow: var(--shadow);
  background: var(--green-100);
  z-index: 0;
}

/* Counts sit inside the cluster circles, so they need no box of their own. */
.cluster-label {
  background: transparent;
  border: 0;
  box-shadow: none;
  color: #fff;
  font-weight: 700;
  font-size: .72rem;
  text-shadow: 0 1px 2px rgba(0, 0, 0, .55);
  padding: 0;
}
.cluster-label::before { display: none; }

/* --- map pin in the admin table --- */

.col-qr { width: 1%; }

/* Small action inside a clickable row - must sit above the row's click overlay. */
.cell-action {
  position: relative;
  z-index: 1;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 34px;
  height: 34px;
  padding: 0;
  border-radius: 8px;
  color: var(--green-700);
  border: 1px solid var(--line);
  background: var(--surface);
  cursor: pointer;
  transition: background .12s, border-color .12s, color .12s;
}
.cell-action:hover,
.cell-action:focus-visible {
  background: var(--green-700);
  border-color: var(--green-700);
  color: #fff;
}

/* --- QR popup --- */

.qr-dialog {
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 0;
  background: var(--surface);
  color: var(--ink);
  box-shadow: 0 12px 40px rgba(0, 0, 0, .25);
  max-width: min(92vw, 380px);
}
.qr-dialog::backdrop { background: rgba(0, 0, 0, .45); }

.qr-dialog-body {
  padding: 1.1rem 1.2rem 1.2rem;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .35rem;
  text-align: center;
}

/* White plate behind the code: a dark theme must not invert a QR. */
.qr-dialog-img {
  width: min(260px, 70vw);
  height: auto;
  background: #fff;
  padding: 10px;
  border-radius: 8px;
}
.qr-dialog-code {
  margin: .5rem 0 0;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: 1.15rem;
  font-weight: 700;
}
.qr-dialog-name { margin: 0; font-size: .92rem; color: var(--ink-soft); }
.qr-dialog-url {
  margin: .2rem 0 0;
  font-size: .72rem;
  color: var(--ink-soft);
  word-break: break-all;
}
.qr-dialog-actions { display: flex; gap: .5rem; margin-top: .8rem; }

/* Leaflet ships its own light styling; soften it to match the cards. */
.leaflet-container { font: inherit; }
.leaflet-control-attribution { font-size: .68rem; }

.tree-footer {
  text-align: center;
  padding: 1.5rem 1rem 0;
  font-size: .85rem;
  color: var(--ink-soft);
}
.tree-footer p { margin: .2rem 0; }
.admin-links { margin-top: .9rem !important; }

.empty-state { text-align: center; padding: 2.5rem 1.25rem; color: var(--ink-soft); }
.empty-state h1, .empty-state h2 { color: var(--ink); margin-bottom: .5rem; }
.empty-state svg { color: var(--green-600); margin-bottom: .5rem; }
.empty-state .btn { margin-top: 1rem; }

/* ================================================================ *
   ADMIN
 * ================================================================ */

.topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: .7rem 1rem;
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--line);
  position: sticky;
  top: 0;
  z-index: 10;
}
.topbar-brand { font-weight: 700; text-decoration: none; color: var(--ink); }
.topbar-actions {
  display: flex;
  gap: .5rem;
  align-items: center;
  /* On a 320px screen these four controls measured 334px wide and pushed the
     whole page sideways, which is what moved everything else out of place.
     Letting them wrap keeps the page exactly as wide as the phone. */
  flex-wrap: wrap;
  justify-content: flex-end;
}

.admin-page {
  position: relative;
  /* Full width: the table is the page, and 88k rows of eight columns have more
     to say with the room than a centred column leaves them. */
  max-width: none;
  margin: 0;
  padding: 1.25rem 1.5rem 4rem;
  display: flex;
  flex-direction: column;
  gap: 1rem;
}
.admin-page--narrow { max-width: 820px; }

.admin--centered { display: grid; place-items: center; min-height: 100dvh; padding: 1rem; }
.login-wrap { width: 100%; max-width: 380px; }
.login-card { display: flex; flex-direction: column; gap: .85rem; }
.login-card h1 { font-size: 1.25rem; }
.login-card p { margin: 0; }

.page-head h1 { font-size: 1.45rem; }
.page-head p { margin: .3rem 0 0; }
.page-head-actions { display: flex; flex-wrap: wrap; gap: .9rem; font-size: .92rem; }

label { display: block; font-size: .9rem; color: var(--ink-soft); }

input[type="text"], input[type="password"], input[type="number"],
input[type="search"], select, textarea {
  width: 100%;
  margin-top: .25rem;
  padding: .6rem .7rem;
  font: inherit;
  font-size: 16px;                 /* stops iOS zooming on focus */
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 9px;
  min-height: 44px;
}
textarea { resize: vertical; }
input:focus-visible, select:focus-visible, textarea:focus-visible {
  outline: 2px solid var(--green-600);
  outline-offset: 1px;
  border-color: var(--green-600);
}

/* ------------------------------------------------- Toolbar (page header) */

.toolbar {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.toolbar-title h1 { font-size: 1.5rem; letter-spacing: -.01em; }
.toolbar-count {
  margin: .15rem 0 0;
  font-size: .9rem;
  color: var(--ink-soft);
}
.toolbar-count strong { color: var(--ink); font-variant-numeric: tabular-nums; }

.toolbar-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
}

/* --- segmented table / map switch --- */



/* The checked input styles its own label - no JS needed for the visual state. */

/* ------------------------------------------------------------ Filters */

/**
 * Flex rather than a fixed grid, so adding or removing a filter does not need a
 * new column template - controls simply reflow onto the next line.
 */
.filters {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem;
  align-items: center;
  padding: .7rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
}
.filters > * { margin-top: 0; }

/* Search gets an icon and the most room: it is the control people reach for. */
.field-search {
  position: relative;
  display: flex;
  align-items: center;
  flex: 3 1 min(100%, 260px);
  /* A flex item will not shrink below its own content by default, and the
     longest option in these dropdowns is wider than a 320px phone - which is
     what pushed the page 14px sideways there. */
  min-width: 0;
}
.field-search svg {
  position: absolute;
  left: .7rem;
  color: var(--ink-soft);
  pointer-events: none;
}
.field-search input { padding-left: 2.2rem; }

.filters select { flex: 1 1 min(100%, 170px); width: auto; min-width: 0; }
.filters .btn { flex: 0 0 auto; }

/* Sorting is not a filter; a subtle tint keeps it visually separate. */
.select-sort { background: var(--bg); }

/* ------------------------------------------------------------ Table */

.table-wrap {
  padding: 0;
  overflow-x: auto;
  /* Keep a sideways swipe inside the table. Without this the browser hands the
     gesture to its own back/forward navigation once the table reaches an edge,
     which reads as the scroll being broken. */
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

.tree-table { width: 100%; border-collapse: separate; border-spacing: 0; font-size: .92rem; }
/* Everything centred: this is a register of measurements read across the row,
   not a document read down the left edge. */
.tree-table th,
.tree-table td { padding: .7rem .85rem; text-align: center; white-space: nowrap; }
.tree-table td em { display: inline-block; }

/* The QR button is the one cell whose content is not text; it centres itself
   rather than inheriting an alignment meant for words. */
.tree-table .col-qr { text-align: center; }
.tree-table .col-qr .cell-action { margin: 0 auto; }

.tree-table thead th {
  font-size: .7rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
  background: var(--surface);
  border-bottom: 1px solid var(--line);
  /* Headings stay put while a long page of trees scrolls under them. */
  position: sticky;
  top: 0;
  z-index: 2;
}

.tree-table tbody td { border-bottom: 1px solid var(--line); }
.tree-table tbody tr:last-child td { border-bottom: 0; }
.tree-table tbody tr { transition: background .1s; }
.tree-table tbody tr:hover { background: var(--green-100); }

/**
 * Whole-row links.
 *
 * This used to stretch the anchor across the row with an absolute ::after
 * overlay anchored to the <tr>. It has to be done another way, because Safari
 * does not treat a positioned <tr> as the containing block for its absolutely
 * positioned descendants: the overlay escaped its row and lay across the page,
 * swallowing taps meant for the Map and Generate QR buttons and eating the
 * sideways swipe on the table. One cause, three faults, and invisible in
 * Chrome - which is why it survived so long.
 *
 * The anchor stays a real link, for the keyboard, middle-click and the status
 * bar. The rest of the row is made clickable in JS, which also lets a drag be
 * told apart from a tap - something an overlay could never do.
 */
.row-clickable { cursor: pointer; }

.row-link { text-decoration: none; font-weight: 700; }
.row-clickable:hover .row-link { text-decoration: underline; }

/**
 * On a phone the finger lands on that overlay, not on the cell, and a drag
 * beginning on a link is taken as dragging the link rather than scrolling the
 * table under it - so the table only moved when the swipe started on the
 * header, the one strip with no overlay over it. Naming the gestures we want
 * hands the swipe back to the scroller, while a tap still follows the link.
 */
.row-clickable,
.row-link {
  touch-action: pan-x pan-y;
  -webkit-user-drag: none;
  /* A drag beginning on a link is otherwise taken as selecting its text or
     lifting the link itself, and the table under the finger never moves. */
  -webkit-touch-callout: none;
  user-select: none;
}

/* The overlay swallows the outline, so show focus on the row instead. */
.row-link:focus-visible { outline: none; }
.row-clickable:focus-within {
  background: var(--green-100);
  outline: 2px solid var(--green-600);
  outline-offset: -2px;
}

/* Centred with the rest of the table; tabular figures keep the digits steady. */
.col-num { font-variant-numeric: tabular-nums; }

@media (max-width: 700px) { .hide-sm { display: none; } }

.pagination {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

/* ------------------------------------------------------------ Tree form */

.tree-form { display: flex; flex-direction: column; gap: 1rem; }

.tree-form .card h2, .photo-card h2, .danger-zone h2 {
  font-size: .78rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--green-700);
  margin-bottom: .85rem;
}

.field-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: .75rem 1rem;
}
@media (min-width: 640px) {
  .field-grid { grid-template-columns: 1fr 1fr; }
  .field--wide { grid-column: 1 / -1; }
}

.field-label { display: block; font-weight: 550; }
.req { color: var(--danger); }

.form-actions {
  display: flex;
  gap: .6rem;
  flex-wrap: wrap;
  position: sticky;
  bottom: 0;
  padding: .8rem 0;
  background: linear-gradient(to top, var(--bg) 65%, transparent);
}

/* ------------------------------------------------------------ Photo */

.photo-card { display: flex; flex-direction: column; gap: .8rem; }

.photo-preview img {
  display: block;
  width: 100%;
  max-height: 340px;
  object-fit: cover;
  border-radius: 10px;
  background: var(--green-100);
}
.photo-preview--empty {
  display: grid;
  place-items: center;
  height: 150px;
  border: 1px dashed var(--line);
  border-radius: 10px;
  color: var(--ink-soft);
}

.photo-forms { display: flex; flex-direction: column; gap: .4rem; }
.photo-buttons { display: flex; gap: .6rem; flex-wrap: wrap; }
.photo-buttons .btn { margin: 0; }

/* "Take photo" only makes sense where there is a camera to open. */
.only-mobile { display: none; }
@media (hover: none) and (pointer: coarse) { .only-mobile { display: inline-flex; } }

.danger-zone { border-color: #f0b4ae; }
.danger-zone h2 { color: var(--danger); }
.danger-zone p { margin-top: 0; }

/* ------------------------------------------------------------ QR */

.qr-card { display: flex; flex-direction: column; align-items: center; gap: 1rem; text-align: center; }

.qr-label {
  background: #fff;
  color: #000;
  padding: 1.25rem;
  border-radius: 10px;
  border: 1px solid var(--line);
}
.qr-image { display: block; width: min(340px, 78vw); height: auto; }
.qr-treeid { margin: .6rem 0 0; font-family: ui-monospace, Menlo, Consolas, monospace; font-size: 1.15rem; font-weight: 700; }
.qr-name { margin: .15rem 0 0; font-weight: 600; }
.qr-botanical { margin: .1rem 0 0; font-style: italic; font-size: .9rem; color: #555; }

.qr-url { word-break: break-all; }
.qr-actions { display: flex; gap: .6rem; flex-wrap: wrap; justify-content: center; }

/* ---------------------------------------------------------- QR label button */

/**
 * The wait, shown as a spinner in the icon's place.
 *
 * Both icons live in the markup and only one is ever shown, so the button never
 * changes width mid-press and the icon it started with is the icon it ends
 * with. Replacing the button's text was what used to lose the icon for good.
 */
.btn-icon-busy { display: none; }
.btn.is-busy .btn-icon-idle { display: none; }
.btn.is-busy .btn-icon-busy {
  display: block;
  animation: btn-spin .75s linear infinite;
  transform-origin: 50% 50%;
}
.btn.is-busy { cursor: progress; }

@keyframes btn-spin { to { transform: rotate(360deg); } }

/* Still turning for anyone who asked for less movement, just calmly - a frozen
   spinner reads as a hung page, which is the opposite of reassuring. */
@media (prefers-reduced-motion: reduce) {
  .btn.is-busy .btn-icon-busy { animation-duration: 2.4s; }
}

.list-actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
.list-actions-buttons { display: flex; gap: .5rem; flex-wrap: wrap; }

/* ------------------------------------------------------------ Scanner */

.scan-card { display: flex; flex-direction: column; gap: .8rem; }

.scan-viewport {
  position: relative;
  width: 100%;
  aspect-ratio: 3 / 4;
  max-height: 60vh;
  border-radius: 10px;
  overflow: hidden;
  background: #0d1410;
  display: grid;
  place-items: center;
}
.scan-viewport video {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}
/* The aiming frame only makes sense once the camera is actually live. */
.scan-frame { display: none; }
.scan-viewport.is-live .scan-frame {
  display: block;
  position: absolute;
  inset: 18%;
  border: 3px solid rgba(255, 255, 255, .85);
  border-radius: 12px;
  box-shadow: 0 0 0 100vmax rgba(0, 0, 0, .28);
}

.scan-actions { display: flex; flex-direction: column; gap: .5rem; }
.scan-manual { display: flex; gap: .6rem; align-items: center; }
.scan-manual input { margin-top: 0; }

@media print {
  .no-print, .topbar { display: none !important; }
  body { background: #fff none; }
  body::before { display: none !important; }
  .admin-page { padding: 0; gap: 0; }
  .qr-card { border: 0; box-shadow: none; padding: 0; }
  .qr-label { border: 1px solid #000; }

  @page { margin: 10mm; }
}

/* ================================================================ *
   SINGLE TREE PAGE  (view + edit merged)
 * ================================================================ */

/* Full width, like the list, with the content centred inside it.

.tree-topline {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
}
/* The plain back link on the import and review pages. Given a real box so it
   is a target a thumb can find, the way the record page's own one is. */
.back-link {
  display: inline-flex;
  align-items: center;
  gap: .3rem;
  min-height: 2.1rem;
  padding: .2rem .1rem;
  font-weight: 600;
  text-decoration: none;
  font-size: .92rem;
}
.back-link:hover { text-decoration: underline; }
.back-link:focus-visible { outline: 2px solid var(--green-600); outline-offset: 2px; border-radius: 6px; }
.tree-topline-actions { display: flex; gap: .5rem; }

/* --- hero: photo beside identity --- */

.tree-hero-card {
  display: grid;
  grid-template-columns: 1fr;
  gap: 1rem;
  padding: 1rem;
}
@media (min-width: 720px) {
  .tree-hero-card { grid-template-columns: 260px 1fr; align-items: start; }
}

.tree-hero-photo { display: flex; flex-direction: column; gap: .5rem; align-items: flex-start; }
.tree-hero-photo img {
  width: 100%;
  aspect-ratio: 4 / 3;
  object-fit: cover;
  border-radius: 10px;
  background: var(--green-100);
}
.tree-hero-empty {
  width: 100%;
  aspect-ratio: 4 / 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  border: 1px dashed var(--line);
  border-radius: 10px;
  color: var(--ink-soft);
  font-size: .85rem;
}
.tree-hero-info h1 { font-size: 1.6rem; margin: .1rem 0 .15rem; }
.tree-hero-info .tree-id { font-size: .78rem; }

/* A plain-text control for a destructive-but-minor action. */
.link-button {
  background: none;
  border: 0;
  padding: 0;
  color: var(--ink-soft);
  font: inherit;
  font-size: .82rem;
  text-decoration: underline;
  cursor: pointer;
}
.link-button:hover { color: var(--danger); }

/* --- the three measurements people change most --- */

.quick-card { padding: 1rem 1.1rem 1.1rem; }
.quick-head { margin-bottom: .7rem; }
.quick-head h2 {
  font-size: .78rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--green-700);
}
.quick-head .hint { margin-top: .15rem; }

.quick-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
  gap: .7rem;
  align-items: end;
}
.quick-field { display: flex; flex-direction: column; gap: .25rem; }
.quick-field span { font-size: .8rem; font-weight: 600; color: var(--ink-soft); }
.quick-field input {
  margin-top: 0;
  font-size: 1.1rem;
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  text-align: center;
}
#quick-save.is-ok { background: var(--green-600); border-color: var(--green-600); }
#quick-save.is-bad { background: var(--danger); border-color: var(--danger); }

/* --- collapsible full record --- */

.details-card { padding: 0; }
.details-card > summary {
  list-style: none;
  cursor: pointer;
  padding: .9rem 1.1rem;
  display: flex;
  align-items: center;
  gap: .5rem;
}
.details-card > summary::-webkit-details-marker { display: none; }
.details-card > summary h2 {
  font-size: .78rem;
  letter-spacing: .09em;
  text-transform: uppercase;
  color: var(--green-700);
}
.details-card > summary::after {
  content: '▾';
  margin-left: auto;
  color: var(--ink-soft);
  transition: transform .15s;
}
.details-card[open] > summary::after { transform: rotate(180deg); }
.details-card[open] > summary { border-bottom: 1px solid var(--line); }

.detail-section { border: 0; margin: 0; padding: 1rem 1.1rem; }
.detail-section + .detail-section { border-top: 1px solid var(--line); }
.detail-section legend {
  font-size: .74rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 700;
  padding: 0;
  margin-bottom: .6rem;
}

/* ---------------------------------------------------------- Excel import */

.import-page { max-width: 720px; }
.import-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .6rem;
  padding: 2rem 1rem;
  border: 2px dashed var(--line);
  border-radius: var(--radius);
  text-align: center;
  color: var(--ink-soft);
}
.import-drop.is-ready { border-color: var(--green-600); color: var(--green-700); }
.import-file { font-weight: 600; }
.import-log {
  margin: 0;
  padding: .9rem 1rem;
  background: var(--green-100);
  border-radius: 10px;
  font-family: ui-monospace, Menlo, Consolas, monospace;
  font-size: .8rem;
  line-height: 1.5;
  white-space: pre-wrap;
  max-height: 420px;
  overflow: auto;
}
.import-stats { display: flex; gap: 1.2rem; flex-wrap: wrap; }
.import-stat { display: flex; flex-direction: column; }
.import-stat strong { font-size: 1.4rem; font-variant-numeric: tabular-nums; }
.import-stat span { font-size: .78rem; color: var(--ink-soft); }




/* ---------------------------------------------------- Touch sizing

   Measured across 320-430px: every control came out 38px tall, under the 44px
   a fingertip reliably hits. This raises them for touch only - a mouse keeps
   the tighter desktop sizing, so nothing shifts on a laptop.

   Sizes are fixed rather than proportional on purpose: a button that changes
   size with the screen lands somewhere different on every phone. */
@media (pointer: coarse) {
  .btn,
  .lang-option,
  .filters input,
  .filters select,
  .quick-field input {
    min-height: 44px;
  }

  .btn--small { min-height: 40px; }
  /* The QR button sits inside a table cell and measured 34px. */
  .cell-action { min-height: 40px; min-width: 40px; }

  /* Leaflet ships 30px controls. The map is the part used standing in front of
     a tree, one-handed, so its own buttons need the same room as ours. */
  .leaflet-touch .leaflet-bar a,
  .leaflet-control-zoom a,
  .leaflet-control-layers-toggle {
    width: 40px !important;
    height: 40px !important;
    line-height: 40px !important;
  }
}

/* Nothing drawn should be able to widen the page; at 430px an icon measured
   433px and did exactly that. */
img,
svg,
canvas {
  max-width: 100%;
}

/* Except the map's own canvas, which this rule had quietly erased.

   Leaflet draws every marker on one canvas, sized larger than the view and
   sitting in an absolutely positioned pane. That pane has no width of its own,
   so `max-width: 100%` resolved against zero and the canvas was laid out zero
   pixels wide: every tree and every cluster circle vanished from the map, and
   nothing on it could be clicked. Leaflet exempts its own tiles and markers
   from exactly this rule; its canvas was the one it did not cover. */
.leaflet-container canvas {
  max-width: none;
}

/* ================================================================== *
   FILTERS ON A PHONE

   The three dropdowns were taking about 180px of a small screen to say
   "all areas, all species, default sort" - a form standing where the
   results should be. On a phone they move into a sheet behind a button;
   everywhere else nothing changes at all.
 * ================================================================== */

/* The markup is split in two so the second half can be lifted into a sheet.
   Above the breakpoint both wrappers dissolve, their children fall straight
   back into the filter row, and the laptop keeps the single line it had. */
.filters-primary,
.filters-advanced { display: contents; }
.filters-open,
.filters-apply,
.filters-grip,
.filters-head,
.filters-label,
.filters-backdrop { display: none; }

@media (max-width: 720px) {

  /* ------------------------------------------------------ the header

     Six things have to share one line: the mark, two tabs, three actions and
     the avatar. They fit once the two widest things give way - the site name
     and the labels on the buttons.

     The name goes and the mark stays. It is the only text in the bar that says
     nothing a person does not already know, having just opened the app; the
     tabs say where they are and the icons say what they can do. */

  .topbar { flex-wrap: nowrap; gap: .35rem; padding: .5rem .6rem; }
  .topbar-brand { flex: none; font-size: .9rem; }
  .topbar-name { display: none; }

  .topbar-tabs { margin-right: auto; gap: 0; min-width: 0; }
  .topbar-tab { margin: 0 .3rem; font-size: .85rem; }
  .topbar-actions { gap: .25rem; flex: none; }

  /* Every action drops to its icon, the primary one keeping its colour. The
     label stays in the markup for screen readers - hidden by clipping rather
     than font-size:0, which leaves the gap behind and pushes the icon off
     centre. */
  .topbar-actions .btn {
    min-height: 38px;
    width: 38px;
    padding: 0;
    gap: 0;
    justify-content: center;
  }
  /* The avatar is the widest control left; it gives back a few pixels so the
     row still fits the narrowest phones. */
  .topbar-user > summary { padding: .15rem .2rem .15rem .15rem; }
  .avatar { width: 28px; height: 28px; font-size: .68rem; }

  .topbar-actions .btn .btn-label {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }

  /* ------------------------------------------------------- the tree table

     No sideways scroll. Two of the six columns were already dropped here; the
     register's own row number goes too, because the Unique Tree ID is the
     identity and a phone has room for one of them. What is left - id, species,
     DBH and the QR button - fits 360px once the cells may wrap and the padding
     comes in. */
  .table-wrap { overflow-x: visible; }
  .tree-table { font-size: .82rem; }
  .tree-table th,
  .tree-table td { padding: .5rem .35rem; white-space: normal; }
  .tree-table th:first-child,
  .tree-table td:first-child { display: none; }
  .tree-table td em { font-size: .78rem; }

  /* The meta row above it stacks rather than squeezing count against paging. */
  .list-meta { gap: .4rem; }
  .list-paging { margin-left: auto; }
  .list-range { display: none; }

  .lang-toggle { flex: none; }
  /* Matched to the 40px icon buttons beside it, halves and all. */
  .lang-option { min-height: 40px; height: 40px; padding: 0 .32rem; }
  .lang-code { font-size: .6rem; }

  .filters { display: block; }

  .filters-primary { display: flex; gap: .5rem; }
  .field-search { flex: 1 1 auto; min-width: 0; }

  .filters-open {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    flex: none;
    min-height: 44px;
    padding: 0 .85rem;
    border: 1px solid var(--line);
    border-radius: 12px;
    background: var(--surface);
    color: var(--ink);
    font: inherit;
    font-size: .88rem;
    font-weight: 600;
    cursor: pointer;
  }
  .filters-open:active { background: var(--green-100); }
  .filters-badge {
    display: inline-grid;
    place-items: center;
    min-width: 19px;
    height: 19px;
    padding: 0 5px;
    border-radius: 999px;
    background: var(--green-700);
    color: #fff;
    font-size: .7rem;
    font-weight: 700;
  }

  /* The dropdowns leave the page and become a sheet. */
  .filters-advanced {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 60;
    display: block;
    max-height: 82vh;
    overflow-y: auto;
    padding: .5rem 1rem calc(1rem + env(safe-area-inset-bottom));
    background: var(--surface);
    border-top: 1px solid var(--line);
    border-radius: 16px 16px 0 0;
    box-shadow: 0 -8px 40px rgba(20, 54, 31, .18);
    transform: translateY(101%);
    transition: transform .22s ease;
    visibility: hidden;
  }
  .filters-advanced.is-open { transform: none; visibility: visible; }

  .filters-grip {
    display: block;
    width: 38px;
    height: 4px;
    margin: .25rem auto .75rem;
    border-radius: 999px;
    background: var(--line);
  }
  .filters-head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: 0 .1rem .25rem;
  }
  .link-quiet { font-size: .82rem; color: var(--ink-soft); }

  .filters-label {
    display: block;
    margin: 1rem 0 .35rem;
    font-size: .68rem;
    font-weight: 700;
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .filters-advanced select { width: 100%; min-height: 48px; }

  /* Submit and Clear belong to the laptop row; the sheet has its own pair. */
  .filters-advanced [data-search-button],
  .filters-clear { display: none; }

  .filters-apply {
    display: block;
    width: 100%;
    min-height: 48px;
    margin-top: 1.25rem;
  }

  .filters-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 55;
    background: rgba(10, 26, 16, .42);
    opacity: 0;
    transition: opacity .2s ease;
  }
  .filters-backdrop.is-open { opacity: 1; }

  /* The page must not scroll behind an open sheet.

     The state class is `filters-sheet-open`, not `filters-open`, and the
     difference is not cosmetic: `.filters-open` is the Filters button's own
     class. Naming the body state the same made <body> match the button rule
     and become `display: inline-flex` - an inline-flex box shrink-wraps its
     contents, so the body stopped being 390px wide and became as wide as the
     table inside it, and the whole page slid sideways behind the sheet. */
  body.filters-sheet-open { overflow: hidden; }
  .filters-advanced { overscroll-behavior: contain; }
}

/* --------------------------------------------------------------------------
   Fullscreen map button — see src/static/map-fullscreen.js

   Asking the browser for fullscreen is only half of it: both maps have a fixed
   height (.all-map is min(70vh, 720px)), and that height survives the switch.
   Without these rules the map goes fullscreen and then sits in the top of a
   black screen at its old size.
   -------------------------------------------------------------------------- */
.map-fs a {
  display: flex;
  align-items: center;
  justify-content: center;
}

.all-map:fullscreen,
.tree-map:fullscreen {
  height: 100%;
  width: 100%;
  max-height: none;
  border: 0;
  border-radius: 0;
  box-shadow: none;
}

/* ==========================================================================
   Sheets - a task over the page rather than instead of it
   ========================================================================== */
.sheet {
  width: min(34rem, calc(100vw - 2rem));
  padding: 1.2rem 1.3rem 1.1rem;
  background: var(--surface);
  color: var(--ink);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 20px 60px rgba(20, 54, 31, .28);
}
.sheet::backdrop { background: rgba(10, 26, 16, .45); }

.sheet-close-form { float: right; margin: -.4rem -.4rem 0 0; }
.sheet-close {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  background: none;
  border: 0;
  border-radius: 7px;
  color: var(--ink-soft);
  cursor: pointer;
}
.sheet-close:hover { background: var(--bg); color: var(--ink); }
.sheet-close:focus-visible { outline: 2px solid var(--green-600); outline-offset: 1px; }

.sheet-title { margin: 0 0 .3rem; font-size: 1.05rem; }
.sheet-intro { margin: 0 0 .9rem; font-size: .86rem; color: var(--ink-soft); }
.sheet-actions { display: flex; justify-content: flex-end; gap: .5rem; margin-top: 1rem; }

/* ------------------------------------------------------ the QR label sheet
   Two files on offer and the four numbers that separate them. A table, because
   that is what a row of comparable numbers is - and because the choice is made
   by reading across one line and pressing the button at the end of it. */
.labels-table { width: 100%; border-collapse: collapse; font-size: .85rem; }
.labels-table th,
.labels-table td { padding: .5rem .4rem; text-align: right; white-space: nowrap; }
.labels-table thead th {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
  border-bottom: 1px solid var(--line);
}
/* The row's name, and the only thing in the table that is words. */
.labels-table tbody th[scope="row"] { text-align: left; font-weight: 600; }
.labels-table tbody td { color: var(--ink-soft); font-variant-numeric: tabular-nums; }
.labels-table tbody tr + tr th,
.labels-table tbody tr + tr td { border-top: 1px solid var(--line); }
.labels-table tbody td:last-child { padding-right: 0; }
.labels-table .btn { width: 100%; }

/* The wait, while it is being waited. */
.labels-work { margin-top: .9rem; }
.labels-bar {
  height: 5px;
  border-radius: 999px;
  background: var(--green-100);
  overflow: hidden;
}
.labels-bar i { display: block; width: 0; height: 100%; background: var(--green-600); }
.labels-note { margin: .45rem 0 0; font-size: .8rem; color: var(--ink-soft); }

.import-drop {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  padding: 1.6rem 1rem;
  background: var(--bg);
  border: 1.5px dashed var(--line);
  border-radius: 10px;
  color: var(--ink-soft);
  font-size: .88rem;
  text-align: center;
  cursor: pointer;
}
.import-drop:hover { border-color: var(--green-600); color: var(--green-700); }
.import-drop:focus-within { outline: 2px solid var(--green-600); outline-offset: 2px; }

.import-dry {
  display: flex;
  align-items: center;
  gap: .45rem;
  margin-top: .7rem;
  font-size: .85rem;
  color: var(--ink-soft);
}
.import-dry input { accent-color: var(--green-600); }

.import-log {
  max-height: 15rem;
  margin: .8rem 0 0;
  padding: .7rem .8rem;
  overflow: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
  font-size: .78rem;
  line-height: 1.5;
  white-space: pre-wrap;
}

/* ==========================================================================
   Passing messages

   Over the page, never in it. A paragraph pushed into the top of a document
   moves everything below it down, stays until the next load, and on a long
   record appears somewhere nobody is looking.

   Failures do not time out. The one message that has to still be there when
   the reader looks up is the one saying something did not work.
   ========================================================================== */
.toasts {
  position: fixed;
  top: .85rem;
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: .45rem;
  width: min(30rem, calc(100vw - 1.6rem));
  pointer-events: none;
}

.toast {
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .6rem .5rem .6rem .85rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-left: 3px solid var(--green-600);
  border-radius: 10px;
  box-shadow: 0 8px 26px rgba(20, 54, 31, .2);
  font-size: .88rem;
  color: var(--ink);
  pointer-events: auto;
  animation: toast-in .18s ease-out;
}
.toast.is-bad { border-left-color: var(--danger); }
.toast.is-info { border-left-color: var(--ink-soft); }
.toast.is-going { opacity: 0; transition: opacity .18s ease; }

.toast-text { flex: 1 1 auto; }

.toast-close {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 1.7rem;
  height: 1.7rem;
  background: none;
  border: 0;
  border-radius: 6px;
  color: var(--ink-soft);
  cursor: pointer;
}
.toast-close:hover { background: var(--bg); color: var(--ink); }
.toast-close:focus-visible { outline: 2px solid var(--green-600); outline-offset: 1px; }

@keyframes toast-in { from { opacity: 0; transform: translateY(-6px); } }
@media (prefers-reduced-motion: reduce) {
  .toast { animation: none; }
}

/* ------------------------------------------------------- the theme switch */
/* Nothing to push apart any more: the row is the sun, the switch and the moon,
   so they sit together in the middle of it rather than clinging to one edge. */
.user-menu-switch {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  width: 100%;
  padding: .55rem .5rem;
}
.switch {
  flex: 0 0 auto;
  position: relative;
  width: 34px;
  height: 19px;
  border-radius: 999px;
  background: var(--line);
  transition: background .16s ease;
}
.switch-knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 15px;
  height: 15px;
  border-radius: 50%;
  background: var(--surface);
  box-shadow: 0 1px 3px rgba(0, 0, 0, .25);
  transition: transform .16s ease;
}
/* Sun, switch, moon - one group at the end of the row, so the label keeps the
   other end and the space between them stays where it was. */
.switch-set { display: inline-flex; align-items: center; gap: .4rem; flex: 0 0 auto; }

.switch-side {
  display: inline-flex;
  color: var(--ink-soft);
  opacity: .45;
  transition: opacity .16s ease, color .16s ease;
}
/* The one in force is lit; the other waits. */
.switch-sun { color: #c98a1e; opacity: 1; }
.user-menu-switch.is-on .switch-sun { color: var(--ink-soft); opacity: .45; }
.user-menu-switch.is-on .switch-moon { color: var(--green-700); opacity: 1; }

.user-menu-switch.is-on .switch { background: var(--green-600); }
.user-menu-switch.is-on .switch-knob { transform: translateX(15px); }
@media (prefers-reduced-motion: reduce) {
  .switch, .switch-knob, .switch-side { transition: none; }
}

/* ==========================================================================
   Filter panels - a search box and a list of checkboxes

   A single-choice dropdown could not ask "which of these": no way to see two
   species together, and no way to reach the hundred-and-twentieth without
   scrolling a native list off the bottom of the screen. These are panels
   instead, and the count beside each value turns a word into a quantity
   before it is chosen.
   ========================================================================== */
.fpick { position: relative; }

.fpick-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: .5rem;
  width: 100%;
  min-height: 2.35rem;
  padding: .4rem .6rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  color: var(--ink);
  font: inherit;
  font-size: .88rem;
  text-align: left;
  cursor: pointer;
}
.fpick-toggle:hover { border-color: var(--green-600); }
.fpick-toggle:focus-visible { outline: 2px solid var(--green-600); outline-offset: 1px; }
.fpick-toggle[aria-expanded="true"] { border-color: var(--green-600); }

.fpick-value {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--ink-soft);
}
/* Something is chosen: the button says so without a badge to count. */
.fpick-value[data-on] { color: var(--green-700); font-weight: 600; }

.fpick-menu {
  position: absolute;
  z-index: 60;
  top: calc(100% + 4px);
  left: 0;
  min-width: 100%;
  max-width: min(17rem, 90vw);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  box-shadow: 0 8px 22px rgba(20, 54, 31, .16);
  overflow: hidden;
}

.fpick-search { padding: .3rem; border-bottom: 1px solid var(--line); }
.fpick-search input {
  width: 100%;
  padding: .22rem .4rem;
  font: inherit;
  font-size: .76rem;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.fpick-search input:focus { outline: 2px solid var(--green-600); outline-offset: -1px; }

/* The two actions, with the count they act on between them.

   They used to be bare browser buttons: the quiet-link class they carried is
   styled on a phone only, so on a laptop the panel ended in two grey slabs.
   They are chips now - a tick and a cross, the same size as the text they sit
   above - and each goes dim when pressing it would change nothing, which is
   the panel's way of saying what state it is already in. */
.fpick-actions {
  display: flex;
  align-items: center;
  gap: .25rem;
  padding: .24rem .35rem;
  border-bottom: 1px solid var(--line);
}

.fpick-act {
  display: inline-flex;
  align-items: center;
  gap: .2rem;
  flex: 0 0 auto;
  padding: .12rem .4rem;
  font: inherit;
  font-size: .68rem;
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 999px;
  cursor: pointer;
  transition: color .12s ease, background .12s ease, border-color .12s ease;
}
.fpick-act svg { opacity: .65; }
.fpick-act:hover:not(:disabled) {
  color: var(--green-700);
  background: var(--green-100);
  border-color: var(--green-600);
}
.fpick-act:hover:not(:disabled) svg { opacity: 1; }
.fpick-act:active:not(:disabled) { transform: translateY(1px); }
.fpick-act:focus-visible { outline: 2px solid var(--green-600); outline-offset: 1px; }

/* Clearing is the one that takes something away, and says so on the way in -
   never before, and never as a colour it wears while sitting there. */
.fpick-act--clear:hover:not(:disabled) {
  color: var(--danger);
  background: color-mix(in srgb, var(--danger) 7%, var(--surface));
  border-color: color-mix(in srgb, var(--danger) 40%, var(--line));
}

/* Spent, not gone: a button that vanished when it finished would move the
   other one out from under the cursor. */
.fpick-act:disabled { opacity: .35; cursor: default; }

/* Between the two, and empty until there is something to count. */
.fpick-tally {
  flex: 1 1 auto;
  min-width: 0;
  text-align: center;
  font-size: .66rem;
  font-weight: 600;
  color: var(--green-700);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.fpick-list { max-height: 12rem; overflow-y: auto; padding: .15rem; }

.fpick-row {
  display: flex;
  align-items: center;
  gap: .4rem;
  padding: .16rem .35rem;
  border-radius: 4px;
  font-size: .77rem;
  cursor: pointer;
}
.fpick-row:hover { background: var(--green-100); }
.fpick-row.is-on { font-weight: 600; }
/* The box itself sets the floor under a row's height; at this size it can
   come down with everything else. */
.fpick-row input { flex: 0 0 auto; width: 12px; height: 12px; accent-color: var(--green-600); }
.fpick-text { flex: 1 1 auto; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.fpick-count { flex: 0 0 auto; color: var(--ink-soft); font-weight: 400; font-size: .68rem; }

.fpick-empty { margin: 0; padding: .5rem; color: var(--ink-soft); font-size: .74rem; text-align: center; }

/* Tightened for a pointer, which lands where it is aimed. A finger does not,
   so on a touch screen the rows and the two actions get their size back. */
@media (hover: none) and (pointer: coarse) {
  .fpick-row { padding: .42rem .45rem; font-size: .88rem; }
  .fpick-row input { width: 16px; height: 16px; }
  .fpick-act { padding: .3rem .6rem; font-size: .8rem; }
  .rpick-end input { padding: .45rem .5rem; font-size: .95rem; }
  .fpick-search input { padding: .42rem .5rem; font-size: .9rem; }
  .fpick-list { max-height: 14rem; }
}

/* ------------------------------------------------------- measurement ranges
   The same panel as the checkbox one, asking the other kind of question: two
   numbers, either of which may be left blank. It hangs off the DBH, height and
   crown headings, so the unit sits under the two boxes as the panel's own
   caption - "DBH (cm)" - where the heading above has room only for the range
   once it is set. */
.rpick-body { display: flex; align-items: flex-end; gap: .3rem; padding: .35rem; }

.rpick-end { display: flex; flex-direction: column; gap: .2rem; flex: 1 1 0; min-width: 0; }
.rpick-end span {
  font-size: .56rem;
  font-weight: 700;
  letter-spacing: .05em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.rpick-end input {
  width: 100%;
  min-width: 0;
  padding: .22rem .35rem;
  font: inherit;
  font-size: .78rem;
  color: var(--ink);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 6px;
  /* No spinner. It is two arrows the width of a digit, for stepping a diameter
     one centimetre at a time - which nobody does - and it was the widest thing
     in the panel after the number itself. */
  appearance: textfield;
  -moz-appearance: textfield;
}
.rpick-end input::-webkit-outer-spin-button,
.rpick-end input::-webkit-inner-spin-button { -webkit-appearance: none; margin: 0; }
.rpick-end input:focus { outline: 2px solid var(--green-600); outline-offset: -1px; }
.rpick-dash { padding-bottom: .26rem; color: var(--ink-soft); font-size: .75rem; }

.fpick--range .fpick-menu { min-width: 11rem; }
/* Nothing follows the actions here, so the rule under them would be a line
   drawn along the bottom edge of the panel. */
.fpick--range .fpick-actions { border-bottom: 0; border-top: 1px solid var(--line); }
/* Not a count in this panel - the column's name and unit. */
.fpick--range .fpick-tally { text-align: left; color: var(--ink-soft); font-weight: 600; }
.th-pick .fpick--range .fpick-value { font-variant-numeric: tabular-nums; }

/* ------------------------------------------------------ sortable headings
   The column being sorted is the thing you click. The arrow belongs to that
   column and shows its direction; the others stay quiet until pointed at, so
   a row of eight arrows never competes with the data underneath. */
.th-sort { padding: 0 !important; }

/* Centred, like every cell under it. A heading pushed to one edge while its
   column sits in the middle reads as two columns rather than one. */
.th-sort-link {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .3rem;
  width: 100%;
  padding: .6rem .7rem;
  color: inherit;
  font: inherit;
  text-decoration: none;
}

/* The arrow sits after the label, which would push the label half its width
   to the left of true centre. An empty box of the same width on the other
   side balances it, so the word is centred whether or not the arrow shows. */
.th-sort-link::before {
  content: '';
  flex: 0 0 auto;
  width: 13px;
}
.th-sort-link:hover { color: var(--green-700); }
.th-sort-link:focus-visible { outline: 2px solid var(--green-600); outline-offset: -2px; }

.th-arrow { opacity: 0; flex: 0 0 auto; transition: opacity .12s ease; }
.th-sort-link:hover .th-arrow { opacity: .4; }
.th-sort.is-sorted .th-arrow { opacity: 1; color: var(--green-700); }
.th-sort.is-sorted .th-sort-link { color: var(--green-700); }

/* ------------------------------------------------- the species heading
   The Botanical name heading is also the species filter: pressing the word
   opens the list of species with the search box and the tick boxes, where a
   button in the filter row used to say "All species" beside the column it was
   talking about. The arrow next to it still sorts, so one heading answers both
   questions asked of one column. */
.th-sort--pick { padding: .25rem .4rem !important; }

.th-pick { display: flex; align-items: center; justify-content: center; gap: .1rem; }
.th-pick .fpick { min-width: 0; }

/* A heading that can be pressed, not a control sitting in one: it keeps the
   thead's own type and shows its edges only when pointed at. */
.th-pick .fpick-toggle {
  gap: .3rem;
  width: auto;
  min-height: 0;
  padding: .35rem .45rem;
  background: none;
  border: 1px solid transparent;
  border-radius: 6px;
  font: inherit;
  color: inherit;
  text-align: center;
}
.th-pick .fpick-toggle:hover,
.th-pick .fpick-toggle[aria-expanded="true"] {
  background: var(--green-100);
  border-color: transparent;
  color: var(--green-700);
}
.th-pick .fpick-value { color: inherit; }
/* What is chosen is not a heading any more, and is not written as one. */
.th-pick .fpick-value[data-on] {
  color: var(--green-700);
  text-transform: none;
  letter-spacing: normal;
}
/* A species is a name, and the table sets names in italic; a measurement is a
   number, and numbers line up. */
.th-pick [data-fpick="botanicalName"] .fpick-value[data-on] { font-style: italic; }

/* Arrow only - the word beside it belongs to the filter now. Dimmed rather
   than hidden: it is the only thing offering to sort this column. */
.th-pick .th-sort-link { width: auto; padding: .35rem .1rem; }
.th-pick .th-sort-link::before { content: none; }
.th-pick .th-arrow { opacity: .32; }
.th-pick .th-sort-link:hover .th-arrow { opacity: 1; }

/* Placed in viewport coordinates by JS - see placeMenu in list.ejs - because
   the heading is inside the table's sideways scroller, which would otherwise
   cut the menu off. The heading's own type stops at the menu's edge: these are
   species names to read, not a caption. */
/* The same panel, standing in for the heading when a filter leaves no table
   to put a heading on. It keeps the ordinary control's look here: on an empty
   card there is no column for it to be the title of. */
/* Where the two panels a phone has no column for wait out the small screen.
   Empty and hidden on anything wider, where they are on their headings. */
.filters-heads:not([hidden]) { display: flex; flex-direction: column; gap: .5rem; }
@media (min-width: 701px) { .filters-heads { display: none; } }

.empty-pick {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: .5rem .9rem;
  margin-top: 1rem;
}
.empty-pick-item { display: inline-flex; align-items: center; gap: .5rem; }
.empty-pick-label {
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.empty-pick .fpick { min-width: 11rem; max-width: 20rem; text-align: left; }

.fpick--head .fpick-menu {
  position: fixed;
  top: 0;
  left: 0;
  min-width: 12.5rem;
  font-size: .78rem;
  font-weight: 400;
  font-style: normal;
  letter-spacing: normal;
  text-transform: none;
  text-align: left;
  color: var(--ink);
}


/* ==========================================================================
   The tree record - one continuous page
   --------------------------------------------------------------------------
   A survey record, not a dashboard. Flat sections on one white surface, thin
   rules between them, and no card around anything: cards imply the pieces are
   separable, and these are one document read in order.

   Density is deliberate. Thirty-eight fields spread out generously become a
   page nobody scrolls to the end of.
   ========================================================================== */
.tree-record {
  /* The same margin from the edge of the page as the table has.

     This used to be a 1560px column centred in whatever room there was, with
     the page's own side padding removed - so on a wide screen a record opened
     from the list began about 150px further in than the list it came from, and
     the two read as different pages laid out by different hands. The record is
     the same page as the table, seen closer, so it starts where the table
     starts and ends where it ends. */
  max-width: none;
  margin: 0;
  padding: 0 1.5rem 1.5rem;
  gap: 0;
}

/* One white sheet for the whole record, not a card per section. The sections
   are divided by rules drawn on it, the way a form on paper is - a box around
   each would say they can be read apart, and they cannot. */
.tr-sheet {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0 1.5rem .9rem;
}

/* The way back: an arrow at the head of the record.

   It used to be a pill as wide as the words "Back to trees", standing on the
   page above the card - the first thing on a page whose subject is the tree
   under it. An arrow says the same thing in the corner where every other page
   keeps one, and the words are still there as its name for a pointer resting
   on it or a screen reader reading it. */
/* The way back.

   It was a flat green tile with an arrow in it, which is the shape of every
   other small button on the page and said nothing about being the way out. A
   round one reads as a control of its own kind, and the name it used to carry
   comes back on hover - as a label floating under it, not as width, so the
   header does not move when the pointer crosses it. */
.tr-back {
  position: relative;
  align-self: center;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2.1rem;
  height: 2.1rem;
  color: var(--green-700);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 50%;
  box-shadow: 0 1px 2px rgba(20, 54, 31, .06);
  text-decoration: none;
  transition: background .15s ease, color .15s ease, border-color .15s ease,
              box-shadow .15s ease, transform .18s cubic-bezier(.3, .8, .4, 1);
}
.tr-back svg { transition: transform .18s cubic-bezier(.3, .8, .4, 1); }
.tr-back:hover {
  color: #fff;
  background: var(--green-600);
  border-color: var(--green-600);
  box-shadow: 0 4px 12px rgba(31, 107, 58, .28);
}
/* The arrow moves, not the button: a button that slides takes the photographs
   beside it with it. */
.tr-back:hover svg { transform: translateX(-2px); }
.tr-back:active { transform: scale(.94); }
.tr-back:focus-visible { outline: 2px solid var(--green-600); outline-offset: 3px; }

/* The name, under the button and out of the layout. */
.tr-back-label {
  position: absolute;
  top: calc(100% + .35rem);
  right: 0;
  transform: translateY(-3px);
  padding: .2rem .45rem;
  white-space: nowrap;
  font-size: .68rem;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(20, 54, 31, .12);
  opacity: 0;
  pointer-events: none;
  transition: opacity .14s ease, transform .14s ease;
  z-index: 3;
}
.tr-back:hover .tr-back-label,
.tr-back:focus-visible .tr-back-label { opacity: 1; transform: translateY(0); }

@media (prefers-reduced-motion: reduce) {
  .tr-back, .tr-back svg, .tr-back-label { transition: opacity .14s ease; }
  .tr-back:hover svg, .tr-back:active { transform: none; }
}

/* A moved button is a nicety; for anyone who asked for less motion it is not
   one, and the colour change alone still says the same thing. */
@media (prefers-reduced-motion: reduce) {
  .tr-back { transition: background .15s ease, color .15s ease; }
  .tr-back:hover,
  .tr-back:active { transform: none; }
}

/* ------------------------------------------------------------------ header */
.tr-head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: 1rem;
  align-items: center;
  padding: .8rem 0;
  border-bottom: 1px solid var(--line);
}

/* The photographs, at the head of the record.

   A mosaic in the header's first column: the main one at full width because it
   is the one that stands for the tree everywhere else, the others beside it at
   half that, and the plus last. The column is a fixed 104px so the header's
   three columns keep their proportions however many photographs there are -
   what grows is the height, one row per two extra pictures. */
.tr-head-photos {
  display: grid;
  grid-template-columns: repeat(2, 48px);
  gap: 8px;
  width: 104px;
  margin: 0;
  padding: 0;
  list-style: none;
}
/* The first one is the tree's photograph; it gets the width to say so. */
.tr-head-photos > .tr-shot:first-child { grid-column: span 2; }
.tr-head-photos > .tr-shot:first-child img,
.tr-head-photos > .tr-shot:first-child label { height: 104px; }

.tr-head-photos .tr-shot { position: relative; }
/* The link is the tile; inline it would leave a baseline gap under the image. */
.tr-head-photos .tr-shot a { display: block; }
.tr-head-photos .tr-shot img {
  display: block;
  width: 100%;
  height: 48px;
  object-fit: cover;
  border-radius: 8px;
  background: var(--green-100);
  border: 1px solid var(--line);
}
.tr-head-photos .tr-shot.is-main img { border-color: var(--green-600); }

/* The plus. Outlined rather than filled: it is the space for a photograph,
   not one of them. */
.tr-head-photos .tr-shot--add label {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .15rem;
  width: 100%;
  height: 48px;
  color: var(--ink-soft);
  background: var(--green-100);
  border: 1px dashed var(--line);
  border-radius: 8px;
  font-size: .58rem;
  line-height: 1.15;
  text-align: center;
  cursor: pointer;
  overflow: hidden;
  transition: color .12s ease, border-color .12s ease;
}
/* The words only fit while it is the whole box; as a small tile the sign says
   it on its own. */
.tr-head-photos .tr-shot--add:not(:first-child) label span { display: none; }
.tr-head-photos .tr-shot--add label:hover,
.tr-head-photos .tr-shot--add label:focus-visible {
  color: var(--green-700);
  border-color: var(--green-600);
  border-style: solid;
}
.tr-head-photos .tr-shot--add label:focus-visible { outline: 2px solid var(--green-600); outline-offset: 2px; }
/* only-mobile is an inline-flex helper for buttons in a row; here the tile is
   a grid cell and the label inside it does the flexing. */
.tr-head-photos .tr-shot--camera { display: none; }
@media (hover: none) and (pointer: coarse) {
  .tr-head-photos .tr-shot--camera { display: block; }
}

/* Remove, and promote. On the tile, and only while it is pointed at: they
   change the record, and a record being read should not be covered in the
   controls for changing it. A touch screen has no pointing, so there they
   stay put. */
.tr-shot-tools {
  position: absolute;
  top: 3px;
  right: 3px;
  display: flex;
  gap: 2px;
  opacity: 0;
  transition: opacity .12s ease;
}
.tr-shot:hover .tr-shot-tools,
.tr-shot:focus-within .tr-shot-tools { opacity: 1; }
@media (hover: none) { .tr-shot-tools { opacity: 1; } }

.tr-shot-tool {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  padding: 0;
  color: var(--ink);
  background: color-mix(in srgb, var(--surface) 88%, transparent);
  border: 1px solid var(--line);
  border-radius: 6px;
  cursor: pointer;
}
.tr-shot-tool:hover { color: var(--green-700); border-color: var(--green-600); }
.tr-shot-tool--warn:hover { color: var(--danger); border-color: var(--danger); }

.tr-head-id h1 {
  margin: 0 0 .2rem;
  font-size: 1.5rem;
  line-height: 1.2;
  color: var(--ink);
}
.tr-head-species { margin: 0 0 .35rem; font-size: .95rem; color: var(--ink); }
.tr-head-species em { color: var(--green-700); font-style: italic; }
.tr-head-facts { margin: 0; font-size: .86rem; color: var(--ink-soft); }

.tr-head-side {
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .55rem;
}
/* A round button and a rectangular one on one line: centred on each other, so
   the round one is not stretched to the height of the other. */
.tr-head-actions {
  display: flex;
  align-items: center;
  gap: .5rem;
  flex-wrap: wrap;
  justify-content: flex-end;
}
.tr-head-meta {
  margin: 0;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: .1rem;
  font-size: .74rem;
  color: var(--ink-soft);
}

/* ---------------------------------------------------------------- sections */
.tr-section { padding: 1.35rem 0 1.15rem; border-bottom: 1px solid var(--line); }
.tr-section:last-of-type { border-bottom: 0; padding-bottom: .2rem; }

/* A section, and the groups inside it, were set at .74rem and .72rem - both
   small, both uppercase, one green and one grey. Read down the page that is
   one long list of headings that all look alike, and no way to tell what
   contains what. The section is a heading now, in the page's own voice; the
   group keeps the small uppercase label. Two ranks, told apart at a glance. */
.tr-section > h2 {
  display: flex;
  align-items: center;
  gap: .5rem;
  margin: 0 0 .9rem;
  font-size: 1rem;
  letter-spacing: -.01em;
  text-transform: none;
  color: var(--ink);
  font-weight: 700;
}
/* A short green stroke before the title: it starts the section without a rule
   across the whole sheet, which would divide it twice over. */
.tr-section > h2::before {
  content: '';
  flex: 0 0 auto;
  width: 3px;
  height: 1.05em;
  border-radius: 2px;
  background: var(--green-600);
}

.tr-col > h3 {
  margin: 0 0 .45rem;
  padding-bottom: .3rem;
  border-bottom: 1px solid var(--line);
  font-size: .66rem;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
  font-weight: 700;
}

/* A thin rule between columns, not a box around each: the columns are one
   table of facts, split only because the page is wide. */
.tr-cols { display: grid; gap: 1.6rem; }
.tr-cols--3 { grid-template-columns: repeat(3, minmax(0, 1fr)); }
.tr-cols--4 { grid-template-columns: repeat(4, minmax(0, 1fr)); }
.tr-cols--2 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
/* Two sections on one row. They keep their own headings and their own rules
   above; only the stacking is dropped. */
.tr-pair {
  display: grid;
  grid-template-columns: minmax(0, 1.25fr) minmax(0, 1fr);
  gap: 0 1.6rem;
}
.tr-pair > .tr-section + .tr-section {
  padding-left: 1.6rem;
  border-left: 1px solid var(--line);
}
@media (max-width: 900px) {
  .tr-pair { grid-template-columns: minmax(0, 1fr); }
  .tr-pair > .tr-section + .tr-section { padding-left: 0; border-left: 0; }
}

.tr-cols > .tr-col + .tr-col { padding-left: 1.6rem; border-left: 1px solid var(--line); }

/* ------------------------------------------------------- label/value pairs */
.tr-list { margin: 0; }

/* A row is a label and its value, side by side.

   They were stacked - the label on one line, the value under it - which reads
   well in a narrow column and badly in a wide one: the record now runs the
   width of the page, so every value sat at the left of half a metre of empty
   paper with its label floating above it. Side by side, the labels line up
   down one edge and the values down another, and a column of thirty-eight
   fields can be scanned rather than read. */
.tr-row {
  display: grid;
  grid-template-columns: minmax(0, 9.5rem) minmax(0, 1fr);
  align-items: baseline;
  column-gap: 1rem;
  row-gap: .05rem;
  padding: .36rem 0;
  border-top: 1px solid var(--line);
}
.tr-row:first-child { border-top: 0; padding-top: 0; }

/* Free text is the exception: a sentence needs the whole width, so its label
   goes back above it rather than stealing a third of the line. */
.tr-row--long { grid-template-columns: minmax(0, 1fr); }

/* A heading for part of a column - the crown spread under the crown's
   measurements. Smaller than the column's own h3, which is what it sits under. */
.tr-sublabel {
  margin: .9rem 0 .35rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

/* The four spreads, across instead of down: one fact about the shape of the
   crown, not four facts that happen to share a unit. Inside here the label
   goes back above its number, because the numbers are being compared with each
   other and want to sit in a row. */
.tr-quad {
  display: grid;
  grid-template-columns: repeat(4, minmax(0, 1fr));
  gap: .5rem;
}
.tr-quad .tr-row {
  grid-template-columns: minmax(0, 1fr);
  padding: 0;
  border-top: 0;
}
.tr-quad .tr-row dt {
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .06em;
  text-transform: uppercase;
}
.tr-quad .tr-row dd { font-variant-numeric: tabular-nums; }

.tr-row dt {
  color: var(--ink-soft);
  font-size: .74rem;
  letter-spacing: .01em;
}
.tr-row dd {
  margin: 0;
  color: var(--ink);
  font-size: .94rem;
  font-weight: 550;
  overflow-wrap: anywhere;
}

/* Free text was written in sentences; it is read in sentences. */
.tr-row--long dd { font-weight: 400; font-size: .9rem; line-height: 1.5; }

.tr-empty { color: var(--ink-soft); font-weight: 400; }
.tr-botanical { font-style: italic; color: var(--green-700); }
.tr-note { display: block; font-size: .7rem; color: var(--ink-soft); font-weight: 400; }

.chip--inline { padding: .12rem .5rem; font-size: .78rem; }

/* --------------------------------------------------- editing, in place

   The control for every editable row is already in the page beside its value;
   all that changes here is which of the two is shown. Nothing moves, nothing
   reflows, and the record a reviewer was reading is still under their eyes
   while they correct it - which is the whole point of not navigating. */
.tr-edit { display: none; }
.tree-record.is-editing .tr-row:not(.tr-row--fixed) .tr-value { display: none; }
.tree-record.is-editing .tr-edit { display: flex; align-items: center; gap: .4rem; }

/* Values that stay read-only while editing say so by going quiet, rather than
   by becoming a box nobody is allowed to type in. */
.tree-record.is-editing .tr-row--fixed dd { color: var(--ink-soft); }

.tr-edit input,
.tr-edit select,
.tr-edit textarea {
  flex: 1 1 auto;
  min-width: 0;
  width: 100%;
  padding: .3rem .45rem;
  font: inherit;
  font-size: .9rem;
  font-weight: 500;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 6px;
}
.tr-edit textarea { resize: vertical; min-height: 3.4rem; line-height: 1.45; }

.tr-edit input:focus,
.tr-edit select:focus,
.tr-edit textarea:focus {
  outline: 2px solid var(--green-600);
  outline-offset: -1px;
  border-color: var(--green-600);
}

/* The unit sits beside the box, never inside it: a diameter is typed as a
   number, and asking for "cm" in the same field is asking for it back out. */
.tr-unit {
  flex: 0 0 auto;
  color: var(--ink-soft);
  font-size: .8rem;
  min-width: 1.6rem;
}

/* ------------------------------------------------------------- the save bar
   Fixed to the bottom, because the record is long and the decision to keep or
   discard belongs wherever the reader has got to - not at the end of a scroll
   they would have to make twice. */
/* Docked to the bottom of the window rather than to the end of the document:
   the decision to keep or discard belongs wherever the reader has got to, and
   a bar that scrolls away is a bar you have to go looking for. */
.tr-savebar {
  position: fixed;
  left: 50%;
  bottom: 1rem;
  transform: translateX(-50%);
  z-index: 70;
  display: flex;
  align-items: center;
  gap: 1.5rem;
  width: min(38rem, calc(100vw - 2rem));
  padding: .6rem .7rem .6rem 1.1rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 12px;
  box-shadow: 0 12px 34px rgba(20, 54, 31, .22);
}

.tr-savebar-state {
  flex: 1 1 auto;
  display: flex;
  align-items: center;
  gap: .45rem;
  margin: 0;
  font-size: .86rem;
  color: var(--ink-soft);
}
/* A dot rather than a colour alone: "unsaved" has to survive being read by
   someone who cannot tell the two greys apart. */
.tr-savebar-state::before {
  content: '';
  width: 7px;
  height: 7px;
  border-radius: 50%;
  background: var(--line);
  flex: 0 0 auto;
}
.tr-savebar-state.is-dirty { color: var(--ink); font-weight: 600; }
.tr-savebar-state.is-dirty::before { background: #c98a12; }
.tr-savebar-state.is-busy::before { background: var(--green-600); animation: savebar-pulse 1s ease-in-out infinite; }
.tr-savebar-state.is-bad { color: var(--danger); font-weight: 600; }
.tr-savebar-state.is-bad::before { background: var(--danger); }
@keyframes savebar-pulse { 50% { opacity: .35; } }
@media (prefers-reduced-motion: reduce) {
  .tr-savebar-state.is-busy::before { animation: none; }
}

.tr-savebar-actions { display: flex; gap: .5rem; flex: 0 0 auto; }
.tr-savebar-actions .btn { min-height: 2.3rem; padding: 0 1rem; }

/* Delete belongs with the other things you can do to the record, not as a
   red slab at the end of it. */
.tr-danger {
  display: none;
  align-items: center;
  gap: .7rem;
  padding: .7rem 0 !important;
  border-bottom: 0 !important;
}
.tree-record.is-editing .tr-danger { display: flex; }
.tr-danger h2 { margin: 0 !important; color: var(--danger) !important; font-size: .95rem; }
/* The stroke before a section title is green everywhere else; this section is
   not one of the register's parts, and the colour should not say it is. */
.tr-danger h2::before { background: var(--danger); }
.tr-danger p { flex: 1 1 auto; margin: 0; font-size: .82rem; }
.tr-danger .btn--danger {
  flex: 0 0 auto;
  min-height: 2.1rem;
  padding: 0 .9rem;
  font-size: .85rem;
  background: transparent;
  color: var(--danger);
  border: 1px solid var(--danger);
}
.tr-danger .btn--danger:hover { background: var(--danger); color: #fff; }

/* The bar floats over the page, so the page needs room under its last line. */
.tree-record.is-editing { padding-bottom: 5.5rem; }

/* --------------------------------------------------------------- location */
.tr-nomap {
  margin: 0;
  padding: 2.5rem 1rem;
  text-align: center;
  color: var(--ink-soft);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 8px;
}

.tr-system { margin-top: 1.3rem; border-top: 1px solid var(--line); padding-top: .8rem; }
.tr-system > summary {
  cursor: pointer;
  display: list-item;
  padding: .4rem 0;
  min-height: 1.9rem;
  font-size: .78rem;
  color: var(--ink-soft);
  font-weight: 600;
  list-style: revert;
}
.tr-system > summary:focus-visible { outline: 2px solid var(--green-600); outline-offset: 3px; }
.tr-list--system { margin-top: .7rem; max-width: 34rem; }
.tr-list--system dd { font-size: .82rem; font-weight: 400; }

/* Four columns want a wide screen; below one they pair up rather than going
   straight from four across to four down. */
@media (max-width: 1280px) and (min-width: 901px) {
  .tr-cols--4 { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

/* ------------------------------------------------------------- narrow view */
@media (max-width: 900px) {
  .tr-cols--4,
  .tr-cols--3,
  .tr-cols--2,
  .tr-cols--map { grid-template-columns: minmax(0, 1fr); }
  /* Stacked, the vertical rule would sit above a column rather than beside it. */
  .tr-cols > .tr-col + .tr-col {
    padding-left: 0;
    border-left: 0;
    padding-top: 1.1rem;
    border-top: 1px solid var(--line);
  }
}

@media (max-width: 560px) {
  /* Below this a 9.5rem label column leaves the value nowhere to go, so label
     and value stack again - which is what they always did here. */
  .tr-row { grid-template-columns: minmax(0, 1fr); }
  .tr-quad { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 680px) {
  /* The sides stay; only the room below changes. The table is inset by the
     same 1.5rem here, and a record that alone touched both edges of a phone
     looked like a different app rather than a closer look at this one. */
  .tree-record { padding: 0 1.5rem 3rem; }
  /* A thumb needs more than an eye does, and this is the only way off the
     record that is not the browser's own button. */
  /* A thumb needs more than an eye does. */
  .tr-back { width: 2.5rem; height: 2.5rem; }
  /* The bar stacks rather than squeezing the two buttons off the edge. */
  .tr-savebar { flex-direction: column; align-items: stretch; gap: .55rem; }
  .tr-savebar-actions > .btn { flex: 1; justify-content: center; }
  .tr-sheet { padding: 0 .9rem 1rem; border-radius: 10px; }
  .tr-head {
    grid-template-columns: auto 1fr;
    gap: .8rem;
  }
  .tr-head-photo { width: 84px; height: 84px; }
  .tr-head-id h1 { font-size: 1.25rem; }
  /* The actions drop below the identity rather than squeezing beside it. */
  .tr-head-side {
    grid-column: 1 / -1;
    align-items: stretch;
  }
  .tr-head-actions { justify-content: flex-start; }
  .tr-head-meta { align-items: flex-start; }
}

/* ==========================================================================
   Admin list, rebuilt to the agreed layout.

   Three zones in the bar - identity, where you are, what you can do - then a
   meta row that answers "how many, which ones, where am I in them", then the
   table. Appended rather than edited in place so every rule here wins on
   source order and the original block stays readable as what it was.
   ========================================================================== */

/* ---------------------------------------------------------------- top bar */
.topbar { justify-content: flex-start; gap: .6rem 1.25rem; }

.topbar-brand { display: inline-flex; align-items: center; gap: .5rem; font-size: 1rem; }
/* The mark is drawn now, not typed: it takes the brand's green rather than
   the ink colour of the name beside it, and needs no font size at all. */
.topbar-mark { display: inline-flex; color: var(--green-700); }
.login-title { display: inline-flex; align-items: center; gap: .5rem; color: var(--green-700); }
.login-title .mark { flex: 0 0 auto; }

.topbar-tabs { display: flex; align-items: center; gap: .35rem; margin-right: auto; }

.topbar-tab {
  position: relative;
  padding: .55rem .2rem;
  margin: 0 .5rem;
  color: var(--ink-soft);
  text-decoration: none;
  font-weight: 600;
  font-size: .92rem;
  white-space: nowrap;
}
.topbar-tab:hover { color: var(--ink); }
.topbar-tab.is-active { color: var(--green-700); }
/* The underline is drawn on a pseudo-element so switching tabs never changes
   the text's own box and nudges the row of tabs sideways. */
.topbar-tab.is-active::after {
  content: "";
  position: absolute;
  left: 0; right: 0; bottom: -.72rem;
  height: 2px;
  background: var(--green-600);
  border-radius: 2px;
}

/* ------------------------------------------------------------ user menu */
.topbar-user { position: relative; }
.topbar-user > summary {
  display: inline-flex;
  align-items: center;
  gap: .25rem;
  padding: .2rem .35rem .2rem .2rem;
  border-radius: 999px;
  cursor: pointer;
  list-style: none;
  color: var(--ink-soft);
}
.topbar-user > summary::-webkit-details-marker { display: none; }
.topbar-user > summary:hover { background: var(--green-100); color: var(--ink); }

.avatar {
  display: inline-grid;
  place-items: center;
  width: 30px; height: 30px;
  border-radius: 50%;
  background: var(--green-600);
  color: #fff;
  font-size: .72rem;
  font-weight: 700;
  letter-spacing: .02em;
}

.user-menu {
  position: absolute;
  right: 0;
  top: calc(100% + .5rem);
  min-width: 12.5rem;
  padding: .35rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  z-index: 30;
}
.user-menu-head {
  margin: .25rem .5rem .15rem;
  font-size: .68rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.user-menu-item {
  display: block;
  width: 100%;
  padding: .45rem .5rem;
  border: 0;
  border-radius: 8px;
  background: none;
  font: inherit;
  text-align: left;
  color: var(--ink);
  text-decoration: none;
  cursor: pointer;
}
.user-menu-item:hover { background: var(--green-100); }
.user-menu-item.is-active { color: var(--green-700); font-weight: 600; }
.user-menu-signout { color: var(--danger); }
.user-menu hr { margin: .35rem .25rem; border: 0; border-top: 1px solid var(--line); }

/* ------------------------------------------------- count / paging / meta */
.list-meta {
  display: flex;
  align-items: center;
  gap: .75rem 1rem;
  flex-wrap: wrap;
  margin: 0 .15rem .6rem;
}
.list-count { margin: 0; font-size: .92rem; color: var(--ink-soft); }
.list-count strong { color: var(--ink); font-size: 1rem; }

.list-paging { display: flex; align-items: center; gap: .4rem; margin-left: auto; }
.list-range { font-size: .82rem; color: var(--ink-soft); }

.page-arrow {
  display: inline-grid;
  place-items: center;
  width: 32px; height: 32px;
  border: 1px solid var(--line);
  border-radius: 9px;
  background: var(--surface);
  color: var(--ink);
}
.page-arrow:hover { background: var(--green-100); border-color: var(--green-600); }
/* Disabled, not hidden: an arrow that vanishes at the first and last page
   makes the pair jump sideways every time you reach an end. */
.page-arrow.is-disabled { opacity: .38; pointer-events: none; }

/* --------------------------------------------------------------- table */
/* Centred, headers and values alike: the same rule sets both, so a value can
   never drift out from under the heading it belongs to. */
.tree-table th, .tree-table td { text-align: center; padding: .8rem .9rem; }
.tree-table tbody td { color: var(--ink); }
.tree-table tbody .row-link { color: var(--green-700); font-weight: 600; }

.col-qr { white-space: nowrap; width: 1%; }

/* --------------------------------------------------------- more filters */
/* Was phone-only. It now holds the seven categorical fields, which exist on
   every screen, so the button does too. */
@media (min-width: 720px) {
  .filters-open { display: inline-flex; }
}

/* The seven categorical filters.

   On a laptop they are a panel that drops under the filter row when "More
   filters" is pressed - inline they would make ten dropdowns across one line,
   which is the thing that row was kept short to avoid. On a phone they are
   simply more of the existing sheet, so `display: contents` lets each field
   flow into it and inherit its behaviour untouched.

   Each field is a label+select pair in its own box. As bare grid children the
   label and the select would land in adjacent columns and every dropdown would
   be captioned by its neighbour. */
.filters-extra { display: contents; }
.filters-field { display: flex; flex-direction: column; gap: .25rem; }

@media (min-width: 721px) {
  .filters-extra { display: none; }

  .filters-advanced.is-open .filters-extra {
    display: grid;
    flex-basis: 100%;
    width: 100%;
    grid-template-columns: repeat(auto-fit, minmax(190px, 1fr));
    gap: .6rem .75rem;
    margin-top: .35rem;
    padding: .85rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--green-100) 45%, var(--surface));
  }
  /* Labels are hidden in the one-line row; inside the panel they are the only
     thing saying which field a dropdown belongs to. */
  .filters-extra .filters-label {
    display: block;
    font-size: .72rem;
    font-weight: 700;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--ink-soft);
  }
  .filters-extra select { width: 100%; }
}

/* --------------------------------------------------------------------------
   "More filters" on a laptop.

   Its whole appearance used to live inside the max-width:720px block, because
   the button was phone-only. Shown on a laptop it inherited nothing and
   rendered as a bare browser button next to styled controls.

   Position comes from `order`, not from moving it in the markup: on a phone the
   button has to sit OUTSIDE .filters-advanced, since that is the sheet it
   opens. Both parents are display:contents, so every control is a flex item of
   .filters and can be resequenced here without touching the DOM.
   -------------------------------------------------------------------------- */
@media (min-width: 721px) {
  .field-search { order: 1; }
  .filters-advanced > select { order: 2; }
  .filters-open { order: 3; }
  /* Hidden as soon as JS takes over. Ordered anyway so that if it ever does
     not, the fallback row still reads search - filters - search button rather
     than putting the submit first, where an unordered flex item lands. */
  .filters [data-search-button] { order: 4; }
  .filters-clear { order: 5; }
  /* Last, so the panel opens as a full-width row under everything else. */
  .filters-advanced.is-open .filters-extra { order: 6; }

  .filters-open {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    flex: 0 0 auto;
    /* Matches the dropdowns beside it: same height, radius and border, so the
       row reads as one set of controls rather than a button bolted on. */
    min-height: 44px;
    margin-top: .25rem;
    padding: .6rem .85rem;
    font: inherit;
    font-size: .92rem;
    font-weight: 600;
    color: var(--ink);
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 9px;
    cursor: pointer;
    transition: background .12s, border-color .12s, color .12s;
  }
  .filters-open svg { color: var(--ink-soft); transition: color .12s; }

  .filters-open:hover {
    border-color: var(--green-600);
    background: color-mix(in srgb, var(--green-100) 55%, var(--surface));
  }
  .filters-open:hover svg { color: var(--green-700); }

  /* Open, and filtering, are different facts and look different. Open is the
     panel being visible; the badge is how many filters are actually applied,
     which survives the panel being closed again. */
  .filters-open[aria-expanded="true"] {
    color: var(--green-700);
    border-color: var(--green-600);
    background: var(--green-100);
  }
  .filters-open[aria-expanded="true"] svg { color: var(--green-700); }

  .filters-open:focus-visible {
    outline: 2px solid var(--green-600);
    outline-offset: 1px;
  }

  .filters-badge {
    display: inline-grid;
    place-items: center;
    min-width: 20px;
    height: 20px;
    padding: 0 .35rem;
    border-radius: 999px;
    background: var(--green-600);
    color: #fff;
    font-size: .72rem;
    font-weight: 700;
    line-height: 1;
  }
}

/* --------------------------------------------------------------------------
   The admin surface is plain.

   The mesh wash, the leaf motif and the tree are for the page a QR code opens
   - read once, on a phone, standing under the tree itself. The working surface
   is a table of 88,134 rows read for minutes at a time, where the same
   decoration is only texture behind data.

   It applies to the tree page too. That page is reached by scanning a QR in
   the field, which was the argument for keeping the decoration there - but it
   is also the page an admin edits measurements on, and it should not be the
   one surface in the app that looks like a different application.
   -------------------------------------------------------------------------- */
body.admin {
  background-color: var(--bg);
  background-image: none;
}
body.admin::before { content: none; }

/* ==========================================================================
   Map tools

   A card on the map, in the page's own light palette rather than a dark panel
   borrowed from the pipeline: this sits inside the register's chrome, and one
   surface that ignores the rest reads as a different application embedded in
   this one.
   ========================================================================== */
.map-panel { position: relative; }

.review-panel {
  /* Dark by token, not by rewriting every rule below: the panel sits ON the
     imagery, where a light card glares and washes out the canopy under it.
     Redefining the palette here means the components inside carry on asking
     for --surface and --ink and simply get darker answers. */
  --surface: #1f242c;
  --bg: #262c36;
  --line: #333b47;
  --ink: #e8ebef;
  --ink-soft: #9aa4b2;
  --green-100: #2b3442;
  --green-600: #3d8bfd;
  --green-700: #7fb8ff;
  --ok-bg: rgba(48, 163, 108, .18);
  --warn-bg: rgba(255, 176, 46, .16);

  position: absolute;
  top: 12px;
  left: 12px;
  z-index: 600;
  width: min(300px, calc(100% - 24px));
  max-height: calc(100% - 24px);
  overflow-y: auto;
  padding: .8rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 14px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, .38);
  font-size: .86rem;
}

/* ---------------------------------------------------------- title bar */
.rp-titlebar { display: flex; align-items: center; gap: .5rem; }
.rp-titlebar svg { color: var(--green-700); flex: 0 0 auto; }
.rp-titlebar strong { flex: 1 1 auto; font-size: .92rem; }
.rp-collapse {
  flex: 0 0 auto;
  display: grid;
  place-items: center;
  width: 26px; height: 26px;
  padding: 0;
  color: var(--ink-soft);
  background: none;
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
.rp-collapse:hover { color: var(--ink); background: var(--green-100); }
/* The chevron points the way the panel will go, not the way it is. */
.rp-collapse.is-closed svg { transform: rotate(180deg); }

/* ------------------------------------------------------------- search */
.rp-search { position: relative; display: flex; gap: .4rem; margin-top: .7rem; }
.rp-search svg {
  position: absolute;
  left: .6rem; top: 50%;
  transform: translateY(-50%);
  color: var(--ink-soft);
  pointer-events: none;
}
.rp-search input {
  flex: 1 1 auto;
  min-width: 0;
  margin: 0;
  min-height: 36px;
  padding: .35rem .5rem .35rem 2rem;
  font-size: .85rem;
  border-radius: 9px;
}
.rp-search button {
  flex: 0 0 auto;
  padding: .35rem .8rem;
  font: inherit;
  font-size: .85rem;
  font-weight: 600;
  color: #fff;
  background: var(--green-600);
  border: 0;
  border-radius: 9px;
  cursor: pointer;
}
.rp-search button:hover { background: var(--green-700); }

/* -------------------------------------------------------------- tools */
.rp-tools { display: flex; gap: .3rem; margin-top: .6rem; }
.rp-tool {
  /* Equal basis with no growth: the row keeps its columns when the active
     tool changes or a label translates longer. */
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .2rem;
  padding: .45rem .1rem;
  font: inherit;
  font-size: .66rem;
  font-weight: 600;
  color: var(--ink-soft);
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 9px;
  cursor: pointer;
}
.rp-tool span { overflow: hidden; text-overflow: ellipsis; max-width: 100%; }
.rp-tool:hover { color: var(--ink); border-color: var(--green-600); }
.rp-tool.is-active {
  color: #fff;
  background: var(--green-600);
  border-color: var(--green-600);
}

.rp-hint { margin: .6rem 0 0; color: var(--ink-soft); line-height: 1.4; font-size: .78rem; }

/* ------------------------------------------------------------ banner */
.rp-banner {
  margin-top: .6rem;
  padding: .55rem;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 10px;
}
.rp-banner p { margin: 0 0 .45rem; font-size: .8rem; color: var(--ink-soft); }
.rp-banner button {
  width: 100%;
  padding: .4rem;
  font: inherit;
  font-size: .82rem;
  font-weight: 600;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: 8px;
  cursor: pointer;
}
.rp-banner button:hover { border-color: var(--green-600); color: var(--green-700); }

/* ------------------------------------------------------------ fields */
.rp-field { display: flex; flex-direction: column; gap: .15rem; margin-top: .6rem; }
.rp-row { display: flex; gap: .5rem; }
.rp-row .rp-field { flex: 1 1 0; min-width: 0; }
.rp-label {
  font-size: .66rem;
  font-weight: 700;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--ink-soft);
}
.review-panel select,
.review-panel input[type="number"] {
  width: 100%;
  margin: 0;
  min-height: 34px;
  padding: .3rem .5rem;
  font-size: .84rem;
  border-radius: 9px;
}
.rp-run {
  width: 100%;
  margin-top: .5rem;
  padding: .45rem;
  font: inherit;
  font-size: .85rem;
  font-weight: 700;
  color: #fff;
  background: var(--green-600);
  border: 0;
  border-radius: 9px;
  cursor: pointer;
}

/* One status line, three meanings. Colour carries the difference, the words
   carry the content - neither is asked to do the other's job. */
.rp-status {
  margin: .55rem 0 0;
  padding: .4rem .55rem;
  border-radius: 9px;
  font-size: .8rem;
  background: var(--bg);
  color: var(--ink-soft);
}
.rp-status.is-ok { background: var(--ok-bg); color: var(--green-700); }
.rp-status.is-warn { background: var(--warn-bg); color: #ffc978; }
.rp-status.is-bad { background: rgba(229, 72, 77, .18); color: #ff9b9e; }

.rp-list {
  margin: .5rem 0 0;
  padding: 0;
  list-style: none;
  max-height: 180px;
  overflow-y: auto;
  border-top: 1px solid var(--line);
}
.rp-list li {
  display: flex;
  justify-content: space-between;
  gap: .5rem;
  padding: .3rem .1rem;
  border-bottom: 1px solid var(--line);
}
.rp-list a { color: var(--green-700); font-weight: 600; text-decoration: none; }
.rp-list b { font-variant-numeric: tabular-nums; }
.rp-foot { margin: .4rem 0 0; font-size: .74rem; color: var(--ink-soft); }

/* -------------------------------------------------- sections and rows */
.rp-section { margin-top: .8rem; border-top: 1px solid var(--line); padding-top: .55rem; }
.rp-sechead {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: 0;
  font: inherit;
  font-size: .88rem;
  font-weight: 700;
  color: var(--ink);
  background: none;
  border: 0;
  cursor: pointer;
}
.rp-sechead > span { flex: 1 1 auto; text-align: left; }
.rp-sechead svg { color: var(--ink-soft); transition: transform .15s; }
.rp-sechead[aria-expanded="false"] svg { transform: rotate(-90deg); }
.rp-active {
  padding: .1rem .4rem;
  border-radius: 999px;
  background: var(--green-100);
  color: var(--green-700);
  font-size: .7rem;
  font-weight: 700;
}
.rp-secbody { margin-top: .3rem; }

.rp-subhead {
  margin: .5rem 0 .15rem;
  font-size: .64rem;
  font-weight: 700;
  letter-spacing: .08em;
  text-transform: uppercase;
  color: var(--ink-soft);
}

.rp-check { display: flex; align-items: center; gap: .5rem; padding: .28rem .1rem; cursor: pointer; }
.rp-check input { width: 15px; height: 15px; margin: 0; accent-color: var(--green-600); }
.rp-check span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
/* The dot is the map's own key: the colour beside a name is the colour that
   name is drawn in, so the legend needs no separate explanation. */
.rp-dot { width: 10px; height: 10px; flex: 0 0 auto; border-radius: 50%; background: var(--green-600); }
.rp-dot.is-register { background: #e8452c; }
.rp-dot.is-flagged { background: transparent; border: 2px solid var(--danger); }
.rp-count { color: var(--ink-soft); font-size: .78rem; font-variant-numeric: tabular-nums; }

.rp-minisearch input { width: 100%; margin: 0; min-height: 32px; padding: .25rem .5rem; font-size: .82rem; border-radius: 8px; }
.rp-linkish {
  margin: .35rem 0 .1rem;
  padding: 0;
  font: inherit;
  font-size: .78rem;
  font-weight: 600;
  color: var(--green-700);
  background: none;
  border: 0;
  cursor: pointer;
}
.rp-species { margin: .2rem 0 0; padding: 0; list-style: none; max-height: 170px; overflow-y: auto; }
.rp-species-row {
  display: flex;
  align-items: center;
  gap: .5rem;
  width: 100%;
  padding: .25rem .35rem;
  font: inherit;
  font-size: .8rem;
  color: var(--ink);
  background: none;
  border: 0;
  border-radius: 7px;
  cursor: pointer;
  text-align: left;
}
.rp-species-row span { flex: 1 1 auto; min-width: 0; overflow: hidden; text-overflow: ellipsis; }
.rp-species-row b { color: var(--ink-soft); font-size: .74rem; font-variant-numeric: tabular-nums; }
.rp-species-row:hover { background: var(--green-100); }
.rp-species-row.is-active { background: var(--green-100); color: var(--green-700); font-weight: 700; }

/* ------------------------------------------------------- the tree card
   What the map says about one tree, shown by pointing at it.

   Read top to bottom rather than across: which tree, what it is, then the
   three measurements one under another, each on its own line with the sign of
   what it measures. Three numbers side by side had to be read as a row of
   headings and a row of values, two passes for three facts; down the card each
   one is a line you stop at or skip.

   Nothing here is a control except the last thing, which is the whole width of
   the card - the card is passed over, not aimed at, so the one thing worth
   clicking is the one thing hard to miss. */
.tree-popup .leaflet-popup-content-wrapper { border-radius: 14px; padding: 0; }
.tree-popup .leaflet-popup-content { margin: 0; width: 250px !important; }

.tree-card { padding: .85rem .85rem .8rem; font-size: .84rem; }

.tc-photo {
  display: block;
  width: 100%;
  height: 96px;
  margin-bottom: .7rem;
  border-radius: 10px;
  object-fit: cover;
}

/* The close-up cut out of the orthophoto - see orthoShot in the list view.

   A window onto a small grid of tiles: the frame is laid out in the tile
   service's own pixels and centred on the tree, and the box around it crops
   whatever the card's width happens to be. Sizing the tiles to the card
   instead would scale them, and a 4 cm orthophoto stretched is just a blurry
   one. */
.tc-shot {
  position: relative;
  width: 100%;
  height: 156px;
  margin-bottom: .7rem;
  border-radius: 10px;
  overflow: hidden;
  background: var(--green-100);
}
.tc-shot-frame {
  position: absolute;
  left: 50%;
  top: 50%;
  width: 320px;
  height: 200px;
  transform: translate(-50%, -50%);
}
/* max-width: none, against the page-wide img rule that would squash a tile
   into the frame and pull the crop off centre. */
.tc-tile {
  position: absolute;
  width: 256px;
  height: 256px;
  max-width: none;
}

.tc-head strong { display: block; font-size: 1.02rem; letter-spacing: -.01em; }
.tc-head em { display: block; margin-top: .1rem; color: var(--ink-soft); font-size: .85rem; }

/* The three across, not down.

   Down the card each was a line to stop at; across, the three are read as one
   row of figures about one tree - which is how they are read in the table the
   card came from, and the card is now short enough to hold them that way. */
.tc-measures {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: .35rem;
  margin: .75rem 0 .8rem;
}
.tc-measure {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .12rem;
  text-align: center;
  min-width: 0;
}
/* The sign, not a button: thin, and the colour of the label it belongs to.
   A shade smaller than it was, now that three of them share one row. */
.tc-measure svg { flex: 0 0 auto; width: 18px; height: 18px; color: var(--ink-soft); }
/* Two lines' worth of room whether the label needs two or one, so that "DBH"
   and "CROWN WIDTH" end level and the three numbers under them sit on one
   line rather than a staircase. */
.tc-measure span {
  display: flex;
  align-items: flex-end;
  justify-content: center;
  min-height: 2.3em;
  font-size: .55rem;
  font-weight: 700;
  letter-spacing: .04em;
  line-height: 1.15;
  text-transform: uppercase;
  color: var(--ink-soft);
}
/* The numbers are what the row is for; they keep their size. */
.tc-measure b { font-size: .9rem; font-variant-numeric: tabular-nums; white-space: nowrap; }

/* The way out of the card, and the only thing in it that does anything. */
.tc-go {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: .4rem;
  padding: .6rem .7rem;
  color: #fff;
  background: var(--green-700);
  border-radius: 10px;
  font-size: .86rem;
  font-weight: 600;
  text-decoration: none;
  transition: background .12s ease;
}
.tc-go:hover { background: var(--green-900); }
.tc-go:focus-visible { outline: 2px solid var(--green-600); outline-offset: 2px; }
/* The dark green goes lighter in the dark theme, not darker, or the button
   would disappear into the card it sits on. */
:root:not([data-theme="light"]) .tc-go { color: var(--green-900); }
:root[data-theme="dark"] .tc-go { color: var(--green-900); }

/* ----------------------------------------------------------- legend */
.map-legend {
  padding: .55rem .7rem;
  background: color-mix(in srgb, var(--surface) 94%, transparent);
  border: 1px solid var(--line);
  border-radius: 10px;
  box-shadow: var(--shadow);
  font-size: .76rem;
  line-height: 1.7;
}
.map-legend strong { display: block; margin-bottom: .15rem; font-size: .7rem; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-soft); }
.map-legend span { display: flex; align-items: center; gap: .45rem; }
.lg-dot { width: 9px; height: 9px; border-radius: 50%; flex: 0 0 auto; }
.lg-dot.is-register { background: #e8452c; }
/* The blue of a tree the map added and nobody has confirmed - the same blue
   the marker is drawn in, written once in each place it is needed. */
.lg-dot.is-new { background: #2f6fed; }
.rp-dot.is-new { background: #2f6fed; }
/* A ring, like the markers it stands for: something was here. */
.rp-dot.is-removed { background: rgba(179, 38, 30, .15); border: 2px solid #b3261e; }

/* The way back, inside the message that says what was rubbed out. */
.rp-undo {
  margin-left: .4rem;
  padding: 0;
  font: inherit;
  font-weight: 700;
  color: var(--green-700);
  background: none;
  border: 0;
  text-decoration: underline;
  cursor: pointer;
}
.rp-undo:hover { color: var(--green-900); }
.rp-undo:disabled { opacity: .5; cursor: default; }
.lg-dot.is-flagged { background: transparent; border: 2px solid var(--danger); }

/* ------------------------------------------------- "where am I" — map-locate.js

   Blue, and nothing else on either map is: the one thing that must never be
   mistaken for a tree is the reader standing among them. */
/* The map plus the things that float over it. .map-panel is a flex column, so
   the wrapper is the flex item that grows and the map fills it - without this
   the map's height: 100% resolves against an auto-height parent and collapses
   the moment the view goes full-screen. */
.all-map-wrap,
.map-locate a { display: flex; align-items: center; justify-content: center; }
.map-locate a.is-on { color: #1c6dd0; }

.locate-halo {
  fill: #1c6dd0;
  fill-opacity: .12;
  stroke: #1c6dd0;
  stroke-opacity: .45;
  stroke-width: 1;
}

/* The dot breathes so it reads as "now" rather than as another marker that
   happens to be blue. Stilled for anyone who asked for less motion. */
.locate-dot { animation: locate-pulse 2.4s ease-in-out infinite; }
@keyframes locate-pulse {
  0%, 100% { stroke-opacity: 1; }
  50% { stroke-opacity: .45; }
}
@media (prefers-reduced-motion: reduce) {
  .locate-dot { animation: none; }
}

.locate-label.leaflet-tooltip {
  padding: .3rem .5rem;
  border: 1px solid var(--line);
  border-radius: 8px;
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  color: var(--ink);
  box-shadow: var(--shadow);
  font-size: .74rem;
  line-height: 1.5;
  white-space: nowrap;
}
.locate-label.leaflet-tooltip::before { display: none; }
.locate-label b { color: var(--green-700); }

/* Errors and "finding you…", over the map rather than in the hint line, which
   is rewritten on every pan. */
.locate-note {
  position: absolute;
  left: 50%;
  bottom: .9rem;
  transform: translateX(-50%);
  z-index: 500;
  max-width: min(30rem, calc(100% - 2rem));
  margin: 0;
  padding: .45rem .75rem;
  border: 1px solid var(--line);
  border-radius: 999px;
  background: color-mix(in srgb, var(--surface) 96%, transparent);
  box-shadow: var(--shadow);
  font-size: .78rem;
  text-align: center;
}
.locate-note.is-bad { border-color: var(--danger); color: var(--danger); }


@media (max-width: 720px) {
  /* On a phone an overlay would cover the map it is meant to work on. */
  .review-panel {
    position: static;
    width: auto;
    max-height: none;
    margin-bottom: .5rem;
  }
}

/* --------------------------------------------------------------------------
   Map view, edge to edge.

   In map view the page IS the map: the padding, the filter row and the hint
   line under it all become frame around the thing being looked at. The filter
   row is hidden rather than removed - it still holds the state the panel's own
   filters drive, so it has to stay in the document.

   --topbar-h is measured in JS rather than guessed: the bar's height changes
   with the font and the language, and a map that is 4px too tall puts a
   scrollbar on a full-screen view.
   -------------------------------------------------------------------------- */
body.map-full { overflow: hidden; }
body.map-full .admin-page { padding: 0; gap: 0; }
body.map-full .filters,
body.map-full .map-hint,
body.map-full .alert { display: none; }

body.map-full .map-panel { height: calc(100dvh - var(--topbar-h, 54px)); }
body.map-full .all-map-wrap { flex: 1; min-height: 0; }
body.map-full .all-map {
  height: 100%;
  max-height: none;
  border-radius: 0;
  border-left: 0;
  border-right: 0;
  border-bottom: 0;
}

/* Fullscreen is taken on the whole section, panel included - see below - so it
   is the section that has to fill the screen, not just the map inside it. */
.map-panel:fullscreen { height: 100%; background: var(--bg); }
.map-panel:fullscreen .all-map-wrap { flex: 1; min-height: 0; }
.map-panel:fullscreen .all-map { height: 100%; border: 0; border-radius: 0; }

/* The button that opens the tools. Marked when the panel is open, so the pair
   reads as one control rather than two things that happen to agree. */
.map-tools-toggle a { display: flex; align-items: center; justify-content: center; }
.map-tools-toggle a.is-active { background: var(--green-600); color: #fff; }

/* --------------------------------------------------------------------------
   Filling the viewport without the Fullscreen API

   iPhone Safari has it for <video> only, so the button falls back to this.
   position:fixed against the viewport rather than height:100% against a
   parent, because every ancestor here has a height of its own that would
   otherwise win.
   -------------------------------------------------------------------------- */
.is-faux-fullscreen {
  position: fixed;
  inset: 0;
  z-index: 2000;
  height: 100dvh;
  width: 100vw;
  margin: 0;
  background: var(--bg);
}
.is-faux-fullscreen .all-map {
  height: 100%;
  max-height: none;
  border: 0;
  border-radius: 0;
}
/* Nothing behind it should scroll while it is up. */
body.has-faux-fullscreen { overflow: hidden; }

/* These two have to out-specify `body.map-full .map-panel`, which sets
   height: calc(100dvh - topbar) - room for a top bar that is not on screen
   once the map covers it. Without them the map stops a top-bar's height short
   of the bottom and leaves a black band there, which on a phone is large:
   the bar wraps to two rows. */
body.has-faux-fullscreen .map-panel.is-faux-fullscreen,
body.map-full .map-panel.is-faux-fullscreen {
  height: 100dvh;
  max-height: 100dvh;
}
body.map-full .map-panel:fullscreen { height: 100%; max-height: none; }

/* A phone must never scroll sideways. If something overflows anyway, it is
   clipped here rather than dragging the whole page off its own left edge -
   which is how a header ends up half off-screen with no way to see it. */
@media (max-width: 720px) {
  html, body { overflow-x: hidden; }
  .admin-page { max-width: 100%; overflow-x: hidden; }
}
