/*
 * Trade Area Analyzer v2.
 *
 * The application is tables of numbers, so most of this is about making a very
 * wide table readable: 39 columns in the admin view, 87 in the client view, and
 * large-market reports run to 300-400 rows.
 */

/* ---------------------------------------------------------------------------
   Tokens.

   A named value everything else refers to, so that changing how the
   application looks means editing this block rather than hunting literals. It
   had thirteen entries and 129 raw colors elsewhere — nine near-identical
   grays, seven reds, six ambers — and two names that were used twenty-seven
   times and never declared, so every use silently took its fallback.

   **One ramp per hue, numbered by lightness.** A component asks for --n-300
   rather than inventing #d0d5db, so two borders drawn by two people a month
   apart are the same border. The semantic names below are what markup should
   actually use: --line says what it is for, --n-200 says what it looks like,
   and a redesign changes the second without touching the first.
   --------------------------------------------------------------------------- */

:root {
    /* Neutrals. The application is tables, so most of the page is drawn from
       this ramp and almost nothing from the others. */
    --n-0: #ffffff;
    --n-25: #fbfcfd;
    --n-50: #f5f7fa;
    --n-100: #eceff4;
    --n-200: #e0e5ec;
    --n-300: #ced5df;
    --n-400: #adb7c4;
    --n-500: #85909e;
    --n-600: #5d6877;
    --n-700: #414b58;
    --n-800: #29313b;
    --n-900: #151a21;

    /* One accent. Color in a data-dense screen should mean something, and
       everything that does not mean anything is a neutral. */
    --a-50: #eef4fa;
    --a-100: #dbe8f4;
    --a-200: #b9d3ea;

    /* Light enough to read on the navy bar, which is the one place the accent
       sits on a dark background instead of under one. */
    --a-300: #7fb5e6;

    --a-500: #1c5e96;
    --a-600: #16507f;
    --a-700: #113f66;

    /* Semantic: what a thing is, rather than what color it is. */
    --g-50: #e9f6ee;
    --g-500: #1f7a45;
    --g-600: #176036;

    --w-50: #fdf5e3;
    --w-400: #d9a008;
    --w-600: #8a6508;

    --r-50: #fdeeee;
    --r-300: #e5a3a3;
    --r-500: #b3312f;
    --r-600: #93231f;

    /* Type. Four sizes and two weights carry the whole application; a fifth
       size is usually a heading that wanted to be a different element. */
    --t-xs: .75rem;
    --t-sm: .8125rem;
    --t-md: .875rem;
    --t-base: .9375rem;
    --t-lg: 1.05rem;
    --t-xl: 1.35rem;

    /* Space. A four-point grid: anything between two steps is a decision
       somebody should have to justify. */
    --s-1: .25rem;
    --s-2: .5rem;
    --s-3: .75rem;
    --s-4: 1rem;
    --s-5: 1.5rem;
    --s-6: 2rem;

    --radius: 6px;
    --radius-sm: 4px;

    /* One shadow, for cards. The popovers mix their own, much heavier and at
       an opacity that has nothing to do with this one. */
    --shadow-sm: 0 1px 2px rgb(21 26 33 / .06);

    /* --- What markup uses ------------------------------------------------ */

    --bg: var(--n-50);
    --surface: var(--n-0);
    --surface-sunken: var(--n-25);
    --surface-raised: var(--n-100);

    --ink: var(--n-900);
    --ink-soft: var(--n-700);
    --muted: var(--n-600);
    --faint: var(--n-500);

    --line: var(--n-200);
    --rule: var(--n-300);
    --line-strong: var(--n-400);

    --accent: var(--a-500);
    --accent-hover: var(--a-600);
    --accent-soft: var(--a-50);

    --rank-bg: var(--n-100);

    /* The heading badge. A tint of the accent rather than the accent itself:
       filled, it carries the same weight as the primary button beside it and
       reads as something you can press. */
    --badge-bg: var(--a-100);
    --badge-ink: var(--a-600);

    /* The default circle. Markup overrides it per badge — declared here rather
       than left to a var() fallback so the value has one home, and so the
       stylesheet guard can still insist every name it reads is one it
       declares. */
    --badge-size: 28px;

    /* Lucide's chevron-down, for the controls that are not text: a select, a
       menu button, a folding card. A background image cannot take
       currentColor, so the color is written into the SVG — which makes this
       the one place in the file a color is spelled rather than named, and the
       reason it is a token at all. The pale one is for the dark app bar. */
    --chevron: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%2385909e' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");
    --chevron-light: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%239fb6cc' stroke-width='2' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='m6 9 6 6 6-6'/%3E%3C/svg%3E");

    --ok-line: var(--g-500);

    --warn-bg: var(--w-50);
    --warn-line: var(--w-400);
    --warn-ink: var(--w-600);

    --error-bg: var(--r-50);
    --error-line: var(--r-500);
    --danger: var(--r-500);
    --danger-hover: var(--r-600);

    /* --- The bar ----------------------------------------------------------

       Its own colors, and not a rung on the neutral ramp. The bar was painted
       with --ink, the body-text token, which meant the one band of color in
       the product could not move without moving every paragraph of text with
       it. Navy rather than near-black: it is the identity, so it is a literal.

       Two text colors, where there were four. The name, the build badge, the
       organization and the signed-in user each picked a different gray off the
       neutral ramp for no reason anyone recorded, and grays mixed for a white
       background go muddy on navy. */
    --app-bar: #12263a;
    --app-bar-raised: #1d3a55;
    --app-bar-ink: var(--n-0);
    --app-bar-ink-soft: #9fb6cc;

    /* --- Data palettes ----------------------------------------------------

       Not part of the ramps above, and not to be tidied into them. A semantic
       token answers "what is this for"; these answer "which one is it", and
       the color is the meaning rather than a decoration on it.

       Collapsing them into --ok-line and friends was tried and broke two
       things at once: the tier chips carry dark text and stopped being
       readable, and the band swatches are a legend for colors RingMapKml
       writes into the KML, so changing one here told the client a lie about
       the other. */

    /* The ring map's quartiles, and the legend that explains them. These four
       have to stay in step with RingMapKml — #40 would move both into
       configuration together. */
    --band-1: #2e7d4f;
    --band-2: #e3a008;
    --band-3: #c0392b;
    --band-4: #9aa0a6;

    /* County tier chips on a study. Pale on purpose: they are a background
       with the tier's numeral in ink on top. */
    --tier-1: #8ef0a8;
    --tier-2: #f5e27a;
    --tier-3: #f6a09a;
    --tier-4: #d7d9dc;

    /* Six regions on the market map. Categorical and muted — the map is for
       finding a market, not for reading a value off, so no one of these should
       pull the eye. */
    --region-1: #8fb6dd;
    --region-2: #94cfa5;
    --region-3: #e6c07b;
    --region-4: #dda0a0;
    --region-5: #b3a6d8;
    --region-6: #8fcdc9;
}

* { box-sizing: border-box; }

/* A column, so the footer sits at the bottom of a short page instead of
   partway up it. The sprite is the only other child and is out of flow. */
body {
    margin: 0;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    background: var(--bg);
    color: var(--ink);
    font: 15px/1.5 -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
}

/* Tabular figures keep digits in vertical alignment down a column. */
.numeric, .grid td, .grid th.numeric { font-variant-numeric: tabular-nums; }

a { color: var(--accent); }

.app-bar {
    display: flex;
    align-items: baseline;
    gap: .75rem;
    padding: .7rem 1.25rem;
    background: var(--app-bar);
    color: var(--app-bar-ink);
}

/* The wordmark, standing in for a logo. Three weights of one string: the name
   in full strength, the half that is the idea in the accent, and the domain
   dropped back because it is an address rather than part of the name. */
.app-bar .brand {
    color: var(--app-bar-ink);
    font-weight: 600;
    letter-spacing: -.01em;
    text-decoration: none;
}

.app-bar .brand .mark { color: var(--a-300); }
.app-bar .brand .tld { font-weight: 400; opacity: .55; }

.app-bar .grow { flex: 1 1 auto; }

.app-nav { display: flex; gap: .25rem; margin-left: 1rem; }

.app-nav a {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    color: var(--app-bar-ink-soft);
    text-decoration: none;
    font-size: .875rem;
    padding: .2rem .6rem;
    border-radius: 4px;
}

.app-nav a:hover { background: rgb(255 255 255 / .1); color: var(--app-bar-ink); }

main { padding: 1.25rem; flex: 1 0 auto; }

/* Quiet. Nobody comes to a screen for its footer, and this one holds three
   things that are each wanted about once a month. */
.site-footer {
    display: flex;
    align-items: baseline;
    gap: var(--s-4);
    padding: var(--s-3) 1.25rem;
    border-top: 1px solid var(--line);
    color: var(--muted);
    font-size: var(--t-sm);
}

.site-footer nav { display: flex; gap: var(--s-3); }
.site-footer a { color: var(--muted); text-decoration: none; }
.site-footer a:hover { color: var(--accent); text-decoration: underline; }
.site-footer .env { margin-left: auto; color: var(--faint); font-variant-numeric: tabular-nums; }

h1 { font-size: 1.4rem; margin: 0 0 .25rem; }

.muted { color: var(--muted); font-size: .875rem; }
.footnote { margin-top: .75rem; }

.crumbs { font-size: .85rem; margin-bottom: .75rem; }

.chip {
    background: var(--accent-soft);
    color: var(--accent);
    border-radius: 3px;
    padding: 0 .35rem;
    font-size: .78rem;
}

.empty {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 1.5rem;
}

code {
    background: var(--rank-bg);
    border: 1px solid var(--line);
    border-radius: 3px;
    padding: .05rem .3rem;
    font-size: .85em;
}

/* --- Report header -------------------------------------------------------- */

.report-head {
    display: flex;
    flex-wrap: wrap;
    gap: 1rem;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: .9rem;
}

.report-head p, .report-head .report-meta { margin: 0; }

/* A div rather than a p, because a <details> inside a <p> closes it: the
   parser treats details as one of the elements that ends an open paragraph. */
.report-head .report-meta { font-size: .9rem; }

.views {
    display: inline-flex;
    margin: 0 0 .6rem;
    border: 1px solid var(--line-strong);
    border-radius: 5px;
    overflow: hidden;
}

.views a {
    padding: .35rem .9rem;
    font-size: .875rem;
    text-decoration: none;
    background: var(--surface);
}

.views a + a { border-left: 1px solid var(--line-strong); }
.views a.active { background: var(--accent); color: var(--surface); }

/* --- Diagnostics ---------------------------------------------------------- */

.diagnostics { list-style: none; margin: 0 0 1rem; padding: 0; }

.diagnostics li {
    border-left: 3px solid var(--warn-line);
    background: var(--warn-bg);
    padding: .5rem .75rem;
    margin-bottom: .35rem;
    font-size: .875rem;
}

.diagnostics li.error { border-left-color: var(--error-line); background: var(--error-bg); }
.diagnostics strong { font-family: ui-monospace, Consolas, monospace; margin-right: .4rem; }

/* --- Report list ---------------------------------------------------------- */

.list {
    border-collapse: collapse;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    width: 100%;
    max-width: 60rem;
}

.list th, .list td { padding: .55rem .8rem; text-align: left; border-bottom: 1px solid var(--line); }
.list thead th { background: var(--rank-bg); font-size: .8rem; text-transform: uppercase; letter-spacing: .03em; }
.list tbody tr:last-child td { border-bottom: 0; }
.list .numeric { text-align: right; }

