/*
 * The panel's entire visual style: one hand-written file, no framework, no
 * build step beyond being copied into the bundle.
 *
 * The screen this styles is a working tool, used by one administrator, often
 * with ten thousand rows on it. So: system fonts at a comfortable size, one
 * accent colour used sparingly enough that it still means "this one", generous
 * hit targets on the few destructive controls, and density everywhere else.
 * Nothing animates. Nothing moves under the cursor.
 *
 * Colours are defined once as custom properties and swapped wholesale for dark
 * mode, so no rule below ever names a colour twice.
 */

/*
 * The one message a visitor can see without the bundle. Styled by a class
 * rather than a `style=` attribute so that the Content-Security-Policy in
 * `web/app/_headers` needs no `unsafe-inline` for it — one inline style is all
 * it takes to have to allow every inline style.
 */
.noscript {
  padding: 2rem;
}

:root {
  --bg: #f5f6f8;
  --surface: #ffffff;
  --surface-sunken: #f0f1f4;
  --fg: #1b1b1f;
  --fg-muted: #5b5b66;
  --fg-faint: #8a8a96;
  --line: #d9dae0;
  --line-strong: #b9bac4;

  /* One accent, used for the primary action, focus, and the current nav item. */
  --accent: #1f5fd0;
  --accent-fg: #ffffff;
  --accent-weak: #e8effb;

  --danger: #b3261e;
  --danger-weak: #fdecea;
  --warn: #8a5a00;
  --warn-weak: #fdf3e0;
  --ok: #17603a;
  --ok-weak: #e7f4ec;

  --radius: 6px;
  --gap: 1rem;
  --page-width: 72rem;
}

@media (prefers-color-scheme: dark) {
  :root {
    --bg: #16171a;
    --surface: #1e2024;
    --surface-sunken: #24262b;
    --fg: #e9e9ee;
    --fg-muted: #a9aab4;
    --fg-faint: #7c7d88;
    --line: #34363d;
    --line-strong: #4a4c55;

    --accent: #7aa7f2;
    --accent-fg: #10131a;
    --accent-weak: #1d2a41;

    --danger: #f2938c;
    --danger-weak: #3a1f1d;
    --warn: #e3b665;
    --warn-weak: #372c14;
    --ok: #86d3a6;
    --ok-weak: #16301f;
  }
}

* {
  box-sizing: border-box;
}

/* The browser's own `[hidden] { display: none }` is only as specific as a class
   selector and loses to any author rule that sets `display`. Half the live
   feedback on the campaign screen is a `hidden` element with `display: flex` on
   it, so without this the findings and the counters would all be on screen at
   once, permanently, and nothing would look broken enough to investigate. */
[hidden] {
  display: none !important;
}

html {
  /* Reserve the scrollbar's width: a table that grows past one screen must not
     shift the whole layout sideways as it does. */
  scrollbar-gutter: stable;
}

body {
  margin: 0;
  font:
    15px/1.5 system-ui,
    -apple-system,
    "Segoe UI",
    Roboto,
    "Helvetica Neue",
    sans-serif;
  color: var(--fg);
  background: var(--bg);
  -webkit-text-size-adjust: 100%;
}

h1,
h2,
h3 {
  margin: 0 0 0.75rem;
  line-height: 1.25;
  font-weight: 600;
}

h1 {
  font-size: 1.35rem;
}
h2 {
  font-size: 1.1rem;
}

p {
  margin: 0 0 0.75rem;
}

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

a:hover {
  text-decoration-thickness: 2px;
}

/* One focus treatment everywhere, including on things that are not inputs.
   Keyboard use is not an afterthought on a screen that is mostly forms. */
:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 3px;
}

/* --- Shell ---------------------------------------------------------------- */

.masthead {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--gap);
  padding: 0.75rem 1.25rem;
  background: var(--surface);
  border-bottom: 1px solid var(--line);
}

