/* Outback Deploy — application styles, on the Outback Drone brand system v1.0.
 *
 * The brand is sharp by design: square corners, hairline rules, no soft
 * shadows. Night Sky leads and carries authority; Signal Orange is spent only
 * on action and active states; Eucalyptus confirms success. Geist Sans carries
 * headlines, interface and body; Geist Mono is reserved for data, labels and
 * coordinates — which in this app means counts, byte sizes, statuses and
 * everything read off a survey.
 *
 * Field-office constraints still hold on top of the brand: high contrast, large
 * hit targets, and progress legible at a glance from across a desk.
 */

/* ---------- brand constants (fixed, never themed) ---------- */

:root {
    --night: #101514;       /* Night Sky      — authority, 56% of the mix */
    --night-deep: #090d0c;  /*                — footer / masthead in dark */
    --orange: #ff5a1f;      /* Signal Orange  — action only, 13% */
    --orange-hi: #ff713f;   /*                — hover, from the web build */

    /*
     * The action colour for *text*, which is not the same as the action colour
     * for a filled button.
     *
     * Signal Orange is a fixed brand constant and never themed. On the dark
     * surface it reads at 5.48:1; on the light one it is 3.07:1 — below AA, and
     * it is the whole secondary-control style of the Train pages ("Delete",
     * "Add Tree", "Use this one"). Measured with scripts/visual/contrast.js,
     * which is also how the masthead's 1.07:1 was found. Darkened here for
     * light and returned to the brand orange under dark below.
     */
    --action: #b23c10;
    --green: #8bea88;       /* Eucalyptus     — success, 6% */
    --sand: #ede7d9;        /* Mineral Sand   — warmth, 25% */
    --paper: #f7f4ec;
    --white: #fffdf8;

    /* Geist is the brand face. It is not vendored here, so the fallback is the
     * nearest neutral grotesque the machine already has. */
    --font-sans: "Geist", "Geist Sans", ui-sans-serif, system-ui, -apple-system,
                 "Segoe UI", "Helvetica Neue", Arial, sans-serif;
    --font-mono: "Geist Mono", ui-monospace, "SF Mono", "JetBrains Mono",
                 "Roboto Mono", Menlo, Consolas, monospace;

    /* Type scale, from the guide: 72 48 28 18 14 11. */
    --fs-display: clamp(2.75rem, 6.4vw, 4.5rem);
    --fs-1: clamp(2rem, 3.6vw, 3rem);
    --fs-2: 1.75rem;
    --fs-3: 1.125rem;
    --fs-4: .875rem;
    --fs-5: .6875rem;

    /* Sharp by design. Kept as a token so the intent is explicit rather than
     * looking like an omission. */
    --radius: 0px;

    --gap: .75rem;
    --pad: 1.375rem;
}

/* ---------- themed surfaces ---------- */

:root {
    /*
     * How tall the masthead is, as a number the masthead is then *held* to
     * rather than a measurement of it — `.masthead-inner` takes its min-height
     * from here, so the two cannot drift apart.
     *
     * It exists because the map is full-bleed: `.mapwrap` starts at y=0 and the
     * masthead is painted over the top of it. Every other map overlay hangs off
     * the bottom edge, so nothing needed this until the results panel and its
     * tab were anchored to the top — and the tab landed entirely behind the
     * masthead, where clicking it opened the account menu instead. `--chrome-top`
     * is a different, smaller number used by the Leaflet control corners; it is
     * not the masthead's height and cannot be used for clearing it.
     */
    --masthead-h: 4.5rem;

    --bg: var(--paper);
    --surface: var(--white);
    --surface-2: var(--sand);
    --fg: #171c1b;
    --fg-muted: #5d635f;
    --fg-faint: #8b8f8b;
    --line: #cfd2ca;
    --line-soft: #dfe1d9;

    --chrome: var(--night);          /* masthead — Night Sky leads */
    --chrome-fg: var(--white);
    --chrome-muted: rgba(255, 253, 248, .62);
    --chrome-line: rgba(255, 253, 248, .18);

    /* Eucalyptus is a light mint: it reads as a fill on Night Sky but not as
     * text on paper, so light mode uses a darkened tint of the same hue. */
    --ok: #2f7331;
    --ok-tint: rgba(139, 234, 136, .3);
    /* The palette has no failure colour. Signal Orange is committed to action,
     * so error gets a cooler, darker red that cannot be mistaken for it. */
    --alert: #c4342b;
    --alert-tint: rgba(196, 52, 43, .1);
    --active: var(--orange);
    --active-tint: rgba(255, 90, 31, .1);
}

@media (prefers-color-scheme: dark) {
    :root {
        --bg: var(--night);
        --surface: #171d1c;
        --surface-2: #1f2624;
        --fg: #f2efe7;
        --fg-muted: #a6aca7;
        --fg-faint: #767c78;
        --line: #2c3331;
        --line-soft: #232928;

        --chrome: var(--night-deep);
        --chrome-fg: var(--white);
        --chrome-muted: rgba(255, 253, 248, .58);
        --chrome-line: rgba(255, 253, 248, .13);

        /* On Night Sky the brand orange clears AA comfortably. */
        --action: var(--orange);

        --ok: var(--green);
        --ok-tint: rgba(139, 234, 136, .12);
        --alert: #f4685c;
        --alert-tint: rgba(244, 104, 92, .12);
        --active-tint: rgba(255, 90, 31, .14);
    }
}

/* ---------- base ---------- */

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

body {
    margin: 0;
    background: var(--bg);
    color: var(--fg);
    font: 1rem/1.55 var(--font-sans);
    -webkit-font-smoothing: antialiased;
    text-rendering: optimizeLegibility;
}

::selection { background: var(--orange); color: var(--night); }

a { color: inherit; }
a:hover { color: var(--orange); }

img, svg { display: block; max-width: 100%; }

:where(a, button, input, select, [tabindex]):focus-visible {
    outline: 2px solid var(--orange);
    outline-offset: 2px;
}

code {
    font: 500 .875em/1.4 var(--font-mono);
    background: var(--surface-2);
    border: 1px solid var(--line-soft);
    padding: .1em .35em;
    color: var(--fg);
}

.wrap { width: min(1180px, 100% - 3rem); margin-inline: auto; }

main { padding: 2.5rem 0 4rem; }

/* ---------- typography ---------- */

h1, h2, h3 {
    margin: 0 0 .35rem;
    font-weight: 640;
    line-height: 1.02;
    letter-spacing: -.045em;
}

h1 { font-size: var(--fs-2); }
h2 { font-size: var(--fs-3); margin-bottom: .9rem; letter-spacing: -.03em; }
h3 { font-size: 1rem; letter-spacing: -.02em; }

/* Headline accents. The guide sets the second clause in Signal Orange. */
h1 em, h2 em, .display em { color: var(--orange); font-style: normal; }

.lede {
    color: var(--fg-muted);
    font-size: var(--fs-3);
    line-height: 1.55;
    max-width: 62ch;
    margin: 0 0 2rem;
}

/* Mono, uppercase, wide-tracked: the guide's label voice. */
.eyebrow, label, th, .status, .picker-head h1, .project h3, .opacity,
.facts dt, .footer, .file .name, .overall .meta {
    font-family: var(--font-mono);
}

.eyebrow {
    display: flex;
    align-items: center;
    gap: .625rem;
    font-size: var(--fs-5);
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin: 0 0 1.125rem;
}

.eyebrow::before {
    content: "";
    width: 7px;
    height: 7px;
    border-radius: 50%;
    background: var(--orange);
    flex: none;
}

.hint { font-size: var(--fs-4); color: var(--fg-faint); margin: .5rem 0 0; line-height: 1.5; }

/* A subtitle that is really a row of measurements, so it is set as data. */
.lede.meta-line {
    font: 500 var(--fs-4)/1.6 var(--font-mono);
    letter-spacing: .03em;
    color: var(--fg-faint);
}

/* ---------- masthead ---------- */

.masthead {
    background: var(--chrome);
    color: var(--chrome-fg);
    border-bottom: 1px solid var(--chrome-line);
    position: sticky;
    top: 0;
    z-index: 600;
}

.masthead-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: 1.25rem;
    /* 4.5rem bar around a 1.5rem mark leaves exactly one mark width of clear
     * space above and below it, which is the rule. Through --masthead-h, so
     * that map overlays clearing the masthead clear the real one. */
    min-height: var(--masthead-h);
    padding: .75rem 0;
}

/* Brand lockup. The mark inherits currentColor so it reverses cleanly on Night
 * Sky, per the approved reverse variant. Clear space is one mark width. */
.brand {
    display: inline-flex;
    align-items: center;
    gap: .75rem;
    color: inherit;
    text-decoration: none;
    padding-right: 1.5rem;   /* one mark width of clear space */
    flex: none;
}

.brand:hover { color: inherit; }

/* The mark's inner node is a knock-out, so it has to carry whatever it sits on.
 * --mark-hole is read by the inline SVG. */
.brand-mark {
    width: 1.5rem;
    height: 1.5rem;
    flex: none;
    color: inherit;
    --mark-hole: var(--bg);
}

.masthead .brand-mark { --mark-hole: var(--chrome); }
.brand-mark svg { width: 100%; height: 100%; display: block; }

.brand-name {
    display: flex;
    gap: .375rem;
    font-size: .8125rem;
    letter-spacing: .08em;
    line-height: 1;
    white-space: nowrap;
}

.brand-name strong { font-weight: 780; }
.brand-name span { font-weight: 430; opacity: .65; }

.nav { display: flex; gap: 2rem; margin-left: auto; margin-right: 1.75rem; }

.nav a {
    color: var(--chrome-muted);
    text-decoration: none;
    font-size: .8125rem;
    font-weight: 560;
}

.nav a:hover { color: var(--chrome-fg); }

.who { display: inline-flex; align-items: center; gap: .875rem; flex: none; }
.who form { display: inline; margin: 0; }

.tenant {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--chrome-muted);
    border: 1px solid var(--chrome-line);
    padding: .5rem .625rem;
    white-space: nowrap;
}

.masthead .who .tenant + .tenant { color: var(--chrome-fg); border-color: rgba(255, 253, 248, .32); }

.masthead .btn-link {
    color: var(--chrome-muted);
    border-bottom-color: var(--chrome-line);
}

.masthead .btn-link:hover { color: var(--chrome-fg); border-bottom-color: var(--chrome-fg); }

/* ---------- buttons ---------- */

.btn {
    appearance: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: .75rem;
    border: 1px solid var(--orange);
    border-radius: var(--radius);
    background: var(--orange);
    color: var(--night);
    padding: .95rem 1.5rem;
    font: 650 .8125rem/1 var(--font-sans);
    cursor: pointer;
    transition: transform .2s, background .2s, border-color .2s, color .2s;
}

.btn:hover:not(:disabled) {
    background: var(--orange-hi);
    border-color: var(--orange-hi);
    color: var(--night);
    transform: translateY(-2px);
}

.btn:disabled { opacity: .4; cursor: not-allowed; }

.btn-quiet {
    background: transparent;
    color: var(--fg-muted);
    border-color: var(--line);
}

.btn-quiet:hover:not(:disabled) {
    background: transparent;
    border-color: var(--fg);
    color: var(--fg);
}

/* The guide's text link: hairline underline, never a decorated button. */
.btn-link {
    appearance: none;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    background: none;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    padding: 0 0 3px;
    font: 650 .8125rem/1.3 var(--font-sans);
    color: var(--fg-muted);
    text-decoration: none;
    cursor: pointer;
}

.btn-link:hover { color: var(--orange); border-bottom-color: var(--orange); }

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

/* ---------- cards ---------- */

.card {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: var(--pad);
    margin-bottom: var(--gap);
}

.card > :last-child { margin-bottom: 0; }

.card-dark {
    background: var(--night);
    border-color: var(--night);
    color: var(--white);
}

.card-dark h2 { color: var(--white); }
.card-dark p { color: rgba(255, 253, 248, .58); }
.card-dark .hint { color: rgba(255, 253, 248, .42); }
.card-dark a { color: var(--white); }
.card-dark a:hover { color: var(--orange); }
.card-dark code { background: rgba(255, 253, 248, .08); border-color: rgba(255, 253, 248, .18); color: var(--white); }

.grid-2 { display: grid; grid-template-columns: 1fr 1fr; gap: 1.25rem; }
.grid-3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: var(--gap); }
.grid-3 .card p { margin: 0; color: var(--fg-muted); font-size: var(--fs-4); }

@media (max-width: 720px) { .grid-2 { grid-template-columns: 1fr; } }
@media (max-width: 860px) { .grid-3 { grid-template-columns: 1fr; } }

/* ---------- forms ---------- */

label {
    display: block;
    font-size: var(--fs-5);
    font-weight: 600;
    color: var(--fg-faint);
    margin-bottom: .5rem;
    text-transform: uppercase;
    letter-spacing: .12em;
}

input[type=text], input[type=search], input[type=password], input[type=email],
input[type=number], input[type=url], select {
    width: 100%;
    padding: .75rem .8125rem;
    font: 1rem/1.4 var(--font-sans);
    color: var(--fg);
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
}

input::placeholder { color: var(--fg-faint); }

input[type=number]:focus,
input[type=text]:focus, input[type=search]:focus, input[type=password]:focus,
input[type=email]:focus, input[type=url]:focus, select:focus {
    outline: none;
    border-color: var(--orange);
    box-shadow: inset 0 0 0 1px var(--orange);
}

input[type=range] { accent-color: var(--orange); }

/* ---------- drop zone ---------- */

.dropzone {
    position: relative;
    border: 1px dashed var(--line);
    border-radius: var(--radius);
    padding: 3rem 1.5rem;
    text-align: center;
    color: var(--fg-muted);
    font-size: var(--fs-4);
    cursor: pointer;
    transition: border-color .15s, background .15s, color .15s;
    /* Survey grid: the guide's graphic language, technical never decorative. */
    background-image:
        linear-gradient(var(--line-soft) 1px, transparent 1px),
        linear-gradient(90deg, var(--line-soft) 1px, transparent 1px);
    background-size: 28px 28px;
}

.dropzone:hover, .dropzone.hot {
    border-color: var(--orange);
    background-color: var(--active-tint);
    color: var(--fg);
}

.dropzone strong {
    display: block;
    color: var(--fg);
    font-size: 1.125rem;
    letter-spacing: -.03em;
    margin-bottom: .35rem;
}

/* ---------- progress ---------- */

