/* ══════════════════════════════════════════════════════════════
   Meetback — Contacts (the saved-contacts list at /contacts)

   Lifted from the pinned reference
   design-system/products/meetback/assets/contacts.html @ 18f77335,
   a signet-transcript already re-skinned to the Meetback names by the
   transcriber. This file finishes that job against the tokens as they exist in
   this repo, the same mapping scanner.css made:

     reference :root       → this repo
     --ink      var(--ink-900)    → var(--ink)                (tokens.css)
     --mist     var(--page)    → var(--surface-muted)      (the transcript's own
                             "var(--surface2) → var(--mist)" re-skin note)
     --surface             → var(--surface)
     --border/--muted/--text → the vars cards-home.css declares at :root
     --font-display        → var(--font-display)       ('Fraunces', serif)
     --r-card   16px       → var(--r-md)               (exactly 16px — the one
                             reference radius that already has a token)
     24 / 15 / 13.5 / 12.5px → --t-xl / --t-base / --t-sm / --t-xs, whose
                             clamp() maxima are those sizes (12.5 → 12, half a
                             pixel), so the drawing holds at 460px and stays
                             fluid below it (iOS-first adaptive invariant).

   The 9px avatar label keeps its literal: it is a 44px badge, not body copy,
   and there is no token below 11px. Shrinking it fluidly would make "LEAD"
   illegible on the narrow device this app is built for first.

   DEPENDS ON cards-home.css BEING LOADED FIRST, exactly as welcome.css and
   scanner.css do: --border, --muted, --text, --accent and the safe-area vars
   (--sat/--sab/--sal/--sar) are declared there, not in tokens.css.

   ⚠ THIS FILE IS NOT LINKED YET. public/index.html is outside this build's
   write scope, so the <link rel="stylesheet" href="/src/styles/contacts.css">
   — after cards-home.css, for the vars above — is the main thread's to add,
   together with the sw.js precache entry and VERSION bump (A5). Until then the
   screen renders unstyled. It is reported in `deviations`.

   Everything is scoped under `.contacts` because the reference's class names
   are deliberately generic — .wrap, .row, .name, .empty, .count — and unscoped
   they would collide with the other screens' stylesheets in this
   single-document app. `.row`, `.name` and `.sub` in particular already exist
   in editor.css and public-card.css.
   ══════════════════════════════════════════════════════════════ */

/* THE SCROLL CONTAINER MOVED ONE LEVEL IN, and that is the whole of finding 3.

   `.contacts` used to be the scroller AND hold the status line as its last
   child, so every message this screen produces was written below 50 rows of
   list. Measured on WebKit at 375×667 with 50 contacts, scrolled to row 24: the
   arming warning and a failed delete's error both landed 1534px below the fold.
   A failed destructive action was silent — not quiet, silent.

   So `.contacts` is now a plain flex column that does not scroll, `.ct-scroll`
   takes the overflow, and `.ct-status` is its SIBLING. The message is pinned
   above the tab bar and is on screen wherever the list happens to be. */
.contacts {
  flex: 1 1 auto;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  font-family: var(--font-body);
  color: var(--text);
  background: var(--bg);
}

.contacts .ct-scroll {
  flex: 1 1 auto;
  min-height: 0;
  /* The list is the one screen in this app with unbounded content: it grows a
     row per contact and must scroll inside the shell rather than growing the
     shell, which is pinned to the viewport. */
  overflow-y: auto;
  /* Explicit, for the reason scanner.css gives: `overflow-y:auto` alone makes
     overflow-x compute to `auto` too, and THIS box is the scroll container, so
     #app{overflow-x:hidden} cannot help it. A pasted email address or a long
     company name is one unbreakable token wider than an SE. */
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
}

/* The reference's `.wrap{max-width:460px;margin:0 auto;padding:8px 2px}`. The
   2px gutter is right for a 460px page centred in a desktop window and wrong in
   the app, where the shell is edge-to-edge and the rows would touch the screen.

   EACH SAFE-AREA INSET IS PAID EXACTLY ONCE, and only the top is paid here:
   left/right are applied by #app for every screen (cards-home.css), and bottom
   is paid by the tab bar, which is an opaque sibling in the flex column rather
   than an overlay. Paying either again cost 82px of content width and 34px of
   dead scroll on the scanner; the same arithmetic applies here. */