.brand {
  font-weight: 650;
  color: var(--fg);
  text-decoration: none;
  letter-spacing: -0.01em;
}

.nav {
  display: flex;
  gap: 0.25rem;
  flex: 1;
}

.nav-item {
  padding: 0.35rem 0.7rem;
  border-radius: var(--radius);
  color: var(--fg-muted);
  text-decoration: none;
}

.nav-item:hover {
  background: var(--surface-sunken);
  color: var(--fg);
}

.nav-item.is-current {
  background: var(--accent-weak);
  color: var(--accent);
  font-weight: 550;
}

/* A screen that does not exist yet. Saying so is better than linking to a 404
   and better than hiding half the panel. */
.nav-item.is-pending {
  color: var(--fg-faint);
  cursor: default;
}

.account {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.username {
  font-weight: 550;
  color: var(--fg);
}

.page {
  max-width: var(--page-width);
  margin: 0 auto;
  padding: 1.5rem 1.25rem 4rem;
}

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

.card + .card {
  margin-top: var(--gap);
}

/* --- Banners and messages ------------------------------------------------- */

.banner {
  margin: 0;
  padding: 0.6rem 1.25rem;
  font-weight: 550;
  text-align: center;
}

/* Test mode is the state you want to be in until you are certain, and the one
   that must never be mistaken for the real thing once you are. */
.banner-test {
  color: var(--warn);
  background: var(--warn-weak);
  border-bottom: 1px solid var(--line);
}

.error,
.warning,
.success {
  padding: 0.6rem 0.8rem;
  border-radius: var(--radius);
  border: 1px solid currentColor;
}

.error {
  color: var(--danger);
  background: var(--danger-weak);
}

.warning {
  color: var(--warn);
  background: var(--warn-weak);
}

.success {
  color: var(--ok);
  background: var(--ok-weak);
}

/* --- Forms ---------------------------------------------------------------- */

label {
  display: block;
  margin-bottom: 0.3rem;
  font-size: 0.9rem;
  font-weight: 550;
  color: var(--fg-muted);
}

input[type="text"],
input[type="password"],
input[type="number"],
input[type="search"],
select,
textarea {
  width: 100%;
  padding: 0.5rem 0.6rem;
  font: inherit;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

input:focus,
select:focus,
textarea:focus {
  border-color: var(--accent);
}

textarea {
  min-height: 8rem;
  resize: vertical;
  /* Templates are read character by character when the segment count is close
     to a boundary, so they are set in a monospaced face. */
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.95rem;
}

input + label,
textarea + label,
select + label {
  margin-top: var(--gap);
}

button {
  padding: 0.5rem 1rem;
  font: inherit;
  font-weight: 550;
  color: var(--accent-fg);
  background: var(--accent);
  border: 1px solid transparent;
  border-radius: var(--radius);
  cursor: pointer;
}

/* Not when it is disabled: Continue spends most of its life that way, and a
   button that lights up under the cursor is a button that says it can be
   pressed. */
button:hover:not([disabled]) {
  filter: brightness(1.08);
}

button.secondary {
  color: var(--fg);
  background: var(--surface);
  border-color: var(--line-strong);
}

/* Sending is the one irreversible action in the program. It gets the one
   colour nothing else uses. */
button.danger {
  color: var(--accent-fg);
  background: var(--danger);
}

button[disabled] {
  opacity: 0.55;
  cursor: not-allowed;
}

/* A button that has to sit inside a line of text — logout in the masthead —
   without looking like a button. */
.link-button {
  padding: 0;
  color: var(--accent);
  background: none;
  border: none;
  font-weight: inherit;
  text-decoration: underline;
}

.logout {
  display: inline;
}

/* --- Input mode tabs ------------------------------------------------------ */

/* Tabs with no script in them. The three radios sit ahead of everything they
   control, so `:checked ~` can reach the tab that should look current and the
   panel that should be visible. That keeps all three panels in the document at
   once — switching tabs cannot lose what was pasted into another one — and it
   keeps working before app.js has loaded.

   The radio is the field the form submits, so the tab on screen and the mode the
   server parses by are the same thing rather than two things kept in step. */

/* A fieldset, because the modes are a radio group and that is what one is for.
   `min-width` because a fieldset's default `min-inline-size: min-content` would
   otherwise let a wide table inside a panel push the card past the page. */
.modes {
  position: relative;
  min-width: 0;
}

.modes legend {
  padding: 0 0.4rem;
  font-size: 1.1rem;
  font-weight: 600;
}

/* Off-screen but still focusable: the tab strip is reachable and traversable
   with the arrow keys because these are real radios, not styled divs. */
.mode-radio {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
  pointer-events: none;
}

.tablist {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-bottom: 1.25rem;
  border-bottom: 1px solid var(--line);
}

.tablist label {
  margin-bottom: -1px;
  padding: 0.45rem 0.9rem;
  font-size: 1rem;
  font-weight: 550;
  color: var(--fg-muted);
  border: 1px solid transparent;
  border-bottom-color: var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  cursor: pointer;
}

.tablist label:hover {
  color: var(--fg);
  background: var(--surface-sunken);
}

#mode-pairs:checked ~ .tablist label[for="mode-pairs"],
#mode-blocks:checked ~ .tablist label[for="mode-blocks"],
#mode-file:checked ~ .tablist label[for="mode-file"] {
  color: var(--accent);
  background: var(--surface);
  border-color: var(--line);
  border-bottom-color: var(--surface);
}

#mode-pairs:focus-visible ~ .tablist label[for="mode-pairs"],
#mode-blocks:focus-visible ~ .tablist label[for="mode-blocks"],
#mode-file:focus-visible ~ .tablist label[for="mode-file"] {
  outline: 2px solid var(--accent);
  outline-offset: -2px;
}

