/* =============================================================================
   Channel reseller portal — E6. AZUVYN / MIDNIGHT SLATE.
   -----------------------------------------------------------------------------
   Mobile-first. Every layout rule below starts from a ~360px column and widens
   at one breakpoint; nothing is designed at desktop and then squeezed. A
   reseller standing in front of their customer with a phone is the case that
   has to work.

   COLOUR comes from /_generated/midnight-slate.css — the checked projection of
   flagship-site/src/data/brand-tokens.json, which BRAND.md names the machine
   owner of every exact Azuvyn value. This file maps those roles onto local
   component names and defines NO brand hex of its own (the two exceptions are
   named and justified below). tools/derived-artifacts.json holds the freshness
   row, so a brand-token move fails flagship's own gate rather than silently
   staling the surface a paying partner signs in to.

   WHY THIS FILE CHANGED (2026-09-02). It previously asked for --color-border,
   --color-surface-muted and --color-text-muted. shared/ui/tokens.css defines
   --color-line, --color-bg and --color-muted — those three names have never
   existed. A dead var() is invisible by construction: it renders, it just
   renders the hardcoded fallback, so every border, the page background and
   every muted string on this portal were hand-copied hexes of the RETIRED
   Florida AI Partners placeholder palette while the file read as token-driven.
   The repair is to delete the second definition, not to correct its spelling.

   The old header also claimed the page "respects the OS colour scheme through
   the tokens' dark block". It did not: that block is [data-theme="dark"]
   opt-in and nothing here ever set the attribute — and had anything set it,
   --color-text would have flipped light while the background kept falling
   back to the light #f5f7fc. Midnight Slate is a single dark rendering, which
   is what azuvyn.com serves, so there is now no second scheme to get wrong.
   color-scheme:dark below is what makes the browser's OWN controls — select
   popups, scrollbars, date pickers — arrive dark instead of white.

   ACCESSIBILITY (COMPLIANCE.md Critical Preserve 9, WCAG 2.2 AA) — unchanged
   in intent, re-measured against the new field:
     • focus is never removed, only restyled — :focus-visible carries a 2px
       outline with its own offset, on every interactive element;
     • state is never colour ALONE — every status pill carries a text label, so
       "live" and "failed" are distinguishable without perceiving hue;
     • prefers-reduced-motion removes the only transition in the file;
     • tap targets are 44px minimum;
     • every foreground/background pair below was measured, not assumed. The
       worst is muted text on a raised card at 4.91:1. Muted is deliberately
       never placed on --surface-highest (4.52:1 — passing, with no headroom
       for a future token nudge).
   ============================================================================= */

/* ARCHIVO + INTER — the Midnight pairing, per BRAND.md and the `typography`
   block of flagship-site/src/data/brand-tokens.json. This is what azuvyn.com
   actually serves: it renders data-site-version="midnight-marble" and loads
   Archivo, because `googleFontsHref(midnightMarble)` in flagship-site/src/lib/seo.ts
   branches to Archivo and midnight-marble.css sets --font-display to match. The
   Sora in flagship's BaseLayout is the OTHER branch of that conditional — the
   retired light shell — and reading it without following the branch is how this
   file briefly shipped Sora.

   ⚠ website-intake is on Sora and is therefore WRONG against the same spec.
   It is a public prospect-facing page, so that is worth fixing; it is not
   fixed here, and this comment exists so the next reader does not "correct"
   this file to match it.

   Fonts are SELF-HOSTED, and must stay that way even though this spoke ships no
   CSP today: the sibling surfaces (website-intake, the client cockpit) already
   self-host, and a Google Fonts URL here would make the portal the one Azuvyn
   surface that breaks the day a default-src lands. Inter is byte-identical to
   website-intake/public/fonts and analytics-reporting/public/fonts. Archivo is
   the v25 variable subset (400..900) from fonts.gstatic.com, cut on the SAME
   unicode-ranges as those files so the three stay swappable. */
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/fonts/archivo-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Archivo";
  font-style: normal;
  font-weight: 400 900;
  font-display: swap;
  src: url("/fonts/archivo-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/inter-latin.woff2") format("woff2");
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: "Inter";
  font-style: normal;
  font-weight: 400 700;
  font-display: swap;
  src: url("/fonts/inter-latin-ext.woff2") format("woff2");
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  /* Surfaces, darkest to lightest. --field is DARKER than the card it sits on:
     a recessed input reads as "type here" on dark, where a raised one reads as
     a chip. (Same reasoning as website-intake, which learned it first.) */
  --bg: var(--azuvyn-midnight-slate-background);
  --card: var(--azuvyn-midnight-slate-surface-raised);
  --card-raised: var(--azuvyn-midnight-slate-surface-highest);
  --field: var(--azuvyn-midnight-slate-surface);

  /* Text */
  --ink: var(--azuvyn-midnight-slate-text-display);
  --text: var(--azuvyn-midnight-slate-text-body);
  --strong: var(--azuvyn-midnight-slate-text-strong);
  --muted: var(--azuvyn-midnight-slate-text-muted);

  --line: var(--azuvyn-midnight-slate-border);
  --line-strong: var(--azuvyn-midnight-slate-border-strong);

  /* Glacial teal is the action signal — the one thing on the page that means
     "this does something". Nothing decorative may borrow it. */
  --brand: var(--azuvyn-midnight-slate-action);
  --brand-hover: var(--azuvyn-midnight-slate-action-hover);
  --on-brand: var(--azuvyn-midnight-slate-on-action);

  --ok: var(--azuvyn-midnight-slate-positive);

  /* ⚠ THE ONLY TWO BRAND HEXES IN THIS FILE, and they are here because Midnight
     Slate does not own them: brand-tokens.json's package roles carry `positive`
     and no warning/danger pair, while this portal must say "pending" and
     "stopped" about a live phone line. Values are not invented — the amber is
     the dark-block --color-warning from shared/ui/tokens.css and the red is the
     --err website-intake already ships on Midnight. Measured on the raised card
     (#18222b): 8.50:1 and 7.35:1, both AA. The right long-term fix is a
     warning/danger pair in brand-tokens.json's midnight-slate roles, at which
     point these two lines become var() like every other role above. */
  --warn: #e6b549;
  --err: #ff8f8f;

  --font: "Inter", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-display: "Archivo", ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;

  --radius: 14px;
  --radius-sm: 10px;

  /* Tells the browser to render its OWN controls dark — the select popup, the
     scrollbar, the autofill sheet. Without it those arrive white on a black
     page. */
  color-scheme: dark;
}

* { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  font-family: var(--font);
  line-height: 1.55;
  color: var(--text);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
}

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--brand);
  color: var(--on-brand);
  font-weight: 600;
  padding: 12px 16px;
  border-radius: var(--radius-sm);
  z-index: 100;
}
.skip:focus { left: 8px; top: 8px; }

.wrap { max-width: 900px; margin: 0 auto; padding: 20px 16px 64px; }

/* ── Header ────────────────────────────────────────────────────────────────
   The lockup is the founder-picked Midnight variant (mineral white art with the
   glacial-teal slash) — the same file flagship-site serves when Midnight Marble
   is live, so a partner arriving from azuvyn.com meets the identical mark. */
header.bar {
  display: flex;
  flex-wrap: wrap;
  gap: 10px 16px;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--line);
  padding-bottom: 16px;
  margin-bottom: 22px;
}
.brand-lockup { display: flex; align-items: center; gap: 12px; min-width: 0; }
.brand-lockup img { height: 30px; width: auto; display: block; flex: none; }
header.bar h1 {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 0;
  color: var(--ink);
}
header.bar .who { font-size: 0.85rem; color: var(--muted); }

h2 {
  font-family: var(--font-display);
  font-size: 1.15rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin: 28px 0 10px;
  color: var(--ink);
}
h3 {
  font-family: var(--font-display);
  font-size: 0.95rem;
  font-weight: 600;
  margin: 0 0 8px;
  color: var(--strong);
}
p { margin: 0 0 12px; }
.lede { color: var(--strong); }
.small { font-size: 0.85rem; color: var(--muted); }
a { color: var(--brand); }
a:hover { color: var(--brand-hover); }
code {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.9em;
  background: var(--field);
  border: 1px solid var(--line);
  border-radius: 5px;
  padding: 1px 5px;
}

.card {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 16px;
  margin: 0 0 14px;
}

/* Every state carries its LABEL as text. Colour is reinforcement, never the
   signal — a reader who cannot distinguish hue still reads the word. */
.pill {
  display: inline-block;
  font-size: 0.75rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  padding: 3px 9px;
  border-radius: 999px;
  border: 1px solid currentColor;
}
.pill.live { color: var(--ok); }
.pill.pending { color: var(--warn); }
.pill.stopped { color: var(--err); }
.pill.neutral { color: var(--muted); }
/* The demo's own pill. Filled rather than outlined, because it is not a STATE
   in the way the four above are — it is what the thing IS, and it never comes
   off. Deliberately not green: `live` is the vocabulary of a customer's line
   and a demo wearing it is the one confusion this surface has to refuse. */
.pill.demo {
  color: var(--on-brand);
  background: var(--brand);
  border-color: var(--brand);
}

label { display: block; font-weight: 600; font-size: 0.9rem; margin: 12px 0 4px; color: var(--strong); }
.hint { font-weight: 400; font-size: 0.82rem; color: var(--muted); margin: 2px 0 6px; }

/* `time` joined this list with the opening-hours editor (2026-09-15). A type
   left out of it renders as a bare UA control — 30px tall, its own font, none
   of the field tokens — which is exactly how a new input type ships looking
   broken next to every other one on the same card. */
input[type="search"], input[type="text"], input[type="email"], input[type="tel"], input[type="url"], input[type="password"], input[type="time"], select, textarea {
  width: 100%;
  min-height: 44px;
  padding: 10px 12px;
  font: inherit;
  /* 16px minimum on the actual control, or iOS zooms the whole page on focus —
     a reseller filling this in on a phone loses their place every field. */
  font-size: max(1rem, 16px);
  color: var(--ink);
  background: var(--field);
  border: 1px solid var(--control-border);
  border-radius: var(--radius-sm);
}
input::placeholder, textarea::placeholder { color: var(--muted); }
textarea { min-height: 88px; resize: vertical; font-family: var(--font); }

.check { display: flex; gap: 10px; align-items: flex-start; margin: 12px 0; }
.check input { width: 20px; height: 20px; margin-top: 3px; flex: none; accent-color: var(--brand); }
.check label { margin: 0; font-weight: 400; color: var(--text); }

/* Standalone download/request anchors share the button's 44px target and
   tokens; an anchor with class="button" must not render as bare inline text. */
button, a.button {
  min-height: 44px;
  padding: 10px 18px;
  font: inherit;
  font-family: var(--font);
  font-weight: 600;
  border-radius: var(--radius-sm);
  border: 1px solid transparent;
  background: var(--brand);
  color: var(--on-brand);
  cursor: pointer;
  transition: background-color 0.15s ease;
}
a.button { display: inline-flex; align-items: center; justify-content: center; text-decoration: none; }
button:hover, a.button:hover { background: var(--brand-hover); }
button.secondary { background: transparent; color: var(--brand); border-color: currentColor; }
button.secondary:hover { background: rgba(95, 198, 184, 0.1); }
button.danger { background: transparent; color: var(--err); border-color: currentColor; }
button[disabled] { opacity: 0.5; cursor: not-allowed; }
button[disabled]:hover { background: var(--brand); }
button.secondary[disabled]:hover, button.danger[disabled]:hover { background: transparent; }

:focus-visible { outline: 2px solid var(--brand); outline-offset: 2px; }

.row { display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px; }

/* The 4px rail carries the state at full strength; the wash behind it is
   reinforcement only. color-mix is used so no generated hex is re-spelled here
   — on a browser too old for it the declaration drops, the wash is simply
   absent, and the rail plus the notice's own text still carry the meaning. */
.notice { border-left: 4px solid; padding: 10px 12px; border-radius: 0 var(--radius-sm) var(--radius-sm) 0; margin: 0 0 14px; }
.notice.info { border-color: var(--brand); background: color-mix(in srgb, var(--brand) 12%, transparent); }
.notice.warn { border-color: var(--warn); background: color-mix(in srgb, var(--warn) 12%, transparent); }
.notice.error { border-color: var(--err); background: color-mix(in srgb, var(--err) 12%, transparent); }
.notice.ok { border-color: var(--ok); background: color-mix(in srgb, var(--ok) 12%, transparent); }

/* A wide table must scroll INSIDE its own box — the page body never scrolls
   sideways on a phone. */
.scroll-x { overflow-x: auto; -webkit-overflow-scrolling: touch; }
table { border-collapse: collapse; width: 100%; font-size: 0.9rem; }
th, td { text-align: left; padding: 8px 10px; border-bottom: 1px solid var(--line); }
th { font-size: 0.78rem; text-transform: uppercase; letter-spacing: 0.04em; color: var(--muted); font-weight: 600; }
td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }

.steps { list-style: none; margin: 8px 0 0; padding: 0; font-size: 0.88rem; }
.steps li { display: flex; gap: 8px; padding: 4px 0; }
.steps .mark { flex: none; width: 1.4em; font-weight: 700; color: var(--brand); }

/* ── Navigation ────────────────────────────────────────────────────────────
   Underline tabs, NOT filled pills. The rule this file states above — "glacial
   teal is the action signal; nothing decorative may borrow it" — was being
   broken by the selected tab, which painted itself the same solid teal as the
   submit button. On a screen whose one primary action is "Launch this line",
   a tab that outshouts it teaches the wrong thing to press. Selection is now
   carried by the rail underneath and by weight/colour of the label, and teal
   appears on the underline only: still unmistakably the brand, no longer
   impersonating a button.

   The rail scrolls horizontally rather than wrapping. Four items wrap to two
   rows at 360px, and a nav that changes height when you change tab makes the
   content jump under your thumb. */
nav.tabs {
  display: flex;
  gap: 2px;
  margin-bottom: 20px;
  border-bottom: 1px solid var(--line);
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
nav.tabs::-webkit-scrollbar { display: none; }
/* The scrollbar is hidden, so something else has to say the rail continues.
   Below the breakpoint the four labels cannot fit any column this surface
   targets — "Usage & billing" alone is half of 360px — so the fade is
   unconditional there rather than guessed at per width. Above it they fit and
   there is nothing to hint at. */
@media (max-width: 699px) {
  nav.tabs {
    mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
    -webkit-mask-image: linear-gradient(to right, #000 calc(100% - 26px), transparent 100%);
  }
}
nav.tabs button {
  flex: none;
  background: transparent;
  color: var(--muted);
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  font-weight: 600;
  font-size: 0.92rem;
  padding: 10px 14px;
  white-space: nowrap;
  /* The 44px target is kept by min-height on the base button rule; the visual
     underline sits at the bottom of it. */
  transition: color 0.15s ease, border-color 0.15s ease;
}
nav.tabs button:hover { background: transparent; color: var(--ink); }
nav.tabs button[aria-current="page"] {
  background: transparent;
  color: var(--ink);
  border-bottom-color: var(--brand);
}
nav.tabs button[aria-current="page"]:hover { background: transparent; color: var(--ink); }
/* The global ring is drawn OUTSIDE the element; on a tab flush to the rail
   that clips against the neighbour, so this one is inset. */
nav.tabs button:focus-visible { outline-offset: -2px; }

/* A count that means "this many need YOU" is worth carrying in the nav — it is
   the only way the number is visible from a tab you are not on. It is a NUMBER
   plus its accessible name, never a bare coloured dot: a dot says something is
   different without saying what, which is the state a partner cannot act on. */
.tab-count {
  display: inline-block;
  min-width: 20px;
  margin-left: 7px;
  padding: 1px 6px;
  border-radius: 999px;
  font-size: 0.72rem;
  font-weight: 700;
  font-variant-numeric: tabular-nums;
  background: var(--card-raised);
  color: var(--muted);
}
.tab-count.attention { background: color-mix(in srgb, var(--warn) 22%, transparent); color: var(--warn); }

.hidden { display: none !important; }

footer {
  margin-top: 36px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  font-size: 0.82rem;
  color: var(--muted);
}

@media (min-width: 700px) {
  .wrap { padding: 32px 24px 72px; }
  .brand-lockup img { height: 34px; }
  header.bar h1 { font-size: 1.15rem; }
  .grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; }
}

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

/* =============================================================================
   COMPONENTS — the layer above the branding.
   -----------------------------------------------------------------------------
   Everything below is structure and hierarchy: what a partner sees FIRST, what
   they can act on, and what is merely available. It defines no brand hex — the
   roles mapped at the top of this file are the only source, so a brand-token
   move still moves this section with it.

   The governing idea is TRIAGE. A reseller opens this portal because one of
   their customers' lines needs something, and the old surface answered that
   question only by reading every card top to bottom. The summary strip, the
   attention grouping and the journey rail exist to answer it above the fold.
   ============================================================================= */

/* Visible to assistive technology, not to the eye. Used where a visual signal
   (a filled dot on the journey rail) carries meaning that must also exist as
   words — clipped rather than display:none, which would remove it from the
   accessibility tree along with the screen. */
.sr {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ── Summary strip ─────────────────────────────────────────────────────────
   Three facts, derived from the SAME /lines payload the list below renders —
   never a second fetch, and never a second opinion. The money tile is absent
   unless the server supplied a figure; this file has no rate table for the
   same reason app.js has none. */
.summary {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(148px, 1fr));
  gap: 10px;
  margin: 0 0 18px;
}
.stat {
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  padding: 13px 15px;
}
.stat .k {
  display: block;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
  margin-bottom: 5px;
}
.stat .v {
  display: block;
  font-family: var(--font-display);
  font-size: 1.6rem;
  font-weight: 600;
  line-height: 1.1;
  letter-spacing: -0.02em;
  color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.stat .sub { display: block; font-size: 0.8rem; color: var(--muted); margin-top: 3px; }
/* Attention is carried by the WORD in .k and by the value's colour together —
   the tile still reads correctly with hue removed. */
.stat.attention { border-color: color-mix(in srgb, var(--warn) 45%, var(--line)); }
.stat.attention .v { color: var(--warn); }

/* ── Section headings inside a tab ─────────────────────────────────────────
   A group heading carries its own count so "Needs you (2)" is legible without
   counting cards. */
.group-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 12px;
  margin: 26px 0 10px;
}
.group-head:first-child { margin-top: 0; }
.group-head h2 { margin: 0; }
.group-head .count { font-size: 0.82rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* ── Line card ─────────────────────────────────────────────────────────────
   A 3px state rail down the left edge. It is REINFORCEMENT, never the signal:
   the pill's word is still the thing that says "test call needed", and the rail
   only lets you find that card again after you have scrolled past it. */
.line {
  position: relative;
  overflow: hidden;
  padding-left: 18px;
}
.line::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 3px;
  background: var(--line-strong);
}
.line.live::before { background: var(--ok); }
.line.pending::before { background: var(--warn); }
.line.stopped::before { background: var(--err); }

.line-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px 12px;
  margin-bottom: 6px;
}
.line-title {
  font-family: var(--font-display);
  font-size: 1.05rem;
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--ink);
  margin: 0;
  min-width: 0;
  overflow-wrap: anywhere;
}
/* The number a partner's CUSTOMER dials is the second most useful fact on the
   card and used to be the fourth line of grey small print. Tabular figures so a
   column of them is scannable. */