.contacts .wrap {
  width: 100%;
  max-width: 460px;
  margin: 0 auto;
  padding: calc(8px + var(--sat)) clamp(14px, 4.5vw, 18px) 8px;
}

/* Q45 — INTER, NOT FRAUNCES, and this is the one correction all three
   directions carried. LANGUAGE.md §2.1 reserves the display face for the name
   on a card: it is the printed-object signal and it is spent by repetition. A
   screen title is not a card, so the weight leads instead. Same spend the share
   hub's card switcher made in Q42. */
.contacts .screen-title {
  font-family: var(--font-body);
  font-size: var(--t-xl);            /* clamp(20px, 5.5vw, 24px) — 24px at width */
  font-weight: 800;
  letter-spacing: -.02em;
  margin: 6px 0 14px;
}

.contacts .count {
  font-family: var(--font-body);
  font-size: var(--t-base);          /* the reference's 15px */
  color: var(--muted);
  font-weight: 400;
}

/* ── the row ───────────────────────────────────────────────────
   The reference's row is one flex line. It gains `flex-wrap` because the
   expansion (data-view, request §4) is a full-width child that drops to a
   second line INSIDE the same card — that is what "reuse the row's own visual
   language" means here: no second surface, no new radius, no new border. */

/* ── Q45 / DIRECTION B — THE CARDS YOU WERE GIVEN ──────────────
   Owner, 2026-08-08: "Each row carries that person's own accent colour as an
   edge, the way a wallet keeps a pass looking like whoever issued it." It won
   over the roster because everywhere else in this app a person's accent is what
   identifies them, and in this list it appeared nowhere — the one screen about
   people you have met had no trace of them in it.

   ⚠ THE EDGE IS A SURFACE, NEVER INK. LANGUAGE.md §2.5 / Q24: the accent
   paints, text takes its surface's ink. `.name` stays --text on --surface at
   every one of the eight accents, including the pale one that measured 1.16:1
   and started that rule. This direction is precisely the one that puts an
   arbitrary user colour next to a name, so the gate is load-bearing here rather
   than incidental.

   ⚠ `--ct-edge` IS SET PER ROW BY screens/contacts.js, from that contact's own
   identity — the ruling says "that person's own", and the proposal's single
   `var(--profile)` would have painted one colour down the whole list. The
   fallback is the app accent, so a row whose script never ran still has an
   edge rather than a 4px hole.

   §2.2 also applies and is the reason the 1px border comes off: a card is
   defined by its radius and its inset, not by a border. The shadow that
   replaces it is the single soft ambient token, not a stack.

   ACCEPTED COST, ruled and recorded so a later fire does not try to recover it:
   four or five contacts fill the screen where the roster direction would have
   held nine or ten. If the density turns out to matter more than the identity
   that is a new observation and a new item, not a re-reading of this answer. */
.contacts .row {
  background: var(--surface);
  border: 0;
  border-left: 4px solid var(--ct-edge, var(--accent));
  border-radius: var(--r-md);        /* the reference's --r-card:16px */
  box-shadow: 0 1px 3px var(--sheet-shadow);
  /* 13px on the left, not 16: the edge is 4px wide and pays for the other 3. */
  padding: 14px 16px 14px 13px;
  margin-bottom: 10px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 13px;
  /* The detail panel's hairline runs the full width of the row; without this it
     would cross the coloured edge and cut it in two. */
  overflow: hidden;
}

/* OPEN — the row whose detail panel is showing.
   The class was already applied and styled nothing, so an expanded row was
   visually identical to a closed one: measured border and shadow were byte-for
   -byte the same. Tapping a row therefore had no legible effect if the panel
   itself opened off screen, which it did. This is the reference's own ink
   colour — no new element, no chevron, no second visual language — as a ring
   that lifts the row off the list.

   ⚠ THE RING IS ADDED TO THE SHADOW, NOT INSTEAD OF IT, and the left border is
   NOT recoloured to --ink: that border is the person's edge and an open row is
   still their row. Direction B keeps the edge through every state. */
