/* Minutes Network Outreach Panel
   Quiet, built at 390px first. Surfaces are separated by hairlines rather than
   by fills. No gradients on text, no glass, no emoji.

   The palette is not invented here. Every value below is lifted literally from
   the live Minutes Network stylesheet at minutesnetwork.io/css/style.css, which
   is the design library this panel belongs to. Their token names are written for
   a dark build that was later inverted (--mn-deep-navy holds #ffffff), so the
   literal values are used with the source noted against each one rather than
   their names, which would read as the opposite of what they do.

   That inversion is also where the dark theme comes from. The names still carry
   the palette it was built on, so dark is not a second palette invented to sit
   beside this one: it is the same library read the way it was originally
   written, --mn-deep-navy back to #0f172a, --mn-navy to #1e293b, --mn-white to
   #f8fafc. Both themes are therefore the house set, not a set and a guess.

   Every colour in this file is a token. A literal below the two blocks is a bug,
   because it is a value that cannot follow the theme. There is exactly one, it
   is marked where it sits, and it is deliberate. */

@font-face {
  font-family: 'Inter';
  src: url('/fonts/inter-var.woff2') format('woff2');
  font-weight: 100 900;
  font-style: normal;
  font-display: swap;
}

:root {
  /* Declared here as well as in the meta tag. The meta is a hint the browser
     may use before CSS arrives; this is the property that actually governs, and
     it is what paints the native select menus, the scrollbars and the form
     controls light rather than dark. */
  color-scheme: light;

  /* Surfaces: --mn-deep-navy, --mn-navy, --mn-slate, then one step on. */
  --bg: #ffffff;
  --surface: #f8fafc;
  --surface-2: #f1f5f9;
  --surface-3: #e2e8f0;

  /* --border-subtle verbatim, the solid line it pairs with, and the one step up
     that a hovered control borrows to say it is live. */
  --line: rgba(0, 0, 0, 0.08);
  --line-2: #e2e8f0;
  --line-3: #cbd5e1;

  /* Ink: --mn-white is the near-black text colour, then --mn-light, --mn-muted. */
  --text: #0f172a;
  --text-2: #475569;
  --text-3: #64748b;

  /* Brand: --mn-blue carries the interface, --mn-purple is the deep anchor,
     --mn-cyan the lighter accent. Blue is the one that holds contrast on white. */
  --accent: #2563eb;
  --accent-deep: #180c70;
  --accent-soft: #eff6ff;
  --cyan: #0ea5e9;

  /* The primary button is held apart from the accent that carries links and
     labels. The two can be the same value on white and cannot be on navy: a
     blue bright enough to read as text on a dark page is too bright to put
     white text on top of. Splitting them here means neither theme has to
     compromise the other. */
  --accent-btn: #2563eb;
  --accent-btn-hover: #1d4ed8;
  --accent-btn-ink: #ffffff;

  /* There is no error or warning colour in the brand set, and one is needed for
     a failed send. Both are deep and low chroma so they read as a state and not
     as an alert box, which the house style rules out. */
  --good: #0f766e;
  --bad: #b42318;
  --warn: #b54708;

  /* The tinted hairlines and fills that go with those three, plus the two
     accents. A pill is a thin border and no fill, so these are mostly borders. */
  --accent-line: #bfdbfe;
  --accent-deep-line: #c7d2fe;
  --good-line: #99e0d6;
  --bad-line: #f4cdc8;
  --bad-line-2: #eeaba3;
  --bad-soft: #fef3f2;
  --bad-ink: #912018;
  --warn-line: #f0c6a0;
  --warn-soft: #fffaf5;
  --warn-ink: #93370d;

  /* The page colour again, for the sticky bars and the fade behind the send
     buttons. --bg-blur is translucent because the topbar pairs it with a
     backdrop-filter, so the alpha is what the blur has to work on. The tab bar
     has no filter, so its fill is opaque: an alpha there blurs nothing and only
     lets the rows underneath show through it. That reads as a smudge on white
     and as legible ghost text on navy, which is how it was caught.

     A transparent keyword cannot stand in for the last one: it resolves to
     transparent black, and a gradient to it greys out through its middle. */
  --bg-blur: rgba(255, 255, 255, 0.92);
  --bg-blur-solid: #ffffff;
  --bg-fade: rgba(255, 255, 255, 0);

  /* The fill a row takes on hover. One step off the page in whichever direction
     the page is not: lighter on navy, darker on white. */
  --hover-row: #f8fafc;

  --scrim: rgba(15, 23, 42, 0.38);

  /* The toast is the one surface that deliberately opposes the page, so it owns
     both of its colours rather than borrowing --text, which would put white on
     near-white the moment the theme flipped. */
  --toast-bg: #0f172a;
  --toast-ink: #ffffff;

  /* --radius-sm for controls, --radius-md for cards. */
  --r: 6px;
  --r-lg: 12px;
  /* --shadow-sm, the only shadow in the panel, and the two the overlays use. */
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.06);
  --shadow-lg: 0 8px 48px rgba(0, 0, 0, 0.12);
  --shadow-up: 0 -8px 48px rgba(0, 0, 0, 0.12);

  --tab-h: 62px;
  --pad: 16px;
}