/* Hidden by default rather than shown, so a panel is visible only because its
   radio is checked. A browser that understands none of this shows all three,
   which is cluttered but honest. */
.mode-panel {
  display: none;
}

#mode-pairs:checked ~ #panel-pairs,
#mode-blocks:checked ~ #panel-blocks,
#mode-file:checked ~ #panel-file {
  display: block;
}

.hint {
  color: var(--fg-muted);
  font-size: 0.9rem;
}

/* A control that would look absurd stretched across the page — the separator
   select is four words wide, not seventy-two rem. */
.field-narrow {
  max-width: 18rem;
  margin-bottom: var(--gap);
}

/* Pasted lists are the point of this screen, so the box they go in is tall
   enough to see a page of them without scrolling, and never wraps a long line
   into looking like two. */
.mode-panel textarea {
  min-height: 16rem;
  white-space: pre;
  overflow-wrap: normal;
  overflow-x: auto;
}

.mode-panel textarea + .hint {
  margin-top: 0.4rem;
}

/* --- Live feedback on the two-blocks panel -------------------------------- */

/* The counters are the whole reason this mode is survivable: one extra line in
   the first block shifts every pair below it, and these two numbers are where
   that shows up while it is still being pasted. Big, tabular, and impossible to
   read as anything but a pair. */
