/* Lobby design system: see DESIGN.md before changing tokens. */
@font-face { font-family: "Atkinson Hyperlegible"; font-weight: 400; font-style: normal; font-display: swap; src: url(/fonts/AtkinsonHyperlegible-400.woff2) format("woff2"); }
@font-face { font-family: "Atkinson Hyperlegible"; font-weight: 400; font-style: italic; font-display: swap; src: url(/fonts/AtkinsonHyperlegible-400i.woff2) format("woff2"); }
@font-face { font-family: "Atkinson Hyperlegible"; font-weight: 700; font-style: normal; font-display: swap; src: url(/fonts/AtkinsonHyperlegible-700.woff2) format("woff2"); }
@font-face { font-family: "Comic Neue"; font-weight: 700; font-style: normal; font-display: swap; src: url(/fonts/ComicNeue-700.woff2) format("woff2"); }
@font-face { font-family: "Libre Franklin"; font-weight: 800; font-style: normal; font-display: swap; src: url(/fonts/LibreFranklin-800.woff2) format("woff2"); }
@font-face { font-family: "Barlow Condensed"; font-weight: 600; font-style: normal; font-display: swap; src: url(/fonts/BarlowCondensed-600.woff2) format("woff2"); }

:root {
  color-scheme: light;
  /* palette: blue and white (member request, henry: "change theme color to blue"; it was red) */
  --wall: #f4f6fb;       /* page background: white with a breath of blue */
  --wall-2: #fafcff;     /* raised surfaces: sheets, inputs */
  --paper: #ffffff;      /* inputs, lists */
  --ink: #0b1a2e;        /* text: a blue so deep it reads as black */
  --ink-2: #4a5f7d;      /* secondary text */
  --ink-3: #8694a9;      /* tertiary text, hints */
  --rule: #d2dcec;       /* separators, input borders */
  --felt: #1150b8;       /* the letterboard: blue felt */
  --felt-groove: #0d439c;
  --letter: #ffffff;     /* letterboard letters */
  --red: #1259d3;        /* your own bubbles, primary buttons */
  --red-ink: #ffffff;
  --bubble: #ffffff;     /* everyone else's bubbles, white in light and dark */
  --bubble-ink: #0b1a2e;
  --bubble-edge: #cfdaeb;
  --signal: #ffffff;     /* change requests + "in progress": white on the blue board */
  --signal-ink: #1150b8;
  --focus: #1259d3;
  --note: #e6eeff;       /* replies from drischat */
  --note-ink: #0d254a;
  --danger: #b42318;
  --ok: #1f7a4d;
  /* type */
  --font: "Atkinson Hyperlegible", system-ui, -apple-system, "Segoe UI", sans-serif;
  --board: "Barlow Condensed", "Arial Narrow", sans-serif;
  --app-h: 100dvh;
  /* the home-bar inset. html.short-view zeroes it: an iPhone home-screen app installed back when the status bar
     was translucent gets a web view that stops a status bar short of the screen (iOS 26, WebKit bug 301108), so
     it never reaches the home bar and padding for it just leaves dead space above the unreachable strip */
  --sab: env(safe-area-inset-bottom);
}
@media (prefers-color-scheme: dark) {
  :root {
    color-scheme: dark;
    --wall: #0a111b;
    --wall-2: #0f1925;
    --paper: #14212f;
    --ink: #eef2fb;
    --ink-2: #a9b8cf;
    --ink-3: #77859a;
    --rule: #25364a;
    --felt: #0f449d;
    --felt-groove: #0c3983;
    --red: #2269e3;
    --bubble-edge: #ffffff;
    --focus: #5a96ff;
    --note: #14263d;
    --note-ink: #dae8ff;
    --danger: #ff8a7a;
    --ok: #6fd3a0;
  }
}

* { box-sizing: border-box; -webkit-tap-highlight-color: transparent; }
html.short-view { --sab: 0px; }
/* a home-screen app whose web view starts under the status bar (see short-view, app.js) gets a black strip there,
   like the opaque status bar, so the clock and battery never sit on the red letterboard */