/* --- The grid ------------------------------------------------------------- */

/*
 * The table is the only thing that scrolls horizontally; the page itself never
 * does. Capped by viewport height so the sticky header has something to stick
 * against.
 */
.grid-scroll {
    overflow: auto;
    max-height: calc(100vh - 13rem);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: 6px;
}

.grid { border-collapse: separate; border-spacing: 0; font-size: .85rem; white-space: nowrap; }

.grid th, .grid td {
    padding: .35rem .6rem;
    border-bottom: 1px solid var(--line);
    border-right: 1px solid var(--line);
    text-align: left;
}

.grid thead th {
    position: sticky;
    top: 0;
    z-index: 2;
    background: var(--rank-bg);
    border-bottom: 2px solid var(--line-strong);
    font-weight: 600;
    font-size: .78rem;
    vertical-align: bottom;
    /* Long headers would otherwise set the column width for every row below. */
    white-space: normal;
    max-width: 9rem;
}

.grid .numeric { text-align: right; }

/* Ranks are the model's output rather than its input, so they read as a
   different kind of number from the values beside them. */
.grid .rank { background: var(--rank-bg); color: var(--accent); font-weight: 600; }

/* The site name stays visible while the other 86 columns scroll past it. */
.grid .sticky-col {
    position: sticky;
    left: 0;
    z-index: 1;
    background: var(--surface);
    border-right: 2px solid var(--line-strong);
    font-weight: 600;
    text-align: left;
}

.grid thead th:first-child { left: 0; z-index: 3; background: var(--rank-bg); }

.grid tbody tr:hover td,
.grid tbody tr:hover th { background: var(--accent-soft); }

/* --- Model editor --------------------------------------------------------
 *
 * The one stateful screen (D10). Dense on purpose: a real model runs to eighty
 * variables and fifteen rankings, so the whole of a tab should be scannable
 * without hunting.
 */

.editor-head {
    display: flex;
    gap: 1rem;
    align-items: flex-start;
    justify-content: space-between;
    margin-bottom: .75rem;
}

.model-name {
    font-size: 1.3rem;
    font-weight: 600;
    border: 1px solid transparent;
    border-radius: 4px;
    padding: .2rem .4rem;
    width: 22rem;
    max-width: 100%;
    background: transparent;
}

.model-desc {
    display: block;
    margin-top: .2rem;
    width: 30rem;
    max-width: 100%;
    border: 1px solid transparent;
    border-radius: 4px;
    padding: .2rem .4rem;
    color: var(--muted);
    background: transparent;
}

.model-name:hover, .model-desc:hover { border-color: var(--line); }
.model-name:focus, .model-desc:focus { border-color: var(--accent); background: var(--surface); outline: none; }

.editor-actions { display: flex; align-items: center; gap: .75rem; }
.saved { color: var(--muted); font-size: .85rem; }

button {
    font: inherit;
    font-size: .875rem;
    padding: .35rem .8rem;
    border: 1px solid var(--line-strong);
    border-radius: 5px;
    background: var(--surface);
    cursor: pointer;
}

button:hover:not(:disabled) { background: var(--accent-soft); }
button:disabled { opacity: .45; cursor: default; }

button.primary { background: var(--accent); border-color: var(--accent); color: var(--surface); }

/* Must restate the background: the generic button:hover rule above is more
   specific than button.primary, so without this a hovered primary button gets
   the pale hover background while keeping white text, and its label vanishes. */
button.primary:hover:not(:disabled) { background: var(--accent-hover); }

button.icon {
    padding: .1rem .4rem;
    border-color: transparent;
    background: transparent;
    color: var(--muted);
}

button.icon:hover:not(:disabled) { background: var(--accent-soft); color: var(--accent); }
button.icon.danger:hover:not(:disabled) { background: var(--error-bg); color: var(--error-line); }

button.link {
    border: 0;
    background: none;
    color: var(--accent);
    padding: .2rem .3rem;
}


/* --- Form controls ------------------------------------------------------

   There were none. Buttons were styled and nothing else was, so every text
   box and dropdown in the application was whatever the browser drew: a select
   with no padding at all, 19px tall, sitting in a row beside a 34px button.
   No `align-items` value fixes that, which is why the rows looked wrong
   however they were aligned.

   One family: the same font, the same border, the same radius, the same
   focus ring, and enough padding that a control is the same height as the
   button next to it.

   Written as an exclusion rather than a list of types, because `type` is
   optional in HTML and fifteen inputs in these views leave it off — a text
   box with no `type` is a text box, and `input[type="text"]` does not reach
   it. Listing the types instead of excluding them left the study's Name field
   unstyled beside a styled one. */
input:not([type="checkbox"], [type="radio"], [type="file"],
           [type="hidden"], [type="submit"], [type="button"]),
select,
textarea {
    font: inherit;
    font-size: var(--t-md);
    line-height: 1.4;
    color: var(--ink);
    background-color: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    padding: .35rem .5rem;
    box-sizing: border-box;
}

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

input:hover:not(:disabled),
select:hover:not(:disabled),
textarea:hover:not(:disabled) { border-color: var(--line-strong); }

/* One focus ring for everything, including buttons. The browser's own differs
   per control and per platform, which is half of why a plain form looks
   unconsidered. */
input:focus-visible,
select:focus-visible,
textarea:focus-visible,
button:focus-visible,
a.button:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: 1px;
    border-color: var(--accent);
}

input:disabled,
select:disabled,
textarea:disabled {
    background-color: var(--surface-sunken);
    color: var(--muted);
    cursor: default;
}

/* The native dropdown arrow is the least designed thing on the screen and
   differs on every platform. Replaced with Lucide's chevron, the same one the
   rest of the application uses — as a background image, because a background
   cannot take currentColor, which is the one place in the stylesheet a color
   is written rather than named. */
select {
    appearance: none;
    padding-right: 1.6rem;
    background-image: var(--chevron);
    background-repeat: no-repeat;
    background-position: right .4rem center;
    background-size: 14px;
}

select:disabled { opacity: .7; }

/* Its own line, and its own size. A textarea left inline puts whatever
   follows it — a Save button, usually — beside its bottom right corner. */
textarea {
    display: block;
    width: 100%;
    max-width: 34rem;
    resize: vertical;
}

/* The ticks, drawn rather than left to the platform. accent-color recolors
   the native control but keeps its size, its corner radius and its focus
   behavior, all of which differ between Windows, macOS and Linux — beside
   controls that now agree on all three, the odd one out is the checkbox. */
input[type="checkbox"],
input[type="radio"] {
    appearance: none;
    width: 1rem;
    height: 1rem;
    margin: 0;
    border: 1px solid var(--rule);
    background-color: var(--surface);
    cursor: pointer;
    flex: none;
}

input[type="checkbox"] { border-radius: var(--radius-sm); }
input[type="radio"] { border-radius: 50%; }

input[type="checkbox"]:hover:not(:disabled),
input[type="radio"]:hover:not(:disabled) { border-color: var(--line-strong); }

input[type="checkbox"]:checked,
input[type="radio"]:checked { background-color: var(--accent); border-color: var(--accent); }

/* Lucide's check, the same one the sprite carries. White, because a
   background image cannot take currentColor. */
input[type="checkbox"]:checked {
    background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='none' stroke='%23ffffff' stroke-width='3' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M20 6 9 17l-5-5'/%3E%3C/svg%3E");
    background-repeat: no-repeat;
    background-position: center;
    background-size: .75rem;
}

input[type="radio"]:checked {
    box-shadow: inset 0 0 0 3px var(--surface);
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled { opacity: .5; cursor: default; }

/* A file input is two controls in one: the browser's button and the name of
   the chosen file. Only the button can be reached, so it is made to match the
   real ones. */
input[type="file"] { font-size: var(--t-sm); color: var(--muted); }

input[type="file"]::file-selector-button {
    font: inherit;
    font-size: var(--t-sm);
    margin-right: .6rem;
    padding: .3rem .7rem;
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    background: var(--surface);
    color: var(--ink);
    cursor: pointer;
}

input[type="file"]::file-selector-button:hover { background: var(--accent-soft); }

.tabs { display: flex; gap: .25rem; border-bottom: 2px solid var(--line-strong); margin-bottom: 1rem; }

.tabs button {
    border: 0;
    border-bottom: 2px solid transparent;
    border-radius: 0;
    margin-bottom: -2px;
    background: none;
    padding: .5rem .9rem;
}

.tabs button.active { border-bottom-color: var(--accent); color: var(--accent); font-weight: 600; }
.tabs .count { color: var(--muted); font-size: .8rem; margin-left: .25rem; }

/* A model is a pipeline: site data feeds rankings, rankings feed scoring,
   scoring feeds what the report prints. The arrows say so. */
.tabs.flow { align-items: center; }
.tabs.flow .flow-arrow { color: var(--muted); padding: 0 .1rem; user-select: none; }

/* The client/admin switch inside the columns stage reuses the report's view
   toggle, but as buttons rather than links. */
.views a { cursor: pointer; }
.views a .count { color: inherit; opacity: .7; font-size: .8rem; margin-left: .25rem; }

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

.cell {
    font: inherit;
    font-size: .875rem;
    padding: .25rem .45rem;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    background: var(--surface);
}

.cell.wide { min-width: 18rem; }
.cell.num { width: 6rem; text-align: right; font-variant-numeric: tabular-nums; }
.cell:focus { outline: 2px solid var(--accent-soft); border-color: var(--accent); }

.grid-edit {
    border-collapse: collapse;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    width: 100%;
}

.grid-edit th, .grid-edit td { padding: .3rem .55rem; text-align: left; border-bottom: 1px solid var(--line); }
.grid-edit thead th { background: var(--rank-bg); font-size: .78rem; text-transform: uppercase; letter-spacing: .03em; }
.grid-edit tbody tr:last-child td { border-bottom: 0; }
.grid-edit .actions { text-align: right; white-space: nowrap; width: 1%; }
.grid-edit .numeric { text-align: right; }
.grid-edit .src { font-family: ui-monospace, Consolas, monospace; font-size: .8rem; }

.ranking, .category {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: .7rem .85rem;
    margin-bottom: .6rem;
}

.ranking-head, .category-head { display: flex; align-items: center; gap: .5rem; margin-bottom: .5rem; }
.ranking-head label { font-size: .8rem; color: var(--muted); }
.spacer { flex: 1; }

/* The Excel sort dialog, in its own words. */
.sort-level { display: flex; align-items: center; gap: .5rem; padding: .2rem 0 .2rem 1rem; }
.sort-level .lead { width: 4.5rem; font-size: .85rem; color: var(--muted); text-align: right; }

.sort-foot { display: flex; align-items: center; gap: .75rem; padding-left: 1rem; margin-top: .3rem; font-size: .85rem; }

.weight { font-size: .8rem; color: var(--muted); display: flex; align-items: center; gap: .3rem; }
.bad { color: var(--error-line); font-weight: 600; }

/* The counterpart to .bad. Both exist because "Deleted South Central, 38
   markets and 566 counties" and "That region is not there" should not look
   the same. */
.good { color: var(--ok-line); font-weight: 600; }

.check { display: inline-flex; align-items: center; gap: .35rem; font-size: .85rem; }
.check.row { display: flex; padding: .15rem .3rem; border-radius: 4px; }
.check.row:hover { background: var(--accent-soft); }

.field { display: block; font-size: .85rem; color: var(--muted); margin-bottom: .6rem; }
.field .cell { display: block; margin-top: .2rem; }

/* --- Modals --- */

.modal-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(23, 24, 28, .45);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 2rem;
    z-index: 10;
}