.line-number {
  font-variant-numeric: tabular-nums;
  font-size: 0.95rem;
  color: var(--strong);
  margin: 0 0 6px;
}
.line-number .k { color: var(--muted); font-size: 0.82rem; margin-right: 6px; }
/* The slug IS machine-facing, so it is typeset as machine-facing rather than
   given the same weight as a sentence. */
.line-slug {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.76rem;
  color: var(--muted);
  margin: 0 0 8px;
  overflow-wrap: anywhere;
}
.line-detail { color: var(--text); margin: 0 0 10px; }

/* ── Actions: one primary, the rest behind a disclosure ────────────────────
   Offboard used to sit inline beside "I made the test call", the same size, one
   gap apart. Both are one tap; only one of them ends a customer's service. The
   destructive pair now lives behind "More", which is a deliberate extra tap and
   the only protection this surface can offer that does not depend on reading. */
.line-actions { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; margin-top: 12px; }
.line-actions .spacer { flex: 1 1 auto; }
button.ghost, a.button.ghost {
  background: transparent;
  color: var(--muted);
  border-color: var(--line-strong);
  font-weight: 600;
}
button.ghost:hover, a.button.ghost:hover { background: var(--card-raised); color: var(--ink); }
.more-panel {
  margin-top: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  width: 100%;
}
.more-panel .row { margin-top: 0; }

/* An in-surface confirmation, replacing window.confirm(). The native dialog is
   the one element on the page the branding cannot reach, and it renders the
   consequence in the OS font on a white sheet — which is exactly where a
   partner stops reading. */
.confirm-box {
  border: 1px solid color-mix(in srgb, var(--err) 45%, var(--line));
  background: color-mix(in srgb, var(--err) 8%, transparent);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-top: 10px;
  width: 100%;
}
.confirm-box p { margin: 0 0 10px; font-size: 0.88rem; }
.confirm-box .row { margin-top: 0; }

/* ── Journey rail ──────────────────────────────────────────────────────────
   Provisioning IS linear, and the old surface only ever drew it after a partial
   FAILURE — so the one moment you could see where you were was the moment
   something had already gone wrong. It is drawn for every line that is not yet
   live.

   Each step carries its own word. The dot's fill is reinforcement; a reader who
   cannot separate the hues still reads the labels and the "done / now / next"
   text that .journey li carries for assistive technology. */
.journey { list-style: none; display: flex; margin: 12px 0 0; padding: 0; gap: 0; }
.journey li {
  flex: 1 1 0;
  min-width: 0;
  position: relative;
  padding-top: 20px;
  text-align: center;
  font-size: 0.66rem;
  line-height: 1.25;
  color: var(--muted);
  /* NOT overflow-wrap: anywhere. These labels are short enough to break at
     their spaces, and `anywhere` broke "Provisioned" mid-word into
     "Provisione / d" on a 360px column — a word split that reads as a
     rendering fault rather than a wrap. */
  hyphens: none;
}
/* The connector is drawn as a bar behind the dots rather than a border on the
   li, so the first and last steps do not grow half-connectors off the ends. */
.journey li::before {
  content: "";
  position: absolute;
  top: 5px;
  left: 0;
  right: 0;
  height: 2px;
  background: var(--line);
}
.journey li:first-child::before { left: 50%; }
.journey li:last-child::before { right: 50%; }
.journey li::after {
  content: "";
  position: absolute;
  top: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 12px;
  height: 12px;
  border-radius: 999px;
  background: var(--bg);
  border: 2px solid var(--line-strong);
}
.journey li.done::before { background: var(--ok); }
.journey li.done::after { background: var(--ok); border-color: var(--ok); }
.journey li.done { color: var(--text); }
.journey li.now::after { background: var(--brand); border-color: var(--brand); }
.journey li.now { color: var(--ink); font-weight: 700; }
.journey li.failed::after { background: var(--err); border-color: var(--err); }
.journey li.failed { color: var(--err); font-weight: 700; }

/* ── Empty state ───────────────────────────────────────────────────────────
   The old empty state was one grey sentence naming a tab. Naming a control is
   not offering it. */
.empty {
  text-align: center;
  padding: 34px 20px;
  border: 1px dashed var(--line-strong);
  border-radius: var(--radius);
  background: var(--card);
}
.empty h2 { margin: 0 0 8px; }
.empty p { color: var(--muted); max-width: 46ch; margin: 0 auto 16px; }

/* ── Skeletons ─────────────────────────────────────────────────────────────
   "Loading…" is a word where a shape belongs: it tells you nothing about what
   is coming, and on a slow connection it is indistinguishable from a surface
   that has finished and found nothing. The global prefers-reduced-motion block
   at the end of this file already stops the shimmer. */
.sk { background: var(--card); border: 1px solid var(--line); border-radius: var(--radius); padding: 16px; margin: 0 0 14px; }
.sk-bar {
  height: 11px;
  border-radius: 6px;
  margin: 0 0 10px;
  background: linear-gradient(90deg, var(--card-raised) 25%, var(--field) 37%, var(--card-raised) 63%);
  background-size: 400% 100%;
  animation: sk-shimmer 1.4s ease-in-out infinite;
}
.sk-bar:last-child { margin-bottom: 0; }
.sk-bar.w40 { width: 40%; }
.sk-bar.w60 { width: 60%; }
.sk-bar.w85 { width: 85%; }
.sk-bar.tall { height: 20px; }
@keyframes sk-shimmer {
  from { background-position: 100% 0; }
  to { background-position: 0 0; }
}

/* ── Segmented choice ──────────────────────────────────────────────────────
   Two mutually exclusive branches, one of which ends in us buying a phone
   number. A <select> hides the alternative behind a tap; radios show both
   choices and which one is taken, which is what a decision with a cost should
   look like. */
/* A <fieldset>, because the two radios are one question and a legend is the
   only element that names a radio group to a screen reader. Its own border and
   padding are removed — the grouping is carried by the legend and by the two
   bordered options, not by a box around a box. */