/* ------------------------------------------------------------------- dark

   Tokens only. Nothing below this block is selected on the theme, which is the
   point: a rule that has to be written twice is a rule that will be changed
   once.

   The attribute is resolved before the first paint by /theme.js and is always
   present and always light or dark, never absent and never a third value. The
   stored preference wins; with nothing stored it follows the operating system.
   A media query is not enough on its own here, because a choice made in the
   panel has to beat the machine it is running on. */

[data-theme='dark'] {
  color-scheme: dark;

  /* The MN scale read the way its names were written, before the inversion.
     --surface is the one value not in the published set: an inverted palette
     needs a step below its darkest surface that an upright one does not, so
     that a card can sit above the page rather than in it. It is --bg darkened,
     not a new hue. */
  --bg: #0f172a;
  --surface: #0b1120;
  --surface-2: #1e293b;
  --surface-3: #334155;

  --line: rgba(255, 255, 255, 0.10);
  --line-2: #2c3a4f;
  --line-3: #475569;

  --text: #f8fafc;
  --text-2: #cbd5e1;
  --text-3: #94a3b8;

  /* 7.7:1 and 9.4:1 on the page. The published #2563eb is 3.0:1 there and is a
     link nobody over forty can read, which is why the accent moves and the
     button below it does not. */
  --accent: #7dabf8;
  --accent-deep: #a5b4fc;
  --accent-soft: rgba(125, 171, 248, 0.18);
  --cyan: #38bdf8;

  --accent-btn: #2563eb;
  --accent-btn-hover: #3b82f6;
  --accent-btn-ink: #ffffff;

  --good: #5eead4;
  --bad: #fda29b;
  --warn: #fdba74;

  --accent-line: #2f4f86;
  --accent-deep-line: #3f3f8f;
  --good-line: #226b62;
  --bad-line: #7a2a24;
  --bad-line-2: #a23b32;
  --bad-soft: #2a1513;
  --bad-ink: #fda29b;
  --warn-line: #7a4a1a;
  --warn-soft: #2a1d0e;
  --warn-ink: #fdba74;

  --bg-blur: rgba(15, 23, 42, 0.92);
  --bg-blur-solid: #0f172a;
  --bg-fade: rgba(15, 23, 42, 0);

  --hover-row: #172033;

  --scrim: rgba(2, 6, 16, 0.66);

  --toast-bg: #f8fafc;
  --toast-ink: #0f172a;

  /* A 6 percent shadow is invisible on navy. Depth on a dark page is carried by
     the shadow going darker than the surface, not lighter, so the alphas climb
     rather than the colour changing. */
  --shadow: 0 2px 8px rgba(0, 0, 0, 0.45);
  --shadow-lg: 0 8px 48px rgba(0, 0, 0, 0.55);
  --shadow-up: 0 -8px 48px rgba(0, 0, 0, 0.55);
}

* { box-sizing: border-box; }
/* A [hidden] element that also sets display would otherwise stay on screen. */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  padding: 0;
  max-width: 100%;
  overflow-x: hidden;
  background: var(--bg);
}

body {
  font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
  color: var(--text);
  font-size: 15px;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
  -webkit-text-size-adjust: 100%;
}