.contacts .row.open {
  box-shadow: 0 0 0 1px var(--ink), 0 1px 3px var(--sheet-shadow);
}

/* PENDING — a destructive write is in flight against this row.
   It is deliberately NOT removed from the list: an unacknowledged delete has
   not happened, and a row that vanishes before the server says so is a lie
   told in the direction the user cannot check. It is dimmed, its trash button
   is disabled, and it carries aria-busy. */
.contacts .row.pending {
  opacity: .55;
}

/* Q45/B — 48px, and it carries INITIALS rather than the reference's literal
   `LEAD`. The size is the direction's (a pass has a face); the content is the
   correction that goes with it — see initials() in screens/contacts.js for why
   LEAD stopped being true when the scanner shipped.
   The 9px literal goes with it: two letters at 16px need no exemption from the
   type scale, and --t-xs is the rung that fits. */
.contacts .avatar {
  width: 48px;
  height: 48px;
  border-radius: 50%;
  /* NOT the accent. legible_accent_text: the pale accent measured 1.16:1 and
     the edge is the only thing on this screen allowed to be arbitrary colour. */
  background: var(--ink);
  color: var(--surface);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: var(--t-xs);
  font-weight: 700;
  letter-spacing: .02em;
  flex-shrink: 0;
  cursor: pointer;
}

/* `.who` is a real <button> in the build (the reference draws a div with
   cursor:pointer). These four lines strip the UA button back to the reference's
   drawing; everything visible below is the reference's own. */
.contacts .who {
  flex: 1;
  min-width: 0;
  cursor: pointer;
  appearance: none;
  background: none;
  border: 0;
  padding: 0;
  margin: 0;
  font: inherit;
  color: inherit;
  text-align: left;
  /* 44pt. Measured at 201×33 before this line — under the minimum on the
     control that opens a contact. The row is already 44px tall (the avatar sets
     that), so centring two lines of text in 44px changes no layout and no
     drawing; it only makes the target the size the drawing already implies. */
  min-height: 44px;
  display: flex;
  flex-direction: column;
  justify-content: center;
}
.contacts .name {
  display: block;
  font-weight: 700;
  font-size: var(--t-base);          /* the reference's 15px */
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.contacts .sub {
  display: block;
  font-size: var(--t-xs);            /* the reference's 12.5px */
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.contacts .icon-btn {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--surface-muted);  /* the transcript's --mist */
  border: none;
  color: var(--text);
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--t-xs);
  font-weight: 600;
  transition: background .15s, color .15s, opacity .15s;
  position: relative;
}

/* 44pt WITHOUT REDRAWING THE BUTTON. The reference draws a 36px circle and
   that is what stays on screen; this pseudo-element extends the HIT AREA to 44
   in both axes (and to the full width of the armed pill, which is wider). 4px
   of overhang each side sits inside the row's own 13px gap, so it cannot reach
   the .who button beside it. Repainting the circle at 44px would have changed
   the drawing to fix a touch problem; this changes only the thing that was
   wrong. */
.contacts .icon-btn::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  width: max(44px, 100%);
  height: 44px;
}

/* WORKING — the delete is in flight. Disabled, so it cannot be tapped again;
   the reference has no drawing for this state because the reference has no
   writes. It keeps the circle and loses the icon. */
.contacts .icon-btn.working {
  opacity: .6;
  cursor: default;
}
.contacts .icon-btn:disabled { pointer-events: none; }

/* ARMED — the first tap of the two-tap confirm (request §4). The button stops
   being a 36px circle and becomes a labelled pill, because the second tap has
   to land on something that says what it will do. Red is the only place in this
   screen that leaves the reference's palette, and it is carrying the one action
   that cannot be undone; --err is the token already declared for exactly that. */
.contacts .icon-btn.armed {
  width: auto;
  padding: 0 14px;
  border-radius: var(--r-pill);
  background: var(--err);
  color: var(--surface);
  letter-spacing: .01em;
}

/* The reference draws no focus ring — a static drawing cannot. These are real
   controls on a touch-and-keyboard surface, so they get one. */