fieldset.segmented { border: 0; padding: 0; margin: 12px 0 4px; min-width: 0; }
fieldset.segmented > legend {
  padding: 0;
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--strong);
  margin-bottom: 6px;
}
.segmented { display: grid; gap: 8px; margin: 6px 0 4px; }
.segmented .opt {
  display: flex;
  gap: 10px;
  align-items: flex-start;
  padding: 12px 14px;
  border: 1px solid var(--line-strong);
  border-radius: var(--radius-sm);
  background: var(--field);
  cursor: pointer;
}
.segmented .opt input { width: 20px; height: 20px; margin: 2px 0 0; flex: none; accent-color: var(--brand); }
.segmented .opt:hover { border-color: var(--brand); }
.segmented .opt:has(input:checked) { border-color: var(--brand); background: color-mix(in srgb, var(--brand) 10%, var(--field)); }
.segmented .opt:has(input:focus-visible) { outline: 2px solid var(--brand); outline-offset: 2px; }
.segmented .opt .t { display: block; font-weight: 600; color: var(--ink); font-size: 0.92rem; }
.segmented .opt .d { display: block; font-size: 0.82rem; color: var(--muted); margin-top: 2px; }

/* ── The intake wizard ─────────────────────────────────────────────────────
   The same four boxes as before, in the same order, carrying the same fields —
   shown one at a time. Twenty inputs in one scroll is not a form, it is a
   document; a reseller filling it in beside their customer needs to know how
   much is left, and a validation failure needs to name a step rather than a
   position in a wall. */
.wizard-head { margin: 0 0 16px; }
.wizard-progress {
  display: flex;
  gap: 6px;
  margin: 12px 0 6px;
}
.wizard-progress span {
  flex: 1 1 0;
  height: 4px;
  border-radius: 999px;
  background: var(--line);
}
.wizard-progress span.done { background: var(--ok); }
.wizard-progress span.now { background: var(--brand); }
.wizard-step-label { font-size: 0.8rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* The step panel is focused on arrival so assistive technology announces the
   new step, and is given tabindex="-1" to make that possible. It is NOT an
   interactive control, so it takes no ring — the accessibility rule this file
   states ("focus is never removed, only restyled") is about things you can
   operate, and drawing a 2px outline around an entire card would read as an
   error state on every Next. Every real control inside it keeps its ring. */
.step:focus,
.step:focus-visible { outline: none; }

.wizard-nav {
  display: flex;
  gap: 8px;
  align-items: center;
  margin-top: 16px;
  padding-top: 14px;
  border-top: 1px solid var(--line);
  margin-bottom: 14px;
}
.wizard-nav .spacer { flex: 1 1 auto; }

/* The review step. A <dl> because it is genuinely term/definition, and because
   it lets a long value wrap under its own label on a 360px column. */
.review { margin: 0; }
.review > div {
  display: grid;
  gap: 2px 16px;
  padding: 9px 0;
  border-bottom: 1px solid var(--line);
}
.review > div:last-child { border-bottom: 0; }
.review dt { font-size: 0.78rem; font-weight: 600; letter-spacing: 0.04em; text-transform: uppercase; color: var(--muted); }
.review dd { margin: 0; color: var(--ink); overflow-wrap: anywhere; }
.review dd.unset { color: var(--muted); font-style: italic; }

/* ── Usage & billing / Account details ─────────────────────────────────────
   Added with the second-pass rebuild of those two tabs. Same contract as the
   rest of this layer: structure only, no brand hex, and state always carries
   its word (the charge and member pills reuse .pill, whose label IS the
   server's own status string). */

/* A card heading with its status pill beside it — the same shape .line-head
   gives a line card, for cards that are not lines. */
.card-head {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  justify-content: space-between;
  gap: 8px 12px;
  margin-bottom: 6px;
}
.card-head h3 { margin: 0; }

/* Machine-facing strings — slugs, SIDs, ledger keys, charge kinds. Typeset as
   machine-facing (the .line-slug rule, generalised) so a partner's eye can
   separate "words we wrote" from "identifiers the system owns". */
.mono {
  font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  font-size: 0.82rem;
  overflow-wrap: anywhere;
}
/* In a table the column may NOT crush — the table lives in .scroll-x, and a
   slug shredded one character per line to fit 375px reads as a fault. Let it
   keep its width and let the box scroll, which is what the box is for. */
td.mono { color: var(--muted); overflow-wrap: normal; white-space: nowrap; }

/* ── A DERIVED ANSWER, STATED ──────────────────────────────────────────────
   The intake works out the receptionist's area code from the number the
   business already uses, so what would have been a field is a sentence. It is
   given a left rule and the field background rather than being left as body
   copy, because it IS an answer to a question and has to read as one — a
   derived value that looks like prose is a value nobody notices was wrong.
   Deliberately NOT `.fact`: that one is a label/value pair with an uppercase
   key, and this is a sentence. */
.derived {
  margin: 6px 0 10px;
  padding: 10px 12px;
  border-left: 3px solid var(--brand);
  background: var(--field);
  border-radius: 0 8px 8px 0;
  font-size: 0.88rem;
  line-height: 1.55;
  color: var(--strong);
}
.derived:empty { display: none; }

/* ── The advanced disclosure ───────────────────────────────────────────────
   Bringing your own numbers is a real case and a rare one. It sits behind a
   summary for the reason the front door's signup does: hierarchy carried by the
   tap it costs, not by colour. Same marker treatment, because two disclosures
   on one surface that open differently read as two different controls. */
details.advanced {
  margin: 18px 0 0;
  border-top: 1px solid var(--line);
  padding-top: 8px;
}
details.advanced > summary {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 10px;
  font-size: 0.88rem;
  color: var(--muted);
}
details.advanced > summary::after { content: "▾"; margin-left: auto; color: var(--muted); }
details.advanced[open] > summary::after { content: "▴"; }
details.advanced > summary:hover { color: var(--ink); }
/* The status line carries a button when the account is not connected, so it is
   a row rather than a paragraph. */
#ln-own-status { display: flex; flex-wrap: wrap; align-items: center; gap: 8px; }

/* ── The demo receptionist ─────────────────────────────────────────────────
   A line card's left rail is SOLID and coloured by state. The demo's is
   HATCHED and always the same, which is the point: it is not a state, it is a
   different kind of object, and the two cards must not be mistaken for each
   other at a glance on a phone.

   Colour carries none of this on its own — the card says "Demo" in a filled
   pill, and the standing sentence under the title says what that means — so a
   reader who cannot see the rail loses nothing. Everything here is a var();
   no hex is re-spelled. */
.demo-card {
  position: relative;
  overflow: hidden;
  padding-left: 18px;
  border-color: color-mix(in srgb, var(--brand) 40%, var(--line));
}
.demo-card::before {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  bottom: 0;
  width: 4px;
  background: repeating-linear-gradient(
    135deg,
    var(--brand) 0 4px,
    transparent 4px 8px
  );
}
/* The value half of a .fact on this card is a partner's own words (a business
   name they typed) rather than an identifier, so it is set in the body face —
   .mono would tell them the system owns the string. */
.demo-value { color: var(--strong); overflow-wrap: anywhere; }

/* A read-only fact: label, then value. NOT a disabled input — a fact that
   cannot be edited should not look like a field that refuses. */
.fact {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 2px 10px;
  margin: 0 0 8px;
}
.fact .k {
  flex: none;
  font-size: 0.74rem;
  font-weight: 600;
  letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--muted);
}
.fact .mono { color: var(--strong); }

