/* The viewer.
 *
 * Direction: Telegram's shell, and a clock you read by colour.
 *
 * The first version of this page was a print -- paper ground, ink text. The
 * second was a darkroom: near-black, one amber safelight, everything square
 * to the pixel. Both of them were designs for a desk. This one is not.
 *
 * It is reached over the tailnet, and a device on the tailnet is the phone in
 * a pocket, usually inside Telegram, having just copied a link to a post. So
 * the page now speaks the language of the app it is opened from: rounded
 * sections on a blue-black ground, separators that begin after the avatar,
 * iOS type sizes, a tab bar at the bottom where the thumb is. Nothing here is
 * a breakpoint the wide layout survives -- the phone is the base, and the desk
 * is the variant, at the end of the file.
 *
 * WHAT COLOUR MEANS, AND IT IS THE ONE RULE:
 * blue owns anything you press, and nothing else uses it. Time is read on a
 * ramp -- green over an hour, amber under an hour, red under ten minutes --
 * and the pill ALWAYS carries the number as well. Colour is never the only
 * thing saying it, because a colour ramp with no number is unreadable to the
 * people it matters most to. Violet is a copy taken, red doubles as
 * destructive, and that is the whole vocabulary.
 *
 * The amber is no longer the identity; it is one step of the ramp. That is a
 * real loss and it is deliberate: an accent reserved for one meaning cannot
 * also be the accent a phone UI presses.
 *
 * THE TYPOGRAPHIC RULE IS KEPT, NARROWED AGAIN: fixed width is the MACHINE
 * -- ids, clocks, durations, counts, stamps -- and proportional is the
 * PERSON. What changed is that a mono face no longer carries structure: a
 * heading, a nav label and a button are words.
 *
 * Light is a token swap and nothing else. Every rule below reads a variable;
 * the light block at the top redeclares the variables and touches no
 * selector. If a rule ever needs a light-specific shape, the token layer is
 * incomplete and that is the bug.
 *
 * No web font. The CSP is self-only and there is no font on the Pi, so both
 * faces are the system stacks -- which on the device this is used from means
 * SF Pro and SF Mono.
 */

:root {
  color-scheme: light dark;

  /* The room. */
  --bg:        #0e1621;
  --sec:       #17212b;
  --sec2:      #1f2c3b;
  --sec3:      #24313f;
  --sep:       rgba(255, 255, 255, 0.07);
  --sep-firm:  rgba(255, 255, 255, 0.14);

  /* Ink. Stepped to clear 4.5:1 on --sec, --ink3 included, because --ink3 is
     where a review stops looking. */
  --ink:       #ffffff;
  --ink2:      #aebac6;
  --ink3:      #75828e;
  --ink4:      #4a5561;

  /* What you press. */
  --blue:      #64baf0;
  --blue-deep: #2b5278;
  --on-blue:   #0e1621;

  /* The ramp, and the two marks. */
  --green:     #4dcb5f;
  --amber:     #f9a63e;
  --red:       #e5615c;
  --violet:    #8774e1;

  /* The veils. The only alpha in the file, and none of them may ever appear
     in a `color:` declaration -- they are background, shadow and gradient. */
  --veil:      rgba(6, 11, 17, 0.55);
  --veil-deep: rgba(6, 11, 17, 0.92);
  --shade:     0 -8px 40px rgba(0, 0, 0, 0.45);
  --shade-rail:-14px 0 40px rgba(0, 0, 0, 0.32);
  --lift:      0 2px 12px rgba(0, 0, 0, 0.40);
  --glass:     rgba(14, 22, 33, 0.62);
  --sheen:     rgba(255, 255, 255, 0.14);
  --scrim-top: rgba(14, 22, 33, 0.94);
  --scrim-out: rgba(14, 22, 33, 0);

  --mono: ui-monospace, "SF Mono", SFMono-Regular, Menlo, "DejaVu Sans Mono",
          Consolas, monospace;
  --sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Segoe UI", Roboto,
          Helvetica, Arial, sans-serif;

  --pad: 16px;
  --r-sec: 14px;
  --r-btn: 12px;
  --r-field: 10px;
  --sidebar: 392px;
}

@media (prefers-color-scheme: light) {
  :root {
    --bg:        #efeff4;
    --sec:       #ffffff;
    --sec2:      #f2f2f7;
    --sec3:      #e8e8ed;
    --sep:       rgba(60, 60, 67, 0.13);
    --sep-firm:  rgba(60, 60, 67, 0.24);

    --ink:       #000000;
    --ink2:      #3c3c43;
    --ink3:      #6c6c70;
    --ink4:      #aeaeb2;

    --blue:      #3390ec;
    --blue-deep: #cfe3fb;
    --on-blue:   #ffffff;

    --green:     #248a3d;
    --amber:     #a56100;
    --red:       #d70015;
    --violet:    #6b57c9;

    --veil:      rgba(0, 0, 0, 0.32);
    --veil-deep: rgba(0, 0, 0, 0.88);
    --shade:     0 -8px 40px rgba(0, 0, 0, 0.16);
    --shade-rail:-14px 0 40px rgba(0, 0, 0, 0.12);
    --lift:      0 2px 12px rgba(0, 0, 0, 0.16);
    --glass:     rgba(255, 255, 255, 0.74);
    --sheen:     rgba(255, 255, 255, 0.6);
    --scrim-top: rgba(0, 0, 0, 0.55);
    --scrim-out: rgba(0, 0, 0, 0);
  }
}

* { box-sizing: border-box; }

/* This rule is load-bearing and the page is unusable without it.
 *
 * The markup hides things with the `hidden` attribute, which works only
 * because the browser's own stylesheet gives it `display: none` -- and ANY
 * author rule setting `display` beats that. Most of the elements here carry
 * one: .app and .login are grids, .view is a flex column, .composer is a
 * flex row, .lightbox is a fixed full-screen grid.
 *
 * Without this the lightbox never hides. It sits over the whole page at 92%
 * ink, swallowing every click and darkening everything behind it, which reads
 * exactly like a broken theme rather than like a stuck overlay. Do not remove
 * it, and do not switch these to a class-based hide without removing `hidden`
 * from the markup at the same time.
 */