.modal {
    background: var(--bg);
    border-radius: 8px;
    width: min(64rem, 100%);
    max-height: 100%;
    display: flex;
    flex-direction: column;
    padding: 1rem 1.15rem;
    box-shadow: 0 10px 40px rgba(0, 0, 0, .3);
}

.modal.narrow { width: min(30rem, 100%); }
.modal header { display: flex; align-items: center; justify-content: space-between; }
.modal header h2 { font-size: 1.05rem; margin: 0 0 .5rem; }
.modal footer { display: flex; align-items: center; justify-content: space-between; gap: .75rem; padding-top: .75rem; }

.picker-filters { display: flex; gap: .5rem; align-items: center; margin-bottom: .5rem; flex-wrap: wrap; }
.picker-results { overflow: auto; flex: 1; min-height: 8rem; border-radius: 6px; }

/* The composite list is a single column of names, so a horizontal scrollbar is
   only ever noise. */
.modal.narrow .picker-results { overflow-x: hidden; }

/* --- Preview -------------------------------------------------------------
 *
 * Ranks banded into thirds. The same three colors will carry the ring map,
 * so the legend is learned here before it has to be read there.
 */

.band {
    display: inline-block;
    width: .6rem;
    height: .6rem;
    border-radius: 2px;
    margin-right: .4rem;
    vertical-align: baseline;
}

/* v1's ring tiers, so a color means the same here as on the map. Gray is the
   weakest quarter, not missing data. */
.band.green { background: var(--band-1); }
.band.yellow { background: var(--band-2); }
.band.red { background: var(--band-3); }
.band.gray { background: var(--band-4); }
.band.none { background: var(--line); }

.legend { display: flex; align-items: center; gap: .3rem; margin: 0 0 .6rem; }
.legend .band { margin-left: .5rem; }
.legend .band:first-child { margin-left: 0; }

.up { color: var(--ok-line); }
.down { color: var(--error-line); }

/* A preview describes the model it was scored against, so once the model moves
   on it is history until re-scored. */
.stale { opacity: .5; }

.baseline { margin: 0 0 .5rem; max-width: 54rem; }

/* --- Sign in --- */

.sign-in {
    max-width: 22rem;
    margin: 3rem auto;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
    padding: 1.5rem;
}

.sign-in h1 { margin: 0 0 1rem; font-size: 1.25rem; }
.sign-in .field { margin-bottom: .8rem; }

.sign-in input[type="email"],
.sign-in input[type="text"],
.sign-in input[type="password"] {
    width: 100%;
    padding: .45rem .5rem;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    font: inherit;
}

.sign-in .editor-actions { margin-top: 1.2rem; }
.sign-in button { width: 100%; padding: .5rem; }

.sign-out { display: flex; align-items: center; margin: 0; }

/* The organization a TAA administrator is working in. Sits between the build
   badge and the name, and is absent for everyone else. */

.org-switch { display: flex; align-items: center; margin: 0 .8rem 0 0; }

.org-switch select {
    background-color: var(--n-800);
    background-image: var(--chevron-light);
    color: var(--line);
    border-color: var(--ink-soft);
    font-size: .85rem;
    padding: .2rem 1.4rem .2rem .4rem;
}

/* --- Choosing a model for a report, and copying one --- */

.apply-model {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin: 0;
    font-size: .85rem;
    color: var(--muted);
}

.apply-model select { max-width: 16rem; }

.row-actions form { margin: 0; }
.row-actions button { font-size: .8rem; padding: .25rem .6rem; }

/* Report downloads: ring map, and the two Excel formats beside it. */
/* Not a flex container. This is a <td>, and `display: flex` takes a cell out
   of the table's own layout — so its height stopped matching the row and its
   bottom border sat above the ones either side of it. */
.downloads { white-space: nowrap; }
.downloads a { font-size: .85rem; }
.downloads a + a { margin-left: .75rem; }

/* --- Administering organizations and the people in them --- */

/* Rows for something switched off: still readable, visibly not in play. */
.list tr.inactive td { opacity: .55; }
.list tr.inactive td .muted { opacity: 1; }

/* A one-field form on its own line, above the table it adds to. */
.add-organization {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: 1rem 0;
}

.add-organization input { flex: 0 1 28rem; }

/* Editing in place inside a table cell: the row is the form. */
.list .rename,
.list .set-role,
.list .set-password {
    display: flex;
    align-items: center;
    gap: .4rem;
    margin: 0;
}

.list .rename input { flex: 1 1 16rem; }
.list .set-password input { flex: 1 1 10rem; }

/* The cell holds a form and sometimes a note under it. */
.list .rename + .muted,
.list td > .muted { display: block; font-size: .8rem; margin-top: .2rem; }

.add-account {
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: .9rem 1rem;
    margin: 1rem 0 1.5rem;
}

.add-account h2 { margin: 0 0 .7rem; font-size: 1rem; }

.add-account .fields {
    display: flex;
    flex-wrap: wrap;
    gap: .8rem;
    margin-bottom: .8rem;
}

.add-account .fields > div { display: flex; flex-direction: column; gap: .2rem; }
.add-account label { font-size: .8rem; color: var(--muted); }

/* The name in the header is a link to your own password, and should not look
   like one until you go looking for it. */

/* --- The settings menu in the header --- */

.app-bar .menu { position: relative; }

.app-bar .menu > summary {
    list-style: none;
    cursor: pointer;
    display: flex;
    align-items: center;
    padding: .25rem .35rem;
    border-radius: 3px;
    color: var(--app-bar-ink-soft);
}

.app-bar .menu > summary::-webkit-details-marker { display: none; }
.app-bar .menu > summary:hover,
.app-bar .menu[open] > summary { color: var(--app-bar-ink); background: var(--app-bar-raised); }

.app-bar .menu .menu-items {
    position: absolute;
    top: calc(100% + .35rem);
    left: 0;
    z-index: 20;
    min-width: 12rem;
    padding: .3rem;
    background: var(--n-800);
    border: 1px solid var(--ink-soft);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgb(0 0 0 / .35);
    display: flex;
    flex-direction: column;
}

.app-bar .menu .menu-items a {
    padding: .4rem .6rem;
    border-radius: 3px;
    color: var(--line);
    text-decoration: none;
    white-space: nowrap;
}

.app-bar .menu .menu-items a:hover { background: var(--ink-soft); }

/* Your own account, kept apart from the things you administer. */
.app-bar .menu .menu-items a.separated {
    margin-top: .3rem;
    padding-top: .5rem;
    border-top: 1px solid var(--ink-soft);
}

/* --- The administration hub --- */

.admin-areas {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr));
    gap: 1rem;
    margin-top: 1.2rem;
}

.admin-areas .area {
    display: block;
    padding: 1rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    text-decoration: none;
    color: inherit;
}

.admin-areas a.area:hover { border-color: var(--faint); background: var(--surface-sunken); }
.admin-areas .area h2 { margin: 0 0 .4rem; font-size: 1rem; }
.admin-areas .area p { margin: 0 0 .5rem; font-size: .88rem; }

/* Not a link, and it should not invite a click. */
.admin-areas .area.unbuilt { border-style: dashed; opacity: .75; }
.admin-areas .area.unbuilt .waiting {
    margin: 0;
    font-size: .82rem;
    color: var(--muted);
}

/* --- Header: name, role, gear, sign out --- */

/* The organization for anyone with no switcher to read it from. */
.app-bar .org-name { color: var(--app-bar-ink-soft); font-size: .85rem; }

.app-bar .who { color: var(--app-bar-ink-soft); font-size: .85rem; }
.app-bar .who .role { opacity: .8; }

/* The gear sits between the name and Sign out, so the two controls that end
   a session or change one are next to each other. */
.app-bar .menu { margin-left: .1rem; }
.app-bar .menu .menu-items { left: auto; right: 0; }

.sign-out { margin-left: .2rem; }

/* --- Re-scoring a report, behind a pencil in its subtitle --- */

.report-head .change-model { display: inline-block; position: relative; vertical-align: middle; }

.report-head .change-model > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    padding: .1rem .2rem;
    border-radius: 3px;
    color: var(--muted);
}

.report-head .change-model > summary::-webkit-details-marker { display: none; }
.report-head .change-model > summary:hover,
.report-head .change-model[open] > summary { color: var(--ink); background: var(--line); }

/* Light, unlike the header menu: this one opens over the page, not the bar. */
.report-head .change-model .menu-items {
    position: absolute;
    z-index: 20;
    margin-top: .3rem;
    padding: .7rem;
    min-width: 22rem;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgb(0 0 0 / .18);
}

.report-head .change-model .apply-model { margin: 0; }

.report-head .change-model .warning {
    margin: .6rem 0 0;
    font-size: .8rem;
    color: var(--muted);
}

/* Disabled models are hidden by default; this is how you ask for them. */
.show-disabled { margin: .6rem 0 0; font-size: .85rem; }

/* --- Adding a model, and assigning it to an organization --- */

.add-model {
    border: 1px solid var(--rule);
    border-radius: 4px;
    padding: .9rem 1rem;
    margin: 1rem 0 .4rem;
}

.add-model .fields { display: flex; flex-wrap: wrap; gap: .8rem; margin-bottom: .8rem; }
.add-model .fields > div { display: flex; flex-direction: column; gap: .2rem; }
.add-model label { font-size: .8rem; color: var(--muted); }
.add-model input { min-width: 18rem; }

/* Yes/No rather than a checkbox: it posts, so it should look like a button
   that did something, and read correctly when the row is disabled. */
.list .set-grant { margin: 0; }

.list .set-grant button {
    min-width: 3.4rem;
    color: var(--muted);
}

.list .set-grant button.granted {
    color: var(--ok-line);
    border-color: var(--ok-line);
    font-weight: 600;
}

/* A refused action, still visible. Disabled says "not now"; absent says
   "not a thing", and retiring is very much a thing. */
.list .row-actions button[disabled] {
    opacity: .45;
    cursor: not-allowed;
}

/* --- Geography ------------------------------------------------------------
   A plain hierarchy editor: three tables, one per level, each drilled into
   from the one above. Editing is in place because the judgement being made is
   about what is on screen. */

.icon-defs { position: absolute; width: 0; height: 0; overflow: hidden; }

/* Icons sit on the text baseline by default, which drops them below the line
   beside a label. Nudged up rather than switched to flex: these appear inside
   buttons, table cells and sentences, and a display change in one of those
   places would move the text around it. */
.icon { vertical-align: -.15em; flex: none; }

/* An icon in a circle, at the head of a screen or a dialog. Says what kind of
   thing you are looking at, where the words say which one. */
.icon-badge {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: var(--badge-size);
    height: var(--badge-size);
    border-radius: 50%;
    background: var(--badge-bg);
    color: var(--badge-ink);
    flex: none;
}

.icon-badge .icon { vertical-align: 0; }

/* A step you have finished with. The card itself folds, down to its own
   heading — the markets and the counties are how the reports were arrived at,
   and once they exist the page is about them. Still one click away, because
   somebody will want to add a county later.

   The whole heading is the control, so there is no separate row to aim at and
   nothing that looks clickable but is not. */
.folding > summary {
    cursor: pointer;
    display: flex;
    align-items: baseline;
    gap: var(--s-2);
    list-style: none;
    margin: -.2rem -.3rem;
    padding: .2rem .3rem;
    border-radius: var(--radius-sm);
}