.overall {
    display: flex;
    align-items: flex-end;
    justify-content: space-between;
    gap: 1rem;
    margin-bottom: .875rem;
}

.overall .pct {
    font-size: var(--fs-1);
    font-weight: 660;
    letter-spacing: -.05em;
    line-height: .9;
}

/* Anything read off the upload: phase, counts, resume state. */
.meta {
    font: 500 var(--fs-5)/1.4 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-muted);
}

.overall .meta { margin-top: .5rem; }

.bar {
    height: 6px;
    background: var(--line-soft);
    border-radius: var(--radius);
    overflow: hidden;
}

.bar > span {
    display: block;
    height: 100%;
    width: 0;
    background: var(--orange);
    transition: width .25s ease;
}

/* ---------- file list ---------- */

.files { list-style: none; margin: 1.5rem 0 0; padding: 0; }

.file {
    display: grid;
    grid-template-columns: 1fr auto;
    gap: .4rem 1rem;
    align-items: center;
    padding: .625rem 0;
    border-top: 1px solid var(--line-soft);
}

.file .name {
    font-size: var(--fs-4);
    font-variant-numeric: tabular-nums;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.file .bar { grid-column: 1 / -1; height: 3px; }

/* Status chips, in the guide's chip idiom. */
.status {
    display: inline-block;
    font-size: 10px;
    font-weight: 600;
    line-height: 1;
    text-transform: uppercase;
    letter-spacing: .1em;
    white-space: nowrap;
    border: 1px solid var(--line);
    padding: .35rem .45rem;
    color: var(--fg-muted);
}

.status.pending    { color: var(--fg-faint); }
.status.uploading  { color: var(--orange); border-color: var(--orange); }
.status.complete,
.status.ready,
.status.finalised  { color: var(--ok); border-color: var(--ok); background: var(--ok-tint); }
.status.duplicate  { color: var(--fg-muted); }
.status.failed     { color: var(--alert); border-color: var(--alert); background: var(--alert-tint); }

/* ---------- notices & flash ---------- */

.notice, .message {
    border-radius: var(--radius);
    border: 1px solid var(--line);
    border-left: 3px solid var(--fg-faint);
    background: var(--surface);
    padding: .875rem 1rem;
    font-size: var(--fs-4);
    margin-bottom: var(--gap);
}

.notice strong, .message strong { font-weight: 660; }

.notice-ok,   .message.success { border-color: var(--ok);     border-left-color: var(--ok);     background: var(--ok-tint); }
.notice-bad,  .message.error   { border-color: var(--alert);  border-left-color: var(--alert);  background: var(--alert-tint); }
.notice-warn, .message.warning { border-color: var(--orange); border-left-color: var(--orange); background: var(--active-tint); }
.message.info                  { border-color: var(--line);   border-left-color: var(--fg-muted); }

.hidden { display: none !important; }

/* ---------- tables ---------- */

table { width: 100%; border-collapse: collapse; font-size: var(--fs-4); }

th {
    text-align: left;
    font-size: var(--fs-5);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--fg-faint);
    padding-bottom: .75rem;
    border-bottom: 1px solid var(--line);
}

td { padding: .75rem 0; border-top: 1px solid var(--line-soft); }
tbody tr:first-child td { border-top: 0; }
td.num { text-align: right; font-variant-numeric: tabular-nums; font-family: var(--font-mono); }
th.num { text-align: right; }

/* ---------- footer ---------- */

.footer {
    color: var(--fg-faint);
    font-size: var(--fs-5);
    font-weight: 500;
    letter-spacing: .08em;
    text-transform: uppercase;
    border-top: 1px solid var(--line);
    padding: 1.5rem 0;
    display: flex;
    justify-content: space-between;
    gap: 1rem;
    flex-wrap: wrap;
}

.footer p { margin: 0; }

/* ---------- sign-in ---------- */

.signin { max-width: 26rem; margin: 2rem auto 3rem; }

/* The mark, at the size it was always meant to be.
   The span carried the dimensions and the `<svg>` inside it carried none, so
   the SVG took the column's full width and rendered 416 px square — a logo
   taller than the form, pushing the Sign in button off the bottom of a 900 px
   screen. The size belongs on the thing being drawn. */
.signin .brand-mark {
    display: block;
    color: var(--fg);
    width: 4.25rem;
    height: 4.25rem;
    margin: 0 auto 1.1rem;
}
.signin .brand-mark svg { display: block; width: 100%; height: 100%; }

.signin h1 { margin-bottom: .35rem; }
.signin .lede { font-size: var(--fs-3); margin-bottom: 1.25rem; }

/* Only the page that carries the mark centres its heading.
   The mark, the heading and the operator's name are one block introducing the
   form, and a centred badge over a left-aligned heading reads as two things
   that failed to line up. The apex sign-in has no mark, so centring its heading
   alone would leave it floating over a left-aligned form. Everything from the
   form down stays left on both, because that is where reading and typing
   happen. */
.signin-branded h1,
.signin-branded .lede { text-align: center; }
.signin .field { margin-bottom: 1.125rem; }
.signin .field-last { margin-bottom: 1.75rem; }
.signin .btn { width: 100%; }
.signin .hint { margin-top: 1.25rem; }

/* ---------- landing ---------- */

.hero-panel {
    position: relative;
    overflow: hidden;
    background: var(--night);
    color: var(--white);
    padding: 0 2.5rem;
    margin-bottom: var(--gap);
}

/* Field imagery with the survey grid over it: "show the work". */
.hero-panel::before {
    content: "";
    position: absolute;
    inset: 0;
    background:
        linear-gradient(90deg, rgba(8, 13, 12, .95) 0%, rgba(8, 13, 12, .72) 38%, rgba(8, 13, 12, .12) 78%),
        linear-gradient(0deg, rgba(8, 13, 12, .85) 0%, transparent 42%),
        url("../img/outback-aerial.jpg") center / cover no-repeat;
}

.hero-panel::after {
    content: "";
    position: absolute;
    inset: 0;
    opacity: .1;
    background-image:
        linear-gradient(rgba(255, 255, 255, .3) 1px, transparent 1px),
        linear-gradient(90deg, rgba(255, 255, 255, .3) 1px, transparent 1px);
    background-size: 80px 80px;
    -webkit-mask-image: linear-gradient(90deg, #000, transparent 70%);
    mask-image: linear-gradient(90deg, #000, transparent 70%);
}

.hero { position: relative; z-index: 2; max-width: 44rem; padding: 4.5rem 0 4.5rem; }
.hero .eyebrow { color: rgba(255, 253, 248, .7); }

.hero h1 {
    font-size: var(--fs-display);
    line-height: .92;
    letter-spacing: -.065em;
    font-weight: 650;
    margin: 0 0 1.5rem;
}

.hero .lede { color: rgba(255, 253, 248, .72); max-width: 34rem; margin-bottom: 0; }

/* Contact and documentation, when the platform has somewhere to point them.
   The quiet button's colours are written out rather than taken from --fg-muted
   and --line: this panel is Night Sky in *both* colour schemes, so tokens that
   flip with the scheme are readable in dark by coincidence and near-invisible
   in light. The same trap the masthead has. */
.hero-actions { margin-top: 2rem; }
.hero-actions .btn { text-decoration: none; }

.hero-actions .btn-quiet {
    background: transparent;
    color: var(--white);
    border-color: rgba(255, 253, 248, .38);
}

.hero-actions .btn-quiet:hover:not(:disabled) {
    background: rgba(255, 253, 248, .08);
    border-color: var(--white);
    color: var(--white);
}

/* Measured facts, in the guide's data voice. */
.hero-stat {
    position: relative;
    z-index: 2;
    display: grid;
    /* auto-fit rather than repeat(3, 1fr): the steps are operator-editable and
       there may be one or two of them. With three it lays out identically. */
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    border-top: 1px solid rgba(255, 253, 248, .18);
}

.hero-stat div {
    display: flex;
    align-items: center;
    gap: 1rem;
    padding: 1.5rem 1.75rem 1.5rem 0;
    border-right: 1px solid rgba(255, 253, 248, .18);
}

.hero-stat div + div { padding-left: 1.75rem; }
.hero-stat div:last-child { border-right: 0; }
.hero-stat strong { font-size: var(--fs-2); letter-spacing: -.05em; line-height: 1; }

.hero-stat span {
    max-width: 11rem;
    color: rgba(255, 253, 248, .55);
    font: 500 var(--fs-5)/1.4 var(--font-mono);
    text-transform: uppercase;
    letter-spacing: .1em;
}

.section-label {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--fg-faint);
    padding-bottom: 1.125rem;
    border-bottom: 1px solid var(--line);
    margin: 3rem 0 1.5rem;
}

.feature-number {
    display: block;
    font: 500 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .12em;
    color: var(--orange);
    margin-bottom: 2.5rem;
}

/* ---------- contact sheet ---------- */

.sheet {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(190px, 1fr));
    gap: var(--gap);
}

.frame { margin: 0; }

.frame img {
    width: 100%;
    height: auto;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    display: block;
    border-radius: var(--radius);
    border: 1px solid var(--line);
    background: var(--surface-2);
    transition: border-color .15s;
}

.frame a:hover img { border-color: var(--orange); }

.frame figcaption {
    font: 500 var(--fs-5)/1.4 var(--font-mono);
    letter-spacing: .04em;
    color: var(--fg-muted);
    margin-top: .5rem;
    display: flex;
    gap: .5rem;
    justify-content: space-between;
    align-items: baseline;
    overflow: hidden;
    white-space: nowrap;
}

.frame figcaption > span:not(.status) { color: var(--fg-faint); flex-shrink: 0; }

/* ---------- compare ---------- */

.compare { display: grid; grid-template-columns: 1fr 1fr; gap: var(--gap); align-items: start; }
@media (max-width: 860px) { .compare { grid-template-columns: 1fr; } }
.compare h2 { margin-bottom: .35rem; }

.compare .when {
    font: 500 var(--fs-5)/1.5 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin: 0 0 1.25rem;
}

/* ---------- full-bleed map ---------- */

body.fullbleed {
    height: 100vh;
    display: flex;
    flex-direction: column;
    overflow: hidden;
}

.masthead-wide { width: 100%; padding-inline: 1.5rem; }

.mapshell {
    flex: 1;
    display: grid;
    grid-template-columns: 22rem 1fr;
    min-height: 0;
}

@media (max-width: 780px) {
    .mapshell { grid-template-columns: 1fr; grid-template-rows: 14rem 1fr; }
}

/* ---------- picker ---------- */

.picker {
    display: flex;
    flex-direction: column;
    min-height: 0;
    background: var(--surface);
    border-right: 1px solid var(--line);
}

.picker-head { padding: .8rem .85rem .7rem; border-bottom: 1px solid var(--line); }

.picker-head h1 {
    font-size: var(--fs-5);
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin-bottom: .875rem;
}

.picker-head input { font-size: var(--fs-4); padding: .4rem .6rem; }

.picker-list { flex: 1; overflow-y: auto; padding: .25rem 0; min-height: 0; }
.picker-empty { padding: 1.125rem; color: var(--fg-faint); font-size: var(--fs-4); line-height: 1.5; }

.project { padding: .25rem 0 .625rem; }

.project h3 {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin: .875rem 1.125rem .5rem;
}

.survey {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    border-left: 2px solid transparent;
    padding: .5rem 1.125rem;
    font: inherit;
    cursor: pointer;
    color: var(--fg);
    transition: background .12s, border-color .12s;
}

.survey:hover { background: var(--surface-2); }
.survey.on { border-left-color: var(--orange); background: var(--surface-2); }
.survey.on .survey-name { color: var(--orange); }

.survey-name { display: block; font-size: var(--fs-4); font-weight: 620; letter-spacing: -.015em; }

.survey-meta {
    display: block;
    font: 500 var(--fs-5)/1.4 var(--font-mono);
    letter-spacing: .04em;
    color: var(--fg-faint);
    margin-top: .15rem;
}

.picker-foot { border-top: 1px solid var(--line); padding: .8rem .85rem .85rem; }

.picker-foot h2 {
    font-size: 1rem;
    letter-spacing: -.03em;
    margin-bottom: .875rem;
}

.facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .375rem .875rem;
    margin: 0 0 1.125rem;
    font-size: var(--fs-5);
}

.facts dt {
    font-weight: 500;
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-faint);
}

.facts dd {
    margin: 0;
    text-align: right;
    font-family: var(--font-mono);
    font-variant-numeric: tabular-nums;
    color: var(--fg);
}

.picker-actions { margin-bottom: 1.125rem; }

/* Which layer of the survey is drawn. A segmented control rather than two
   separate buttons: the choice is exclusive and one of them is always on, and
   a pair of independent-looking buttons reads as two things you can turn on. */
.layerswitch {
    display: flex;
    border: 1px solid var(--line);
    border-radius: 3px;
    overflow: hidden;
    margin: 0 0 .7rem;
}

.layerswitch button {
    flex: 1;
    background: none;
    border: 0;
    border-right: 1px solid var(--line);
    color: var(--fg-faint);
    padding: .32rem .5rem;
    font: inherit;
    font-size: var(--fs-5);
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .1em;
    line-height: 1;
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}

.layerswitch button:last-child { border-right: 0; }
.layerswitch button:hover { color: var(--fg); }

.layerswitch button.on {
    background: var(--orange);
    /* Against Signal Orange, not the page — this text sits on the fill. */
    color: var(--night);
}

.opacity {
    display: flex;
    align-items: center;
    gap: .75rem;
    font-size: var(--fs-5);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--fg-faint);
    margin: 0;
}

.opacity input { flex: 1; }

/* ---------- map ---------- */

.mapwrap { position: relative; min-height: 0; }
#map { position: absolute; inset: 0; background: var(--bg); }

/* Opacity here is driven by the mosaic layer itself — do not set it. */
.frame-mosaic { position: absolute; top: 0; left: 0; pointer-events: none; }

.maphint {
    position: absolute;
    /* Centred on the visible map, not on the pane that runs under the sidebar. */
    left: calc(var(--picker-w, 0px) + (100% - var(--picker-w, 0px)) / 2);
    /* Just above the tool bar rather than level with it: at a narrow window the
       bar reaches the middle of the map and a centred message lands on top of
       the buttons it is telling you about. */
    bottom: calc(1.5rem + 2.4rem + .4rem);
    transform: translateX(-50%);
    z-index: 500;
    background: var(--night);
    color: rgba(255, 253, 248, .8);
    border: 1px solid var(--night);
    border-left: 3px solid var(--orange);
    border-radius: var(--radius);
    padding: .625rem 1rem;
    font-size: var(--fs-4);
    line-height: 1.45;
    max-width: min(40rem, 90%);
}

