/* Concorde design system.
   Grey paper, neutral charcoal ink, a single vermilion accent.
   Elongated serif display, wide-tracked technical labels, hairline rules,
   and a faint film-grain. Shared by every page; class names are stable so the
   app markup just gets dressed up. */

:root {
  color-scheme: light dark;
  --paper: #f4f4f5;          /* light grey */
  --surface: #fcfcfd;        /* near-white */
  --surface-2: #ededef;
  --surface-3: #e3e3e6;
  --border: #dcdce0;         /* hairline */
  --border-strong: #c3c3c9;
  --ink: #1f2023;            /* neutral charcoal */
  --ink-2: #494b50;
  --muted: #74767c;
  --red: #c1121f;            /* vermilion (the one accent) */
  --red-soft: #f6e2e0;
  --accent: var(--red);
  --accent-ink: #fcfcfd;
  --ok: #2f7d4f;  --ok-soft: #e6f0ea;
  --bad: #c1121f; --bad-soft: #f6e2e0;
  --warn: #8a6d1a; --warn-soft: #efeadb;
  --radius: 3px; --radius-sm: 2px; --radius-lg: 5px;
  --shadow: 0 1px 0 var(--border), 0 12px 30px rgba(20,20,24,.07);
  --shadow-lg: 0 24px 70px rgba(20,20,24,.20);
  --maxw: 1080px;
  --serif: "Hoefler Text", "Iowan Old Style", "Palatino Linotype", Palatino,
           Garamond, Georgia, "Times New Roman", serif;
  --label: "Helvetica Neue", "Arial Narrow", Arial, sans-serif;
  --mono: ui-monospace, "SF Mono", Menlo, Consolas, monospace;
  --grain: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
}
@media (prefers-color-scheme: dark) {
  :root {
    --paper: #161719; --surface: #1e1f22; --surface-2: #26272b; --surface-3: #2f3035;
    --border: #34363b; --border-strong: #474a50;
    --ink: #f2f3f5; --ink-2: #c4c7cd; --muted: #8b8e95;
    --red: #ff5d52; --red-soft: #351b1a;
    --ok: #67c98a; --ok-soft: #14231a; --bad: #ff6b6b; --bad-soft: #351b1a;
    --warn: #e9c46a; --warn-soft: #2a2510; --accent-ink: #161719;
    --shadow: 0 1px 0 var(--border), 0 16px 40px rgba(0,0,0,.5);
    --shadow-lg: 0 28px 80px rgba(0,0,0,.7);
  }
}

* { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
/*
 * Touch, as against a mouse.
 *
 * Three things a phone needs that a desktop does not, and their absence is most of what "it does
 * not feel like an app" is made of. A tap must not select the label of what it tapped, or the next
 * tap opens a text selection instead of doing something. A double tap must not zoom, which
 * `touch-action: manipulation` says. And the grey flash a mobile browser paints over a tapped
 * element is the browser telling the reader they are on a web page.
 */
button, .tab, .convo-item, .ws-file, a.btn-sm, .brand {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
}
/* What somebody is reading has to stay selectable, on the other hand: that is how a line gets
   quoted out of a conversation. */
.msg, .md, pre, .code, .chat { user-select: text; }
/* A scroller inside the page keeps its scrolling to itself rather than dragging the page once it
   reaches an end, which is what makes a list feel attached to the thumb on it. */
.chat, .convo-list, .ws-list, .tabs { overscroll-behavior: contain; }
body {
  margin: 0; color: var(--ink); font-family: var(--serif);
  font-size: 17px; line-height: 1.62; letter-spacing: .003em;
  background: var(--paper);
  background-attachment: fixed;
  -webkit-font-smoothing: antialiased; text-rendering: optimizeLegibility;
}
/* film grain */
body::after {
  content: ""; position: fixed; inset: 0; z-index: 1; pointer-events: none;
  background-image: var(--grain); background-size: 160px 160px; opacity: .05;
  mix-blend-mode: multiply;
}
@media (prefers-color-scheme: dark) { body::after { mix-blend-mode: screen; opacity: .07; } }
body > * { position: relative; z-index: 2; }

::selection { background: var(--red); color: #fff; }
a { color: var(--red); text-decoration: none; }
a:hover { text-decoration: underline; text-underline-offset: 3px; text-decoration-thickness: 1px; }
.wrap { max-width: var(--maxw); margin: 0 auto; padding: 0 1.6rem; }

/* ---- red hairline strip + top bar ---- */
.topbar { position: sticky; top: 0; z-index: 30; background: color-mix(in srgb, var(--surface) 88%, transparent);
  backdrop-filter: saturate(130%) blur(10px); border-bottom: 1px solid var(--ink); }
.topbar::before { content: ""; display: block; height: 3px; background: var(--red); }
.topbar .wrap { display: flex; align-items: center; gap: .9rem; height: 60px; }
.brand { color: inherit; text-decoration: none; font-family: var(--serif); font-weight: 600; font-size: 1.32rem; letter-spacing: .02em; display: flex; align-items: center; gap: .6rem; }
.brand .dot { width: 12px; height: 12px; border-radius: 50%; background: var(--red);
  box-shadow: 0 0 0 3px var(--surface), 0 0 0 4px var(--border); }
.spacer, .grow { flex: 1; }
.who { color: var(--muted); font-size: .82rem; font-style: italic; }
/* The screen's own name in the app bar, which is a phone's arrangement: on a wide screen the tab
   bar is on the page and already says which screen this is, so saying it twice is noise. */
.screen-title { display: none; }
/* The way out of a conversation. Off on a wide screen, where nothing was ever left behind. */
.bar-back { display: none; }
/* A tab's picture belongs to the bottom bar on a phone, where six destinations have to be told
   apart at a glance. The wide screen's tabs are lettered on purpose, and an icon in front of each
   word is a second alphabet in the same row. */
.tab-icon { display: none; }

/* ---- type: elongated serif display + tracked technical labels ---- */
h1 { font-family: var(--serif); font-weight: 600; font-size: 2.7rem; line-height: 1.06;
  letter-spacing: -.01em; margin: 1.3rem 0 .5rem; }
h2 { font-family: var(--serif); font-weight: 600; font-size: 1.5rem; letter-spacing: -.005em; margin: 1.6rem 0 .7rem; }
h3 { font-family: var(--serif); font-weight: 600; font-size: 1.12rem; margin: 0 0 .25rem; }
p { margin: .5rem 0; }
.muted { color: var(--muted); }
.small { font-size: .84rem; }
.center { text-align: center; }
.hidden { display: none !important; }
.row { display: flex; gap: .7rem; align-items: flex-start; }
.row > * { flex: 1; }
/* the technical-label look: use on eyebrows, meta, table headers */
.eyebrow, .label { font-family: var(--label); text-transform: uppercase; letter-spacing: .28em;
  font-size: .68rem; font-weight: 700; color: var(--red); }

/* ---- cards: hairline plates ---- */
.card { background: var(--surface); border: 1px solid var(--border); border-radius: var(--radius);
  box-shadow: var(--shadow); padding: 1.4rem 1.5rem; }
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(265px, 1fr)); gap: 1.1rem; }
.agent-card { cursor: pointer; position: relative; transition: transform .16s ease, box-shadow .16s ease, border-color .16s ease; }
.agent-card::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--red); opacity: 0; transition: opacity .16s; }
.agent-card:hover { transform: translateY(-2px); border-color: var(--border-strong); box-shadow: 0 18px 44px rgba(20,20,24,.12); }
.agent-card:hover::before { opacity: 1; }
.agent-card .name { font-family: var(--serif); font-weight: 600; font-size: 1.18rem; letter-spacing: -.005em; }
.clickable { cursor: pointer; transition: background .12s; }
.clickable:hover { background: var(--surface-2); }