.folding > summary::-webkit-details-marker { display: none; }

/* The same chevron the dropdowns use, turned to face the way it will go. */
.folding > summary::before {
    content: "";
    width: 14px;
    height: 14px;
    flex: none;
    align-self: center;
    background: var(--chevron) no-repeat center / 14px;
    transform: rotate(-90deg);
    transition: transform .12s ease;
}

.folding[open] > summary::before { transform: rotate(0deg); }

.folding > summary:hover { background: var(--surface-sunken); }
.folding > summary > h2 { margin: 0; }
.folding[open] > summary { margin-bottom: .7rem; }

/* What the heading says when the card is shut, which is most of the time. */
.aside {
    margin-left: .4rem;
    font-size: var(--t-sm);
    font-weight: 400;
    color: var(--muted);
}

/* Export, and the gear that configures what goes in it. */
.study-actions { display: flex; align-items: center; gap: .5rem; }

.button.export {
    display: inline-flex;
    align-items: center;
    gap: .45rem;
    padding: .5rem 1.1rem;
    font-size: var(--t-lg);
    font-weight: 600;
    border-radius: var(--radius);
}

/* Nothing to deliver is not an error, so it is the same shape with the life
   taken out of it rather than a missing button or a message. */
.button.export.disabled {
    opacity: .45;
    cursor: not-allowed;
    border-color: var(--rule);
    background: var(--surface);
    color: var(--muted);
}

.settings-open { padding: .45rem; }

/* What a report is still waiting on. Only the two unfinished states are
   marked: complete is where every report is meant to end up, so a chip for it
   would sit on every row of a finished study and make the two that matter
   harder to pick out, not easier.

   Semantic colors rather than the tier palette: this is progress, not a
   rank. */
.state {
    display: inline-block;
    padding: .1rem .45rem;
    border-radius: 999px;
    border: 1px solid;
    font-size: var(--t-xs);
    white-space: nowrap;
}

/* Beside a heading it is a tag on a name, so it stops inheriting the
   heading's weight and size. */
h1 > .state, h2 > .state { font-weight: 400; align-self: center; }

.state.empty { background: var(--surface-sunken); border-color: var(--line); color: var(--muted); }
.state.waiting { background: var(--warn-bg); border-color: var(--warn-line); color: var(--warn-ink); }

/* A settings dialog holds more than a name and a note. */
dialog.editor.wide { width: min(34rem, calc(100vw - 2rem)); }

/* The button that makes the reports, and the reason it is off when it is. */
.start-reports {
    display: flex;
    align-items: center;
    gap: var(--s-3);
    margin-top: var(--s-4);
}

/* Red, for the dialogs that destroy something. */
.icon-badge.danger { background: var(--error-bg); color: var(--error-line); }

/* The sites a report holds: seven columns and as many rows as were imported,
   so it takes the window. `.list` caps at 60rem, which is right for a list of
   names and leaves this one with the coordinates crammed and half the page
   empty beside them. */
.list.sites-edit { max-width: none; }

/* Under the brand list and the way to add to it, because it acts on both. */
.find-sites { margin-top: var(--s-4); }

/* Adding one brand at one end, removing them all at the other. */
.brand-controls {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--s-4);
    flex-wrap: wrap;
}

.brand-controls .inline-add { margin-top: 0; }
.brand-controls form { margin: 0; }

/* Wide enough for a progress bar and the sentence under it. */
dialog.run-progress { width: min(30rem, calc(100vw - 2rem)); }
dialog.run-progress .run-live { margin: 0; }

/* A heading carrying one. The badge is taller than the text, so the heading
   has to stop being a plain block for the two to sit on one line. */
.badged {
    display: flex;
    align-items: center;
    gap: var(--s-2);
}

.totals { margin: 0 0 1rem; }
.totals strong { font-variant-numeric: tabular-nums; }

/* Export and restore sit where the deleting happens, not on a settings page
   nobody opens until after the mistake. */
.backup {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 1rem;
    padding: .7rem .9rem;
    margin: 0 0 1.5rem;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--surface);
    font-size: .875rem;
}

.backup form { display: flex; align-items: center; gap: .5rem; margin: 0; }
.backup input[type="file"] { font-size: .8125rem; max-width: 18rem; }

/* Marks the row the level below is showing, so three stacked tables read as
   one path rather than three lists. */
.list tr.current > td { background: var(--accent-soft); }

.list th.actions, .list td.actions { width: 1%; white-space: nowrap; text-align: right; }
.list td.actions form { display: inline; margin: 0; }

/* An icon button: the affordance is the shape, so it carries no border until
   it is pointed at. */
button.icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    padding: .2rem;
    border: 1px solid transparent;
    border-radius: 3px;
    background: none;
    color: var(--muted);
    cursor: pointer;
    line-height: 0;
}

button.icon:hover { color: var(--ink); border-color: var(--line-strong); background: var(--bg); }
button.icon:focus-visible { outline: 2px solid var(--accent); outline-offset: 1px; }
button.icon.danger:hover { color: var(--error-line); border-color: var(--error-line); }

/* Rename in place. The collapsed class is added by script, so without it the
   form is simply visible and the page still edits. */
.editable { display: inline-flex; align-items: center; gap: .4rem; }
.editable .rename { display: flex; align-items: center; gap: .4rem; margin: 0; }
.editable .rename input { flex: 1 1 14rem; min-width: 0; }

/* Collapsed shows the value and the pencil; open shows the form. Stated as
   "everything that is not the form" so it works whatever the value is. */
.editable.collapsed > form { display: none; }
.editable:not(.collapsed) > *:not(form) { display: none; }

/* Except the logo, which stays on screen while its file picker is open. A
   rename field contains the name it replaces; a file input shows nothing at
   all, so hiding the current pin would mean choosing a replacement for
   something you can no longer see. */
.list td.logo .editable:not(.collapsed) > img,
.list td.logo .editable:not(.collapsed) > .no-logo { display: block; }

/* Moving a market between regions, or a county between markets. The county
   control is a type-ahead over a datalist shared by the whole table: 205
   options per row rendered a 2.26MB page. */
.list .move { display: flex; align-items: center; gap: .4rem; margin: 0; }
.list .move select,
.list .move input { flex: 1 1 10rem; min-width: 0; max-width: 15rem; }

/* One field and a button, under the table it adds to. */
.add {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin: .75rem 0 2rem;
}

.add input { flex: 0 1 22rem; }

/* --- Retailers ------------------------------------------------------------
   A brand's logo is its map pin, so the list shows it: a row with nothing in
   that column is a brand that draws nothing. */

.list th.logo, .list td.logo { width: 1%; padding-right: .4rem; }
.list td.logo .no-logo { color: var(--line-strong); }

/* Fixed height, free width. Most of these pins are wordmarks rather than
   squares — Barnes & Noble is six times as wide as it is tall — and forcing
   them into a 24px box renders them as a sliver of nothing. */
.list td.logo img {
    display: block;
    height: 24px;
    width: auto;
    max-width: 5rem;
    object-fit: contain;
}

/* Changing a logo happens in the logo cell, behind the same pencil as a
   rename — an image is a value like any other. */
.list .upload { display: flex; align-items: center; gap: .35rem; margin: 0; }
.list .upload input[type="file"] { font-size: .75rem; width: 11rem; }

/* Categories as tabs, each carrying how many brands this market holds for it.
   A zero is worth seeing rather than hiding: a model asking for that kind of
   list here would resolve to nothing. */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: .35rem;
    margin: 0 0 1.25rem;
    border-bottom: 1px solid var(--line);
    padding-bottom: .5rem;
}

.tabs a {
    display: inline-flex;
    align-items: center;
    gap: .4rem;
    padding: .3rem .7rem;
    border: 1px solid var(--line);
    border-radius: 3px;
    background: var(--surface);
    color: var(--ink);
    font-size: .875rem;
    text-decoration: none;
}

.tabs a:hover { border-color: var(--line-strong); }

.tabs a.current {
    border-color: var(--accent);
    background: var(--accent-soft);
    font-weight: 600;
}

.tabs .count {
    min-width: 1.4rem;
    padding: 0 .3rem;
    border-radius: 8px;
    background: var(--rank-bg);
    font-size: .75rem;
    font-variant-numeric: tabular-nums;
    text-align: center;
}

.tabs .count.none { color: var(--muted); }

/* A row that opens an editor. The whole row is the affordance, so it says so
   on hover and when focused — a double-click with nothing to suggest it is a
   feature nobody finds. */
.list.rows-open tbody tr { cursor: pointer; }
.list.rows-open tbody tr:hover > td { background: var(--accent-soft); }
.list.rows-open tbody tr:focus-visible {
    outline: 2px solid var(--accent);
    outline-offset: -2px;
}

/* The editor itself. <dialog> brings the focus trap, Escape and the backdrop;
   this is only the box. */
dialog.editor {
    width: min(26rem, calc(100vw - 2rem));
    padding: 1.25rem 1.4rem;
    border: 1px solid var(--line-strong);
    border-radius: 6px;
    background: var(--surface);
    color: var(--ink);
}

dialog.editor::backdrop { background: rgb(0 0 0 / .35); }
dialog.editor h2 { margin: 0 0 1rem; font-size: 1.05rem; }
dialog.editor label { display: block; margin: .8rem 0 .25rem; font-size: .8125rem; font-weight: 600; }
dialog.editor input[type="text"], dialog.editor input:not([type]) { width: 100%; }

/* A select sizes itself to its longest option, so "Douglas County, NE — Omaha,
   NE" pushed the control past the dialog and gave it a horizontal scrollbar.
   border-box because the padding has to come out of the width, not be added
   to it. */
dialog.editor select { width: 100%; max-width: 100%; box-sizing: border-box; }
dialog.editor input[type="file"] { width: 100%; font-size: .8125rem; }

/* The pin as it stands, because a file input says nothing about what is
   already there. */
dialog.editor .current-logo {
    display: flex;
    align-items: center;
    min-height: 2.5rem;
    padding: .4rem .6rem;
    margin-bottom: .5rem;
    border: 1px solid var(--line);
    border-radius: 4px;
    background: var(--bg);
}

dialog.editor .current-logo img { max-width: 100%; object-fit: contain; }
dialog.editor .current-logo .no-logo { color: var(--muted); font-size: .8125rem; }
dialog.editor .muted { margin: .75rem 0 0; }

.dialog-actions {
    display: flex;
    justify-content: flex-end;
    gap: .5rem;
    margin-top: 1.25rem;
}

/* A description is prose in a table of labels, so it is given room and allowed
   to wrap where every other cell is a single line. */
.list td.description {
    max-width: 28rem;
    white-space: normal;
    font-size: .875rem;
    color: var(--muted);
}

dialog.editor textarea {
    width: 100%;
    font: inherit;
    font-size: .875rem;
    padding: .35rem .5rem;
    border: 1px solid var(--line-strong);
    border-radius: 4px;
    resize: vertical;
}

/* The add row carries two fields here; let the name stay short and the
   description take what is left. */
.add input[name="description"] { flex: 1 1 20rem; }

/* Which comparator set a model wants, beside the name and description it
   belongs with. Not a stage: it says what the analysis is measured against
   rather than how. */
.model-comparators {
    display: flex;
    align-items: center;
    gap: .5rem;
    margin-top: .4rem;
    padding-left: .4rem;
    font-size: .8125rem;
    color: var(--muted);
}