/* Leaflet's own chrome, toned to match. Vendored CSS is left untouched. */
.leaflet-container { font: inherit; background: var(--bg); }
.leaflet-control-attribution { font: 500 10px var(--font-mono); }

.leaflet-bar,
.leaflet-control-attribution {
    border-radius: var(--radius) !important;
    border: 1px solid var(--line) !important;
    box-shadow: none !important;
    background: var(--surface) !important;
    color: var(--fg-muted) !important;
}

.leaflet-bar a,
.leaflet-bar a:hover {
    background: var(--surface);
    color: var(--fg);
    border-bottom-color: var(--line);
    border-radius: var(--radius) !important;
}

.leaflet-bar a:hover { background: var(--surface-2); color: var(--orange); }
.leaflet-control-attribution a { color: var(--fg-muted); }

.leaflet-tooltip {
    background: var(--night);
    color: var(--white);
    border: 0;
    border-radius: var(--radius);
    box-shadow: none;
    font: 500 var(--fs-5) var(--font-mono);
    letter-spacing: .06em;
}

.leaflet-tooltip-left::before { border-left-color: var(--night); }
.leaflet-tooltip-right::before { border-right-color: var(--night); }

/* ---------- responsive ---------- */

@media (max-width: 900px) {
    .hero-panel { padding: 0 1.5rem; }
    .hero { padding: 3rem 0 3rem; }
    .hero-stat { grid-template-columns: 1fr; }
    .hero-stat div { border-right: 0; border-top: 1px solid rgba(255, 253, 248, .12); padding: 1rem 0; }
    .hero-stat div + div { padding-left: 0; }
    .hero-stat div:first-child { border-top: 0; }
    .nav { gap: 1.25rem; margin-right: 1rem; }
    .masthead-inner { flex-wrap: wrap; }
}

@media (max-width: 560px) {
    .wrap { width: min(1180px, 100% - 2rem); }
    .nav { display: none; }
    .brand { padding-right: 0; }
    .footer { flex-direction: column; gap: .5rem; }
}

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

/* ---------- survey rows: eye, body, menu ---------- */

.picker-base {
    border-bottom: 1px solid var(--line);
    padding: .35rem 0;
    background: var(--surface-2);
}

.survey-row {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    border-left: 3px solid transparent;
}

.survey-row.on { border-left-color: var(--orange); background: var(--surface-2); }
.survey-row:hover { background: var(--surface-2); }

.survey-body {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    padding: .35rem .5rem .35rem .25rem;
    font: inherit;
    color: var(--fg);
    cursor: pointer;
    min-width: 0;
}

/* The eye reads as on/off at a glance: the slash is what changes, not the
   colour alone, so it still works for anyone who cannot separate the two. */
.eye {
    background: none;
    border: 0;
    padding: .45rem .3rem .45rem .6rem;
    cursor: pointer;
    color: var(--fg-faint);
    line-height: 0;
}

.eye.on { color: var(--orange); }
.eye:hover { color: var(--fg); }
.eye .eye-slash { opacity: 1; }
.eye.on .eye-slash { opacity: 0; }
.eye:not(.on) .eye-open { opacity: .45; }

/* ---------- per-survey menu ---------- */

.menu { position: relative; }

.menu-button {
    background: none;
    border: 0;
    padding: .4rem .7rem;
    font: inherit;
    font-size: 1rem;
    line-height: 1;
    color: var(--fg-faint);
    cursor: pointer;
}

.menu-button:hover, .menu-button[aria-expanded="true"] { color: var(--fg); }

.menu-list {
    position: absolute;
    right: .4rem;
    top: 100%;
    z-index: 800;
    min-width: 12rem;
    background: var(--surface);
    border: 1px solid var(--line);
    padding: .25rem 0;
}

.menu-item {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    padding: .45rem .9rem;
    font: inherit;
    font-size: .85rem;
    color: var(--fg);
    cursor: pointer;
    white-space: nowrap;
}