.contacts .who:focus-visible,
.contacts .icon-btn:focus-visible,
.contacts .link:focus-visible,
.contacts #ctList:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 2px;
  border-radius: 6px;
}
/* #ctList takes focus programmatically after a delete (the row that held focus
   is gone). That is a script move, not a user one, so it must not draw a ring —
   :focus-visible above covers the keyboard case and this covers the rest. */
.contacts #ctList:focus { outline: none; }

/* ── the expansion ─────────────────────────────────────────────
   A wrapped line inside the row. `flex-basis:100%` is what drops it below the
   avatar/name/trash line; the negative-free padding and the hairline above it
   are the row's own border colour, so nothing new is introduced. */
.contacts .detail {
  flex: 0 0 100%;
  border-top: 1px solid var(--border);
  padding-top: 10px;
  margin-top: 1px;
}
/* CSS must never set `display` on .detail unconditionally — that would beat the
   UA stylesheet's [hidden] and leave a collapsed panel visible. Honoured here
   explicitly so the rule above cannot be "tidied" into a display declaration. */
.contacts .detail[hidden] { display: none; }

.contacts .det-row {
  display: flex;
  align-items: baseline;
  gap: 10px;
  padding: 4px 0;
}

/* A row whose value is a tel: or mailto: LINK. Dialling the number is the
   entire reason this panel exists, and the anchors measured 229×16px, 4px
   apart. The row becomes 44pt tall and the anchor fills it, so the target is
   the line you can see rather than the sixteen pixels of text on it. */
.contacts .det-row.det-tap {
  align-items: stretch;
  padding: 0;
  min-height: 44px;
}
.contacts .det-row.det-tap .det-k {
  display: flex;
  align-items: center;
}
/* Two 44pt targets that abut share an edge, and the edge is where a mis-tap
   dials the wrong thing. Six pixels of nothing between them is cheaper than
   phoning someone you meant to email. */
.contacts .det-row.det-tap + .det-row.det-tap { margin-top: 6px; }

.contacts .det-k {
  flex: 0 0 68px;
  font-size: var(--t-xs);
  color: var(--muted);
}
.contacts .det-v {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-sm);
  /* A pasted email or a scanned phone number is one unbreakable token, and it
     is the widest thing this screen can contain. `anywhere` also shrinks the
     box's min-content width, so it cannot push the row wide in the first
     place; nothing is truncated. */
  overflow-wrap: anywhere;
}
.contacts .det-none { color: var(--muted); }
.contacts .link {
  color: var(--accent);
  text-decoration: none;
  /* The anchor, not its text, is the target — see .det-tap above. */
  display: flex;
  align-items: center;
  min-height: 44px;
}
.contacts .link:hover { text-decoration: underline; }

/* ── empty and loading ─────────────────────────────────────────
   One box for both, as the reference has it. The copy differs from the
   transcript's and that is deliberate — see the header of screens/contacts.js
   and build request §5b.

   ⚠ THE STATES ARE RESTYLED EXPLICITLY, NOT LEFT TO INHERIT (Q45). This
   screen's failure copy was rebuilt twice in one week (Q34, Q36) and a fifth
   cause once rendered blank because a lookup table was missed. A redesign that
   only dresses the happy path is how that happens again — so `empty` and
   `state-bad` each take the new surface treatment by name.

   THEY DO NOT TAKE THE ACCENT EDGE. A state panel is not a person, and a 4px
   colour down the side of "we could not read your contacts" would read as a
   contact whose name failed to load. */
.contacts .empty {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--r-md);
  box-shadow: 0 1px 3px var(--sheet-shadow);
  padding: clamp(22px, 6.5vw, 26px) clamp(16px, 5vw, 20px);
  text-align: center;
  color: var(--muted);
  font-size: var(--t-sm);            /* the reference's 13.5px */
  line-height: 1.5;
}
.contacts .empty b { color: var(--text); font-weight: 600; }

/* UNAVAILABLE — the same box as `empty`, deliberately NOT the same box.
   "No contacts yet" and "we could not read your contacts" are different claims
   about the world and the second one must not be able to pass for the first.
   The border carries --err, which this screen already uses for the one
   irreversible action, and the heading line stays in the text colour so the
   panel reads as a statement rather than an alarm. */