.model-comparators select { font: inherit; max-width: 20rem; }

/* --- Configuration --------------------------------------------------------
   One card per setting, because each needs its explanation beside it: a
   settings screen with nothing but labels is one nobody dares use. */

.settings { display: flex; flex-direction: column; gap: .75rem; max-width: 46rem; }

.setting {
    padding: .9rem 1rem;
    border: 1px solid var(--line);
    border-radius: 5px;
    background: var(--surface);
}

.setting-head { display: flex; align-items: baseline; gap: .5rem; }
.setting-head label { font-weight: 600; }
.setting .muted { margin: .35rem 0 .7rem; }

.setting form { display: inline-flex; align-items: center; gap: .4rem; margin: 0; }
.setting form.reset { margin-left: .5rem; }
.setting input, .setting select { font: inherit; font-size: .9rem; width: 7rem; }
.setting .unit { color: var(--muted); font-size: .875rem; margin-right: .3rem; }

.setting .provenance { margin: .6rem 0 0; font-size: .8125rem; }

/* --- Collecting a report's site data ---
   Its own band above the table rather than a link beside the downloads: those
   re-read what is already stored, this one reaches a service that may charge
   for it, and the two should not look like the same kind of button. */

.run {
    margin: 0 0 1.2rem;
    padding: .7rem .9rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--surface-sunken);
}

.run > p { margin: .4rem 0 0; font-size: .85rem; }
.run .run-last { margin-top: .3rem; }

.run-menu, .drop-menu { display: inline-block; position: relative; }

/* A menu of links on a light background: the same button as the run menu,
   with a panel sized for a list rather than for paragraphs. */
.drop-menu > summary {
    list-style: none;
    cursor: pointer;
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    padding: .3rem .7rem;
    border: 1px solid var(--rule);
    border-radius: var(--radius-sm);
    background: var(--surface);
    font-size: var(--t-md);
}

.drop-menu > summary::-webkit-details-marker { display: none; }

/* The chevron the dropdowns use, so a menu button and a select look like the
   same kind of thing. */
.drop-menu > summary::after {
    content: "";
    width: 14px;
    height: 14px;
    background: var(--chevron) no-repeat center / 14px;
    transform: rotate(0);
    transition: transform .12s ease;
}

.drop-menu[open] > summary::after { transform: rotate(180deg); }
.drop-menu > summary:hover, .drop-menu[open] > summary { background: var(--surface-sunken); }

.drop-menu .menu-items {
    position: absolute;
    right: 0;
    z-index: 20;
    margin-top: .3rem;
    padding: .3rem;
    min-width: 17rem;
    display: flex;
    flex-direction: column;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: var(--radius);
    box-shadow: 0 6px 18px rgb(0 0 0 / .18);
}

.drop-menu .menu-items a {
    display: flex;
    flex-direction: column;
    gap: .1rem;
    padding: .4rem .55rem;
    border-radius: var(--radius-sm);
    font-size: var(--t-md);
    text-decoration: none;
    color: var(--ink);
}

.drop-menu .menu-items a:hover { background: var(--accent-soft); }
.drop-menu .menu-items a .muted { font-size: var(--t-xs); }

/* The button and the facts share a column, right-aligned, so the header is
   two things: what this report is, and what you can do with it or know about
   it. */
.head-right { display: flex; flex-direction: column; align-items: flex-end; gap: .45rem; margin-left: auto; }
.head-right .head-groups { align-items: flex-end; }

.run-menu > summary {
    list-style: none;
    cursor: pointer;
    display: inline-block;
    padding: .35rem .8rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--surface);
    font-size: .9rem;
}

.run-menu > summary::-webkit-details-marker { display: none; }
.run-menu > summary:hover, .run-menu[open] > summary { background: var(--line); }

.run-menu .menu-items {
    position: absolute;
    z-index: 20;
    margin-top: .3rem;
    padding: .8rem;
    min-width: 26rem;
    background: var(--surface);
    border: 1px solid var(--rule);
    border-radius: 4px;
    box-shadow: 0 6px 18px rgb(0 0 0 / .18);
}

/* The explanation under each button is the point of the menu, so the two
   forms are stacked and separated rather than sitting side by side. */
.run-menu .menu-items form { margin: 0 0 .9rem; }
.run-menu .menu-items form:last-of-type { margin-bottom: 0; }
.run-menu .menu-items form p { margin: .35rem 0 0; font-size: .8rem; }

.run-menu .menu-items .warning {
    margin: .8rem 0 0;
    padding-top: .6rem;
    border-top: 1px solid var(--rule);
    font-size: .8rem;
}

/* While a run is under way: label, counts, bar and a way to stop it. */
.run-live { display: flex; align-items: center; gap: .8rem; flex-wrap: wrap; }
.run-live form { margin: 0; }
.run-live progress { flex: 1 1 12rem; height: .6rem; }

/* --- A site that could not be ranked ---
   Its numbers are real but describe a ring of the wrong size, so the row is
   muted rather than colored: it should read as "set aside", not as "worst". */

tbody tr.invalid { background: var(--surface-sunken); color: var(--muted); }
tbody tr.invalid th, tbody tr.invalid td { font-style: italic; }

.not-ranked {
    margin-left: .45rem;
    padding: .05rem .35rem;
    border: 1px solid var(--error-line);
    border-radius: 3px;
    font-size: .7rem;
    font-style: normal;
    white-space: nowrap;
    color: var(--error-line);
}

/* Keeps an unranked row's name aligned with the ranked ones above, which carry
   a tier block it does not. */
tbody tr.invalid th.sticky-col { padding-left: 1.45rem; }

/* --- What kind of place a site is ---
   A label, not a color: the tier block already uses color to say where a site
   ranks, and v1's answer here was two more shades of blue behind the row. A
   proposed site ranks like any other, so this marks it rather than judging it. */

.site-category {
    margin-left: .45rem;
    padding: .05rem .35rem;
    border: 1px solid var(--rule);
    border-radius: 3px;
    font-size: .7rem;
    font-style: normal;
    font-weight: normal;
    white-space: nowrap;
    color: var(--muted);
}

/* The one you went looking for, so it is filled rather than outlined. */
.site-category.is-proposed {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--surface);
}

/* Enough to catch the eye while scrolling eighty-three rows, and no more. */
tbody tr.proposed th.sticky-col { box-shadow: inset 3px 0 0 var(--accent); }

/* --- Importing locations --- */

/* The heading, and the one button that makes a new one.

   Baseline alignment puts the button's text on the h1's baseline, which leaves
   its padding and border hanging below it — so the h1's own quarter-rem was
   swallowed and the button sat against whatever came next. The margin goes on
   the header, and the h1 gives up its own so the two do not stack. */
.page-head {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: 1rem;
    margin: 0 0 var(--s-4);
}

.page-head h1 { margin-bottom: 0; }

/* A link that acts like a button. `primary` on one of these did nothing at
   all until now: the rule was `button.primary`, an element and a class, which
   an <a class="button primary"> never matches. Three of them claimed it and
   got a plain white button — including the study's Export, the one control
   that page is built around. */
a.button.primary {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--surface);
}

a.button.primary:hover { background: var(--accent-hover); }

a.button {
    padding: .35rem .8rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--surface);
    font-size: .9rem;
    text-decoration: none;
    color: inherit;
    white-space: nowrap;
}

a.button:hover { background: var(--line); }

.import-file { display: flex; align-items: center; gap: .75rem; margin-bottom: 1.2rem; }

.import-options {
    margin: 0 0 1.2rem;
    padding: .8rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
}

.import-options legend { padding: 0 .4rem; font-weight: 600; font-size: .9rem; }
.import-options > div { margin-bottom: .9rem; }
.import-options > div:last-child { margin-bottom: 0; }
.import-options p { margin: .3rem 0 0; font-size: .8rem; max-width: 48rem; }

.import-preview { margin-top: 1.5rem; }
.import-preview h2 { margin-bottom: .6rem; font-size: 1.05rem; }
.import-preview > p { margin: .25rem 0; font-size: .85rem; }

.import-confirm { margin-top: 1rem; }
.import-confirm p { margin: .35rem 0 0; font-size: .8rem; }

/* The density bands, and the county figures behind a starting radius. */
table.bands td, table.bands th { vertical-align: middle; }
table.bands form { display: contents; }
.band-removals { display: flex; flex-wrap: wrap; gap: .4rem; margin-top: .6rem; }
.band-removals form { margin: 0; }
.county-figures { padding: .6rem .8rem; border-left: 3px solid var(--accent); background: var(--accent-soft); }
.suggested { margin-top: .35rem; font-style: italic; }
.run-menu .radius-form {
    display: flex; align-items: center; gap: .4rem;
    margin-bottom: .9rem; padding-bottom: .9rem;
    border-bottom: 1px solid var(--rule);
}
.import-apply { margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--rule); }
.import-apply p { margin: .3rem 0 0; font-size: .8rem; }

/* --- Retailers found in an imported market plan --- */

dialog.new-retailers { max-width: 34rem; width: 90vw; }
dialog.new-retailers h2 { margin: 0 0 .4rem; font-size: 1.05rem; }
dialog.new-retailers > p { margin: 0 0 .8rem; font-size: .85rem; }

/* Scrollable: a market plan can name thirty-odd, and a dialog taller than the
   window puts its buttons where nobody can reach them. */
.new-retailer-list {
    max-height: 22rem;
    overflow-y: auto;
    border: 1px solid var(--rule);
    border-radius: 4px;
}

.new-retailer {
    display: flex;
    align-items: center;
    gap: .7rem;
    padding: .4rem .6rem;
    border-bottom: 1px solid var(--rule);
    cursor: pointer;
}

.new-retailer:last-of-type { border-bottom: 0; }
.new-retailer:hover { background: var(--accent-soft); }

/* Contained rather than sized: these are logos of every shape, and a row that
   changed height per retailer would make the list impossible to scan. */
.new-retailer img { width: 2rem; height: 2rem; object-fit: contain; flex: none; }

.new-retailer-name { flex: 1; font-weight: 500; }
.new-retailer-actions { display: flex; gap: .6rem; margin-top: .9rem; }

/* The placemarks behind a grouped import note — available, not in the way. */
.note-names { margin: -.1rem 0 .5rem 1rem; font-size: .8rem; }
.note-names summary { cursor: pointer; color: var(--muted); }
.note-names p { margin: .3rem 0 0; }

/* --- The import page, laid out ---
   The two setup cards side by side: they are the header of this page, and
   stacked they pushed the thing you actually came to look at below the fold. */

/* stretch, not flex-start: the two cards hold different amounts and sat at
   different heights, which read as one of them being unfinished. */
.import-setup { display: flex; gap: 1rem; align-items: stretch; }
.import-setup > .import-options { flex: 1 1 0; margin-bottom: 1.2rem; }

@media (max-width: 60rem) { .import-setup { flex-direction: column; } }

.file-path { flex: 1 1 18rem; max-width: 26rem; background: var(--surface); }
.file-hidden { display: none; }
.import-help { margin: -.6rem 0 1.2rem; font-size: .8rem; }

.radius-line { display: flex; align-items: center; gap: .5rem; flex-wrap: wrap; }
.radius-line label { margin: 0; }

/* A dialog stretches its inputs to the full width, which is right for a name
   and wrong for a number with a unit beside it — the box filled the row and
   pushed "miles" onto the next line. */
dialog.editor .radius-line input { width: 6rem; }
.radius-working { min-height: 2.4rem; }