h1, h2, h3, p { margin: 0; }
button, input, select, textarea { font: inherit; color: inherit; }

/* Sized to keep iOS from zooming the page on focus. */
input, select, textarea { font-size: 16px; }

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

/* ------------------------------------------------------------------ login */

.login {
  min-height: 100vh;
  min-height: 100dvh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 24px;
  background: var(--surface);
}

.login-card {
  width: 100%;
  max-width: 360px;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  padding: 32px 24px;
}

.wordmark {
  font-size: 17px;
  font-weight: 600;
  letter-spacing: -0.01em;
  margin-bottom: 4px;
}
.wordmark span {
  display: block;
  font-size: 13px;
  font-weight: 600;
  color: var(--accent);
  letter-spacing: 0.06em;
  text-transform: uppercase;
  margin-top: 2px;
}

.login-card .field { margin-top: 24px; }

/* ----------------------------------------------------------------- shared */

label, .label {
  display: block;
  font-size: 12px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--text-3);
  margin-bottom: 8px;
}

input[type='text'], input[type='password'], input[type='email'], input[type='number'],
input[type='search'], select, textarea {
  width: 100%;
  min-height: 46px;
  padding: 11px 13px;
  background: var(--bg);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  outline: none;
  transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
textarea { min-height: 160px; line-height: 1.6; resize: vertical; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 13px; }
/* A ring rather than a heavier border, so the control does not move by a pixel
   when it takes focus. */
input:focus, select:focus, textarea:focus {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-soft);
}
input::placeholder, textarea::placeholder { color: var(--text-3); }
select { appearance: none; background-image: none; }

/* The native control sizes itself to its own button, which lands well under the
   44px minimum. Padding brings the hit area up without restyling the control. */
input[type='file'] {
  width: 100%;
  padding: 11px 0;
  font-size: 16px;
  color: var(--text-2);
}
input[type='file']::file-selector-button {
  min-height: 44px;
  margin-right: 12px;
  padding: 8px 14px;
  background: var(--surface-2);
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  color: var(--text);
  font: inherit;
  font-size: 14px;
  cursor: pointer;
}

.btn {
  min-height: 46px;
  padding: 11px 18px;
  border: 1px solid var(--line-2);
  background: var(--bg);
  color: var(--text);
  border-radius: var(--r);
  font-weight: 500;
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, opacity 0.15s ease;
}
.btn:hover { background: var(--surface-2); border-color: var(--line-3); }
.btn:disabled { opacity: 0.45; cursor: not-allowed; }
/* The site's primary button is a three stop gradient. The house style rules out
   gradients and glows on a CTA, so this is the flat middle stop of it. */
.btn-primary { background: var(--accent-btn); border-color: var(--accent-btn); color: var(--accent-btn-ink); font-weight: 600; }
/* Accent at 45 percent still reads as a live button, which is the wrong signal
   on an unarmed send. A disabled primary drops back to a plain surface. */
.btn-primary:disabled { background: var(--surface-2); border-color: var(--line-2); color: var(--text-3); opacity: 1; }
.btn-primary:hover { background: var(--accent-btn-hover); border-color: var(--accent-btn-hover); }
.btn-danger { color: var(--bad); border-color: var(--bad-line); }
.btn-danger:hover { background: var(--bad-soft); border-color: var(--bad-line-2); }
.btn-ghost { background: transparent; }
.btn-full { width: 100%; }
/* Smaller type and padding, but never below the 44px touch minimum. */
.btn-sm { min-height: 44px; padding: 8px 14px; font-size: 14px; }

.row { display: flex; gap: 10px; flex-wrap: wrap; }
.row > * { min-width: 0; }
.grow { flex: 1 1 160px; }
/* The theme button is a glyph, so its name is carried here instead. Not
   display: none, which would take it out of the accessibility tree along with
   the screen. */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.muted { color: var(--text-2); }
.dim { color: var(--text-3); }
.small { font-size: 13px; }
.mono { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 12px; }
.stack > * + * { margin-top: 14px; }
.spacer { height: 12px; }

/* Thin border, no fill. A row of filled badges competes with the names, which
   are the thing being scanned. */
