/* =========================================================
   Mekudeshet — the matchmaker portal.

   Deliberately not the marketing site's stylesheet. This is the same tool the
   iOS app is, on a bigger screen: the tokens below are lifted from
   ios/Mekudeshet/Theme/Theme.swift so a shadchan moving between phone and
   laptop is looking at one product, not two.

   Layout follows the same rule as the app — a bottom tab bar on a phone, and
   the same four destinations as a rail once there's room for one.
   ========================================================= */

:root {
  /* One plum accent. No gradients, no gold, no glow. */
  --accent: #6E4067;
  --accent-pressed: #5A3354;
  --accent-soft: #F0E9EF;

  --ink: #18141A;
  --ink-2: #6E6873;
  --ink-3: #A8A2AD;

  --bg: #FBFAFB;
  --card: #ffffff;
  /* An engaged couple's card — "super pale lavender" (case 24). A wash off
     the accent, not a second accent. */
  --card-celebrated: #F7F0F6;
  --line-celebrated: #E7D8E5;
  --fill: #F2F0F3;
  --line: #ECE8EC;

  /* Colour carries state, in the brand's own family rather than green and
     amber (case 107). Plum is settled, rose is waiting on a person. Chosen by
     measurement, not taste: plum reads 8.35:1 on a white card where the old
     green managed 3.46, and the two separate 1.68 where green and amber
     managed 1.37 — so the meaning survives the palette. Keep in step with
     `activeUI` / `waitingUI` in Theme.swift. */
  --ok: #6B3F63;
  --ok-soft: #F0E8EF;
  --wait: #9A5F6E;
  --wait-soft: #F8EDEF;
  --stop: #A33A2B;
  --stop-soft: #F8EBE8;
  --whatsapp: #25D366;
  /* The second series in a paired chart. Categorical, not semantic:
     reusing the green would read as a status rather than a category. */
  --series-2: #2E6FA8;

  --r: 16px;
  --r-sm: 12px;
  --r-xs: 10px;
  --shadow: 0 10px 30px -18px rgba(24, 20, 26, .35);
  --tabbar: 74px;
  --sans: -apple-system, BlinkMacSystemFont, "Inter", "Segoe UI", Roboto, sans-serif;
}

@media (prefers-color-scheme: dark) {
  :root {
    --accent: #C9A5C2; --accent-pressed: #B892B1; --accent-soft: #2A2130;
    --ink: #F2EFF3; --ink-2: #A9A2AE; --ink-3: #776F7C;
    --bg: #141116; --card: #1C181E; --fill: #241F26; --line: #2B252D;
    /* In the dark it has to lift off the card rather than pale down to it. */
    --card-celebrated: #261E2A; --line-celebrated: #3A2E40;
    --ok: #C49CBC; --ok-soft: #2A2130; --wait: #D3A0AD; --wait-soft: #30222A;
    --stop: #DE8A75; --stop-soft: #37211C;
    --series-2: #7FB2E0;
  }
}

* , *::before, *::after { box-sizing: border-box; }

html, body { height: 100%; }
body {
  margin: 0;
  background: var(--bg);
  color: var(--ink);
  font: 16px/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;
}
img { max-width: 100%; display: block; }
button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; border: 0; background: none; }
a { color: var(--accent); text-decoration: none; }
h1, h2, h3, h4 { margin: 0; letter-spacing: -.02em; line-height: 1.15; }