[hidden] { display: none !important; }

html, body {
  margin: 0;
  height: 100%;
  height: 100dvh;   /* the bar at the bottom of a phone browser is not height */
  background: var(--bg);
  color: var(--ink);
  font: 15px/1.45 var(--sans);
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  overscroll-behavior-y: none;   /* pull-to-refresh reloads the whole app */
}

body { -webkit-tap-highlight-color: transparent; }

button, input, textarea, select { font: inherit; color: inherit; }

/* 16px or Safari zooms the page on focus and never zooms back out. Every
   field on this page is at or above it, and that is not a style choice. */
input, textarea { font-size: 16px; }

:focus-visible { outline: 2px solid var(--blue); outline-offset: 2px; }

/* Somewhere you GO, as opposed to something you press -- see `link` and
   `pressable` in the script. It is a real <a href>, so it takes the browser's
   link colour and underline unless told otherwise; every place one appears
   says what it should look like there, and this only undoes the default.
   Declared here, above .ghost, so an anchor wearing a button's clothes still
   gets the button's colour. */
.link { color: inherit; text-decoration: none; cursor: pointer; }

/* The machine's voice: what the system counted or stamped, never a word
   somebody chose. */
.chat-id, .chat-when, .tag, .note, .logline, .log-tag,
.meter-time, .meter-label, .row-time, .day-label, .seam, .num,
.id, .counts, .ev-time, .ev-via, .beat-at, .k, .v, .qid, .qage, .kv {
  font-family: var(--mono);
  font-variant-numeric: tabular-nums;
  font-feature-settings: "tnum";
}

.error { color: var(--red); }
.spacer { flex: 1; min-width: 8px; }

/* Anything you can put a thumb on says so when you do. The tap highlight is
   off, so this is the only feedback left -- adding one without the other
   trades a grey flash for nothing at all. */
.pressable, .ghost, .nav button, .seg button {
  touch-action: manipulation;
}
.pressable:active { background: var(--sec2); }
.ghost:active:not(:disabled), .nav button:active, .seg button:active {
  opacity: 0.55;
}

/* ------------------------------------------------------------------ login */

.login { height: 100%; display: grid; place-items: center; padding: 22px; }

.card {
  width: min(340px, 100%);
  display: flex;
  flex-direction: column;
}

.brand {
  display: flex; align-items: center; gap: 10px;
  font-size: 13px; font-weight: 600; letter-spacing: 0.14em;
  color: var(--ink3);
}
.brand i {
  width: 26px; height: 26px; border-radius: 50%; display: block; flex: none;
  background: linear-gradient(150deg, var(--blue), var(--blue-deep));
}
.login .brand { flex-direction: column; gap: 15px; font-size: 22px;
  letter-spacing: -0.02em; color: var(--ink); margin-bottom: 10px; }
.login .brand i { width: 74px; height: 74px; }

.card > p { margin: 8px 0 22px; font-size: 14.5px; color: var(--ink2);
  text-align: center; line-height: 1.45; }
.card .fine { margin: 16px 0 0; font-size: 13px; color: var(--ink3);
  text-align: center; line-height: 1.5; }

/* The two fields are one grouped section, the way a phone asks for a
   password: the labels are the placeholders and the rule between them starts
   at the text. */
.card label {
  position: absolute; width: 1px; height: 1px; overflow: hidden;
  clip-path: inset(50%); white-space: nowrap;
}
.card input {
  width: 100%;
  background: var(--sec);
  border: 0; border-bottom: 1px solid var(--sep);
  padding: 15px 16px;
  outline: none;
  color: var(--ink);
}
.card input:first-of-type { border-radius: var(--r-sec) var(--r-sec) 0 0; }
.card label + input:last-of-type { border-radius: 0 0 var(--r-sec) var(--r-sec);
  border-bottom: 0; }
.card input::placeholder { color: var(--ink3); }
.card input:focus { background: var(--sec2); }
#code { letter-spacing: 0.3em; font-family: var(--mono); }

.card button {
  margin-top: 16px;
  background: var(--blue);
  color: var(--on-blue);
  border: 0; border-radius: var(--r-btn);
  padding: 14px;
  font-size: 16px; font-weight: 600;
  cursor: pointer;
}
.card button:disabled { opacity: 0.45; cursor: default; }
.card .error { margin: 14px 0 0; font-size: 14px; text-align: center; }

/* ------------------------------------------------------------------ shell */

/* Three rows and none of them is a fixed height. The strip is whatever the
   strip is at any label length and any Dynamic Type size, and the tab bar is
   whatever it is once the home indicator has had its inset -- which is the
   whole reason the old --strip and --nav tracks are gone. */
.app {
  height: 100%;
  display: grid;
  grid-template-rows: auto minmax(0, 1fr) auto;
  position: relative;
}

.strip {
  background: var(--sec);
  border-bottom: 1px solid var(--sep);
  padding: 8px var(--pad) 10px;
  padding-top: max(8px, env(safe-area-inset-top));
  padding-left: max(var(--pad), env(safe-area-inset-left));
  padding-right: max(var(--pad), env(safe-area-inset-right));
  display: flex; flex-wrap: wrap; align-items: center; gap: 8px 10px;
  min-width: 0;
}

.counts { display: flex; align-items: center; gap: 14px; font-size: 12px;
  color: var(--ink3); white-space: nowrap; overflow: hidden; }
.counts b { font-weight: 600; color: var(--ink2); margin-left: 5px; }
.counts .hot b { color: var(--blue); }

.dot { width: 8px; height: 8px; border-radius: 50%; background: var(--green);
  display: inline-block; flex: none; }
.dot.amber { background: var(--amber); }
.dot.off { background: var(--ink4); }

/* The paste box is the arrival path: somebody is in Telegram, has copied a
   link, and comes here to open it. It gets a line of its own. */
#lookup-form { display: flex; order: 3; width: 100%; }
#lookup {
  width: 100%;
  height: 38px; padding: 0 12px;
  background: var(--bg); border: 0; border-radius: var(--r-field);
  color: var(--ink); outline: none;
}
#lookup::placeholder { color: var(--ink3); }