/* The team roster. Each row is one person: identity left, role and status
   right, 44px minimum like every other target on this surface. */
ul.members { list-style: none; margin: 0 0 6px; padding: 0; }
.members li {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 4px 10px;
  min-height: 44px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.members li:last-child { border-bottom: 0; }
.member-id { display: flex; flex-direction: column; min-width: 0; margin-right: auto; }
.member-name { color: var(--ink); font-weight: 600; overflow-wrap: anywhere; }
.member-email { font-size: 0.8rem; color: var(--muted); overflow-wrap: anywhere; }
.member-role { font-size: 0.82rem; color: var(--muted); }

/* The server's notCovered list. A plain bulleted list, tightened — the .steps
   check-mark list it replaced implied each boundary was a completed step. */
ul.plain { margin: 8px 0 0; padding-left: 20px; font-size: 0.88rem; }
ul.plain li { margin: 0 0 6px; color: var(--text); }

/* The agreement, presented for acceptance. Text, never markup — the class
   replaces an inline style so the one legal document on the surface is styled
   from the same sheet as everything else. */
.agreement-body {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  font-family: var(--font);
  font-size: 0.9rem;
  line-height: 1.6;
  margin: 0;
}

/* ── The front door's two doors ────────────────────────────────────────────
   Sign-in is the hero — nearly everyone arriving here already has an account —
   and signup is the quieter path behind a native disclosure. Hierarchy is
   carried by size and by the tap the disclosure costs, not by teal: the one
   teal element on the page stays the sign-in submit button. */
.door-hero { padding: 22px 18px; }
.door-hero > h2 { margin-top: 0; font-size: 1.35rem; }
.door-alt > summary {
  cursor: pointer;
  min-height: 44px;
  display: flex;
  align-items: center;
  gap: 10px;
  color: var(--muted);
}
/* display:flex removes the native marker, so the affordance is drawn back by
   hand — an expandable row with no marker is a sentence, not a control. */
.door-alt > summary::after { content: "▾"; margin-left: auto; color: var(--muted); }
.door-alt[open] > summary::after { content: "▴"; }
.door-alt > summary h2 { margin: 0; font-size: 0.95rem; color: var(--strong); }
.door-alt > summary:hover h2 { color: var(--ink); }
.door-alt[open] > summary { margin-bottom: 4px; }

@media (min-width: 700px) {
  .stat .v { font-size: 1.75rem; }
  .journey li { font-size: 0.76rem; }
  .review > div { grid-template-columns: 200px 1fr; align-items: baseline; }
  .review dt { padding-top: 1px; }
  .door-hero { padding: 28px 24px; }
}

/* ── What this line's receptionist knows (E7) ──────────────────────────────
   Appended rather than woven in: this panel hangs off the line card, borrows
   the card's own tokens, and shares `.more-panel`'s divider treatment so a
   partner reads the two disclosures as the same kind of thing. Nothing here
   introduces a colour — every value resolves to a Midnight Slate token, so the
   panel follows a palette change without being edited. */
.brain-panel {
  margin-top: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  width: 100%;
}
.brain-panel > .small:first-child { margin-top: 0; }
.brain-group {
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  padding: 12px 14px;
  margin-top: 12px;
  background: var(--field);
}
.brain-group h4 {
  margin: 0;
  font-family: var(--font-display);
  font-size: 0.95rem;
  color: var(--ink);
}
.brain-group > .small { margin: 4px 0 10px; }
.brain-empty { color: var(--muted); font-size: 0.88rem; margin: 0 0 10px; }

/* One stored item. The text is the row's whole point, so the two controls sit
   to the side on a wide screen and drop beneath it on a phone rather than
   competing with it for the first line. */
.brain-entry {
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.brain-entry .row { margin-top: 0; }
.brain-body p { margin: 0; }
.brain-q { font-weight: 600; color: var(--strong); font-size: 0.92rem; }
.brain-a, .brain-text { font-size: 0.92rem; color: var(--text); }
.brain-a { margin-top: 2px !important; }

.brain-form {
  border-top: 1px solid var(--line);
  padding-top: 10px;
  margin-top: 10px;
}
.brain-form label:first-of-type { margin-top: 0; }
.brain-form .row { margin-top: 12px; }
/* The server's refusal, at the top of the form that caused it — a reason
   rendered anywhere else is a reason read after the partner has given up. */
.brain-error { margin-bottom: 10px; }

@media (min-width: 700px) {
  .brain-entry {
    flex-direction: row;
    /* WRAP is load-bearing, not tidiness: the edit form is appended as a THIRD
       child of this row, and without wrapping it would be squeezed into the
       controls' column instead of taking a line of its own. */
    flex-wrap: wrap;
    align-items: flex-start;
    justify-content: space-between;
    gap: 16px;
  }
  .brain-entry > .row { flex: 0 0 auto; }
  /* The form is a child of the entry when editing, so it must break the row
     back into a column or the fields render in the controls' narrow column. */
  .brain-entry .brain-form { flex-basis: 100%; }
}

/* ── The calls this line has taken (§2(d)) ─────────────────────────────────
   Hangs off the line card exactly as `.brain-panel` does, and borrows the same
   divider treatment so a partner reads the two disclosures as one kind of
   thing. No colour is introduced: every value resolves to a Midnight Slate
   token, so a palette change reaches this panel without editing it. */
.calls-panel {
  margin-top: 10px;
  padding-top: 12px;
  border-top: 1px solid var(--line);
  width: 100%;
}
.calls-panel > .small:first-child { margin-top: 0; }
.call-empty { color: var(--muted); font-size: 0.88rem; margin: 10px 0 0; }

/* One call. The outcome is the line a reseller triages on, so it leads and
   carries the weight; everything else is subordinate to it. */
.call-entry {
  padding: 10px 0;
  border-top: 1px solid var(--line);
}
.call-entry p { margin: 0; }
.call-outcome {
  font-weight: 600;
  color: var(--strong);
  font-size: 0.92rem;
  display: flex;
  gap: 8px;
  align-items: baseline;
}
.call-emoji { flex: 0 0 auto; }
.call-who { font-size: 0.92rem; color: var(--text); margin-top: 2px !important; }
.call-meta { font-size: 0.82rem; color: var(--muted); margin-top: 2px !important; }
.call-summary { font-size: 0.9rem; color: var(--text); margin-top: 6px !important; }
.call-entry .row { margin-top: 8px; }

/* The transcript. `pre` because a transcript's own line breaks are the turn
   structure — reflowing it into a paragraph destroys who said what — but it
   WRAPS, because a phone is 375px wide and a horizontally scrolling block of
   speech is a block nobody reads. */
.call-transcript { margin-top: 8px; }
.call-body {
  margin: 0;
  padding: 10px 12px;
  border: 1px solid var(--line);
  border-radius: var(--radius-sm);
  background: var(--field);
  font-family: inherit;
  font-size: 0.88rem;
  line-height: 1.5;
  color: var(--text);
  white-space: pre-wrap;
  overflow-wrap: anywhere;
  max-height: 420px;
  overflow-y: auto;
}

/* The route's own boundary statements. Small, but never hidden: two of them
   explain a number that will not match the Usage tab, and a partner who cannot
   see that explanation will ask about it as a defect. */
.call-notes {
  margin: 14px 0 0;
  padding-left: 18px;
  font-size: 0.8rem;
  color: var(--muted);
}
.call-notes li { margin-top: 4px; }

/* §7's support line in the app shell footer. Its own rule only so the link is
   legible against the muted footer text — the address is the actionable half. */
footer .support { margin-top: 8px; }
footer .support a { color: var(--strong); }

/* =============================================================================
   THE CONCIERGE DOCK (E8)
   -----------------------------------------------------------------------------
   A corner dock, because the concierge is a companion to whatever screen the
   partner is already on — a tab would have made it a destination, and a
   question about the Usage table is asked while looking at the Usage table.

   IT DEFINES NO BRAND HEX, like everything else below the mapping block. The
   two colour pairs it introduces are existing roles: the partner's own turn
   sits on --field (the recessed surface, the same one their typing already
   sits on) and the concierge's on --card-raised.

   ACCESSIBILITY, measured against the same bar as the rest of the file:
     • the launcher and every control clear 44px;
     • which side a bubble sits on is decoration — each one carries a visible
       "You" / "Concierge" label, so the speaker survives a screen reader and a
       flattened layout;
     • muted text is never placed on --card-raised (4.52:1, no headroom); the
       muted strings here sit on --card and --field;
     • the found-card ring is a 2px outline, not a colour wash, and the global
       prefers-reduced-motion block above removes its transition;
     • on a ~360px column the panel takes the full width minus the gutters —
       a reseller standing in front of their customer is the case that has to
       work, and a fixed-width chat window is the one that does not.
   ============================================================================= */
.cc-dock {
  position: fixed;
  right: 12px;
  bottom: 12px;
  z-index: 40;
  display: flex;
  flex-direction: column;
  align-items: flex-end;
  gap: 10px;
  /* The DOCK does not take pointer events — only its children do. Otherwise an
     invisible column down the side of the page would swallow clicks on the
     line cards behind it. */
  pointer-events: none;
}
.cc-dock > * { pointer-events: auto; }

.cc-launcher {
  box-shadow: 0 6px 20px rgba(0, 0, 0, 0.35);
}

/* ⚠️ LOAD-BEARING, and it shipped wrong once. `display: flex` on a class beats
   the user agent's `[hidden] { display: none }`, so without this rule the panel
   is OPEN on every page load however faithfully the JavaScript sets `hidden` —
   the toggle appears to do nothing and the dock covers the line cards. Every
   `hidden`-toggled element that is given a display in this file needs its own
   line like this one. */
.cc-panel[hidden] { display: none; }

.cc-panel {
  width: min(380px, calc(100vw - 24px));
  max-height: min(70vh, 560px);
  display: flex;
  flex-direction: column;
  background: var(--card);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
  padding: 14px;
  gap: 10px;
}

.cc-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; }
.cc-head h2 { margin: 0; font-family: var(--font-display); font-size: 1.05rem; color: var(--ink); }
.cc-close { min-height: 36px; padding: 6px 12px; }

/* The sentence that says what this thing is NOT. It is the first thing in the
   panel and it is not small print: a partner who believes the concierge can
   launch a line will ask it to, and the honest answer is cheaper given once,
   up front, than in every refusal. */
.cc-lede { margin: 0; font-size: 0.82rem; color: var(--muted); }

.cc-log {
  flex: 1 1 auto;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 8px;
  padding-right: 4px;
  overflow-wrap: anywhere;
}

.cc-turn { border-radius: var(--radius-sm); padding: 8px 10px; }
.cc-turn p { margin: 4px 0 0; font-size: 0.88rem; color: var(--text); white-space: pre-wrap; }
.cc-who {
  display: block;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--muted);
}
.cc-user { background: var(--field); }
.cc-assistant { background: var(--card-raised); }
.cc-assistant p { color: var(--strong); }
.cc-error { border: 1px solid var(--err); }
.cc-error p { color: var(--err); margin-top: 0; }
.cc-note p { color: var(--warn); margin-top: 0; font-size: 0.82rem; }

