/* One stylesheet for every page.
 *
 * ---------------------------------------------------------------------------
 * THE LOOK IS THE EXCHANGE'S, AND THAT IS THE POINT
 *
 * Same owner, same city, same two languages, and people will meet both. So this
 * is not "inspired by" thexchange.app — it is the same design language, taken
 * deliberately: the cool grey ground, the Georgia heading over a sans line, the
 * tracked small-caps rule over a section, the one blue accent, the icon tab bar,
 * the left-ruled caution. A first version of this app was built in warm cream
 * and amber, and it was a different product's clothes on this one.
 *
 * WHAT IS NOT BORROWED, each with its reason below: the deck card, which The
 * Exchange has no equivalent of, and the message bubbles, which it draws
 * differently because a noticeboard's introductions are not a conversation.
 *
 * ---------------------------------------------------------------------------
 * PHONE FIRST AND ALMOST ONLY. Everybody using this is holding a phone,
 * standing up, in a city they arrived in three weeks ago. The laptop layout is
 * the phone layout in a column — deliberate, not unfinished.
 *
 * WHAT MOVES WITH THE LANGUAGE. A bolted-on translation gives itself away in
 * the type before anybody reads a word: Georgia has no Chinese glyphs and falls
 * back per character mid-sentence, CJK wants more room between lines, and
 * Chinese has no case, so a tracked-out uppercase label reads as broken rather
 * than as a label.
 */

:root {
  color-scheme: light dark;

  /* Three surfaces. paper is the ground, raise is lifted off it, card is lifted
     off that. The Exchange learned the cost of having only two: a page keeping
     its own copy of the palette was missing one, and a whole panel came out
     transparent. */
  --paper: #eef0f4;
  --raise: #f7f8fb;
  --card: #fcfcfd;
  --ink: #151b28;
  --ink-2: #4e5968;
  --muted: #8a939f;
  --line: #e3e6ec;
  --hair: #eaecf1;

  /* ONE ACCENT, SPENT IN THREE PLACES: the message you sent, the button that
     sends it, and the tab you are on. Nowhere else — that restraint is what
     makes a colour read as an identity rather than as decoration. */
  --accent: #3f68d8;
  --accent-ink: #ffffff;
  --accent-soft: #e8ecf7;

  /* The seconds. Amber marks a caution, red marks a way out. The same two the
     parent uses, and the amber is the bolt in the mark. */
  --warm: #d99038;
  --hot: #d4485c;

  --serif: Georgia, "Songti SC", "Noto Serif CJK SC", "Times New Roman", serif;
  --sans: -apple-system, BlinkMacSystemFont, "Segoe UI", "PingFang SC",
    "Hiragino Sans GB", "Microsoft YaHei", "Noto Sans SC", sans-serif;
  --shadow: 0 1px 2px rgba(21, 27, 40, .05), 0 10px 30px rgba(21, 27, 40, .08);

  --pad: 1.15rem;
  /* HOW WIDE THE APP IS, one number, read by the page and the tab bar. */
  --col: 34rem;
  --top: env(safe-area-inset-top, 0px);
  --bottom: env(safe-area-inset-bottom, 0px);
}

/* The phone's setting, unless the person has overruled it — see setTheme in
   app.js. Guarded with :not([data-theme="light"]) so choosing light on a dark
   phone works; repeated for [data-theme="dark"] so choosing dark on a light
   phone works too. Either half alone is a toggle that only moves one way. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --paper: #0b0e15;
    --raise: #141924;
    --card: #1b212d;
    --ink: #e9edf5;
    --ink-2: #a3b0c4;
    --muted: #6f7d92;
    --line: #232b39;
    --hair: #1e2531;
    --accent: #5c8dff;
    --accent-ink: #0b0e15;
    --accent-soft: #18233d;
    --warm: #e4a65a;
    --hot: #f0687c;
    --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 34px rgba(0, 0, 0, .32);
  }
}
:root[data-theme="dark"] {
  --paper: #0b0e15;
  --raise: #141924;
  --card: #1b212d;
  --ink: #e9edf5;
  --ink-2: #a3b0c4;
  --muted: #6f7d92;
  --line: #232b39;
  --hair: #1e2531;
  --accent: #5c8dff;
  --accent-ink: #0b0e15;
  --accent-soft: #18233d;
  --warm: #e4a65a;
  --hot: #f0687c;
  --shadow: 0 1px 2px rgba(0, 0, 0, .4), 0 12px 34px rgba(0, 0, 0, .32);
}

html[data-lang="zh"] {
  --serif: "Songti SC", "Noto Serif CJK SC", "Source Han Serif SC", "STSong", Georgia, serif;
  --sans: -apple-system, BlinkMacSystemFont, "PingFang SC", "Hiragino Sans GB",
    "Microsoft YaHei", "Noto Sans SC", "Segoe UI", sans-serif;
}
html[data-lang="zh"] body { line-height: 1.75; }
html[data-lang="zh"] .rule span { text-transform: none; letter-spacing: .02em; }

* { box-sizing: border-box; }

/* One size up from the browser default, which is a size chosen for documents.
   This is read one-handed, at arm's length, by somebody walking. */
html { font-size: 17px; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font: 1rem/1.5 var(--sans);
  -webkit-font-smoothing: antialiased;
  /* No bounce past the ends: on a deck that is one card, a rubber-band scroll
     reads as the card being draggable in a direction it is not. */
  overscroll-behavior-y: none;
}

img { max-width: 100%; display: block; }
[hidden] { display: none !important; }