.pill {
  display: inline-block;
  padding: 3px 9px;
  border-radius: 999px;
  font-size: 11px;
  font-weight: 600;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  border: 1px solid var(--line-2);
  color: var(--text-3);
  background: var(--bg);
  white-space: nowrap;
}
.pill-new { color: var(--accent); border-color: var(--accent-line); }
.pill-contacted { color: var(--accent-deep); border-color: var(--accent-deep-line); }
.pill-replied { color: var(--good); border-color: var(--good-line); }
.pill-closed { color: var(--text-3); }
.pill-sent { color: var(--good); border-color: var(--good-line); }
.pill-failed { color: var(--bad); border-color: var(--bad-line); }
.pill-sandbox { color: var(--warn); border-color: var(--warn-line); }
.pill-live { color: var(--accent-deep); border-color: var(--accent-deep-line); }

.card {
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  box-shadow: var(--shadow);
  padding: 16px;
}
.card + .card { margin-top: 10px; }

.notice {
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  padding: 12px 14px;
  font-size: 13px;
  color: var(--text-2);
  background: var(--surface);
}
.notice-bad { border-color: var(--bad-line); background: var(--bad-soft); color: var(--bad-ink); }
.notice-warn { border-color: var(--warn-line); background: var(--warn-soft); color: var(--warn-ink); }

.empty { padding: 34px 16px; text-align: center; color: var(--text-3); font-size: 14px; }

/* ------------------------------------------------------------------ shell */

.app { min-height: 100vh; min-height: 100dvh; }

.sidebar { display: none; }

.main {
  padding: 0 var(--pad);
  padding-bottom: calc(var(--tab-h) + 28px + env(safe-area-inset-bottom));
  max-width: 100%;
}

.topbar {
  position: sticky;
  top: 0;
  z-index: 5;
  background: var(--bg-blur);
  backdrop-filter: saturate(180%) blur(8px);
  border-bottom: 1px solid var(--line);
  margin: 0 calc(var(--pad) * -1);
  padding: 14px var(--pad);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 12px;
}
.topbar h1 { font-size: 17px; font-weight: 600; letter-spacing: -0.01em; }
.topbar-right { display: flex; align-items: center; gap: 10px; }

/* Square, quiet, and a full touch target even though it carries no label. The
   sandbox pill next to it is the loud thing in this corner and should stay the
   loud thing, so this is a hairline and an outline glyph until it is hovered. */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  flex: none;
  padding: 0;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: var(--bg);
  color: var(--text-2);
  cursor: pointer;
  transition: background 0.15s ease, border-color 0.15s ease, color 0.15s ease;
}
.icon-btn:hover { background: var(--surface-2); border-color: var(--line-3); color: var(--text); }
.icon-btn:focus-visible { outline: none; border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.icon-btn svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.7; stroke-linecap: round; stroke-linejoin: round; }

.view { padding-top: 18px; }

.tabbar {
  position: fixed;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 20;
  display: grid;
  grid-template-columns: repeat(5, 1fr);
  background: var(--bg-blur-solid);
  border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
}
.tabbar button {
  min-height: var(--tab-h);
  border: 0;
  background: transparent;
  color: var(--text-3);
  font-size: 11px;
  font-weight: 500;
  letter-spacing: 0.02em;
  cursor: pointer;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 5px;
  padding: 8px 2px;
}
.tabbar button svg { width: 19px; height: 19px; stroke: currentColor; fill: none; stroke-width: 1.6; }
.tabbar button[aria-current='true'] { color: var(--accent); }

/* --------------------------------------------------------------- contacts */

.filters { display: grid; gap: 10px; grid-template-columns: 1fr 1fr; }
.filters .wide { grid-column: 1 / -1; }
/* display: grid above is an author rule and beats the browser's own
   [hidden] { display: none }, so the secondary panel would never hide without
   this line. */
.filters[hidden] { display: none; }

.filters-extra { margin-top: 10px; }

/* Two columns and an odd number of controls leaves the last one sitting beside
   a hole, which reads as a control that failed to draw. It takes the width
   instead. Which index is the odd one out differs between the two panels,
   because the search box spans both tracks and so starts the next row clean.
   Both tests are on the last child, so a panel that comes out even is left
   alone. */
@media (max-width: 1039px) {
  .filters > .wide ~ :last-child:nth-child(even) { grid-column: 1 / -1; }
  .filters-extra > :last-child:nth-child(odd) { grid-column: 1 / -1; }
}