.cc-suggest { display: flex; flex-wrap: wrap; gap: 6px; }
.cc-suggest button { min-height: 36px; padding: 6px 10px; font-size: 0.8rem; font-weight: 500; }

.cc-status { margin: 0; font-size: 0.78rem; color: var(--muted); min-height: 1.1em; }

.cc-compose { display: flex; gap: 8px; align-items: flex-end; }
.cc-compose textarea {
  flex: 1 1 auto;
  margin: 0;
  min-height: 44px;
  resize: vertical;
}
.cc-compose button { min-height: 44px; padding: 10px 16px; }

/* The card the concierge just walked the partner to. A ring rather than a
   background change, so it reads on top of every status class a card can
   already be wearing. */
.card.cc-found { outline: 2px solid var(--brand); outline-offset: 3px; }

/* Review refinements: control edges are distinct from decorative dividers. */
:root { --control-border: var(--muted); }
input[aria-invalid="true"], select[aria-invalid="true"], textarea[aria-invalid="true"] { border-color: var(--err); }
.field-error { color: var(--err); margin: 6px 0 14px; font-size: .9rem; }
/* An error inside one of the two grid rows spans the row rather than becoming a
   third column squeezed beside the control it is about. `fieldError` inserts it
   directly after the input, so it lands inside the grid either way. */