/* ---------------------------------------------------------------------------
 * The page head
 *
 * A LARGE SERIF TITLE OVER A SANS LINE, which is the parent's signature and the
 * thing that makes a screen there recognisable before a word is read. It
 * scrolls away rather than sticking: a sticky bar is for a screen you are deep
 * inside, and these are the screens you arrive at.
 * ------------------------------------------------------------------------- */
.top {
  display: flex;
  align-items: flex-start;
  gap: .8rem;
  /* THE HEAD SITS OUTSIDE main, so it carries main's column and gutter itself.
     Without them the title starts hard against the glass — which is what it
     did, and which reads as a stylesheet that failed to load rather than as a
     design. */
  max-width: var(--col);
  margin: 0 auto;
  /* The parent's own spacing: .5rem above, 1.6rem below, .9rem between the
     name and whatever is beside it. */
  padding: calc(var(--top) + .5rem) var(--pad) 1.6rem;
  gap: .9rem;
}
.top h1 {
  font: 400 1.85rem/1.1 var(--serif);
  letter-spacing: -.01em;
  margin: 0;
  display: flex;
  align-items: center;
  gap: .55rem;
}
html[data-lang="zh"] .top h1 { letter-spacing: 0; line-height: 1.25; }
.top h1 svg { width: 1.5rem; height: 1.5rem; flex: none; border-radius: .38rem; }
.top .sub { margin: .2rem 0 0; color: var(--muted); font-size: .86rem; }
.top .grow { flex: 1; }

/* THE BRAND ROW. The front door's head — see brandTop in app.js for why this
   screen gets a line and the others get a title. */
.top.brand { align-items: center; padding-top: calc(var(--top) + .9rem); padding-bottom: .8rem; }
.top.brand h1 { font-size: 1.5rem; gap: .5rem; }
.top.brand h1 svg { width: 2.1rem; height: 2.1rem; border-radius: .55rem; }

/* A round action in the head. The parent's shape: a filled circle for the one
   thing this screen is for, an outlined one for everything else. */
button.act {
  flex: 0 0 auto;
  width: 2.5rem; height: 2.5rem; min-height: 0;
  border-radius: 50%; padding: 0;
  font-size: 1.15rem; line-height: 1;
}
button.act.on { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }

/* The language button, and it says the language it switches TO — one that says
   English while you are reading English is one nobody presses. A pill rather
   than a circle: 中文 is two characters and EN is two letters, and a circle
   tight enough for one is too tight for the other. */
button.lang {
  flex: 0 0 auto;
  height: 2.5rem;
  padding: 0 .85rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink-2);
  font-size: .85rem;
  min-height: 0;
}

/* A section, with its name tracked out over a rule. */
.rule { display: flex; align-items: center; gap: .8rem; margin: 1.6rem 0 .7rem; }
.rule span {
  font-size: .74rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--muted);
  flex: 0 0 auto;
}
.rule::after { content: ""; flex: 1; height: 1px; background: var(--line); }

main {
  max-width: var(--col);
  margin: 0 auto;
  padding: 0 var(--pad) 5.6rem;
}

/* ---------------------------------------------------------------------------
 * The tabs
 *
 * Icons over labels, the accent on the one you are on. Three, always the same
 * three, always in the same order — the one navigation decision on a phone that
 * must never move.
 * ------------------------------------------------------------------------- */
.tabs {
  position: fixed;
  left: 0; right: 0; bottom: 0;
  z-index: 6;
  display: flex;
  padding: .5rem var(--pad) calc(var(--bottom) + .45rem);
  background: color-mix(in srgb, var(--paper) 92%, transparent);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
}
.tabs a {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: .18rem;
  padding: .3rem 0;
  color: var(--muted);
  text-decoration: none;
  font-size: .74rem;
}
.tabs a svg {
  width: 1.45rem; height: 1.45rem;
  stroke: currentColor; fill: none;
  stroke-width: 1.6; stroke-linecap: round; stroke-linejoin: round;
}
.tabs a[aria-current="page"] { color: var(--accent); }

/* ---------------------------------------------------------------------------
 * The pieces
 * ------------------------------------------------------------------------- */
button, .btn {
  font: inherit;
  font-weight: 560;
  border: 1px solid var(--line);
  background: var(--card);
  color: var(--ink);
  border-radius: .7rem;
  padding: .7rem 1rem;
  cursor: pointer;
  /* The smallest thing a thumb hits reliably, and every button here is pressed
     one-handed by somebody walking. */
  min-height: 2.6rem;
  text-decoration: none;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
}
button:active, .btn:active { transform: translateY(1px); }
button:disabled { opacity: .45; cursor: default; }
/* .btn is the same thing drawn as a link — the base rule above covers both and
   these three did not, so `a.btn.solid` came out as a plain white button. It
   was only on Browse's "Make your card"; it is on three screens now. */
button.solid, .btn.solid { background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
button.warn, .btn.warn { color: var(--hot); }
button.plain, .btn.plain { border-color: transparent; background: none; padding: .45rem; min-height: 2.2rem; }

label {
  display: block;
  font-size: .74rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--muted);
  margin: 1.4rem 0 .45rem;
}
html[data-lang="zh"] label { text-transform: none; letter-spacing: .02em; font-size: .82rem; }

input[type="text"], input[type="date"], input[type="month"], textarea, select {
  width: 100%;
  font: inherit;
  color: var(--ink);
  background: var(--card);
  border: 1px solid var(--line);
  border-radius: .7rem;
  padding: .7rem .8rem;
  /* Never below 16px or iOS zooms the page on focus and never zooms back. */
  font-size: 1rem;
}
textarea { resize: vertical; min-height: 5rem; font-family: inherit; }