.menu-item:hover { background: var(--surface-2); }
.menu-item.danger { color: var(--bad, #c4342b); }
.menu-item.danger:hover { background: color-mix(in srgb, var(--bad, #c4342b) 10%, transparent); }

/* Tiles arrive as their own layer; keep them under the picker's popovers. */
.survey-tiles { mix-blend-mode: normal; }

/* ---------- upload: collapsed frame list ---------- */

.files-head {
    display: flex;
    justify-content: flex-end;
    margin-top: 1rem;
}

/* Collapsed, only the frame currently transferring is shown — a survey is
   hundreds of near-identical rows and they bury the overall progress. */
.files.collapsed > .file { display: none; }
.files.collapsed > .file.current { display: grid; }
.files.collapsed > .file.current { border-top: 0; }

/* ---------- processing queue (bottom right of the map) ---------- */

/* Top right. The queue is a running commentary on work the operator started,
   and it is read at a glance rather than worked in, so it sits opposite the
   survey list rather than crowding it. */
.queue {
    position: absolute;
    right: var(--map-gutter, 1rem);
    top: calc(var(--chrome-top, 0px) + var(--map-gutter, 1rem));
    z-index: 600;
    /* Narrower than a panel: it carries a label, a bar and a percentage, and
       sizing it like a card made a one-line status the widest thing on the
       map. It grows when opened to show the jobs. */
    width: min(17rem, calc(100% - 2rem));
    background: color-mix(in srgb, var(--surface) 90%, transparent);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid var(--line);
    border-radius: 3px;
    font-size: .74rem;
}
.queue:has(.queue-panel:not([hidden])) { width: min(22rem, calc(100% - 2rem)); }

.queue-bar {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "label pct" "bar bar";
    gap: .3rem .75rem;
    align-items: center;
    width: 100%;
    background: none;
    border: 0;
    padding: .4rem .65rem;
    font: inherit;
    color: var(--fg);
    cursor: pointer;
    text-align: left;
}

.queue-bar:hover { background: var(--surface-2); }
.queue-label { grid-area: label; font-weight: 600; }
.queue-pct {
    grid-area: pct;
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--fg-muted);
    font-variant-numeric: tabular-nums;
}
.queue-bar > .bar { grid-area: bar; height: 4px; }

.queue-panel {
    border-top: 1px solid var(--line);
    /* Fills the window rather than a fixed box. Anchored at the top of the
       map, the space below it is whatever the window has; the old 16rem cap
       was sized for a panel that opened upward from the bottom corner, and
       left a third of the jobs scrolled out of sight under half a screen of
       empty map. */
    max-height: calc(100vh - var(--chrome-top, 4rem) - 7rem);
    overflow-y: auto;
    padding: .6rem .8rem .8rem;
}

.queue-panel h3 {
    font-family: var(--font-mono);
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--fg-muted);
    margin: 0 0 .5rem;
}

.queue-panel ul { list-style: none; margin: 0; padding: 0; }

.queue-job { padding: .4rem 0; border-top: 1px solid var(--line-soft); }
.queue-job:first-child { border-top: 0; }
.queue-job-name { display: block; font-weight: 600; }
.queue-job-stage {
    display: block;
    font-family: var(--font-mono);
    font-size: .68rem;
    color: var(--fg-muted);
    margin: .1rem 0 .3rem;
}
.queue-job .bar { height: 3px; }
.queue-job.failed .queue-job-stage { color: var(--bad, #c4342b); }
.queue-job.queued .bar > span { background: var(--fg-faint); }

/* ---------- frame actions ---------- */

.frame { position: relative; }

.frame-menu-button {
    position: absolute;
    top: .35rem;
    right: .35rem;
    background: color-mix(in srgb, var(--night) 72%, transparent);
    color: var(--white);
    border: 0;
    padding: .1rem .45rem;
    font: inherit;
    font-size: .9rem;
    line-height: 1.2;
    cursor: pointer;
    opacity: 0;
    transition: opacity .12s;
}

.frame:hover .frame-menu-button,
.frame-menu-button:focus-visible,
.frame-menu-button[aria-expanded="true"] { opacity: 1; }

/* Positioned against the viewport, so it is not clipped by the scrolling
   contact sheet the way an absolutely-positioned child would be. */
.frame-menu {
    position: fixed;
    z-index: 900;
    min-width: 11rem;
    background: var(--surface);
    border: 1px solid var(--line);
    padding: .25rem 0;
}

/* ---------- queue job details ---------- */

.queue-job-head {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "name kind" "stage stage" "bar bar";
    gap: .15rem .5rem;
    width: 100%;
    background: none;
    border: 0;
    padding: .4rem 0;
    font: inherit;
    color: var(--fg);
    text-align: left;
    cursor: pointer;
}

.queue-job-head:hover .queue-job-name { color: var(--orange); }
.queue-job-name { grid-area: name; font-weight: 600; }

.queue-job-kind {
    grid-area: kind;
    font-family: var(--font-mono);
    font-size: .6rem;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--fg-muted);
    border: 1px solid var(--line);
    padding: 0 .35rem;
    align-self: center;
}

.queue-job-stage { grid-area: stage; }
.queue-job-head > .bar { grid-area: bar; height: 3px; margin-top: .2rem; }

.queue-job.paused .bar > span { background: var(--fg-faint); }
.queue-job.failed .bar > span { background: var(--bad, #c4342b); }

.queue-job-detail { padding: .2rem 0 .6rem; }
.queue-job-detail .facts { font-size: .72rem; margin-bottom: .5rem; }

.queue-job-error {
    font-size: .72rem;
    color: var(--bad, #c4342b);
    margin: 0 0 .5rem;
}

.queue-job-log {
    font-family: var(--font-mono);
    font-size: .62rem;
    line-height: 1.45;
    max-height: 9rem;
    overflow: auto;
    background: var(--night);
    color: var(--sand);
    padding: .5rem .6rem;
    margin: 0 0 .5rem;
    white-space: pre-wrap;
    word-break: break-word;
}

.queue-job-actions { display: flex; gap: .8rem; flex-wrap: wrap; }
.queue-job-actions .btn-link { font-size: .74rem; }
.queue-job-actions .btn-link.danger { color: var(--bad, #c4342b); }

/* ---------- floating chrome over the map ---------- */

/*
 * On the map the header and sidebar float over the imagery rather than boxing
 * it in: the map is the content, and giving it the whole viewport means an
 * operator sees more ground for the same screen. The panels stay legible
 * because they are frosted rather than transparent — plain transparency over
 * an aerial photograph makes text unreadable the moment it crosses a light
 * patch of dirt.
 */

.mapchrome .mapshell {
    grid-template-columns: var(--picker-w) 1fr;
    /* The map spans both columns and the full height; the picker sits on top
       of it, so imagery continues underneath both. */
    grid-template-areas: "picker map";
}

/*
 * Everything the map floats on top of itself has to be kept out from under the
 * chrome that floats on top of *it*.
 *
 * The map pane deliberately spans the full window so imagery runs behind the
 * sidebar and the header. The consequence is that a control anchored to the
 * pane's own top-left corner is anchored behind both of them — which is where
 * the zoom buttons, the layer switcher, the queue and the coordinate readout
 * had all ended up.
 *
 * These two measurements are the whole fix, so they are named once and used
 * everywhere rather than repeated as literals that drift apart.
 */
.mapchrome {
    --picker-w: 22rem;
    --chrome-top: 3.9rem;
    --map-gutter: 1rem;
}

/* Leaflet's own control corners: zoom, layer switcher, scale, the readout. */
.mapchrome .leaflet-left { margin-left: var(--picker-w); }
.mapchrome .leaflet-top { margin-top: var(--chrome-top); }

.mapchrome .mapwrap {
    grid-area: 1 / 1 / -1 / -1;
    z-index: 0;
}

.mapchrome .picker {
    grid-area: picker;
    z-index: 400;
    background: color-mix(in srgb, var(--surface) 86%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.1);
    backdrop-filter: blur(14px) saturate(1.1);
    border-right: 1px solid color-mix(in srgb, var(--line) 70%, transparent);
    /* Clear the floating header, which now overlaps it. The value is measured
       from the masthead at runtime — see templates/layout/map.php. The 3.9rem
       here is only the fallback for the moment before that script runs. */
    padding-top: var(--chrome-top, 3.9rem);
}

.mapchrome .masthead {
    position: fixed;
    inset: 0 0 auto 0;
    background: color-mix(in srgb, var(--chrome) 78%, transparent);
    -webkit-backdrop-filter: blur(14px) saturate(1.1);
    backdrop-filter: blur(14px) saturate(1.1);
    border-bottom: 1px solid color-mix(in srgb, #fff 12%, transparent);
    z-index: 500;
}

.mapchrome { padding-top: 0; }
.mapchrome .mapshell { height: 100vh; }

/* Leaflet's controls would otherwise sit under the header. */
.mapchrome .leaflet-top { padding-top: 3.4rem; }

/*
 * Where backdrop-filter is unsupported the panels fall back to nearly opaque
 * rather than see-through, because unreadable is worse than un-pretty.
 */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .mapchrome .picker { background: color-mix(in srgb, var(--surface) 97%, transparent); }
    .mapchrome .masthead { background: var(--chrome); }
}

@media (max-width: 780px) {
    /* The picker becomes a bottom sheet, so nothing is hiding the left edge
       any more and the offsets have to come back off. */
    .mapchrome { --picker-w: 0px; }
    .mapchrome .leaflet-left { margin-left: 0; }
    .mapchrome .mapshell { grid-template-columns: 1fr; grid-template-areas: "map"; }
    .mapchrome .picker {
        grid-area: 1 / 1 / -1 / -1;
        align-self: end;
        max-height: 55vh;
        border-right: 0;
        border-top: 1px solid var(--line);
        padding-top: 0;
    }
}

/* ---------- account menu ---------- */

.usermenu { position: relative; }

.usermenu-button {
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    background: none;
    border: 1px solid color-mix(in srgb, currentColor 28%, transparent);
    color: var(--chrome-fg);
    padding: .28rem .6rem .28rem .3rem;
    font: inherit;
    font-size: .82rem;
    cursor: pointer;
}

.usermenu-button:hover { border-color: color-mix(in srgb, currentColor 55%, transparent); }

.usermenu-avatar {
    display: grid;
    place-items: center;
    width: 1.5rem;
    height: 1.5rem;
    background: var(--orange);
    color: var(--night);
    font-family: var(--font-mono);
    font-size: .7rem;
    font-weight: 700;
}

.usermenu-caret { font-size: .6rem; opacity: .7; }

.usermenu-list {
    position: absolute;
    right: 0;
    top: calc(100% + .4rem);
    z-index: 900;
    min-width: 14rem;
    background: var(--surface);
    color: var(--fg);
    border: 1px solid var(--line);
    padding: 0 0 .25rem;
    text-align: left;
}

.usermenu-head {
    display: grid;
    gap: .1rem;
    padding: .7rem .9rem;
    border-bottom: 1px solid var(--line);
    margin-bottom: .25rem;
}

.usermenu-head strong { font-size: .88rem; }
.usermenu-head span { font-size: .74rem; color: var(--fg-muted); }

.usermenu-role {
    font-family: var(--font-mono);
    font-size: .62rem !important;
    text-transform: uppercase;
    letter-spacing: .1em;
}

/* The menu items are links and postLink forms, not buttons, so they need the
   same treatment .menu-item gives the survey menu's buttons. */
.usermenu-list .menu-item,
.usermenu-list form .menu-item {
    display: block;
    width: 100%;
    padding: .45rem .9rem;
    font-size: .85rem;
    color: var(--fg);
    text-decoration: none;
    background: none;
    border: 0;
    text-align: left;
    cursor: pointer;
}

.usermenu-list .menu-item:hover { background: var(--surface-2); }
.usermenu-list form { margin: 0; display: block; }

/* ---------- profile form fields ---------- */

.field { margin-bottom: 1rem; }
.field-last { margin-bottom: 1.4rem; }

/* ---------- map tools ---------- */

/* One bar, grouped by what a tool does to the map. The groups are separated
   by a gap and the buttons inside one are joined, so the grouping is legible
   without a label for it. Same translucent chrome as the readout and the
   panels, so the map reads through everything floating on it. */
.maptools {
    position: absolute;
    right: .75rem;
    bottom: 1.5rem;
    justify-content: flex-end;
    z-index: 600;
    display: flex;
    flex-wrap: wrap;
    gap: .45rem;
    max-width: calc(100% - 1.5rem);
}

.maptool-group {
    display: flex;
    background: color-mix(in srgb, var(--surface) 90%, transparent);
    backdrop-filter: blur(8px);
    border: 1px solid var(--line);
    border-radius: 3px;
    box-shadow: 0 2px 10px rgb(0 0 0 / .14);
    /* So the first and last buttons' corners follow the group's. */
    overflow: hidden;
}

.maptool {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    background: none;
    border: 0;
    color: var(--fg);
    padding: .3rem .6rem;
    font: inherit;
    font-size: .72rem;
    font-weight: 600;
    line-height: 1;
    white-space: nowrap;
    cursor: pointer;
    transition: background .12s ease, color .12s ease;
}

/* A hairline between neighbours rather than a border on each, so joined
   buttons do not show a doubled 2px seam. */
.maptool + .maptool { box-shadow: inset 1px 0 0 var(--line); }

.maptool svg { flex-shrink: 0; }
.maptool:hover { background: var(--surface-2); }
.maptool:focus-visible { outline: 2px solid var(--orange); outline-offset: -2px; }

.maptool.on,
.maptool[aria-pressed="true"] {
    background: var(--orange);
    color: var(--night);
}
/* The active button owns its edges — the neighbour's hairline would otherwise
   cut across the orange. */
.maptool.on + .maptool,
.maptool[aria-pressed="true"] + .maptool { box-shadow: none; }

/* Count of finished exports waiting to be collected. */
.maptool-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.15rem;
    height: 1.15rem;
    padding: 0 .25rem;
    border-radius: 999px;
    background: var(--orange);
    color: var(--night);
    font-size: .65rem;
    font-variant-numeric: tabular-nums;
}
.maptool.on .maptool-count,
.maptool[aria-pressed="true"] .maptool-count { background: var(--night); color: var(--orange); }

/* Under a narrow window the labels are the first thing to go: the icons carry
   the meaning and a bar that wraps to three rows over the map does not. */
@media (max-width: 700px) {
    .maptool span:not(.maptool-count) { display: none; }
    .maptool { padding: .5rem .6rem; }
}

/* ---------- export dialog and list ---------- */

.exportdialog {
    position: absolute;
    left: calc(var(--picker-w, 0px) + (100% - var(--picker-w, 0px)) / 2);
    top: 50%;
    transform: translate(-50%, -50%);
    z-index: 800;
    width: min(26rem, calc(100% - 2rem));
    background: var(--surface);
    border: 1px solid var(--line);
    padding: 1.1rem 1.2rem 1.2rem;
}

.exportdialog h3 { font-size: 1rem; margin: 0 0 .2rem; }
.exportdialog .field { margin-bottom: .8rem; }
.exportdialog-actions { display: flex; gap: .6rem; margin-top: .4rem; }

.exportpanel {
    width: min(30rem, calc(100% - 1.5rem));
    border: 1px solid var(--line);
    padding: .8rem .9rem 1rem;
}

.exportpanel h3 {
    font-family: var(--font-mono);
    font-size: .65rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--fg-muted);
    margin: 0 0 .6rem;
}

.exportpanel ul { list-style: none; margin: 0; padding: 0; }

.export-row {
    display: grid;
    grid-template-columns: 1fr auto;
    grid-template-areas: "name actions" "meta actions";
    gap: .1rem .8rem;
    padding: .5rem 0;
    border-top: 1px solid var(--line-soft);
    font-size: .82rem;
}

.export-row:first-child { border-top: 0; }
.export-name { grid-area: name; font-weight: 600; }
.export-meta {
    grid-area: meta;
    font-family: var(--font-mono);
    font-size: .68rem;
    color: var(--fg-muted);
}
.export-actions { grid-area: actions; display: flex; gap: .7rem; align-items: center; }
.export-actions .btn-link { font-size: .74rem; }
.export-actions .danger { color: var(--bad, #c4342b); }
.export-error { grid-column: 1 / -1; font-size: .7rem; color: var(--bad, #c4342b); }
.export-row.building .export-meta { color: var(--warn, #b4801f); }

/* ---------- post-upload summary ---------- */

.jobcard {
    border: 1px solid var(--line);
    padding: .9rem 1rem 1rem;
    margin-bottom: .8rem;
}

.status.processed, .status.completed { color: var(--good, #2f7331); }
.status.running, .status.submitted, .status.building { color: var(--warn, #b4801f); }
.status.queued, .status.paused { color: var(--fg-faint); }
.status.cancelled { color: var(--fg-faint); }

/* ---------- frame selection ---------- */

.framebar {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
    padding: .5rem 0 .8rem;
    border-bottom: 1px solid var(--line-soft);
    margin-bottom: .9rem;
    font-size: .82rem;
}

.framebar-select { display: inline-flex; align-items: center; gap: .4rem;
                   text-transform: none; letter-spacing: 0; font-size: .82rem;
                   font-weight: 600; color: var(--fg); margin: 0; }
.framebar-count { font-family: var(--font-mono); font-size: .72rem; color: var(--fg-muted); }
.framebar-actions { display: flex; gap: .9rem; margin-left: auto; }
.framebar-actions .btn-link { font-size: .78rem; }
.framebar-actions .danger { color: var(--bad, #c4342b); }

/* In selection mode the whole tile is the target, so the pointer says so and
   the per-frame menu gets out of the way. */
body.selecting .frame { cursor: pointer; }
body.selecting .frame-menu-button { display: none; }
body.selecting .frame a { pointer-events: none; }

.frame.picked img { outline: 3px solid var(--orange); outline-offset: -3px; }
.frame.picked figcaption { color: var(--orange); }

/* Operator, inside the account menu rather than in the masthead: the person's
   name is what identifies them, the operator is context. */
.usermenu-tenant {
    display: flex;
    justify-content: space-between;
    gap: .8rem;
    margin-top: .45rem;
    padding-top: .45rem;
    border-top: 1px solid var(--line-soft);
    font-size: .74rem !important;
}
.usermenu-tenant strong { color: var(--fg); font-size: .78rem; }

/* ---------- flight planner ---------- */

.planner .picker-head .mode { display: flex; gap: 0; margin: .6rem 0 .5rem; }

.mode-option {
    flex: 1;
    background: none;
    border: 1px solid var(--line);
    color: var(--fg-muted);
    padding: .45rem .5rem;
    font: inherit;
    font-size: .78rem;
    font-weight: 600;
    cursor: pointer;
}

.mode-option + .mode-option { border-left: 0; }
.mode-option.on { background: var(--orange); border-color: var(--orange); color: var(--night); }

.planner-form { padding: .9rem 1rem; }
.planner-form .field { margin-bottom: .8rem; }
.planner-form .grid-2 { gap: .6rem; }

.withunit { position: relative; }
.withunit input { padding-right: 2.4rem; }
.withunit span {
    position: absolute;
    right: .6rem;
    top: 50%;
    transform: translateY(-50%);
    font-family: var(--font-mono);
    font-size: .7rem;
    color: var(--fg-faint);
    pointer-events: none;
}

.planstats { margin-bottom: .8rem; }
.planstats .facts { font-size: .78rem; }

/* Leaflet's layer control, moved to the bottom left and brought onto brand. */
.leaflet-bottom.leaflet-left .leaflet-control-layers { margin-bottom: 3.4rem; margin-left: .75rem; }
/* The readout sits below this stack, so the stack stops where it begins. */
.mapchrome .leaflet-bottom.leaflet-left { margin-bottom: 2.2rem; }
.leaflet-control-layers {
    border: 1px solid var(--line) !important;
    border-radius: 0 !important;
    background: var(--surface) !important;
    color: var(--fg);
    box-shadow: none !important;
}
.leaflet-control-layers-expanded { padding: .5rem .7rem; font-size: .8rem; }

/* ---------- collapsible project groups ---------- */

.project-head {
    display: grid;
    grid-template-columns: auto 1fr auto;
    align-items: center;
    gap: .45rem;
    width: 100%;
    background: none;
    border: 0;
    padding: .5rem 1rem .3rem;
    font: inherit;
    cursor: pointer;
    text-align: left;
}

.project-head:hover .project-name { color: var(--fg); }

.project-caret {
    font-size: .6rem;
    color: var(--fg-faint);
    transition: transform .12s ease;
}

.project.collapsed .project-caret { transform: rotate(-90deg); }

.project-name {
    font-family: var(--font-mono);
    font-size: .68rem;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--fg-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.project-count {
    font-family: var(--font-mono);
    font-size: .62rem;
    color: var(--fg-faint);
    border: 1px solid var(--line);
    padding: 0 .3rem;
}

/* ---------- editable shape handles ---------- */

/*
 * The handle is a marker so it can be dragged, but it should look like the
 * vector dot it replaced. The ::after gives it a hit area larger than the dot,
 * because grabbing a 10px target on a laptop trackpad is otherwise fiddly.
 */
.vertex {
    background: var(--white, #fffdf8);
    border: 2px solid var(--orange, #ff5a1f);
    border-radius: 50%;
    box-sizing: border-box;
    cursor: grab;
}

.vertex::after {
    content: '';
    position: absolute;
    inset: -7px;
}

.vertex:active { cursor: grabbing; }
.vertex-first { background: var(--orange, #ff5a1f); }

/* Midpoints read as "not yet a corner": hollow, dimmer, and they firm up on
   hover so it is obvious they can be grabbed. */
.vertex-mid {
    background: transparent;
    border-style: dashed;
    border-color: color-mix(in srgb, var(--orange, #ff5a1f) 65%, transparent);
    opacity: .75;
}

.vertex-mid:hover {
    background: var(--white, #fffdf8);
    border-style: solid;
    opacity: 1;
}

/* ---------- heading compass ---------- */

.heading-control { display: grid; grid-template-columns: 5.5rem 1fr; gap: .8rem; align-items: center; }

.compass {
    width: 5.5rem;
    height: 5.5rem;
    padding: 0;
    background: none;
    border: 0;
    cursor: grab;
    touch-action: none;
}

.compass:active { cursor: grabbing; }
.compass svg { width: 100%; height: 100%; display: block; }
.compass:focus-visible { outline: 2px solid var(--orange); outline-offset: 2px; }

.compass-face { fill: var(--surface-2); stroke: var(--line); stroke-width: 1.5; }
.compass-tick {
    font-family: var(--font-mono);
    font-size: 11px;
    fill: var(--fg-faint);
    text-anchor: middle;
    dominant-baseline: middle;
}
.compass-needle { stroke: var(--orange); stroke-width: 4; stroke-linecap: round; }
.compass-needle-tail { stroke: var(--fg-faint); stroke-width: 3; stroke-linecap: round; }
.compass-head { fill: var(--orange); }

/* The needle is two-headed on purpose: a flight line has a bearing, not a
   direction — flying it north-to-south covers the same ground as south-to-north. */
#heading-needle { transition: transform .08s linear; }

.heading-readout { display: grid; gap: .3rem; }
.heading-name { font-size: .78rem; color: var(--fg-muted); margin: 0; }
.heading-readout .btn-link { font-size: .74rem; text-align: left; }

/* ---------- evenly spaced export row ---------- */

.exportrow {
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    margin-top: .7rem;
    border: 1px solid var(--line);
}

.exportrow a {
    padding: .4rem .2rem;
    font-size: .74rem;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    color: var(--fg-muted);
}

.exportrow a + a { border-left: 1px solid var(--line); }
.exportrow a:hover { background: var(--surface-2); color: var(--fg); }

/* ---------- boundary import ---------- */

.importrow { margin-top: .5rem; }
.importrow .btn-link { font-size: .78rem; }
.import-error { color: var(--bad, #c4342b); }

/* ---------- import shape picker ---------- */

.import-picker {
    margin-top: .6rem;
    border: 1px solid var(--line);
    background: var(--surface);
    padding: .5rem 0 .3rem;
}

.import-picker h2 {
    font-family: var(--font-mono);
    font-size: .62rem;
    text-transform: uppercase;
    letter-spacing: .12em;
    color: var(--fg-muted);
    margin: 0 .8rem .35rem;
}

.import-choice {
    display: block;
    width: 100%;
    text-align: left;
    background: none;
    border: 0;
    border-left: 3px solid transparent;
    padding: .35rem .8rem;
    font: inherit;
    color: var(--fg);
    cursor: pointer;
}

.import-choice:hover, .import-choice:focus-visible {
    background: var(--surface-2);
    border-left-color: var(--green, #8bea88);
    outline: none;
}

.import-choice-name { display: block; font-size: .82rem; font-weight: 600; }
.import-choice-meta {
    display: block;
    font-family: var(--font-mono);
    font-size: .66rem;
    color: var(--fg-muted);
}

#import-cancel { font-size: .74rem; margin: .3rem .8rem 0; }

/* Flight plan list — per-row actions and a status column that lines up.
 *
 * The status cell sat between right-aligned numbers and an action column, and
 * inherited neither: the chip floated against the numbers on its left rather
 * than sitting under its own heading. Both the heading and the cell are pinned
 * here, and the column is sized to the widest chip so it stops moving as the
 * statuses change. */
.plan-table th.status-col,
.plan-table td.status-col { text-align: left; width: 1%; white-space: nowrap; padding-left: 1rem; }
.plan-table th.menu-col,
.plan-table td.menu-col { text-align: right; width: 1%; padding-left: .25rem; }

.rowmenu { position: relative; display: inline-block; }
.rowmenu-button {
    background: none;
    border: 1px solid transparent;
    color: var(--fg-muted);
    cursor: pointer;
    font-size: 1rem;
    line-height: 1;
    padding: .2rem .5rem;
    border-radius: 2px;
}
.rowmenu-button:hover { background: var(--surface-2); color: var(--fg); }
.rowmenu-button[aria-expanded="true"] { background: var(--surface-2); border-color: var(--line); color: var(--fg); }

.rowmenu-list {
    position: absolute;
    right: 0;
    top: calc(100% + 4px);
    z-index: 40;
    min-width: 12.5rem;
    background: var(--surface);
    border: 1px solid var(--line);
    box-shadow: 0 8px 24px rgb(0 0 0 / .18);
    padding: .25rem 0;
    text-align: left;
}
/* The last rows of the table would otherwise open a menu below the fold. */
.plan-table tbody tr:nth-last-child(-n+2) .rowmenu-list { top: auto; bottom: calc(100% + 4px); }

.rowmenu-head {
    margin: 0;
    padding: .5rem .75rem .25rem;
    font-size: 10px;
    font-weight: 600;
    text-transform: uppercase;
    letter-spacing: .1em;
    color: var(--fg-faint);
}
.rowmenu-list .menu-item { display: block; width: 100%; text-align: left; }

.visually-hidden {
    position: absolute;
    width: 1px; height: 1px;
    margin: -1px; padding: 0;
    overflow: hidden;
    clip: rect(0 0 0 0);
    white-space: nowrap;
}

/* A hint that is a caution rather than an aside — used where a choice has a
   consequence the operator should see before committing, not after. */
.hint.warn {
    color: var(--warn, #b4801f);
    border-left: 2px solid var(--warn, #b4801f);
    padding-left: .6rem;
}

/* ---------------------------------------------------------------- map tools */

/* Crosshair while measuring: the default grab-hand says "drag me", which is
   the opposite of what a click does in this mode. */
.leaflet-container.measuring { cursor: crosshair; }
.leaflet-container.measuring .leaflet-interactive { cursor: crosshair; }

.map-readout {
    /* On the floor of the map, clear of Leaflet's own bottom-left stack. */
    position: absolute;
    left: calc(var(--picker-w, 0px) + .75rem);
    bottom: .5rem;
    z-index: 610;
    background: color-mix(in srgb, var(--surface) 88%, transparent);
    border: 1px solid var(--line);
    backdrop-filter: blur(6px);
    padding: .28rem .45rem;
    font-family: var(--font-mono);
    font-size: 10px;
    line-height: 1.45;
    color: var(--fg-muted);
    display: flex;
    flex-direction: column;
    gap: .1rem;
    margin-bottom: .35rem !important;
}
.map-readout-copy {
    background: none;
    border: 0;
    padding: 0;
    text-align: left;
    font: inherit;
    color: var(--fg);
    cursor: pointer;
    display: flex;
    flex-direction: column;
}
.map-readout-copy:hover { color: var(--orange); }
.map-readout-dms { color: var(--fg-faint); font-size: 10px; }
.map-readout-elev { color: var(--orange); }
.map-readout-elev.muted { color: var(--fg-faint); }

/* The running total at each vertex. Not interactive — clicking it would land
   on the label rather than adding the next point. */
.measure-label {
    background: var(--night);
    color: #fff;
    font-family: var(--font-mono);
    font-size: 10px;
    padding: .1rem .3rem;
    white-space: nowrap;
    border: 0;
    width: auto !important;
    height: auto !important;
    margin: 4px 0 0 6px !important;
    pointer-events: none;
}

.measure-hint {
    top: auto;
    bottom: calc(1.5rem + 2.4rem + .4rem);
    /* Inherits .maphint's position but not its centring, so the offset has to
       be repeated here — a bare 50% centres on the pane that runs under the
       sidebar, which puts the hint left of where anyone is looking. */
    left: calc(var(--picker-w, 0px) + (100% - var(--picker-w, 0px)) / 2);
    transform: translateX(-50%);
    z-index: 500;
}

.map-profile {
    width: min(28rem, calc(100% - 2rem));
    padding: .45rem .6rem .55rem;
}
.map-profile-head { display: flex; justify-content: space-between; align-items: center; gap: .5rem; }
.map-profile-head strong { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; }
.map-profile-close {
    background: none; border: 0; cursor: pointer;
    color: var(--fg-muted); font-size: 1.1rem; line-height: 1; padding: 0 .2rem;
}
.map-profile-close:hover { color: var(--fg); }
.map-profile-chart { display: block; width: 100%; height: 120px; margin: .4rem 0 .3rem; }
.map-profile-stats {
    margin: 0;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--fg-muted);
    line-height: 1.6;
}

/* ------------------------------------------------------------- contour mode */


/*
 * The panel dock.
 *
 * Contours, exports and the elevation profile each used to claim their own
 * corner of the map, which put the contour panel on top of Leaflet's zoom
 * buttons and stacked the other two on the tool bar. Three panels, three
 * collisions, and no rule saying where a panel belongs.
 *
 * The rule now is: a panel opens directly above the button that opens it. They
 * share one slot and only one is ever open, so nothing can land on anything
 * else however many panels get added later.
 */
.mapdock-panel {
    position: absolute;
    right: var(--map-gutter, 1rem);
    /* Clear of the tool bar: its height plus the gutter under it. */
    bottom: calc(1.5rem + 2.4rem + .5rem);
    z-index: 620;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border: 1px solid var(--line);
    border-radius: 3px;
    box-shadow: 0 10px 30px rgb(0 0 0 / .22);
    max-height: min(26rem, calc(100vh - 12rem));
    overflow-y: auto;
}

.contour-panel {
    width: 13.5rem;
    padding: .45rem .6rem .55rem;
}
.contour-head { display: flex; justify-content: space-between; align-items: center; }
.contour-head strong { font-size: 12px; text-transform: uppercase; letter-spacing: .08em; }
.contour-close {
    background: none; border: 0; cursor: pointer;
    color: var(--fg-muted); font-size: 1.1rem; line-height: 1; padding: 0 .2rem;
}
.contour-close:hover { color: var(--fg); }
.contour-field { display: block; margin-top: .35rem; }
.contour-field > span {
    display: block;
    font-size: 10px; text-transform: uppercase; letter-spacing: .1em;
    color: var(--fg-faint); margin-bottom: .15rem;
}
.contour-field select { width: 100%; }
.contour-status {
    margin: .5rem 0 0;
    font-family: var(--font-mono);
    font-size: 10.5px;
    color: var(--fg-muted);
    line-height: 1.5;
}

/* Only index contours are labelled, so the label has to be legible over
   imagery of any brightness — hence the halo rather than a fill. */
.contour-label {
    font-family: var(--font-mono);
    font-size: 10px;
    font-weight: 600;
    color: #3d1a06;
    text-shadow: 0 0 3px #fff, 0 0 3px #fff, 0 0 3px #fff;
    white-space: nowrap;
    width: auto !important;
    height: auto !important;
    pointer-events: none;
}

/* ---------- impersonation banner ---------- */

/* Lives in the operator's stylesheet rather than the portal's because it is
 * shown on the *tenant* side, on every layout, while a super admin is signed in
 * as someone else. Fixed to the top and pushing the page down rather than
 * overlaying it: a banner that can be scrolled past is a banner that gets
 * forgotten, and forgetting is the failure this feature is designed around.
 *
 * Signal Orange at full strength. The brand spends it on action only, and this
 * is the most consequential state the application has. */

.impersonation {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: 2000;
    display: flex;
    align-items: center;
    gap: .875rem;
    padding: .5rem 1.25rem;
    background: var(--orange);
    color: var(--night);
    font-size: var(--fs-4);
    border-bottom: 1px solid var(--night);
}

body.impersonating { padding-top: 2.75rem; }

/* The map is full-bleed and sizes itself off the viewport, so the offset has to
 * move the chrome rather than pad the body. */
body.impersonating.fullbleed { padding-top: 0; }
body.impersonating.fullbleed .masthead { margin-top: 2.75rem; }

.impersonation-mark {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 1.35rem;
    height: 1.35rem;
    flex: 0 0 auto;
    border: 1.5px solid var(--night);
    font: 700 .8rem/1 var(--font-mono);
}

.impersonation-text { flex: 1 1 auto; min-width: 0; }
.impersonation-text strong { font-weight: 700; }

.impersonation-clock {
    font: 600 var(--fs-4)/1 var(--font-mono);
    letter-spacing: .06em;
    font-variant-numeric: tabular-nums;
    padding: .35rem .5rem;
    border: 1px solid var(--night);
}

.impersonation-exit {
    flex: 0 0 auto;
    background: var(--night);
    color: var(--white);
    border: 1px solid var(--night);
    padding: .45rem .9rem;
    font: 650 .75rem/1 var(--font-sans);
    text-decoration: none;
}

.impersonation-exit:hover { background: var(--night-deep); color: var(--orange); }

@media (max-width: 720px) {
    .impersonation { flex-wrap: wrap; }
    body.impersonating { padding-top: 4.5rem; }
}

/* ---------------------------------------------------- map chrome, tightened */

/*
 * The controls Leaflet ships are sized for a full-page map with nothing else on
 * it. Here they sit alongside a survey list, a tool bar and a queue, and at
 * their stock size they were the heaviest thing in view — a 46px layers button
 * next to a 28px tool button reads as an accident rather than a hierarchy.
 */
.mapchrome .leaflet-control-layers-toggle {
    width: 30px;
    height: 30px;
    background-size: 18px 18px;
}
.mapchrome .leaflet-control-zoom a {
    width: 26px;
    height: 26px;
    line-height: 26px;
    font-size: 16px;
}
.mapchrome .leaflet-control-scale-line {
    padding: 1px 5px;
    font-size: 10px;
    line-height: 1.3;
    border-width: 1px;
}
.mapchrome .leaflet-control-attribution { font-size: 9px; padding: 1px 4px; }

/* Selects inside a floating panel are chrome, not a form. The stock control is
   sized for a page where it is the subject. */
.contour-panel select,
.contour-panel input {
    padding: .25rem .4rem;
    font-size: .72rem;
    line-height: 1.2;
    height: auto;
}
.contour-field > span { font-size: 9px; margin-bottom: .1rem; }
.contour-head strong { font-size: 11px; }
.contour-status { font-size: 10px; margin-top: .35rem; }

.map-profile-head strong { font-size: 11px; }
.map-profile-chart { height: 92px; margin: .3rem 0 .25rem; }
.map-profile-stats { font-size: 10px; line-height: 1.5; }

/* The readout has nothing to say until the cursor has been over the map, and an
   empty box in the corner reads as something broken rather than as something
   waiting. */
.map-readout:not(.has-fix) { display: none; }

/* A survey that something is still writing to. */
.survey-busy {
    display: inline-flex;
    vertical-align: -1px;
    margin-left: .3rem;
    color: var(--orange);
    animation: survey-busy-spin 1.1s linear infinite;
}
@keyframes survey-busy-spin { to { transform: rotate(360deg); } }

/* Nobody needs a spinner enough to sit through it. */
@media (prefers-reduced-motion: reduce) {
    .survey-busy { animation: none; }
}

/* Disabled rather than absent, so the menu keeps its shape and the title
   attribute can say why. */
.menu-item:disabled {
    opacity: .38;
    cursor: not-allowed;
}
.menu-item:disabled:hover { background: none; }

/* ------------------------------------------------------- hiding the sidebar */

/*
 * One variable does the work.
 *
 * Every control floating on the map is positioned against --picker-w, so
 * zeroing it moves the zoom buttons, the coordinate readout, the queue and the
 * centred hints across together. Nothing else needs to know the sidebar is
 * gone.
 */
.mapchrome.picker-hidden { --picker-w: 0px; }

.mapchrome .picker {
    transition: transform .2s ease, opacity .2s ease;
}
.mapchrome.picker-hidden .picker {
    transform: translateX(-100%);
    opacity: 0;
    pointer-events: none;
}

/* Rides the sidebar's edge, so it is where the edge is whichever state it is
   in — no second position to keep in step. */
.picker-toggle {
    position: absolute;
    left: var(--picker-w, 0px);
    /* Just below the zoom stack, which ends around 9rem under the header.
       Level with it this sat *underneath* — they share the edge and Leaflet's
       controls are far higher in the stack, so the clicks went to the map. */
    top: calc(var(--chrome-top, 4rem) + 9.5rem);
    z-index: 1100;
    width: 1.15rem;
    height: 2.6rem;
    padding: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    -webkit-backdrop-filter: blur(8px);
    backdrop-filter: blur(8px);
    border: 1px solid var(--line);
    border-left: 0;
    border-radius: 0 3px 3px 0;
    color: var(--fg-muted);
    cursor: pointer;
    transition: left .2s ease, background .12s ease, color .12s ease;
}
.picker-toggle:hover { background: var(--surface-2); color: var(--fg); }
.picker-toggle:focus-visible { outline: 2px solid var(--orange); outline-offset: 1px; }
.picker-toggle svg { transition: transform .2s ease; }
.mapchrome.picker-hidden .picker-toggle svg { transform: rotate(180deg); }

@media (prefers-reduced-motion: reduce) {
    .mapchrome .picker, .picker-toggle, .picker-toggle svg { transition: none; }
}

/* On a narrow window the sidebar is a bottom sheet, and hiding it sideways
   makes no sense. */
@media (max-width: 780px) {
    .picker-toggle { display: none; }
    .mapchrome.picker-hidden .picker { transform: none; opacity: 1; pointer-events: auto; }
}

/* Whole-file product downloads. */
.productpanel { width: min(22rem, calc(100% - 2rem)); padding: .45rem .6rem .55rem; }
.product-list { list-style: none; margin: .35rem 0 0; padding: 0; }
.product-list li {
    display: grid;
    grid-template-columns: 1fr auto;
    align-items: baseline;
    gap: 0 .5rem;
    padding: .3rem 0;
    border-top: 1px solid var(--line-soft, var(--line));
}
.product-list li:first-child { border-top: 0; }
.product-name { font-weight: 600; font-size: .78rem; }
/* Size before the name is read: a 1 GB elevation model is a different decision
   from a 7 MB report, and on a field connection it is the deciding one. */
.product-meta {
    grid-column: 1;
    font-family: var(--font-mono);
    font-size: 10px;
    color: var(--fg-faint);
}
.product-crop { grid-row: 1 / span 2; grid-column: 2; font-size: .72rem; align-self: center; }

/* The page you are on.
 *
 * An underline rather than a colour change: the nav sits on a translucent bar
 * over aerial imagery, and a colour that reads as "active" over dark scrub does
 * not over pale dirt.
 *
 * The rule needs room to be seen. The links had no vertical padding, so the
 * first attempt drew a 2px line hard against the descenders where it read as
 * part of the type rather than as a mark. Padding gives it somewhere to sit. */
.nav a {
    padding-bottom: .45rem;
    border-bottom: 2px solid transparent;
    transition: border-color .12s ease, color .12s ease;
}
/* --chrome-fg, not --fg. The masthead is Night Sky in both schemes, so the
 * page foreground is only readable on it by coincidence: in light mode --fg is
 * near-black on a near-black bar, and the current page went to 1.07:1 — there,
 * but invisible. The chrome tokens are the ones that know what this bar is. */
.nav a.here,
.nav a.here:hover {
    color: var(--chrome-fg);
    border-bottom-color: var(--orange);
}
.nav a:hover { border-bottom-color: color-mix(in srgb, var(--orange) 40%, transparent); }

/* ---------- error pages ---------- */

/* The page that renders when something has already gone wrong. No JavaScript,
   no tenant, no database — see templates/layout/error.php for why. */
.errorpage { display: flex; flex-direction: column; min-height: 100vh; }
.errorpage main { flex: 1; display: flex; align-items: center; }

.errorbody { max-width: 34rem; padding: 4rem 0; }

/* The status, set as a mark rather than a heading: it is the first thing
   someone reporting the problem will be asked for, and the sentence under it
   is what they actually need to read. */
.errorcode {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .18em;
    color: var(--orange);
    margin: 0 0 .75rem;
}

.errorbody h1 { font-size: clamp(1.75rem, 4vw, 2.5rem); letter-spacing: -.03em; margin: 0 0 .75rem; }
.errorlede { font-size: 1.0625rem; color: var(--fg-muted, var(--fg-faint)); margin: 0 0 1rem; }
.errornote { color: var(--fg-faint); margin: 0 0 1.5rem; }

.errorurl {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .5rem;
    margin: 0 0 1.75rem;
    font-size: var(--fs-5);
}
.errorurl span { text-transform: uppercase; letter-spacing: .12em; color: var(--fg-faint); }
.errorurl code {
    font-family: var(--font-mono);
    /* A long survey URL must wrap rather than push the page sideways. */
    overflow-wrap: anywhere;
    background: var(--line-soft);
    padding: .2rem .4rem;
    border-radius: 2px;
}

.errorlinks { display: flex; flex-wrap: wrap; align-items: center; gap: 1.25rem; }

/* ---------- analyses ---------- */

.analysis-new { border-bottom: 1px solid var(--line-soft); padding-bottom: .6rem; margin-bottom: .6rem; }
.analysis-choice { display: flex; align-items: center; gap: .75rem; }
.analysis-describes { margin: .2rem 0 .6rem; font-size: var(--fs-5); color: var(--fg-faint); }

.analysis-list { list-style: none; margin: 0; padding: 0; }
.analysis-empty { font-size: var(--fs-5); color: var(--fg-faint); }

.analysis-item { padding: .55rem 0; border-bottom: 1px solid var(--line-soft); }
.analysis-item:last-child { border-bottom: 0; }

/* The head is a button now, so it has to be told not to look like one. */
.analysis-head {
    display: flex;
    justify-content: space-between;
    align-items: baseline;
    gap: .5rem;
    flex: 1 1 auto;
    min-width: 0;
    background: none;
    border: 0;
    padding: 0;
    margin: 0;
    color: inherit;
    font: inherit;
    text-align: left;
    cursor: pointer;
}

.analysis-head strong { flex: 1 1 auto; min-width: 0; }

.analysis-row { display: flex; align-items: baseline; gap: .5rem; }

/* Rotates rather than swapping glyphs, so open and shut are the same mark at
   two angles and nothing shifts as it turns. */
.analysis-caret {
    flex: 0 0 auto;
    align-self: center;
    width: .5rem;
    height: .5rem;
    border-right: 1.5px solid var(--fg-faint);
    border-bottom: 1.5px solid var(--fg-faint);
    transform: rotate(-45deg);
    transition: transform .12s ease;
}

.analysis-item.is-open .analysis-caret { transform: rotate(45deg); }

/* Shut by default: seven results is seven walls of facts otherwise. */
.analysis-body { display: none; }
.analysis-item.is-open .analysis-body { display: block; }

.analysis-delete {
    flex: 0 0 auto;
    align-self: center;
    width: 1.4rem;
    height: 1.4rem;
    padding: 0;
    border: 1px solid transparent;
    border-radius: 3px;
    background: none;
    color: var(--fg-faint);
    font-size: var(--fs-4);
    line-height: 1;
    cursor: pointer;
}

/* Only reads as destructive once it is being aimed at — a row of red crosses
   down a results list is a panel that looks like a list of errors. */
.analysis-delete:hover:not(:disabled),
.analysis-delete:focus-visible {
    color: var(--alert);
    border-color: var(--line);
}

.analysis-delete:disabled { opacity: .4; cursor: default; }

/* The state, not the name, is what someone opening this panel is looking for. */
.analysis-state {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-faint);
    white-space: nowrap;
}

.status-running .analysis-state { color: var(--orange); }
.status-failed .analysis-state { color: var(--alert); }
.status-completed .analysis-state { color: var(--ok); }

.analysis-stage { margin: .2rem 0 0; font-size: var(--fs-5); color: var(--fg-faint); }
.analysis-error { margin: .2rem 0 0; font-size: var(--fs-5); color: var(--alert); }

/* Label and figure on one line each, so a column of results can be compared
   down the panel rather than read as sentences. */
.analysis-facts {
    display: grid;
    grid-template-columns: auto 1fr;
    gap: .1rem .75rem;
    margin: .4rem 0 .1rem;
    font-size: var(--fs-5);
}
.analysis-facts dt { color: var(--fg-faint); }
.analysis-facts dd { margin: 0; font-family: var(--font-mono); text-align: right; }

.analysis-actions { display: flex; flex-wrap: wrap; align-items: center; gap: .75rem; margin-top: .45rem; }

.analysis-formats { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }
.analysis-formats span {
    font: 500 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-faint);
}
.analysis-formats .btn-link { font-size: var(--fs-5); font-family: var(--font-mono); }

/* A numbered stop on a visiting route. */
.route-stop span {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 22px;
    height: 22px;
    border-radius: 50%;
    background: var(--orange);
    color: var(--night);
    font: 700 10px/1 var(--font-mono);
    border: 2px solid var(--night);
}

/* The analysis panel carries a sentence of description per choice, which left
   unchecked stretches the dock across half the map. */
.analysispanel { width: min(27rem, calc(100vw - 2rem)); }
.analysis-describes { max-width: 100%; }

.analysis-route {
    margin: .5rem 0 0;
    padding-top: .5rem;
    border-top: 1px solid var(--line-soft);
    font-size: var(--fs-5);
    color: var(--orange);
}

/* ---------- analysis pages ---------- */

.analysis-picker { display: flex; align-items: center; gap: 1rem; flex-wrap: wrap; }
.analysis-picker label {
    font: 500 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin: 0;
}
.analysis-picker select { flex: 1 1 22rem; margin: 0; }

/* One card per analysis, side by side while there is room. Each has to carry a
   sentence of description, so they do not collapse below a readable width. */
.analysis-cards {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: var(--gap, 1rem);
}

.analysis-card h3 { margin: 0 0 .35rem; font-size: 1.0625rem; }
.analysis-card p { color: var(--fg-muted, var(--fg-faint)); }
.analysis-card form { margin: 0; }

/* Still shown, not hidden: knowing an analysis exists and what it is waiting
   for is worth more than a shorter page. */
.analysis-card.unavailable { opacity: .72; }
.analysis-blocked { font-size: var(--fs-5); color: var(--fg-faint); margin: 0; }

.analysis-downloads { display: flex; flex-wrap: wrap; gap: .5rem; }

/* Stat tiles. The portal has had these since it was built; the operator
   application needs them now that a result has headline figures of its own,
   and one definition is better than the two drifting apart. */
.tiles {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
    gap: .75rem;
    margin: 0 0 1.25rem;
}

.tile {
    background: var(--surface, transparent);
    border: 1px solid var(--line);
    padding: .85rem 1rem;
}

.tile dt {
    font: 500 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin-bottom: .4rem;
}

.tile dd {
    margin: 0;
    /* Tabular so a column of figures lines up on the decimal — these are read
       by comparison far more often than one at a time. */
    font: 600 1.35rem/1.1 var(--font-mono);
    font-variant-numeric: tabular-nums;
    letter-spacing: -.02em;
}

/* Columns inside a card need room between them. The shared table rule is
   `padding: 6px 0` — right for a two-column fact list, wrong for the eight
   columns of a tree list, where the cumulative distance ran straight into the
   coordinates and read as one number. */
.card table th,
.card table td { padding-inline: .85rem; }
.card table th:first-child,
.card table td:first-child { padding-inline-start: 0; }
.card table th:last-child,
.card table td:last-child { padding-inline-end: 0; }

/* The one tool in the bar that leaves the map. Bigger and filled, because it is
   a destination rather than a layer toggle, and because on a bar of six equal
   buttons the most useful one was the hardest to find. */
.maptool.maptool-primary {
    background: var(--orange);
    color: var(--night);
    font-size: .8rem;
    font-weight: 700;
    padding: .5rem .9rem;
}

.maptool.maptool-primary:hover { background: var(--orange-hi); color: var(--night); }

/* ---------- analysis, in the map sidebar ---------- */

.picker-analysis { border-top: 1px solid var(--line); padding: .85rem; }
.picker-analysis h2 {
    font: 500 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .1em;
    text-transform: uppercase;
    color: var(--fg-faint);
    margin: 0 0 .6rem;
}

.analysis-choices { display: grid; gap: .35rem; }

/* Title and what it does on one control: choosing between seven analyses means
   reading what they do, and a bare list of names is a quiz. */
.analysis-option {
    display: grid;
    gap: .15rem;
    text-align: left;
    background: none;
    border: 1px solid var(--line);
    color: var(--fg);
    padding: .5rem .6rem;
    font: inherit;
    cursor: pointer;
}
.analysis-option strong { font-size: var(--fs-4); font-weight: 600; }
.analysis-option span { font-size: var(--fs-5); color: var(--fg-faint); line-height: 1.35; }
.analysis-option:hover:not(:disabled) { border-color: var(--orange); }
.analysis-option.on { border-color: var(--orange); background: rgba(255, 90, 31, .1); }
.analysis-option:disabled { opacity: .55; cursor: not-allowed; }

/* A model that can never read this imagery, as opposed to a survey that has
   not been reconstructed far enough yet. The second is a wait and the first is
   a no, and the same grey for both invites somebody to keep checking back. */
.analysis-option.is-unsuitable span { color: var(--warn, #b4801f); }

/* The public masthead: sign in, and the trial. Buttons rather than the nav's
   plain links, because these are the two actions the page exists to offer and
   the nav they sit in has nothing else in it to be consistent with. */
.nav-public { display: flex; align-items: center; gap: .6rem; }
.nav-public .btn { white-space: nowrap; }

/* On a phone the two buttons and the wordmark do not fit on one line. The
   trial is the one that can go: it is the hero's primary action, an inch
   further down the same screen, whereas signing in has nowhere else to be. */
@media (max-width: 34rem) {
    .nav-public .btn:not(.btn-quiet) { display: none; }
}

/* ---------- people ---------- */

.invite-form .field-pair { align-items: end; }
.invite-form .field { display: grid; gap: .25rem; }
.invite-form .field > span:first-child,
.signup-form .field > span:first-child { text-transform: uppercase; }

/* A select that submits on change needs no button beside it, so the form is
   laid out as if it were just the control. */
.inline-form { display: inline; margin: 0; }
.inline-form select { margin: 0; min-width: 9rem; }

.row-actions { white-space: nowrap; text-align: right; }
.table tr.is-inactive td, .table tr.is-inactive th { opacity: .55; }
.table .muted.small { display: block; }

/* A description list, not a facts table: `.facts` right-aligns its values so a
   column of measurements lines up, and a sentence right-aligned against the
   page edge is unreadable. */
.role-list { display: block; }
.role-list dt { margin: .7rem 0 .1rem; }
.role-list dd { margin: 0; text-align: left; color: var(--fg-faint); }

/* ---------- the account needs attention ---------- */

/* In the flow, not floating. On the map layout `.mapshell` is `flex: 1` in a
   column, so a banner between the masthead and it simply takes its own height
   and the map fills what is left — no overlay, and nothing covered. */
.billing-banner {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 1rem;
    flex-wrap: wrap;
    padding: .55rem 1rem;
    font-size: var(--fs-4);
    border-bottom: 1px solid var(--line);
    background: var(--surface-2);
    color: var(--fg);
}
.billing-banner a { font-weight: 600; }

/* Two tones, because two different things are being said: one is a deadline
   approaching and the other is a thing that has already happened. */
.billing-banner.warn {
    background: color-mix(in srgb, var(--warn, #b4801f) 14%, transparent);
    border-bottom-color: var(--warn, #b4801f);
}
.billing-banner.alert {
    background: color-mix(in srgb, var(--alert) 14%, transparent);
    border-bottom-color: var(--alert);
}

.usage-table .is-at-limit td,
.usage-table .is-at-limit th { color: var(--warn, #b4801f); font-weight: 600; }

.plan-price { font: 600 var(--fs-3)/1.2 var(--font-mono); margin: 0 0 .4rem; }

.subscription-actions { margin: 1rem 0 0; }

.reset-accounts { list-style: none; padding: 0; margin: 1rem 0; display: grid; gap: .6rem; }
.reset-accounts li { display: grid; gap: .1rem; }
.reset-accounts .muted { font-family: var(--font-mono); }

/* ---------- choosing between operators ---------- */

.signin-wide { max-width: 32rem; }

.operator-choice { display: grid; gap: .5rem; margin: 1.2rem 0; }

/* Each operator is one target, not a radio plus a submit. The choice and the
   action are the same act, and splitting them adds a step to a page whose whole
   job is to get out of the way. */
.operator-option {
    display: grid;
    gap: .15rem;
    text-align: left;
    background: var(--surface-2);
    border: 1px solid var(--line);
    color: var(--fg);
    padding: .7rem .85rem;
    font: inherit;
    cursor: pointer;
}
.operator-option:hover { border-color: var(--orange); }
.operator-option strong { font-size: var(--fs-3); font-weight: 600; }
.operator-host { font: var(--fs-5)/1 var(--font-mono); color: var(--fg-faint); }
.operator-role {
    font: 600 var(--fs-5)/1.4 var(--font-mono);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--fg-faint);
}

/* ---------- signing up ---------- */

.signup-form .field { display: grid; gap: .25rem; margin-bottom: 1.1rem; }
.signup-form .field > span:first-child {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--fg-faint);
}
/* Undo the global `label` rule, which uppercases and mono-spaces everything
   inside a label — right for the field's name, wrong for a sentence explaining
   it. WHOLE SENTENCES SHOUTED IN MONOSPACE is what these read as otherwise, and
   they are the part doing the explaining. */
.signup-form .hint,
.signup-form .check span {
    font: var(--fs-5)/1.45 var(--font-sans);
    font-weight: 400;
    letter-spacing: 0;
    text-transform: none;
    color: var(--fg-faint);
}
.signup-form .check span { font-size: var(--fs-4); color: var(--fg); }
.signup-form .hint.is-free { color: var(--ok, #4f9d5d); }
.signup-form .hint.is-taken { color: var(--warn, #b4801f); }

.field-error { font-size: var(--fs-5); color: var(--alert); }

/* Two on a line where the screen allows and stacked where it does not — a
   given name and a family name are one question asked twice. */
.field-pair { display: grid; gap: 0 1rem; grid-template-columns: 1fr 1fr; }
@media (max-width: 30rem) { .field-pair { grid-template-columns: 1fr; } }

/* The address and its domain read as one thing, because they are one thing.
   Shown rather than derived silently: it is permanent and it is theirs. */
.slug-row { display: flex; align-items: stretch; }
.slug-row input { margin: 0; border-right: 0; border-radius: 0; min-width: 0; }
.slug-domain {
    display: flex;
    align-items: center;
    padding: 0 .7rem;
    border: 1px solid var(--line);
    background: var(--surface-2);
    color: var(--fg-faint);
    font: var(--fs-5)/1 var(--font-mono);
    white-space: nowrap;
}

/* The terms, in the form, scrollable rather than collapsed behind a link.
   Somebody ticking a box next to text they were never shown is the version
   that is worth nothing later. */
.terms-scroll {
    max-height: 16rem;
    overflow-y: auto;
    border: 1px solid var(--line);
    background: var(--surface-2);
    padding: .8rem 1rem;
    margin-bottom: 1rem;
    font-size: var(--fs-5);
    line-height: 1.5;
}
.terms-scroll p { margin: 0 0 .7em; }

.signup-url { font: var(--fs-3)/1.3 var(--font-mono); word-break: break-all; }

.legal-body { white-space: normal; }
.legal-body p { margin: 0 0 .9em; line-height: 1.6; }

/* ---------- an area of the basemap ---------- */

/* A button, like the ones the analysis step offers below it. It was an
   underlined link, which read as a footnote to the base-imagery row rather than
   as the action it is — and every other thing the sidebar can do is a button. */
.picker-base-label {
    margin: .5rem .75rem .1rem;
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--fg-faint);
}

/* Two shapes, side by side and equal — neither is the default, because which
   one is right depends entirely on the ground. */
.basemap-shapes {
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: .4rem;
    margin: 0 .75rem .55rem;
}
.basemap-shapes .btn { text-align: center; }

/* `[hidden]` first, and it is not decoration. The attribute only works through
   the user-agent rule `display: none`, and any author rule setting `display`
   outranks it — so a panel styled `display: grid` and marked hidden is simply
   visible, with nothing in the markup or the stylesheet looking wrong. This
   one shipped open, above the survey list, with an empty detail box. */
.basemap-step[hidden] { display: none; }

.basemap-step {
    display: grid;
    gap: .45rem;
    padding: .6rem .75rem .75rem;
    border-top: 1px solid var(--line);
    margin-top: .35rem;
}

.basemap-step .field { margin: 0; display: grid; gap: .2rem; }
.basemap-step .field > span {
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--fg-faint);
}
.basemap-step select { width: 100%; margin: 0; }
.basemap-step p { margin: 0; }

/* The two numbers that decide whether to press the button, and the one place a
   refusal is explained — so it is a live line rather than an alert after the
   fact. Monospaced because it is mostly figures being compared. */
.basemap-estimate {
    font: var(--fs-5)/1.4 var(--font-mono);
    color: var(--fg-faint);
}
.basemap-estimate.is-refused { color: var(--alert); }

.basemap-actions { display: flex; align-items: center; gap: .6rem; }

.analysis-extent { margin-top: .7rem; display: grid; gap: .4rem; }
.analysis-chosen {
    margin: 0;
    font: 600 var(--fs-5)/1 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--orange);
}

.analysis-results { margin-top: .9rem; display: grid; gap: .5rem; }
.analysis-results .analysis-item { border-top: 1px solid var(--line-soft); padding-top: .5rem; }
.analysis-format { font-family: var(--font-mono); font-size: var(--fs-5); }

/* ---------- shared with a client ---------- */

.sharepage { display: flex; flex-direction: column; min-height: 100vh; }
.sharepage main { flex: 1; }

.share-list { list-style: none; margin: 0; padding: 0; }
.share-list li {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    padding: .7rem 0;
    border-bottom: 1px solid var(--line-soft);
}
.share-list li:last-child { border-bottom: 0; }
.share-name { font-weight: 600; }
.share-meta { font-family: var(--font-mono); font-size: var(--fs-5); color: var(--fg-faint); }

.analysis-heading { display: flex; align-items: baseline; justify-content: space-between; gap: .75rem; }
.analysis-heading h2 { margin: 0; }
.analysis-heading .btn-link { font-size: var(--fs-5); white-space: nowrap; }

.analysis-note {
    margin: .6rem 0 0;
    padding: .45rem .6rem;
    border-left: 2px solid var(--orange);
    background: rgba(255, 90, 31, .1);
    font-size: var(--fs-5);
}

/* The tab the results pop out of. Always there once a survey has any, so the
   layout does not move when one finishes. */
/* Same surface, blur, border and shadow as every other panel on this map —
   it was Night Sky with masthead foregrounds, which is the chrome's palette
   and made the one panel that floats over the imagery look like a different
   application. */
.analysis-tab {
    position: absolute;
    /* Below the masthead, which is painted over the map. At the plain gutter
       this sat behind it: invisible, and `elementFromPoint` at its centre
       returned the account menu, so closing the results panel closed it for
       good. */
    top: calc(var(--masthead-h) + 1px + var(--map-gutter, 1rem));
    right: 0;
    z-index: 621;
    display: flex;
    align-items: center;
    gap: .5rem;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    color: var(--fg);
    border: 1px solid var(--line);
    border-right: 0;
    border-radius: 3px 0 0 3px;
    box-shadow: 0 10px 30px rgb(0 0 0 / .22);
    padding: .5rem .75rem;
    font: 600 12px/1 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    cursor: pointer;
}

.analysis-tab:hover { color: var(--orange); }
.analysis-tab.on { border-color: var(--orange); color: var(--orange); }

.analysis-tab-count {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 1.35rem;
    height: 1.35rem;
    padding: 0 .3rem;
    border-radius: 999px;
    background: var(--orange);
    color: var(--night);
    font-size: 10px;
}

/* Results, on the map rather than under the chooser. Built on .mapdock-panel's
   surface so it sits in the same visual family as the contour and export
   panels, and reads as this application rather than as the masthead. */
.analysis-panel {
    position: absolute;
    /* The same gutter above as beside, so the panel sits square in the corner
       of the visible map. The masthead is painted over the top of the map, so
       "the top of the map" is under it, not at y=0 — hence the offset. The 1px
       is the masthead's bottom border. */
    top: calc(var(--masthead-h) + 1px + var(--map-gutter, 1rem));
    right: var(--map-gutter, 1rem);
    z-index: 620;
    width: min(23rem, calc(100% - 2rem));

    /*
     * Full height, always — not sized to its contents.
     *
     * Sizing to content meant the panel changed shape every time a result
     * arrived or a download menu opened, and the map moved under it. A fixed
     * column is a place on the screen that results appear in, which is what a
     * panel is for.
     *
     * It stops above the map toolbar rather than at the gutter: .maptools sits
     * `bottom: 1.5rem` at the same right edge, and a panel run to the floor
     * would cover Analyse and Exports — the two buttons somebody reading these
     * results is most likely to want next.
     */
    bottom: calc(1.5rem + 2.9rem);
    display: flex;
    flex-direction: column;
    /* The results scroll; the heading and its close button stay put. */
    overflow: hidden;
    background: color-mix(in srgb, var(--surface) 94%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    color: var(--fg);
    border: 1px solid var(--line);
    border-radius: 3px;
    box-shadow: 0 10px 30px rgb(0 0 0 / .22);
    padding: .85rem;
}

/* `display: flex` on the panel beats the UA stylesheet's `[hidden]
   { display: none }`, so the closed panel would sit open over the map. */
.analysis-panel[hidden] { display: none; }

/* The tab is the closed state of the panel, so it goes away when the panel is
   open — they occupy the same corner now that the panel starts at the gutter,
   and the panel carries its own close button. */
.analysis-tab:has(~ .analysis-panel:not([hidden])) { display: none; }

.analysis-panel-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: .75rem;
    margin-bottom: .5rem;
    flex: 0 0 auto;
}

/* The one part that scrolls. min-height:0 because a flex item's default
   min-height is its content, which would push the panel past its own bottom
   instead of scrolling inside it. */
.analysis-results {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
    /* The results are a grid, and a grid given more height than its rows need
       shares the surplus between them. Two analyses ended up half a screen
       apart with a rule between them, which reads as a page still loading. */
    align-content: start;
}

.analysis-panel-head h2 {
    margin: 0;
    font: 600 12px/1 var(--font-mono);
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--fg);
}

/* Nothing to override any more: the panel is on --surface like the rest, so the
   page's own foreground tokens are the right ones. */
.analysis-panel .analysis-item { border-top-color: var(--line-soft); }

/*
 * On the analysis page the sidebar carries two things that both want room: the
 * survey list and the chooser under it. The list takes `flex: 1` and left the
 * chooser squeezed off the bottom with nothing scrolling to reach it — the
 * button that runs the analysis sat at y=945 in a 900px window.
 *
 * So the list gets a bounded share and the chooser scrolls itself. Scoped with
 * :has() so the imagery page, which has no chooser, keeps its full-height list.
 */
.picker:has(.picker-analysis) .picker-list { flex: 0 1 auto; max-height: 40%; }

.picker:has(.picker-analysis) .picker-analysis {
    flex: 1 1 auto;
    overflow-y: auto;
    min-height: 0;
}

/* One control instead of seven links. */
.analysis-download {
    margin: 0;
    padding: .2rem .4rem;
    background: transparent;
    border: 1px solid var(--line);
    color: var(--fg);
    font: 500 var(--fs-5)/1.4 var(--font-mono);
    max-width: 11rem;
}
.analysis-download:focus { outline: none; border-color: var(--orange); }

/* ---------------------------------------------------------------------------
   Trained models — the Train page, its settings form and the labelling screen.

   `.pill`, `.table`, `.button` and `.right` are defined here rather than beside
   the components that first used them: the fleet page has been asking for
   `.pill` and `.right` since it was written and getting nothing, which is the
   silent-CSS failure this file already warns about. They are shared now.
   --------------------------------------------------------------------------- */

.button {
    appearance: none;
    display: inline-flex;
    align-items: center;
    gap: .5rem;
    border: 1px solid var(--orange);
    border-radius: var(--radius);
    background: var(--orange);
    color: var(--night);
    padding: .7rem 1.15rem;
    font: 650 .8125rem/1 var(--font-sans);
    text-decoration: none;
    cursor: pointer;
    transition: background .2s, border-color .2s, transform .2s;
}
.button:hover:not(:disabled) { background: var(--orange-hi); border-color: var(--orange-hi); transform: translateY(-1px); }
.button:disabled { opacity: .4; cursor: not-allowed; }
.button.ghost { background: transparent; color: var(--fg-muted); border-color: var(--line); }
.button.ghost:hover:not(:disabled) { color: var(--fg); border-color: var(--fg-muted); background: transparent; }
.button.danger { background: var(--alert); border-color: var(--alert); color: var(--paper); }

button.link, a.link {
    appearance: none;
    background: none;
    border: 0;
    padding: 0;
    color: var(--action);
    font: inherit;
    cursor: pointer;
    text-decoration: underline;
}

.table { width: 100%; border-collapse: collapse; font-size: var(--fs-4); }
.table th, .table td { padding: .6rem .5rem; border-bottom: 1px solid var(--line-soft); text-align: left; vertical-align: top; }
.table th { font-weight: 650; color: var(--fg-muted); font-size: var(--fs-5); text-transform: uppercase; letter-spacing: .04em; }
.table td.right, .table th.right, .right { text-align: right; }

.small { font-size: var(--fs-5); }

.pill {
    display: inline-block;
    padding: .15rem .55rem;
    border: 1px solid var(--line);
    border-radius: 999px;
    font-size: var(--fs-5);
    color: var(--fg-muted);
    white-space: nowrap;
}
.pill.ok { border-color: var(--ok); color: var(--ok); }
.pill.bad { border-color: var(--alert); color: var(--alert); }
.pill.running { border-color: var(--orange); color: var(--orange); }

.row { display: flex; gap: var(--gap); flex-wrap: wrap; }
.row .grow { flex: 1 1 20rem; }

.card.empty { text-align: center; }
.card.danger { border-color: var(--alert); }

label.check, label.class-pick {
    display: flex;
    align-items: baseline;
    gap: .6rem;
    padding: .35rem 0;
    cursor: pointer;
}
label.check span strong { font-weight: 650; }
label.inline { display: inline-flex; align-items: center; gap: .6rem; }

.class-row { display: flex; gap: .5rem; align-items: center; margin-bottom: .5rem; }
.class-row input[type=text] { flex: 1 1 auto; }
.class-row input[type=color] { width: 3rem; height: 2.4rem; padding: .1rem; }

.field-label {
    font: 650 var(--fs-5)/1 var(--font-sans);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--fg-muted);
    margin: 1rem 0 .4rem;
}

.swatch { width: .85rem; height: .85rem; border-radius: 3px; display: inline-block; flex: 0 0 auto; }

/* A bar rather than a spinner: training reports a real fraction, and a spinner
   would throw that away. */
.meter { height: .4rem; background: var(--line-soft); border-radius: 999px; overflow: hidden; }
.meter-fill { height: 100%; background: var(--orange); transition: width .4s; }

/* The labelling screen fills the window under the masthead — it is a map, and a
   map in a column is not worth drawing. */
.label-screen {
    display: flex;
    position: fixed;
    top: var(--masthead-h);
    left: 0;
    right: 0;
    bottom: 0;
}
.label-panel {
    flex: 0 0 var(--picker-w, 22rem);
    overflow-y: auto;
    padding: var(--pad);
    border-right: 1px solid var(--line);
    background: var(--surface);
}
.label-map { flex: 1 1 auto; height: 100%; min-width: 0; }
.label-panel h1 { font-size: var(--fs-2); margin: .2rem 0 1rem; }
.label-panel .actions { display: flex; gap: .5rem; flex-wrap: wrap; margin: .75rem 0; }
.label-panel .status { color: var(--fg-muted); font-size: var(--fs-5); min-height: 2.4em; }
.label-panel .tally { margin-left: auto; color: var(--fg-muted); font-variant-numeric: tabular-nums; }

.verdict { display: flex; align-items: center; gap: .5rem; padding: .3rem 0; font-size: var(--fs-5); }
.verdict .choice { margin-left: auto; display: flex; gap: .5rem; }
.verdict .choice label { display: inline-flex; align-items: center; gap: .2rem; cursor: pointer; }

/* Labels that are sentences, not field names.

   The stock `label` is uppercased and letter-spaced, which is right for "NAME"
   above an input and wrong for "Canopy height — needs dsm + dtm": it came out
   as CANOPY HEIGHT NEEDS DSM DTM, one shouted run-on with the punctuation
   swallowed. These read as prose. */
label.check, label.class-pick, label.inline, .verdict label {
    text-transform: none;
    letter-spacing: normal;
    font-weight: 400;
    font-size: var(--fs-4);
    color: var(--fg);
    margin-bottom: 0;
}
label.check span, label.class-pick span { display: inline; }
label.check .muted, label.class-pick .muted { color: var(--fg-muted); }
label.check input, label.class-pick input, .verdict input { flex: 0 0 auto; }

/* The stock rules name every input type but not textarea, so a description box
   fell back to the browser's own — white, and half the width of the field
   above it. */
textarea {
    width: 100%;
    padding: .75rem .8125rem;
    font: 1rem/1.4 var(--font-sans);
    color: var(--fg);
    background: var(--bg);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    resize: vertical;
}
textarea:focus { outline: none; border-color: var(--orange); }

/* The list of what has been outlined, beside the map. */
.label-list { margin-bottom: 1rem; }
.label-row {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .3rem .4rem;
    border-radius: var(--radius);
    font-size: var(--fs-5);
    cursor: pointer;
}
.label-row:hover { background: color-mix(in srgb, var(--fg) 8%, transparent); }
.label-row.on { background: color-mix(in srgb, var(--orange) 18%, transparent); }
.label-row .label-name { flex: 1 1 auto; }
.label-row .label-remove { color: var(--fg-muted); text-decoration: none; font-size: 1.1rem; line-height: 1; }
.label-editor { display: flex; gap: .5rem; align-items: center; padding: .3rem .4rem .6rem 1.4rem; }
.label-editor .label-class { flex: 1 1 auto; padding: .3rem .4rem; font-size: var(--fs-5); }
.label-row .label-edit, .label-row .label-remove { font-size: var(--fs-5); text-decoration: none; color: var(--fg-muted); }
.label-row:hover .label-edit { color: var(--orange); }
.label-row:hover .label-remove { color: var(--alert); }

/* The zoom control belongs in the map's own top-left corner.
   `.mapchrome .leaflet-left` is offset by the picker width because on the
   imagery map the picker floats *over* the map. Here the panel is a flex
   sibling and the map already begins to the right of it, so the offset applied
   twice and put the control in the middle of the picture. */
.label-screen .leaflet-left { margin-left: 0; }
.label-screen .leaflet-top { margin-top: 0; }
/* Floating over the map's top-right. Above Leaflet's own controls (z-index
   1000) so it is never buried under the attribution or a zoom button. */
.map-tools {
    position: absolute;
    top: 1rem;
    right: 1rem;
    z-index: 1100;
    display: flex;
    gap: .5rem;
    align-items: flex-start;
}
.map-tools > * , .drawing-tools {
    padding: .5rem;
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    border: 1px solid var(--line);
}
.drawing-tools { display: flex; gap: .5rem; }
/* …and `hidden` has to beat that `display: flex`. */
.drawing-tools[hidden] { display: none; }

/* `hidden` must actually hide.
   The attribute's UA rule is `display: none` at the lowest specificity, so any
   class rule that sets `display` beats it — `.field` and `.actions` both do.
   The layer picker stayed on screen as an empty select, and the verdict panel
   as a stray empty box, in each case because the element was hidden in the only
   sense that did not matter. */
.label-panel [hidden] { display: none !important; }

/* The status line, along the foot of the map rather than the foot of the panel.
   Offset by the panel's width because its containing block is the whole screen,
   not the map. */
.map-status {
    position: absolute;
    bottom: 1.25rem;
    left: calc(var(--picker-w, 22rem) + 1rem);
    max-width: 40rem;
    z-index: 1100;
    margin: 0;
    padding: .5rem .75rem;
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    border: 1px solid var(--line);
    color: var(--fg);
    font-size: var(--fs-5);
}
.map-status[hidden] { display: none; }

/* Outlines nested under the training area they belong to. */
.area { margin-bottom: .35rem; }
.area-children { padding-left: 1.1rem; border-left: 1px solid var(--line-soft); margin-left: .55rem; }
.area-children[hidden] { display: none; }
.area-children .label-row { font-size: var(--fs-5); }
.area-toggle {
    width: 1rem;
    flex: 0 0 auto;
    color: var(--fg-muted);
    text-decoration: none;
    line-height: 1;
}
.area-row .label-name { font-weight: 650; }

/* A heading with an action on the end of it. */
.heading-with-add { display: flex; align-items: center; justify-content: space-between; gap: .5rem; }
.add-button {
    appearance: none;
    width: 1.5rem;
    height: 1.5rem;
    flex: 0 0 auto;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: transparent;
    color: var(--fg-muted);
    font: 500 1.05rem/1 var(--font-sans);
    cursor: pointer;
}
.add-button:hover { color: var(--night); background: var(--orange); border-color: var(--orange); }
/* Its own class. `.queue` is the imagery map's floating job widget —
   position:absolute, right:1rem, z-index:600 — so the Train panel's queue was
   rendering over the top-right of the map instead of in the panel. */
.train-queue { margin: .75rem 0; }
.train-queue[hidden] { display: none; }

/* The whole screen, while the box works.
   Over the masthead too: the map, the panel and the menu are all inert until
   the GPU comes back, and dimming only the map would suggest otherwise. */
.busy {
    position: fixed;
    inset: 0;
    z-index: 4000;
    display: flex;
    align-items: center;
    justify-content: center;
    background: color-mix(in srgb, var(--night-deep, #0b0f0e) 78%, transparent);
    backdrop-filter: blur(2px);
}
.busy[hidden] { display: none; }
.busy-card {
    width: min(30rem, calc(100vw - 3rem));
    padding: var(--pad);
    border-radius: var(--radius);
    background: var(--surface);
    border: 1px solid var(--line);
    text-align: center;
}
.busy-title { font: 650 var(--fs-2)/1.2 var(--font-sans); margin: 0 0 .35rem; color: var(--fg); }
.busy-stage { color: var(--fg-muted); font-size: var(--fs-4); margin: 0 0 1rem; min-height: 1.4em; }
.busy-card .meter { margin-bottom: .6rem; }
.table td.nowrap { white-space: nowrap; }
.table td.nowrap a, .table td.nowrap form { margin-left: .5rem; display: inline; }

/* Judging detections on the map. */
.judge-tools {
    position: absolute;
    left: calc(var(--picker-w, 22rem) + 1rem);
    top: 1rem;
    z-index: 1100;
    display: flex;
    gap: .5rem;
    align-items: center;
    padding: .5rem;
    border-radius: var(--radius);
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    border: 1px solid var(--line);
}
.judge-tools[hidden] { display: none; }
/* The one that ends the job, sized so it is obvious it does. */
.judge-tools #save-verdicts { padding: .85rem 1.4rem; font-size: .9375rem; }

/* The little yes/no, at the click. Absolute inside .label-screen, which is the
   fixed containing block, so its coordinates are the map's own. */
.verdict-pop {
    position: absolute;
    z-index: 1200;
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .4rem .5rem;
    border-radius: var(--radius);
    background: var(--surface);
    border: 1px solid var(--line);
    box-shadow: 0 6px 18px rgb(0 0 0 / .35);
    white-space: nowrap;
}
.verdict-pop[hidden] { display: none; }
.verdict-pop-what { font-size: var(--fs-5); color: var(--fg-muted); margin-right: .15rem; }
.verdict-pop .button { padding: .4rem .85rem; }
.verdict-pop .button.yes { background: #4C9A2A; border-color: #4C9A2A; color: #fff; }
.verdict-pop .button.no { border-color: var(--alert); color: var(--alert); }
.verdict-pop .button.no:hover { background: var(--alert); color: var(--paper); }
/* A rejection is ground the model was wrong about, not an example of the thing. */
.label-row.rejected .label-name { text-decoration: line-through; color: var(--fg-muted); }

/* What to do next, once answers are saved. Bottom-centre of the map: out of
   the way of the shapes, and where the eye already is after pressing Save. */
.next-steps {
    position: absolute;
    /* Centred on the visible map, not on the pane that runs under the panel —
       the same sum .maphint uses. `picker-w + 50%` centres it on the window and
       pushes it a whole half-panel to the right. */
    left: calc(var(--picker-w, 0px) + (100% - var(--picker-w, 0px)) / 2);
    transform: translateX(-50%);
    bottom: 4.5rem;
    z-index: 1200;
    width: min(28rem, 60vw);
    padding: var(--pad);
    border-radius: var(--radius);
    background: var(--surface);
    border: 1px solid var(--line);
    box-shadow: 0 8px 24px rgb(0 0 0 / .4);
    text-align: center;
}
.next-steps[hidden] { display: none; }
.next-title { font: 650 var(--fs-3)/1.2 var(--font-sans); margin: 0 0 .3rem; color: var(--fg); }
.next-buttons { display: flex; gap: .5rem; justify-content: center; margin-top: .9rem; }
/* The shape sits before the format: what to export, then what to put it in. */
.analysis-shape { max-width: 9rem; margin-right: .35rem; }

/* `.muted` had no rule of its own.
   Thirty-nine uses across the Train pages rendered at full body colour — the
   same silent-CSS failure `.pill` had, found by measuring the computed colour
   rather than reading this file. */
.muted { color: var(--fg-muted); }

/* The download dialog: what to include, then what file to put it in. */
.download-dialog {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    color: var(--fg);
    padding: 0;
    max-width: min(28rem, calc(100vw - 2rem));
}
.download-dialog::backdrop { background: rgb(0 0 0 / .55); }
.download-form { padding: var(--pad); }
.download-title { font-size: var(--fs-3); margin: 0 0 1rem; }
.download-dialog .field { margin-top: 1rem; }
.download-actions { display: flex; gap: .5rem; margin-top: 1.25rem; }


/* Gaps the UI audit measured, each one a browser default showing through on a
   dark page. Found by reading computed styles, not this file. */

/* The only fieldsets on the site are the model form's, and nothing styled them
   — they rendered with the UA's 3D groove border and a notched legend on a flat
   design. */
fieldset {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: .9rem 1rem 1rem;
    margin: 0 0 1rem;
}
legend {
    padding: 0 .4rem;
    font: 650 var(--fs-5)/1 var(--font-sans);
    text-transform: uppercase;
    letter-spacing: .04em;
    color: var(--fg-muted);
}

/* The two rules in the panel were the UA's inset grey groove. */
hr {
    border: 0;
    border-top: 1px solid var(--line-soft);
    margin: 1.25rem 0;
}

/* Blue is the browser's accent, not this platform's. `input[type=range]`
   already had this; the boxes and radios were missed. */
input[type=checkbox], input[type=radio] { accent-color: var(--orange); }

/* A colour well with a black hairline is the UA's; make it match the fields
   beside it. */
input[type=color] {
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--bg);
    padding: .15rem;
}

/* An `<a class="btn">` was underlined *inside* the orange fill — `.btn` never
   said otherwise. Pre-existing, and on /plans and the upload pages. */
.btn { text-decoration: none; }

/* Labels that are sentences must not be monospace. The Train override reset the
   case, spacing, weight, size and colour of these and missed the family, so
   "Passes over the examples" rendered as code. */
label.check, label.class-pick, label.inline, .verdict label {
    font-family: var(--font-sans);
}

/* `#undo` is a direct child of `.map-tools`, so `.button.ghost` (0,2,0) beat the
   plate rule (0,1,0) and left it transparent over live imagery, at a different
   size from every other button on the screen. */
.map-tools > .button.ghost {
    background: color-mix(in srgb, var(--surface) 92%, transparent);
    padding: .7rem 1.15rem;
}

/* A destructive link, so Delete does not read as Open. `.danger` only ever
   existed scoped to three components, so there was nothing site-wide to reach
   for. */
.danger-link { color: var(--alert); }
.danger-link:hover { color: var(--alert); text-decoration-thickness: 2px; }
