/* Cynch Contacts.
   Token names follow the rest of the estate so the apps feel related; the accent is
   Google's contacts blue rather than CynchCal's indigo, because this app is meant to
   be recognisable to anyone who has used Google Contacts — the whole brief was that
   it work the same way.

   Mobile first, throughout. The base rules are the phone layout and every media query
   adds to it, because this address book is mostly read one-handed on the way to a
   meeting: the phone case is the real case, not a narrowed-down desktop. */

:root {
  --ink: #101826;
  --ink-soft: #3d4a5c;
  --muted: #71808f;
  --paper: #f7f8fa;
  --card: #ffffff;
  --line: #e0e4ea;
  --line-soft: #eef1f5;
  --accent: #1a73e8;
  --accent-deep: #1557b0;
  --accent-wash: #e8f0fe;
  --signal: #e8543f;
  --ok: #2e7d4f;
  --err: #c0392b;
  --warn: #b26a00;
  --star: #f0b429;
  --radius: 10px;
  --shadow: 0 1px 2px rgba(16, 24, 38, .06), 0 8px 24px -12px rgba(16, 24, 38, .22);
  --shadow-lg: 0 4px 8px rgba(16, 24, 38, .08), 0 24px 48px -16px rgba(16, 24, 38, .3);
  --font-body: "Instrument Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
  --font-mono: ui-monospace, "SF Mono", Menlo, monospace;

  /* The app bar's height is a variable because the sticky letter headings in the list
     and the drawer both have to start below it, and three files disagreeing about
     that number is how a sticky header ends up covering a row. */
  --bar: 56px;
  --rail: 280px;
}

/* Dark palette, applied two ways: from the operating system by default, and from an
   explicit data-theme when somebody overrides it in Settings. The token list is
   repeated rather than shared because a media query cannot be aliased to a plain
   selector. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --ink: #e8edf2;
    --ink-soft: #b6c2cf;
    --muted: #8593a3;
    --paper: #0f1418;
    --card: #171d24;
    --line: #262f39;
    --line-soft: #1e262e;
    --accent: #8ab4f8;
    --accent-deep: #aecbfa;
    --accent-wash: #1c2b45;
    --star: #f5c451;
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px -12px rgba(0, 0, 0, .6);
    --shadow-lg: 0 4px 8px rgba(0, 0, 0, .5), 0 24px 48px -16px rgba(0, 0, 0, .8);
  }
}

[data-theme="dark"] {
  --ink: #e8edf2;
  --ink-soft: #b6c2cf;
  --muted: #8593a3;
  --paper: #0f1418;
  --card: #171d24;
  --line: #262f39;
  --line-soft: #1e262e;
  --accent: #8ab4f8;
  --accent-deep: #aecbfa;
  --accent-wash: #1c2b45;
  --star: #f5c451;
  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 8px 24px -12px rgba(0, 0, 0, .6);
  --shadow-lg: 0 4px 8px rgba(0, 0, 0, .5), 0 24px 48px -16px rgba(0, 0, 0, .8);
}

/* `hidden` has to keep working.
   The browser's own `[hidden] { display: none }` is a UA rule, so any author rule that
   sets `display` beats it — `.field { display: block }` alone was enough to make a
   hidden form field visible, which is the sort of thing that shows a rep a duration box
   on an email. Declared here, with `!important`, so `el.hidden = true` means what it
   says everywhere and nothing has to toggle `style.display` by hand. */
[hidden] { display: none !important; }

* { box-sizing: border-box; }
html, body { height: 100%; }
body {
  margin: 0;
  font-family: var(--font-body);
  font-size: 15px;
  line-height: 1.5;
  color: var(--ink);
  background: var(--paper);
  overscroll-behavior-y: none;
  -webkit-text-size-adjust: 100%;
}
:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; border-radius: 4px; }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation: none !important; transition: none !important; }
}

button, input, select, textarea { font: inherit; color: inherit; }
/* `appearance: none` matters more than it looks. A button left with the native
   appearance draws the platform's own control instead of its children in some engines —
   a `<button>` wrapping an `<img>` renders as an empty rounded box, which is exactly how
   the card thumbnails first shipped: loaded, sized, complete, and invisible. Everything
   else here was already overriding the native look one property at a time. */
button {
  cursor: pointer; appearance: none; -webkit-appearance: none;
  background: none; border: none; padding: 0; color: inherit;
}
/* Not underlined by default.
   Every link in this app already opts out individually — the bottom bar, the quick
   actions, the drawer, list rows, chips, tree rows — because a row that happens to be
   an anchor should not read as a paragraph of links. Four separate fixes went in one at
   a time before it was clear the default was the bug, so it is the default that
   changes; the handful of places that genuinely want an underline (prose values,
   sources, the login links) declare it on hover themselves. */
a { color: var(--accent); text-decoration: none; }
.icon { display: inline-flex; align-items: center; justify-content: center; flex: none; }