.hint { font-size: .85rem; color: var(--muted); margin: .4rem 0 0; }
.row { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; }
.grow { flex: 1; }

/* A row of taps rather than a dropdown: a <select> on a phone is a wheel that
   hides every option but one. */
.picks { display: flex; flex-wrap: wrap; gap: .45rem; }
.picks button { padding: .5rem .85rem; min-height: 2.3rem; border-radius: 999px; font-size: .9rem; }
.picks button[aria-pressed="true"] {
  background: var(--accent-soft);
  border-color: var(--accent);
  color: var(--accent);
}

/* An empty screen: serif heading, sans body, centred. */
.note { padding: 2.6rem .5rem; text-align: center; }
.note h2 { font: 400 1.5rem/1.2 var(--serif); margin: 0 0 .5rem; }
.note p { margin: 0 auto; color: var(--ink-2); max-width: 26rem; }
.note .btn, .note button { margin-top: 1.1rem; }

/* A CAUTION, RULED DOWN ITS LEFT EDGE. The parent's shape, and the right one
   for the single sentence this app owes somebody who just arrived. */
.callout {
  background: var(--raise);
  border: 1px solid var(--line);
  border-left: 3px solid var(--warm);
  border-radius: .7rem;
  padding: .85rem 1rem;
  margin: 1.4rem 0;
  color: var(--ink-2);
  font-size: .92rem;
}

/* ---------------------------------------------------------------------------
 * Browse
 *
 * LIFTED FROM THE EXCHANGE, RULE FOR RULE (.deck and .pc in its
 * board/public/index.html). Not re-derived: these are its numbers — the 1.3rem
 * corners, the 5rem scrim, the 1.8rem serif name, the .92rem where-line at 82%
 * white, the person's own line in serif at 1.02rem clamped to three lines, the
 * pill actions and the count under them.
 *
 * The parts that are its product and not this one are left out: the four-letter
 * type badge, the rank mark, the days strip.
 * ------------------------------------------------------------------------- */

/* THE STAGE, WITH A HEIGHT AND NOT AN ASPECT RATIO. The card is absolutely
   positioned inside it, which is what lets one swap for the next without the
   page reflowing under a thumb. The height is the screen minus what is above
   and below it — and it reads --vh, set in app.js, because 100vh on a phone is
   measured without the browser's own chrome and is always a little too tall. */
.deck {
  position: relative;
  width: 100%;
  height: max(13rem, min(62vh, 34rem, calc(var(--vh, 1vh) * 100 - 21rem - env(safe-area-inset-bottom))));
  margin: 0 0 .9rem;
  touch-action: pan-y;
  overflow: hidden;
  border-radius: 1.3rem;
}
.pc {
  position: absolute;
  inset: 0;
  border-radius: 1.3rem;
  overflow: hidden;
  background: var(--raise);
  box-shadow: var(--shadow);
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}
.pc img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }
/* No photograph is not a hole: the initial, big, on the accent, behind the
   words rather than instead of them. */
.pc .noshot {
  position: absolute; inset: 0;
  display: grid; place-items: center; align-content: start;
  padding-top: 1.1rem;
  background: var(--accent); color: var(--accent-ink);
  font-family: var(--serif); font-size: 4.5rem; line-height: 1; opacity: .55;
}
/* Words on a scrim, not straight onto the photograph: white on a picture is
   legible right up until somebody uploads a bright one. */