/* ---- buttons: crisp, lettered ---- */
button, .btn-primary, .btn-ghost, .btn-danger, .btn-sm {
  font-family: var(--label); font-size: .76rem; font-weight: 700; text-transform: uppercase; letter-spacing: .12em;
  border-radius: var(--radius-sm); padding: .6rem 1.15rem; cursor: pointer;
  border: 1px solid var(--ink); background: var(--surface); color: var(--ink);
  transition: transform .08s, background .14s, color .14s, border-color .14s, opacity .12s;
}
button:hover { background: var(--ink); color: var(--surface); }
button:active { transform: translateY(1px); }
button:disabled { opacity: .45; cursor: not-allowed; }
.btn-primary { background: var(--red); border-color: var(--red); color: #fff; }
.btn-primary:hover { background: #a50f1a; border-color: #a50f1a; color: #fff; }
.btn-ghost { background: transparent; border-color: var(--border-strong); color: var(--ink-2); }
.btn-ghost:hover { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.btn-danger { background: transparent; color: var(--red); border-color: var(--red); }
.btn-danger:hover { background: var(--red); color: #fff; }
.btn-sm { padding: .35rem .7rem; font-size: .68rem; letter-spacing: .1em; }
/*
 * A tap target is 44px, which is what a thumb is.
 *
 * Measured before this existed: every control in the console was between 25 and 33 pixels tall on
 * a phone, because they were sized for a pointer that lands where it is aimed. Apple's own number
 * is 44 and Android's is 48dp, and a 25px button is one somebody misses and then presses twice.
 * Done with a minimum height rather than with padding, so nothing about the desktop layout moves,
 * and keyed on a coarse pointer as well as on width: a tablet is not a narrow screen.
 */
@media (pointer: coarse), (max-width: 620px) {
  button, .btn-primary, .btn-ghost, .btn-danger, .btn-sm, a.btn-sm, input, select {
    min-height: 44px;
  }
  .btn-sm { padding: .55rem .9rem; font-size: .72rem; }
  /* A row of buttons that each grew needs room to wrap rather than to squeeze. */
  .btn-row { gap: .5rem; flex-wrap: wrap; }
  /*
   * A button says what it does, in words, in the case those words are written in.
   *
   * The mistake this undoes was invisible on a desktop and disfiguring on a phone. The uppercase,
   * letterspaced label face is right for a *label* and this stylesheet put it on `button`, so
   * everything that happened to be a button wore it. A row in the forum is a button: the subject
   * of a thread came out as WHAT TO DO ABOUT THE SURPL... and the line of what was last said under
   * it as THEY ARE STILL THERE., in a tracked-out label face, at which point a list of things
   * people said reads as a control panel. Buttons that are actually buttons keep the label face;
   * anything whose content is a sentence somebody wrote gets its case and its spacing back.
   */
  .convo-item, .convo-item .name, .convo-item .last, .ws-file, .list-item {
    text-transform: none; letter-spacing: normal; font-family: var(--serif); font-weight: 400;
  }
  /*
   * And a button's own label is sentence case too.
   *
   * MARK IT SETTLED, RUN IT ONCE, NOW, EVERY OTHER DAY AT 9: tracked-out capitals are a house
   * style that works on a wide screen at .76rem in a row of two, and on a phone it turns a screen
   * of controls into a wall of shouting where nothing is easier to read than anything else.
   */
  button, .btn-primary, .btn-ghost, .btn-danger, .btn-sm, a.btn-sm {
    text-transform: none; letter-spacing: .01em; font-size: .84rem;
  }
  /* A heading the app bar has already said. Only ever set when the two texts match. */
  .said-in-the-bar { display: none; }
  /* And the press: a tap with no answer for 300 milliseconds reads as a dead control, which is
     most of what "it does not feel like an app" is made of. */
  button:active, .btn-sm:active, a.btn-primary:active { transform: scale(.97); }
  .convo-item:active, .ws-file:active { background: var(--surface-3); transform: none; }
}
.btn-row { display: flex; flex-wrap: wrap; gap: .55rem; align-items: center; }

/* ---- fields ---- */
label { display: block; font-family: var(--label); text-transform: uppercase; letter-spacing: .14em;
  font-size: .7rem; font-weight: 700; color: var(--muted); margin: .9rem 0 .35rem; }
input, textarea, select { width: 100%; font-family: var(--serif); font-size: 1rem; color: var(--ink);
  background: var(--surface); border: 1px solid var(--border-strong); border-radius: var(--radius-sm); padding: .6rem .8rem;
  transition: border-color .12s, box-shadow .12s; }
input::placeholder, textarea::placeholder { color: var(--muted); font-style: italic; }
input:focus, textarea:focus, select:focus { outline: none; border-color: var(--red); box-shadow: 0 0 0 2px var(--red-soft); }
textarea { min-height: 5rem; resize: vertical; line-height: 1.55; }
select { appearance: none; background-image:
  linear-gradient(45deg, transparent 50%, var(--muted) 50%), linear-gradient(135deg, var(--muted) 50%, transparent 50%);
  background-position: calc(100% - 18px) 55%, calc(100% - 13px) 55%; background-size: 5px 5px; background-repeat: no-repeat; padding-right: 2rem; }

/* ---- tabs: ruled, tracked ---- */
.tabs { display: flex; gap: .1rem; flex-wrap: wrap; border-bottom: 1px solid var(--ink); margin: 1.1rem 0 1.4rem; }
.tab { font-family: var(--label); text-transform: uppercase; letter-spacing: .14em; font-size: .72rem; font-weight: 700;
  border: none; background: none; color: var(--muted); padding: .65rem .9rem; border-bottom: 2px solid transparent; margin-bottom: -1px; }
.tab:hover { color: var(--ink); background: none; }
.tab.on { color: var(--red); border-bottom-color: var(--red); }

/* ---- pills ---- */
.pill { display: inline-flex; align-items: center; gap: .35rem; padding: .12rem .55rem; border-radius: 999px;
  font-family: var(--label); text-transform: uppercase; letter-spacing: .1em; font-size: .64rem; font-weight: 700;
  background: var(--surface); color: var(--muted); border: 1px solid var(--border-strong); }
.pill.active, .pill.approve, .pill.yes, .pill.include { color: var(--ok); border-color: var(--ok); background: var(--ok-soft); }
.pill.reject, .pill.no, .pill.exclude { color: var(--red); border-color: var(--red); background: var(--red-soft); }
.pill.paused, .pill.pending { color: var(--warn); border-color: var(--warn); background: var(--warn-soft); }
.pill.retired { color: var(--muted); }

/* ---- lists / stats ---- */
.list-item { display: flex; align-items: center; gap: .7rem; padding: .7rem 0; border-bottom: 1px solid var(--border); }
.list-item:last-child { border-bottom: none; }
.ok { color: var(--ok); font-weight: 600; } .bad { color: var(--bad); font-weight: 600; }
.stat { display: inline-block; margin-right: 1.8rem; }
.stat b { display: block; font-family: var(--serif); font-size: 1.55rem; font-weight: 600; letter-spacing: -.01em; }
.stat .muted, .stat span:not(:first-child) { font-family: var(--label); text-transform: uppercase; letter-spacing: .12em; font-size: .62rem; }
.empty { text-align: center; color: var(--muted); padding: 2.6rem 1rem; font-style: italic; }

/* ---- banner ---- */
.banner { display: flex; align-items: center; gap: .85rem; background: var(--surface-2);
  border: 1px solid var(--border); border-left: 3px solid var(--red); border-radius: var(--radius);
  padding: .9rem 1.15rem; margin: 1rem 0; color: var(--ink-2); font-size: .95rem; }

/* ---- code, toast, modal ---- */
.code { font-family: var(--mono); font-size: .8rem; background: var(--surface-2); border: 1px solid var(--border); border-radius: 3px; padding: .1rem .4rem; }

/* Three numbered steps in a card: how to make a Telegram bot, which is the part nobody knows. */
.how-to { margin: .5rem 0 1.1rem; padding-left: 1.2rem; color: var(--muted); font-size: .84rem; line-height: 1.55; }
.how-to li { margin: .2rem 0; }
pre { font-family: var(--mono); font-size: .8rem; line-height: 1.5; background: var(--surface-2); border: 1px solid var(--border); border-radius: var(--radius); padding: .9rem 1rem; overflow: auto; white-space: pre-wrap; word-break: break-word; }
details > summary { cursor: pointer; padding: .35rem 0; font-weight: 600; }
.toast { position: fixed; bottom: 1.5rem; left: 50%; transform: translateX(-50%) translateY(12px); background: var(--ink); color: var(--surface);
  font-family: var(--label); text-transform: uppercase; letter-spacing: .1em; font-size: .74rem; font-weight: 700;
  padding: .75rem 1.15rem; border-radius: 999px; box-shadow: var(--shadow-lg); opacity: 0; pointer-events: none; transition: opacity .22s, transform .22s; z-index: 60; }
.toast.show { opacity: 1; transform: translateX(-50%) translateY(0); }
.modal-overlay { position: fixed; inset: 0; background: color-mix(in srgb, var(--ink) 55%, transparent); backdrop-filter: blur(3px); display: flex; align-items: flex-start; justify-content: center; padding: 5vh 1rem; z-index: 50; overflow: auto; }
.modal { background: var(--surface); border: 1px solid var(--ink); border-radius: var(--radius); box-shadow: var(--shadow-lg); max-width: 680px; width: 100%; padding: 1.7rem 1.8rem; max-height: 92vh; overflow: auto; }
.proposal-head { display: flex; align-items: flex-start; gap: .8rem; padding-bottom: .8rem; margin-bottom: .5rem; border-bottom: 1px solid var(--border); }
.proposal-head h2 { margin: .1rem 0 0; }
.actions { display: flex; flex-direction: column; margin: .3rem 0 .5rem; }
.action-item { display: flex; gap: .85rem; align-items: flex-start; padding: .7rem 0; border-bottom: 1px solid var(--border); }
.action-item:last-child { border-bottom: none; }
.ai-num { flex: none; width: 1.7rem; height: 1.7rem; border-radius: 50%; display: flex; align-items: center; justify-content: center; border: 1px solid var(--red); color: var(--red); font-family: var(--label); font-weight: 700; font-size: .72rem; }
.ai-title { font-weight: 600; }
.kv { display: flex; gap: .6rem; justify-content: space-between; padding: .2rem 0; }

/* ---- wizard steps ---- */
.steps { display: flex; gap: .6rem; margin: 1rem 0 1.4rem; }
.step { height: 2px; flex: 1; background: var(--border-strong); }
.step.on { background: var(--red); }

/* ---- chat ---- */
/*
 * `dvh` rather than `vh`, because a keyboard changes the viewport and `vh` does not.
 *
 * On a phone `100vh` is the window as if no keyboard were open, so a log measured in `vh` keeps its
 * full height when one appears and pushes the composer somewhere no thumb reaches. The dynamic unit
 * is the window that is actually visible, which is the one the reader has.
 */
.chat { display: flex; flex-direction: column; gap: .55rem; max-height: 52vh; overflow: auto; padding: .25rem; }
@supports (height: 100dvh) {
  .chat { max-height: 52dvh; }
  .modal { max-height: 92dvh; }
  .modal-overlay { padding: 5dvh 1rem; }
}
.chat-input { display: flex; gap: .5rem; margin-top: .8rem; }
.chat-input input { flex: 1; }
.msg { padding: .6rem .9rem; border-radius: var(--radius); max-width: 82%; font-size: .96rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
.msg.me { align-self: flex-end; background: var(--ink); color: var(--surface); }
.msg.bot { align-self: flex-start; background: var(--surface); border: 1px solid var(--border); }

/* ---- plain paper backgrounds (no imagery) ---- */
.auth-bg { background: var(--paper); }
.cloud-band { display: none; }

/* a long hairline rule with a red tick, used as a section divider */
.rule { height: 1px; background: var(--border); margin: 1.4rem 0; position: relative; }
.rule::before { content: ""; position: absolute; left: 0; top: -1px; width: 42px; height: 3px; background: var(--red); }

/* ---- role signposts (home + console headers) ---- */
.kicker { font-family: var(--label); text-transform: uppercase; letter-spacing: .2em;
  font-size: .6rem; font-weight: 700; color: var(--red); display: block; margin-bottom: .5rem; }
.role-card .kicker { color: var(--red); }
.role-card.op .kicker { color: var(--red); }
.consolebar { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  background: var(--surface-2); border: 1px solid var(--border); border-left: 3px solid var(--red);
  border-radius: var(--radius); padding: .55rem .8rem; margin: 1.1rem 0; font-size: .9rem; }
.consolebar.op { border-left-color: var(--red); }
.consolebar .tag { font-family: var(--label); text-transform: uppercase; letter-spacing: .14em;
  font-size: .62rem; font-weight: 700; color: var(--ink); }
/* topbar nav links that wrap gracefully */
.navlinks { display: flex; align-items: center; gap: .55rem; flex-wrap: wrap; justify-content: flex-end; }
.navlinks a, .navlinks .who { white-space: nowrap; }

/* ---- responsive ---- */
@media (max-width: 900px) {
  .home-hero .wrap { padding: 3.6rem 1.3rem 2.6rem !important; }
  .home-hero h1 { font-size: 2.5rem !important; }
}
@media (max-width: 620px) {
  body { font-size: 16px; }
  h1 { font-size: 1.95rem; } h2 { font-size: 1.3rem; }
  /*
   * `clip`, so nothing inside can make the page scroll sideways.
   *
   * Three things on a phone deliberately reach past this padding to the edges of the screen: the
   * tab strip, the forum, and the composer. A full-bleed child is a negative margin, and a negative
   * margin is a child wider than its parent, which is one mistake away from a page that scrolls
   * sideways. It was not even a mistake in the end: the sticky tab strip is a scroll container, and
   * a scroll container's contents are supposed to stay inside it, but with a negative margin and a
   * scroll position of its own Chromium put 57 pixels of the strip into the width of the document
   * about half the time this was measured at 320px. `clip` settles it for every such child at once.
   *
   * `clip` rather than `hidden`: hidden makes this a scroll container, and a sticky child sticks to
   * its nearest scroll container, so hidden would silently unstick the tab strip. `clip` only
   * clips, and it clips at the padding box, which is exactly where the screen ends.
   */
  .wrap { padding: 0 1.05rem; overflow-x: clip; }
  .card { padding: 1.1rem 1.15rem; }
  .row { flex-direction: column; } .row > * { width: 100%; }
  /*
   * The app bar: one row, and the name of the screen you are on.
   *
   * What it was before is worth writing down, because it is the difference between an app and a
   * web page with a header. The brand, the signed-in name and a bordered SIGN OUT box wrapped onto
   * two rows and took 245 pixels off the top of an 844 pixel screen, on every screen, and none of
   * those three is what somebody opening a conversation came for. So: the mark stays as the way
   * home and shrinks to its dot, the screen's own name takes the space that frees, and who you are
   * signed in as moves to the You screen, which is the screen about exactly that.
   */
  .topbar .wrap { height: auto; min-height: 52px; flex-wrap: nowrap; gap: .55rem;
    padding-top: calc(.3rem + env(safe-area-inset-top)); padding-bottom: .3rem; }
  .brand { min-height: 44px; min-width: 44px; }
  .brand-word { display: none; }
  .brand .dot { width: 13px; height: 13px; }
  .screen-title { display: block; font-family: var(--serif); font-size: 1.12rem; font-weight: 600;
    letter-spacing: -.01em; color: var(--ink); white-space: nowrap; overflow: hidden;
    text-overflow: ellipsis; }
  .who { display: none; }
  #signOut { display: none; }
  .spacer { flex: 1 1 auto; }
  .navlinks { justify-content: flex-end; }
  /*
   * The tabs are a bar along the bottom, which is where a thumb is.
   *
   * They were a sideways-scrolling strip under the top bar. Two things were wrong with that and
   * both are fatal: at 390px the sixth tab was off the side of the screen with nothing to say so,
   * and the whole row sat at the top, which is the part of a phone a hand holding it cannot reach.
   * A fixed bottom bar is what every application on the device does, so it needs no explaining,
   * and six destinations fit across 390 pixels once each is an icon with a word under it.
   */
  .tabs { position: fixed; left: 0; right: 0; bottom: 0; z-index: 40; margin: 0;
    display: flex; flex-wrap: nowrap; align-items: stretch; justify-content: space-around;
    gap: 0; padding: 0 .2rem calc(env(safe-area-inset-bottom)); border-top: 1px solid var(--border);
    border-bottom: none; background: var(--surface); overflow: visible; }
  .tab { flex: 1 1 0; display: flex; flex-direction: column; align-items: center;
    justify-content: center; gap: .12rem; min-width: 0; min-height: 52px;
    padding: .4rem .1rem .3rem; border: none; background: none;
    font-family: var(--label); font-size: .58rem; font-weight: 700; letter-spacing: .02em;
    text-transform: none; color: var(--muted); }
  .tab-icon { display: block; height: 23px; }
  .tab-label { display: block; max-width: 100%; overflow: hidden; text-overflow: ellipsis;
    white-space: nowrap; }
  /* The one you are on is the red one, not a red line under a grey one: on a bar this small a
     1px underline is invisible at arm's length. */
  .tab.active, .tab.on { color: var(--red); border: none; }
  .tab:active { background: var(--surface-2); }
  /* Room for the bar, so the last line of every screen is readable rather than under it. */
  #pane { padding-bottom: 4.6rem; }
  /*
   * Inside a conversation the bar steps aside.
   *
   * Native applications do this and the reason is the composer: a text box, a send button, a tab
   * bar and a keyboard stacked at the bottom of a phone is four rows of chrome under what you are
   * writing. The way out of the conversation is the back button at the top of it.
   */
  body[data-convo="one"] .tabs { display: none; }
  /*
   * A field's own label and a state's pill, in the case their words are written in.
   *
   * Here rather than up with the other 44px rules, and the reason is worth knowing: the base rules
   * for `label` and `.pill` are declared further down this file than that block, and a media query
   * does not raise specificity. Same specificity, later wins, so an override written above them
   * silently did nothing and CURRENT PASSWORD stayed CURRENT PASSWORD.
   */
  label { text-transform: none; letter-spacing: .01em; font-size: .84rem; font-weight: 600;
    color: var(--ink); }
  .pill { font-size: .58rem; letter-spacing: .06em; }
  /* In a list row, sentence case, the way the hub's are: these two pages are the halves of one
     product a person moves between, and "THE ROOM" beside a name on one screen and "Running"
     beside one on the next is the kind of difference that reads as two applications. */
  .convo-item .pill { text-transform: none; letter-spacing: .02em; font-size: .68rem; }
  .home-hero h1 { font-size: 2.05rem !important; }
  .home-hero p { font-size: 1.02rem !important; }
  .btn-row { gap: .5rem; } .btn-row > * { flex: 1 1 auto; text-align: center; }
  .modal { padding: 1.2rem 1.15rem; }
  .msg { max-width: 92%; }
  .stat { margin-right: 1.1rem; }
  .consolebar { flex-direction: column; align-items: flex-start; gap: .3rem; }
  .consolebar .grow { flex-basis: auto; }
}

/* -- workspace browser (file tree + markdown preview) --------------------- */
.ws { display: grid; grid-template-columns: 260px 1fr; gap: 1rem; align-items: start;
  border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.ws-tree { border-right: 1px solid var(--border); background: var(--surface-2);
  padding: .5rem; max-height: 520px; overflow: auto; }
.ws-folder > summary { cursor: pointer; font-family: var(--label); letter-spacing: .04em;
  font-size: .82rem; padding: .25rem .3rem; list-style: none; }
.ws-folder > summary::-webkit-details-marker { display: none; }
.ws-folder { margin-left: .2rem; border-left: 1px solid var(--border); padding-left: .5rem; }
.ws-file { display: block; width: 100%; text-align: left; background: none; border: none;
  font-family: var(--mono); font-size: .8rem; color: var(--ink-2); cursor: pointer;
  padding: .28rem .35rem; border-radius: var(--radius-sm); }
.ws-file:hover { background: var(--surface-3); }
.ws-file.on { background: var(--surface-3); color: var(--ink); font-weight: 600; }
.ws-preview { padding: .9rem 1.1rem; max-height: 520px; overflow: auto; min-width: 0; }
.ws-empty { padding: 2rem 0; text-align: center; }
.ws-path { display: flex; align-items: center; gap: .6rem; font-family: var(--mono);
  font-size: .72rem; color: var(--muted); border-bottom: 1px solid var(--border);
  padding-bottom: .45rem; margin-bottom: .7rem; word-break: break-all; }
.ws-raw { margin-left: auto; font-family: var(--label); text-transform: uppercase;
  letter-spacing: .1em; font-size: .58rem; border: 1px solid var(--border);
  background: var(--surface); border-radius: var(--radius-sm); padding: .12rem .4rem; cursor: pointer; }
.ws-raw.on { background: var(--ink); color: var(--surface); border-color: var(--ink); }
.ws-rawbody pre { white-space: pre-wrap; font-family: var(--mono); font-size: .78rem; margin: 0; }
/*
 * On a phone the workspace is a list of documents, and the one you tap.
 *
 * Two boxes stacked was what this was: a 220px scroller with the files in it, and under it a
 * 260px box saying "Pick a document." The instruction is not needed when the only thing on the
 * screen is the list, and the box holding it was a third of the screen reserved for nothing.
 */
@media (max-width: 620px) {
  .ws { grid-template-columns: 1fr; border: none; border-radius: 0; margin: 0 -1.15rem; }
  .ws-tree { border-right: none; border-bottom: none; max-height: none; padding: 0;
    background: var(--surface); }
  .ws-file { min-height: 52px; padding: .7rem 1.15rem; border-bottom: 1px solid var(--border);
    font-size: .95rem; }
  .ws-folder { margin: 0; border-left: none; padding-left: 0; }
  .ws-folder > summary { padding: .7rem 1.15rem; min-height: 44px; font-size: .8rem;
    color: var(--muted); background: var(--surface-2); border-bottom: 1px solid var(--border); }
  /* Nothing picked yet: the list is the screen, so the invitation to pick is a line rather than a
     box, and it goes as soon as something is open. */
  .ws-preview { padding: .8rem 1.15rem; max-height: none; }
  .ws-empty { padding: 1.1rem 0 0; text-align: left; font-size: .88rem; }
  .ws-path { padding: .6rem 1.15rem; }
  .ws-raw { text-transform: none; letter-spacing: .01em; }
}

/* rendered markdown */
.md { font-size: .93rem; line-height: 1.6; color: var(--ink-2); word-wrap: break-word; }
.md h1, .md h2, .md h3, .md h4 { font-family: var(--serif); color: var(--ink);
  line-height: 1.2; margin: 1.1rem 0 .5rem; }
.md h1 { font-size: 1.5rem; } .md h2 { font-size: 1.22rem; } .md h3 { font-size: 1.05rem; }
.md h1:first-child, .md h2:first-child { margin-top: 0; }
.md p { margin: .55rem 0; } .md ul, .md ol { margin: .5rem 0 .5rem 1.3rem; }
.md li { margin: .2rem 0; } .md a { color: var(--red); }
.md code { font-family: var(--mono); font-size: .85em; background: var(--surface-2);
  border: 1px solid var(--border); border-radius: var(--radius-sm); padding: .05rem .3rem; }
.md pre.md-code { background: var(--surface-2); border: 1px solid var(--border);
  border-radius: var(--radius); padding: .7rem .85rem; overflow: auto; margin: .7rem 0; }
.md pre.md-code code { background: none; border: none; padding: 0; font-size: .8rem; }
.md blockquote { border-left: 3px solid var(--border-strong); margin: .7rem 0;
  padding: .1rem 0 .1rem .9rem; color: var(--muted); font-style: italic; }
.md hr { border: none; border-top: 1px solid var(--border); margin: 1.1rem 0; }

/* ---- conversations: the group's room and each member's own -----------------
   Two panes on a wide screen, one on a phone, on the same grid the workspace
   browser uses. The list is the sidebar there and a sideways scroller here,
   because a thread list stacked above a conversation on a 390px screen pushes
   the messages off the bottom of the page. */
/* `minmax` and not a fixed 260: a fixed column with text that will not wrap is what made this list
   scroll sideways inside the card, cutting off every preview at the same place. */
.convo { display: grid; grid-template-columns: minmax(13rem, 17rem) minmax(0, 1fr); gap: 1rem;
  /* `stretch`, so the list's column and its dividing line run the height of the card. With
     `start` the list stopped under its last thread and the card had a white notch below it. */
  align-items: stretch;
  border: 1px solid var(--border); border-radius: var(--radius); overflow: hidden; }
.convo-list { border-right: 1px solid var(--border); background: var(--surface-2);
  padding: .4rem; max-height: 60vh; overflow: auto; }
.convo-item { display: block; width: 100%; text-align: left; background: none; border: none;
  border-radius: var(--radius-sm); padding: .5rem .55rem; cursor: pointer; color: var(--ink); }
.convo-item:hover { background: var(--surface-3); color: var(--ink); }
.convo-item.on { background: var(--surface); box-shadow: inset 3px 0 0 var(--red); }
/*
 * A row's own text, in the case it was written in.
 *
 * A row is a `<button>`, and this stylesheet's `button` rule sets the uppercase, letterspaced
 * label face on every one of them. That is right for a control whose label is a word somebody
 * chose ("Send", "Retire") and wrong for a row whose content is a sentence somebody typed: the
 * subject of a thread came out as WHAT TO DO ABOUT THE SURPL... and the line under it as THEY ARE
 * STILL THERE., so a list of what people are discussing read as a control panel. Not a phone
 * rule: it was wrong on a desktop too, only less obviously.
 */
.convo-item { text-transform: none; letter-spacing: normal; font-family: var(--serif); }
.convo-item .name { font-family: var(--serif); font-weight: 600; font-size: 1rem;
  text-transform: none; letter-spacing: normal;
  display: flex; align-items: center; gap: .4rem; min-width: 0; }
/* The name itself truncates, not the row: the row also holds the "group" and "you" pills, and a
   pill pushed out of sight by a long name is worse than a shortened name. */
.convo-item .name .who-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
/* `display: block` is load-bearing: `text-overflow` does nothing on an inline element, so with the
   span left inline the preview was clipped by the column edge with no ellipsis and no way to tell
   the text had been cut. */
.convo-item .last { display: block; color: var(--muted); font-size: .78rem; white-space: nowrap;
  text-transform: none; letter-spacing: normal; overflow: hidden; text-overflow: ellipsis; }
.convo-pane { padding: .8rem .9rem; min-width: 0; }
/* The open thread's subject, its state, and the Decision it came to. */
.thread-head { display: flex; align-items: center; gap: .6rem; flex-wrap: wrap;
  padding-bottom: .6rem; margin-bottom: .6rem; border-bottom: 1px solid var(--border); }
/* The subject itself, which on a phone is the only thing naming what is on the screen: the list it
   was chosen from is gone, so this reads as a title rather than as another line of the page. It
   wraps, because a subject is a sentence somebody typed and truncating it hides the difference
   between two threads about one thing. */
.thread-head .who-email { font-weight: 700; font-size: 1.02rem; line-height: 1.35;
  overflow-wrap: anywhere; }
.thread-head .meta { color: var(--muted); font-size: .78rem; }
/*
 * Who wrote what, in a room with more than two voices in it.
 *
 * Reported as: the labels for who wrote what in the group chat are not in the right place, make it
 * more obvious who wrote what. Both halves of that were true. Every inbound message was styled the
 * same, filled and on the right, so another member's message looked exactly like your own and the
 * only difference between the two was a name in small type above it. And that name was laid out
 * against the whole column, so on the right-hand bubbles it sat on the left, over the wrong thing.
 *
 * Three kinds now, told apart by position and by colour before a word is read: yours on the right,
 * another member's on the left, and the agent's on the left in a ground of its own with a mark down
 * its edge, because it is the one voice in the room that is not a person. The name goes on its own
 * bubble's side, which is what makes it a label rather than a floating line.
 */
.msg-said { font-family: var(--label); text-transform: uppercase; letter-spacing: .12em;
  font-size: .58rem; font-weight: 700; color: var(--muted); margin: .5rem 0 -.3rem; max-width: 82%; }
.msg-said.mine { align-self: flex-end; text-align: right; }
.msg-said.theirs, .msg-said.agent { align-self: flex-start; }
.msg.mine { align-self: flex-end; background: var(--ink); color: var(--surface); }
.msg.theirs { align-self: flex-start; background: var(--surface-2); border: 1px solid var(--border-strong); }
.msg.agent { align-self: flex-start; background: var(--surface); border: 1px solid var(--border);
  border-left: 3px solid var(--red); }
/* The old pair, kept only so an agent still serving a cached page is not left unstyled: `inbound`
   is somebody's own message and `outbound` is the agent's, which is what they used to mean when
   every conversation had exactly one member in it. */
.msg.inbound:not(.mine):not(.theirs) { align-self: flex-end; background: var(--ink); color: var(--surface); }
.msg.outbound:not(.agent) { align-self: flex-start; background: var(--surface); border: 1px solid var(--border); }
.msg-when { font-size: .62rem; opacity: .6; display: block; margin-top: .2rem; }
/* On a wide screen there is nothing to go back to: both halves are on the page at once, so this
   is off by default and the phone's own rules below turn it on. */
.convo-back { display: none; align-items: center; gap: .5rem; margin-bottom: .5rem; }

/*
 * A phone shows one thing at a time: the list of subjects, or one subject.
 *
 * Two columns side by side is a desktop idea, and the first attempt at making it fit a phone was to
 * turn the left column into a strip of chips that scrolled sideways. Measured on a 390px screen
 * that strip held one chip 859 pixels wide, because a subject's title is a sentence: the list was
 * unreadable and unnavigable at once. A forum on a phone is a list you tap into and come back out
 * of, which is what every app does, and it is what `.convo[data-open]` switches to here.
 *
 * The class is set by the page rather than by a hover state or a scroll position, because which of
 * the two views a reader is in is a fact about what they last tapped and has to survive a repaint.
 */
@media (max-width: 620px) {
  /*
   * The forum takes the screen: no card, no border, no rounded corner around it.
   *
   * A card is right for a block of content on a page among other blocks. A list of conversations
   * is not that, it is the screen, and wrapping it in one cost 2rem of a 390px width and drew a
   * box inside a box inside a box: the page's padding, the card's border, then the forum's own.
   */
  body[data-screen="talk"] #pane > .card { border: none; border-radius: 0; background: none;
    padding: 0; margin: -.4rem -1.05rem 0; box-shadow: none; }
  .convo { grid-template-columns: 1fr; border: none; border-radius: 0; margin: 0; }
  /* The list is the screen, so it is white all the way down: rows that stopped two thirds of the
     way and left the page's grey under them read as a list that failed to finish loading. */
  .convo-list { border-right: none; max-height: none; padding: 0; background: var(--surface);
    min-height: calc(100vh - 52px); }
  @supports (height: 100dvh) {
    .convo-list { min-height: calc(100dvh - 52px); }
  }
  /*
   * A row of the list: the subject, what was last said under it, and a chevron.
   *
   * Sized like a list row in an application rather than like an item in a sidebar. The chevron is
   * not decoration: it is the only thing on the row that says tapping it goes somewhere, and
   * without it a list of grey-on-white lines is a list of text.
   */
  .convo-item { width: 100%; white-space: normal; min-height: 68px;
    padding: .8rem 2.1rem .8rem 1.05rem; border-bottom: 1px solid var(--border);
    border-radius: 0; background: var(--surface); position: relative; }
  .convo-item::after { content: ""; position: absolute; right: 1.05rem; top: 50%;
    width: 8px; height: 8px; margin-top: -5px; border-right: 1.5px solid var(--border-strong);
    border-bottom: 1.5px solid var(--border-strong); transform: rotate(-45deg); opacity: .75; }
  .convo-item .name { display: flex; gap: .4rem; align-items: center; flex-wrap: nowrap;
    font-size: 1.02rem; font-weight: 600; line-height: 1.3; }
  .convo-item .last { display: block; margin-top: .12rem; font-size: .87rem; color: var(--muted); }
  /* Nothing marks the open one on a phone: it is the screen you are on, not a row in a sidebar. */
  .convo-item.on { box-shadow: none; background: var(--surface); }
  /* Whichever half is not open is out of the way entirely, rather than shrunk. */
  .convo[data-open="list"] .convo-pane { display: none; }
  .convo[data-open="one"] .convo-list { display: none; }
  /*
   * A conversation is its own screen: a bar naming it, the log, and the composer under it.
   *
   * `grid` with the log as the only row that can grow, inside a container the height of the window
   * minus the app bar. That is what makes the log the tall thing on the screen without arithmetic
   * about how tall everything else came out, which is what the old `calc(100dvh - 330px)` was: a
   * guess that went wrong the moment a subject wrapped onto two lines.
   */
  .convo[data-open="one"] { display: block; margin: 0; }
  /*
   * A column the height of what is left of the window, with the log as the part that gives.
   *
   * Flex and not a grid with named rows, which is what this was first: the pane holds six children
   * and three of them come and go (the way back, the busy line, the note), and a child set to
   * `display: none` leaves the grid entirely rather than leaving an empty row. So the rows shifted
   * under the ones that vanished, `1fr` landed on the busy line, and the log stayed the height of
   * its text with 400 pixels of nothing under the composer. A flex column has no positions to get
   * wrong: whatever is there is laid out in order, and the log is the one thing that grows.
   */
  .convo[data-open="one"] .convo-pane { display: flex; flex-direction: column; min-height: 0;
    padding: 0; height: calc(100vh - 52px - env(safe-area-inset-top)); }
  @supports (height: 100dvh) {
    .convo[data-open="one"] .convo-pane {
      height: calc(100dvh - 52px - env(safe-area-inset-top));
    }
  }
  .convo[data-open="one"] #chat { flex: 1 1 auto; }
  .convo[data-open="one"] #composer,
  .convo[data-open="one"] .thread-head,
  .convo[data-open="one"] #busy,
  .convo[data-open="one"] #convoNote { flex: 0 0 auto; }
  /* No room for a tab bar that is not there: inside a conversation the screen ends at the
     composer, and 4.6rem of padding under it is a page that scrolls for nothing. */
  body[data-convo="one"] #pane { padding-bottom: 0; }
  /*
   * The log is the row that grows, and the only one that scrolls.
   *
   * `justify-content: flex-end` is what makes a short conversation look like one. Filled from the
   * top, three messages sat in the top fifth of the screen with 500 pixels of empty grey under
   * them, which reads as a page that failed to load rather than as a conversation that has just
   * started. Every messaging application on the device anchors the newest message to the bottom,
   * and a flex column that overflows still scrolls correctly when it is filled from that end.
   */
  .convo[data-open="one"] .chat { max-height: none; min-height: 0; height: auto; overflow-y: auto;
    padding: .8rem 1.05rem; justify-content: flex-end; }
  .chat { max-height: 58vh; }
  @supports (height: 100dvh) { .chat { max-height: 58dvh; } }
  /*
   * What the forum is stays on the page; what it is *for* does not, once you are in it.
   *
   * A paragraph explaining threads is worth its room the first time and is a third of a small
   * screen every time after, and the app bar now names the screen. It stays on a wide screen,
   * where there is room for both.
   */
  #talkHead { display: none; }
  /*
   * The way back and the subject are both in the app bar, so neither has a row here.
   *
   * `.convo-back` was that row and the app bar's own back button replaced it. It stays in the
   * markup because a wide screen never had it and a narrow one no longer needs it, and deleting it
   * would leave the pane with no way out if the bar were ever not there.
   */
  .convo-back { display: none; }
  /* Drawn rather than typed: the angle-quote glyph this used to be renders as a heavy serif wedge
     in this face, which reads as a punctuation mark somebody left in the bar. */
  .bar-back { display: flex; align-items: center; justify-content: center; border: none;
    background: none; min-width: 42px; min-height: 44px; padding: 0; font-size: 0;
    color: var(--red); }
  .bar-back span { display: block; width: 11px; height: 11px; margin-left: 4px;
    border-left: 2px solid currentColor; border-bottom: 2px solid currentColor;
    transform: rotate(45deg); }
  .bar-back:active { transform: none; opacity: .5; }
  /* With the back button in the bar the mark shifts along, so it keeps out of the way of it. */
  #barBack:not(.hidden) ~ .brand { display: none; }
  /*
   * What is left of the subject's own row: who opened it, whether it is settled, and the control
   * that settles it. The title itself is in the bar above, so this is a strip rather than a header.
   */
  .thread-head { padding: .4rem 1.05rem .45rem; margin: 0; gap: .5rem;
    background: var(--surface); border-bottom: 1px solid var(--border); }
  .thread-head .who-email { display: none; }
  .thread-head .grow { min-width: 0; }
  .thread-head .meta { font-size: .76rem; }
  /* Small, because settling a subject is a thing you do once and reading it is a thing you do
     every time. */
  .thread-head .btn-sm { min-height: 34px; padding: .25rem .6rem; font-size: .72rem; }
  /*
   * The composer: one row at the bottom of the screen, and nothing under it.
   *
   * It is a grid row rather than a fixed bar now, because the screen is a grid: fixed positioning
   * needed the pane to reserve exactly the right padding underneath, and every time the composer
   * grew by a line that reservation was wrong.
   */
  .convo[data-open="one"] #composer { padding: .5rem .8rem calc(.5rem + env(safe-area-inset-bottom));
    background: var(--surface); border-top: 1px solid var(--border); }
  .composer-row { display: flex; align-items: flex-end; gap: .5rem; }
  .composer-row .chat-input { flex: 1 1 auto; min-height: 44px; max-height: 40vh;
    padding: .6rem .8rem; border-radius: 22px; resize: none; font-family: var(--serif);
    font-size: 16px; line-height: 1.4; }
  .composer-row #send { flex: 0 0 auto; min-width: 64px; min-height: 44px; border-radius: 22px;
    padding: 0 1rem; }
  /*
   * A bubble is a bubble: filled, round, and without a border around it.
   *
   * On a wide screen a hairline border is what separates one member's bubble from the page. On a
   * phone the log has its own ground and the borders read as boxes, which is the difference
   * between a chat and a table of rows. The agent keeps the mark down its edge, because it is the
   * one voice in the room that is not a person and that is worth a pixel.
   */
  .msg { max-width: 84%; border-radius: 18px; padding: .55rem .85rem; font-size: .97rem; }
  .msg.theirs { border: none; background: var(--surface-2); }
  .msg.mine { border-bottom-right-radius: 6px; }
  .msg.theirs { border-bottom-left-radius: 6px; }
  .msg.agent { border: none; background: var(--surface); border-left: 3px solid var(--red);
    border-radius: 6px 18px 18px 6px; }
  /* Whose it is, in the case they wrote their name in. */
  .msg-said { text-transform: none; letter-spacing: .01em; font-size: .68rem; font-weight: 600;
    margin: .45rem .2rem -.2rem; }
  /* And what the busy line and the notes say sits between the log and the composer rather than
     reserving space in the middle of the screen when there is nothing to say. */
  .convo[data-open="one"] #busy:empty, .convo[data-open="one"] #convoNote:empty { display: none; }
  .convo[data-open="one"] #busy, .convo[data-open="one"] #convoNote { padding: .35rem 1.05rem; }
  /*
   * A new subject is a button that floats over the list, which is where a phone puts the one thing
   * a screen is for. In the flow it was a full-width bordered box at the top of the list, so the
   * first thing on the screen was a control rather than the conversations.
   */
  body[data-convo="list"] #startThread { position: fixed; right: 1rem; z-index: 45;
    bottom: calc(4.6rem + env(safe-area-inset-bottom)); border-radius: 26px; min-height: 52px;
    padding: 0 1.25rem; background: var(--red); color: #fff; border-color: var(--red);
    box-shadow: 0 6px 18px rgba(0, 0, 0, .22); text-transform: none; letter-spacing: .01em;
    font-size: .82rem; }
  body[data-convo="one"] #startThread { display: none; }
  /* The form it opens is the screen while it is open, not a box at the top of the list. */
  #newThread form { padding: 0 1.05rem; }
}

/* The brand is a link home. Underlined on hover rather than always, because a page title that is
   permanently underlined reads as a mistake, and a link nobody can tell is one is not a way out. */
.brand:hover { text-decoration: underline; text-underline-offset: .18em; }

/* Wide screens. The column is sized for reading, not for the window: 1080px is right for prose on
   a laptop and looks like a phone page marooned in grey on a 3200px monitor. So the column grows,
   once, and stops well before a line of text gets too long to track. Both apps carry the same rule
   because they are one product and this is exactly where the two drifted apart before. */
@media (min-width: 1500px) {
  :root { --maxw: 1320px; }
}