/* A quiet control for the disclosure toggle and the clear link. It carries the
   44px touch minimum like every other control, and gives it up on a desktop
   with a mouse along with the rest of them. */
.btn-text {
  display: inline-flex;
  align-items: center;
  min-height: 44px;
  margin-top: 10px;
  padding: 0;
  background: none;
  border: 0;
  color: var(--text-2);
  font: inherit;
  text-decoration: underline;
  text-underline-offset: 3px;
  cursor: pointer;
}
.btn-text:hover { color: var(--text); }
.small .btn-text { margin-top: 0; font-size: inherit; }

.list { margin-top: 14px; }

/* Column headings belong to the table layout, which only exists from 1040. */
.list-head { display: none; }

.item {
  width: 100%;
  text-align: left;
  display: block;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 14px;
  cursor: pointer;
  min-height: 44px;
}
.item + .item { margin-top: 8px; }
.item:hover { border-color: var(--line-3); }

/* Templates, Activity and Settings build their rows from these three rather than
   from the contact cells below, so both sets have to exist. Without .item-top
   the name and its note sit against each other with no space at all. */
.item-top { display: flex; align-items: baseline; justify-content: space-between; gap: 10px; }
.item-name { font-weight: 600; letter-spacing: -0.01em; }
.item-sub { color: var(--text-2); font-size: 13px; margin-top: 3px; word-break: break-word; }

/* A grid rather than a plain stack, so the list and the status share the last
   line. Stacked, the pill sat on a line of its own under everything else and
   cost 33px a card to say one word, which is most of a fifth row for nothing.
   The lead is the person, or the company where nobody is named yet.

   Scoped to the contact rows. Every list in the panel shares .item, and a
   template row laid out on these five cells is not a template row. */
.item-contact {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  grid-template-areas:
    'lead lead'
    'org  org'
    'mail mail'
    'list status';
  column-gap: 12px;
  row-gap: 2px;
  align-items: baseline;
}
.i-lead { grid-area: lead; font-weight: 600; letter-spacing: -0.01em; }
.i-org { grid-area: org; }
.i-mail { grid-area: mail; }
.i-list { grid-area: list; }
.i-org, .i-mail, .i-list { color: var(--text-2); font-size: 13px; word-break: break-word; }
.i-list { color: var(--text-3); }
/* Without this the pill is stretched by the column and reads as a button. */
.item-contact .pill { grid-area: status; justify-self: end; }

.pager { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-top: 16px; }

/* --------------------------------------------------------------- template */

.thumb {
  width: 100%;
  height: 84px;
  object-fit: cover;
  border-radius: var(--r);
  border: 1px solid var(--line-2);
  background: var(--surface-2);
  display: block;
}

.asset-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(104px, 1fr)); gap: 10px; }
.asset {
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  overflow: hidden;
  background: var(--bg);
  cursor: pointer;
  padding: 0;
  min-height: 44px;
}
.asset[aria-pressed='true'] { border-color: var(--accent); box-shadow: 0 0 0 3px var(--accent-soft); }
.asset img { width: 100%; height: 72px; object-fit: cover; display: block; }
.asset span { display: block; padding: 6px 8px; font-size: 11px; color: var(--text-3); word-break: break-all; }
.asset-none { display: flex; align-items: center; justify-content: center; height: 100px; color: var(--text-3); font-size: 13px; }

/* The one literal in the file, and the one surface that must not follow the
   theme. This frame holds the email itself, and the email is going to land in
   somebody else's inbox on white. Painting it navy because the panel is navy
   would preview a message that does not exist and hide exactly the fault the
   preview is there to catch, a dark asset or an unset text colour that vanishes
   on a white background. */
.preview-frame {
  width: 100%;
  height: 520px;
  border: 1px solid var(--line-2);
  border-radius: var(--r);
  background: #fff;
}

/* ------------------------------------------------------------------- send */

.send-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.progress { font-variant-numeric: tabular-nums; color: var(--text-2); font-size: 13px; }