/* Rows with room to read, and somewhere to put the two controls. */
table.import-rows th, table.import-rows td { padding: .45rem .7rem; }
table.import-rows tbody tr:hover { background: var(--accent-soft); }
table.import-rows .row-actions { width: 4.2rem; white-space: nowrap; padding-right: .3rem; }
table.import-rows tbody tr[hidden] { display: none; }

.dialog-actions { display: flex; gap: .6rem; margin-top: 1rem; }
dialog.editor label.checkbox { display: block; margin-top: .8rem; font-weight: normal; }

/* The answer, over the figures it was read from. The paragraph is muted, so
   the headline has to take its color back. */
.radius-working .radius-headline {
    display: block;
    margin-bottom: .15rem;
    font-size: 1.05rem;
    font-weight: 600;
    color: var(--ink);
}

/* An icon button that carries a word too. */
button.icon.labelled {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    width: auto;
    padding: .25rem .5rem;
    font-size: .8125rem;
}

/* The ways of filling a report, beside the one that spends on it. */
.toolbar .button[disabled] { opacity: .5; cursor: not-allowed; }
.toolbar .button { font-size: .85rem; display: inline-flex; align-items: center; }

/* The three ways in, said once each beside the icon of the button that does
   it — so the sentence and its button are found by the same glance. */
.ways { list-style: none; margin: .6rem 0 0; padding: 0; font-size: var(--t-md); }

.ways li { display: flex; align-items: baseline; gap: .45rem; margin: .3rem 0; }
.ways .icon { color: var(--muted); }
.add-coords { display: flex; gap: .7rem; }
.add-coords > div { flex: 1; }
.add-coords input { width: 100%; box-sizing: border-box; }
button.primary { padding: .35rem .8rem; border-radius: 4px; font-size: .9rem; }
.import-read { margin-top: .9rem; padding-top: .8rem; border-top: 1px solid var(--rule); }

/* The radius sits above the button that reads it, boxed with it. */
.rerun-block {
    margin-top: .9rem; padding-top: .9rem;
    border-top: 1px solid var(--rule);
}
.rerun-block .radius-form { margin-bottom: .7rem; padding-bottom: 0; border-bottom: 0; }

/* --- The report header, in groups ---
   Downloads stacked under a label, and what the last run cost beside them. The
   facts used to sit under the buttons and push the report itself down. */

/* Each group is one line - its label, then its items - and the groups stack.
   Stacking the items instead made two tall columns out of two short rows. */
.head-groups { display: flex; flex-direction: column; gap: .2rem; }

.head-group {
    display: flex;
    align-items: baseline;
    flex-wrap: wrap;
    gap: .55rem;
    font-size: .8rem;
}

/* Fixed, so the two labels line up and their items start together. */
.head-group-label { flex: none; min-width: 5.5rem; }

/* The middot written as the character itself. Every attempt to write it as
   a CSS hex escape put a real NUL byte in this file instead, which the
   browser drew as a replacement glyph followed by "b7". */
.head-group .fact + .fact::before { content: "·"; margin-right: .3rem; }

.head-group-label {
    font-size: .7rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
}

.head-group a { white-space: nowrap; }
.head-group span { color: var(--muted); white-space: nowrap; }
.head-group .not-live { font-style: italic; }

/* The card is three buttons and nothing else. */
.run .toolbar { margin: 0; }

/* Two labelled groups, one at each end. The distance is the point: the left
   one acts on what is in the report, the right one puts things in it, and a
   single row of five buttons said they were all the same kind of thing. */
.toolbar {
    display: flex;
    flex-wrap: wrap;
    align-items: flex-end;
    justify-content: space-between;
    gap: var(--s-4);
}

.tool-group { display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; }

/* Edit sites sits opposite Run analysis. Only this one: the edit toolbar has
   no analysis half to sit opposite, so its buttons start at the left edge like
   everything else on the page. */
.tool-group.sites { margin-left: auto; }

/* The run menu is a button in the row, not a block of its own. */
.tool-group .run-menu > summary { margin: 0; }

/* An icon inside a button, sharing its color and its baseline. */
.button > .icon, button > .icon { margin-right: .35rem; }

/* Studies: a job over several reports, and the zip it delivers (D15). */
.card {
    margin: 0 0 1.2rem;
    padding: .8rem 1rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
}

.card > h2 { margin: 0 0 .7rem; font-size: 1.05rem; }
details.card > summary > h2 { font-size: 1.05rem; }
.card > p { margin: .35rem 0; font-size: .85rem; }
.card label { display: block; margin: .7rem 0 .25rem; font-size: .85rem; font-weight: 600; }
.card input[type="text"], .card input[type="number"], .card textarea { max-width: 28rem; }
.card textarea { width: 100%; max-width: 28rem; }
.card button { margin-top: .8rem; }

/* Side by side where there is room, stacked where there is not. */
.card-row { display: flex; flex-wrap: wrap; gap: 1.2rem; align-items: stretch; }
.card-row > .card { flex: 1 1 22rem; margin-bottom: 0; }

/* A select whose effect the table beside it reflects, so it saves on change
   rather than waiting for a button somebody has to find. */
.inline-select { display: inline; }
.inline-select select { font-size: .85rem; max-width: 16rem; }

.inline-add { display: flex; gap: .5rem; align-items: center; margin-top: .9rem; }
.inline-add label { margin: 0; font-size: .85rem; font-weight: 600; }
.inline-add button { margin: 0; }

.upload-form { display: flex; gap: .5rem; align-items: center; margin-top: .9rem; }
.upload-form button { margin: 0; }

/* The county cell: a button that reads as the value it holds. */
.county-pick {
    padding: .1rem .3rem;
    font-size: .85rem;
    text-align: left;
    border: 1px solid transparent;
    background: none;
}

.county-pick:hover { border-color: var(--rule); border-radius: 3px; }
#county-picker select { max-width: 100%; }
.archive-study { margin-top: 1.2rem; border-top: 1px solid var(--rule); }

/* The one study option that changes what is in the deliverable. */
.deliverable-options {
    margin: 1rem 0 0;
    padding: .6rem .8rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
}

.deliverable-options legend { padding: 0 .4rem; font-weight: 600; font-size: .85rem; }
.deliverable-options .check { display: flex; gap: .4rem; align-items: center; margin: 0; }
.deliverable-options p { margin: .4rem 0 0; font-size: .8rem; }

/* The market plan: which markets, and which of their counties are worth it. */
.markets { list-style: none; margin: 0; padding: 0; }

.markets li {
    display: flex;
    gap: .5rem;
    align-items: baseline;
    padding: .3rem 0;
    border-bottom: 1px solid var(--rule);
}

.markets li:last-child { border-bottom: none; }
.markets form { margin-left: auto; }
.markets button { margin: 0; font-size: .8rem; }

/* Enough markets to need columns; few enough to show all of a region's. */
.market-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr));
    gap: .2rem .8rem;
    margin: .6rem 0;
}

.market-grid .check {
    display: flex;
    gap: .4rem;
    align-items: baseline;
    margin: 0;
    font-weight: normal;
    font-size: .85rem;
}

/* Already covered: shown rather than hidden, so the list reads the same twice. */
.market-pick button { margin-top: .4rem; }

/* Center, not baseline. A button and a select share a text baseline and
   nothing else, so the taller one hung below the input beside it. */
.plan-form { display: flex; gap: .5rem; align-items: center; flex-wrap: wrap; }
.plan-form label { margin: 0; font-size: .85rem; font-weight: 600; }
.plan-form input[type="number"],
.plan-form input[data-grouped] { width: 8rem; }
.plan-form button { margin: 0; }

/* Two settings on one line, and without this they read as one: "people"
   finished the first and "Scored with" began immediately after it.

   `.unit + label`, not `label + .unit + label` — the input sits between the
   label and the unit, so the longer selector matched nothing at all. */
.plan-form .unit + label { margin-left: var(--s-4); }

table.counties td.tick, table.counties th.tick { width: 1.6rem; padding-right: 0; }
table.counties .row-actions { text-align: right; }

/* The same four colors the county maps and the ring maps use. */
.tier {
    display: inline-block;
    min-width: 2.1rem;
    padding: 0 .3rem;
    border-radius: 3px;
    font-size: .75rem;
    font-weight: 600;
    text-align: center;
    color: var(--ink);
}

.tier-green { background: var(--tier-1); }
.tier-yellow { background: var(--tier-2); }
.tier-red { background: var(--tier-3); }
.tier-gray { background: var(--tier-4); }

.rest { margin-top: 1rem; }
.rest > summary { cursor: pointer; font-size: .85rem; color: var(--muted, var(--muted)); }
.rest table { margin-top: .6rem; }

/* The market map: the same choice as the list beside it, drawn. */
/* Standing on its own now, not inside a card, so it carries the card's
   surface itself rather than sitting on the page background. */
/* The map and the panel beside it, in one card.

   The panel is a fixed column and the map takes what is left. It is not
   taking space from the map: the country draws 670px wide whatever the
   container, because its 1.61:1 shape hits the 26rem height cap first, and
   that left 288px blank either side. The one region this costs anything is
   the Northwest, which is wide enough to use the room — about 70px off a
   869px map, which is a better trade than a layout that moves depending on
   which region you picked. */
.map-with-panel {
    display: grid;
    grid-template-columns: 15rem minmax(0, 1fr);
    gap: var(--s-3);
    align-items: start;
    margin: 0 0 1rem;
    padding: .6rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--shadow-sm);
}

/* Below the map on a phone, where there is no blank space to use and a
   15rem column would leave the map 100px wide. */
@media (max-width: 860px) {
    .map-with-panel { grid-template-columns: 1fr; }
    .map-with-panel .map-panel { order: 2; }
}

.map-panel h2 {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-2);
    margin: 0 0 var(--s-2);
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--faint);
}

.map-panel form { margin: 0; }

/* A region, and a market under one. The same shape so moving between the two
   states does not feel like moving between two screens. */
.map-pick,
.map-markets li {
    display: block;
    width: 100%;
    text-align: left;
    padding: .35rem .5rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    background: none;
    color: var(--ink);
    font: inherit;
}

.map-markets { list-style: none; margin: 0; padding: 0; max-height: 24rem; overflow-y: auto; }

.map-pick-name { display: block; font-size: var(--t-sm); }
.map-pick-meta { display: block; font-size: .7rem; color: var(--muted); }

.map-pick { cursor: pointer; }

.map-pick:hover,
.map-pick:focus-visible,
.map-markets li.lit {
    border-color: var(--accent);
    background: var(--accent-soft);
}

/* The map sits on water.

   The country is 1.61:1 in a card that is wider, so a good half of this
   element is whatever is behind the coastline — and white read as nothing
   rather than as sea. The palest accent is already in the ramp and is what
   hover states use, so it is a blue the rest of the application has agreed to.

   It also does the dividing: the panel stays on the card's own white and the
   map has its own ground, which is the split the panel was asked for. */
.market-map {
    min-width: 0;
    padding: .4rem;
    border-radius: var(--radius);
    background: var(--accent-soft);
}

.market-map-svg {
    display: block;
    width: 100%;
    max-height: 26rem;
    height: auto;
}

.market-shape {
    stroke: var(--surface);
    stroke-width: 0.7;
    stroke-linejoin: round;
    transition: fill .08s;
}

/* Six, in the order the regions sort. Muted, because the map is for finding
   a market rather than for reading a value off. */