.ghost {
  background: var(--sec2);
  border: 0; border-radius: var(--r-btn);
  color: var(--ink);
  min-height: 34px; padding: 0 13px;
  font-size: 14px;
  cursor: pointer;
  white-space: nowrap;
  display: inline-flex; align-items: center; justify-content: center;
}
.ghost:disabled { opacity: 0.4; cursor: default; }
.ghost.on { color: var(--blue); }
.ghost.danger { color: var(--red); }
.ghost.primary { background: var(--blue); color: var(--on-blue);
  font-weight: 600; }

/* --------------------------------------------------------------- tab bar */

/* Five destinations as equal columns: no scroller, no fade, nothing to
   discover. It sits at the bottom because that is where the thumb is, and it
   carries the home-indicator inset so the last row of a board is reachable. */
.nav {
  display: grid; grid-auto-flow: column; grid-auto-columns: 1fr;
  background: var(--sec);
  border-top: 1px solid var(--sep);
  padding-bottom: env(safe-area-inset-bottom);
}
.nav button {
  background: none; border: 0; cursor: pointer;
  min-height: 52px; padding: 6px 2px;
  display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 3px;
  font-size: 10px; font-weight: 500;
  color: var(--ink3);
}
.nav button svg { width: 25px; height: 25px; display: block; }
.nav button .wrap { position: relative; display: block; }
.nav button b {
  position: absolute; top: -3px; left: 14px;
  background: var(--red); color: var(--ink);
  border-radius: 999px; padding: 1px 5px;
  font-family: var(--mono); font-size: 10px; font-weight: 700;
  font-variant-numeric: tabular-nums;
}
.nav button.on { color: var(--blue); }

.view { min-height: 0; overflow: hidden; display: flex;
  flex-direction: column; }
.board {
  overflow-y: auto; min-height: 0; flex: 1;
  overscroll-behavior: contain;
  padding: 0 var(--pad);
  padding-bottom: max(18px, env(safe-area-inset-bottom));
}

/* ---------------------------------------------------------------- toolbar */

.tools-bar {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 0 4px; flex: none;
  color: var(--ink3); font-size: 13px;
  margin: 0 var(--pad);
}
.tools-bar em { font-style: normal; color: var(--blue);
  font-family: var(--mono); font-variant-numeric: tabular-nums; }

.seg { display: flex; background: var(--sec2); border-radius: var(--r-field);
  padding: 2px; }
.seg button {
  background: none; border: 0; color: var(--ink2); cursor: pointer;
  font-size: 13px; padding: 7px 14px; border-radius: 8px; min-height: 34px;
}
.seg button.on { background: var(--sec); color: var(--ink); font-weight: 600; }

/* ------------------------------------------------------------------ tags */

.tag {
  display: inline-block;
  font-size: 11px; font-weight: 600; letter-spacing: 0.01em;
  padding: 2px 8px; line-height: 1.45;
  border: 0; border-radius: 999px;
  background: var(--sec2);
  color: var(--ink2);
  white-space: nowrap;
}
.tag.ban, .tag.blocked, .tag.report, .tag.tick { color: var(--red); }
.tag.susp { color: var(--amber); }
.tag.live, .tag.savable { color: var(--blue); }
.tag.raised, .tag.win, .tag.copy { color: var(--violet); }
.tag.author { color: var(--ink2); }

/* --------------------------------------------------------------- the list */

/* A board is a Telegram list: one rounded section, rows inside it, and a
   separator that starts where the text starts rather than at the edge. */
.rows { display: flex; flex-direction: column;
  background: var(--sec); border-radius: var(--r-sec); overflow: hidden;
  margin-top: 10px; }
.rows .th { display: none; }   /* a phone list has no column headings */

.rows .tr {
  display: flex; align-items: center; gap: 12px;
  padding: 10px 14px; min-height: 60px;
  cursor: pointer; position: relative;
}
.rows .tr + .tr::before {
  content: ""; position: absolute; top: 0; left: 68px; right: 0;
  border-top: 1px solid var(--sep);
}

/* The face. There is no photograph of a person here and there never will be,
   so it is the id's own two digits over a colour -- enough to tell two rows
   apart at a glance, and it invents nothing. */
.av {
  width: 42px; height: 42px; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: linear-gradient(150deg, var(--blue), var(--blue-deep));
  color: var(--ink); font-size: 14px; font-weight: 600;
  font-family: var(--mono);
}
.trmain { flex: 1; min-width: 0; }
/* The id and the account on one line, and the line ends where the card ends.
   Unstyled this was a block: a long @username wrapped and pushed the markers
   down a row, which on a narrow card is the whole card. */