.hidden { display: none !important; }
.sr-only {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

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

/* How wide the reading column is allowed to get. Overridden upward at the
   breakpoints below; everything that has to stay in step with it — the
   docked record panel — is calculated from it rather than repeating it. */
:root {
  --reading: 1180px;
  /* The horizontal inset of a list row. A table header has to match it
     exactly or the columns do not line up with the cells beneath them, so it
     is named once and both read it. Found the hard way: `.row` was widened to
     18px at 1200 and every header stayed at 16. */
  --row-x: 16px;
}

#shell { min-height: 100%; display: flex; flex-direction: column; }

.rail { display: none; }

.screen {
  flex: 1;
  padding: 20px 20px calc(var(--tabbar) + 28px);
  max-width: 780px;
  width: 100%;
  margin: 0 auto;
}

/* The app's TabHeader: a big left-aligned title with one trailing control. */
.screen-head {
  display: flex; align-items: flex-start; justify-content: space-between;
  gap: 16px; margin-bottom: 16px;
}
.screen-head h1 { font-size: 30px; font-weight: 700; }
.screen-head .sub { color: var(--ink-2); font-size: 14px; margin-top: 2px; }
.head-actions { display: flex; gap: 10px; flex-shrink: 0; }

.icon-btn {
  width: 40px; height: 40px; border-radius: 50%;
  display: grid; place-items: center;
  background: var(--card); border: 1px solid var(--line); color: var(--accent);
  transition: transform .12s, background .12s;
}
.icon-btn:active { transform: scale(.96); }
.icon-btn.filled { background: var(--accent); color: #fff; border-color: transparent; }
@media (prefers-color-scheme: dark) { .icon-btn.filled { color: #1a1520; } }

/* ---------- Tab bar (phone) ---------- */

.tabbar {
  position: fixed; left: 50%; transform: translateX(-50%); bottom: 12px;
  width: min(440px, calc(100% - 24px));
  /* Was a fixed four. Home made it five and Admin makes it six for admins,
     and a hardcoded count wraps the bar onto two rows. */
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: color-mix(in srgb, var(--card) 88%, transparent);
  -webkit-backdrop-filter: blur(18px); backdrop-filter: blur(18px);
  border: 1px solid var(--line); border-radius: 999px;
  padding: 8px; z-index: 40; box-shadow: var(--shadow);
}
.tabbar button {
  display: flex; flex-direction: column; align-items: center; gap: 3px;
  padding: 7px 0; border-radius: 999px;
  color: var(--ink-2); font-size: 11px; font-weight: 500;
}
.tabbar button svg { width: 21px; height: 21px; }
/* Six labels have to fit the same pill four used to. */
@media (max-width: 420px) {
  .tabbar button { font-size: 10px; }
  .tabbar button svg { width: 19px; height: 19px; }
}
.tabbar button[aria-current="page"] { color: var(--accent); background: var(--accent-soft); }
.tabbar .badge {
  position: absolute; transform: translate(14px, -6px);
  min-width: 16px; height: 16px; padding: 0 4px; border-radius: 999px;
  background: var(--wait); color: #fff; font-size: 10px; font-weight: 700;
  display: grid; place-items: center;
}

/* ---------- Cards, rows, chips ---------- */

.card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow);
}
.card.pad { padding: 18px; }
.card + .card { margin-top: 14px; }

.rows > * + * { border-top: 1px solid var(--line); }
.row {
  display: flex; align-items: center; gap: 14px;
  padding: 13px var(--row-x); width: 100%; text-align: left; color: inherit;
}
.row:hover { background: var(--fill); }
.row .grow { flex: 1; min-width: 0; }
.row .title { font-weight: 500; }
.row .meta { font-size: 13px; color: var(--ink-2); }
.row .chev { color: var(--ink-3); flex-shrink: 0; }

.chips { display: flex; gap: 8px; overflow-x: auto; padding: 2px 0 10px; scrollbar-width: none; }
.chips::-webkit-scrollbar { display: none; }
/* Facet chips wrap instead of scrolling sideways: one real option reads
   "Growing in modesty - wears skirts shabbat / sometimes", and two others
   differ only at the end ("goes to class often" / "sometimes"), so anything
   that pushes them off the edge or truncates them hides the difference. */
.chips.wrap { flex-wrap: wrap; overflow-x: visible; }
.chips.wrap .chip { white-space: normal; text-align: left; }
.chip[disabled] { opacity: .4; cursor: default; }
/* Case 70 — hearting somebody from the roster, not only from their profile. */
.row-heart {
  display: grid; place-items: center; width: 38px; height: 38px;
  border-radius: 50%; color: var(--ink-3, var(--ink-2)); cursor: pointer; flex-shrink: 0;
}
.row-heart:hover { background: var(--fill); }
.row-heart.on { color: var(--accent); }
.row-heart.on svg { fill: currentColor; }
.chip {
  white-space: nowrap; padding: 8px 14px; border-radius: 999px;
  background: var(--fill); color: var(--ink-2); font-size: 14px;
}
.chip[aria-pressed="true"] { background: var(--accent); color: #fff; font-weight: 600; }
@media (prefers-color-scheme: dark) { .chip[aria-pressed="true"] { color: #1a1520; } }
.chip .n { font-size: 11px; font-weight: 700; opacity: .75; margin-left: 4px; }

.section-head { font-size: 15px; font-weight: 600; margin: 22px 0 8px; }
.section-note { font-size: 13px; color: var(--ink-2); margin: -4px 0 10px; }

/* ---------- Avatars, badges ---------- */

.avatar {
  /* `position: relative` so the photograph can be pinned to the box — see
     the note on `.avatar img`. */
  position: relative;
  border-radius: 50%; background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; font-weight: 600; flex-shrink: 0;
  width: 46px; height: 46px; font-size: 15px; overflow: hidden;
}
.avatar.lg { width: 92px; height: 92px; font-size: 30px; }
.avatar.sm { width: 34px; height: 34px; font-size: 12px; }
/*
  Pinned, not sized in percentages.

  Kim, on a roster card: "how do we fix this on desktop?" — a band of
  `--accent-soft` across the top of the photograph. This was `width: 100%;
  height: 100%; object-fit: cover`, which reads as though it fills the box and
  does not: the parent is `display: grid; place-items: center`, so the image is
  a centred grid item rather than a stretched one, and the percentage height
  resolves against an indefinite area. Chrome laid a 217x372 photograph out at
  116x199 and clipped it; Safari left a gap.

  Absolute positioning with `inset: 0` gives the image a box that is definite
  by construction, in every engine, without depending on how percentages
  resolve inside a grid area. Measured rather than assumed: rendered headless
  at 116x116 before and after, which is how the 116x199 was found.

  `object-position` is raised above centre because these are photographs of
  people and a person's face is not in the middle of a full-length shot.
  Centred, Chaya Ovadia's card cropped to her waist.
*/
.avatar img {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%;
  object-fit: cover; object-position: 50% 22%;
}

.badge {
  display: inline-flex; align-items: center; gap: 5px;
  font-size: 12px; font-weight: 500; padding: 3px 9px; border-radius: 999px;
  background: var(--fill); color: var(--ink-2);
}
.badge.ok { background: var(--ok-soft); color: var(--ok); }
.badge.wait { background: var(--wait-soft); color: var(--wait); }
.badge.stop { background: var(--stop-soft); color: var(--stop); }

/* ---------- Buttons & forms ---------- */

.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 8px;
  padding: 13px 20px; border-radius: 14px; font-weight: 600; font-size: 15px;
  background: var(--fill); color: var(--ink); transition: transform .12s, opacity .12s;
}
.btn:active { transform: scale(.985); }
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn.primary { background: var(--accent); color: #fff; }
@media (prefers-color-scheme: dark) { .btn.primary { color: #1a1520; } }
.btn.ok { background: var(--ok); color: #fff; }
.btn.block { width: 100%; }
.btn.sm { padding: 9px 14px; font-size: 13.5px; border-radius: 10px; }
.btn.danger { color: var(--stop); }

.field { display: block; margin-bottom: 14px; }
.field > span {
  display: block; font-size: 13px; font-weight: 500;
  color: var(--ink-2); margin-bottom: 6px;
}
.field input, .field select, .field textarea {
  width: 100%; padding: 12px 13px; border-radius: var(--r-sm);
  background: var(--fill); border: 1.5px solid transparent; color: var(--ink);
  transition: border-color .15s;
}
.field input:focus, .field select:focus, .field textarea:focus {
  outline: none; border-color: var(--accent);
}
.field textarea { resize: vertical; min-height: 110px; }
.field-row { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }

/* A single's own information, laid out by what each field is (case 53). */
.pf-head { display: flex; align-items: center; gap: 7px; }
.pf-head svg { color: var(--accent); }
.pf-row { align-items: baseline; }
.pf-label { flex: 1 1 auto; color: var(--ink-2); font-size: 14px; }
.pf-value { font-weight: 500; font-size: 14px; text-align: right; }
.pf-phone {
  font-weight: 500; font-size: 14px; text-align: right;
  color: var(--accent); text-decoration: none;
}
.pf-phone:hover { text-decoration: underline; }
/* `.rows > * + *` already draws the separator; this only sets the shape. */
.pf-stack { display: flex; flex-direction: column; gap: 5px; padding: 13px 16px; }
.pf-prose {
  margin: 0; font-size: 14px; font-weight: 500;
  line-height: 1.5; color: var(--ink);
}
.pf-list { margin: 0; padding-left: 18px; font-size: 14px; font-weight: 500; }
.pf-list li { margin: 2px 0; }

/* "Do they know?" — three choices, none of them preselected (case 49). */
.aware-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.aware-choice {
  padding: 11px 6px; border-radius: var(--r-sm); cursor: pointer;
  background: var(--fill); border: 1.5px solid var(--line);
  color: var(--ink); font-size: 13px; font-weight: 500;
  transition: background .15s, border-color .15s, color .15s;
}
.aware-choice:hover { border-color: var(--accent); }
.aware-choice.on {
  background: var(--accent); border-color: var(--accent); color: #fff;
}

.search {
  display: flex; align-items: center; gap: 9px;
  padding: 11px 13px; border-radius: var(--r-sm); background: var(--fill);
  margin-bottom: 12px;
}
.search input { flex: 1; background: none; border: 0; outline: none; }
.search svg { color: var(--ink-3); flex-shrink: 0; }

.switch { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 13px 16px; }
.switch input { appearance: none; width: 48px; height: 29px; border-radius: 999px; background: var(--line); position: relative; transition: background .18s; flex-shrink: 0; }
.switch input::after { content: ""; position: absolute; inset: 3px auto 3px 3px; width: 23px; border-radius: 50%; background: #fff; transition: transform .18s; }
.switch input:checked { background: var(--accent); }
.switch input:checked::after { transform: translateX(19px); }

/* ---------- States ---------- */

.empty { text-align: center; color: var(--ink-2); padding: 56px 24px; }
.empty svg { color: var(--ink-3); margin: 0 auto 14px; }
.empty h3 { font-size: 17px; margin-bottom: 6px; color: var(--ink); }
.empty p { margin: 0; font-size: 14px; }

.skeleton { background: var(--fill); border-radius: 8px; animation: pulse 1.4s ease-in-out infinite; }
@keyframes pulse { 0%, 100% { opacity: 1 } 50% { opacity: .5 } }

.toast {
  position: fixed; left: 50%; bottom: calc(var(--tabbar) + 22px);
  transform: translateX(-50%); z-index: 60;
  background: var(--ink); color: var(--bg);
  padding: 12px 18px; border-radius: 12px; font-size: 14px; max-width: 90vw;
  box-shadow: var(--shadow); animation: rise .2s ease-out;
}
.toast.err { background: var(--stop); color: #fff; }
@keyframes rise { from { opacity: 0; transform: translate(-50%, 8px) } }

/* ---------- Sheet (the app's modal) ---------- */

.sheet-backdrop {
  position: fixed; inset: 0; z-index: 50; background: rgba(20, 17, 22, .45);
  -webkit-backdrop-filter: blur(2px); backdrop-filter: blur(2px);
  display: flex; align-items: flex-end; justify-content: center;
  animation: fade .18s ease-out;
}
@keyframes fade { from { opacity: 0 } }
.sheet {
  background: var(--bg); width: min(560px, 100%);
  border-radius: 22px 22px 0 0; max-height: 92vh; overflow-y: auto;
  animation: slide .24s cubic-bezier(.22,1,.36,1);
}
@keyframes slide { from { transform: translateY(24px) } }
.sheet-head {
  position: sticky; top: 0; z-index: 1; background: var(--bg);
  display: flex; align-items: center; justify-content: space-between;
  gap: 12px; padding: 16px 18px 12px; border-bottom: 1px solid var(--line);
}
.sheet-head h2 { font-size: 17px; }
.sheet-body { padding: 18px; }

/* ---------- Case Loop ---------- */

.case-banner {
  display: flex; align-items: center; gap: 12px; width: 100%; text-align: left;
  padding: 15px 16px; border-radius: var(--r);
  background: var(--wait-soft); border: 1px solid color-mix(in srgb, var(--wait) 32%, transparent);
  color: inherit; margin-bottom: 14px;
}
.case-banner .t { font-weight: 600; font-size: 15px; }
.case-banner .s { font-size: 13px; color: var(--ink-2); }

.confirm-card { border: 1.5px solid color-mix(in srgb, var(--wait) 38%, transparent); }
.resolution {
  background: var(--fill); border-radius: var(--r-sm);
  padding: 14px; margin: 10px 0 14px; white-space: pre-wrap;
}
.timeline { list-style: none; margin: 0; padding: 0; }
.timeline li { display: flex; gap: 10px; padding: 7px 0; font-size: 13.5px; }
.timeline .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--line); margin-top: 7px; flex-shrink: 0; }
.timeline .who { font-weight: 500; }
.timeline .when { color: var(--ink-3); font-size: 12px; }

/* ---------- Match card ---------- */

.pair { display: flex; align-items: center; gap: 8px; padding: 4px 0 14px; }
.pair .side { flex: 1; text-align: center; }
/* Case 69 — "these thumb nails should be much bigger". The card was always
   this tall; the faces sat in the middle of it with air above and below. */
.pair .avatar.match { width: 96px; height: 96px; font-size: 32px; margin: 0 auto; }
.pair .side .nm { font-weight: 600; font-size: 14px; margin-top: 7px; }
.pair .heart { color: var(--accent); flex-shrink: 0; }
/* Unmatched carries a broken heart, not the same purple one (case 22). */
.pair .heart.off { color: var(--ink-3); }
.pair .avatar { margin: 0 auto; }

/* An ended idea stays where she left it, dimmed and labelled — it is not an
   unmatch, and it must not read as a live idea either (case 22). */
.card.ended { opacity: .58; }
.ended-flag {
  display: flex; align-items: center; gap: 5px;
  font-size: 12.5px; font-weight: 500; color: var(--ink-3);
  margin-bottom: 8px;
}

/* ---------- Grid ---------- */

.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(150px, 1fr)); gap: 12px; }
.tile { padding: 18px 12px; text-align: center; }
.tile .nm { font-weight: 600; font-size: 14px; margin: 9px 0 3px; }
.tile .mt { font-size: 12px; color: var(--ink-2); }

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

.auth { min-height: 100dvh; display: grid; place-items: center; padding: 24px; }
.auth-card { width: min(400px, 100%); text-align: center; }
.auth .mark {
  width: 78px; height: 78px; border-radius: 50%; margin: 0 auto 20px;
  background: var(--accent-soft); color: var(--accent);
  display: grid; place-items: center; font: 600 36px/1 ui-serif, Georgia, serif;
}
.auth h1 { font-size: 28px; margin-bottom: 4px; }
/* The wordmark is white artwork. The portal is light by default and flips on
   `prefers-color-scheme: dark`, exactly like every other colour in this file,
   so the logo is inverted to ink for the light card and left white for the
   dark one rather than being shipped as two files (cases 45 and 46). */
.wordmark {
  width: 240px; max-width: 70%; height: auto;
  display: block; margin: 0 auto; filter: invert(1);
}
@media (prefers-color-scheme: dark) { .wordmark { filter: none } }
.auth .tag { color: var(--ink-2); margin: 0 0 28px; }
.auth form { text-align: left; }
.auth .err { color: var(--stop); font-size: 14px; margin: 0 0 12px; display: flex; gap: 7px; }
.auth .back { display: block; margin-top: 22px; font-size: 14px; color: var(--ink-2); }

/* ---------- Desktop: the tab bar becomes a rail ---------- */

/*
  Desktop is a different shape, not a wider phone.

  Kim: "Move the menu to the top on desktop. Make the pages more desktop
  friendly instead of mobile just stretched."

  The sections used to sit in a 232px column down the left, which is the
  layout a phone's tab bar turns into when you have more height than width.
  On a monitor it is the wrong way round: height is the scarce dimension and
  width is the plentiful one, so the navigation goes along the top where it
  costs one row, and the whole width goes to the roster.

  No markup changed. The rail is already brand, then sections, then a spacer,
  then Sign out — which reads down a column or along a bar depending only on
  `flex-direction`.
*/
@media (min-width: 880px) {
  .tabbar { display: none; }
  .rail {
    display: flex; flex-direction: row; align-items: center; gap: 2px;
    width: 100%; padding: 10px 26px;
    border-bottom: 1px solid var(--line); background: var(--card);
    position: sticky; top: 0; z-index: 30;
  }
  .rail .brand {
    display: flex; align-items: center; gap: 10px;
    padding: 0 22px 0 0; margin-right: 6px; font-weight: 700;
    letter-spacing: -.02em; border-right: 1px solid var(--line);
    align-self: stretch;
  }
  .rail .brand .mark {
    width: 30px; height: 30px; border-radius: 50%; background: var(--accent-soft);
    color: var(--accent); display: grid; place-items: center;
    font: 600 16px/1 ui-serif, Georgia, serif; flex-shrink: 0;
  }
  .rail button {
    display: flex; align-items: center; gap: 9px;
    padding: 9px 14px; border-radius: 10px; color: var(--ink-2);
    font-size: 15px; font-weight: 500; width: auto; white-space: nowrap;
  }
  .rail button:hover { background: var(--fill); }
  .rail button[aria-current="page"] { background: var(--accent-soft); color: var(--accent); font-weight: 600; }
  /* Beside the label rather than pushed to the far end of a column. */
  .rail .badge { margin-left: 2px; background: var(--wait); color: #fff; }
  .rail .spacer { flex: 1; }
  .screen { padding: 30px 26px 64px; max-width: var(--reading); margin: 0 auto; }
  .sheet-backdrop { align-items: center; }
  .sheet { border-radius: 20px; max-height: 86vh; }
  .toast { bottom: 26px; }
}

@media (prefers-reduced-motion: reduce) {
  * { animation-duration: .001ms !important; transition-duration: .001ms !important; }
}

/* =========================================================================
   Home, resumes and the admin area.

   Added when the portal was brought level with the app. Everything here uses
   the same tokens as the rest of the sheet — there is no second palette, and
   dark mode comes free because the tokens already flip.
   ========================================================================= */

/* A plain search field, distinct from `.field input` which carries a label. */
.input {
  width: 100%;
  padding: 11px 14px;
  border: 1px solid var(--line);
  border-radius: 12px;
  background: var(--fill);
  color: var(--ink);
  font: inherit;
  font-size: 15px;
}
.input:focus { outline: none; border-color: var(--accent); background: var(--card); }

.btn.ghost {
  background: none;
  border: none;
  color: var(--accent);
  padding: 2px 0;
  gap: 4px;
}

.link { color: var(--accent); text-decoration: none; display: block; }
.link:hover { text-decoration: underline; }

/* The pipeline: one strip with four numbers in it, rather than four tiles of
   equal weight that make the eye stop at each in turn. */
.stat-strip { display: flex; padding: 16px 0; }
.stat-cell {
  flex: 1;
  background: none;
  border: none;
  border-left: 1px solid var(--line);
  cursor: pointer;
  padding: 0 8px;
  color: inherit;
  font: inherit;
}
.stat-cell:first-child { border-left: none; }
.stat-n { font-size: 24px; font-weight: 600; }
/* A zero is information, not an alarm — greying it lets the eye land on the
   stages that actually have something in them. */
.stat-n.dim { color: var(--ink-3); }
.stat-l { font-size: 13px; color: var(--ink-2); margin-top: 2px; }

.door-grid, .tile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
  gap: 12px;
}
.door {
  display: flex;
  align-items: center;
  gap: 11px;
  padding: 12px;
  text-align: left;
  cursor: pointer;
  border: 1px solid var(--line);
}
.door-icon {
  display: grid;
  place-items: center;
  width: 30px; height: 30px;
  flex: none;
  border-radius: 9px;
  background: var(--accent-soft);
  color: var(--accent);
}

.tile {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  min-height: 118px;
  padding: 14px 10px;
  cursor: pointer;
  border: 1px solid var(--line);
  text-align: center;
}
.tile-icon {
  display: grid;
  place-items: center;
  width: 44px; height: 44px;
  border-radius: 50%;
  background: var(--accent-soft);
  color: var(--accent);
}
.tile-label { font-size: 14px; font-weight: 500; }

/* A label/value row that can also open to explain itself. */
.kv {
  display: block;
  width: 100%;
  padding: 12px 14px;
  border: none;
  border-top: 1px solid var(--line);
  background: none;
  color: inherit;
  font: inherit;
  text-align: left;
}
.card > .kv:first-child { border-top: none; }
.kv .meta { font-size: 13px; color: var(--ink-2); }
.kv code { font-size: 12px; display: block; margin-top: 3px; color: var(--ink-3); }

.bar {
  height: 5px;
  border-radius: 3px;
  background: var(--fill);
  margin-top: 7px;
  overflow: hidden;
}
.bar span { display: block; height: 100%; background: var(--accent); border-radius: 3px; }

/* The export picker. A disabled row still occupies its place, so the list is
   in the same order for everybody and "not on file" reads differently from
   "not included". */
.pick {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 10px 14px;
  border-top: 1px solid var(--line);
  cursor: pointer;
  font-size: 14px;
}
.card > .pick:first-child { border-top: none; }
.pick.off { color: var(--ink-3); cursor: default; }
.pick .meta { font-size: 12px; color: var(--ink-3); }

/* `.meta` used to exist only as `.row .meta`. The admin and home screens use it
   on its own, where it was inheriting body size and reading as body copy. */
.meta { font-size: 13px; color: var(--ink-2); }
.card > .meta, p.meta { margin: 4px 0 0; }

/* "May be dating" (feedback SS10.1). Loud on purpose: it is the one thing on a
   profile that can cost a matchmaker a week of work on somebody unavailable. */
.warn-pill {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 5px 11px; border-radius: 999px;
  font-size: 12px; font-weight: 500;
  color: var(--wait);
  background: var(--wait-soft);
}

/* ---------- Watermark (feedback SS10.1) ----------
   The viewer's name across a photograph. Tiled and diagonal rather than one
   corner mark, which is cropped off in two seconds; low contrast both ways so
   it stays legible over a dark photo and a pale one without burying the face.
   Repeated with a CSS gradient of text - `content` cannot tile, so the name is
   drawn once per cell by repeating the pseudo-element via background. */
.watermarked { position: relative; display: inline-block; }
.watermark {
  position: absolute; inset: 0; overflow: hidden; pointer-events: none;
  border-radius: inherit;
}
.watermark::before {
  content: attr(data-name) "   " attr(data-name) "   " attr(data-name);
  position: absolute;
  top: -60%; left: -60%; width: 220%; height: 220%;
  transform: rotate(-28deg);
  /* Bigger and stronger at Kim's asking (case 102): "too faint and too tiny".
     Line height and word spacing grew with the type so the marks stay a grid
     rather than closing into a mat. */
  font-size: 15px; font-weight: 600; line-height: 2.9;
  letter-spacing: .02em;
  color: rgba(255, 255, 255, .34);
  text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
  word-spacing: 1.7em;
  overflow-wrap: break-word;
}

/* The full-screen look at a photograph (§7.6).

   Above the sheets rather than inside one: it belongs to the picture, not to
   the panel the picture happened to be in, and a sheet's own scroll and
   padding would fight it for the screen. */
.lightbox {
  position: fixed; inset: 0; z-index: 200;
  display: flex; align-items: center; justify-content: center;
  background: rgba(0, 0, 0, .93);
  padding: 56px 20px 68px;
  animation: fade-in .16s ease-out;
}
@keyframes fade-in { from { opacity: 0; } }
.lightbox-frame { max-width: 100%; max-height: 100%; display: flex; }
/* The stage clips, so a rotated or cropped photograph is framed here exactly
   the way `frameStyle` frames it in a circle — one set of arithmetic. */
.lightbox-stage {
  position: relative; overflow: hidden;
  display: flex; align-items: center; justify-content: center;
  max-width: min(92vw, 900px); max-height: calc(100vh - 124px);
}
.lightbox-stage img { max-width: min(92vw, 900px); max-height: calc(100vh - 124px); }
.lightbox-stage .watermark::before { font-size: 15px; line-height: 3.4; }
.lightbox-caption {
  position: absolute; left: 0; right: 0; bottom: 22px;
  text-align: center; color: rgba(255, 255, 255, .78); font-size: 13.5px;
}
.lightbox-x, .lightbox-arrow {
  position: absolute; display: flex; align-items: center; justify-content: center;
  border: none; cursor: pointer; color: #fff;
  background: rgba(255, 255, 255, .12); border-radius: 999px;
}
.lightbox-x { top: 16px; right: 16px; width: 42px; height: 42px; }
.lightbox-arrow { top: 50%; margin-top: -23px; width: 46px; height: 46px; }
.lightbox-arrow.left { left: 14px; }
.lightbox-arrow.right { right: 14px; }
.lightbox-x:hover, .lightbox-arrow:hover { background: rgba(255, 255, 255, .22); }

/* The other photographs on file. A person with four of them used to look like
   a person with one. */
.thumbstrip {
  display: flex; gap: 8px; justify-content: center; flex-wrap: wrap;
  margin-top: 12px;
}
.thumb {
  position: relative;
  width: 46px; height: 46px; padding: 0; overflow: hidden; cursor: zoom-in;
  border-radius: 10px; border: 1px solid var(--line); background: var(--fill);
  display: flex; align-items: center; justify-content: center;
}
/* Same shape of fault as `.avatar img`, same fix: a centred flex item is not
   a stretched one, so the percentage height had nothing definite to resolve
   against. Nobody had reported this one — it is the strip of other
   photographs — but it is the same three lines and would have failed the same
   way the moment somebody looked at it. */
.thumb img {
  position: absolute; inset: 0; display: block;
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 22%;
}

/* The framing viewport (§7.6). The window is the crop rectangle, so it has to
   be square and it has to clip — the picture is dragged around behind it. */
.frame-stage {
  position: relative; overflow: hidden; touch-action: none; cursor: grab;
  width: 100%; max-width: 340px; aspect-ratio: 1; margin: 0 auto;
  border-radius: 12px; background: var(--fill); user-select: none;
}
.frame-stage:active { cursor: grabbing; }
.frame-stage img {
  position: absolute; left: 50%; top: 50%;
  transform-origin: center center; translate: -50% -50%;
  pointer-events: none;
}

/* =========================================================================
   The desktop back office (§19.5).

   The portal was a good phone screen that happened to run on a monitor: one
   860px column down the middle and a person's profile thrown over the top of
   it in a modal. Staff on a big screen work a list and a record together —
   scan, open, compare, go back — and a modal makes that a sequence of
   interruptions instead.

   Two changes, both only above 1200px, so nothing about the phone moves.
   ========================================================================= */
@media (min-width: 1200px) {
  /* Use the monitor. 1180 is about 110 characters of body text at this size,
     which is wide for prose and right for a table of people. */
  :root { --reading: 1180px; }
  /* The navigation stays full width — it is chrome, not content — while the
     reading column stays a readable width inside it. */

  /*
     Wider, and the roster gets denser rather than merely bigger.

     A phone shows one person per row because there is room for one. Stretching
     that to 1180px gives a column of cards with half a screen of white space
     beside each name, which is the "mobile just stretched" Kim was pointing
     at. Above 1200 the grid takes larger tiles — a face you can recognise at
     a glance is the point of the grid view — and the form fields stop being
     one per line.
  */
  .grid { grid-template-columns: repeat(auto-fill, minmax(196px, 1fr)); gap: 16px; }
  .tile { padding: 20px 14px; }
  .tile .avatar.xl { width: 132px; height: 132px; }

  /* Three across instead of two: at this width two columns of fields leave a
     third column of nothing. */
  .field-row { grid-template-columns: repeat(3, 1fr); gap: 16px; }

  /* A row of somebody has room to breathe rather than to sprawl. */
  :root { --row-x: 18px; }
  .row { padding: 15px var(--row-x); }

  /* The record docks beside the list instead of over it. */
  .sheet-backdrop {
    justify-content: flex-end; align-items: stretch;
    background: transparent; backdrop-filter: none;
    pointer-events: none;                 /* the list behind stays usable */
  }
  .sheet {
    pointer-events: auto;
    width: 480px; max-width: 480px;
    height: 100dvh; max-height: 100dvh;
    border-radius: 0; border-left: 1px solid var(--line);
    box-shadow: -18px 0 40px rgba(0, 0, 0, .10);
  }
  /* Stacked sheets (a photograph being framed on top of a profile) sit one
     panel further in, so the record underneath stays readable. */
  .sheet-backdrop ~ .sheet-backdrop .sheet { margin-right: 480px; }

  /* Make room rather than hiding the list under the panel. */
  /* Derived from `--reading` rather than repeating the number, so widening
     the column below cannot leave the docked panel overlapping the list. */
  body.has-pane .screen { max-width: calc(var(--reading) - 480px); }
  body.has-pane #shell { padding-right: 480px; }
  /* The body is locked while a sheet is open so the modal scrolls alone; a
     docked panel is not a modal, and the list behind it still has to scroll. */
  body.has-pane { overflow: auto !important; }
}

/* The grid layout already fills its column with `auto-fill`, so widening the
   column above is all "more people per row" needs — no second rule for it. */

/* The payments worksheet (§17.1). A table because the columns are the point —
   she asked for a title row, and a row of cards is not a title row. */
.pay { width: 100%; border-collapse: collapse; font-size: 13px; }
.pay th {
  text-align: left; font-weight: 600; color: var(--ink-2);
  padding: 8px 10px; border-bottom: 1px solid var(--line); white-space: nowrap;
}
.pay td { padding: 7px 10px; border-bottom: 1px solid var(--line); vertical-align: middle; }
.pay tr:hover td { background: var(--fill); }
.pay .pay-in { min-width: 110px; padding: 6px 8px; font-size: 13px; }

/* Bigger roster photographs (case 6, §7.6).
   Square past this size: a circle crops the corners off a face, and both Amy
   and Adelle asked for the large format over "little round thumbnails".
   Sized to what the pictures carry — the imported originals run to a median of
   about 277px, so a tile much larger than this is upscaling into mush. */
.avatar.big { width: 68px; height: 68px; font-size: 22px; }
.avatar.xl  { width: 116px; height: 116px; font-size: 34px; }
.avatar.sq  { border-radius: 14px; }
.tile .avatar.xl { margin: 0 auto 4px; }
.rows .row .avatar.big { flex-shrink: 0; }

/* Today's rows carry faces rather than a second number — a matchmaker
   recognises people far faster than she reads a count. They overlap, so three
   of them cost the width of one and a half. */
.avatar.xs { width: 26px; height: 26px; font-size: 10px; }
.faces { display: flex; flex-shrink: 0; }
.faces .avatar.xs + .avatar.xs { margin-left: -8px; }
.faces .avatar.xs { box-shadow: 0 0 0 1.5px var(--card); }

/* Case 24 — the couple who got there does not look like every other row. */
.card.celebrated {
  background: var(--card-celebrated);
  border-color: var(--line-celebrated);
}

/* A case is a small record, not a line of text: number and state on top, what
   it was about, then who and when. A card each with air between them — one
   card with dividers ran the state stripes into a single unbroken line. */
.case-list { display: flex; flex-direction: column; gap: 10px; }
.case-card {
  display: flex; align-items: flex-start; gap: 10px; width: 100%;
  text-align: left; cursor: pointer;
  padding: 14px 15px; padding-left: 18px;
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  /* Inset rather than a border-left, so it cannot escape the rounded corner. */
  box-shadow: inset 3px 0 0 0 var(--stripe, var(--ink-3));
  color: inherit; font: inherit;
}
.case-card:hover { border-color: var(--accent); }
.case-card .meta { font-size: 12px; }


/* =========================================================================
   A monitor with room to spare.

   At 1728 the column was 1180 with 274px of margin down each side, which is
   a wide screen used as a narrow one. A roster is a table of people, not an
   essay, so it can take the width — five tiles across became seven, and the
   change costs nothing below this size.
   ========================================================================= */
@media (min-width: 1560px) {
  :root { --reading: 1440px; }
}


/* =========================================================================
   The roster as a table, and matches as a wall (Kim: "more desktop friendly
   instead of mobile just stretched").

   Below 880 nothing here applies and both screens stay exactly as they were
   on the phone: a face, a name, a folded line of details underneath.
   ========================================================================= */
.roster-head { display: none; }
.rr-cell, .rr-status { display: none; }

@media (min-width: 880px) {
  /* One grid shared by the header and every row, so the columns line up by
     construction rather than by two lists of widths agreeing. */
  .roster-row, .roster-head {
    display: grid;
    grid-template-columns: 68px minmax(180px, 1.4fr) 56px minmax(110px, 1fr)
                           72px minmax(120px, 1fr) minmax(96px, auto) 40px 20px;
    align-items: center;
    gap: 14px;
  }
  .roster-head {
    padding: 10px var(--row-x); border-bottom: 1px solid var(--line);
    font-size: 12px; font-weight: 600; letter-spacing: .02em;
    color: var(--ink-2); text-transform: uppercase;
  }
  .rr-cell, .rr-status { display: flex; align-items: center; gap: 6px; }
  .rr-cell {
    font-size: 14px; color: var(--ink-2);
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
  }
  .rr-status { flex-wrap: wrap; }
  /* The folded line is the phone's version of these columns. */
  .rr-meta { display: none; }
  .rr-name { min-width: 0; }
  .rr-name .title { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  /* Matches tile rather than stack. minmax keeps a card wide enough for two
     faces and a heart between them, which is the narrowest it can usefully be. */
  .match-grid {
    display: grid; gap: 14px;
    grid-template-columns: repeat(auto-fill, minmax(330px, 1fr));
  }
  /* Equal heights across a row, with the matchmaker line pinned to the
     bottom. Left to size themselves the cards came out ragged, because a
     couple with a date count is one line taller than a couple without —
     which reads as a layout fault rather than as a difference in the data. */
  .match-grid > .card { display: flex; flex-direction: column; }
  .match-grid > .card > :last-child { margin-top: auto; }
}


/*
  The controls on a match card.

  Kim's screenshot: the "More..." menu hanging outside the right edge of the
  card. The row was `display: flex` with no wrap and a `<select>` on the end
  whose intrinsic width does not negotiate — fine down a single column, wrong
  the moment the cards tiled three across.

  Wrapping rather than widening the card. Three couples across a 1440 screen
  is the density the screen is for, and the fourth control is a menu of
  rarities that belongs on a second line when the first is full.
*/
.match-actions {
  display: flex; flex-wrap: wrap; gap: 8px; margin-top: 12px;
}
.match-actions > .btn { flex: 0 0 auto; }
/* The advance verb is the one thing on this card somebody came to press, so
   it gets a line of its own at full width rather than competing for room
   with History, Matchmakers and a menu of rarities. `order` puts it last
   whatever the markup order, and a 100% basis is what forces the wrap. */
.match-actions > .btn.primary { order: 2; flex: 1 1 100%; min-width: 0; }
/*
   A native select sizes itself to its longest option — "Put back to
   Suggestion" — so it took a whole line to show the word "More". Given an
   explicit width it shows its own label and opens at full width anyway,
   which is where the long strings actually need the room.
*/
.match-actions > select { flex: 0 0 auto; width: 5.6rem; min-width: 0; }


/* =========================================================================
   Home, Profile and the admin doors on a monitor.

   All three were a single stack of full-width rows holding a line of text
   each — the shape a phone needs and a monitor wastes. Nothing here applies
   below the breakpoints, so the phone is untouched.
   ========================================================================= */

/* --- Profile header ---------------------------------------------------- */
.profile-id { display: flex; align-items: center; gap: 16px; text-align: left; }
.profile-stats {
  display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 16px;
}

@media (min-width: 1100px) {
  /* Home's sections two abreast. `align-items: start` so a short block does
     not stretch to match a tall one beside it, and the grid moves whole
     sections because each is a <section> holding its own heading. */
  .home-deck {
    display: grid; grid-template-columns: 1fr 1fr;
    gap: 4px 24px; align-items: start;
  }
  .home-block { min-width: 0; }
  /* The first heading in a column should not be pushed down by the margin
     that separates sections further down it. */
  .home-block > .section-head:first-child { margin-top: 6px; }

  /*
     Tighter rows on a monitor.

     13px of vertical padding is a thumb target. With a pointer it is a fat
     button, and three of them stacked is what made this screen look like a
     phone list widened rather than a dashboard. The roster keeps its own
     spacing — that one is scanned, and these are read.
  */
  .home-block .row { padding: 11px var(--row-x); }
  .home-block .row .title { font-size: 14.5px; }

  /*
     Two by two, not three and a stray.

     `auto-fill` at 150px puts three doors across a half-width column and
     wraps the fourth underneath on its own, which looks like an accident.
     Four doors in a column this wide want a square. Stretched so "Private
     clients" wrapping to two lines lifts its neighbours rather than standing
     a head taller than them.
  */
  .home-block .door-grid { grid-template-columns: repeat(2, 1fr); align-items: stretch; }
  .home-block .door { height: 100%; }

  /* The profile's navigation rows: four destinations, two columns. A row
     whose text ends after forty pixels does not need fourteen hundred. */
  .profile-nav { display: grid; grid-template-columns: 1fr 1fr; }
  /* The card draws one border; the rows inside it draw the divisions, and in
     two columns the right-hand column needs a left edge. */
  .profile-nav > .row:nth-child(2n) { border-left: 1px solid var(--line); }

  /* Bigger doors. Fifteen tiles at 150px left two thirds of the screen
     empty; at 190 they read as buttons rather than as a mosaic. */
  .admin-doors { grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); gap: 14px; }
}


/* =========================================================================
   Admin records as tables.

   App accounts and the change log were both a stack of bordered cards, each
   holding two or three lines of small text. They are records with identical
   fields in every row, which is what a table is for — and below 880 they
   fold back to the stacked shape a phone needs.
   ========================================================================= */
.admin-head { display: none; }

@media (min-width: 880px) {
  .admin-head {
    display: grid; align-items: center; gap: 14px;
    padding: 10px var(--row-x); border-bottom: 1px solid var(--line);
    font-size: 12px; font-weight: 600; letter-spacing: .02em;
    color: var(--ink-2); text-transform: uppercase;
  }
  .admin-row { display: grid; align-items: center; gap: 14px; }

  /* Accounts: face, name, email, role, password, and the two controls. */
  /*
     No `auto` tracks here, and that is the whole point.

     The first version used `minmax(..., auto)` for the last three columns.
     An auto track sizes to its content, and the header's content is the word
     "Role" while the row's is a badge — so the header and the rows resolved
     to different widths and the columns did not line up. Caught by the check
     that compares their cell positions rather than by looking at it.

     Every track is now either fixed or a fraction, so both grids resolve
     identically whatever is in them.
  */
  .admin-table > .admin-head:not(.log-head):not(.rm-head):not(.cs-head),
  .admin-table > .admin-row:not(.log-row):not(.rm-row):not(.cs-row) {
    /*
       Sized to fit the breakpoint, which it never was. The old tracks asked
       for 854px of column plus 70 of gap plus 36 of padding — 960 in an 880
       window — so at anything under a wide laptop the password column gave up
       its text and "Changed Aug 26, 2026" arrived as "Changed Aug 26".
    */
    /*
       Role is 140 because "Internal matchmaker" measures 137. It was 104, and
       a role that cannot be read is worse than a column that is a little wide
       — the whole point of the column is telling an admin apart from a
       matchmaker at a glance.
    */
    grid-template-columns: 34px minmax(96px, 1fr) minmax(134px, 1.4fr)
                           140px 120px 240px;
  }

  /*
     And tighten the row.

     It stood 68px tall against 53 everywhere else, and none of that was
     content: 15px of padding above and below a 37px control. The controls come
     down a little and the padding with them, which lands it on the same 53 as
     the change log and the roadmap beside it.
  */
  .admin-table > .admin-row:not(.log-row):not(.rm-row):not(.cs-row) {
    padding-top: 10px; padding-bottom: 10px;
  }
  .ar-actions .chip { padding: 6px 12px; font-size: 13.5px; }
  /*
     The role menu, held to its column.

     A native `select` takes its width from its longest option, and the longest
     here is "Make Internal matchmaker" — 212px of control for a closed menu
     that reads "Role…". With the button beside it the pair wanted 340px in a
     250px column, and being `flex-end` they overflowed leftwards, across the
     password date. That is what was covering "Never set". Same trap the
     roadmap picker had in the case sheet.
  */
  .ar-actions select { width: 118px; min-width: 0; }
  /* The log asks a different question and takes different columns. */
  .log-head, .log-row {
    grid-template-columns: 150px minmax(150px, 1fr) minmax(190px, 1.4fr) minmax(150px, 1.2fr);
  }

  /* The roadmap asks a third question: what is waiting, and on whom.
     Fixed or fractional throughout, for the reason written above — an `auto`
     track sizes to its own content, so the word "Status" and a badge resolve
     to different widths and the header stops lining up with the rows. */
  /* Minimums sized to fit at the breakpoint itself, not just on a laptop: at
     880 the row gives up 36px of padding and 70px more to five gaps, so the
     six tracks share what is left. Everything past that is fraction, and the
     ask takes most of it because it is the only column saying what the item
     is. */
  .rm-head, .rm-row {
    grid-template-columns: 92px minmax(200px, 2.2fr) 100px 112px 74px minmax(108px, 1fr);
  }

  /* Cases: number, what, state, who, how long, where from. Sized to fit at the
     breakpoint itself — see the roadmap note above. */
  /* Cases take their columns beside the rest of the case rules, further down:
     the narrow layout has to set `grid-template-columns` too, and the last
     rule of equal weight wins wherever it happens to sit in the file. */
  .cs-row .badge { white-space: nowrap; }

  .ar-name { font-weight: 600; font-size: 14.5px; min-width: 0;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  /* The actor's role sits under the name rather than taking a column of its
     own — it qualifies the name and is never the thing being looked for.
     `block` because inline it rendered as "Sam GabbayAdmin". */
  .ar-name { white-space: normal; }
  .ar-name .meta { display: block; font-weight: 400; line-height: 1.3; }
  .ar-cell { font-size: 14px; color: var(--ink-2); min-width: 0;
             overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
  .ar-action { color: var(--ink); }
  .ar-actions { display: flex; gap: 6px; align-items: center; justify-content: flex-end; }
  /* A deletion or a role change keeps its marker, now on a table row. */
  .log-row.severe { box-shadow: inset 3px 0 0 var(--wait); }

  /* Stats groups two abreast — each is a short card of figures, and stacked
     they made a page you scroll to compare two numbers. */
  .stats-deck { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 24px; align-items: start; }

  /* Search: the filters stop being a full-width form above the results and
     become a column beside them, so changing an age range does not push the
     people you are looking at off the screen. */
  .search-layout { display: grid; grid-template-columns: 300px 1fr; gap: 24px; align-items: start; }
  .search-layout > .search-filters { position: sticky; top: 84px; }
}

/* ---------- Confidentiality notice editor ---------- */

/* One column on a phone: the wording first, the preview under it. Nobody
   edits a legal notice on a handset, but landing here from a link should not
   produce two 150px columns. */
.notice-editor { display: grid; gap: 24px; align-items: start; }

/* The preview renders the real card, which is `max-width:620px` and sized for
   a phone-ish column. Below that width the pane would letterbox it and the
   line breaks a reader is checking would be the pane's, not the notice's. */
@media (min-width: 1080px) {
  .notice-editor { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }
}

/*
   The preview pane used to be `position: sticky`, and it could never have
   worked.

   A sticky element only has somewhere to travel while its containing block is
   taller than it is, and this pane *is* the tallest thing in its grid row — so
   the row is exactly its height, there is no slack, and `top: 84px` never
   engaged. It scrolled like anything else, straight up under the header, which
   is sticky and does work. Measured mid-scroll: pane top 22, header bottom 63,
   42px of the menu wearing a dark slab.

   It scrolls with the page now. The pinning cost little — the wording and its
   preview are read together, side by side, and both fit a screen.
*/

/* ---------- Roadmap rows ---------- */

/* Below 880 the grid is off and the row is a flex column again, so the cells
   stack the way every other admin record does on a phone. */
/* `stretch`, not `flex-start`: the ask is `nowrap` so it can ellipsis, and a
   flex-start child sizes to its own text — which on a phone pushed the row
   past the viewport and took the whole page sideways with it. */
.rm-row { flex-direction: column; align-items: stretch; gap: 4px; }
.rm-row > * { min-width: 0; }
/* Stacked, not side by side: "MK-009" and "§10.4" next to each other need a
   column wide enough for both and give the ask nothing. */
.rm-id { display: flex; flex-direction: column; gap: 1px; min-width: 0; }
.rm-code { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
           font-size: 13px; font-weight: 600; }
.rm-what { min-width: 0; }
.rm-title { font-weight: 600; font-size: 14.5px; }
/* One line each. The ask runs to a paragraph on some items and the row has to
   stay the height of a row — the whole text is one click away. */
.rm-title, .rm-ask, .rm-owner {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.rm-ask { font-size: 13px; color: var(--ink-2); }
.rm-owner { font-size: 13.5px; min-width: 0; }
.rm-signals { display: flex; flex-wrap: wrap; gap: 4px 10px; font-size: 13px; }
.rm-lab { color: var(--ink-2); }
/* Stacked, a lone em-dash under a title says nothing at all — the column it
   was standing in for is not on screen. It comes back with the headings. */
.rm-blank { display: none; }

@media (min-width: 880px) {
  .rm-row { flex-direction: row; align-items: center; gap: 14px; }
  .rm-lab { display: none; }
  .rm-blank { display: block; }
  /* Signals wrap within their column rather than widening it. */
  .rm-signals { flex-direction: column; gap: 2px; align-items: flex-start; }
}

/* ---------- Case rows ---------- */

/* Inset rather than border-left, so the stripe cannot escape the rounded
   corner of the table — the same reason the card did it this way. */
.cs-row { box-shadow: inset 3px 0 0 0 var(--stripe, var(--ink-3)); }

/*
   Narrow: three lines, not six.

   Letting the six cells stack made each report seven lines tall and fitted
   three on a phone — taller than the card this replaced, which is the wrong
   direction. So the narrow layout is laid out rather than left to fall over:
   number and state on the first line, what was reported on the second, and
   everything about provenance on the third, which is the shape the card had.
*/
.cs-row {
  display: grid; gap: 3px 10px; align-items: center;
  grid-template-columns: auto auto 1fr;
  grid-template-areas:
    "id    status status"
    "what  what   what"
    "who   when   where";
}
.cs-row > * { min-width: 0; }
.cs-id     { grid-area: id; display: flex; flex-direction: row;
             align-items: baseline; gap: 7px; min-width: 0; white-space: nowrap; }
.cs-status { grid-area: status; justify-self: start; }
.cs-what   { grid-area: what; }
.cs-who    { grid-area: who; }
.cs-when   { grid-area: when; }
.cs-where  { grid-area: where; }
/* Reads as one sentence of provenance, the way the card's meta line did. */
.cs-when::before, .cs-where::before { content: "· "; color: var(--ink-3); }
.cs-num { font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
          font-size: 12px; font-weight: 700; color: var(--ink-3); }
/* One line each: a report's first line runs long and the row has to stay the
   height of a row. The whole thing is one click away. */
.cs-what, .cs-who, .cs-when, .cs-where {
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.cs-what { font-weight: 500; font-size: 14.5px; }

/* The line that says which part of the conversation answered a search (case
   181). Tinted and quoted so it reads as lifted from the thread rather than as
   part of the report, and allowed a second line where the row itself is not. */
.cs-hit {
  display: flex; align-items: flex-start; gap: 5px;
  margin-top: 4px; padding: 4px 7px;
  font-weight: 400; font-size: 12.5px; line-height: 1.35;
  color: var(--ink-2); background: var(--accent-soft); border-radius: 6px;
  white-space: normal;
}
.cs-hit svg { flex: none; margin-top: 2px; color: var(--accent); }
/* A row carrying a hit is allowed to be two lines; the clamp above is there to
   keep a long first line from setting the height, not to hide this. */
.cs-what:has(.cs-hit) { white-space: normal; overflow: visible; }
.cs-who { font-size: 13.5px; }

@media (min-width: 880px) {
  /* Back to one row of six columns. The areas above must be cleared or they
     keep placing cells wherever they were named, and the columns are declared
     here rather than up with the other tables because the narrow layout sets
     them too — of two rules with the same weight, the later one wins.

     The status column is wide because one of its labels is "Fixed — needs
     your OK", and a badge that wraps to two lines makes every row carrying it
     taller than its neighbours. */
  .cs-head, .cs-row {
    grid-template-columns: 112px minmax(170px, 2.4fr) 178px 116px 96px minmax(96px, 1fr);
  }
  .cs-row { grid-template-areas: none; gap: 14px; }
  /* Clearing the areas is not enough on its own: each cell still carries
     `grid-area: <name>`, and a name with no line to match places it in an
     implicit track rather than the column it belongs to. The cells go back to
     source order. */
  .cs-id, .cs-status, .cs-what, .cs-who, .cs-when, .cs-where { grid-area: auto; }
  .cs-when::before, .cs-where::before { content: none; }
}

/* The roadmap picker in a case sheet. Fills its card and never exceeds it —
   see the note beside it in app.js. */
.case-link-pick { width: 100%; max-width: 100%; min-width: 0; }

/* ---------- Payroll filters ---------- */

/* A grid, not a wrapping flex row.
   As a flex row these controls were every height at once — .input carries a
   border and 11px of padding, the selects were .chip with neither — so the
   labels sat on four different baselines and the last two fields wrapped onto
   a line of their own. On a grid every cell is the same width and every
   control the same height, and a narrow window drops whole columns instead of
   orphaning one field. */
.payroll-filters {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(158px, 1fr));
  gap: 12px 14px;
  align-items: end;
}
.payroll-filters label { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
/* Search and the matchmaker names need the room; the rest are short words.
   "Rabbi Moshe Mustacchi" wants 206px and a single column is not that wide. */
.payroll-filters label.wide { grid-column: span 2; }
.payroll-filters .meta { font-size: 12px; }

/* One height, one border, one radius, whatever the element underneath is. */
.payroll-filters .input,
.payroll-filters select {
  width: 100%;
  height: 40px;
  padding: 0 12px;
  border: 1px solid var(--line);
  border-radius: 10px;
  background: var(--fill);
  color: var(--ink);
  font-size: 14px;
  line-height: normal;
}
.payroll-filters select { padding-right: 28px; }
.payroll-filters .input:focus,
.payroll-filters select:focus { outline: none; border-color: var(--accent); background: var(--card); }
.payroll-filters .f-export { height: 40px; padding: 0 18px; font-size: 14px; }

@media (max-width: 660px) {
  /* One column: a two-column span would run off the end of the grid. */
  .payroll-filters { grid-template-columns: 1fr 1fr; }
  .payroll-filters label.wide { grid-column: span 2; }
}

.payroll-filters .payroll-pick { width: 100%; }

/* Twelve columns where the payments sheet has ten, which put the last one —
   the one you actually click — permanently half off the edge for the sake of
   72px. It earns that back from the padding rather than from the names. */
.pay-wide th, .pay-wide td { padding-left: 7px; padding-right: 7px; }
.pay-wide .pay-in { min-width: 90px; }

/* --- the payroll ledger ------------------------------------------------- */

/* The figures she works from, above the table rather than inside it: the one
   that matters is what is still to go out, so it reads first and largest. */
.ledger-totals {
  display: flex; flex-wrap: wrap; gap: 10px 28px;
  margin: 4px 0 14px; padding: 12px 14px;
  border: 1px solid var(--line); border-radius: 12px; background: var(--fill);
}
.ledger-totals > div { display: flex; flex-direction: column; gap: 2px; }
.ledger-totals strong { font-size: 19px; font-variant-numeric: tabular-nums; }

/* Fixed columns. Left to size themselves the money columns took whatever the
   couple's names did not want, so Owed was a 190px box holding "$250.00" while
   the status select next to it clipped "approved" down to "appro". Only the
   couple flexes; everything else is sized for what actually goes in it. */
/* The min-width is what makes `fixed` safe. Without it a 390px phone squeezes
   these columns proportionally below the widths set here, and the cells stop
   containing their own text — "Couple" and "Match" printed on top of each
   other. Below this the wrapper scrolls instead. */
.pay-ledger { table-layout: fixed; width: 100%; min-width: 1100px; }
.pay-ledger th, .pay-ledger td { padding-left: 8px; padding-right: 8px; }
.pay-ledger td { vertical-align: top; padding-top: 9px; }
.pay-ledger th:nth-child(1), .pay-ledger td:nth-child(1) { width: 46px; }
.pay-ledger th:nth-child(2), .pay-ledger td:nth-child(2) { width: 172px; }
.pay-ledger th:nth-child(3), .pay-ledger td:nth-child(3) { width: auto; min-width: 220px; }
.pay-ledger th:nth-child(4), .pay-ledger td:nth-child(4) { width: 96px; }
.pay-ledger th:nth-child(5), .pay-ledger td:nth-child(5) { width: 124px; }
.pay-ledger th:nth-child(6), .pay-ledger td:nth-child(6) { width: 92px; }
.pay-ledger th:nth-child(7), .pay-ledger td:nth-child(7) { width: 112px; }
.pay-ledger th:nth-child(8), .pay-ledger td:nth-child(8) { width: 124px; }
.pay-ledger th:nth-child(9), .pay-ledger td:nth-child(9) { width: 112px; }
/* The two money headers belong over their figures. */
.pay-ledger th:nth-child(6), .pay-ledger th:nth-child(7) { text-align: right; }

/* Money lines up on the right or it cannot be read down a column. */
.pay-ledger .num { text-align: right; font-variant-numeric: tabular-nums; }

/*
 * The by-milestone table. Sized to what each column actually holds — the
 * first cut borrowed .pay-ledger's widths, which were tuned for nine columns,
 * so "Date #2" wrapped inside its pill and the reason folded three times
 * while Couple sat on a field of empty space (Sam, 1 Sep).
 */
.pay-items { table-layout: fixed; width: 100%; min-width: 1010px; }
.pay-items th, .pay-items td { padding-left: 8px; padding-right: 8px; }
.pay-items td { vertical-align: top; padding-top: 9px; }
.pay-items .badge { white-space: nowrap; }
.pay-items th:nth-child(1), .pay-items td:nth-child(1) { width: 118px; } /* Type */
.pay-items th:nth-child(2), .pay-items td:nth-child(2) { width: 170px; } /* Matchmaker */
.pay-items th:nth-child(3), .pay-items td:nth-child(3) { width: auto; min-width: 220px; }
.pay-items th:nth-child(4), .pay-items td:nth-child(4) { width: 92px; }  /* Amount */
.pay-items th:nth-child(5), .pay-items td:nth-child(5) { width: 215px; } /* Reason */
.pay-items th:nth-child(6), .pay-items td:nth-child(6) { width: 76px; }  /* Cycle */
.pay-items th:nth-child(7), .pay-items td:nth-child(7) { width: 132px; } /* Status */
.pay-items th:nth-child(4) { text-align: right; }
.pay-items .num { text-align: right; font-variant-numeric: tabular-nums; }
.pay-ledger .pay-in {
  width: 100%; min-width: 0; text-align: right; font-variant-numeric: tabular-nums;
}
/* Sized to its longest option — a native select clips rather than wraps. */
.pay-ledger .pay-status { width: 100%; min-width: 0; padding-left: 10px; padding-right: 6px; }
/* Names are the one thing allowed to wrap; nothing else in the row should. */
.pay-ledger td:nth-child(2) .meta,
.pay-ledger td:nth-child(4),
.pay-ledger td:nth-child(6),
.pay-ledger td:nth-child(7) { white-space: nowrap; }
.pay-ledger tr.is-open > td { background: var(--card); }

/* The count of entries doubles as the control that opens them. */
.twist { min-width: 30px; text-align: center; padding-left: 8px; padding-right: 8px; }

/* Where the line stands, in a word and a colour. Derived from the money, so
   it is never edited — only read. */
.pill {
  display: inline-block; padding: 2px 9px; border-radius: 999px;
  font-size: 12px; font-variant-numeric: tabular-nums; white-space: nowrap;
}
.pill-unpaid    { background: #fdecec; color: #8c2f2f; }
.pill-part-paid { background: #fdf3e2; color: #8a5a12; }
.pill-settled   { background: #e9f5ec; color: #24603a; }
.pill-overpaid  { background: #eceaf8; color: #443a7a; }
.pill-nothing-owed { background: var(--fill); color: var(--muted); }

/* The drawer. Collapsed rows must not merely be invisible — an empty <tr>
   still draws its borders and leaves a gap under every line. */
.ledger-drawer { display: none; }
.ledger-drawer.is-open { display: table-row; }
/* The card surface, not --fill: every button in here is --fill too, so on a
   --fill drawer "Reverse" and "Add payment" rendered as bare text with no
   button behind them at all. */
.ledger-drawer > td { background: var(--card); padding: 0 8px 14px; box-shadow: inset 0 1px 0 var(--line); }
.ledger { padding: 10px 0 0; }

/* Capped rather than full width: six short columns stretched across a wide
   screen puts the date and its amount too far apart to read as one entry. */
.ledger-table { width: 100%; max-width: 780px; border-collapse: collapse; font-size: 13px; margin-bottom: 10px; }
.ledger-table th {
  text-align: left; font-weight: 600; color: var(--muted);
  padding: 4px 8px; border-bottom: 1px solid var(--line);
}
.ledger-table th:nth-child(2), .ledger-table td:nth-child(2) {
  text-align: right; font-variant-numeric: tabular-nums;
}
/* The action column holds a button or a word about why there isn't one. */
.ledger-table th:last-child, .ledger-table td:last-child { text-align: right; width: 96px; }
.ledger-table td { padding: 5px 8px; border-bottom: 1px solid var(--line); }
.ledger-table .neg { color: #8c2f2f; }
/* A reversed entry stays legible — it is still part of the record. */
.ledger-table tr.is-undone td { opacity: 0.55; text-decoration: line-through; }
.ledger-table tr.is-undone td:last-child { text-decoration: none; }

/* .input is width:100%, which in a flex row puts every field on its own line.
   Each one is sized for what goes in it instead, and the note takes the slack. */
.ledger-add { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
.ledger-add .input { width: auto; min-width: 0; flex: 0 0 auto; }
.ledger-add .f-amount { width: 110px; text-align: right; font-variant-numeric: tabular-nums; }
.ledger-add .f-date   { width: 165px; }
.ledger-add .f-how    { width: 150px; }
/* Grows into the space, but capped: left to fill a wide drawer it took 780px
   and pushed the button onto a second row. */
.ledger-add .f-note   { flex: 1 1 200px; max-width: 320px; }

@media (max-width: 700px) {
  .ledger-totals { gap: 8px 18px; }
  .ledger-totals strong { font-size: 17px; }
  .ledger-add .input { flex: 1 1 120px; }
}

/* ---------- Waiting: the approvals queue (Kim, on the approvals layout) ------
   A card per person rather than a roster row, because the row has to carry a
   decision as well as a name. The person opens; the buttons stay buttons. */
.waiting-card { padding: 6px 0 14px; }
.waiting-card .row:hover { background: transparent; }
.waiting-card .warn-pill { margin-top: 6px; }
/* The line that says how they got here and how long they have sat. Quieter
   than the facts above it: it colours the decision rather than making it. */
.waiting-sub { color: var(--ink-3); margin-top: 2px; }

.waiting-actions { display: flex; gap: 10px; padding: 0 var(--row-x); }
.waiting-actions .btn { flex: 1; justify-content: center; }

.waiting-more { width: 100%; margin-top: 14px; }

/* The last decision, kept named until the next one replaces it. This queue is
   678 rows deep and every decision is one tap, so a thumb going down it will
   eventually land on the wrong person. Sticky rather than in the flow: the
   mistake is noticed while scrolling on, not at the top of the page. */
.waiting-undo {
  position: sticky; top: 8px; z-index: 5;
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; margin-bottom: 12px;
  border-radius: 999px;
  background: var(--card); border: 1px solid var(--line);
  box-shadow: var(--shadow);
  font-size: 13px;
}
.waiting-undo span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ---------- Who could take them, and who they take (MK-087) ------------------
   Adelle's list, made legible. The reasons a suggestion was made and the
   conditions nobody can check sit on the same card and must not read alike —
   one is why this person is here, the other is what still needs a human. */
.fit-reasons {
  margin: 12px 0 0; padding-left: 18px;
  font-size: 13.5px; color: var(--ink-2);
}
.fit-reasons li { margin: 2px 0; }
/* Amber, and never green: these are her words for something the rules could
   not enforce, so they have to look like a question rather than a tick. */
.fit-cautions {
  margin: 8px 0 0; padding-left: 18px;
  font-size: 13px; color: var(--wait);
}
.fit-cautions li { margin: 2px 0; }

.mm-rules { margin-top: 6px; }
/* Her sentence, verbatim, set apart so it reads as a quotation and not as a
   field the system computed. */
.mm-note {
  margin-top: 4px; padding-left: 10px;
  border-left: 2px solid var(--line);
  font-size: 12.5px; font-style: italic; color: var(--ink-3);
}

/* ============================================================================
   Admin — a dashboard, not a menu
   ----------------------------------------------------------------------------
   Twenty identical tiles answered "where is everything" and nothing else. An
   admin's first question is "what needs me", so the queues lead, carrying
   numbers that fall as the work is done, and the doors follow in groups.
   ========================================================================== */

.adm-queues {
  display: grid; gap: 12px; margin-bottom: 10px;
  grid-template-columns: repeat(auto-fit, minmax(168px, 1fr));
}
.adm-q {
  text-align: left; padding: 16px 16px 14px;
  border: 1px solid var(--line); border-radius: 16px;
  background: var(--card); cursor: pointer;
  transition: transform .12s ease, box-shadow .12s ease;
}
.adm-q:hover { transform: translateY(-1px); box-shadow: var(--shadow); }
.adm-q:active { transform: none; }
.adm-q-n {
  font-size: 30px; font-weight: 650; line-height: 1.05;
  letter-spacing: -.02em; font-variant-numeric: tabular-nums;
  color: var(--ink);
}
.adm-q-label { margin-top: 6px; font-size: 14px; font-weight: 550; color: var(--ink); }
.adm-q-note { margin-top: 2px; font-size: 12.5px; color: var(--ink-3); }

/* Three weights, and only three. Something a person is blocked on, something
   waiting to be worked, and something that is merely true. A dashboard where
   every tile shouts is one where nothing does. */
.adm-q-you {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--line));
  background: var(--accent-soft);
}
.adm-q-you .adm-q-n { color: var(--accent); }
.adm-q-work .adm-q-n { color: var(--ink); }
.adm-q-quiet .adm-q-n { color: var(--ink-3); font-weight: 600; }

.adm-q-skeleton {
  grid-column: 1 / -1; height: 96px; border-radius: 16px;
  background: var(--fill);
}

/* The standing totals. Small and once, so the numbers above keep meaning
   "somebody is waiting" rather than "here are some figures". */
.adm-facts {
  font-size: 12.5px; color: var(--ink-3);
  margin: 0 0 22px; padding-bottom: 4px;
}

.adm-group-head {
  font-size: 12px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3);
  margin: 22px 0 10px;
}
.adm-doors {
  display: grid; gap: 8px;
  grid-template-columns: repeat(auto-fill, minmax(196px, 1fr));
}
/* A row, not a tile. These are a list of places to go, and a tall square with
   a centred icon spends a card's worth of space saying one word. */
.adm-door {
  display: flex; align-items: center; gap: 11px;
  padding: 12px 14px; width: 100%; text-align: left;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: var(--ink); cursor: pointer;
  font-size: 14px;
}
.adm-door:hover { background: var(--fill); }
.adm-door-icon { color: var(--accent); display: flex; flex-shrink: 0; }
.adm-door-label { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ============================================================================
   Feedback — one band of controls, not five
   ----------------------------------------------------------------------------
   The screen stacked a summary, a whose-picker, a search box, a row of filter
   chips and a section heading before a single report appeared. Two of those
   counted the same cases twice: "Open 45" in a tile and again on a chip beside
   it. So the tiles are the filter now, and the chips are gone.
   ========================================================================== */

.fb-filters {
  display: grid; gap: 8px; margin-bottom: 12px;
  grid-template-columns: repeat(auto-fit, minmax(124px, 1fr));
}
.fb-stat {
  display: flex; flex-direction: column; gap: 1px;
  padding: 11px 13px; text-align: left;
  border: 1px solid var(--line); border-radius: 12px;
  background: var(--card); color: inherit; cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.fb-stat:hover { background: var(--fill); }
.fb-stat-n {
  font-size: 22px; font-weight: 650; line-height: 1.15;
  font-variant-numeric: tabular-nums; color: var(--ink);
}
.fb-stat-label { font-size: 12.5px; color: var(--ink-2); }

/* Selected reads as pressed, not merely tinted — this band is the only filter
   on the screen now, so which one is on has to be unmistakable. */
.fb-stat[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: color-mix(in srgb, var(--accent) 55%, var(--line));
}
.fb-stat[aria-pressed="true"] .fb-stat-n,
.fb-stat[aria-pressed="true"] .fb-stat-label { color: var(--accent); }

/* Somebody is blocked on an answer from the person reading this. Amber even
   when it is not the selected filter, because it is the one tile that is
   asking rather than reporting. */
.fb-stat-you { border-color: color-mix(in srgb, var(--wait) 50%, var(--line)); }
.fb-stat-you .fb-stat-n { color: var(--wait); }

/* Search, scope and the roadmap on one line — all three are "narrow what I am
   looking at", all three are short, and a monitor has room for them abreast.
   Wraps to stacked rows on a phone. */
.fb-tools {
  display: flex; flex-wrap: wrap; align-items: center; gap: 10px;
  margin-bottom: 14px;
}
.fb-tools .search { flex: 1 1 260px; margin: 0; }
.fb-roadmap {
  display: inline-flex; align-items: center; gap: 7px; flex-shrink: 0;
  padding: 9px 13px; border-radius: 10px;
  border: 1px solid var(--line); background: var(--card);
  color: var(--ink-2); font-size: 13.5px; cursor: pointer;
}
.fb-roadmap:hover { background: var(--fill); color: var(--ink); }
.fb-roadmap svg { color: var(--accent); flex: none; }

/* A segmented pair, not two loose chips. Everyone/Just mine are two halves of
   one question and should look like one control. */
.seg {
  display: inline-flex; flex-shrink: 0;
  border: 1px solid var(--line); border-radius: 10px;
  background: var(--card); padding: 3px; gap: 2px;
}
.seg-b {
  padding: 7px 13px; border-radius: 7px; border: 0;
  background: transparent; color: var(--ink-2);
  font-size: 13.5px; cursor: pointer; white-space: nowrap;
}
.seg-b:hover { color: var(--ink); }
.seg-b[aria-pressed="true"] { background: var(--accent); color: #fff; font-weight: 550; }
@media (prefers-color-scheme: dark) { .seg-b[aria-pressed="true"] { color: #1a1520; } }

/* ============================================================================
   Profile — a page belonging to a person, not a row about one
   ----------------------------------------------------------------------------
   The first cut of this was a single narrow strip of stacked cards, which is
   the phone screen given more room rather than a desktop layout — the same
   "mobile just stretched" the roster grid and `.home-deck` already answer.

   Who you are is context: it takes one side, it does not change, and on a wide
   screen it stays put while the settings scroll past it. What you can do is
   the content and gets the width. On a phone the grid collapses and the same
   DOM reads top to bottom — identity, what you came to change, the way out.
   ========================================================================== */

.me-layout { display: block; }
.me-side { margin-bottom: 20px; }
/* min-width so a long e-mail or a wide card cannot blow the grid track out. */
.me-main { min-width: 0; }

.me-card { text-align: center; }
.me-face {
  position: relative; display: inline-block;
  padding: 0; border: 0; background: none; cursor: pointer;
  border-radius: 20px; line-height: 0;
}
/* The picture is the thing on this screen somebody wants to change, so it is
   the thing you can press — not a field hidden inside an "Edit" sheet. */
.me-face-edit {
  position: absolute; right: -2px; bottom: -2px;
  display: flex; align-items: center; justify-content: center;
  width: 32px; height: 32px; border-radius: 50%;
  background: var(--accent); color: #fff;
  border: 3px solid var(--card);
}
@media (prefers-color-scheme: dark) { .me-face-edit { color: #1a1520; } }
.me-face:hover .me-face-edit { filter: brightness(1.08); }

.me-name {
  margin-top: 14px; font-size: 22px; font-weight: 650;
  letter-spacing: -.02em; line-height: 1.2; color: var(--ink);
}
.me-role { margin-top: 3px; font-size: 14px; color: var(--ink-2); }
.me-mail {
  margin-top: 2px; font-size: 13px; color: var(--ink-3);
  overflow: hidden; text-overflow: ellipsis;
}
.me-edit { margin-top: 16px; }

.me-group-head {
  font-size: 12px; font-weight: 600; letter-spacing: .06em;
  text-transform: uppercase; color: var(--ink-3);
  margin: 0 0 8px;
}
.me-block { margin-bottom: 22px; min-width: 0; }

/* Sign out was the twin of Change password — same size, same weight, one of
   them routine and one the end of the session. Quiet, and to one side. */
.me-out { margin-top: 10px; text-align: center; }
.me-signout {
  padding: 10px 18px; border-radius: 10px;
  border: 1px solid var(--line); background: transparent;
  color: var(--stop); font-size: 14px; cursor: pointer;
}
.me-signout:hover { background: var(--fill); }

.me-foot {
  text-align: center; color: var(--ink-3);
  font-size: 12px; margin-top: 18px;
}

@media (min-width: 880px) {
  /* Identity to one side, settings to the other. `align-items: start` so the
     short identity card does not stretch to the height of the column beside
     it, and `position: sticky` so it stays legible while the rest scrolls. */
  .me-layout {
    display: grid; gap: 32px; align-items: start;
    grid-template-columns: minmax(0, 300px) minmax(0, 1fr);
  }
  .me-side { margin-bottom: 0; position: sticky; top: 88px; }
  .me-out { text-align: left; }
}

@media (min-width: 1180px) {
  /* The two short groups abreast, so a three-row list and a one-row list stop
     leaving half the column empty. The wide one keeps the full width. */
  .me-deck {
    display: grid; gap: 0 24px; align-items: start;
    grid-template-columns: 1fr 1fr;
  }
  .me-block-wide { grid-column: 1 / -1; }
}

/* ----------------------------------------------------------------------------
   A case is its own screen

   It was briefly a pane beside the list. Two things were wrong with that: this
   screen holds a conversation with a composer in it, and a third of the window
   is a poor place to write a reply — and a case already has an identity. Every
   Case Loop notification carries a `case:22` deep link, and people here refer
   to these by number in conversation. A thing with a number that gets linked to
   wants an address.
   -------------------------------------------------------------------------- */

.fb-list { min-width: 0; }

/* One column on a phone, two on a monitor: what you do, and what you need to
   know. Stacked, the decision sat at the top, the conversation below the fold,
   and a phone screenshot at full column width pushed the comment box off the
   screen entirely. */
.case-cols { display: block; }
.case-main { min-width: 0; }
.case-side { min-width: 0; }

@media (min-width: 1000px) {
  .case-cols {
    display: grid; gap: 22px; align-items: start;
    grid-template-columns: minmax(0, 1fr) minmax(0, 320px);
  }
  /* Prose takes a column and not the monitor — 1180px of comment is one long
     line, the same "phone stretched" fault one screen along. */
  .case-main { max-width: 720px; }
  .case-side { position: sticky; top: 88px; }
}

/* Attachments are nearly always phone screenshots — tall, and at full width one
   of them pushed the comment box off the screen. A square crop says "there is a
   picture here and this is roughly it"; the picture opens at its own size. */
.att-grid {
  display: grid; gap: 8px; margin-top: 10px;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
}
.att {
  display: block; aspect-ratio: 1; overflow: hidden; padding: 0;
  border-radius: 9px; border: 1px solid var(--line);
  background: var(--fill); cursor: pointer; width: 100%;
}
.att:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }
.att img { width: 100%; height: 100%; object-fit: cover; display: block; }
.att:hover { border-color: var(--accent); }
.att-file {
  display: flex; flex-direction: column; align-items: center; justify-content: center;
  gap: 5px; aspect-ratio: 1; color: var(--ink-2); font-size: 11.5px; text-align: center;
}
.att-file svg { color: var(--accent); }
/* ----------------------------------------------------------------------------
   Search: one filter rail, not a column of boxes

   The filters were six separate cards stacked in a narrow column — the basics,
   one per facet group, then the school field — so narrowing a search meant
   reading down a stack of bordered rectangles with a gap between each. They are
   sections of one panel now, divided by a rule.
   -------------------------------------------------------------------------- */

.rail-card {
  background: var(--card); border: 1px solid var(--line);
  border-radius: var(--r); box-shadow: var(--shadow);
  overflow: hidden;
}
.rail-card + .rail-card { margin-top: 12px; }
.rail-sec { padding: 15px 16px; }
.rail-sec + .rail-sec { border-top: 1px solid var(--line); }
.rail-sec > .field + .field,
.rail-sec > .field + .field-row,
.rail-sec > .field-row + .field { margin-top: 12px; }

/* Quieter than `.section-head`, which is sized for a page and shouted inside a
   240px rail. */
.rail-head {
  font-size: 11.5px; font-weight: 600; letter-spacing: .05em;
  text-transform: uppercase; color: var(--ink-3);
  margin: 0 0 9px;
}
.rail-sec .chips.wrap { padding-bottom: 0; }

/* ----------------------------------------------------------------------------
   Search filters: tick boxes, not chips

   A chip is a thing already chosen — a tag on a record, a filter in force.
   These are a list you may pick several of, which is what a checkbox has meant
   since before the web. It also fixes a shape problem: "Growing in modesty -
   wears skirts shabbat / sometimes" as a pill wrapped to two lines inside a
   rounded rectangle the width of the rail and read as broken. As a row with a
   box beside it, a long option is just a long option.
   -------------------------------------------------------------------------- */

.opts { display: flex; flex-direction: column; gap: 1px; }

.opt {
  display: flex; align-items: flex-start; gap: 10px;
  padding: 7px 8px; margin: 0 -8px; border-radius: 8px;
  cursor: pointer; font-size: 13.5px; line-height: 1.35;
}
.opt:hover { background: var(--fill); }
.opt:has(input:disabled) { cursor: default; opacity: .45; }
.opt:has(input:disabled):hover { background: none; }

/* A box drawn rather than the platform's, so it matches the accent and sits on
   the same baseline as the first line of a label that wraps. */
.opt input[type="checkbox"] {
  appearance: none; flex: none;
  width: 17px; height: 17px; margin-top: 1px;
  border: 1.5px solid var(--line); border-radius: 5px;
  background: var(--card); cursor: pointer;
  transition: background .12s ease, border-color .12s ease;
}
.opt input[type="checkbox"]:hover { border-color: var(--accent); }
.opt input[type="checkbox"]:checked {
  background: var(--accent); border-color: var(--accent);
  /* The tick, drawn in the box rather than loaded as an icon. */
  background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='white' stroke-width='2.4' stroke-linecap='round' stroke-linejoin='round'%3E%3Cpath d='M3.5 8.5l3 3 6-6'/%3E%3C/svg%3E");
  background-size: 15px 15px; background-position: center; background-repeat: no-repeat;
}
.opt input[type="checkbox"]:focus-visible { outline: 2px solid var(--accent); outline-offset: 2px; }

.opt-label { flex: 1; min-width: 0; color: var(--ink); }
/* The count is a fact about the option, not part of its name — right-aligned
   and quiet so a column of them can be read down. */
.opt-n {
  flex: none; color: var(--ink-3); font-size: 12.5px;
  font-variant-numeric: tabular-nums; padding-top: 1px;
}

/* The lone toggle at the end of the first card. It carried `padding:0` inline,
   which beat both the section's padding and the switch's own, so it sat flush
   against the card's rounded corners while everything above it was inset. */
.opt-solo { margin: 0; border-radius: 0; align-items: center; }
.opt-solo:hover { background: none; }

/* A rail heading is a heading whatever it labels — a select, a pair of number
   boxes or a list of tick boxes. These were three different things at once. */
.rail-sec .field { margin-bottom: 0; }
.rail-sec .field + .rail-head { margin-top: 14px; }
.rail-sec .field-row + .rail-head { margin-top: 14px; }
.rail-sec .field-row .field > span {
  font-size: 12px; color: var(--ink-3); margin-bottom: 4px;
}

/* ---------- The person record, set like a dossier ----------

   Sam, on the old look: "I am not a fan of this bubble feel. It does not
   feel premium." The bubbles were capsule chips, floating rounded cards and
   pill tabs — a consumer-app vocabulary. A matchmaker's record reads better
   as an editorial page: the name in the brand serif, hairline rules instead
   of boxes, small-caps labels, and colour kept for the few things that mean
   something. Scoped to `.person` so no other sheet moves. */

.person .sheet-body { padding: 26px 28px 22px; }

/* The portrait: a framed rectangle, not a bubble. */
.person .avatar.lg {
  width: 150px; height: 192px; border-radius: 10px; font-size: 42px;
  box-shadow: 0 0 0 1px var(--line), var(--shadow);
}
.person .thumbstrip { margin-top: 10px; }

/* The name is the headline, in the face the brand is named in. */
.person h2 {
  font-family: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  font-size: 36px; font-weight: 600; line-height: 1.05; letter-spacing: .01em;
}
.person .sub {
  font-size: 12px; letter-spacing: .18em; text-transform: uppercase;
  margin-top: 4px;
}

/* Facts, not pills: quiet small caps separated by middle dots. */
.person .badge {
  background: none; padding: 0; border-radius: 0; gap: 0;
  font-size: 11px; font-weight: 500; letter-spacing: .16em;
  text-transform: uppercase; color: var(--ink-2);
}
.person .badge + .badge::before {
  content: "\00b7"; margin-right: 8px; color: var(--ink-3);
}
.person .badge.ok { color: var(--ok); }

/* Reaching them: one ruled band, no boxes. */
.person .person-contact {
  display: flex; gap: 8px; margin: 2px 0 18px; padding: 6px 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
.contact-tile {
  flex: 1; display: flex; flex-direction: column; align-items: center;
  justify-content: flex-end; gap: 7px; padding: 12px 4px;
  color: var(--accent); text-align: center;
}
.contact-tile.off { color: var(--ink-3); opacity: .55; }
.contact-tile .ct-icon { height: 20px; display: flex; align-items: center; }
.contact-tile .ct-label {
  font-size: 10px; letter-spacing: .14em; text-transform: uppercase;
  color: var(--ink-2);
}

/* Tabs are an underline, not a row of capsules. */
.person .chips { gap: 22px; padding: 0; margin-bottom: 16px;
  border-bottom: 1px solid var(--line); }
.person .chip {
  background: transparent; border: none; border-radius: 0;
  padding: 2px 1px 11px; font-size: 12px; font-weight: 500;
  letter-spacing: .14em; text-transform: uppercase; color: var(--ink-2);
}
.person .chip[aria-pressed="true"] {
  background: transparent; color: var(--ink); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}

/* The doors — Find the right single, Resume, Dating history — are entries
   in a ruled list, not floating cards. */
.person .card.row {
  background: transparent; border: none; border-radius: 0; box-shadow: none;
  border-top: 1px solid var(--line); padding: 15px 2px; margin: 0;
}
.person .card.row .title { font-weight: 500; font-size: 15px; }
.person .card.row .meta { font-size: 13px; }
.person .card.row .chev { color: var(--ink-3); }
.person .card.row:hover { background: var(--fill); }

/* The facts table: ruled rows, labels in small caps on the left. */
.person .card.rows {
  background: transparent; border: none; border-radius: 0; box-shadow: none;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
  margin-top: 0;
}
/* Between-row rules come from `.rows > * + *`; only the frame is added here. */
.person .card.rows .row {
  align-items: baseline; padding: 12px 2px;
}
.person .card.rows .row > .grow {
  font-size: 11px !important; letter-spacing: .15em; text-transform: uppercase;
  color: var(--ink-3) !important;
}

/* Buttons keep their weight but lose the lozenge. */
.person .btn { border-radius: 9px; }

/* The record as a page (Sam: "a detail screen, not a popover"). A centred
   reading column: a dossier is read, not docked. */
.person-top { max-width: 700px; margin: 16px auto 0; padding: 0 22px; }
.person.person-page {
  max-width: 700px; margin: 0 auto; padding: 8px 22px 72px;
}
.person.person-page .sheet-body { padding: 0; }

/* Desktop: the record as a two-column dossier. The rail holds the person —
   photograph, ways to reach them, actions — and the reading matter takes
   the width. Narrow windows stack, which is the phone layout. */
.person-side { text-align: center; }
.person-head { text-align: center; margin-bottom: 20px; }
.person-facts {
  margin-top: 8px; display: flex; gap: 6px 8px;
  justify-content: center; flex-wrap: wrap;
}
.person .person-portrait { margin-bottom: 16px; }
/* The watermark wrapper shrink-wraps by default; here it is the portrait's
   own box, or a width:100% avatar measures nothing and collapses to a
   thumbnail — which is exactly what shipped first. */
.person .person-portrait .watermarked { display: block; width: 100%; }
.person .person-portrait .avatar.lg {
  width: 100%; height: auto; aspect-ratio: 3 / 4; font-size: 64px;
  border-radius: 12px;
}

/* The actions are a ruled list, not a stack of lozenges. One filled button —
   Edit details — and everything else in quiet rows. */
.person-actions { margin-top: 0; text-align: left; }
.person-action {
  display: flex; align-items: center; width: 100%;
  padding: 12px 2px; border: none; background: none; color: var(--ink);
  font-size: 14px; font-weight: 500; text-align: left;
  border-bottom: 1px solid var(--line); border-radius: 0;
}

.person-action:hover { background: var(--fill); }
.person-action::after {
  content: "\2192"; margin-left: auto; color: var(--ink-3); font-size: 13px;
}
.person-action-vcard { margin: 0; border-top: 1px solid var(--line); }
.person .btn.primary.person-action-primary {
  width: 100%; margin: 14px 0; border-radius: 9px;
}

/* The way back is a word, not a lozenge. */
.person-top .btn.sm {
  background: none; padding: 6px 2px; font-weight: 500; font-size: 14px;
  color: var(--ink-2); border-radius: 0;
}
.person-top .btn.sm:hover { color: var(--ink); }

@media (min-width: 1000px) {
  .person-top { max-width: 1120px; }
  .person.person-page {
    max-width: 1120px;
    display: grid; grid-template-columns: 320px minmax(0, 1fr);
    gap: 8px 64px; align-items: start;
  }
  .person-side { position: sticky; top: 22px; }
  /* The reading column is addressed, not centred: a dossier header sits
     flush left over its own text. */
  .person-head, .person-main .sub { text-align: left; }
  .person-facts { justify-content: flex-start; }
  .person h2 { font-size: 44px; }
  .person .person-contact { justify-content: space-between; }
}

/* The tab rule already draws the line; the first door under it should not. */
.person-main .chips + .card.row { border-top: none; }

/* ---------- The registry ----------
   The roster in the dossier's language: serif headline, hairlines instead
   of a floating card, small-caps labels, tabs instead of capsules, and
   colour only where it means something. Scoped to `.registry`. */

.registry .screen-head h1 {
  font-family: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  font-size: 40px; font-weight: 600; letter-spacing: .01em;
}
.registry .screen-head .sub {
  font-size: 11px; letter-spacing: .16em; text-transform: uppercase;
  color: var(--ink-3); margin-top: 4px;
}

/* The head's controls step back: one filled circle for Add, everything else
   quiet. */
.registry .head-actions .chip {
  background: none; border-radius: 0; padding: 6px 2px;
  font-size: 12px; letter-spacing: .12em; text-transform: uppercase;
  color: var(--ink-2);
}
.registry .head-actions .chip.on { color: var(--accent); box-shadow: inset 0 -2px 0 var(--accent); }
.registry .head-actions select.icon-btn {
  background: none; border: none; border-bottom: 1px solid var(--line);
  border-radius: 0 !important; font-size: 13px; color: var(--ink-2);
  min-width: 110px;
}
.registry .head-actions .icon-btn:not(.filled):not(select) {
  background: none; border: 1px solid var(--line); border-radius: 9px;
}

/* Search is a rule, not a lozenge. */
.registry .search {
  background: none; border: none; border-bottom: 1px solid var(--line);
  border-radius: 0; padding: 6px 2px 12px; margin: 6px 0 4px;
}
.registry .search input { font-size: 16px; }
.registry .search svg { color: var(--ink-3); }

/* Gender and collections as tabs, the dossier's way. */
.registry .chips { gap: 22px; padding: 10px 0 0; }
.registry .chips + .chips {
  gap: 18px; padding: 2px 0 12px; border-bottom: 1px solid var(--line);
  margin-bottom: 0;
}
.registry .chip {
  background: none; border-radius: 0; padding: 4px 1px 9px;
  font-size: 12px; font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-2);
}
.registry .chip[aria-pressed="true"], .registry .chip.on {
  background: none; color: var(--ink); font-weight: 600;
  box-shadow: inset 0 -2px 0 var(--accent);
}
.registry .chip .n { font-weight: 500; opacity: .6; margin-left: 6px; }

/* The table sheds its card. */
.registry .roster-table {
  background: none; border: none; border-radius: 0; box-shadow: none;
  border-bottom: 1px solid var(--line);
}
.registry .roster-head {
  font-size: 10.5px; font-weight: 500; letter-spacing: .16em;
  color: var(--ink-3); border-bottom: 1px solid var(--line);
}
.registry .roster-row { padding-top: 12px; padding-bottom: 12px; }
.registry .roster-row .title { font-size: 15px; font-weight: 500; }
/* The face is a small plate, like the dossier portrait, not a bubble. */
.registry .roster-row .avatar.big {
  width: 52px; height: 64px; border-radius: 8px; font-size: 17px;
}
@media (min-width: 880px) {
  /* The status column is fixed, not auto: each row is its own grid, and a
     row with two badges was growing its column and shearing every cell out
     of line with its neighbours. */
  .registry .roster-row, .registry .roster-head {
    grid-template-columns: 52px minmax(180px, 1.4fr) 56px minmax(110px, 1fr)
                           72px minmax(120px, 1fr) 148px 40px 20px;
  }
  .registry .rr-status { flex-direction: column; align-items: flex-start; gap: 3px; }
}
/* Status is a word with a pulse, not a pill. */
.registry .rr-status .badge {
  background: none; padding: 0; border-radius: 0;
  font-size: 10.5px; font-weight: 500; letter-spacing: .14em;
  text-transform: uppercase; color: var(--ink-2);
}
.registry .rr-status .badge::before {
  content: ""; width: 6px; height: 6px; border-radius: 50%;
  background: var(--ink-3); flex-shrink: 0;
}
.registry .rr-status .badge.ok { color: var(--ok); }
.registry .rr-status .badge.ok::before { background: var(--ok); }
.registry .btn.block { background: none; border: 1px solid var(--line); border-radius: 9px; }

/* Coordinators: the fact-table small caps are for labels, not for people's
   names, and the list arrows are for doors, not for Save. */
.coord .card.rows .row > .grow {
  font-size: inherit !important; letter-spacing: normal;
  text-transform: none; color: inherit !important;
}
.coord .person-action::after { content: none; }