html.under-bar body::after { content: ""; position: fixed; z-index: 2147483647; top: 0; left: 0; right: 0; height: env(safe-area-inset-top); background: #000; pointer-events: none; }
html, body { margin: 0; height: 100%; touch-action: manipulation; background: var(--wall); color: var(--ink); font: 17px/1.45 var(--font); overscroll-behavior: none; }
/* status-bar area matches the board on iOS; the lower half matches the composer, so if iOS ever leaves a gap
   under the app it blends in instead of showing a red bar */
body.in-chat { background: #fff linear-gradient(var(--felt), var(--felt)) top / 100% 50% no-repeat; }
button { font: inherit; color: inherit; cursor: pointer; }
input, textarea { font: inherit; font-size: 17px; color: var(--ink); }
a { color: var(--red); }
[hidden] { display: none !important; }
.sr-only { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.board :focus-visible { outline-color: var(--letter); }
.boot { height: var(--app-h); background: var(--felt); }

/* ---------- the letterboard ---------- */
.board {
  background-color: var(--felt);
  background-image: repeating-linear-gradient(to bottom, transparent 0 7px, var(--felt-groove) 7px 9px);
  color: var(--letter);
  font-family: var(--board);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: .14em;
  text-shadow: 0 1px 0 #000a;
}
.board .row1 { font-size: 26px; line-height: 1; }
.board .row2 { font-size: 15px; line-height: 1.2; letter-spacing: .12em; opacity: .82; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

/* ---------- auth ---------- */
.auth { min-height: var(--app-h); display: flex; flex-direction: column; }
.auth .board { padding: calc(56px + env(safe-area-inset-top)) 24px 40px; }
.auth .board .row1 { font-size: 64px; letter-spacing: .18em; }
.auth .board .row2 { font-size: 20px; margin-top: 14px; white-space: normal; }
.auth-body { flex: 1; padding: 28px 24px calc(28px + var(--sab)); max-width: 420px; width: 100%; }
.auth-body p { margin: 0 0 20px; color: var(--ink-2); }
.auth form { display: grid; gap: 12px; }
.auth label { display: grid; gap: 5px; font-weight: 700; font-size: 15px; }
.field { width: 100%; padding: 12px 14px; background: var(--paper); border: 1.5px solid var(--rule); border-radius: 10px; outline: none; }
.field:focus { border-color: var(--red); }
.btn { border: 0; border-radius: 10px; padding: 13px 18px; font-weight: 700; background: var(--red); color: var(--red-ink); }
.btn:disabled { opacity: .5; }
.btn.quiet { background: transparent; border: 1.5px solid var(--rule); color: var(--ink); }
.btn.small { padding: 7px 12px; font-size: 15px; }
.btn.signal { background: var(--red); color: var(--red-ink); }
.err { color: var(--danger); min-height: 1.3em; font-size: 15px; }
.hint { color: var(--ink-2); font-size: 15px; margin-top: 22px; }

/* ---------- chat layout ---------- */
/* bug fix (Ethan: "make the background of the group chat solid white, the text still spills over certain graphic
   objects like the rectangle"): the chat is plain solid white under the letterboard, in dark mode too, with nothing
   drawn behind the messages (it replaced the liquid glass over the live Bii lobby). In dark mode the chat takes the
   light palette so its text stays dark on the white; the red letterboard is unchanged. */
.chat { position: fixed; top: var(--app-top, 0px); left: 0; right: 0; height: var(--app-h); display: flex; flex-direction: column; max-width: 760px; margin: 0 auto; background: #fff; --wall: #fff; }
@media (prefers-color-scheme: dark) {
  .chat, .sheet.docked {
    color-scheme: light; color: var(--ink);
    --wall-2: #fafcff; --paper: #ffffff; --ink: #0b1a2e; --ink-2: #4a5f7d; --ink-3: #8694a9; --rule: #d2dcec;
    --red: #1259d3; --bubble-edge: #cfdaeb; --focus: #1259d3; --note: #e6eeff; --note-ink: #0d254a; --danger: #b42318; --ok: #1f7a4d;
  }
  .chat .msg .who { color: var(--c, var(--ink-2)); }
  .chat .mface.cutout img { filter: none; }
  .chat .mpick:not(:first-child) { color: var(--c, var(--ink)); }
}
.chat > .top, .chat > .chiron, .chat > .tabbar, .chat > .banner, .chat > .messages, .chat > .typing, .chat > .composer { position: relative; z-index: 1; }

.top { display: flex; align-items: center; gap: 12px; padding: calc(12px + env(safe-area-inset-top)) 14px 12px 18px; }
/* member request: the chat's top bar is solid, opaque felt red (no grooves) */
.chat > .top.board { background: var(--felt); background-image: none; }
/* bug fix, Ethan: the drischat name is bigger and stands alone (the "7 members" line is gone; faces show who's here) */
.chat > .top .row1 { font-size: 44px; line-height: .9; letter-spacing: .1em; white-space: nowrap; }

/* Who's here (bug fix, Ethan: "display the active members' Bii faces along the top of the group chat tab"): a quiet
   row on the chat's white, you first, each Bii head over its member's name in their colour. It sits under the composer
   (member request, Ethan: "move the bar of active chat members under the keyboard"), where the keyboard covers it: it
   holds the bottom safe area while the keyboard is down and is gone while it's up, so the composer sits on the keys. */
.chat > .here { position: relative; z-index: 1; flex: none; display: flex; gap: 4px; padding: 6px 10px calc(4px + var(--sab)); overflow-x: auto;
  overscroll-behavior-x: contain; scrollbar-width: none; -webkit-overflow-scrolling: touch; border-top: 1px solid var(--rule); }
.chat > .here::-webkit-scrollbar { display: none; }
.chat > .composer:has(+ .here) { padding-bottom: 8px; }
body.ev-on .chat > .here, .kb-open .chat > .here { display: none; }
.here-p { flex: none; width: 64px; display: flex; flex-direction: column; align-items: center; gap: 2px; padding: 0 0 2px; border: 0;
  background: none; color: var(--c, var(--ink)); font: 700 12px/1.2 var(--font); cursor: pointer; -webkit-tap-highlight-color: transparent; }
.here-face { width: 50px; height: 50px; display: grid; place-items: center; }
.here-face .mface, .here-face img, .here-face svg { display: block; width: 100%; height: 100%; }
.here-face .mface.flat svg { padding: 4px; }
.here-n { max-width: 100%; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.here-p:active .here-face { scale: .92; }
.here-note { margin: 0; min-height: 66px; display: flex; align-items: center; padding: 0 8px; color: var(--ink-3); font-size: 14px; }

/* the section bar (member request, Ethan: "a horizontal bar across the screen like the paintings, drawings, prints
   bar on ethanorchard.com"): Lobby, Events, Arcade and Change as equal cells edge to edge (no Chat cell: the drischat name goes back), labels in heavy tracked
   Libre Franklin caps. All red (bug fix, Ethan): the closed cells are felt red with white labels, split by a faint white
   hairline, under a white rule that parts it from the letterboard; the open one is white paper inside a thick red frame
   with a red label. The frame is drawn by ::before because
   the hard buttons (buttons.css) strip every button's own border and shadow. Lobby and Arcade open under it (--tabs-y). */
.tabbar { --c: #1259d3; flex: none; display: flex; background: var(--c); border-top: 2px solid #fff; user-select: none; -webkit-user-select: none; }
.tabbar .tab { position: relative; flex: 1 1 0; min-width: 0; height: 44px; margin: 0; padding: 0 6px;
  display: flex; align-items: center; justify-content: center; gap: 6px; border: 0; background: var(--c); color: #fff;
  font: 800 15px/1 "Libre Franklin", "Helvetica Neue", Arial, sans-serif; letter-spacing: .1em; text-transform: uppercase;
  white-space: nowrap; overflow: hidden; cursor: pointer; -webkit-tap-highlight-color: transparent; text-rendering: geometricPrecision; }
/* no colour fade between open and closed (bug fix, Ethan: "sometimes in the Change tab the Chat button disappears"): on
   iPhone the label's inherited colour fade could stall and leave CHAT red on its red cell; the swap is instant now */
.tabbar .tab::before { content: ""; position: absolute; inset: 0; border: 6px solid var(--c); pointer-events: none; }
.tabbar .tab + .tab::after { content: ""; position: absolute; left: 0; top: 12px; bottom: 12px; width: 1px;
  background: rgba(255, 255, 255, .45); pointer-events: none; }
.tabbar .tab[aria-selected="true"]::after, .tabbar .tab[aria-selected="true"] + .tab::after { display: none; }
.tabbar .tab > span { padding-left: .1em; overflow: hidden; text-overflow: clip; }
.tabbar .tab[aria-selected="true"] { background: #fff; color: var(--c); }
.tabbar .tab:active { filter: brightness(.94); }
/* the hard-button rim still shows on press, but not as a standing ring on the open tab (its fill says it's open) */
#tabbar .tab[aria-selected="true"]:not(:active, [data-hit], :focus-visible) { outline-color: transparent !important; }
.tabbar .tab-n { display: inline-grid; place-items: center; min-width: 19px; height: 19px; padding: 0 4px; background: #fff; color: var(--c);
  font: 800 11px/1 "Libre Franklin", Arial, sans-serif; letter-spacing: 0; font-style: normal; }
.tabbar .tab[aria-selected="true"] .tab-n { background: var(--c); color: #fff; }
@media (max-width: 600px) {
  #askBtn { gap: 7px; }
  .tabbar .tab { font-size: 13px; letter-spacing: .07em; padding: 0 4px; gap: 4px; }
  .tabbar .tab::before { border-width: 5px; }
}
/* three cells on a phone: while a change runs, the dizzy Bii head takes the count's room so CHANGE never clips */
@media (max-width: 430px) { #askBtn.dizzy #kdot { display: none; } }
@media (max-width: 350px) { .tabbar .tab { font-size: 11.5px; letter-spacing: .04em; } #askBtn { gap: 5px; padding: 0 2px; } }

/* Lobby and Arcade open under the letterboard and the bar, at the chat's width; sheets (the change and menu,
   opened from the letterboard) still come up over them */
body.tabs-on .plaza, body.tabs-on .arc { top: calc(var(--app-top, 0px) + var(--tabs-y, 0px)); height: calc(var(--app-h) - var(--tabs-y, 0px)); max-width: 760px; margin: 0 auto; }
body.tabs-on .plaza .pz-top { padding-top: 10px; }
body.tabs-on .arc-top { padding-top: 8px; }
body.tabs-on.in-plaza .scrim, body.tabs-on.in-arcade .scrim, body.tabs-on.in-books .scrim, body.tabs-on.in-board .scrim { z-index: 31; }
body.tabs-on.in-plaza .sheet, body.tabs-on.in-arcade .sheet, body.tabs-on.in-books .sheet, body.tabs-on.in-board .sheet { z-index: 32; }
/* Change is a section too (bug fix, Ethan: "it shouldn't be pulled up over the rest of the UI; format it the same as the
   arcade and the events"): the Change sheet docks under the bar on plain white paper, square, no scrim or slide, and
   no Close (the other cells and CHAT are the way out). app.js openSheet adds .docked + body.ask-on. */
.sheet.docked { top: calc(var(--app-top, 0px) + var(--tabs-y, 0px)); bottom: auto; height: calc(var(--app-h) - var(--tabs-y, 0px)); max-height: none;
  z-index: 21; border-radius: 0; background: #fff; transform: none; transition: none; padding: 16px 14px calc(24px + var(--sab)); }
.kb-open .sheet.docked { border-radius: 0; padding-bottom: 16px; }
.sheet.docked .close { display: none; }
body.ask-on #jump { display: none; }

/* the chiron (bug fix, Ethan): the change in progress crawls right to left under the letterboard, like a news
   ticker. A white tab in board letters holds still on the left; the requester's words scroll past on deep ink. */
.chiron { flex: none; display: flex; align-items: stretch; height: 34px; background: #0b1a2e; color: #fff; overflow: hidden; border-top: 2px solid #fff; }
.chiron-tag { flex: none; display: flex; align-items: center; padding: 0 22px 0 18px; background: #fff; color: var(--felt); font: 600 16px/1 var(--board); letter-spacing: .14em; text-transform: uppercase; clip-path: polygon(0 0, 100% 0, calc(100% - 10px) 100%, 0 100%); }
.chiron-track { flex: 1; min-width: 0; overflow: hidden; display: flex; align-items: center; mask-image: linear-gradient(to right, transparent, #000 14px, #000 calc(100% - 14px), transparent); -webkit-mask-image: linear-gradient(to right, transparent, #000 14px, #000 calc(100% - 14px), transparent); }
.chiron-crawl { display: flex; flex: none; white-space: nowrap; animation: chiron-crawl var(--crawl-s, 20s) linear infinite; will-change: transform; }
.chiron-item { padding: 0 2.5em 0 1.2em; font: 700 15px/1 var(--font); }
.chiron-item::after { content: ""; display: inline-block; width: 7px; height: 7px; margin: 0 0 2px 2.5em; background: var(--felt); vertical-align: middle; }
@keyframes chiron-crawl { from { transform: translateX(0); } to { transform: translateX(-50%); } }
/* bug fix (Ethan: "the change should scroll across that bar, not stay still"): the crawl keeps moving with Reduce
   Motion on, like the spinning 🅱️; a still chiron reads as broken */
@media (prefers-reduced-motion: reduce) {
  .chiron-crawl { animation-duration: var(--crawl-s, 20s) !important; animation-iteration-count: infinite !important; }
}

/* DRISCHAT: the letterboard is the name in the big white condensed caps on felt, centred across the board. It's a button
   again (member request, henry): tapping it opens the room menu from any section; the Chat cell tapped again still does too. */
.chat > .top .logo-h { flex: 1; min-width: 0; display: flex; margin: 0; font: inherit; }
.chat > .top.board, .chat > .top.board:has(+ .tabbar) { padding: env(safe-area-inset-top) 0 0; gap: 0; }
.chat > .top .names { flex: 1; min-width: 0; margin: 0; padding: 12px 14px; border: 0; background: none;
  display: flex; align-items: center; justify-content: center; color: var(--letter); font: inherit; font-size: min(44px, calc((100vw - 28px) / 5.2));
  line-height: .9; letter-spacing: .1em; text-indent: .1em; text-transform: inherit; text-shadow: inherit; white-space: nowrap;
  font-weight: inherit; cursor: pointer; user-select: none; -webkit-user-select: none; -webkit-tap-highlight-color: transparent; }
.chat > .top .names:active { opacity: .7; }
/* Events lives in the menu now (member request, Ethan): its row carries the count of events you haven't answered */
.sheet .item .ev-due { display: inline-grid; place-items: center; min-width: 19px; height: 19px; padding: 0 4px; margin-left: 6px; vertical-align: 1px;
  background: #1259d3; color: #fff; font: 800 11px/1 "Libre Franklin", Arial, sans-serif; }

/* the change button: the section bar's last cell, just CHANGE (bug fix, Ethan: the pen is out). It opens the Change
   sheet and never stays selected. While a change is being made, the requester's Bii head appears before the label
   and loops the loop with spiral eyes (app.js renderCall). The count (waiting changes, or 1 for a reply you haven't
   read) is the bar's hard square after the label, like the other cells'. */
#askBtn .call-ico { position: relative; flex: none; display: none; place-items: center; width: 24px; height: 24px; margin: -2px 0; padding: 0; overflow: visible; }
#askBtn.dizzy .call-ico { display: grid; }
#askBtn .call-ico > * { grid-area: 1 / 1; }
#askBtn .dizzy-slot { position: relative; display: block; width: 24px; height: 24px; pointer-events: none; }
#askBtn .dizzy-slot:empty { display: none; }
.dz-loop { display: block; width: 100%; height: 100%; animation: dz-loop 1.5s linear infinite; }
/* the loop: the head travels round a small circle while turning over twice, like a coin rolling round a rim */
@keyframes dz-loop {
  from { transform: rotate(0turn) translateY(-3px) rotate(0turn); }
  to { transform: rotate(1turn) translateY(-3px) rotate(1turn); }
}
#askBtn .dizzy-slot .mface { display: block; width: 100%; height: 100%; filter: drop-shadow(0 1px 0 rgba(0, 25, 60, .35)); }
#askBtn .dizzy-slot .mface svg, #askBtn .dizzy-slot .mface img { display: block; width: 100%; height: 100%; }
#askBtn .dizzy-slot .mface.cutout { position: relative; border-radius: 50%; background: #fff; overflow: hidden; }
.dz-over { position: absolute; inset: 0; }
.dz-spin { transform-box: fill-box; transform-origin: center; animation: spin .7s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.banner { display: flex; align-items: center; gap: 12px; padding: 10px 14px 10px 18px; background: var(--note); color: var(--note-ink); font-size: 15px; }
.banner .txt { flex: 1; }
.banner .x { background: none; border: 0; color: inherit; opacity: .6; font-size: 15px; padding: 4px 6px; text-decoration: underline; }

.messages { flex: 1; overflow-y: auto; overflow-x: hidden; padding: 8px 14px 6px; -webkit-overflow-scrolling: touch; overscroll-behavior: contain; }
.older { text-align: center; color: var(--ink-3); font-size: 14px; padding: 10px 0; min-height: 1px; }
.day { margin: 18px 4px 6px; font-size: 14px; font-weight: 700; color: var(--ink-2); }

.msg { display: flex; flex-direction: column; align-items: flex-start; margin-top: 3px; }
.msg.first { margin-top: 14px; }
.msg .who { font-size: 14px; font-weight: 700; margin: 0 0 3px 2px; color: var(--c, var(--ink-2)); }
@media (prefers-color-scheme: dark) { .msg .who { color: color-mix(in oklab, var(--c, var(--ink-2)) 55%, white); } }
.msg .bubble { max-width: min(80%, 540px); padding: 8px 13px 9px; border-radius: 16px; background: var(--bubble); color: var(--bubble-ink); white-space: pre-wrap; overflow-wrap: anywhere; box-shadow: 0 0 0 1px var(--bubble-edge); }
.msg:not(.own).last .bubble { border-bottom-left-radius: 5px; }
.msg .bubble a { color: inherit; }
.msg .time { font-size: 12.5px; color: var(--ink-3); margin: 3px 4px 0; }
.msg.own { align-items: flex-end; }
.msg.own .bubble { background: var(--red); color: var(--red-ink); box-shadow: none; }
.msg.own.last .bubble { border-bottom-right-radius: 5px; }
.msg.pending .bubble { opacity: .55; }
.msg.failed .bubble { outline: 2px solid var(--danger); }
.msg.failed .time { color: var(--danger); }
/* blood speak: the swapped-in 🅱️ emoji */
/* Mood faces: the sender's Mii head beside every message, pulling the face that fits what they said */
.msg .line { display: flex; align-items: flex-end; gap: 6px; width: 100%; }
.msg.own .line { flex-direction: row-reverse; }
.msg .mface { flex: none; width: 40px; height: 40px; margin-bottom: -4px; }
.msg .mface img, .msg .mface svg { display: block; width: 100%; height: 100%; }
.msg .mface.flat svg { padding: 3px; }
/* Whole-figure cutouts (Ethan's Henceforth Prior): the drawing's own face, ink only; light ink on dark */
@media (prefers-color-scheme: dark) { .mface.cutout img { filter: invert(1); } }
.msg:not(.own) .who { margin-left: 48px; }
.msg:not(.own) .time { margin-left: 50px; }
.msg.own .time { margin-right: 50px; }
.msg .bubble .blood { font-style: normal; font-weight: 400; margin-right: .03em; }
.msg.own .bubble .blood { filter: drop-shadow(0 0 1px #fff); } /* the red 🅱️ keeps its edge on a red bubble */
.msg .bubble .grawlix { font-family: "Comic Sans MS", "Comic Neue", "Chalkboard SE", "Comic Sans", cursive; font-weight: 700; font-style: normal; letter-spacing: .02em; white-space: nowrap; }
.msg .bubble.big, .msg.own .bubble.big { font-size: 44px; line-height: 1.1; background: none; color: var(--ink); box-shadow: none; padding: 0 2px; }

/* Simlish invisible ink (member request): henry's bubbles show gibberish under a shimmering speckle
   veil; rubbing or holding the bubble (public/app.js) reveals the real text for a few seconds, like
   iMessage's invisible ink. Real and fake text share one grid cell so the crossfade doesn't reflow. */
/* German rule (member request): translated bubbles flip to the original on tap; Charlie's non-German
   messages get a rubber "Abgelehnt" stamp slammed on them before the text goes back to the composer. */
.msg .bubble.tr { cursor: pointer; -webkit-tap-highlight-color: transparent; }
.msg.rejected .bubble { position: relative; opacity: 1; animation: reject-fade .5s ease 1s forwards; }
.msg.rejected .bubble::after {
  content: attr(data-stamp); position: absolute; left: 50%; top: 50%; z-index: 1;
  padding: 1px 10px 2px; border: 3px solid var(--felt); border-radius: 4px; background: #fff; color: var(--felt);
  font: 600 22px/1.1 var(--board); letter-spacing: .08em; text-transform: uppercase; white-space: nowrap;
  transform: translate(-50%, -50%) rotate(-9deg); animation: stamp-slam .22s cubic-bezier(.2, 1.6, .5, 1) both;
}
.msg.rejected .time { color: var(--felt); font-weight: 700; }
@keyframes stamp-slam { from { opacity: 0; transform: translate(-50%, -50%) rotate(-16deg) scale(2.2); } }
@keyframes reject-fade { to { opacity: .25; transform: translateY(4px); } }
.msg .bubble.ink { display: inline-grid; touch-action: pan-y; -webkit-user-select: none; user-select: none; }
.msg .bubble.ink > span { grid-area: 1 / 1; }
.msg .bubble.ink .ink-fake, .msg .bubble.ink .ink-real { transition: opacity .35s ease; }
.msg .bubble.ink .ink-real { opacity: 0; }
.msg .bubble.ink.revealed .ink-fake { opacity: 0; }
.msg .bubble.ink.revealed .ink-real { opacity: 1; }
.msg .bubble.ink .ink-veil {
  border-radius: inherit; pointer-events: none; opacity: .5; transition: opacity .3s ease;
  background-image: radial-gradient(circle, color-mix(in oklab, var(--bubble-ink) 60%, transparent) 1px, transparent 1.5px);
  background-size: 7px 7px;
  animation: ink-shimmer 2.4s linear infinite;
}
.msg.own .bubble.ink .ink-veil { background-image: radial-gradient(circle, color-mix(in oklab, var(--red-ink) 65%, transparent) 1px, transparent 1.5px); }
.msg .bubble.ink.revealed .ink-veil { opacity: 0; }
@keyframes ink-shimmer { from { background-position: 0 0; } to { background-position: 14px -10px; } }

.sys { margin: 12px 4px; font-size: 14.5px; color: var(--ink-2); overflow-wrap: anywhere; }
.sys q { font-style: italic; color: var(--ink); quotes: "“" "”"; }
.gba-add { display: flex; align-items: center; gap: 12px; margin: 12px 4px; padding: 10px 12px; background: var(--paper); border: 1px solid var(--rule); border-radius: 10px; }
.gba-add .txt { flex: 1; min-width: 0; font-size: 15px; color: var(--ink-2); overflow-wrap: anywhere; }
.gba-add .txt b { color: var(--ink); }
.gba-add .btn { text-decoration: none; flex: none; }
.gba-mini { flex: none; position: relative; width: 46px; height: 29px; background: #4a4446; border-radius: 4px 4px 2px 2px; clip-path: polygon(0 12%, 8% 12%, 8% 0, 92% 0, 92% 12%, 100% 12%, 100% 100%, 0 100%); }
.gba-mini span { position: absolute; inset: 22% 9% 14%; background: #fff; border-radius: 2px; font: 600 10px/1 var(--board); letter-spacing: .08em; color: var(--felt); padding: 3px 4px; }

/* replies from Lobby: a note from the front desk */
.note { margin: 14px 0; max-width: min(88%, 560px); background: var(--note); color: var(--note-ink); border-radius: 4px; padding: 10px 14px 11px; box-shadow: 0 0 0 1px #1259d326; }
.note .tag { display: flex; align-items: baseline; gap: 8px; margin-bottom: 3px; }
.note .tag b { font: 600 14px/1 var(--board); letter-spacing: .14em; text-transform: uppercase; background: var(--felt); color: var(--letter); padding: 3px 6px 2px; border-radius: 2px; }
.note .tag span { font-size: 13.5px; opacity: .75; }
.note .body { white-space: pre-wrap; overflow-wrap: anywhere; }
/* "Bugs wanted" poster (member request: advertise that bug fixes earn stars): a pale note under a strip of felt letter tiles. */
.bounty { margin: 16px 0; max-width: min(88%, 420px); background: var(--note); color: var(--note-ink); border-radius: 4px; padding: 0 0 12px; box-shadow: 0 0 0 1px #1259d326; overflow: hidden; }
.bounty-board { display: flex; flex-wrap: wrap; gap: 3px; padding: 9px 12px; background: var(--felt); }
.bounty-board i { font: 600 17px/1 var(--board); font-style: normal; color: var(--letter); width: 15px; padding: 4px 0 3px; text-align: center; background: #ffffff14; border-radius: 2px; }
.bounty-board i.sp { background: none; width: 8px; }
.bounty-body { display: flex; gap: 12px; align-items: flex-start; padding: 12px 14px 10px; }
.bounty-body .t1 { font-weight: 700; }
.bounty-body .sub2 { font-size: 14.5px; opacity: .8; margin-top: 2px; }
.bounty-reward { position: relative; flex: none; width: 46px; height: 46px; transform: rotate(-8deg); }
.bounty-reward svg { width: 100%; height: 100%; fill: var(--red); stroke: var(--red); stroke-width: 1.5; stroke-linejoin: round; }
.bounty-reward b { position: absolute; inset: 0; display: grid; place-items: center; padding-top: 3px; font: 600 15px/1 var(--board); color: var(--red-ink); }
.bounty > .btn { margin-left: 72px; }
.bounty-paid { display: flex; align-items: center; gap: 8px; }
.bounty-paid .grow { flex: 1; min-width: 0; }
.bounty-paid b { color: var(--ink); }
.bp-star { flex: none; width: 18px; height: 18px; }
.bp-star svg { width: 100%; height: 100%; fill: var(--red); }
.bp-go { flex: none; border: 1.5px solid var(--rule); background: var(--paper); color: var(--red); font-weight: 700; font-size: 14px; border-radius: 10px; padding: 6px 10px; min-height: 36px; }
.bug-chip { display: inline-flex; align-items: center; gap: 7px; margin: 0 0 10px; border: 1.5px dashed var(--red); background: transparent; color: var(--red); font-weight: 700; font-size: 15px; border-radius: 10px; padding: 8px 12px; min-height: 40px; }
.bug-chip span { display: block; width: 18px; height: 18px; }
.bug-chip svg { width: 100%; height: 100%; fill: var(--red); }
.bug-chip[hidden] { display: none; }
.note.failed { background: transparent; border: 1.5px dashed var(--rule); color: var(--ink); box-shadow: none; }

.jump { position: absolute; right: 14px; bottom: calc(80px + var(--sab)); border: 0; background: var(--ink); color: var(--wall); border-radius: 99px; padding: 8px 14px; font-size: 14px; font-weight: 700; z-index: 4; }
.typing { height: 20px; padding: 0 18px; font-size: 13.5px; color: var(--ink-3); font-style: italic; }

.composer { display: flex; align-items: flex-end; gap: 8px; padding: 8px 12px calc(10px + var(--sab)); background: transparent; }
.composer textarea { flex: 1; resize: none; max-height: 150px; min-height: 44px; padding: 10px 15px; border-radius: 22px; border: 1.5px solid var(--rule); background: var(--paper); outline: none; line-height: 1.35; }
.composer textarea:focus { border-color: var(--red); }
.send { flex: none; width: 44px; height: 44px; border-radius: 50%; border: 0; background: var(--red); color: var(--red-ink); display: grid; place-items: center; }
.send svg { width: 20px; height: 20px; }

/* ---------- sheets ---------- */
.scrim { position: fixed; inset: 0; background: #0b1a2e80; z-index: 20; opacity: 0; transition: opacity .2s; }
.scrim.open { opacity: 1; }
.sheet { position: fixed; left: 0; right: 0; bottom: 0; z-index: 21; max-width: 760px; margin: 0 auto; max-height: calc(var(--app-h) - 28px - env(safe-area-inset-top)); overflow-y: auto; background: var(--wall-2); border-radius: 20px 20px 0 0; padding: 18px 18px calc(24px + var(--sab)); transform: translateY(100%); transition: transform .26s cubic-bezier(.2, .8, .2, 1); }
.sheet.open { transform: none; }
.sheet-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; }
.sheet h2 { margin: 0; font-size: 22px; line-height: 1.2; }
.sheet h3 { margin: 26px 0 8px; font-size: 16px; }
.sheet .lede { color: var(--ink-2); margin: 6px 0 16px; max-width: 60ch; }
.close { flex: none; background: none; border: 0; color: var(--ink-2); font-size: 15px; font-weight: 700; padding: 6px 2px; text-decoration: underline; }
.muted { color: var(--ink-3); font-size: 15px; }

.ask textarea { width: 100%; min-height: 110px; resize: vertical; padding: 12px 14px; border-radius: 10px; border: 1.5px solid var(--rule); background: var(--paper); outline: none; }
.ask textarea:focus { border-color: var(--red); }
.ask .row { display: flex; justify-content: space-between; align-items: center; gap: 12px; margin-top: 10px; }
.ask .row .muted { font-size: 14px; }

/* Stars (member request): a change costs one. Lit stars are the felt red, spent ones are just an outline. */
.stars-line { display: flex; align-items: center; gap: 14px; margin: 2px 0 14px; }
.stars-line .t1 { font-weight: 700; }
.stars-line .sub2 { color: var(--ink-3); font-size: 14px; }
.stars-row { display: flex; gap: 3px; flex: none; }
.stars-row i { display: block; width: 26px; height: 26px; }
.stars-row svg { width: 100%; height: 100%; fill: none; stroke: var(--rule); stroke-width: 2; stroke-linejoin: round; }
.stars-row i.on svg { fill: var(--red); stroke: var(--red); }
/* Star rules (member request, henry): the prices on a felt letterboard, one row per thing, word tiles on the left,
   a dotted leader, the amount in tiles with a star on the right. Earning rows show their amount on white tiles. */
.linkish { border: 0; background: none; padding: 0; color: var(--red); font: inherit; font-weight: 700; text-decoration: underline; text-underline-offset: 2px; }
.sr-board { display: grid; gap: 9px; padding: 14px 14px 15px; border-radius: 4px; background-color: var(--felt);
  background-image: repeating-linear-gradient(to bottom, transparent 0 7px, var(--felt-groove) 7px 9px); }
.sr-row { display: flex; align-items: center; gap: 8px; min-width: 0; }
.sr-what, .sr-k { display: flex; gap: 2px; flex-wrap: nowrap; }
.sr-what { min-width: 0; overflow: hidden; }
.sr-board i { font: 600 18px/1 var(--board); font-style: normal; color: var(--letter); width: 13px; padding: 5px 0 4px; text-align: center;
  background: #ffffff14; border-radius: 2px; text-shadow: 0 1px 0 #0008; }
.sr-board i.sp { background: none; width: 7px; }
.sr-dots { flex: 1; min-width: 12px; height: 3px; background: radial-gradient(circle, #ffffff70 1.2px, transparent 1.6px) 0 50% / 7px 3px repeat-x; }
.sr-k { flex: none; align-items: center; }
.sr-k.plus i { background: #fff; color: var(--felt); text-shadow: none; }
.sr-k b { display: block; width: 20px; height: 20px; margin-left: 4px; }
.sr-k b svg { width: 100%; height: 100%; fill: var(--letter); }
.sr-rules { margin: 18px 0 0; padding: 0; list-style: none; max-width: 60ch; }
.sr-rules li { position: relative; padding: 9px 0 9px 22px; border-top: 1px solid var(--rule); }
.sr-rules li:first-child { border-top: 0; }
.sr-rules li.sr-free { margin-bottom: 6px; padding: 11px 14px 11px 36px; border-radius: 4px; background: var(--note); color: var(--ink); font-weight: 700; }
.sr-rules li.sr-free + li { border-top: 0; }
.sr-rules li.sr-free::before { left: 13px; top: 15px; width: 13px; height: 13px; background: var(--felt);
  clip-path: polygon(50% 0, 62% 36%, 100% 38%, 69% 60%, 80% 98%, 50% 76%, 20% 98%, 31% 60%, 0 38%, 38% 36%); }
.sr-rules li::before { content: ""; position: absolute; left: 3px; top: 17px; width: 7px; height: 7px; background: var(--felt); }
.sr-sub { margin: 0 0 12px; color: var(--ink-2); font-size: 15px; }
.sr-badges { display: grid; grid-template-columns: repeat(4, 1fr); gap: 12px 8px; margin: 0; padding: 0; list-style: none; }
.sr-badges li { display: flex; flex-direction: column; align-items: center; gap: 5px; font-size: 14px; color: var(--ink-3); }
.sr-medal { display: grid; place-items: center; width: 48px; height: 48px; clip-path: polygon(30% 0, 70% 0, 100% 30%, 100% 70%, 70% 100%, 30% 100%, 0 70%, 0 30%);
  background: var(--rule); color: var(--ink-3); font: 600 22px/1 var(--board); }
.sr-badges li.on { color: var(--ink); font-weight: 700; }
.sr-badges li.on .sr-medal { background: var(--felt); }
.sr-medal svg { width: 26px; height: 26px; fill: var(--letter); }
.sr-sr { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); }
.sr-go { display: flex; flex-wrap: wrap; gap: 10px; margin-top: 22px; }
.sr-go .btn { text-decoration: none; }

.jobs { list-style: none; margin: 0; padding: 0; }
.job { padding: 12px 0; border-top: 1px solid var(--rule); }
.job:first-child { border-top: 0; }
.job .head { display: flex; align-items: baseline; gap: 8px; font-size: 15px; }
.job .head b { font-weight: 700; }
.job .head .status { color: var(--ink-2); }
.job .head .sp { flex: 1; }
.job .prompt { margin-top: 3px; white-space: pre-wrap; overflow-wrap: anywhere; }
.job .act { margin-top: 5px; font-size: 14.5px; color: var(--ink-2); font-style: italic; }
.job .reply { margin-top: 8px; padding: 7px 11px; background: var(--note); color: var(--note-ink); border-radius: 4px; font-size: 15.5px; }
.job.running .status { color: var(--ink); font-weight: 700; }
.job.running .status::before { content: ""; display: inline-block; width: 12px; height: 12px; margin-right: 6px; vertical-align: -1px; border-radius: 50%; border: 2px solid var(--rule); border-top-color: var(--ink); animation: spin .9s linear infinite; }
.job.failed .status { color: var(--danger); }

.working { display: flex; gap: 16px; align-items: center; padding: 18px 16px; margin-top: 4px; background: var(--felt); color: var(--letter); border-radius: 12px; }
.working .spinner { flex: none; width: 44px; height: 44px; border-radius: 50%; border: 4px solid #ffffff33; border-top-color: var(--signal); border-right-color: var(--signal); animation: spin 1s linear infinite; }
.working .t1 { font-weight: 700; font-size: 18px; }
.working .t2 { font-size: 15px; opacity: .8; margin-top: 2px; }
.working .t3 { font-size: 14.5px; opacity: .7; margin-top: 4px; font-style: italic; min-height: 1.3em; }
.working .actions { display: flex; gap: 14px; margin-top: 10px; }
.working .actions button { background: none; border: 0; color: var(--letter); padding: 0; font-size: 15px; text-decoration: underline; opacity: .85; }

.reply-card { margin-top: 4px; background: var(--note); color: var(--note-ink); border-radius: 4px; padding: 16px 18px 18px; }
.reply-card .t1 { font-weight: 700; font-size: 18px; margin-bottom: 6px; }
.reply-card .body { white-space: pre-wrap; margin-bottom: 14px; }
.reply-card.failed { background: var(--paper); color: var(--ink); border: 1.5px dashed var(--rule); }

.group { background: var(--paper); border-radius: 12px; overflow: hidden; }
.item { display: flex; align-items: center; gap: 12px; width: 100%; padding: 13px 16px; border: 0; border-top: 1px solid var(--rule); background: none; text-align: left; text-decoration: none; color: var(--ink); }
.item:first-child { border-top: 0; }
.item .sub2 { color: var(--ink-2); font-size: 14.5px; }
.item .grow { flex: 1; min-width: 0; }
.people { display: flex; flex-wrap: wrap; gap: 6px 16px; }
.person { display: flex; align-items: center; gap: 7px; font-weight: 700; color: var(--c, var(--ink)); }
@media (prefers-color-scheme: dark) { .person { color: color-mix(in oklab, var(--c, var(--ink)) 55%, white); } }
.person .d { width: 9px; height: 9px; border-radius: 50%; border: 1.5px solid var(--ink-3); }
.person .d.on { background: var(--ok); border-color: var(--ok); }
.invite-box { display: flex; gap: 8px; padding: 0 16px 14px; }
.invite-box input { flex: 1; min-width: 0; padding: 9px 11px; border-radius: 8px; border: 1.5px solid var(--rule); background: var(--wall-2); font-size: 15px; }

.toast { position: fixed; left: 50%; top: calc(14px + env(safe-area-inset-top)); transform: translate(-50%, -160%); background: var(--ink); color: var(--wall); padding: 10px 16px; border-radius: 10px; z-index: 40; transition: transform .3s; max-width: calc(100% - 32px); font-size: 15px; font-weight: 700; }
.toast.show { transform: translate(-50%, 0); }

/* keyboard open (iOS): the sheet rides above the keyboard, and home-bar padding goes away */
.sheet { bottom: var(--kb, 0px); overscroll-behavior: contain; }
.kb-open .composer { padding-bottom: 8px; }
.kb-open .sheet { padding-bottom: 16px; border-radius: 20px; }

@media (min-width: 761px) { .chat { box-shadow: 0 0 0 1px var(--rule); } }
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after { animation-duration: 0s !important; animation-iteration-count: 1 !important; transition-duration: 0s !important; }
  .dz-loop { animation-duration: 1.6s !important; animation-iteration-count: infinite !important; }
  .dz-spin { animation-duration: .9s !important; animation-iteration-count: infinite !important; }
}
.confetti { position: fixed; inset: 0; width: 100%; height: 100%; pointer-events: none; z-index: 50; }

/* @mentions (member request): a member's name in a bubble is bold in their name color; your own name (or @everyone)
   is a small red felt tile, and the whole bubble gets a red edge so you spot it scrolling past. */
.msg .bubble .mention { font-weight: 700; color: var(--mc, inherit); white-space: nowrap; }
.msg .bubble .mention.me { color: #fff; background: var(--felt); border-radius: 4px; padding: 0 .22em; margin: 0 -.05em; box-decoration-break: clone; -webkit-box-decoration-break: clone; }
.msg.own .bubble .mention { color: inherit; text-decoration: underline 2px; text-underline-offset: .18em; }
.msg.own .bubble .mention.me { color: var(--felt); background: #fff; text-decoration: none; }
.msg.at-me:not(.own) .bubble:not(.big) { box-shadow: 0 0 0 2px var(--red); }

/* The picker: members float over the bottom of the chat while you type "@". Best match first (who's here before who's away), Bii face and name. */
/* Bug fix (burrattababy: "can only @ people who are active"): the row put who's here first and ran everyone else off
   the right edge, so it wraps now: every member shows, on a paper backing so the chat doesn't peek between chips. */
.mention-bar { position: absolute; left: 0; right: 0; bottom: 100%; display: flex; flex-wrap: wrap; gap: 6px; padding: 6px 12px; max-height: 164px; overflow-y: auto; overscroll-behavior: contain; scrollbar-width: none; -webkit-overflow-scrolling: touch; background: var(--paper); border-top: 1px solid var(--rule); }
.mention-bar::-webkit-scrollbar { display: none; }
.mention-bar[hidden] { display: none; }
.mpick { flex: none; display: flex; align-items: center; gap: 7px; min-height: 44px; padding: 3px 14px 3px 4px; border: 0; background: var(--paper); color: var(--c, var(--ink)); font: inherit; font-size: 16px; font-weight: 700; -webkit-tap-highlight-color: transparent; }
.mpick:first-child { background: var(--felt); color: #fff; }
.mpick .mp-face { width: 36px; height: 36px; display: grid; place-items: center; }
.mpick .mp-face .mface, .mpick .mp-face img, .mpick .mp-face svg { display: block; width: 100%; height: 100%; }
.mpick.all .mp-face { font-size: 24px; line-height: 1; color: var(--red); }
.mpick.all:first-child .mp-face { color: #fff; }
.mpick:not(.here):not(.all) .mp-face { opacity: .6; } /* away: their Bii is asleep */
@media (prefers-color-scheme: dark) { .mpick:not(:first-child) { color: color-mix(in oklab, var(--c, var(--ink)) 55%, white); } }

/* ---------- replies (member request, Ethan: "swiping right on someone's message allows you to reply to it") ----------
   Swipe a message right: it follows the finger and a hard square reply tile slides in from the edge, pale while you
   pull, felt red once letting go would start the reply. A reply carries the quote as a pale slip in the original
   author's colour, tucked behind the top of its bubble: their Bii head, name and one line of their words (or a photo
   thumbnail). The composer shows the same slip above the field while you write, with an x to drop it. */
#list .msg[data-id] { touch-action: pan-y; }
.msg.swiping, .msg.swipe-back { position: relative; transform: translateX(var(--sx, 0px)); }
.msg.swipe-back { transition: transform .24s cubic-bezier(.25, 1.2, .4, 1); }
.swipe-arrow {
  position: absolute; left: -46px; top: 50%; width: 34px; height: 34px; margin-top: -17px; display: grid; place-items: center;
  background: var(--note); color: var(--red); opacity: var(--sk, 0); transform: scale(calc(.5 + .5 * var(--sk, 0)));
  transition: background-color .12s, color .12s; pointer-events: none;
}
.swipe-arrow svg { width: 19px; height: 19px; }
.swipe-arrow.armed { background: var(--felt); color: #fff; }
@media (prefers-reduced-motion: reduce) { .msg.swipe-back { transition: none; } }

.msg.has-rq { margin-top: 12px; }
.msg .line { position: relative; z-index: 1; }
.rq, .rb-q {
  position: relative; display: flex; align-items: center; gap: 7px; min-width: 0; border: 0; text-align: left; cursor: pointer;
  background: color-mix(in oklab, var(--rc, var(--ink-3)) 11%, #fff); color: var(--ink-2); font: 14px/1.3 var(--font);
  -webkit-tap-highlight-color: transparent;
}
.rq::before, .rb-q::before { content: ""; position: absolute; left: 0; top: 0; bottom: 0; width: 3px; background: var(--rc, var(--ink-3)); }
.msg .rq { max-width: min(76%, 440px); margin: 0 0 -8px 46px; padding: 5px 10px 13px 9px; }
.msg .rq::before { bottom: 8px; }
.msg.own .rq { margin: 0 46px -8px 0; }
.msg.has-rq .who + .rq { margin-top: 1px; }
.rq-face { flex: none; width: 24px; height: 24px; }
.rq-face .mface { display: block; width: 100%; height: 100%; margin: 0; }
.rq-face .mface img, .rq-face .mface svg { display: block; width: 100%; height: 100%; }
.rq-txt { display: flex; flex-direction: column; min-width: 0; }
.rq-txt b { font-size: 13px; font-weight: 700; color: color-mix(in oklab, var(--rc, var(--ink-2)) 80%, #000); }
.rq-txt > span:last-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.rq-ph { flex: none; width: 34px; height: 34px; object-fit: cover; margin-left: auto; }
.msg.rq-ring .bubble, .msg.rq-ring .ph-bub { animation: rq-ring 1.7s ease both; }
@keyframes rq-ring { 0%, 55% { outline: 3px solid var(--ink); outline-offset: 3px; } 100% { outline: 3px solid transparent; outline-offset: 3px; } }

.composer.replying { flex-wrap: wrap; row-gap: 6px; }
.reply-bar { order: -1; flex: 0 0 100%; display: flex; min-width: 0; }
.reply-bar[hidden] { display: none; }
.rb-q { flex: 1; padding: 6px 10px 6px 12px; min-height: 44px; }
.rb-q .rq-face { width: 30px; height: 30px; }
.rb-to { font-size: 13px; }
.rb-to b { color: color-mix(in oklab, var(--rc, var(--ink-2)) 80%, #000); }
.rb-x { flex: none; width: 44px; border: 0; display: grid; place-items: center; background: color-mix(in oklab, var(--rc, var(--ink-3)) 11%, #fff); color: var(--ink-2); cursor: pointer; }
.rb-x svg { width: 18px; height: 18px; }