.trtitle { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.trside { display: flex; flex-direction: column; align-items: flex-end;
  gap: 5px; flex: none; }

.id { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.who { font-size: 15px; font-weight: 400; color: var(--ink2); }
.who span { color: var(--ink3); }
.kindcol { font-size: 14px; color: var(--ink2); }
.num { font-size: 14px; color: var(--ink2); text-align: right; }
.num.zero { color: var(--ink4); }
.tags { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; }
.trmain .tags { margin-top: 3px; }
/* The destructive control does not travel in a list that scrolls under a
   thumb. On a phone taking a post down is done from the sheet, where it sits
   alone; the width brings the per-row button back. */
.rowact { display: none; gap: 6px; justify-content: flex-end; }
.rowact .ghost { min-height: 34px; font-size: 13px; }

.empty { color: var(--ink3); text-align: center; margin-top: 14vh;
  padding: 0 20px; font-size: 15px; }
.empty .note { margin-top: 8px; font-size: 13px; color: var(--ink4); }

/* --------------------------------------------------------- the countdown */

/* The signature, and the one place colour carries meaning. The number is
   always there: the ramp is a second reading of the same fact, never the
   only one. tickMeters() puts .hot / .warm / .cool on the wrapper. */
.meter { display: flex; align-items: center; gap: 8px; }
.meter-track {
  flex: 1; min-width: 30px; height: 4px; border-radius: 999px;
  background: var(--sep-firm); overflow: hidden;
}
.meter-fill { display: block; height: 100%; border-radius: 999px;
  background: var(--green); }
.meter-time {
  font-size: 12.5px; font-weight: 600;
  border-radius: 999px; padding: 3px 10px;
  background: var(--green); color: var(--on-blue);
  text-align: center; white-space: nowrap;
}
.meter-label { display: none; }

.meter.warm .meter-fill, .meter.warm .meter-time { background: var(--amber); }
.meter.hot  .meter-fill, .meter.hot  .meter-time { background: var(--red); }

/* Under ten minutes the pill breathes. It is the only thing on the page that
   moves on its own, which is what makes it mean anything. */
@keyframes breathe { 0%, 100% { opacity: 1 } 50% { opacity: 0.55 } }
.meter.hot .meter-time { animation: breathe 2.4s ease-in-out infinite; }

/* In a row the track is noise -- the pill says it. The track earns its place
   only where there is one post on screen: the drawer and the wall. */
.trside .meter .meter-track { display: none; }

/* ------------------------------------------------------------------- wall */

.wall { display: grid; gap: 10px; padding: 12px 0;
  grid-template-columns: repeat(auto-fill, minmax(164px, 1fr)); }
.cell { position: relative; aspect-ratio: 4 / 5; background: var(--sec);
  border-radius: var(--r-sec); overflow: hidden; cursor: pointer; }
.cell img, .cell video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover; background: var(--sec);
}
.cell .cell-top { position: absolute; top: 0; left: 0; right: 0;
  display: flex; justify-content: space-between; gap: 6px; padding: 9px; }
.cell .cell-foot { position: absolute; left: 0; right: 0; bottom: 0;
  display: flex; align-items: center; gap: 8px; padding: 9px;
  background: linear-gradient(to top, var(--scrim-top), var(--scrim-out)); }
.cell .id { font-size: 12px; border-radius: 999px; padding: 3px 9px;
  background: var(--glass); backdrop-filter: blur(8px); }
.cell .meter { flex: 1; }
.cell .meter .meter-track { display: none; }
.cell .gone { position: absolute; inset: 0; border-radius: var(--r-sec); }

/* ------------------------------------------------------------------ queue */

.qhead {
  display: flex; align-items: center; gap: 9px;
  padding: 20px 4px 8px;
  font-size: 12.5px; font-weight: 600; letter-spacing: 0.05em;
  text-transform: uppercase;
  color: var(--ink3);
}
.qhead b { color: var(--ink2); }
.qhead.later { margin-top: 4px; }

/* A report is a card, not a row: the text is the point and it is somebody's
   sentence, so it gets the width. The two actions sit under it, split, the
   way a phone offers two choices. */
.qrow { background: var(--sec); border-radius: var(--r-sec); margin-top: 10px;
  overflow: hidden; display: block; padding: 13px 14px 0; }
.qrow .qhead { padding: 0; }
/* The row's own first block is the id and its age; the markup puts them in a
   div together, so they read as one line rather than two. */
.qrow > div:first-child { display: flex; align-items: baseline; gap: 10px; }
.qid { font-size: 16px; font-weight: 600; color: var(--ink);
  display: inline-block; }
.qage { font-size: 13px; color: var(--ink3); }
.qwho { display: flex; align-items: center; gap: 8px; flex-wrap: wrap;
  margin: 7px 0; }
.qsaid { font-size: 15px; line-height: 1.45; color: var(--ink);
  max-width: 76ch; white-space: pre-wrap; overflow-wrap: anywhere;
  margin: 0 0 13px; }
.qrow.answered .qsaid { color: var(--ink2); }
.qact { display: flex; margin: 0 -14px; border-top: 1px solid var(--sep); }
.qact .ghost {
  flex: 1; min-height: 46px; border-radius: 0; background: none;
  color: var(--blue); font-size: 15px; font-weight: 500;
}
.qact .ghost + .ghost { color: var(--ink2); border-left: 1px solid var(--sep); }
.qact .ghost.danger { color: var(--red); }

/* ------------------------------------------------------------------ stats */

.tiles { display: grid; gap: 10px; margin-top: 12px;
  grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); }
.tile { padding: 14px 15px 15px; background: var(--sec);
  border-radius: var(--r-sec); }
.tile .k { font-size: 12.5px; font-weight: 500; color: var(--ink3);
  font-family: var(--sans); letter-spacing: 0; text-transform: none; }
.tile .v { font-size: 28px; font-weight: 700; letter-spacing: -0.03em;
  margin-top: 5px; }
.tile .s { font-size: 12.5px; color: var(--ink3); margin-top: 2px; }
.tile.hot .v { color: var(--blue); }

.panels { display: grid; gap: 10px;
  grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); }
.pan { padding: 16px; background: var(--sec); border-radius: var(--r-sec);
  margin-top: 10px; }
.pan h3 { margin: 0 0 4px; font-size: 16px; font-weight: 600;
  letter-spacing: -0.01em; }
.pan .cap { margin: 0 0 15px; font-size: 13.5px; color: var(--ink3);
  line-height: 1.45; }

.hbars { display: flex; flex-direction: column; gap: 9px; }
.hbar { display: grid; grid-template-columns: 84px minmax(0, 1fr) 54px;
  gap: 10px; align-items: center; }
.hbar .l { font-size: 13.5px; color: var(--ink2); }
.hbar .t { height: 10px; background: var(--sec2); border-radius: 999px; }
.hbar .t i { display: block; height: 100%; background: var(--blue-deep);
  border-radius: 999px; }
.hbar.now .t i { background: var(--blue); }
.hbar .n { font-family: var(--mono); font-size: 12.5px; color: var(--ink2);
  text-align: right; font-variant-numeric: tabular-nums; }

.prop { position: relative; padding-bottom: 24px; }
.prop .bar { display: flex; height: 32px; gap: 3px; }
.prop .bar span { display: flex; align-items: center; padding: 0 10px;
  font-family: var(--mono); font-size: 12px; font-weight: 700;
  color: var(--on-blue); overflow: hidden; white-space: nowrap;
  border-radius: 4px; }
.prop .bar span:first-child { border-radius: 10px 4px 4px 10px; }
.prop .bar span:last-child { border-radius: 4px 10px 10px 4px; }
.prop .bar .lose { background: var(--amber); }
.prop .bar .draw { background: var(--blue-deep); }
.prop .bar .win { background: var(--blue); }
.prop .mark { position: absolute; top: -4px; height: 40px;
  border-left: 2px dashed var(--ink2); }