.send-actions {
  position: sticky;
  bottom: calc(var(--tab-h) + 10px + env(safe-area-inset-bottom));
  z-index: 6;
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 10px;
  padding: 12px 0 0;
  background: linear-gradient(to top, var(--bg) 62%, var(--bg-fade));
}
.send-actions .btn { min-height: 50px; }

.queue-item { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 9px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
.queue-item:last-child { border-bottom: 0; }
.queue-scroll { max-height: 320px; overflow-y: auto; }

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

.sheet {
  position: fixed;
  inset: 0;
  z-index: 40;
  background: var(--scrim);
  display: flex;
  align-items: flex-end;
  justify-content: center;
}
.sheet-panel {
  width: 100%;
  max-width: 560px;
  max-height: 88vh;
  overflow-y: auto;
  background: var(--bg);
  border: 1px solid var(--line);
  border-radius: 16px 16px 0 0;
  box-shadow: var(--shadow-up);
  padding: 18px var(--pad) calc(28px + env(safe-area-inset-bottom));
}
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 16px; }
.sheet-head h2 { font-size: 16px; font-weight: 600; }

.map-row { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; align-items: center; padding: 8px 0; border-bottom: 1px solid var(--line); }
.map-row .head { font-size: 13px; color: var(--text-2); word-break: break-word; }

/* ------------------------------------------------------------------ toast */

.toast {
  position: fixed;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(var(--tab-h) + 20px + env(safe-area-inset-bottom));
  z-index: 60;
  max-width: calc(100% - 32px);
  background: var(--toast-bg);
  border: 1px solid var(--toast-bg);
  border-radius: var(--r);
  color: var(--toast-ink);
  box-shadow: var(--shadow-lg);
  padding: 12px 16px;
  font-size: 14px;
}
.toast-bad { background: var(--bad-ink); border-color: var(--bad-ink); }

/* ------------------------------------------------------------- 768 and up */

@media (min-width: 768px) {
  .sheet { align-items: center; }
  .sheet-panel { border-radius: var(--r-lg); padding-bottom: 24px; box-shadow: var(--shadow-lg); }
}

/* The sidebar replaces the tab bar at 768, as specified. The content column is
   only about 500px wide here, so the roomier layouts wait until 1040. */
@media (min-width: 768px) {
  .app { display: grid; grid-template-columns: 236px 1fr; }

  .sidebar {
    display: block;
    position: sticky;
    top: 0;
    align-self: start;
    height: 100vh;
    height: 100dvh;
    border-right: 1px solid var(--line);
    padding: 24px 14px;
    background: var(--surface);
  }
  .sidebar .wordmark { padding: 0 10px 22px; }
  .sidebar nav { display: flex; flex-direction: column; gap: 2px; }
  .sidebar nav button {
    display: flex;
    align-items: center;
    gap: 11px;
    min-height: 44px;
    padding: 10px 12px;
    border: 0;
    border-radius: var(--r);
    background: transparent;
    color: var(--text-2);
    font-size: 14px;
    font-weight: 500;
    cursor: pointer;
    text-align: left;
  }
  .sidebar nav button svg { width: 18px; height: 18px; stroke: currentColor; fill: none; stroke-width: 1.6; flex: none; }
  .sidebar nav button:hover { background: var(--surface-2); color: var(--text); }
  .sidebar nav button[aria-current='true'] { background: var(--bg); color: var(--accent); box-shadow: var(--shadow); }

  .tabbar { display: none; }
  .main { padding: 0 24px; padding-bottom: 56px; max-width: 1280px; }
  .topbar { margin: 0 -24px; padding: 18px 24px; }
  .send-actions { bottom: 16px; max-width: 420px; }
  .toast { bottom: 24px; }
}

@media (min-width: 1040px) {
  /* Both filter grids share these tracks so their column edges line up. The
     secondary row holds seven controls, so it fills four and starts a fifth. */
  .filters { grid-template-columns: 2fr 1fr 1fr 1fr; }
  .filters .wide { grid-column: auto; }
  .main { padding: 0 32px; padding-bottom: 56px; }
  .topbar { margin: 0 -32px; padding: 18px 32px; }
}

