/* chat — the DMG surface (TAD Decision 14, amended 2026-09-04).
   ==================================================================
   Originally adapted from the editor lane's `gb-console` primitive, which D14
   named as the reference implementation. The DEVICE SHELL is now gone — the
   grey body, the rounded corner, the bezel and the wordmark. The lit screen
   is the page.

   Why: the shell was ornament that cost layout. It drew a handheld around a
   conversation, and the drawing consumed the margin on every side plus a
   44px foot for a label — on a phone, where the log is already competing with
   a keyboard, that is the scarcest space on the surface. The reuse D14 called
   for was a look, not a chassis.

   The frame went the same way one pass later, and for a reason worth keeping:
   a hard black edge a few pixels from the actual screen edge does not read as
   a bezel, it reads as a page that failed to fill. Ornament that describes
   hardware needs the hardware around it to be legible as ornament at all.
   What carries the theme with none of it left is the four-shade palette and
   the pixel face — which was always the part doing the work.

   Palette is the four DMG shades, hard-coded rather than tokenised, for the
   same reason the primitive hard-codes them: this must look identical for
   every viewer regardless of any host theme. It is now only those four — the
   bezel black and shell grey went with the hardware they described, and a
   token nothing reads is a colour the next person assumes is in the design.

   TEXT SIZES ARE ALL 16px, AND THAT IS NOT A STYLE CHOICE. The face's Latin
   glyphs sit on an 8-cells-per-em grid (measured: coordinate GCD 512 against
   4096 unitsPerEm), so only multiples of 8px land every cell on a whole pixel.
   The earlier mix of 11/13/14/15px put stems on half-pixels and rendered the
   face soft — which reads as "the font is bad" rather than "the size is
   wrong". If you need a smaller size here, 8px is the only other crisp one.
   The italic that used to sit on the console label is gone for the same class
   of reason: the face has one upright weight, so italic was synthetic slant.

   The same grid rule governs the ICONS. They are drawn on a 12-unit viewBox
   and rendered at 24px so every unit is exactly two device pixels; an
   in-between size puts their edges on halves and they go soft in exactly the
   way half-pixel text does. */

:root {
  --dmg-darkest: #0f380f;
  --dmg-dark:    #306230;
  --dmg-light:   #8bac0f;
  --dmg-lightest:#9bbc0f;

  /* FONT (TAD Decision 11). Pokemon Classic by TheLouster115, CC BY-SA 3.0 —
     see pokemon_classic.woff2.NOTICE.md. D11 asked for "a verified
     redistributable license"; this face closes that by VERIFICATION, reading
     designer and license out of the font's own name table. It replaced
     game_boy_1989.woff, which was closed by operator acceptance despite the
     file asserting a Nintendo copyright with empty license fields.
     The fallbacks still matter: one weight, no italic, so any synthetic
     styling is the browser's invention. The face is PROPORTIONAL — the
     monospace entries below are a legibility fallback, not a metric match. */
  --chat-font: 'Pokemon Classic', ui-monospace, SFMono-Regular,
               'DejaVu Sans Mono', Menlo, Consolas, monospace;
}

@font-face {
  font-family: 'Pokemon Classic';
  font-style: normal;
  font-weight: normal;
  /* local() first so a viewer who already has the face installed spends no
     bytes; the vendored copy is the fallback. Both spellings are listed
     because a system install may register the full name. */
  src: local('Pokemon Classic'),
       local('Pokemon Classic Regular'),
       url('/pokemon_classic.woff2') format('woff2');
  /* swap, not block: a chat that shows nothing until a font arrives is worse
     than one that reflows once. */
  font-display: swap;
}

* { box-sizing: border-box; }

body {
  margin: 0;
  /* The lit shade, not a darker ground behind it. On a phone this is what
     overscroll rubber-banding exposes at the top and bottom of a swipe, so
     anything else here shows as a band of another colour flashing past the
     edge of a surface that is supposed to be the whole screen. */
  background: var(--dmg-lightest);
  font-family: var(--chat-font);
  /* Pixel faces have no italic or weight range; asking for either gets a
     synthesised slant the face was never drawn for. */
  font-synthesis: none;
}

/* -- the screen -------------------------------------------------------- */

.chat {
  position: relative;
  display: flex;
  flex-direction: column;
  /* 100dvh, not 100vh: on mobile browsers the URL bar makes 100vh taller than
     what is actually visible, so a full-height surface gets its bottom edge —
     the composer — pushed under the chrome. */
  height: 100dvh;
  /* No frame either. The black border was the last of the device, and at
     phone width it read as a letterbox rather than a bezel — a hard edge that
     close to the screen edge looks like the page failed to fill, not like
     hardware. One unbroken field of the lit shade instead. */
  background: var(--dmg-lightest);
  color: var(--dmg-darkest);
  /* pan-y, not none: the surface holds a scrollable log and a composer that
     the phone keyboard can push below the fold. */
  touch-action: pan-y;
}