.prop .mark em { position: absolute; top: 42px; left: 0; font-style: normal;
  transform: translateX(-50%); font-size: 11.5px;
  color: var(--ink2); white-space: nowrap; }

.keys { display: flex; gap: 16px; margin-top: 14px; flex-wrap: wrap; }
.key { display: flex; align-items: center; gap: 7px; font-size: 13.5px;
  color: var(--ink2); }
.key i { width: 10px; height: 10px; border-radius: 3px; display: block;
  flex: none; }
.key b { font-family: var(--mono); color: var(--ink); font-weight: 600;
  font-variant-numeric: tabular-nums; }

.vbars { display: grid; gap: 7px; align-items: end; height: 118px;
  grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr); }
.vbar { display: flex; flex-direction: column; justify-content: flex-end;
  align-items: center; gap: 6px; height: 100%; }
.vbar i { display: block; width: 100%; background: var(--blue-deep);
  border-radius: 6px 6px 3px 3px; }
.vbar.peak i { background: var(--blue); }
.vbar b, .vbar em { font-family: var(--mono); font-style: normal;
  font-weight: 400; font-size: 11px; color: var(--ink3);
  font-variant-numeric: tabular-nums; white-space: nowrap; }
.vbar em { font-size: 10.5px; }

/* A ten-digit id and a username in one cell is what forced a sideways scroll
   on a 390px board. Fixed layout, and the cell wraps. */
.mini { width: 100%; border-collapse: collapse; table-layout: fixed; }
.mini th { text-align: left; font-size: 12px; color: var(--ink3);
  font-weight: 500; padding: 0 0 9px; }
.mini td { padding: 9px 0; border-top: 1px solid var(--sep);
  font-family: var(--mono); font-size: 13px; color: var(--ink2);
  font-variant-numeric: tabular-nums; overflow-wrap: anywhere; }
.mini td.r { text-align: right; }
.mini td .link { color: var(--ink); font-weight: 600; }

/* --------------------------------------------------------------- activity */

/* The feed carries five thousand lines a day, so a line has to be scannable
   rather than parseable: a mark that says what kind of thing happened, one
   sentence, and the time. */
/* The container the rows are cut out of, same as .rows on the other boards:
   the corners belong to the group, not to whichever row happens to be at the
   end of it. */
.feed { display: flex; flex-direction: column; margin-top: 10px;
  border-radius: var(--r-sec); overflow: hidden; }
.ev { display: flex; align-items: flex-start; gap: 11px;
  padding: 10px 14px; position: relative; background: var(--sec);
  cursor: default; }
.ev + .ev::before { content: ""; position: absolute; top: 0; left: 14px;
  right: 0; border-top: 1px solid var(--sep); }
.ev:first-child { border-radius: var(--r-sec) var(--r-sec) 0 0; }
.ev:last-child { border-radius: 0 0 var(--r-sec) var(--r-sec); }
.ev-tag {
  flex: none; align-self: flex-start; margin-top: 1px;
  min-width: 64px; text-align: center;
  border-radius: 999px; padding: 3px 9px;
  background: var(--sec2); color: var(--ink2);
  font-family: var(--sans); font-size: 10.5px; font-weight: 700;
  letter-spacing: 0.03em;
}
.ev-msg { flex: 1; min-width: 0; font-size: 14.5px; color: var(--ink);
  overflow-wrap: anywhere; }
.ev-msg .link { color: var(--blue); font-weight: 500;
  font-family: var(--mono); }
.ev-time { font-size: 13px; color: var(--ink3); flex: none; align-self: flex-start;
  order: 3; margin-top: 3px; }
.ev-via { font-size: 12px; color: var(--ink3); margin-left: 8px; }
.ev-body { display: block; font-size: 13px; color: var(--ink3);
  margin-top: 3px; white-space: pre-wrap; overflow-wrap: anywhere; }
.ev.sweep .ev-tag { background: var(--amber); color: var(--on-blue); }
.ev.save .ev-tag { background: var(--violet); color: var(--ink); }
.ev.action .ev-tag { background: var(--red); color: var(--on-blue); }

/* ------------------------------------------------------------ the sidebar */

.split { display: flex; flex-direction: column; flex: 1; min-height: 0; }
.sidebar { display: flex; flex-direction: column; min-height: 0; flex: 1; }

/* On a phone the list and the conversation are two screens, never two panes.
   Stacked in the same column at flex:1 apiece they came out half a screen
   each -- a chat list you cannot scan above a transcript you cannot read --
   and that is what this rule is for. `reading` is the state the router
   already sets on #app when the hash is #/chat/<id>; it had no rule here at
   all, which is the whole bug. Above 821px it means nothing, because both
   panes fit side by side and the back button is hidden. */
.split > .main { display: none; }
.app.reading .split > .sidebar { display: none; }
.app.reading .split > .main { display: flex; }

/* And once it is a screen it gets the screen. A conversation was arriving
   under a two-row strip and over a tab bar -- about 160px of chrome framing
   the one thing on the page you actually read, on the device it is mostly
   read on. So while a chat is open the strip and the tab bar go: the header
   already names who this is, and `<` goes back to the list, which is where
   both of them live. That is the ordinary shape of a pushed screen, and it is
   the shape because it is the one that leaves room for the content.
   The notch inset moves with them -- the header is the top element now. */
.app.reading > .strip, .app.reading > .nav { display: none; }
.app.reading .chat-head { padding-top: max(9px, env(safe-area-inset-top));
  padding-left: max(var(--pad), env(safe-area-inset-left));
  padding-right: max(var(--pad), env(safe-area-inset-right)); }
.sidebar-head { display: flex; gap: 8px; padding: 12px var(--pad) 4px;
  flex: none; }
.sidebar-head input {
  flex: 1; min-width: 0; height: 38px; padding: 0 12px;
  background: var(--sec); border: 0; border-radius: var(--r-field);
  color: var(--ink); outline: none;
}
.sidebar-head input::placeholder { color: var(--ink3); }

.chats { flex: 1; overflow-y: auto; min-height: 0; overscroll-behavior: contain;
  padding: 10px var(--pad) 0; }