.market-shape.r0 { fill: var(--region-1); }
.market-shape.r1 { fill: var(--region-2); }
.market-shape.r2 { fill: var(--region-3); }
.market-shape.r3 { fill: var(--region-4); }
.market-shape.r4 { fill: var(--region-5); }
.market-shape.r5 { fill: var(--region-6); }

.market-shape.pickable { cursor: pointer; }
.market-shape.lit { fill: var(--muted); }

/* Chosen now, against already covered by this study. Different, because one
   is a decision being made and the other is one already taken. */
.market-shape.picked { fill: var(--g-600); }

/* Already covered by this study: darker than its region, so it reads as
   settled rather than as another color to choose from. */
.market-shape.already { fill: var(--ink-soft); }

.market-shape:focus { outline: 2px solid var(--ink); outline-offset: 1px; }

/* The list's half of the same hover. */
.market-grid .check.lit { background: var(--surface-raised); border-radius: 3px; }

/* An outline that is not a market here — Glendive, MT. Drawn, because a hole
   in Montana reads as a defect; gray and outlined, because pale-on-white still
   reads as a hole, and the point is that it is a place rather than nothing. */
.market-shape.unlisted {
    fill: var(--line);
    stroke: var(--line-strong);
    cursor: default;
}

/* Ending a report or a study. Last on the page and quiet: it is not what
   anybody came for, and it is not reversible. */
.delete-report,
.delete-study {
    margin-top: 1.5rem;
    padding-top: .8rem;
    border-top: 1px solid var(--rule);
}

.delete-report button,
.delete-study button { margin: 0; font-size: .85rem; }

.delete-report button:hover,
.delete-study button:hover { color: var(--danger); }

/* The confirming button inside the dialog, where the decision is actually
   taken and the word should look like what it does. */
/* The filled, unmissable version, for the button that actually does the
   deleting once a dialog has asked.
 
   Not the icon buttons. This rule and `button.icon` are both one element and
   one class, so they tie on specificity and this one — written later, for the
   study dialog — was winning. Every delete icon in every table was a solid red
   box at rest, which is the opposite of what the icon-button rule above says it
   wants: no border until it is pointed at. A row's least-used control was the
   loudest thing on the row. */
button.danger:not(.icon) {
    border-color: var(--danger);
    background: var(--danger);
    color: var(--surface);
}

button.danger:not(.icon):hover { background: var(--danger-hover); }

#delete-study .check { display: flex; gap: .4rem; align-items: baseline; margin: 0 0 .2rem; }

/* The dialog serves every row, so it hides the halves that do not apply. A
   class that sets display beats the browser's own rule for [hidden], which is
   why this has to say so. */
#delete-study [hidden] { display: none; }
#delete-study .warning { font-size: .8rem; }

/* Two verbs behind one word, so the dialog offers both rather than choosing.
   Each is its own form — a form cannot be nested — and the row lays them out
   as if they were plain buttons. */
#remove-report { width: min(32rem, calc(100vw - 2rem)); }
#remove-report .dialog-actions form { margin: 0; }
#remove-report .dialog-actions { flex-wrap: wrap; align-items: center; }

/* The base map, inside the deliverable group it belongs to. The name is a
   link back to the file as it arrived; Remove sits beside it because it acts
   on that file and not on the field below. */
.deliverable-options > label { display: block; margin-top: 1rem; font-size: .85rem; }
.deliverable-options > label.check { margin-top: 0; }
.deliverable-options input[type=file] { margin-top: .35rem; font-size: .85rem; }

.deliverable-options .current-map {
    display: flex;
    align-items: baseline;
    gap: .4rem;
    margin: .35rem 0 0;
}

.deliverable-options .current-map button { margin: 0; padding: 0 .3rem; font-size: .75rem; }
.deliverable-options .current-map button:hover { color: var(--danger); }

/* --- Finding sites ---------------------------------------------------------
   Two jobs on one page: deciding what to search for, and cutting down what
   came back. The second is the bigger one — the geocoder returned 47
   Starbucks in Douglas County against a list of 14 — so the per-brand block
   carries its own count and its own way out. */

.lede { max-width: 46rem; margin: 0 0 1.25rem; }

.brand-block { margin-top: 1.5rem; }
.brand-block:first-of-type { margin-top: .5rem; }

.brand-head {
    display: flex;
    align-items: baseline;
    gap: .6rem;
    border-bottom: 1px solid var(--rule);
    padding-bottom: .3rem;
}

.brand-head h3 { margin: 0; font-size: .95rem; }
.brand-head form { margin: 0 0 0 auto; }
.brand-head button { margin: 0; font-size: .8rem; }
.brand-head button:hover { color: var(--danger); }

.list.found td { padding-top: .3rem; padding-bottom: .3rem; }

/* Landing on the row that was just acted on, after a post-redirect-get that
   would otherwise put somebody back at the top of eighty locations. The
   highlight fades, so it says "here" without staying lit. */
.list.found tr:target { scroll-margin-top: 4rem; }
.list.found tr:target td { background: var(--accent-soft, var(--accent-soft)); }
.list.found tr:target td:first-child { box-shadow: inset 3px 0 0 var(--accent); }

/* Enough room under the last row that an anchored one is not flush against the
   bottom of the window. */
.add-sites { scroll-margin-top: 1rem; }
#retailers { scroll-margin-top: 1rem; }
.list.found .coords { font-variant-numeric: tabular-nums; font-size: .8rem; }
.list.found .row-actions form { margin: 0; }
.list.found .row-actions button { font-size: .8rem; padding: .2rem .5rem; }

/* Still legible, visibly not in play. Not hidden: a location set aside is the
   record of a judgement, and hiding it would mean making the judgement again
   the next time somebody wondered. */
.list.found tr.aside td { opacity: .5; }
.list.found tr.aside td .tag { opacity: 1; }

/* A short word about where a value came from, beside the value. */
.tag {
    display: inline-block;
    margin-left: .35rem;
    padding: 0 .35rem;
    border: 1px solid var(--line-strong, var(--line-strong));
    border-radius: 3px;
    font-size: .7rem;
    color: var(--muted);
    white-space: nowrap;
}

.tag.warn { border-color: var(--warn-ink); color: var(--warn-ink); }

.add-sites {
    display: flex;
    align-items: center;
    gap: .75rem;
    margin-top: 1.5rem;
    flex-wrap: wrap;
}

.add-sites button { margin: 0; }
.add-sites .muted { font-size: .8rem; }

.list.brands .tick { width: 2rem; }

/* Three screens that are one job. They span the hub's grid and carry their own
   heading, so "the retailer one" is a place to look rather than three cards to
   tell apart. */
.admin-areas .area-group {
    grid-column: 1 / -1;
    padding: .9rem 1rem 1.1rem;
    border: 1px solid var(--rule);
    border-radius: 4px;
    background: var(--surface-sunken, var(--surface-sunken));
}

.admin-areas .area-group > h2 {
    margin: 0;
    font-size: .8rem;
    font-weight: 600;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
}

.admin-areas .area-group .admin-areas { margin-top: .8rem; }
.admin-areas .area-group .area { background: var(--surface, var(--surface)); }

/* Across, not only back up. Sits under the crumb on each of the three. */
.sub-nav {
    display: flex;
    gap: 1rem;
    margin: -.4rem 0 1rem;
    border-bottom: 1px solid var(--rule);
}

.sub-nav a {
    padding: .3rem 0 .45rem;
    border-bottom: 2px solid transparent;
    font-size: .875rem;
    text-decoration: none;
    color: var(--muted);
}

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

.sub-nav a.current {
    border-bottom-color: var(--accent);
    color: var(--ink);
    font-weight: 600;
}

/* The per-list door, distinguished from the one above it that replaces
   everything. Indented and quieter: same shape, much smaller blast radius. */
.backup.scoped {
    margin-top: 1rem;
    background: var(--surface-sunken, var(--surface-sunken));
}

.list.brands .row-actions form { margin: 0; }
.list.brands .row-actions { text-align: right; width: 3rem; }

/* The way back to the market's list, under the list it would replace. */
.revert { margin-top: .8rem; }
.revert button { margin: 0; font-size: .85rem; }
.revert button:hover { color: var(--danger); }

/* A color setting, beside its field. "#8c00ff00" is not a color anybody can
   picture, which is the whole reason these are stored as #rrggbb rather than
   as KML's own backwards form. */
.swatch {
    display: inline-block;
    width: 1.4rem;
    height: 1.4rem;
    margin-right: .4rem;
    border: 1px solid var(--line-strong, var(--line-strong));
    border-radius: 3px;
    vertical-align: middle;
}

/* --- The landing page ------------------------------------------------------
   Two lists and, when there is something to say, one line above them. Built
   from the tokens above rather than from new values, which is the point of
   having them: this whole screen introduces no color of its own. */

.panes {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(20rem, 1fr));
    gap: var(--s-4);
}

.pane {
    padding: var(--s-4);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface);
    box-shadow: var(--shadow-sm);
}

.pane > header {
    display: flex;
    align-items: baseline;
    gap: var(--s-3);
    margin-bottom: var(--s-3);
    padding-bottom: var(--s-2);
    border-bottom: 1px solid var(--line);
}

.pane > header h2 { margin: 0; font-size: var(--t-lg); }

/* Direct children only. The heading itself is a link now, and the old rule
   matched descendants — which pushed the pane's own title to the right-hand
   edge and shrank it to caption size. */
.pane > header > a { margin-left: auto; font-size: var(--t-sm); }

/* The title leads somewhere, and still reads as a title: the color comes from
   the heading, and the underline only on hover. A pane header carrying a blue
   underlined word looks like a control rather than a name. */
.pane > header h2 a { color: inherit; text-decoration: none; }

.pane > header h2 a:hover,
.pane > header h2 a:focus-visible { text-decoration: underline; }

/* The pane's own action, against the far edge.

   Not `primary`. Two filled navy blocks on a page whose only other navy is the
   header bar pulled the eye to "New study" before "Studies" — backwards, since
   the dashboard is for picking work up again and creating one is the
   occasional act. A plain button with a plus is still plainly a button, and
   still plainly an action rather than another link. */
.pane > header > button {
    margin-left: auto;
    display: inline-flex;
    align-items: center;
}

/* Back to full strength on hover: `button.icon` is muted at rest, which is
   right in a table row of them and too faint for the only control in a
   header. */
.pane > header > button.icon { color: var(--muted); }
.pane > header > button.icon:hover,
.pane > header > button.icon:focus-visible { color: var(--accent); }

.pane .empty-line { margin: 0; font-size: var(--t-md); }

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

.recent li {
    display: flex;
    align-items: baseline;
    gap: var(--s-3);
    padding: var(--s-2) 0;
    border-bottom: 1px solid var(--line);
}

.recent li:last-child { border-bottom: 0; padding-bottom: 0; }

/* The name takes the room and the numbers sit against the right edge, so a
   column of them can be read down rather than hunted for. */
.recent li > a,
.recent li > .recent-name { flex: 1; min-width: 0; }

.recent .meta {
    flex-shrink: 0;
    font-size: var(--t-sm);
    color: var(--muted);
    white-space: nowrap;
}

/* Under the work, because it is not the work. A row of links rather than
   cards: the cards are what /Admin is, and a second set here would be a second
   front door to keep in step with the first. */
/* What you may manage, as tiles.

   **auto-fill, never auto-fit.** This section is two tiles for an Analyst and
   nine for a SuperAdmin. auto-fit collapses the empty tracks and stretches
   what is left across the full width, so two tiles become two enormous ones
   and the row reads as though something failed to load. auto-fill keeps the
   tracks, so two tiles are two tiles. */