.chat__panel {
  flex: 1 1 auto;
  min-height: 0;          /* let the scrollable log shrink; see .chat__log */
  display: flex;
  flex-direction: column;
  gap: 10px;
  /* env() insets rather than a flat gutter: edge to edge means the content
     now reaches the notch and the home indicator, which a fixed padding
     would happily put text underneath. The 12px is the floor on a device
     that reports no inset. */
  padding: calc(12px + env(safe-area-inset-top))
           calc(12px + env(safe-area-inset-right))
           calc(12px + env(safe-area-inset-bottom))
           calc(12px + env(safe-area-inset-left));
}
.chat__panel[hidden] { display: none; }

/* Connecting / unavailable, over the whole screen. Opaque and full-bleed, so
   it is only correct while there is nothing behind it to cover — once
   messages are on screen the network notice goes in the bar instead. */
.chat__message {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 12px;
  text-align: center;
  font-size: 16px;
  line-height: 1.5;
  color: var(--dmg-darkest);
  background: var(--dmg-lightest);
}
.chat__message--error { color: #fff; background: #870a4c; }
.chat__message[hidden] { display: none; }

/* -- chat surface ------------------------------------------------------ */

.chat__bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding-bottom: 8px;
  border-bottom: 2px solid var(--dmg-dark);
  font-size: 16px;
}

/* min-width:0 + the ellipsis below: a long display name must yield to the
   gear rather than push it off the bar. */