.sidebar-foot { padding: 12px var(--pad); font-size: 12.5px;
  color: var(--ink3); flex: none; }

/* A row is an id first. These people have no faces and often no name -- the
   circle carries two digits of the id, never an invented identity. */
.chat-row {
  display: grid; grid-template-columns: auto 1fr auto; gap: 2px 12px;
  padding: 10px 14px;
  background: var(--sec);
  cursor: pointer;
  position: relative;
  align-items: center;
}
.chat-row:first-child { border-radius: var(--r-sec) var(--r-sec) 0 0; }
.chat-row:last-child { border-radius: 0 0 var(--r-sec) var(--r-sec);
  margin-bottom: 12px; }
.chat-row + .chat-row::before { content: ""; position: absolute; top: 0;
  left: 68px; right: 0; border-top: 1px solid var(--sep); }
.chat-row.active { background: var(--sec2); }
.chat-row .av { grid-row: 1 / span 3; }
.chat-id { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.chat-when { font-size: 13px; color: var(--ink3); text-align: right; }
.chat-preview {
  grid-column: 2 / -1; font-size: 14px; color: var(--ink2);
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.chat-flags { grid-column: 2 / -1; display: flex; gap: 6px; margin-top: 4px; }

/* ------------------------------------------------------------- the thread */

.main { display: flex; flex-direction: column; min-width: 0; min-height: 0;
  flex: 1; }

.chat-head { display: flex; align-items: center; gap: 11px;
  padding: 9px var(--pad); background: var(--sec);
  border-bottom: 1px solid var(--sep); flex: none; }
.who-name { font-size: 16px; font-weight: 600; letter-spacing: -0.01em; }
.who-meta { font-size: 13px; color: var(--ink3); margin-top: 1px; }
.tools { display: flex; gap: 6px; flex-wrap: wrap; justify-content: flex-end; }
.back { display: inline-flex; background: none; color: var(--blue);
  font-size: 20px; padding: 0 4px; min-height: 44px; }

.timeline { flex: 1; overflow-y: auto; overscroll-behavior: contain;
  padding: 14px 12px 10px; min-height: 0; }

/* Floating over the thread, not a row above it. As a laid-out bar this was
   ~56px of height held for a button that is pressed once a session, taken
   from the messages every second of it. Over the thread it costs nothing and
   is in the same place -- the top, which is where you already are when you
   want it. */
.older-wrap { height: 0; z-index: 2; position: relative;
  display: flex; justify-content: center; pointer-events: none; }
.older-wrap button { pointer-events: auto; margin-top: 8px;
  box-shadow: var(--lift); background: var(--sec2); }
/* Only when it is actually there: the pill overlays the first row, so the
   thread makes room for it, and stops making room the moment there is
   nothing older left to ask for. */
.older-wrap:not([hidden]) + .timeline { padding-top: 52px; }

/* A bubble is a bubble: the time gutter is gone, and the stamp lives inside
   the bubble the way every chat client puts it. */
.row { display: flex; flex-direction: column; margin-bottom: 8px; }
.row > .meter { align-self: center; margin: 4px 0 6px; }
.row-time { display: none; }

.day { display: flex; justify-content: center; margin: 18px 0 12px; }
.day-label { font-size: 12px; color: var(--ink2); background: var(--sec);
  border-radius: 999px; padding: 4px 12px; }

.seam { font-size: 12.5px; line-height: 1.5; color: var(--ink2);
  background: var(--sec); border-radius: var(--r-sec);
  padding: 11px 14px; margin: 18px 0 12px; max-width: 74ch; align-self: center; }

/* What a person wrote. */
.said { max-width: min(560px, 82%); background: var(--sec);
  border-radius: 16px 16px 16px 6px;
  padding: 9px 13px 8px; align-self: flex-start; }

/* What the bot said: pushed right, in the outgoing colour. The operator is
   not a participant, but the transcript is still a conversation and reading
   it as anything else was the mistake. */
.said.out { align-self: flex-end; background: var(--blue-deep);
  border-radius: 16px 16px 6px 16px; color: var(--ink); }

/* Reconstructed from the feed and the outbox rather than recorded as it
   happened. Said quietly, but said. */
.said.guessed { border: 1px dashed var(--sep-firm); }

.said-text { white-space: pre-wrap; overflow-wrap: anywhere; font-size: 15px;
  line-height: 1.42; user-select: text; }
.said-text strong { font-weight: 600; }
.said-text em { font-style: italic; }
.said-text a { color: var(--blue); text-underline-offset: 2px; }
.said-text code, .said-text pre {
  font-family: var(--mono); font-size: 0.88em;
  background: var(--bg); border-radius: 6px; padding: 1px 5px;
}
.said-text pre { display: block; padding: 9px 11px; margin: 6px 0;
  overflow-x: auto; white-space: pre; }
.said-text blockquote { margin: 6px 0; padding-left: 10px;
  border-left: 2px solid var(--blue); color: var(--ink2); }
.said-text .spoiler { background: var(--ink4); color: transparent;
  border-radius: 4px; cursor: pointer; }
/* A tap reveals it. The old rule was :hover only, which on a phone means the
   spoiler could never be read at all. */
.said-text .spoiler.shown { background: transparent; color: inherit; }

.kind { font-size: 11px; font-weight: 600; color: var(--ink3);
  margin-bottom: 3px; }
.said.out .kind { color: var(--ink2); }

.buttons { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
.chip { font-size: 13px; color: var(--blue); background: var(--sec);
  border-radius: 999px; padding: 6px 12px; }
.said.out .chip { background: var(--bg); }

/* A system line is a log line, because that is what it is. */
.logline { font-size: 13px; color: var(--ink3);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 9px;
  padding: 5px 6px; align-self: center; max-width: 92%; }
.logline .log-tag { font-size: 11px; color: var(--ink2); }
.logline .note { font-size: 11.5px; color: var(--ink4); }
.note { font-size: 12px; color: var(--ink3); }
.said.out .note { color: var(--ink2); }

.composer {
  display: flex; align-items: flex-end; gap: 9px; flex: none;
  padding: 9px 12px;
  padding-bottom: max(12px, env(safe-area-inset-bottom));
  background: var(--sec); border-top: 1px solid var(--sep);
}
#compose-text {
  flex: 1; min-height: 40px; max-height: 140px; resize: none;
  background: var(--bg); border: 0; border-radius: 20px;
  padding: 10px 15px; color: var(--ink); outline: none;
  line-height: 1.35;
}
#compose-text::placeholder { color: var(--ink3); }
.composer .ghost.primary { width: 40px; height: 40px; min-height: 40px;
  border-radius: 50%; padding: 0; flex: none; font-size: 0; }
.composer .ghost.primary::after {
  content: ""; width: 18px; height: 18px; display: block;
  background: currentColor;
  clip-path: polygon(0 44%, 62% 44%, 42% 20%, 55% 8%, 100% 50%, 55% 92%,
                     42% 80%, 62% 56%, 0 56%);
}

/* ------------------------------------------------------------------- dice */
/* The one loud thing in a log line, so everything around it stays quiet. The
   gap between the two faces is the information: wider means longer up. */

.dice { display: inline-flex; align-items: center; gap: 9px;
  vertical-align: middle; }
.die { display: grid; grid-template-columns: repeat(3, 4px);
  grid-template-rows: repeat(3, 4px); gap: 3px; padding: 6px;
  background: var(--sec2); border-radius: 7px; }
.pip { width: 4px; height: 4px; border-radius: 50%; background: var(--ink2);
  display: block; }
.pip.off { background: transparent; }
/* The die that decided it. On a loss that is the bot's, and it is the throw
   that put somebody's media in a channel. */
.die.hot { background: var(--blue); }
.die.hot .pip { background: var(--on-blue); }
.dice-versus { font-size: 11px; color: var(--ink3); }
.dice-verdict { font-size: 13.5px; color: var(--ink2); font-family: var(--sans); }
.dice.lose .dice-verdict { color: var(--amber); }
.dice.win .dice-verdict { color: var(--blue); }
.dice-savable { font-size: 11px; font-weight: 600; color: var(--blue);
  background: var(--sec2); border-radius: 999px; padding: 2px 8px; }

/* ------------------------------------------------------------------ media */

.media { margin: 6px 0 4px; }
.media img, .media video { max-width: 100%; max-height: 340px; display: block;
  cursor: zoom-in; background: var(--sec2); border-radius: 11px; }

/* A media whose post has come down. Drained on purpose: the interface says
   the thing the product promises, without a sentence about it. */
.gone {
  font-size: 13px; color: var(--ink3);
  border-radius: 11px; padding: 22px 14px; text-align: center;
  display: grid; place-items: center; line-height: 1.5;
  background: repeating-linear-gradient(135deg, var(--sec) 0 8px,
                                        var(--sec2) 8px 16px);
}

/* ---------------------------------------------------------------- drawer */

/* On a phone it is a sheet from the bottom edge; on a desk it is the right
   rail it always was. Same markup, and inset:0 rather than a top offset --
   the strip is no longer a number this file knows. */
.scrim { position: absolute; inset: 0; background: var(--veil); z-index: 8; }
.drawer {
  position: absolute; left: 0; right: 0; bottom: 0; top: auto;
  max-height: 92%;
  background: var(--sec);
  border-radius: 18px 18px 0 0;
  box-shadow: var(--shade);
  z-index: 9; display: flex; flex-direction: column; min-height: 0;
  animation: rise 260ms cubic-bezier(0.32, 0.72, 0, 1);
}
@keyframes rise { from { transform: translateY(14%); } to { transform: none; } }

/* The grabber. It is not decoration: it is the only thing that says this
   sheet is dismissed by dragging it down. */
.dhead::before {
  content: ""; position: absolute; top: 8px; left: 50%; width: 36px;
  height: 5px; border-radius: 3px; background: var(--sep-firm);
  transform: translateX(-50%);
}
.dhead { display: flex; align-items: center; gap: 10px;
  padding: 22px 16px 12px; flex: none; position: relative; }
.dhead .id { font-size: 17px; }
.dbody { padding: 0 16px 16px; display: flex; flex-direction: column;
  gap: 16px; overflow-y: auto; min-height: 0; overscroll-behavior: contain;
  padding-bottom: max(16px, env(safe-area-inset-bottom)); }
.dsec h4 { margin: 0 0 10px; font-size: 12.5px; font-weight: 600;
  letter-spacing: 0.05em; text-transform: uppercase; color: var(--ink3); }
/* The post first, the facts under it. This was a row on every width: a
   132px thumbnail beside a two-column list, which on a phone left the value
   column about 80px wide -- a caption, a timestamp and a dice roll each
   folding onto three or four lines. The media IS the post, and this sheet is
   opened to look at it, so on the base layout it gets the width. The row
   comes back on the desk, at the end of the file, where 468px of sheet has
   room for both. */
.dsec.split-row { display: flex; flex-direction: column; gap: 13px; }
/* Stacked, the list is simply as wide as the sheet. It used to carry an
   inline flex:1 for the row layout, which in a column means a flex-basis of
   zero -- the list then falls back to its MIN-content height and every value
   breaks at every opportunity. Widths belong to the two layouts, so they are
   written where the two layouts are. */
.dsec.split-row .kv { width: 100%; }
.dsec .media { margin: 0; width: 100%; }
.dsec .media img, .dsec .media video { max-height: 42vh; max-width: 100%;
  width: auto; margin: 0 auto; }

.kv { display: grid; grid-template-columns: 104px minmax(0, 1fr);
  gap: 7px 12px; margin: 0; font-size: 14px; }
.kv dt { color: var(--ink3); }
.kv dd { margin: 0; color: var(--ink2); overflow-wrap: anywhere;
  user-select: text; }
.kv dd b, .kv dd .link { color: var(--ink); font-weight: 600; }

.lifebar { position: relative; height: 10px; margin: 2px 0 0; }
.lifetrack { position: absolute; inset: 0; background: var(--sec2);
  border-radius: 999px; overflow: hidden; }
.lifetrack i { display: block; height: 100%; background: var(--blue-deep); }
.pin { position: absolute; top: -3px; width: 3px; height: 16px;
  border-radius: 999px; transform: translateX(-1px); }
.pin.raise { background: var(--amber); }
.pin.copy { background: var(--violet); }
.pin.now { background: var(--ink); height: 20px; top: -5px; }
.lifeends { display: flex; justify-content: space-between; margin-top: 12px;
  font-size: 12.5px; color: var(--ink3); }

.beats { list-style: none; margin: 12px 0 0; padding: 0; }
.beat { display: grid; grid-template-columns: 64px minmax(0, 1fr); gap: 12px;
  padding: 7px 0; font-size: 14px; align-items: baseline; }
.beat-at { font-size: 12.5px; color: var(--ink3); text-align: right; }
.beat-what { color: var(--ink2); display: flex; align-items: center; gap: 8px;
  flex-wrap: wrap; }
.beat-what b { color: var(--ink); font-weight: 500; font-family: var(--mono); }
.beat.raise .beat-at, .beat.raise .beat-what b { color: var(--amber); }
.beat.copy .beat-at, .beat.copy .beat-what b { color: var(--violet); }
.beat.now .beat-at, .beat.now .beat-what b { color: var(--ink); }

.listing { display: flex; flex-direction: column; gap: 2px;
  font-size: 14px; color: var(--ink2); }
.listing div { display: flex; justify-content: space-between; gap: 12px;
  padding: 8px 0; border-top: 1px solid var(--sep); min-width: 0; }
.listing div:first-child { border-top: 0; }
.listing div > * { min-width: 0; overflow-wrap: anywhere; }
.listing span { color: var(--ink3); font-family: var(--mono); font-size: 12.5px;
  flex: none; }
.listing .link { font-weight: 600; color: var(--ink);
  font-family: var(--mono); }
.warnbox { background: var(--sec2); border-radius: var(--r-btn);
  border-left: 3px solid var(--red);
  padding: 11px 13px; font-size: 13.5px; color: var(--ink2); }

/* --------------------------------------------------------------- overlays */

#dialog {
  border: 0; border-radius: var(--r-sec);
  background: var(--sec); color: var(--ink);
  padding: 0; width: min(400px, 90vw);
}
#dialog::backdrop { background: var(--veil); }
#dialog-form { padding: 20px 20px 16px; display: flex; flex-direction: column; }
#dialog-title { margin: 0; font-size: 17px; font-weight: 600; text-align: center; }
#dialog-body { margin: 8px 0 0; font-size: 14px; color: var(--ink2);
  white-space: pre-wrap; text-align: center; line-height: 1.45; }