/* ── shared controls ────────────────────────────────────────────────── */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  /* 40px tall at minimum: below that a thumb misses it. */
  min-height: 40px; padding: 8px 16px; border-radius: 20px;
  border: 1px solid var(--line); background: var(--card);
  font-weight: 500; font-size: 14px; color: var(--ink);
  transition: background .12s, border-color .12s;
}
.btn:hover { background: var(--line-soft); }
.btn--primary { background: var(--accent); border-color: var(--accent); color: #fff; }
.btn--primary:hover { background: var(--accent-deep); border-color: var(--accent-deep); }
[data-theme="dark"] .btn--primary, :root:not([data-theme="light"]) .btn--primary { color: #0f1418; }
.btn--danger { color: var(--err); border-color: color-mix(in srgb, var(--err) 40%, transparent); }
.btn--danger:hover { background: color-mix(in srgb, var(--err) 10%, transparent); }
.btn--ghost { border-color: transparent; background: none; }
.btn--ghost:hover { background: var(--line-soft); }
.btn--block { width: 100%; }
.btn[disabled] { opacity: .5; cursor: not-allowed; }

/* A round icon button, which is most of the buttons in a mobile app bar. */
.iconbtn {
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center; color: var(--ink-soft);
  transition: background .12s;
}
.iconbtn:hover { background: var(--line-soft); }
.iconbtn[aria-pressed="true"] { color: var(--accent); }
.iconbtn--star[aria-pressed="true"] { color: var(--star); }

.field { display: block; margin-bottom: 14px; }
.field > span, .label {
  display: block; font-size: 12px; font-weight: 600; color: var(--muted);
  margin-bottom: 5px; text-transform: uppercase; letter-spacing: .03em;
}
.input, .select, .textarea {
  width: 100%; padding: 11px 12px;
  border: 1px solid var(--line); border-radius: 8px;
  background: var(--card); color: var(--ink);
  /* 16px stops iOS Safari zooming the page when a field takes focus. */
  font-size: 16px;
}
.input:focus, .select:focus, .textarea:focus {
  border-color: var(--accent); outline: none; box-shadow: 0 0 0 3px var(--accent-wash);
}
.textarea { resize: vertical; min-height: 84px; line-height: 1.55; }
.row { display: flex; gap: 10px; flex-wrap: wrap; }
.row > * { flex: 1 1 160px; min-width: 0; }
.check { display: flex; align-items: center; gap: 10px; font-size: 14px; cursor: pointer; }
.check input { accent-color: var(--accent); width: 18px; height: 18px; flex: none; }

.chip {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 10px 5px 8px; border-radius: 16px;
  background: var(--accent-wash); color: var(--accent-deep);
  font-size: 13px; font-weight: 500; max-width: 100%;
}
.chip__x { display: grid; place-items: center; width: 18px; height: 18px; border-radius: 50%; }
.chip__x:hover { background: color-mix(in srgb, var(--accent) 20%, transparent); }
.chip--plain { background: var(--line-soft); color: var(--ink-soft); }

/* ── picking tags and lists ──────────────────────────────────────────── */
/* The fast filing sheet: chips you tap rather than a checkbox list. Wrapped rather
   than scrolled, because the whole point is seeing the taxonomy you already have
   before inventing another word for something that exists. */
.chiprow { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.chip--pick {
  border: 1px solid var(--line); background: var(--card); color: var(--ink);
  /* 34px, not the chip's natural height: these are the targets a thumb hits forty
     times in a filing pass, and the 40px floor elsewhere would make six of them fill
     a phone screen. */
  min-height: 34px; padding: 0 12px; gap: 6px; cursor: pointer;
}
.chip--pick:hover { border-color: var(--accent); }
.chip--pick[aria-pressed="true"] {
  background: var(--accent-wash); border-color: var(--accent); color: var(--accent);
  font-weight: 600;
}
/* On *some* of the selection. Dashed, because the one thing this state must not look
   like is either of the settled two — a rep who reads "some" as "on" unticks it and
   silently strips the label from the three contacts that had it. */
.chip--pick[data-some="true"] { border-style: dashed; border-color: var(--accent); color: var(--accent); }
.chip__mine { font-size: 10px; text-transform: uppercase; letter-spacing: .04em; opacity: .6; }

.error-banner, .ok-banner {
  padding: 10px 14px; border-radius: 8px; margin-bottom: 14px; font-size: 14px;
}
.error-banner {
  background: color-mix(in srgb, var(--err) 12%, transparent);
  color: var(--err); border: 1px solid color-mix(in srgb, var(--err) 30%, transparent);
}
.ok-banner {
  background: color-mix(in srgb, var(--ok) 12%, transparent);
  color: var(--ok); border: 1px solid color-mix(in srgb, var(--ok) 30%, transparent);
}

.spinner {
  width: 16px; height: 16px; border-radius: 50%;
  border: 2px solid color-mix(in srgb, currentColor 30%, transparent);
  border-top-color: currentColor;
  animation: spin .6s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) { .spinner { animation-duration: 2s; } }

.avatar {
  width: 40px; height: 40px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  font-size: 15px; font-weight: 600; color: #fff;
  object-fit: cover; background: var(--muted);
  user-select: none;
}
.avatar--lg { width: 96px; height: 96px; font-size: 34px; }
.avatar--sm { width: 32px; height: 32px; font-size: 12px; }

/* ── the shell ──────────────────────────────────────────────────────── */
/* Phone: app bar on top, one scrolling pane, a bottom bar, and a floating button.
   The drawer is off-canvas. Desktop turns the drawer into a permanent rail and drops
   the bottom bar. */

.app {
  display: grid;
  grid-template-areas: "bar" "main" "nav";
  grid-template-rows: auto minmax(0, 1fr) auto;
  /* Not plain 100dvh: dvh follows the browser's collapsing chrome and ignores the
     keyboard, which would leave the bottom bar and the editor's Save bar behind it.
     `--vvh` is set from visualViewport in main.js; the fallback is what a browser
     without it gets. */
  height: var(--vvh, 100dvh);
}

.topbar {
  grid-area: bar;
  display: flex; align-items: center; gap: 4px;
  min-height: var(--bar); padding: 8px 8px;
  padding-top: max(8px, env(safe-area-inset-top));
  background: var(--card); border-bottom: 1px solid var(--line);
  position: relative; z-index: 20;
}
.topbar__title {
  font-size: 18px; font-weight: 600; letter-spacing: -.01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis; min-width: 0; flex: 1;
}
.topbar__spacer { flex: 1; }
.topbar__mark { font-weight: 700; letter-spacing: -.02em; font-size: 17px; padding: 0 6px; }

/* The search box is the app bar's main event on a phone, the way it is in Google
   Contacts: a pill that fills the bar, with the menu button inside it. */
.searchbar {
  display: flex; align-items: center; gap: 6px; flex: 1; min-width: 0;
  background: var(--line-soft); border-radius: 24px;
  padding: 0 6px 0 4px; height: 44px;
}
.searchbar input {
  flex: 1; min-width: 0; border: none; background: none; outline: none;
  font-size: 16px; color: var(--ink); padding: 0 2px;
}
.searchbar input::placeholder { color: var(--muted); }

/* ── drawer ── */

.scrim {
  position: fixed; inset: 0; z-index: 40;
  background: rgba(16, 24, 38, .45);
  animation: fade .15s ease-out;
}
@keyframes fade { from { opacity: 0; } }

/* Hidden until opened on a phone; the desktop query below turns it into a permanent
   rail. Without the `display: none` the off-canvas panel sits over the list at all
   times, which is invisible in a desktop browser and immediately fatal on a phone. */
.drawer { display: none; }
.drawer--open {
  display: block;
  position: fixed; inset: 0 auto 0 0; z-index: 41;
  width: min(var(--rail), 86vw);
  background: var(--card); border-right: 1px solid var(--line);
  overflow-y: auto; overscroll-behavior: contain;
  padding: 8px 8px max(16px, env(safe-area-inset-bottom));
  animation: slide-in .18s ease-out;
  box-shadow: var(--shadow-lg);
}
.drawer, .drawer--open {
  background: var(--card); border-right: 1px solid var(--line);
  overflow-y: auto; overscroll-behavior: contain;
  padding: 8px 8px max(16px, env(safe-area-inset-bottom));
}
@keyframes slide-in { from { transform: translateX(-100%); } }

.drawer__brand {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 12px 16px; font-size: 18px; font-weight: 700; letter-spacing: -.02em;
}
.drawer__section {
  font-size: 11px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .06em;
  padding: 16px 12px 6px;
}
.drawer__item {
  display: flex; align-items: center; gap: 14px; width: 100%;
  padding: 11px 12px; border-radius: 0 22px 22px 0; margin-right: 8px;
  font-size: 14px; font-weight: 500; color: var(--ink-soft); text-align: left;
  text-decoration: none;
}
.drawer__item:hover { background: var(--line-soft); }
.drawer__item[aria-current="page"] {
  background: var(--accent-wash); color: var(--accent-deep); font-weight: 600;
}
.drawer__item .icon { color: currentColor; }
.drawer__count { margin-left: auto; font-size: 12px; color: var(--muted); font-weight: 500; }
.drawer__item[aria-current="page"] .drawer__count { color: var(--accent-deep); }
.drawer__swatch { width: 12px; height: 12px; border-radius: 3px; flex: none; }
.drawer hr { border: none; border-top: 1px solid var(--line-soft); margin: 8px 12px; }

/* ── bottom navigation (phone only) ── */

.bottomnav {
  grid-area: nav;
  display: flex; background: var(--card); border-top: 1px solid var(--line);
  padding-bottom: env(safe-area-inset-bottom);
  z-index: 20;
}
.bottomnav__item {
  flex: 1; display: flex; flex-direction: column; align-items: center; gap: 2px;
  padding: 8px 4px 6px; font-size: 11px; font-weight: 500; color: var(--muted);
  min-height: 52px;
  /* These are real anchors, so they inherit the underline from the `a` rule. A bottom
     bar with four underlined words in it reads as a row of hyperlinks rather than as
     navigation. */
  text-decoration: none;
}
.bottomnav__item[aria-current="page"] { color: var(--accent); }
.bottomnav__item[aria-current="page"] .icon {
  background: var(--accent-wash); border-radius: 12px; padding: 2px 12px;
}

/* ── the main pane ── */

.main { grid-area: main; overflow-y: auto; overscroll-behavior-y: contain; position: relative; }
.pane { padding-bottom: 88px; }        /* room for the floating button */
.pane--form { padding: 16px 16px 96px; max-width: 720px; margin: 0 auto; }

.fab {
  position: fixed; right: 16px; z-index: 25;
  bottom: calc(16px + 52px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 10px;
  height: 56px; padding: 0 20px; border-radius: 18px;
  background: var(--accent); color: #fff; font-weight: 600; font-size: 15px;
  box-shadow: var(--shadow-lg);
}
[data-theme="dark"] .fab, :root:not([data-theme="light"]) .fab { color: #0f1418; }
.fab:hover { background: var(--accent-deep); }

/* ── the contact list ───────────────────────────────────────────────── */

.listhead {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 16px 8px; color: var(--muted); font-size: 13px;
}
.listhead__count { font-weight: 500; }

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

/* The sticky letter, which is what makes a long list navigable by thumb. */
.clist__letter {
  position: sticky; top: 0; z-index: 5;
  padding: 6px 16px; background: var(--paper);
  font-size: 12px; font-weight: 700; color: var(--muted); letter-spacing: .06em;
  border-bottom: 1px solid var(--line-soft);
}

.crow {
  display: flex; align-items: center; gap: 14px;
  width: 100%; padding: 10px 16px; text-align: left;
  min-height: 64px; border-bottom: 1px solid var(--line-soft);
  background: none;
}
.crow:hover { background: var(--card); }
.crow[aria-selected="true"] { background: var(--accent-wash); }
.crow__body { flex: 1; min-width: 0; }
.crow__name {
  font-size: 15px; font-weight: 500; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
  display: flex; align-items: center; gap: 6px;
}
.crow__sub {
  font-size: 13px; color: var(--muted);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.crow__actions { display: flex; align-items: center; gap: 2px; flex: none; }
.crow__star { color: var(--star); }
.crow__tag {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--accent-deep); background: var(--accent-wash);
  padding: 1px 6px; border-radius: 4px; flex: none;
}

/* Tapping the avatar in selection mode toggles the row, so it becomes a checkbox. */
.crow__pick { position: relative; flex: none; }
.crow__pick input {
  position: absolute; inset: 0; opacity: 0; width: 100%; height: 100%; margin: 0;
  cursor: pointer;
}
.crow__tick {
  position: absolute; inset: 0; border-radius: 50%;
  display: grid; place-items: center; color: #fff; background: var(--accent);
  opacity: 0; transition: opacity .1s;
}
.crow__pick input:checked ~ .crow__tick { opacity: 1; }
.crow__pick input:focus-visible ~ .crow__tick { outline: 2px solid var(--accent); outline-offset: 2px; }

/* The A–Z rail: a thumb-sized column down the right edge that jumps the list. */
.azrail {
  position: fixed; right: 0; z-index: 15;
  /* Ends above the floating button rather than beside it. At 375px the extended FAB is
     180px wide and reaches x=359 — two pixels *into* these buttons — and its higher
     z-index means the letters underneath it silently stop responding to a tap. The
     scrub gesture never noticed, because list.js derives the letter from the rail's
     geometry rather than from a hit test; only the tap was broken. */
  bottom: calc(52px + 72px + env(safe-area-inset-bottom));
  /* And starts below the bar as it actually renders: `--bar` is .topbar's min-height,
     but .topbar also pads itself by max(8px, safe-area-inset-top), so on a notched
     phone the first letter and a half were sitting underneath it. */
  top: calc(var(--bar) + max(8px, env(safe-area-inset-top)));
  display: flex; flex-direction: column; justify-content: center;
  padding: 0 8px; gap: 0;
  touch-action: none; user-select: none;
}
.azrail button {
  /* 9.5px text in a 12px box was the smallest target in the app by some way. */
  font-size: 11px; font-weight: 700; color: var(--muted);
  min-height: 20px; padding: 1px 4px; line-height: 1.28;
}
.azrail button:hover, .azrail button[aria-pressed="true"] { color: var(--accent); }
/* Shown while a drag is in progress, naming the letter under the thumb. */
.azrail__bubble {
  position: fixed; right: 52px; z-index: 16;
  width: 56px; height: 56px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent); color: #fff; font-size: 24px; font-weight: 700;
  box-shadow: var(--shadow-lg); pointer-events: none;
}

/* ── the selection bar ── */
/* Replaces the app bar while rows are selected, which is how a mobile list does bulk
   actions without a permanent column of checkboxes. */

.selbar {
  grid-area: bar; display: flex; align-items: center; gap: 4px;
  min-height: var(--bar); padding: 8px; padding-top: max(8px, env(safe-area-inset-top));
  background: var(--accent-wash); border-bottom: 1px solid var(--line);
  z-index: 20;
}
.selbar__count { font-size: 17px; font-weight: 600; color: var(--accent-deep); flex: 1; padding-left: 4px; }
.selbar .iconbtn { color: var(--accent-deep); }

/* ── the detail screen ──────────────────────────────────────────────── */

.hero {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 24px 16px 18px; text-align: center;
  background: var(--card); border-bottom: 1px solid var(--line);
}
.hero__name { font-size: 24px; font-weight: 600; letter-spacing: -.02em; line-height: 1.2; }
.hero__sub { font-size: 14px; color: var(--muted); }
.hero__photo { position: relative; }
.hero__photobtn {
  position: absolute; right: -4px; bottom: -4px;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--card); border: 1px solid var(--line); color: var(--ink-soft);
  display: grid; place-items: center; box-shadow: var(--shadow);
}

/* The round quick actions — call, text, email — directly under the name. */
.quickrow { display: flex; justify-content: center; gap: 22px; padding: 4px 16px 20px; background: var(--card); }
/* `text-decoration: none` because Call/Text/Email are real anchors (tel:, sms:,
   mailto:) and inherit the underline from the `a` rule; underlined captions under the
   round buttons read as a row of links rather than as the action row it is. */
.quick {
  display: flex; flex-direction: column; align-items: center; gap: 6px;
  font-size: 12px; color: var(--accent); text-decoration: none;
}
.quick__circle {
  width: 48px; height: 48px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--accent-wash); color: var(--accent-deep);
}
.quick[aria-disabled="true"] { color: var(--muted); pointer-events: none; }
.quick[aria-disabled="true"] .quick__circle { background: var(--line-soft); color: var(--muted); }

.section {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  margin: 12px; overflow: hidden;
}
.section__head {
  padding: 14px 16px 6px; font-size: 12px; font-weight: 700;
  color: var(--muted); text-transform: uppercase; letter-spacing: .05em;
}

.frow {
  display: flex; align-items: flex-start; gap: 14px;
  padding: 12px 16px; width: 100%; text-align: left;
  border-top: 1px solid var(--line-soft);
}
.section__head + .frow, .frow:first-child { border-top: none; }
.frow > .icon { color: var(--muted); margin-top: 2px; }
.frow__body { flex: 1; min-width: 0; }
.frow__value {
  font-size: 15px; color: var(--ink); overflow-wrap: anywhere;
}
.frow__value a { color: var(--accent); text-decoration: none; }
.frow__value a:hover { text-decoration: underline; }
.frow__label { font-size: 12px; color: var(--muted); margin-top: 1px; }
.frow__actions { display: flex; gap: 2px; flex: none; }
.frow--note .frow__value { white-space: pre-wrap; }

.labelrow { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px; }

/* A quiet strip saying this card is linked to a colleague, and what the directory
   says about them today. */
.livehint {
  display: flex; align-items: flex-start; gap: 10px; margin: 12px; padding: 12px 14px;
  background: var(--accent-wash); color: var(--accent-deep);
  border-radius: 12px; font-size: 13px; line-height: 1.5;
}

/* ── the editor ─────────────────────────────────────────────────────── */

.editor__group { margin-bottom: 22px; }
.editor__grouphead {
  display: flex; align-items: center; gap: 10px; margin-bottom: 10px;
  font-size: 12px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
.editor__grouphead .icon { color: var(--muted); }

/* One repeating value: the type selector, the value, and a remove button. */
.multi { display: flex; gap: 8px; align-items: flex-start; margin-bottom: 10px; }
.multi__main { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 8px; }
.multi__type { width: 128px; flex: none; }
.multi .iconbtn { margin-top: 2px; }
@media (max-width: 599px) {
  /* At phone width the type selector goes above the value rather than beside it: two
     controls side by side leaves neither wide enough to read. */
  .multi { flex-wrap: wrap; }
  .multi__type { width: 100%; order: -1; }
}

.addfield {
  display: inline-flex; align-items: center; gap: 8px;
  color: var(--accent); font-weight: 500; font-size: 14px; padding: 8px 4px;
}

/* A sticky footer so Save is always reachable while editing a long card. */
.formbar {
  position: sticky; bottom: 0; z-index: 10;
  display: flex; gap: 10px; justify-content: flex-end;
  padding: 12px 16px max(12px, env(safe-area-inset-bottom));
  margin: 0 -16px -96px; background: var(--card); border-top: 1px solid var(--line);
}
.formbar .btn { flex: 1; }

/* ── sheets and dialogs ─────────────────────────────────────────────── */
/* On a phone these rise from the bottom, which is where a thumb is. On a wider screen
   they become a centred dialog. */

.sheet {
  position: fixed; inset: auto 0 0 0; z-index: 50;
  background: var(--card); border-radius: 16px 16px 0 0;
  box-shadow: var(--shadow-lg);
  /* Same reason as `.app` above — a sheet with a text field in it is typed into blind
     if it is measured against a viewport the keyboard is not part of. */
  max-height: min(86dvh, calc(var(--vvh, 100dvh) - 24px));
  display: flex; flex-direction: column;
  animation: rise .18s ease-out;
  padding-bottom: env(safe-area-inset-bottom);
}
@keyframes rise { from { transform: translateY(100%); } }
.sheet__head {
  display: flex; align-items: center; gap: 10px;
  padding: 16px 16px 10px; flex: none;
}
.sheet__title { font-size: 18px; font-weight: 600; letter-spacing: -.01em; flex: 1; }
.sheet__body { padding: 4px 16px 16px; overflow-y: auto; flex: 1; }
.sheet__foot {
  display: flex; gap: 10px; padding: 12px 16px 16px; flex: none;
  border-top: 1px solid var(--line-soft);
}
.sheet__foot .btn { flex: 1; }

/* A grab handle, so the sheet reads as something that came up from the bottom. */
.sheet__grip {
  width: 36px; height: 4px; border-radius: 2px; background: var(--line);
  margin: 8px auto 0; flex: none;
}

/* A list of actions — the "more" menu. */
.menu { list-style: none; margin: 0; padding: 4px 0; }
.menu__item {
  display: flex; align-items: center; gap: 16px; width: 100%;
  padding: 13px 16px; text-align: left; font-size: 15px; color: var(--ink);
}
.menu__item:hover { background: var(--line-soft); }
.menu__item .icon { color: var(--muted); }
.menu__item--danger { color: var(--err); }
.menu__item--danger .icon { color: var(--err); }
.menu__item[aria-disabled="true"] { opacity: .45; pointer-events: none; }

/* ── snackbar ───────────────────────────────────────────────────────── */
/* Every destructive action gets one with an Undo, because that is cheaper than a
   confirmation dialog on every delete and it is what people expect. */

.snackbar {
  position: fixed; z-index: 60; left: 12px; right: 12px;
  bottom: calc(12px + 52px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 12px;
  padding: 14px 16px; border-radius: 10px;
  background: var(--ink); color: var(--paper);
  box-shadow: var(--shadow-lg); font-size: 14px;
  animation: rise .18s ease-out;
}
.snackbar__text { flex: 1; }
.snackbar__action {
  font-weight: 700; text-transform: uppercase; font-size: 13px;
  letter-spacing: .04em; color: var(--accent);
  padding: 4px 6px; flex: none;
}
.snackbar--err { background: var(--err); color: #fff; }
.snackbar--err .snackbar__action { color: #fff; text-decoration: underline; }

/* ── empty states ───────────────────────────────────────────────────── */

.empty {
  display: flex; flex-direction: column; align-items: center; gap: 10px;
  padding: 56px 32px; text-align: center; color: var(--muted);
}
.empty__icon { color: var(--line); }
.empty__title { font-size: 17px; font-weight: 600; color: var(--ink); }
.empty p { margin: 0; font-size: 14px; line-height: 1.55; max-width: 34ch; }

/* The same words inside a card section, where they are a note and not a screen. At the
   full-screen padding, an empty Follow-ups and an empty Activity between them push the
   phone numbers off the bottom of a phone — two hundred pixels each to say that nothing
   has happened yet. */
.section .empty { padding: 18px 20px 10px; gap: 6px; }
.section .empty .empty__title { font-size: 15px; }
.section .empty p { font-size: 13px; }
.section .empty .empty__icon { display: none; }

.loading { display: grid; place-items: center; padding: 48px; color: var(--muted); }

/* ── merge & fix ────────────────────────────────────────────────────── */

.dupe {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  margin: 12px; overflow: hidden;
}
.dupe__head {
  display: flex; align-items: center; gap: 10px;
  padding: 12px 16px; border-bottom: 1px solid var(--line-soft);
}
.dupe__why { font-size: 12px; color: var(--muted); flex: 1; }
.dupe__card { display: flex; align-items: center; gap: 12px; padding: 10px 16px; }
.dupe__card + .dupe__card { border-top: 1px solid var(--line-soft); }
.dupe__foot { display: flex; gap: 8px; padding: 12px 16px; border-top: 1px solid var(--line-soft); }
.dupe__foot .btn { flex: 1; }

/* ── settings, team, connected apps ─────────────────────────────────── */

.card {
  background: var(--card); border: 1px solid var(--line); border-radius: 12px;
  padding: 16px; margin-bottom: 14px;
}
.card__title { font-size: 16px; font-weight: 600; margin: 0 0 4px; }
.card__lead { font-size: 13.5px; color: var(--muted); line-height: 1.55; margin: 0 0 14px; }

.setrow {
  display: flex; align-items: center; gap: 14px;
  padding: 12px 0; border-top: 1px solid var(--line-soft);
}
.setrow:first-of-type { border-top: none; }
.setrow__body { flex: 1; min-width: 0; }
.setrow__name { font-size: 15px; font-weight: 500; }
.setrow__hint { font-size: 13px; color: var(--muted); line-height: 1.45; }

.seg { display: inline-flex; border: 1px solid var(--line); border-radius: 20px; overflow: hidden; flex: none; }
.seg button { padding: 8px 14px; font-size: 13px; font-weight: 500; color: var(--ink-soft); min-height: 38px; }
.seg button + button { border-left: 1px solid var(--line); }
.seg button[aria-pressed="true"] { background: var(--accent-wash); color: var(--accent-deep); font-weight: 600; }

.token {
  font-family: var(--font-mono); font-size: 13px; overflow-wrap: anywhere;
  background: var(--line-soft); padding: 10px 12px; border-radius: 8px;
  border: 1px solid var(--line);
}
.pill {
  display: inline-block; font-size: 11px; font-weight: 700; letter-spacing: .04em;
  text-transform: uppercase; padding: 2px 8px; border-radius: 10px;
  background: var(--line-soft); color: var(--ink-soft);
}
.pill--ok { background: color-mix(in srgb, var(--ok) 15%, transparent); color: var(--ok); }
.pill--warn { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
.pill--err { background: color-mix(in srgb, var(--err) 14%, transparent); color: var(--err); }

/* ── auth screens ───────────────────────────────────────────────────── */
/* Class names mirror cynch-mailer's and CynchCal's login so the three read as one
   product to somebody who signs in to all of them. */

.login-wrap { display: grid; place-items: center; min-height: 100dvh; padding: 24px 16px; }
.login-card {
  width: 100%; max-width: 400px; background: var(--card);
  border: 1px solid var(--line); border-radius: 14px; padding: 28px 24px;
  box-shadow: var(--shadow);
}
.login-brand { font-size: 13px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase; color: var(--accent); }
.login-title { font-size: 22px; font-weight: 700; letter-spacing: -.02em; margin: 6px 0 0; }
.login-lead { color: var(--muted); font-size: 14px; line-height: 1.55; margin: 8px 0 20px; }
.login-input-wrap { position: relative; display: block; }
.login-input-wrap .input { padding-right: 44px; }
.login-eye {
  position: absolute; right: 4px; top: 50%; transform: translateY(-50%);
  width: 36px; height: 36px; border-radius: 8px;
  display: grid; place-items: center; opacity: .55; color: var(--ink-soft);
}
.login-eye:hover { opacity: 1; background: var(--line-soft); }
.login-eye[aria-pressed="true"] { opacity: 1; color: var(--accent); }
.login-actions { display: flex; flex-direction: column; gap: 12px; margin-top: 20px; }
.login-link { font-size: 14px; color: var(--accent); text-decoration: none; text-align: center; }
.login-link:hover { text-decoration: underline; }

/* ── research ────────────────────────────────────────────────────────── */
/* The small row of outbound lookups that sits under a name, an email address or a
   company domain. Chips rather than a menu because on a phone a rep wants one thumb
   press to get to LinkedIn, not a press to open a menu and a second to choose. */

.research { display: flex; flex-wrap: wrap; gap: 6px; padding: 2px 0 0; }
.research__link {
  display: inline-flex; align-items: center; gap: 5px;
  /* 32px rather than the 40px floor used elsewhere: these sit in dense runs of three
     or four under a field, they are never the only way to do anything, and at 40px
     they dominate the card they are meant to annotate. */
  min-height: 32px; padding: 4px 10px 4px 8px;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); color: var(--ink-soft);
  font-size: 12.5px; font-weight: 500; text-decoration: none;
  white-space: nowrap;
}
.research__link:hover { background: var(--line-soft); color: var(--ink); }
.research__link .icon { color: var(--muted); }
.research__link:hover .icon { color: var(--ink-soft); }
/* LinkedIn keeps its own blue so it is findable at a glance in a row of grey chips. */
.research__link--linkedin { color: #0a66c2; border-color: color-mix(in srgb, #0a66c2 35%, var(--line)); }
.research__link--linkedin .icon { color: #0a66c2; }
.research__link--linkedin:hover { background: color-mix(in srgb, #0a66c2 10%, transparent); color: #0a66c2; }

/* The pre-demo research button. Deliberately the most prominent thing on the card
   after the quick actions: it is the reason a rep opens this screen ten minutes
   before a call. */
.researchbtn {
  display: flex; align-items: center; justify-content: center; gap: 8px;
  width: 100%; min-height: 44px; padding: 10px 16px;
  border-radius: 22px; border: 1px solid var(--accent);
  background: var(--accent-wash); color: var(--accent-deep);
  font-size: 14.5px; font-weight: 600;
}
.researchbtn:hover { background: color-mix(in srgb, var(--accent) 18%, transparent); }
.researchbtn[disabled] { opacity: .6; cursor: progress; }
.researchbtn__hint {
  display: block; padding: 8px 16px 0; text-align: center;
  font-size: 12.5px; color: var(--muted); line-height: 1.5;
}

/* ── the generated brief ── */

.brief {
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); overflow: hidden;
}
.brief__head {
  display: flex; align-items: center; gap: 8px;
  padding: 12px 14px; border-bottom: 1px solid var(--line-soft);
  font-size: 12px; font-weight: 700; color: var(--muted);
  text-transform: uppercase; letter-spacing: .05em;
}
.brief__body {
  padding: 14px; font-size: 14.5px; line-height: 1.6; color: var(--ink);
  /* The model returns markdown-ish prose with its own line breaks; honouring them is
     the difference between a readable brief and one long paragraph. */
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.brief__body strong { font-weight: 600; }
.brief__meta {
  padding: 10px 14px; border-top: 1px solid var(--line-soft);
  font-size: 12px; color: var(--muted); line-height: 1.5;
}

/* Sources are numbered and always shown. A brief without them is a rumour, and the
   rep is about to repeat it out loud on a call. */
.brief__sources { padding: 4px 14px 12px; }
.source {
  display: flex; align-items: baseline; gap: 8px;
  padding: 5px 0; font-size: 13px;
}
.source__n {
  flex: none; min-width: 18px; height: 18px; border-radius: 9px;
  display: inline-grid; place-items: center;
  background: var(--line-soft); color: var(--muted);
  font-size: 10.5px; font-weight: 700;
}
.source a { color: var(--accent); text-decoration: none; overflow-wrap: anywhere; }
.source a:hover { text-decoration: underline; }

/* Shown while the model is searching. A rep will not wait patiently at a bare
   spinner for forty seconds, so the step is named. */
.working {
  display: flex; align-items: center; gap: 12px;
  padding: 18px 16px; font-size: 14px; color: var(--ink-soft);
}
.working__text { line-height: 1.5; }
.working__note { display: block; font-size: 12.5px; color: var(--muted); margin-top: 2px; }

/* ── companies ── */

.company__head {
  display: flex; align-items: center; gap: 14px;
  padding: 18px 16px; background: var(--card); border-bottom: 1px solid var(--line);
}
.company__mark {
  width: 48px; height: 48px; border-radius: 12px; flex: none;
  display: grid; place-items: center;
  background: var(--accent-wash); color: var(--accent-deep);
}
.company__name { font-size: 19px; font-weight: 600; letter-spacing: -.01em; }
.company__meta { font-size: 13px; color: var(--muted); }

/* ── the interaction timeline ────────────────────────────────────────── */
/* A dated line per call, text, email or meeting. Read far more often than it is
   written, so it is dense: a rep wants the last six touches visible without
   scrolling, not six generous cards. */

.timeline { list-style: none; margin: 0; padding: 4px 0 8px; }
.tl {
  display: flex; gap: 12px; padding: 10px 16px;
  border-top: 1px solid var(--line-soft);
}
.timeline > .tl:first-child { border-top: none; }
.tl__rail { display: flex; flex-direction: column; align-items: center; flex: none; width: 24px; }
.tl__dot {
  width: 24px; height: 24px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--line-soft); color: var(--muted);
}
/* One colour per channel, so a timeline is scannable without reading it. */
.tl__dot--call { background: color-mix(in srgb, var(--ok) 18%, transparent); color: var(--ok); }
.tl__dot--email { background: var(--accent-wash); color: var(--accent-deep); }
.tl__dot--sms, .tl__dot--whatsapp { background: color-mix(in srgb, #25d366 20%, transparent); color: #1da851; }
.tl__dot--meeting, .tl__dot--demo { background: color-mix(in srgb, var(--warn) 18%, transparent); color: var(--warn); }
/* The connector, so consecutive entries read as one thread rather than a list. */
.tl__line { flex: 1; width: 2px; background: var(--line-soft); margin-top: 4px; min-height: 4px; }
.tl:last-child .tl__line { display: none; }

.tl__body { flex: 1; min-width: 0; }
.tl__head {
  display: flex; align-items: baseline; gap: 8px; flex-wrap: wrap;
  font-size: 14px; font-weight: 500;
}
.tl__when { font-size: 12.5px; color: var(--muted); font-weight: 400; margin-left: auto; white-space: nowrap; }
.tl__text {
  font-size: 13.5px; color: var(--ink-soft); line-height: 1.55; margin-top: 3px;
  white-space: pre-wrap; overflow-wrap: anywhere;
}
.tl__meta { font-size: 12px; color: var(--muted); margin-top: 3px; }
/* Marks an entry a machine wrote, so a rep knows nobody typed it. */
.tl__src {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); background: var(--line-soft); padding: 1px 6px; border-radius: 4px;
}

/* The row of "log a…" buttons above the timeline. */
.logrow { display: flex; flex-wrap: wrap; gap: 6px; padding: 12px 16px 4px; }

/* ── the account tree ───────────────────────────────────────────────────── */

.tree { list-style: none; margin: 0; padding: 0; }
.tree__row {
  display: flex; align-items: center; gap: 8px;
  width: 100%; padding: 10px 12px; text-align: left;
  min-height: 48px; border-bottom: 1px solid var(--line-soft);
}
.tree__row:hover { background: var(--card); }
.tree__row[aria-current="page"] { background: var(--accent-wash); }
/* Indentation carries the hierarchy, set inline per row from its depth — the depth is
   data, so it cannot be a class. */
.tree__toggle {
  width: 28px; height: 28px; border-radius: 6px; flex: none;
  display: grid; place-items: center; color: var(--muted);
}
.tree__toggle:hover { background: var(--line-soft); }
/* The `chevron` glyph points right, which is already the closed state, so it is the
   OPEN state that rotates — down, the way a disclosure triangle does. Rotating on
   `false` instead inverts the whole affordance: an open branch shows a closed arrow
   and a closed one shows an arrow pointing at nothing. */
.tree__toggle[aria-expanded="true"] .icon { transform: rotate(90deg); }
.tree__toggle .icon { transition: transform .12s; }
.tree__spacer { width: 28px; flex: none; }
.tree__body { flex: 1; min-width: 0; }
.tree__name {
  font-size: 14.5px; font-weight: 500; color: var(--ink);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.tree__sub { font-size: 12.5px; color: var(--muted); }
.tree__count { font-size: 12px; color: var(--muted); flex: none; }
.tree__kind {
  font-size: 10px; font-weight: 700; letter-spacing: .04em; text-transform: uppercase;
  color: var(--muted); flex: none;
}

/* ── colour swatches ─────────────────────────────────────────────────── */
/* Choosing a contact's colour. On a phone the colour is how a name is picked out of a
   list at a glance, so this is worth a real control rather than a hidden setting. */

.swatches {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: 10px;
  padding: 4px 0 8px;
}
.swatch {
  width: 100%; aspect-ratio: 1; border-radius: 50%;
  border: 2px solid transparent; position: relative;
  /* 40px floor even inside a grid: these are the smallest tap targets in the app. */
  min-height: 40px;
}
.swatch[aria-pressed="true"] {
  border-color: var(--ink);
  box-shadow: 0 0 0 2px var(--card) inset;
}
.swatch__tick { position: absolute; inset: 0; display: grid; place-items: center; color: #fff; }

/* ── follow-ups ──────────────────────────────────────────────────────── */

.due {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 600; padding: 2px 8px; border-radius: 10px;
  background: var(--line-soft); color: var(--ink-soft); flex: none;
}
/* Overdue is the only state worth a colour: everything else is just a date, and a list
   where every row shouts has no way left to say "this one". */
.due--overdue { background: color-mix(in srgb, var(--err) 14%, transparent); color: var(--err); }
.due--today { background: color-mix(in srgb, var(--warn) 16%, transparent); color: var(--warn); }
.due--done { background: color-mix(in srgb, var(--ok) 14%, transparent); color: var(--ok); }

.fu { display: flex; align-items: flex-start; gap: 12px; padding: 12px 16px; width: 100%; text-align: left; border-top: 1px solid var(--line-soft); }
.fu:first-child { border-top: none; }
.fu__tick {
  width: 26px; height: 26px; border-radius: 50%; flex: none; margin-top: 1px;
  border: 2px solid var(--line); display: grid; place-items: center; color: transparent;
}
.fu__tick:hover { border-color: var(--ok); color: var(--ok); }
.fu__tick[aria-pressed="true"] { background: var(--ok); border-color: var(--ok); color: #fff; }
.fu__body { flex: 1; min-width: 0; }
.fu__note { font-size: 14.5px; color: var(--ink); overflow-wrap: anywhere; }
.fu--done .fu__note { text-decoration: line-through; color: var(--muted); }
.fu__meta { font-size: 12.5px; color: var(--muted); margin-top: 2px; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; }

/* ── the card scanner ────────────────────────────────────────────────── */

.scan {
  display: flex; flex-direction: column; align-items: center; gap: 12px;
  padding: 22px 16px; text-align: center;
}
.scan__shot {
  width: 100%; max-width: 320px; border-radius: 12px; border: 1px solid var(--line);
  display: block;
}
.scan__hint { font-size: 13px; color: var(--muted); line-height: 1.55; max-width: 40ch; }

/* A kept card photograph, full width in its sheet. Not `.scan__shot`'s 320px: the
   reason anybody opens this is to read the 8pt line they think was transcribed wrong,
   and a thumbnail of a thumbnail answers nothing. */
.cardfull {
  width: 100%; display: block; border-radius: 10px; border: 1px solid var(--line);
  background: var(--bg);
}

/* The shots taken so far. A strip of thumbnails rather than one big preview: with three
   photographs the useful question is "did I get the back?", which a row answers at a
   glance, while a full-width image each would put the Read button three screens down. */
.scan__strip {
  display: flex; flex-wrap: wrap; gap: 8px; justify-content: center;
  width: 100%;
}
.scan__thumb { position: relative; width: 96px; height: 72px; flex: none; }
.scan__thumbimg {
  width: 100%; height: 100%; object-fit: cover;
  border-radius: 10px; border: 1px solid var(--line); display: block;
}
/* The number is the whole point of the strip: it is what "read from photos 1 and 3" in
   the proposal refers to, and without it that sentence names nothing on screen. */
.scan__thumbnum {
  position: absolute; left: 4px; top: 4px;
  min-width: 18px; padding: 0 5px; border-radius: 6px;
  background: rgba(0, 0, 0, .62); color: #fff;
  font-size: 11px; font-weight: 700; line-height: 18px; text-align: center;
}
.scan__drop {
  position: absolute; right: -6px; top: -6px;
  width: 28px; height: 28px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card); border: 1px solid var(--line); color: var(--muted);
  box-shadow: var(--shadow);
}
.scan__drop:hover { color: var(--danger); border-color: var(--danger); }
/* Whose card this block is, when a read came back with more than one person on it. */
.scan__who {
  display: flex; align-items: baseline; gap: 2px; flex-wrap: wrap;
  padding: 14px 0 4px; font-size: 14px; color: var(--ink);
}
.scan__who span { color: var(--muted); font-size: 13px; }

/* ── typeahead (used by the editor's relation field and the label picker) ── */

.typeahead { position: relative; }
.typeahead__list {
  position: absolute; left: 0; right: 0; top: calc(100% + 4px); z-index: 30;
  background: var(--card); border: 1px solid var(--line); border-radius: 10px;
  box-shadow: var(--shadow-lg); max-height: 260px; overflow-y: auto;
  list-style: none; margin: 0; padding: 4px;
}
.typeahead__item {
  display: flex; align-items: center; gap: 10px; width: 100%;
  padding: 8px 10px; border-radius: 8px; text-align: left; font-size: 14px;
}
.typeahead__item[aria-selected="true"], .typeahead__item:hover { background: var(--accent-wash); }

/* ── tablet ─────────────────────────────────────────────────────────── */

@media (min-width: 600px) {
  .pane--form { padding: 24px 24px 96px; }
  .section { margin: 16px 24px; }
  .hero { padding: 32px 24px 20px; }
  .formbar { margin: 0 -24px -96px; }
  .formbar .btn { flex: 0 0 auto; min-width: 120px; }
  .sheet {
    inset: 50% auto auto 50%; transform: translate(-50%, -50%);
    width: min(560px, calc(100vw - 48px)); border-radius: 16px;
    max-height: min(84dvh, 780px); animation: pop .16s ease-out;
  }
  @keyframes pop { from { transform: translate(-50%, -48%) scale(.98); opacity: 0; } }
  .sheet__grip { display: none; }
  .snackbar { left: auto; right: 24px; width: min(420px, calc(100vw - 48px)); }
}

/* ── desktop ────────────────────────────────────────────────────────── */

@media (min-width: 900px) {
  .app {
    grid-template-areas: "bar bar" "rail main";
    grid-template-columns: var(--rail) minmax(0, 1fr);
    grid-template-rows: auto minmax(0, 1fr);
  }

  /* The drawer stops being a sheet and becomes a permanent rail. */
  .drawer, .drawer--open {
    display: block; grid-area: rail; position: static; inset: auto; width: auto;
    animation: none; box-shadow: none; background: var(--paper);
    padding-bottom: 24px;
  }
  .scrim { display: none; }
  .bottomnav { display: none; }
  .only-narrow { display: none !important; }

  .fab { bottom: 24px; right: 24px; }
  .pane { padding-bottom: 96px; }
  .azrail { display: none; }
  .searchbar { max-width: 640px; }
  .topbar { padding: 8px 16px; }

  .crow { padding-left: 24px; padding-right: 24px; }
  .clist__letter { padding-left: 24px; }
  .listhead { padding-left: 24px; padding-right: 24px; }
}

/* Two panes: the list keeps its place while a card is read beside it, which is what
   makes going through a label on a laptop bearable. */
@media (min-width: 1280px) {
  .app--split {
    grid-template-areas: "bar bar bar" "rail main detail";
    grid-template-columns: var(--rail) minmax(320px, 420px) minmax(0, 1fr);
  }
  .detailpane {
    grid-area: detail; overflow-y: auto; border-left: 1px solid var(--line);
    background: var(--paper);
  }
  .app--split .fab { right: auto; left: calc(var(--rail) + 24px); }
  .app--split .crow { padding-left: 16px; padding-right: 16px; }
  .app--split .clist__letter, .app--split .listhead { padding-left: 16px; }
}

/* ── print ──────────────────────────────────────────────────────────── */
/* Google Contacts prints, so this does too. What comes out is the selection as a plain
   list — no app bar, no drawer, no buttons, black on white whatever the theme. */

@media print {
  .topbar, .selbar, .drawer, .bottomnav, .fab, .azrail, .snackbar, .scrim,
  .quickrow, .frow__actions, .crow__actions, .formbar, .sheet, .listhead { display: none !important; }
  :root {
    --ink: #000; --ink-soft: #222; --muted: #555;
    --paper: #fff; --card: #fff; --line: #bbb; --line-soft: #ddd;
  }
  body { background: #fff; font-size: 11pt; }
  .app { display: block; height: auto; }
  .main { overflow: visible; }
  .pane { padding: 0; }
  .section, .card { border: none; margin: 0 0 8pt; break-inside: avoid; }
  .crow { break-inside: avoid; border-bottom: 1px solid #ddd; min-height: 0; padding: 6pt 0; }
  .avatar { display: none; }
  .clist__letter { background: none; border-bottom: 1px solid #999; margin-top: 10pt; }
  a { color: #000; text-decoration: none; }
  /* A printed address book is useless without the addresses spelled out. */
  .frow__value a::after { content: ""; }
}