.offering-row .field-error,
.hours-row .field-error { grid-column: 1 / -1; margin: 0; }
.check { min-height: 44px; align-items: center; }
.check label { display: flex; align-items: center; min-height: 44px; flex: 1; cursor: pointer; }
.cc-close, .cc-suggest button { min-height: 44px; }
.cc-launcher { box-shadow: none; margin-left: auto; flex: 0 0 auto; }
.cc-panel { max-height: min(85dvh, 660px); overflow-y: auto; }
.cc-log { min-height: 80px; }
header.bar { flex-wrap: wrap; }
.line-tools { display: grid; gap: 12px; margin: 24px 0 8px; }
.line-tools input, .line-tools select { margin-bottom: 0; }
.line-history summary, .line-id summary, .accepted-agreement summary { cursor: pointer; min-height: 44px; padding: 10px 0; color: var(--strong); }
.line-id, .line-id span, .agreement-text { overflow-wrap: anywhere; white-space: pre-wrap; }
.line-id { font-size: .8rem; }
.provisioning-report { border-top: 1px solid var(--line); margin-top: 16px; padding-top: 16px; }
.review-edits { margin-top: 18px; }
.wizard-head, .step { scroll-margin-top: 20px; }
.sk-summary .sk { margin: 0; min-height: 125px; }
#tab-lines > .sk { min-height: 270px; }
.period-breakdown { margin-top: 20px; padding-top: 16px; border-top: 1px solid var(--line); }
.agreement-text { font: inherit; line-height: 1.6; max-height: 65vh; overflow: auto; }
.responsive-table td { overflow-wrap: anywhere; }
@media (min-width: 640px) { .line-tools { grid-template-columns: 2fr 1fr; } }
@media (max-width: 639px) {
  .responsive-table, .responsive-table tbody, .responsive-table tr, .responsive-table td { display: block; width: 100%; }
  .responsive-table thead { position: absolute; width: 1px; height: 1px; overflow: hidden; clip-path: inset(50%); }
  .responsive-table tr { padding: 12px 0; border-bottom: 1px solid var(--line-strong); }
  .responsive-table td { display: grid; grid-template-columns: 90px minmax(0, 1fr); gap: 12px; border: 0; padding: 8px 0; text-align: right; white-space: normal; }
  .responsive-table td::before { content: attr(data-label); color: var(--muted); text-align: left; font-weight: 500; }
  .responsive-table td > * { grid-column: 2; }
  .responsive-table td:first-child { text-align: left; }
  .responsive-table .pill { justify-self: end; }
  .responsive-table .line-id summary { padding: 10px 0; }
}

#ln-eligibility summary { min-height: 44px; padding: 10px 0; cursor: pointer; color: var(--strong); }


/* The compact intake keeps related choices together without another screen. */
@media (min-width: 640px) {
  #line-form #ln-has-number,
  #line-form #ln-when { display: grid; gap: 12px; grid-template-columns: repeat(2, minmax(0, 1fr)); }
  #line-form #ln-when { grid-template-columns: repeat(3, minmax(0, 1fr)); }
  #line-form .segmented .opt { margin: 0; }
}

/* ── "What it can answer": hours, services, address ────────────────────────
   Added 2026-09-15 with the step that closes the pilot letter's §2(a) promise.
   Structure only, like the rest of this file — no brand hex, every colour a
   token, and every control keeps the 44px target and the 16px font the rest of
   the form uses (an iOS zoom on focus loses a reseller their place, and the
   hours editor is the densest thing on the form to lose your place in). */
fieldset.hours { border: 0; padding: 0; margin: 12px 0 4px; min-width: 0; }
fieldset.hours > legend {
  padding: 0;
  font-weight: 600;
  font-size: 0.9rem;
  color: var(--strong);
}

/* TWO COLUMNS ON A PHONE, FOUR ON A LAPTOP — the same row either way.
   At 375px a day, a Closed box and two time fields will not sit on one line at
   a legible size, so the day and its Closed box take the first line and the two
   times take the second. It is one grid with a changed template rather than two
   layouts, so the tab order is the reading order at every width. */
.hours-row {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 6px 10px;
  align-items: center;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.hours-row:last-child { border-bottom: 0; }
.hours-row .hours-day { font-weight: 600; color: var(--strong); font-size: 0.9rem; }
.hours-row .hours-closed {
  display: flex;
  align-items: center;
  justify-content: flex-end;
  gap: 8px;
  margin: 0;
  min-height: 44px;
  font-weight: 400;
  color: var(--text);
}
.hours-row .hours-closed input { width: 20px; height: 20px; flex: none; accent-color: var(--brand); }
/* A closed day's times are left in place and dimmed rather than removed: the
   row keeps its height, so ticking Closed down the week does not make the list
   jump under the pointer that is ticking it. */
.hours-row input[type="time"]:disabled { opacity: 0.45; }

@media (min-width: 640px) {
  .hours-row { grid-template-columns: minmax(90px, 1fr) minmax(0, 1.2fr) minmax(0, 1.2fr) auto; }
  .hours-row .hours-closed { order: 4; justify-content: flex-start; }
}

/* One service is a name and a price side by side, with its own Remove. The
   button is last in the source order so a keyboard reaches the two fields
   before the control that deletes them. */
.offering-row {
  display: grid;
  grid-template-columns: minmax(0, 1fr);
  gap: 8px;
  padding: 8px 0;
  border-bottom: 1px solid var(--line);
}
.offering-row:last-child { border-bottom: 0; }
.offering-row input { margin: 0; }
@media (min-width: 560px) {
  .offering-row { grid-template-columns: minmax(0, 1.6fr) minmax(0, 1fr) auto; align-items: center; }
}

.addr-grid { display: grid; grid-template-columns: minmax(0, 1fr); gap: 0 12px; }
.addr-grid label { margin-top: 12px; }
@media (min-width: 560px) {
  .addr-grid { grid-template-columns: minmax(0, 2fr) minmax(0, 1fr) minmax(0, 1fr); }
}

/* A refusal's code, for support (#291). Small print, not dimmed: the notice it
   sits in is already a tinted surface, and a muted grey on top of that tint is
   where AA contrast would quietly go. Size alone marks it as secondary. */
.refusal-ref { display: block; margin-top: 4px; font-size: 0.78rem; letter-spacing: 0.01em; }
.download-error { margin-top: 8px; }