#dialog-input { margin-top: 16px; min-height: 78px; resize: vertical;
  padding: 11px 13px; background: var(--bg); border: 0;
  border-radius: var(--r-btn); outline: none; color: var(--ink); }
.dialog-act { display: flex; gap: 8px; margin-top: 18px; }
.dialog-act .ghost { flex: 1; min-height: 44px; font-size: 15px; }

.lightbox { position: fixed; inset: 0; background: var(--veil-deep);
  display: grid; place-items: center; z-index: 20;
  padding: max(16px, env(safe-area-inset-top)) 16px
           max(16px, env(safe-area-inset-bottom)); }
.lightbox img, .lightbox video { max-width: 100%; max-height: 100%;
  border-radius: 10px; touch-action: pinch-zoom; }

.toast { position: fixed; left: 50%; transform: translateX(-50%);
  bottom: max(26px, calc(env(safe-area-inset-bottom) + 74px));
  background: var(--sec2); border-radius: var(--r-btn);
  color: var(--ink); padding: 12px 18px; font-size: 14.5px; z-index: 30;
  max-width: 88vw; box-shadow: var(--shade);
  animation: rise 200ms ease-out; }
.toast.bad { color: var(--red); }

/* ------------------------------------------------------------------- desk */

/* Not a second design: the same tokens, the same sections, the same rows.
   What the width buys is the sidebar back, the drawer as a rail instead of a
   sheet, and the tab bar on its side. */