.counters {
  display: flex;
  gap: 1.5rem;
  margin: 0 0 var(--gap);
  padding: 0.5rem 0.7rem;
  font-size: 1.05rem;
  font-variant-numeric: tabular-nums;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.counters output {
  font-weight: 650;
}

/* Past `WARN_IF_PARTS_ABOVE`. Amber rather than red: the message can still be
   sent, it simply costs more than one SMS per recipient. */
.counters.is-warn {
  color: var(--warn);
  background: var(--warn-weak);
  border-color: currentColor;
}

/* Room left before another segment, set quietly beside the numbers it explains
   rather than as a finding of its own — nothing is wrong yet. */
.headroom {
  color: var(--fg-muted);
  font-size: 0.9rem;
}

/* Divergence is not a subtle state. */
.counters.is-bad {
  color: var(--danger);
  background: var(--danger-weak);
  border-color: currentColor;
}

.finding {
  display: flex;
  flex-wrap: wrap;
  gap: 0.75rem;
  align-items: center;
  margin-bottom: 0.6rem;
}

/* An error is a refusal; a warning is a question. They must not look alike. */
.finding.error {
  color: var(--danger);
  font-weight: 550;
}

.finding.warning {
  color: var(--warn);
}

.finding button {
  padding: 0.25rem 0.7rem;
  font-size: 0.9rem;
}

.preview {
  margin-bottom: var(--gap);
}

/* A row whose other half does not exist. Marked, never dropped, and never
   quietly paired with whatever happened to be next. */
.is-unpaired td {
  color: var(--danger);
  background: var(--danger-weak);
}

/* The rows between the first twenty and the last five. */
.preview-gap td {
  color: var(--fg-faint);
  text-align: center;
  font-size: 0.85rem;
  background: var(--surface-sunken);
}

/* --- The file-import panel ------------------------------------------------ */

/* Big, obviously a target, and equally usable without a mouse: the file input
   inside it is a real one, so the panel works by keyboard and by drag alike. */
.dropzone {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 0.75rem;
  align-items: baseline;
  margin-bottom: var(--gap);
  padding: 1.75rem 1.25rem;
  text-align: left;
  background: var(--surface-sunken);
  border: 2px dashed var(--line-strong);
  border-radius: var(--radius);
}

.dropzone-hint {
  margin: 0;
  font-size: 1.05rem;
  font-weight: 550;
}

.dropzone label {
  margin: 0;
  color: var(--fg-muted);
  font-weight: 400;
}

/* The only state on this screen that responds to the pointer, because a drop
   target that does not acknowledge the file over it is a drop target people
   let go of too early. */
.dropzone.is-dragging {
  color: var(--accent);
  background: var(--accent-weak);
  border-color: var(--accent);
}

/* Replace / Append / Cancel. A warning rather than an error: nothing is wrong,
   there is simply a decision that is not ours to make (§12). */
.combine {
  padding: 0.6rem 0.8rem;
  background: var(--warn-weak);
  border: 1px solid var(--warn);
  border-radius: var(--radius);
}

.import-summary {
  margin-bottom: var(--gap);
}

/* What the reader had to guess — the sheet, the delimiter, the columns, the
   header — set small and in one line. Present so a wrong guess can be noticed,
   not so it has to be read every time. */
.import-facts {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem 1rem;
  margin: 0 0 0.5rem;
  color: var(--fg-muted);
  font-size: 0.9rem;
}

.import-facts strong {
  color: var(--fg);
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* The four numbers of §11.2, in the counters' clothes: same box, same tabular
   figures, so the two panels count out loud in the same voice. */
.counters .is-invalid {
  color: var(--danger);
}

.counters .is-duplicate {
  color: var(--warn);
}

/* A row that will not be sent, and the reason it will not. Never dropped: the
   list that silently shrank from 150 to 148 is the outcome this whole screen
   exists to prevent. */
tr.is-invalid td {
  background: var(--danger-weak);
}

tr.is-duplicate td {
  background: var(--warn-weak);
}

td.reason {
  color: var(--fg-muted);
  font-size: 0.85rem;
  white-space: normal;
}

/* --- The list review ------------------------------------------------------ */

.review h3 {
  margin: var(--gap) 0 0.5rem;
  font-size: 1rem;
}

/* The one question on this screen that has to be answered before anything can
   be sent, so it is framed rather than left as three loose radios. */
.duplicates {
  margin-top: var(--gap);
  padding: 0.75rem 1rem 1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

.duplicates legend {
  padding: 0 0.4rem;
  font-weight: 600;
}

.choices {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem 1.5rem;
  margin-bottom: var(--gap);
}

/* Label and control on one line, and the whole label a hit target — these are
   pressed once each and read carefully, not swept through. */
label.choice {
  display: flex;
  gap: 0.4rem;
  align-items: baseline;
  margin: 0;
  font-weight: 400;
  cursor: pointer;
}

td.choice {
  width: 1%;
  text-align: center;
}

/* --- The message editor --------------------------------------------------- */

/* Shorter than the paste boxes on purpose: this is one message, and a field
   sized for ten thousand lines would suggest otherwise. It wraps, unlike them:
   this is prose, not a list of rows that must not look like two. */
.composer textarea {
  min-height: 7rem;
}

.composer h2 {
  margin-top: 0;
}

/* The one button that leaves this screen, and it is the last thing on it. */
.actions {
  display: flex;
  justify-content: flex-end;
  gap: 0.75rem;
  margin-top: var(--gap);
}

/* --- The confirmation figures --------------------------------------------- */

/* The two numbers on this screen are the two that get confused with each other:
   347 recipients is 694 messages when the text runs to two SMS, and whichever
   one the screen made prominent is the one people remember. So they are the
   same size, side by side, and each carries its own word. */
.figures {
  display: flex;
  flex-wrap: wrap;
  gap: 1rem 3rem;
  margin-top: var(--gap);
}

.figure {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  margin: 0;
}

.figure output {
  font-size: 2rem;
  font-weight: 650;
  font-variant-numeric: tabular-nums;
  line-height: 1.1;
}

.figure span {
  color: var(--fg-muted);
  font-size: 0.9rem;
}

/* The message as it was written — every space and line break of it, because
   those are characters somebody is being charged for. */
.message-preview {
  margin: 0;
  padding: 0.75rem 0.9rem;
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
  font-size: 0.95rem;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

/* An amber card rather than an amber line: the cost of a campaign is not a
   footnote, and this is the last screen before it is incurred. */
.panel-warn {
  border-color: var(--warn);
  background: var(--warn-weak);
}

.panel-warn h2 {
  margin-top: 0;
  color: var(--warn);
}

/* The tick that has to happen before a multi-segment campaign can go out.
   Roomy, because it is the one control on the screen somebody might otherwise
   click past without reading. */
.acknowledge {
  gap: 0.6rem;
  align-items: flex-start;
  margin: var(--gap) 0;
  padding: 0.75rem 0.9rem;
  background: var(--surface-sunken);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

/* A link that stands beside a button and has to look like its equal without
   pretending to be the primary action. */
.button-link {
  padding: 0.5rem 1rem;
  color: var(--fg);
  font-weight: 550;
  text-decoration: none;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

.button-link:hover {
  background: var(--surface-sunken);
}

/* The last chance to reconsider (§22.1). Native <dialog>, so the backdrop, the
   focus trap and Escape are the browser's job rather than three more bugs. */
.modal {
  max-width: 32rem;
  padding: 1.25rem 1.5rem;
  color: var(--fg);
  background: var(--surface);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

.modal h2 {
  margin-top: 0;
}

.modal::backdrop {
  background: rgb(0 0 0 / 45%);
}

/* --- Tables --------------------------------------------------------------- */

/* Recipient tables run to a hundred rows a page, so the rules are thin, the
   header sticks, and the numeric columns line up under each other. */
.table-wrap {
  overflow-x: auto;
  border: 1px solid var(--line);
  border-radius: var(--radius);
  background: var(--surface);
}

table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.92rem;
}

th,
td {
  padding: 0.45rem 0.7rem;
  text-align: left;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

thead th {
  position: sticky;
  top: 0;
  background: var(--surface-sunken);
  color: var(--fg-muted);
  font-size: 0.85rem;
  font-weight: 600;
}

tbody tr:last-child td {
  border-bottom: none;
}

tbody tr:hover td {
  background: var(--surface-sunken);
}

td.numeric,
th.numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

td.phone,
td.code {
  font-family: ui-monospace, SFMono-Regular, "SF Mono", Menlo, Consolas, monospace;
}

/* --- The recipient table -------------------------------------------------- */

.search {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  align-items: center;
  margin-bottom: var(--gap);
}

.search label {
  margin: 0;
}

.search input {
  flex: 1 1 16rem;
  width: auto;
}

/* Widened to the whole row by the bundle, so the cursor says so. The disclosure
   underneath is a native <details> and works without any of this. */
.recipients tbody tr {
  cursor: pointer;
}

.recipients summary {
  color: var(--accent);
  cursor: pointer;
}

/* The stored message, which may be long and must wrap rather than stretch the
   table sideways. */
.recipients .message-preview {
  max-width: 34rem;
  margin-top: 0.4rem;
  white-space: pre-wrap;
}

.recipients td {
  vertical-align: top;
}

/* A row that costs more than one SMS. Marked, because "which promo codes pushed
   it over" is the question the distribution panel raises and this answers. */
tr.is-multipart td {
  background: var(--warn-weak);
}

.pager {
  display: flex;
  gap: 0.75rem;
  align-items: center;
  justify-content: flex-end;
  margin-top: var(--gap);
}

.button-link.is-disabled {
  color: var(--fg-faint);
  border-color: var(--line);
}

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

/* Native <progress>: announced by a screen reader without help, and legible
   before a single rule below has been read. */
progress {
  width: 100%;
  height: 0.6rem;
  margin-top: var(--gap);
  accent-color: var(--accent);
}

/* The four views of a list. Reuses the tab strip from the input modes, because
   they are the same thing — but these are links, since each view is an address
   somebody can send to a colleague. */
.filters {
  margin-bottom: var(--gap);
}

.filters a,
.filters span {
  margin-bottom: -1px;
  padding: 0.35rem 0.8rem;
  color: var(--fg-muted);
  font-weight: 550;
  text-decoration: none;
  border: 1px solid transparent;
  border-bottom-color: var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
}

.filters a:hover {
  color: var(--fg);
  background: var(--surface-sunken);
}

.filters [aria-current="page"] {
  color: var(--accent);
  background: var(--surface);
  border-color: var(--line);
  border-bottom-color: var(--surface);
}

/* --- Status pills --------------------------------------------------------- */

/* Every recipient state the panel shows is a word, never a provider status
   number: the colour is the summary, the word is the truth. */
.pill {
  display: inline-block;
  padding: 0.1rem 0.5rem;
  border-radius: 999px;
  font-size: 0.82rem;
  font-weight: 550;
  background: var(--surface-sunken);
  color: var(--fg-muted);
}

.pill-ok {
  background: var(--ok-weak);
  color: var(--ok);
}

.pill-warn {
  background: var(--warn-weak);
  color: var(--warn);
}

.pill-bad {
  background: var(--danger-weak);
  color: var(--danger);
}

.muted {
  color: var(--fg-muted);
}

.numeric {
  font-variant-numeric: tabular-nums;
}

/* --- Login ---------------------------------------------------------------- */

/* The one screen with no shell around it, so it centres itself. */
.login {
  max-width: 22rem;
  margin: 12vh auto 2rem;
  padding: 1.5rem;
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.login h1 {
  margin-bottom: 1.25rem;
  text-align: center;
}

.login-form button {
  width: 100%;
  margin-top: 1.25rem;
  padding: 0.6rem;
}

.login .error {
  margin-bottom: 1rem;
}

.locale-switcher {
  margin: 1.25rem 0 0;
  font-size: 0.85rem;
  color: var(--fg-muted);
  text-align: center;
}

.locale-switcher span[aria-current] {
  font-weight: 650;
  color: var(--fg);
}

.locale-switcher a {
  margin-left: 0.25rem;
}

/* In the masthead the switcher is one item in a row, not a paragraph. */
.account .locale-switcher {
  margin: 0;
  text-align: left;
}

/* ---------------------------------------------------------------------------
   The static panel
   ---------------------------------------------------------------------------

   Everything above was written for server-rendered HTML, and almost all of it
   still applies — the panel looks the same because it is the same markup drawn
   by a different thing. What follows is only what the move genuinely added:
   controls that used to be links and are now buttons, and two tables that did
   not exist.

   Nothing here restyles anything above. If a rule below is doing more than
   filling a gap, it is in the wrong place. */

/* The mode strip and the status filters were anchors on a server-rendered page
   and are buttons now. They keep the same look and the same `aria-current`
   selector, so the two vocabularies did not fork. */
.filters button {
  margin-bottom: -1px;
  padding: 0.35rem 0.8rem;
  font: inherit;
  font-weight: 550;
  color: var(--fg-muted);
  background: none;
  border: 1px solid transparent;
  border-bottom-color: var(--line);
  border-radius: var(--radius) var(--radius) 0 0;
  cursor: pointer;
}

.filters button:hover {
  color: var(--fg);
  background: var(--surface-sunken);
}

.filters button[aria-current="page"] {
  color: var(--accent);
  background: var(--surface);
  border-color: var(--line);
  border-bottom-color: var(--surface);
}

/* The `:checked ~` selector that highlighted the current tab needed the radio
   and the label to be siblings in one form. They are not any more, so the
   router marks the tab instead — same appearance, different mechanism. */
.tablist label[aria-current="page"] {
  color: var(--accent);
  background: var(--surface);
  border-color: var(--line);
  border-bottom-color: var(--surface);
}

/* The panels the screen redraws on every keystroke. They hold nothing of their
   own; they are named so the redraw has something to replace. */
.template-panel:empty,
.review-panel:empty,
.findings:empty,
.import-pending:empty {
  display: none;
}

.findings > .finding + .finding {
  margin-top: 0.5rem;
}

/* GSM-7 or UCS-2, set beside the counts it explains. Quiet: it is the reason
   for the numbers, not one of them. */
.encoding {
  color: var(--fg-faint);
  font-variant: small-caps;
  letter-spacing: 0.04em;
}

.separator {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin-top: var(--gap);
}

.separator select {
  width: auto;
}

.import-name {
  margin: 0.5rem 0 0;
  color: var(--fg-muted);
  font-size: 0.92rem;
}

/* Rows that will not be sent. Open by default — a disclosure nobody opens is a
   list nobody reads, and the whole point is that they are shown rather than
   dropped. */
.problems {
  margin-top: var(--gap);
}

.problems summary {
  font-weight: 600;
  cursor: pointer;
}

/* The provider counted a different number of segments than we did, and it is
   the one that bills. Marked rather than hidden: a panel that quietly showed
   its own number would under-report what a campaign cost. */
.parts-disagree {
  color: var(--warn);
  font-weight: 600;
}

.parts-disagree small {
  color: var(--fg-faint);
  font-weight: 400;
}

/* Not every message fits in one SMS, so the confirmation screen shows the
   spread rather than one number that is true of nobody. */
.distribution {
  margin-top: var(--gap);
  padding: 0.75rem 1rem 1rem;
  background: var(--surface-sunken);
  border: 1px solid var(--line);
  border-radius: var(--radius);
}

.distribution h2 {
  margin-top: 0;
  font-size: 1rem;
}

.distribution table {
  background: var(--surface);
}

/* The acknowledgement, and the two buttons under it. Framed, because it is the
   last thing between a draft and ten thousand messages. */
.confirm {
  margin-top: var(--gap);
  padding: 1rem;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
}

.confirm .choice {
  align-items: flex-start;
  font-weight: 550;
}

.confirm .finding[hidden] {
  display: none;
}

/* --- Narrow screens ------------------------------------------------------- */

/* Not a phone layout — this is a desktop tool — but a laptop at half width
   should not clip the masthead. */
@media (max-width: 40rem) {
  .masthead {
    padding: 0.6rem 0.9rem;
  }

  .nav {
    order: 3;
    flex-basis: 100%;
  }

  .page {
    padding: 1rem 0.9rem 3rem;
  }
}