/* ------------------------------------------------- tighter, where there is a
   mouse. The 44px touch target and the 16px input floor are iOS rules: they
   stop a thumb missing and stop Safari zooming the page on focus. Neither
   applies to a pointing device, and at this width that sizing reads as a phone
   app blown up. Gated on pointer: fine so a large tablet in landscape, which is
   wide enough to hit 1040, keeps the full touch sizing.

   Lighter is mostly subtraction: the row fill sat one step off the page
   background and so added weight without separating anything. The hairline does
   that on its own, and the fill comes back on hover where it means something. */

@media (min-width: 1040px) and (hover: hover) and (pointer: fine) {
  .btn-text { min-height: 0; margin-top: 8px; }
  .small .btn-text { margin-top: 0; }
  input[type='text'], input[type='password'], input[type='email'], input[type='number'],
  input[type='search'], select {
    min-height: 34px;
    padding: 6px 10px;
    font-size: 13.5px;
  }
  .btn { min-height: 34px; padding: 6px 14px; font-size: 13.5px; }
  .btn-sm { min-height: 30px; padding: 4px 12px; font-size: 13px; }
  .icon-btn { width: 32px; height: 32px; }
  .icon-btn svg { width: 16px; height: 16px; }
  /* Import and Add are two buttons, not a pair of banners. */
  .actions .btn { flex: 0 0 auto; }

  .filters { gap: 8px; }
  .list { margin-top: 10px; }

  /* A control has a natural size and the column is wider than that. A two digit
     interval box and a Sign out button do not need all of it, and at that width
     they read as banners rather than as controls. 560 is the form measure the
     detail sheet already uses, so this is the existing number, not a new one.
     The label is left alone, and so is the HTML body, which is code and wants
     the room. The Contacts filter row is outside .field and keeps the full
     width, where four controls across is the point. */
  .field > input, .field > select, .btn-full, .row { max-width: 560px; }

  /* The list becomes a table. Five columns, one grid definition shared by the
     heading row and every contact row, so a column cannot drift between them.
     A box around all of 50 rows is 50 outlines to read past; one hairline
     between them separates just as well and leaves the names as the only strong
     marks on the page. The fill returns on hover, where it points at something.

     Address is given the widest track because it holds the longest strings and
     is the column that decides whether a contact is usable. */
  .list-head, .item-contact {
    display: grid;
    grid-template-columns: minmax(150px, 1.15fr) minmax(150px, 1.35fr) minmax(190px, 1.6fr) minmax(120px, 0.95fr) 78px;
    align-items: baseline;
    gap: 16px;
  }
  /* The phone layout places its five cells by name into four rows. Both the
     named areas and the names on the cells have to be given up here, or the
     cells go on obeying them and the five columns above never fill. */
  .item-contact { grid-template-areas: none; }
  .item-contact > span { grid-area: auto; }

  .list-head {
    padding: 0 10px 7px;
    border-bottom: 1px solid var(--line-2);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: 0.04em;
    text-transform: uppercase;
    color: var(--text-3);
  }

  .item {
    background: transparent;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    padding: 9px 10px;
  }
  .item + .item { margin-top: 0; }
  .item:last-child { border-bottom: 0; }
  .item:hover { background: var(--hover-row); }

  /* Every cell is one line. A wrapped cell pushes its row out of step with the
     rest of the column, which is the thing a table is for. */
  .item-contact > span {
    display: block;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
  .i-lead { font-size: 14px; }
  .i-org, .i-mail, .i-list { font-size: 12.5px; word-break: normal; }
  /* A company row has no title, and its empty cell has to hold its column open
     or every cell to its right slides one place left on that row alone. */
  .item-contact > span:empty { display: block; }
  /* Two classes, so this has to undo the pill's named area itself. The reset on
     the cells above cannot reach it, and a name with no area behind it makes the
     grid invent two columns and a second row to hold it. */
  .item-contact .pill { grid-area: auto; justify-self: start; }
  .item-name { font-size: 14px; }
  .item-sub { font-size: 12.5px; margin-top: 1px; }

  .pill { padding: 2px 7px; font-size: 10px; }
  .card { border-radius: var(--r); padding: 13px; }
  .card + .card { margin-top: 6px; }
  .stack > * + * { margin-top: 11px; }
  .spacer { height: 9px; }
  .view { padding-top: 14px; }
  .topbar { padding-top: 13px; padding-bottom: 13px; }
  .empty { padding: 26px 16px; }
}