@media (min-width: 821px) {
  .app { grid-template-rows: auto minmax(0, 1fr); grid-template-columns: 76px 1fr;
    grid-template-areas: "strip strip" "nav view"; }
  .strip { grid-area: strip; flex-wrap: nowrap; padding: 8px 16px; }
  #lookup-form { order: 0; width: auto; }
  #lookup { width: 280px; }
  .nav { grid-area: nav; grid-auto-flow: row; grid-auto-columns: auto;
    grid-auto-rows: min-content; border-top: 0;
    border-right: 1px solid var(--sep); padding: 10px 0 0; gap: 4px; }
  .nav button { border-radius: var(--r-sec); margin: 0 10px; }
  .nav button.on { background: var(--sec2); }
  .view { grid-area: view; }

  .board { padding: 0 20px 20px; }
  .rows .tr { min-height: 64px; }
  .rowact { display: flex; }
  .split { flex-direction: row; }
  .split > .main, .app.reading .split > .sidebar { display: flex; }
  .sidebar { width: var(--sidebar); flex: none; border-right: 1px solid var(--sep);
    background: var(--sec); }
  .chats { padding: 10px 10px 0; }
  .chat-row { border-radius: var(--r-btn); }
  .chat-row + .chat-row::before { display: none; }
  .chat-row:last-child { margin-bottom: 0; }
  .back { display: none; }
  .timeline { padding: 18px 22px 12px; }
  .composer { padding: 12px 22px; }

  .dsec.split-row { flex-direction: row; align-items: flex-start;
    flex-wrap: wrap; }
  .dsec .media { width: 132px; flex: none; }
  .dsec.split-row .kv { width: auto; flex: 1; min-width: 0; }
  .dsec .media img, .dsec .media video { max-height: 116px; margin: 0; }

  .drawer { left: auto; top: 0; width: min(468px, 100%); max-height: none;
    border-radius: 0; border-left: 1px solid var(--sep);
    box-shadow: var(--shade-rail); animation: none; }
  .dhead::before { display: none; }
  .dhead { padding: 14px 16px; border-bottom: 1px solid var(--sep); }
  .dbody { padding-top: 16px; }
  .toast { bottom: 26px; }
}

/* Three things move on this page: the pill that breathes, the sheet that
   rises, the toast. Somebody who has asked for none of it gets none of it. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
  }
}