.contacts .empty.state-bad {
  border-color: var(--err);
  text-align: left;
}

/* ⚠ WHAT DIRECTION B DELIBERATELY DID NOT TAKE FROM DIRECTION C, ruled by the
   owner in the same breath as the pick: C was the direction that gave the armed
   delete confirm its own surface, and B keeps the pinned line as it is. The
   armed confirm is the one irreversible action on this screen and it sits in
   small muted text on the page colour. If that is still hard to see now B has
   shipped, it is a defect worth its own item — not something to smuggle in
   under this pick. Nothing below `.ct-status` changed in this commit. */

/* ── the status line ───────────────────────────────────────────
   PINNED. A sibling of the scroller, not its last child — see the note at
   `.contacts` for the measurement that forced it. Arming the delete, the
   outcome of one, a stale read and a failed read all speak here, and the user
   sees every one of them without scrolling.

   It is not `position:fixed`: the tab bar is an opaque sibling in the same flex
   column, so a normal flow child above it is already pinned, pays no safe-area
   inset twice, and cannot cover a row. */
.contacts .ct-status {
  flex: 0 0 auto;
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 0 clamp(14px, 4.5vw, 18px);
  background: var(--bg);
}
/* Empty is the common case, and an empty bar must occupy nothing — the list
   should not be shortened by a message that is not there. The region itself
   stays in the DOM: a live region that is inserted or display-toggled at the
   moment its text changes is not reliably announced.

   The class is set by say() rather than derived with `:has(.ct-msg:empty)`,
   which would be tidier and is not universally available on the iOS versions
   this app targets — and a dropped rule here means the bar loses its rule and
   its breathing space at exactly the moment it is carrying an error. */
.contacts .ct-status.on {
  padding-top: 8px;
  padding-bottom: 10px;
  border-top: 1px solid var(--border);
}

.contacts .ct-msg {
  flex: 1 1 auto;
  min-width: 0;
  font-size: var(--t-xs);
  line-height: 1.45;
  color: var(--muted);
  margin: 0;
  overflow-wrap: anywhere;
}
.contacts .ct-msg:empty { display: none; }

/* Tone. `warn` carries the armed confirm and a stale list — things that are
   true and want reading. `error` carries a failure. */
.contacts .ct-msg[data-tone="warn"] { color: var(--text); }
.contacts .ct-msg[data-tone="error"] { color: var(--err); font-weight: 600; }

/* The surface's ONE action slot: whatever there is to try again right now.
   Never two — a screen with competing "try again" buttons has not told the user
   which one is theirs. 44pt, because it is only ever offered on a failure. */
.contacts .ct-retry {
  flex: 0 0 auto;
  min-height: 44px;
  padding: 0 16px;
  border: 1px solid var(--ink);
  border-radius: var(--r-pill);
  background: var(--ink);
  color: var(--surface);
  font-family: inherit;
  font-size: var(--t-xs);
  font-weight: 600;
  cursor: pointer;
}
.contacts .ct-retry[hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .contacts .icon-btn { transition: none; }
}

/* ══════════════════════════════════════════════════════════════
   Q65 — SENT TO YOU: the second section, and it borrows rather than invents.

   A lead row IS a contact row — same card, same radius, same accent edge, same
   expansion — because they are the same subject seen from opposite ends: a card
   the owner captured, and a person who handed themselves over. A second visual
   language here would say they are different KINDS of thing, which they are
   not; what differs is how each arrived, and the sub-line carries that.

   So this file gains exactly ONE declaration: the space above the second heading.
   Everything else a lead row needs, a contact row already had.
   ══════════════════════════════════════════════════════════════ */
.contacts .screen-title.ct-second { margin-top: 30px; }

/* ⚠ AND THERE IS NO `.row.lead` RULE, WHICH IS THE POINT. One was written —
   padding-right: 16px, to close the gap where a contact's delete button sits —
   and MEASURED against `.contacts .row`, which already declares exactly that.
   A declaration that changes nothing is worse than none: it reads as a
   difference the next person has to account for. The class is still on the
   element, so a real difference has somewhere to go the day there is one. */