.chat__who {
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.chat__name {
  /* Both halves are needed. The ellipsis is what the reader sees; min-width:0
     is what lets the span shrink far enough to need one, since a flex item
     defaults to a content-sized floor and would otherwise just push the gear
     off the bar. */
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* -- settings ---------------------------------------------------------- */

.chat__settings { position: relative; flex: 0 0 auto; }

/* Square, so the gear sits in a real target rather than a text-sized sliver.
   32px clears the 24px minimum a thumb needs without crowding a 16px bar. */
.chat__icon {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 0;
  color: var(--dmg-darkest);
  background: none;
  /* Transparent rather than absent, so showing the border on hover does not
     resize the button and shift the bar. */
  border: 2px solid transparent;
  border-radius: 3px;
  cursor: pointer;
}

.chat__icon:hover,
.chat__icon[aria-expanded="true"] { border-color: var(--dmg-dark); }

.chat__gear {
  display: block;
  width: 24px;
  height: 24px;
  fill: currentColor;
  /* The gear is axis-aligned rectangles on a 12-unit grid at exactly 2x, so
     antialiasing has nothing to smooth and would only blur the edges. */
  shape-rendering: crispEdges;
}

.chat__menu {
  position: absolute;
  top: calc(100% + 6px);
  right: 0;
  z-index: 2;
  /* Sized by its longest item rather than capped: these are full sentences
     ("Update Display Name"), and a wrapped menu label reads as two entries. */
  min-width: 140px;
  display: flex;
  flex-direction: column;
  padding: 4px;
  background: var(--dmg-lightest);
  border: 2px solid var(--dmg-darkest);
  border-radius: 3px;
  /* Hard offset shadow, no blur: a soft drop shadow is the one thing on this
     surface that could not exist on the hardware it is imitating. */
  box-shadow: 4px 4px 0 var(--dmg-dark);
}
.chat__menu[hidden] { display: none; }

.chat__menu-item {
  padding: 8px 10px;
  white-space: nowrap;
  font: inherit;
  font-size: 16px;
  text-align: left;
  color: var(--dmg-darkest);
  background: none;
  border: 0;
  border-radius: 2px;
  cursor: pointer;
}

/* The one entry here that cannot be undone, set apart from the routine one it
   sits beside. Colour is not available to say so — the palette is four greens
   by D14 — and a rule is the honest signal anyway: this item is not the same
   KIND of thing as the one above it, which is what the reader needs to know
   before their finger is already moving. */
.chat__menu-item--grave {
  margin-top: 4px;
  padding-top: 10px;
  border-top: 2px solid var(--dmg-dark);
  border-radius: 0;
}

.chat__menu-item:hover,
.chat__menu-item:focus-visible {
  color: var(--dmg-lightest);
  background: var(--dmg-darkest);
}

/* The colour is the disambiguator when two members share a name (D13). It is
   never the ONLY carrier of that distinction — the swatch sits beside the
   name rather than tinting it, so the pairing survives a monochrome display
   or a colour-blind viewer who cannot separate two of the eight hues. */
.chat__swatch {
  width: 10px;
  height: 10px;
  flex: 0 0 auto;
  border: 1px solid var(--dmg-darkest);
}

/* Reconnect indicator. Lives in the bar rather than the screen overlay so a
   blip never paints over the conversation — the overlay is opaque and covers
   the whole panel, which is right only when there is nothing behind it. */
.chat__net { font-size: 16px; color: var(--dmg-dark); }
.chat__net[hidden] { display: none; }

.chat__log {
  flex: 1 1 auto;
  overflow-y: auto;
  /* min-height:0 is load-bearing, not tidying. A flex item's default
     min-height is auto, which refuses to shrink below its content — so
     overflow-y would never engage and the log would push the composer off
     the bottom instead of scrolling. The 46vh cap this replaces was a
     workaround for exactly that, and it left dead space whenever the shell
     was taller or shorter than the guess. */
  min-height: 0;
  margin: 0;
  padding: 0;
  list-style: none;
  font-size: 16px;
  line-height: 1.5;
  overflow-wrap: anywhere;   /* an unbroken 4000-char body must not widen the device */
}

.chat__msg { padding: 4px 0; }
.chat__msg + .chat__msg { border-top: 1px solid rgba(48, 98, 48, 0.25); }

/* Author line and its delete control share a row, with the control pushed to
   the far edge. Previously the button sat inline right after the name, where
   it inherited none of the name's alignment and landed above the baseline at
   a different height per message — visibly ragged down the log. A flex row
   gives it one column to live in and one vertical centre to share. */
.chat__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}

.chat__author {
  display: inline-flex;
  align-items: center;
  gap: 5px;
  min-width: 0;
  font-weight: bold;
}

.chat__body { display: block; white-space: pre-wrap; }   /* D11: plain text, newlines kept */

/* Delete control, on the caller's own messages only. Square like the gear so
   the two read as the same kind of thing, and quiet until touched: it appears
   on every one of your own messages, so a drawn box on each was noise down
   the whole log. */
.chat__delete {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  padding: 0;
  font: inherit;
  font-size: 16px;
  line-height: 1;
  color: var(--dmg-dark);
  background: none;
  border: 2px solid transparent;
  border-radius: 3px;
  cursor: pointer;
}

.chat__delete:hover:not(:disabled),
.chat__delete:focus-visible {
  color: var(--dmg-lightest);
  background: var(--dmg-darkest);
  border-color: var(--dmg-darkest);
}
.chat__delete:disabled { opacity: 0.4; cursor: default; }

/* An optimistic post, not yet confirmed by a poll. Dimmed rather than
   removed-and-re-added so the message does not visibly flicker when the
   server's copy arrives. */
.chat__msg--pending { opacity: 0.55; }

/* An unconfirmed message has no id yet, so there is nothing to delete. Hidden
   rather than merely disabled: the control appears the moment the node is
   promoted, and a button that is present but inert invites the click that
   does nothing. */
.chat__msg--pending .chat__delete { visibility: hidden; }

.chat__composer { display: flex; gap: 8px; align-items: flex-end; }

.chat__composer textarea {
  flex: 1 1 auto;
  resize: none;
  padding: 6px;
  font: inherit;
  color: var(--dmg-darkest);
  background: var(--dmg-light);
  border: 2px solid var(--dmg-dark);
  border-radius: 3px;
}
/* The default placeholder is the browser's grey at reduced opacity, which on
   this mid-green ground measured 2.8:1 — legible as a shape, not as words.
   The darkest shade takes it to 5:1. `opacity: 1` is required: Firefox dims
   placeholders independently of colour and would undo it. */
.chat__composer textarea::placeholder {
  color: var(--dmg-darkest);
  opacity: 1;
}

.chat__composer textarea:focus-visible,
.chat__icon:focus-visible,
.chat__btn:focus-visible { outline: 2px solid var(--dmg-darkest); outline-offset: 2px; }

.chat__btn {
  flex: 0 0 auto;
  padding: 6px 10px;
  font: inherit;
  color: var(--dmg-lightest);
  background: var(--dmg-darkest);
  border: 2px solid var(--dmg-darkest);
  border-radius: 3px;
  cursor: pointer;
}
.chat__btn:disabled { opacity: 0.5; cursor: default; }

/* Visually hidden but reachable by a screen reader. */
.chat__sr {
  position: absolute;
  width: 1px; height: 1px;
  padding: 0; margin: -1px;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