.manage { margin-top: var(--s-5); }

.manage h2 {
    margin: 0 0 var(--s-3);
    font-size: var(--t-xs);
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--faint);
}

.manage-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(11rem, 1fr));
    gap: var(--s-2);
}

.manage-grid a {
    display: flex;
    align-items: center;
    gap: .5rem;
    padding: .6rem .7rem;
    border: 1px solid var(--line);
    border-radius: var(--radius);
    background: var(--surface-sunken);
    color: var(--ink);
    text-decoration: none;
    font-size: var(--t-md);
}

/* The icon is the quieter half: it helps you find the row you want at a
   glance and should not be the thing you read. */
.manage-grid a svg { color: var(--muted); flex: 0 0 auto; }

.manage-grid a:hover,
.manage-grid a:focus-visible {
    border-color: var(--accent);
    background: var(--accent-soft);
    color: var(--accent);
}

.manage-grid a:hover svg,
.manage-grid a:focus-visible svg { color: var(--accent); }

/* The way to everything, under the shortcuts to some of it. */
.manage > .more {
    display: inline-block;
    margin-top: var(--s-3);
    font-size: var(--t-sm);
}

/* Pushed to the far end: it is the way to everything rather than one more
   thing beside the six. */
.shortcuts a.more { margin-left: auto; color: var(--muted); }
.shortcuts a.more:hover { color: var(--accent); }

/* Whose it is, on a landing page that can hold more than one customer's work.
   Between the name and the numbers, because it qualifies the name. */
.recent .owner {
    flex-shrink: 0;
    padding: 0 .35rem;
    border-radius: var(--radius-sm);
    background: var(--surface-raised);
    font-size: var(--t-xs);
    color: var(--muted);
    white-space: nowrap;
}

/* The site name in the grid, which opens that site against the benchmark.
   Underlined only on hover: eighty-three blue underlines down the first column
   turn the report's own labels into a list of links, and the name is what the
   row is called before it is somewhere to go. */
.grid .sticky-col .site-link { color: inherit; text-decoration: none; }
.grid .sticky-col .site-link:hover,
.grid .sticky-col .site-link:focus-visible { text-decoration: underline; }

/* One site against the average. Narrower than the grid — five columns, read
   rather than scanned — and left where a page of text would be. */
.compare { max-width: 56rem; }
.compare td:first-child { width: 40%; }

/* ---------------------------------------------------------------------------
   Finding things in a list that has outgrown a screen.

   The search sits between the heading and the New button, so the row reads
   "what this is - find one - make one". It is allowed to shrink before the
   heading does, because a narrowed search box is still usable and a wrapped
   heading is not.
   --------------------------------------------------------------------------- */
.list-search {
    display: flex;
    align-items: center;
    gap: var(--s-2);
    margin: 0;
    flex: 0 1 26rem;
    margin-left: auto;
}

.list-search input[type="search"] { flex: 1 1 auto; min-width: 0; }

.list-search button {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    white-space: nowrap;
}

/* The New button follows the search rather than being pushed to the far edge
   by it: two `margin-left: auto` in one row would split them apart. */
.page-head > .list-search + .primary,
.page-head > .list-search + button { margin-left: 0; }

.quiet-link { color: var(--muted); font-size: .875rem; white-space: nowrap; }
.quiet-link:hover { color: var(--ink); }

.pager {
    display: flex;
    align-items: center;
    gap: .25rem;
    margin: var(--s-4) 0 0;
    font-size: .875rem;
}

/* The count first and then pushed away from the controls, so "21-40 of 63"
   reads as a label on the table rather than as another button. */
.pager-count { color: var(--muted); margin-right: auto; }

.pager a,
.pager .pager-gap {
    display: inline-block;
    min-width: 2rem;
    padding: .25rem .5rem;
    border: 1px solid transparent;
    border-radius: var(--radius);
    text-align: center;
    text-decoration: none;
    color: var(--ink);
}

.pager a:hover { background: var(--surface-sunken); }

.pager a.active {
    border-color: var(--accent);
    background: var(--accent);
    color: var(--surface);
    font-weight: 600;
}

.pager .pager-step { min-width: 0; color: var(--accent); }

/* Still a link in the markup, because the first and last pages are where the
   arrows stop meaning anything and a missing control is harder to understand
   than a dimmed one. */
.pager .pager-step.disabled {
    color: var(--muted);
    pointer-events: none;
    opacity: .55;
}

.pager .pager-gap { color: var(--muted); border: 0; }

/* The study a report is filed under, under its name rather than after it.
   Inline, it wrapped mid-phrase — "(Pickleman's Nebraska" above "Study)" — in
   a column that has to hold a county name too. A line of its own is the
   pattern the rest of this table already uses for secondary text, and it is
   deliberate rather than accidental. */
.in-study {
    display: block;
    color: var(--muted);
    font-size: .8rem;
    margin-top: .15rem;
}

/* The dashboard's last row when the list runs out before the work does. */
.recent .more-row { text-align: center; }

.recent .more-row a {
    display: block;
    padding: .2rem 0;
    color: var(--muted);
    letter-spacing: .2em;
    text-decoration: none;
}

.recent .more-row a:hover { color: var(--accent); }

/* ---------------------------------------------------------------------------
   Where an organization stands this month.

   One number, large, and the working under it. Somebody opening this page is
   asking "can I run this report", and that is answered by what is left rather
   than by a table of what has gone.
   --------------------------------------------------------------------------- */
.credit-standing { max-width: 48rem; }

.credit-headline {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    gap: var(--s-3);
    flex-wrap: wrap;
}

.credit-figure strong { font-size: 2.2rem; line-height: 1.1; margin-right: .4rem; }
.credit-figure.over strong { color: var(--error-line); }

.credit-bar {
    height: .5rem;
    margin: var(--s-3) 0 var(--s-2);
    border-radius: 999px;
    background: var(--surface-sunken);
    overflow: hidden;
}

.credit-bar span {
    display: block;
    height: 100%;
    background: var(--accent);
}

.credit-bar span.over { background: var(--error-line); }

.credit-line { margin: 0; }

/* A run that reached nothing is listed so the ledger can be seen working, and
   dimmed so it is not mistaken for a charge. `.list tr.inactive` already does
   this for deactivated rows elsewhere. */
table.credits td:first-child { white-space: nowrap; }

/* The terms, as a summary that opens the dialog holding them. */
.list button.credit-terms { padding: 0; white-space: nowrap; }
.list button.credit-terms:hover { text-decoration: underline; }

/* A sentence under the field it explains, rather than beside it. */
.editor .field-note { margin: -.4rem 0 var(--s-3); font-size: .8rem; }

/* Step 1 of the market analysis (#101): the ranked, tiered DMA table and the
   two penetration goals above it. */

/* The ranked plan table, on the study screen and the market page.
   
   It wears `.list` for the borders, padding and heading band every other table
   in the application has — it did not, and read as bare rows on a white page.
   `.plan` is then only what makes it different from a list of reports.

   The class used to be `.markets`, which is a <ul> style elsewhere in this
   file: the table picked up `list-style: none` and nothing else, which is a
   confusing way to have no styles at all. */
table.plan {
    /* Eleven columns against .list's eight, so it may use the full width. */
    max-width: none;
}

table.plan td.tick, table.plan th.tick { width: 1.6rem; padding-right: 0; }

/* Tabular figures so the goals line up down the column — without them
   "463,000" and "488,000" are different widths and the eye cannot compare a
   ladder. */
table.plan td.numeric,
table.plan th.numeric { font-variant-numeric: tabular-nums; }

/* Tighter than .list: eleven columns at .8rem of side padding is a table that
   scrolls on a laptop. */
table.plan th, table.plan td { padding: .4rem .55rem; }

/* The name is the one column worth the width; everything else is a number or a
   badge and should take only what it needs. */
table.plan td.name { white-space: nowrap; width: 100%; }

/* The lifecycle stage is prose in a row of figures, and the longest of them —
   "Market Dominance" — is what sets the column's width. It has to be allowed
   that width: `.name` takes all the slack, so without this the stage column is
   squeezed to its narrowest word and "New Market Entry" becomes three lines
   that set the height of every row around it. */
table.plan td.stage { font-size: .85rem; white-space: nowrap; }

table.plan tbody tr:hover { background: var(--accent-soft); }

.region-goals input[type="number"] { width: 6.5rem; }

/* Wraps on a narrow screen rather than pushing the unit note off the card. */
.region-goals { flex-wrap: wrap; }
.region-goals .muted { font-size: .8rem; }

/* Said rather than implied: a ranking on residents is not the model's
   ranking, and a screen that looks identical either way would be the worst
   outcome available. Bordered rather than colored — it is a statement about
   provenance, not a failure. */
.provisional {
    margin: .7rem 0 0;
    padding: .5rem .7rem;
    border-left: 3px solid var(--tier-2);
    background: var(--surface-sunken);
    font-size: .85rem;
    color: var(--muted);
}

.provisional strong { color: var(--ink); }

/* The two market-code buttons sit side by side: a preview that costs nothing
   and the write that follows it. */
.market-codes {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
}

.market-review pre {
    white-space: pre-wrap;
    margin: 0.5rem 0 0;
    font-size: 0.9em;
    color: var(--muted);
}

/* The purchase sits inside the banner that explains why it is needed. */
.buy-markets {
    display: flex;
    flex-wrap: wrap;
    gap: 0.75rem;
    align-items: center;
    margin-top: 0.75rem;
}

/* --- Site tabs and the demographic panels (#103) -------------------------- */

/* Links, not buttons: the tab is in the URL so it can be sent to somebody. */
.tabs {
    display: flex;
    flex-wrap: wrap;
    gap: var(--s-3);
    margin: var(--s-4) 0 var(--s-3);
    border-bottom: 1px solid var(--line);
}

.tabs a {
    padding: .45rem .1rem .55rem;
    color: var(--muted);
    text-decoration: none;
    border-bottom: 2px solid transparent;
    margin-bottom: -1px;
}

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

.tabs a.current {
    color: var(--ink);
    font-weight: 600;
    border-bottom-color: var(--accent);
}

/* The analyst's own package opens on a row of these, and they are the one
   part of it this page can fill completely. */
.indicators {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));
    gap: var(--s-3);
    margin: var(--s-4) 0;
}

.indicator {
    display: flex;
    flex-direction: column;
    gap: .15rem;
    padding: .7rem .9rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: 6px;
}

.indicator .figure { font-size: 1.35rem; font-weight: 600; font-variant-numeric: tabular-nums; }

.distribution { margin: var(--s-5) 0; }
.distribution h2 { font-size: 1.05rem; margin: 0 0 .3rem; }
.distribution .provisional { margin: 0 0 .6rem; max-width: 46rem; }

table.bands { max-width: 46rem; }
table.bands th[scope="row"] { font-weight: normal; white-space: nowrap; }
table.bands td.numeric { width: 6rem; font-variant-numeric: tabular-nums; }

/* Half the table, so the shape is legible. Without a width the bar column
   took only what three columns of content left it, and every bar was a sliver
   of a sliver. */
table.bands td.bar { width: 45%; padding-left: .9rem; }

table.bands td.bar span {
    display: block;
    height: .6rem;
    min-width: 1px;
    border-radius: 2px;
    background: var(--accent);
}

table.industries { max-width: 36rem; }