.pc .scrim {
  position: relative;
  padding: 5rem 1.2rem 1.2rem;
  background: linear-gradient(to top, rgba(10, 14, 22, .86), rgba(10, 14, 22, .52) 42%, rgba(10, 14, 22, 0));
}
.pc .nameline { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.pc h2 { margin: 0; font-family: var(--serif); font-weight: 400; font-size: 1.8rem; line-height: 1.1; color: #fff; }
/* Their badge family, doing this app's one job: an age. Shared with .hero so
   your own card carries the same badge everybody else is shown — a pill on
   Browse and bare text on Profile is the same fact drawn two ways. */
.pc .type, .hero .type {
  flex: 0 0 auto; font-weight: 500; font-size: .78rem; letter-spacing: .06em;
  background: rgba(255, 255, 255, .2); color: #fff;
  border-radius: 99px; padding: .25rem .6rem;
}
.pc .where { margin: .3rem 0 0; font-size: .92rem; color: rgba(255, 255, 255, .82); }
/* Their own line, in serif, clamped to three — a card is looked at for about a
   second and a fourth line is a line nobody reads. */
.pc .goal {
  margin: .6rem 0 0;
  font-family: var(--serif); font-size: 1.02rem; line-height: 1.5;
  color: rgba(255, 255, 255, .92);
  display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.pc .alt { margin: .35rem 0 0; font-size: .86rem; color: rgba(255, 255, 255, .68); }
.pchips { display: flex; flex-wrap: wrap; gap: .3rem; margin: .6rem 0 0; }
.pchip {
  font-size: .72rem; padding: .22rem .55rem; border-radius: 99px;
  border: 1px solid rgba(255, 255, 255, .35); background: rgba(255, 255, 255, .14); color: #fff;
}
.pchip.on { background: #fff; color: var(--ink); border-color: #fff; }
.pc .more {
  position: absolute; top: .8rem; right: .8rem; z-index: 3;
  width: 2.2rem; height: 2.2rem; min-height: 0; padding: 0;
  border-radius: 50%; border: 0;
  background: rgba(10, 14, 22, .55); color: #fff; font-size: 1rem; line-height: 1;
  backdrop-filter: blur(6px);
}
.dots { position: absolute; top: .7rem; left: .8rem; right: 3.4rem; display: flex; gap: .2rem; z-index: 2; }
.dots i { height: 3px; flex: 1; border-radius: 2px; background: rgba(255, 255, 255, .35); }
.dots i.on { background: #fff; }

/* Their action row, verbatim. Flex hands a child its longest word before it
   takes space from a sibling, so both need telling they may be narrower than
   their content — which is why the `>*` rule is there. */
.pacts { display: flex; gap: .6rem; width: 100%; }
.pacts > * { flex: 1 1 0; min-width: 0; }
.pacts button {
  border: 1px solid var(--line); background: var(--raise); color: var(--ink);
  border-radius: 99px; padding: .8rem 1rem; font-size: 1rem;
  text-align: center; min-height: 0;
}
.pacts .primary { background: var(--accent); border-color: var(--accent); color: var(--accent-ink); }
.pcount { margin: .7rem 0 0; text-align: center; font-size: .82rem; color: var(--muted); font-variant-numeric: tabular-nums; }

/* THE FILTER, THEIR .filt — a solid accent pill, not a row with a caret. And
   above the card, which is the second of their Six Changes to Browse: the
   control that decides who you are shown must not sit under the person it
   showed you. */
.topics { position: relative; display: flex; gap: .5rem; align-items: center; margin: 0 0 .9rem; }
.filt {
  display: inline-flex; align-items: center; gap: .5rem;
  border: 1px solid var(--accent); background: var(--accent); color: var(--accent-ink);
  border-radius: 99px; padding: .6rem 1.1rem; font-size: .98rem; line-height: 1;
  min-height: 0;
}
.filt .cv { font-style: normal; font-size: .72rem; opacity: .8; }
.filt.quiet { background: var(--raise); border-color: var(--line); color: var(--ink); }

/* ---------------------------------------------------------------------------
 * The home screen, offered
 *
 * One strip at the top of Messages, once, with an × that means never again.
 * Quiet on purpose — it is an offer, not a warning, and the amber callout at
 * the bottom of this screen is the one thing here allowed to raise its voice.
 * ------------------------------------------------------------------------- */
.ins {
  display: flex; align-items: center; gap: .6rem;
  margin: .8rem 0 0; padding: .6rem .7rem .6rem .85rem;
  border: 1px solid var(--line); border-radius: .8rem;
  background: var(--raise);
  font-size: .86rem; line-height: 1.45; color: var(--ink-2);
}
.ins > span { flex: 1; min-width: 0; }
/* The share glyph sits IN the sentence, so it rides the text baseline rather
   than the line box — otherwise it hangs below the words around it. */
.insico { width: 1.05em; height: 1.05em; vertical-align: -.18em; margin: 0 .1em; }
.ins .insgo {
  flex: none; min-height: 2rem; padding: .35rem .8rem; font-size: .85rem;
  border-radius: 99px; background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}
.ins .insno {
  flex: none; min-height: 0; padding: .2rem .35rem; border: 0; background: none;
  color: var(--muted); font-size: 1.05rem; line-height: 1;
}

/* ---------------------------------------------------------------------------
 * YOUR PEOPLE — the strip of faces over the conversations
 *
 * The Exchange's one deliberate departure from WeChat on its Messages screen,
 * and the reason carries word for word: WeChat hides starting a chat behind the
 * ＋ in the corner and a billion people know it is there. Nobody here does. The
 * strip is what stops an empty inbox being a dead end, so it is on the screen
 * rather than behind a button — and it is why the head of this page no longer
 * has a ＋ of its own.
 *
 * EVERYBODY YOU MATCHED, including people already in the list underneath. That
 * list is your conversations, which is a different question, and the same face
 * appearing twice is how every messenger with a row of faces already works.
 * ------------------------------------------------------------------------- */
.cansub { margin: .1rem 0 .7rem; font-size: .84rem; line-height: 1.5; color: var(--muted); }
.can {
  display: flex; gap: .9rem; overflow-x: auto;
  padding: .1rem .1rem 1rem; margin: 0;
  /* No scrollbar on either engine. A 4px grey bar under a row of faces is the
     one thing on this screen that says "web page". */
  scrollbar-width: none; -webkit-overflow-scrolling: touch;
}
.can::-webkit-scrollbar { display: none; }
.can .one {
  flex: 0 0 auto; width: 4.4rem; min-height: 0;
  border: 0; background: none; padding: 0;
  font: inherit; text-align: center; color: var(--ink); text-decoration: none;
  /* SAID OUT LOUD BECAUSE A BUTTON IS AN inline-flex ROW HERE — see the base
     rule above. Without this the ring and the name sit side by side and the
     circle is squeezed to an oval by its own label. The two \uFF0B circles are
     buttons and the faces are links, so it looked right for the faces and
     wrong for the two things anybody presses first. */
  display: block; gap: 0;
}
.can .one b {
  display: block; margin: .4rem 0 0; font-size: .78rem; font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
/* THE RING IS THE WHOLE AFFORDANCE. A circle on this app is otherwise a face in
   a room; a circle with the accent around it is somebody you may write to. Two
   rings rather than one so it reads off a bright photograph as well as off the
   paper. */
.can .av {
  width: 3.5rem; height: 3.5rem; margin: 0 auto;
  display: grid; place-items: center; overflow: hidden;
  border-radius: 50%; background: var(--raise);
  font: 400 1.35rem/1 var(--serif); color: var(--ink-2);
  box-shadow: 0 0 0 2px var(--paper), 0 0 0 4px var(--accent);
}
.can .av img { width: 100%; height: 100%; object-fit: cover; }
/* The ＋ and the room button at the head of the strip. A dashed ring rather
   than a filled disc: it is the space a person goes in, not a person. */
.can .one.add .av {
  background: none; border: 2px dashed var(--line); box-shadow: none;
  color: var(--accent); font: 400 1.5rem/1 var(--sans);
}
.can .one.add:hover .av { border-color: var(--accent); }
/* Two discs for the room button, because a room is three people to fifty and
   one circle would read as one of them. */
.can .av.pile { grid-auto-flow: column; place-content: center; }
.can .av.pile i {
  display: block; width: 1.3rem; height: 1.3rem; border-radius: 50%;
  background: var(--accent); border: 2px solid var(--paper);
}
.can .av.pile i + i { margin-left: -.45rem; }

/* ---------------------------------------------------------------------------
 * WHO IS IN, AS A NUMBER AND NEVER A LIST
 *
 * The Exchange puts this strip over Browse and the reason carries: a deck that
 * has run out, with no number anywhere on the screen, reads as an app that is
 * broken rather than as a city that is early. Being somewhere has no weight
 * unless you can see the edge of it.
 *
 * A COUNT, A CITY, AND NOTHING ELSE. No name, no face, nothing that could tell
 * anybody who is in it — the same rule the deck itself is under.
 * ------------------------------------------------------------------------- */
.stat {
  display: flex; align-items: center; gap: .8rem; width: 100%; min-height: 0;
  margin: 0 0 .8rem; padding: .8rem .95rem;
  border: 1px solid var(--hair); border-radius: 1rem;
  background: var(--card); box-shadow: var(--shadow);
  font: inherit; text-align: left; color: inherit; text-decoration: none;
}
.stat .grow { min-width: 0; }
.stat b { display: block; font-weight: 600; font-size: .98rem; }
.stat p { margin: .12rem 0 0; font-size: .84rem; color: var(--muted); }
.stat .cv { flex: none; font-style: normal; color: var(--muted); }

/* ---------------------------------------------------------------------------
 * Your own card, drawn as a card
 *
 * The Exchange's Profile is the photograph with the name written across it and
 * three buttons under it. The form is what you get when you press Edit; it is
 * not what the screen IS. Same here — a page that opens on a name field is a
 * form, and nobody's face is a form.
 * ------------------------------------------------------------------------- */
.hero {
  position: relative; margin: 0 0 .9rem;
  height: max(13rem, min(44vh, calc(var(--vh, 1vh) * 100 - 22rem)));
  border-radius: 1.3rem; overflow: hidden;
  background: var(--raise); box-shadow: var(--shadow);
  display: flex; flex-direction: column; justify-content: flex-end;
}
.hero img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.hero .noshot {
  position: absolute; inset: 0; display: grid; place-items: center;
  background: var(--accent); color: var(--accent-ink);
  font: 400 5rem/1 var(--serif); opacity: .55;
}
.hero .scrim {
  position: relative; padding: 4.5rem 1.15rem 1.15rem;
  background: linear-gradient(to top, rgba(10, 14, 22, .86), rgba(10, 14, 22, .5) 44%, rgba(10, 14, 22, 0));
}
.hero .nameline { display: flex; align-items: baseline; gap: .6rem; flex-wrap: wrap; }
.hero h2 { margin: 0; font: 400 2rem/1.1 var(--serif); color: #fff; }
.hero .where { margin: .3rem 0 0; font-size: .95rem; color: rgba(255, 255, 255, .82); }

/* ---------------------------------------------------------------------------
 * The list of conversations
 * ------------------------------------------------------------------------- */
.list { list-style: none; margin: 0; padding: 0; }
.list li { border-bottom: 1px solid var(--hair); }
.list li:last-child { border-bottom: none; }
.list a {
  display: flex; gap: .75rem; align-items: center;
  padding: .75rem .1rem; text-decoration: none; color: inherit;
}
.list a:hover { background: var(--raise); }
/* A rounded SQUARE in a list, a circle everywhere else — the parent's note is
   right that it is what tells a conversation row apart at a glance. */
.who { width: 2.9rem; height: 2.9rem; border-radius: .8rem; object-fit: cover; background: var(--raise); flex: none; }
.who.round { border-radius: 50%; }
.who.pile { display: grid; place-items: center; font-size: .82rem; color: var(--muted); font-weight: 600; }
/* A room's face is the people in it, stacked. Three at most — a fourth at this
   size is a smudge — and the count goes under the name instead. */
.who.stack { position: relative; background: none; }
.who.stack img {
  position: absolute; width: 1.85rem; height: 1.85rem; border-radius: .5rem;
  object-fit: cover; background: var(--raise); border: 2px solid var(--paper);
}
.who.stack img:nth-child(1) { top: 0; left: 0; }
.who.stack img:nth-child(2) { bottom: 0; right: 0; }
.who.stack img:nth-child(3) { bottom: 0; left: 0; z-index: 1; }
.list .name { font: 400 1.15rem/1.25 var(--serif); }
.list .said { color: var(--muted); font-size: .88rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.list .when { color: var(--muted); font-size: .72rem; flex: none; align-self: flex-start; padding-top: .3rem; }

/* A way out without opening it. Lifted from the parent including the mistake it
   records: the row was first made to SLIDE, and sliding took its left edge off
   the screen — the face and the name went with it, so the one thing you could
   no longer see was the conversation you were about to leave. It shrinks. */
.swipe { position: relative; overflow: hidden; }
.swipe .wipe {
  position: absolute; top: 0; right: 0; bottom: 0;
  width: 6rem; border: 0; border-radius: 0;
  background: var(--hot); color: #fff;
  font: inherit; font-size: .88rem;
  cursor: pointer; padding: 0;
}
.swipe a { position: relative; background: var(--paper); width: 100%; transition: width .16s ease; touch-action: pan-y; }
.swipe.open a { width: calc(100% - 6.4rem); }
@media (prefers-reduced-motion: reduce) { .swipe a { transition: none; } }

/* ---------------------------------------------------------------------------
 * THE PIN OVER A ROOM
 *
 * The Exchange's shape and its reasoning, both: one button across the top of a
 * room showing the reader their OWN line and nobody else's, with everything
 * that would have been a paragraph behind it.
 *
 *   "A panel at the top of a room says the same thing every visit in the place
 *    people came to read a conversation. A button is one line."
 *
 * It redraws only when a number has actually changed — the room polls every
 * five seconds, and a control rebuilt on each pass moves focus and restarts
 * whatever is being read aloud.
 * ------------------------------------------------------------------------- */
.pin {
  display: block; width: 100%;
  margin: .5rem 0 .2rem;
  padding: .6rem .85rem;
  border: 1px solid var(--line);
  background: var(--raise);
  border-radius: .8rem;
  text-align: left;
  min-height: 0;
}
.pin .big { font-size: .95rem; font-weight: 600; }
.pin .small { font-size: .78rem; color: var(--muted); }
/* Your own line, in the accent, because it is the half of this that is yours. */
.pin .yours { color: var(--accent); font-weight: 600; }
/* ---------------------------------------------------------------------------
 * WHO WAS EARLY
 *
 * DARK IN BOTH THEMES, AND THAT IS THE DECISION — The Exchange's call on the
 * same panel, and the reasoning carries: everything else here follows the
 * reader's theme because it is the app; this is one OBJECT sitting on the app,
 * and an object that keeps its own ground reads as a thing rather than as a
 * paragraph with a border. It is also how it gets its contrast for free —
 * near-white on near-black clears every ratio without a morning spent on it.
 * ------------------------------------------------------------------------- */
.pts {
  --d-bg: #10141c; --d-card: #1a1f2b; --d-line: #262d3b;
  --d-ink: #f2f5fa; --d-mute: #b4c1d2; --d-key: #5c8dff;
  margin: 0 0 1.1rem; padding: 1rem;
  border-radius: 1rem; overflow: hidden;
  background: var(--d-bg); color: var(--d-ink);
}
.pts .ptshead { display: flex; align-items: center; gap: .9rem; }
/* The place, as a mark rather than a line of text: it is the one number
   somebody came to this panel to read. */
.pts .ptsplace {
  flex: 0 0 auto; min-width: 4.6rem; padding: .5rem .6rem;
  border-radius: .7rem; background: var(--d-card); text-align: center;
}
.pts .ptsplace b {
  display: block; font: 400 1.5rem/1.1 var(--serif); color: var(--d-key);
  font-variant-numeric: tabular-nums;
}
.pts .ptsplace span { display: block; margin: .15rem 0 0; font-size: .68rem; color: var(--d-mute); }
.pts .k {
  font-size: .68rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--d-mute); font-weight: 700;
}
html[data-lang="zh"] .pts .k { text-transform: none; letter-spacing: .02em; font-size: .76rem; }
.pts .big { margin: .2rem 0 0; font-size: 1.35rem; font-weight: 700; font-variant-numeric: tabular-nums; }
/* What the total is made of. A number nobody can take apart is a number they
   have no reason to believe. */
.pts .ptsrows { margin: .85rem 0 0; border-top: 1px solid var(--d-line); }
.pts .ptsrow {
  display: flex; gap: .6rem; padding: .45rem 0;
  font-size: .85rem; color: var(--d-mute);
}
.pts .ptsrow + .ptsrow { border-top: 1px solid var(--d-line); }
.pts .ptsrow span:last-child { color: var(--d-ink); font-weight: 600; font-variant-numeric: tabular-nums; }
.pts .ptsgo { width: 100%; margin: .9rem 0 0; }
/* The honest line keeps its own colours here, because the panel does. */
.pts .honest {
  margin: .9rem 0 0; background: var(--d-card);
  border-left-color: var(--warm); color: var(--d-mute);
}

/* ---------------------------------------------------------------------------
 * A ledger, on the page that is only ledgers
 *
 * The same bar and the same numbers as the pin over a room, at the size of
 * something you came to read rather than something sitting above a
 * conversation. The bar's rules are the pin's, said once for both, because two
 * progress bars in one product that do not agree is two progress bars.
 * ------------------------------------------------------------------------- */
.led {
  margin: 0 0 .9rem;
  padding: .95rem 1rem 1rem;
  border: 1px solid var(--hair);
  border-radius: 1rem;
  background: var(--card);
  box-shadow: var(--shadow);
}
.led .to { display: flex; align-items: baseline; gap: .6rem; text-decoration: none; color: inherit; }
.led .to .cv { font-style: normal; color: var(--muted); flex: none; }
.led h2 { margin: 0; font: 400 1.3rem/1.2 var(--serif); min-width: 0; }
.led .when { color: var(--muted); font-size: .72rem; flex: none; }
.led .why { margin: .3rem 0 .8rem; color: var(--ink-2); font-size: .92rem; line-height: 1.5; }
.led .why.quiet { color: var(--muted); }
.led .big { font-size: 1.05rem; font-weight: 600; font-variant-numeric: tabular-nums; }
.led .small { margin: .35rem 0 0; font-size: .78rem; line-height: 1.5; color: var(--muted); }
.led .yours { color: var(--accent); font-weight: 600; }
/* WHO STILL OWES YOU. A list of rows with a tick at the end, on the card, only
   for the person being paid — see ledgerLines in the store for why it is
   theirs alone. Settled rows stay and go quiet rather than disappearing: a
   record that deletes what has been dealt with is not a record. */
.owing { margin: .9rem 0 0; padding: .8rem 0 0; border-top: 1px solid var(--hair); }
.owing .k {
  font-size: .7rem; letter-spacing: .12em; text-transform: uppercase;
  color: var(--muted); font-weight: 700;
}
html[data-lang="zh"] .owing .k { text-transform: none; letter-spacing: .02em; font-size: .78rem; }
.owing .how { margin: .35rem 0 .2rem; font-size: .78rem; line-height: 1.5; color: var(--muted); }
.owing .line { display: flex; align-items: center; gap: .65rem; padding: .55rem 0; }
.owing .line + .line { border-top: 1px solid var(--hair); }
.owing .line .grow { min-width: 0; }
.owing .line .name { font-size: .95rem; font-weight: 560; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.owing .line .said { font-size: .8rem; color: var(--muted); font-variant-numeric: tabular-nums; }
.owing .line button { min-height: 2.1rem; padding: .35rem .8rem; font-size: .85rem; border-radius: 99px; flex: none; }
/* Settled, and still on the page. Dimmed rather than removed. */
.owing .line.done { opacity: .5; }
.owing .line .who { width: 2.2rem; height: 2.2rem; }

.led .acts { margin: .9rem 0 0; }
.led .acts button { width: 100%; }
.pin .bar, .led .bar {
  height: 5px; border-radius: 3px;
  background: var(--line);
  margin: .4rem 0 .3rem;
  overflow: hidden;
}
.pin .bar i, .led .bar i { display: block; height: 100%; background: var(--accent); border-radius: 3px; }

/* THE SENTENCE THAT KEEPS IT HONEST, on the screen where somebody is about to
   promise money rather than in a terms page nobody opens. */
.honest {
  font-size: .82rem; color: var(--ink-2);
  background: var(--raise);
  border-left: 3px solid var(--warm);
  border-radius: .5rem;
  padding: .6rem .8rem;
  margin: .8rem 0;
}

/* ---------------------------------------------------------------------------
 * A room
 *
 * NOT BORROWED, and the reason is in the parent's own product: its messages are
 * introductions — one note, one reply, one at a time — and it draws them as a
 * correspondence. This is a conversation, and a conversation is bubbles.
 * ------------------------------------------------------------------------- */
.said { display: flex; flex-direction: column; gap: 2px; padding: .4rem 0 1rem; }
/* Consecutive lines from one person are one thing, not four: 2px inside a run,
   .6rem before somebody else starts. The cheapest thing that makes a room read
   as a conversation rather than as a list. */
.bub + .bub.starts, .evt + .bub, .bub + .evt { margin-top: .6rem; }
.bub {
  max-width: 82%;
  padding: .55rem .8rem;
  border-radius: 1.05rem;
  background: var(--raise);
  border: 1px solid var(--line);
  font-size: .95rem;
  align-self: flex-start;
}
.bub.mine { align-self: flex-end; background: var(--accent); color: var(--accent-ink); border-color: var(--accent); }
/* The corner nearest the speaker tightens on the last line of a run — a tail
   without drawing one. */
.bub.ends { border-bottom-left-radius: .3rem; }
.bub.mine.ends { border-bottom-left-radius: 1.05rem; border-bottom-right-radius: .3rem; }
.bub .from { font-size: .76rem; font-weight: 640; margin-bottom: .1rem; color: var(--accent); }
.bub.mine .from { color: inherit; opacity: .75; }
.bub .alt { font-size: .88rem; opacity: .62; margin-top: .25rem; }
.bub .at { font-size: .68rem; opacity: .55; margin-top: .15rem; text-align: right; }
/* Only the last line of a run carries a time. Four timestamps two seconds apart
   is four pieces of noise for one fact. */
.bub:not(.ends) .at { display: none; }

/* What happened to the room, drawn across it. Nobody said this, so it must not
   look like somebody did. */
.evt {
  align-self: center; text-align: center;
  color: var(--muted); font-size: .78rem;
  padding: .3rem .9rem; margin: .4rem 0; max-width: 90%;
}

.composer {
  position: sticky;
  bottom: calc(var(--bottom) + 4.4rem);
  display: flex;
  gap: .5rem;
  align-items: flex-end;
  padding: .5rem 0;
  background: linear-gradient(to top, var(--paper) 70%, transparent);
}
.composer textarea { min-height: 2.6rem; max-height: 8rem; border-radius: 1.3rem; padding: .6rem .9rem; }
.composer button {
  border-radius: 999px; padding: 0 1.1rem; flex: none; min-height: 2.6rem;
  background: var(--accent); color: var(--accent-ink); border-color: var(--accent);
}

/* ---------------------------------------------------------------------------
 * Sheets
 * ------------------------------------------------------------------------- */
dialog {
  border: none;
  border-radius: 1.1rem 1.1rem 0 0;
  padding: var(--pad);
  padding-bottom: calc(var(--bottom) + var(--pad));
  background: var(--card);
  color: var(--ink);
  width: 100%;
  max-width: var(--col);
  margin: auto auto 0;
  box-shadow: var(--shadow);
}
dialog::backdrop { background: rgba(11, 14, 21, .45); }
dialog h2 { font: 400 1.35rem/1.2 var(--serif); margin: 0 0 .4rem; }
dialog p { color: var(--ink-2); margin: 0 0 .9rem; }
dialog .row { margin-top: .9rem; }
dialog .row button { flex: 1; }

/* A code, read off a screen and typed into a phone. Tabular and tracked out,
   because six characters run together is six characters typed wrong. */
.code {
  font: 600 1.6rem/1.2 ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  letter-spacing: .18em;
  padding: .7rem 0;
  text-align: center;
}
input.code { width: 100%; text-align: center; text-transform: uppercase; }

/* What went wrong, said once and then gone. */
.said-wrong {
  position: fixed;
  left: 50%; transform: translateX(-50%);
  top: calc(var(--top) + .6rem);
  z-index: 20;
  background: var(--ink); color: var(--paper);
  padding: .6rem 1rem; border-radius: 999px;
  font-size: .9rem;
  box-shadow: var(--shadow);
  max-width: calc(100% - 2rem);
}

/* ---------------------------------------------------------------------------
 * A screen you go INTO
 *
 * The room and the invite page are not tabs — you arrive from somewhere and go
 * back. They get a compact sticky bar and the bottom of the screen, and no
 * mark: a product logo beside somebody's name reads as though the app is in the
 * conversation.
 * ------------------------------------------------------------------------- */
body.deep .top {
  position: sticky;
  top: 0;
  z-index: 5;
  align-items: center;
  padding: calc(var(--top) + .6rem) var(--pad) .6rem;
  background: color-mix(in srgb, var(--paper) 90%, transparent);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line);
}
body.deep .top h1 { font-size: 1.25rem; }
body.deep .top h1 svg { display: none; }
/* SMALLER, BUT STILL A PILL. It was squared off to 2.4rem to make room for the
   ⋯, and 中文 is two characters that do not fit across 2.4rem — so the button in
   the header of every conversation read as 中 over 文. The note over
   button.lang says why this is a pill and it is right in here too. */
body.deep .top button.lang { width: auto; height: 2.4rem; padding: 0 .55rem; font-size: .72rem; }
body.deep main {
  padding-bottom: calc(var(--bottom) + .8rem);
  display: flex;
  flex-direction: column;
  /* The composer sits on the bottom edge even with three messages in the room.
     `position: sticky` only pins what you would otherwise scroll past, so in a
     new conversation it stayed where the flow put it — floating halfway up the
     phone under the last bubble. dvh, not vh: vh on a phone is the height
     WITHOUT the browser's chrome, so a vh-tall column is always a little too
     tall and the composer sits just off the bottom of it. */
  min-height: calc(100vh - 3.6rem - var(--top));
  min-height: calc(100dvh - 3.6rem - var(--top));
}
body.deep .said { flex: 1; }
body.deep .composer { bottom: var(--bottom); }

/* ---------------------------------------------------------------------------
 * On a laptop
 *
 * The phone layout in a column with edges. A second layout is a second set of
 * bugs for a surface almost nobody uses — but "the phone layout at phone width"
 * and "the phone layout stretched over two thousand pixels" are not the same
 * thing, and the second is what shipped first.
 * ------------------------------------------------------------------------- */
@media (min-width: 46rem) {
  body { background: color-mix(in srgb, var(--paper) 88%, var(--ink) 4%); }
  body::before {
    content: "";
    position: fixed;
    inset: 0 auto 0 50%;
    transform: translateX(-50%);
    width: var(--col);
    background: var(--paper);
    border-inline: 1px solid var(--line);
    z-index: -1;
  }
  /* THE BAR STOPS BEING A PHONE'S TAB BAR. The parent turns it into a row of
     inline pills on a wide screen — icons beside labels rather than over them,
     and the active one in the soft accent rather than pinned to the bottom of
     a monitor. */
  .tabs {
    left: 50%; right: auto;
    transform: translateX(-50%);
    width: var(--col);
    border-inline: 1px solid var(--line);
  }
  /* A LITTLE AIR BETWEEN THE PILLS. With three they had room; with four the
     soft-accent pill on the current tab ran into the label of the next one. */
  .tabs { gap: .25rem; padding-inline: .35rem; }
  .tabs a { flex-direction: row; gap: .4rem; border-radius: .6rem; font-size: .88rem; }
  .tabs a svg { width: 1.15rem; height: 1.15rem; }
  .tabs a:hover { background: var(--raise); color: var(--ink); }
  .tabs a[aria-current="page"] { background: var(--accent-soft); color: var(--accent); }
  body.deep .top { max-width: var(--col); margin: 0 auto; border-inline: 1px solid var(--line); }
  dialog { margin: auto; border-radius: 1.1rem; max-width: 27rem; padding-bottom: var(--pad); }
}

@media (prefers-reduced-motion: no-preference) {
  .card, .bub { animation: rise .18s ease-out; }
  @keyframes rise { from { opacity: 0; transform: translateY(6px); } }
}
