/* ==========================================================================
   Community — SAMPLE
   Chat, presence, profiles, discovery. Not wired into the site.

   Only tokens that exist in tokens.css. --s-9, --s-13 and --s-14 are NOT in
   the scale, and an undefined custom property invalidates the whole
   declaration at computed-value time — silently.
   ========================================================================== */

.page-cmx, .page-cmp {
  --cmx-max:   1500px;
  --cmx-hair:  rgb(255 255 255 / .09);
  --cmx-pane:  rgb(255 255 255 / .04);
  --cmx-pane2: rgb(255 255 255 / .07);
  --cmx-glass: blur(16px) saturate(1.3);
  --cmx-rail:  17rem;
  --cmx-side:  16rem;
  background: var(--surface-0);
  overflow-x: clip;
  /* Tells the browser to render NATIVE chrome dark: the document scrollbar,
     form controls, spinners. ::-webkit-scrollbar can restyle a scrolling
     element but cannot reach the document's own bar, which is why a dark page
     was still edged with a white slab. One declaration, and it is also what
     makes a date picker or a select look right without touching either. */
  color-scheme: dark;
}

/* ── AMBIENCE ──────────────────────────────────────────────────────────── */
.cmxSky { position: fixed; inset: 0; z-index: 0; pointer-events: none; overflow: hidden; }
.cmxSky__grid {
  position: absolute; inset: -20%;
  background-image:
    linear-gradient(to right, rgb(240 198 74 / .04) 1px, transparent 1px),
    linear-gradient(to bottom, rgb(240 198 74 / .04) 1px, transparent 1px);
  background-size: 58px 58px;
  mask-image: radial-gradient(ellipse 70% 55% at 50% 25%, #000 12%, transparent 72%);
  transform: perspective(700px) rotateX(62deg) translateY(16%);
  transform-origin: 50% 100%;
}
.cmxSky__orb { position: absolute; border-radius: 50%; filter: blur(90px); opacity: .26;
  animation: cmxDrift 26s var(--ease-inout) infinite alternate; }
.cmxSky__orb--1 { width: 38vw; height: 38vw; left: -10vw; top: -12vw;
  background: radial-gradient(circle, rgb(240 198 74 / .5), transparent 70%); }
.cmxSky__orb--2 { width: 30vw; height: 30vw; right: -8vw; bottom: -10vw;
  background: radial-gradient(circle, rgb(232 84 74 / .3), transparent 70%); animation-delay: -13s; }
@keyframes cmxDrift { from { translate: 0 0; } to { translate: 3vw 4vh; } }

/* Sections lift above the ambience — never <main>, whose z-index would create
   a stacking context and trap every overlay inside it. */
.cmxShell, .cmxFlag, .cmpWrap { position: relative; z-index: 1; }

/* ── SHARED ────────────────────────────────────────────────────────────── */
/* --av-size is written inline by yc_avatar(). BOTH dimensions have to be set
   here: site.css carries `img { max-width:100%; height:auto }`, which beats the
   HTML height attribute, so a 256x340 portrait rendered 46 wide by 61 tall and
   border-radius:50% turned it into an ellipse. aspect-ratio is the backstop if
   the variable is ever missing. */
.ycAv {
  width: var(--av-size, 40px);
  height: var(--av-size, 40px);
  aspect-ratio: 1;
  border-radius: 50%; display: block; flex: none;
}
/* A hairline and a backing colour on PHOTO avatars only. A generated tile is
   always a light-on-colour gradient and needs neither; a real photograph can be
   almost black, and three of the first six people on the discover page looked
   like their avatar had failed to load. The ring gives a dark photo an edge. */
img.ycAv {
  object-fit: cover; object-position: 50% 30%;
  background: var(--surface-2);
  box-shadow: inset 0 0 0 1px rgb(255 255 255 / .1), 0 0 0 1px rgb(255 255 255 / .06);
}

.cmxDot {
  width: .45rem; height: .45rem; border-radius: 50%; flex: none;
  background: var(--y-green); display: inline-block;
  box-shadow: 0 0 0 0 rgb(62 158 95 / .6);
  animation: cmxPulse 2.4s var(--ease-out) infinite;
}
@keyframes cmxPulse {
  0%   { box-shadow: 0 0 0 0 rgb(62 158 95 / .55); }
  70%  { box-shadow: 0 0 0 .5rem rgb(62 158 95 / 0); }
  100% { box-shadow: 0 0 0 0 rgb(62 158 95 / 0); }
}

.cmxBtn {
  display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
  padding: .6rem 1.1rem; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid transparent; font: inherit; font-size: var(--fs-sm); font-weight: 600;
  text-decoration: none;
  transition: box-shadow var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              filter var(--dur) var(--ease-out);
}
/* No translate on hover — see the note at .cmxRoom. A 2px lift on a 40px
   button means the bottom 2px of it flickers when the pointer rests there. The
   lift is faked with shadow, which cannot move the hit area. */
.cmxBtn--primary { background: var(--y-yellow); color: var(--y-black); box-shadow: var(--glow-yellow); }
.cmxBtn--primary:hover { box-shadow: var(--glow-yellow), 0 6px 18px -6px rgb(240 198 74 / .5); filter: brightness(1.06); }
.cmxBtn--ghost { border-color: var(--cmx-hair); color: var(--text-hi); background: var(--cmx-pane); }
.cmxBtn--ghost:hover { border-color: rgb(240 198 74 / .55); color: var(--accent-ink); }
.cmxBtn--on { background: var(--cmx-pane2); color: var(--text-mid); border-color: var(--cmx-hair); }

.cmxIcon {
  width: 2.3rem; height: 2.3rem; flex: none; display: grid; place-items: center;
  border-radius: 50%; cursor: pointer; font-size: 1rem; line-height: 1;
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane); color: var(--text-mid);
  transition: color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out),
              transform var(--dur) var(--ease-spring);
}
.cmxIcon:hover { color: var(--accent-ink); border-color: var(--y-yellow); transform: scale(1.08); }

.cmxLink { border: 0; background: none; padding: 0; cursor: pointer; font: inherit;
  color: var(--accent-ink); text-decoration: underline; text-underline-offset: 3px; }

.cmxAnon {
  font-family: var(--font-mono); font-size: .55rem; letter-spacing: .1em;
  text-transform: uppercase; padding: .1rem .35rem; border-radius: 4px;
  background: var(--surface-2); color: var(--text-lo);
}

/* ══════════════════════════════════════════════════════════════════════════
   SHELL — three columns
   ══════════════════════════════════════════════════════════════════════════ */
.cmxShell {
  max-width: var(--cmx-max); margin-inline: auto;
  padding: var(--s-5) var(--gutter) 0;
  display: grid;
  grid-template-columns: var(--cmx-rail) minmax(0, 1fr) var(--cmx-side);
  grid-template-areas: "rail main side";
  gap: var(--s-5);
  /* The three columns each scroll independently; the PAGE does not. A chat
     that moves the whole document loses the composer off the bottom. */
  height: calc(100svh - 5.5rem);
}

/* ── RAIL ──────────────────────────────────────────────────────────────── */
.cmxRail {
  grid-area: rail; display: flex; flex-direction: column; gap: var(--s-4);
  min-height: 0;
}
.cmxRail__title { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  color: var(--text-hi); margin: 0; }
.cmxRail__sub { margin: .2rem 0 0; font-size: var(--fs-xs); color: var(--text-lo); }

.cmxRooms { display: grid; gap: var(--s-2); overflow-y: auto; min-height: 0; flex: 1; }
/* ── HOVER MUST NOT MOVE THE THING BEING HOVERED ────────────────────────────
   These cards used to shift 3px on :hover. That is a feedback loop, not a
   flourish: with the pointer near an edge, the card slides out from under it,
   :hover stops matching, the card slides back, :hover matches again — and it
   oscillates for as long as the mouse is held still. It reads as a rendering
   fault, which is exactly what it is.

   The rule that avoids it: never animate a property that changes the element's
   OWN hit area on a hover of that same element. So the motion lives in a
   pseudo-element and in the contents instead — an accent bar wipes in from the
   left and the row lifts its text, while the card's box never moves a pixel.
   Same read, no loop. --------------------------------------------------- */
.cmxRoom {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-3) var(--s-3) calc(var(--s-3) + 3px);
  border-radius: var(--radius);
  border: 1px solid transparent; text-decoration: none;
  transition: background-color var(--dur) var(--ease-out), border-color var(--dur) var(--ease-out);
}
/* The accent bar. Scales from 0 rather than animating width, so it is a
   compositor-only change and cannot reflow the row it sits in. */
.cmxRoom::before {
  content: ''; position: absolute; z-index: -1;
  left: 0; top: .55rem; bottom: .55rem; width: 3px;
  border-radius: 3px; background: var(--y-yellow);
  scale: 1 0; transform-origin: 50% 50%;
  transition: scale var(--dur) var(--ease-spring);
}
.cmxRoom:hover { background: var(--cmx-pane); }
.cmxRoom:hover::before, .cmxRoom.is-on::before { scale: 1 1; }
.cmxRoom:hover .cmxRoom__emoji { scale: 1.14; }
.cmxRoom.is-on { background: var(--cmx-pane2); border-color: rgb(240 198 74 / .35); }
.cmxRoom__emoji { font-size: 1.1rem; transition: scale var(--dur) var(--ease-spring); }
.cmxRoom__body { min-width: 0; }
.cmxRoom__body b { display: block; font-size: var(--fs-sm); color: var(--text-hi); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxRoom__body i { display: block; font-style: normal; font-size: var(--fs-xs); color: var(--text-lo);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxRoom__live {
  display: inline-flex; align-items: center; gap: .3rem; flex: none;
  font-family: var(--font-mono); font-size: .62rem; color: var(--y-green);
}
.cmxRoom__idle { font-family: var(--font-mono); font-size: .6rem; color: var(--text-lo); flex: none; }
.cmxRail__foot { padding-bottom: var(--s-4); }

/* ══════════════════════════════════════════════════════════════════════════
   MAIN
   ══════════════════════════════════════════════════════════════════════════ */
.cmxMain {
  grid-area: main; min-height: 0; min-width: 0;
  display: grid;
  /* The COLUMN must be declared, not just the rows. With rows alone the
     implicit column resolves to max-content, so the header and the log sized
     themselves to their content — measured 382px wide inside a 351px parent
     at 375px viewport. This is the third time this exact defect has appeared
     in this project; rows-without-columns is always worth a second look. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto auto;
  border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: linear-gradient(180deg, var(--cmx-pane2), transparent 30%), var(--surface-1);
  overflow: hidden;
}

.cmxTop {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5); border-bottom: 1px solid var(--cmx-hair);
}
.cmxTop__emoji { font-size: 1.5rem; flex: none; }
.cmxTop__body { flex: 1; min-width: 0; }
.cmxTop__body h2, .cmxTop__t { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  color: var(--text-hi); margin: 0; line-height: 1.2; }
.cmxTop__body p, .cmxTop__s { margin: .1rem 0 0; font-size: var(--fs-xs); color: var(--text-lo);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

.cmxWho { display: flex; align-items: center; gap: var(--s-3); flex: none; }
.cmxStack { display: flex; align-items: center; }
.cmxStack > * { margin-left: -.6rem; box-shadow: 0 0 0 2px var(--surface-1); }
.cmxStack > *:first-child { margin-left: 0; }
.cmxStack__more {
  width: 1.875rem; height: 1.875rem; border-radius: 50%; display: grid; place-items: center;
  background: var(--surface-2); color: var(--text-mid);
  font-family: var(--font-mono); font-size: .58rem;
}
.cmxWho__txt { display: inline-flex; align-items: center; gap: .4rem;
  font-size: var(--fs-xs); color: var(--text-lo); white-space: nowrap; }
.cmxWho__txt b { color: var(--text-hi); }

/* ── LOG ───────────────────────────────────────────────────────────────── */
.cmxLog {
  overflow-y: auto; min-height: 0;
  padding: var(--s-5) var(--s-5) var(--s-2);
  display: flex; flex-direction: column; gap: var(--s-3);
  scroll-behavior: smooth;
}
.cmxEmpty { text-align: center; color: var(--text-lo); padding: var(--s-16) 0; margin: 0; }

.cmxSys {
  align-self: center; margin: var(--s-2) 0;
  padding: .3rem .9rem; border-radius: var(--radius-pill);
  background: var(--surface-2); color: var(--text-lo);
  font-size: var(--fs-xs);
  animation: cmxSysIn .5s var(--ease-out);
}
@keyframes cmxSysIn { from { opacity: 0; scale: .9; } to { opacity: 1; scale: 1; } }

.cmxMsg {
  position: relative;
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-3); align-items: start;
  animation: cmxIn .42s var(--ease-out);
}
/* A message from the same author immediately above loses its avatar and name.
   Repeating them turns a conversation into a list of business cards. */
.cmxMsg.is-group { grid-template-columns: 34px minmax(0, 1fr); }
.cmxMsg.is-group .cmxMsg__body { padding-top: 0; }
@keyframes cmxIn {
  from { opacity: 0; translate: 0 10px; scale: .98; }
  to   { opacity: 1; translate: 0 0;    scale: 1; }
}
.cmxMsg__av { grid-column: 1; }
.cmxMsg__body { grid-column: 2; min-width: 0; }

.cmxMsg__who { display: flex; align-items: baseline; gap: var(--s-2); margin: 0 0 .25rem; }
.cmxMsg__who b { font-size: var(--fs-sm); color: var(--text-hi); font-weight: 600; }
.cmxMsg__who time { font-family: var(--font-mono); font-size: .58rem; color: var(--text-lo); }

.cmxBubble {
  display: inline-block; max-width: 46rem;
  padding: .6rem .9rem; border-radius: 4px 14px 14px 14px;
  background: var(--surface-2); color: var(--text-mid);
  font-size: var(--fs-sm); line-height: var(--lh-body);
  overflow-wrap: anywhere;
}
/* ── YOUR messages sit on the right ───────────────────────────────────────
   Everything you receive stays left. This is the one layout convention every
   messaging app shares, and breaking it makes a chat genuinely hard to scan:
   without it you have to read the name on every bubble to know who is who. */
.cmxMsg.is-mine {
  grid-template-columns: minmax(0, 1fr) auto;
  justify-items: end;
}
.cmxMsg.is-mine .cmxMsg__av   { grid-column: 2; grid-row: 1; }
.cmxMsg.is-mine .cmxMsg__body { grid-column: 1; grid-row: 1; text-align: right; }
.cmxMsg.is-mine.is-group      { grid-template-columns: minmax(0, 1fr) 34px; }
.cmxMsg.is-mine .cmxMsg__who  { justify-content: flex-end; }
.cmxMsg.is-mine .cmxRx        { justify-content: flex-end; }
.cmxMsg.is-mine .cmxMsg__tools { right: auto; left: 0; }

.cmxMsg.is-mine .cmxBubble {
  background: linear-gradient(135deg, rgb(240 198 74 / .24), rgb(240 198 74 / .13));
  border: 1px solid rgb(240 198 74 / .32); color: var(--text-hi);
  /* The tail flips to the other corner with it. */
  border-radius: 14px 4px 14px 14px;
  text-align: left;
}

/* A message still in flight is dimmed, and one that failed says so. Without
   this a send that silently did nothing looks identical to one that worked. */
.cmxMsg.is-sending { opacity: .55; }
.cmxMsg.is-failed .cmxBubble { border-color: var(--y-red); color: var(--y-red-300); }

.cmxSticker { font-size: 2.6rem; line-height: 1; padding: .2rem 0;
  animation: cmxPop .45s var(--ease-spring); }
@keyframes cmxPop { from { scale: .3; rotate: -12deg; } to { scale: 1; rotate: 0deg; } }

.cmxGif {
  display: inline-flex; align-items: center; gap: var(--s-3);
  padding: var(--s-4) var(--s-5); border-radius: var(--radius);
  border: 1px dashed var(--cmx-hair); background: var(--surface-2);
}
.cmxGif__tag { font-family: var(--font-mono); font-size: .55rem; letter-spacing: .12em;
  padding: .15rem .4rem; border-radius: 4px; background: var(--y-red); color: var(--y-white); }
.cmxGif__cap { font-size: var(--fs-sm); color: var(--text-mid); }

/* ── reactions ─────────────────────────────────────────────────────────── */
.cmxRx { display: flex; flex-wrap: wrap; gap: .3rem; margin-top: .35rem; }
.cmxRx:empty { display: none; }
.cmxRx__chip {
  display: inline-flex; align-items: center; gap: .25rem; cursor: pointer;
  padding: .1rem .45rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane);
  font: inherit; font-size: .72rem; color: var(--text-mid);
  transition: border-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring);
}
.cmxRx__chip:hover { border-color: var(--y-yellow); scale: 1.08; }
.cmxRx__chip i { font-style: normal; font-family: var(--font-mono); font-size: .6rem; color: var(--text-lo); }
.cmxRx__chip.is-bump { animation: cmxBump .4s var(--ease-spring); }
@keyframes cmxBump { 0% { scale: 1; } 45% { scale: 1.3; } 100% { scale: 1; } }

/* ── MESSAGE TOOLS ──────────────────────────────────────────────────────────
   Absolute, so they never take layout space — a row that appeared on hover
   would reflow the whole log under the cursor.

   Anchored to the TOP-OUTER corner of the message: right for messages you
   received, left for your own, which is the empty half of the name row in each
   case. -.5rem used to lift it into the previous message's bubble; it now sits
   on its own row. ---------------------------------------------------------- */
.cmxMsg__tools {
  position: absolute; top: -.15rem; right: 0; z-index: 3;
  display: flex; align-items: center; gap: .15rem; padding: .2rem;
  border-radius: var(--radius-pill); border: 1px solid var(--cmx-hair);
  background: rgb(13 13 15 / .95); backdrop-filter: blur(8px);
  box-shadow: 0 6px 18px -10px rgb(0 0 0 / .9);
  opacity: 0; translate: 0 4px; pointer-events: none;
  transition: opacity var(--dur) var(--ease-out), translate var(--dur) var(--ease-out);
}
.cmxMsg:hover .cmxMsg__tools,
.cmxMsg:focus-within .cmxMsg__tools { opacity: 1; translate: 0 0; pointer-events: auto; }

.cmxTool {
  width: 1.7rem; height: 1.7rem; display: grid; place-items: center; cursor: pointer;
  border: 0; border-radius: 50%; background: none; font-size: .8rem; color: var(--text-mid);
  transition: background-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring),
              color var(--dur) var(--ease-out);
}
.cmxTool:hover { background: var(--surface-2); scale: 1.15; }

/* A hairline between the playful actions and the consequential ones. Three
   reaction emoji and a delete sitting flush against each other is a misclick
   with no undo on one side of it. */
.cmxTool__sep { width: 1px; height: 1rem; background: var(--cmx-hair); margin-inline: .15rem; flex: none; }

.cmxTool--more svg { width: 1rem; height: 1rem; }
.cmxTool--add svg { width: 1.05rem; height: 1.05rem; }
.cmxTool--add:hover { color: var(--text-hi); }

/* The ＋ picker. Fixed on <body> (see openPicker) so it survives the hover
   row fading out; same glass and shadow as the ••• menu so the two read as
   one family. */
.cmxPick {
  position: fixed; z-index: 500;
  display: grid; grid-template-columns: repeat(8, 2.2rem); gap: .15rem; padding: .4rem;
  border-radius: var(--radius); border: 1px solid var(--cmx-hair);
  background: var(--surface-2); backdrop-filter: var(--cmx-glass);
  box-shadow: 0 24px 60px -20px rgb(0 0 0 / .9);
  animation: cmxRise .16s var(--ease-out);
}
.cmxPick__emo {
  width: 2.2rem; height: 2.2rem; display: grid; place-items: center;
  border: 0; border-radius: var(--radius); background: none; cursor: pointer;
  font-size: 1.2rem; line-height: 1;
  transition: background-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring);
}
.cmxPick__emo:hover, .cmxPick__emo:focus-visible { background: var(--cmx-pane2); scale: 1.18; outline: none; }
@media (max-width: 30rem) { .cmxPick { grid-template-columns: repeat(6, 2.2rem); } }
.cmxTool--more:hover { color: var(--text-hi); }

/* ── typing ────────────────────────────────────────────────────────────── */
.cmxTyping {
  display: flex; align-items: center; gap: .25rem;
  margin: 0; padding: 0 var(--s-5) var(--s-2);
  font-size: var(--fs-xs); color: var(--text-lo);
}
.cmxTyping[hidden] { display: none; }
.cmxTyping span {
  width: .3rem; height: .3rem; border-radius: 50%; background: var(--text-lo);
  animation: cmxBounce 1.3s var(--ease-inout) infinite;
}
.cmxTyping span:nth-child(2) { animation-delay: .16s; }
.cmxTyping span:nth-child(3) { animation-delay: .32s; }
.cmxTyping i { font-style: normal; margin-left: .4rem; }
@keyframes cmxBounce {
  0%, 60%, 100% { translate: 0 0; opacity: .4; }
  30%           { translate: 0 -4px; opacity: 1; }
}

/* ── composer ──────────────────────────────────────────────────────────── */
.cmxComposer { border-top: 1px solid var(--cmx-hair); padding: var(--s-3) var(--s-5) var(--s-4); }
.cmxAs { font-size: var(--fs-xs); color: var(--text-lo); margin-bottom: var(--s-2);
  display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.cmxAs b { color: var(--text-hi); }
.cmxGate { margin: 0; text-align: center; color: var(--text-mid); font-size: var(--fs-sm); }
.cmxGate a { color: var(--accent-ink); }

.cmxTray {
  display: grid; gap: var(--s-2); margin-bottom: var(--s-3);
  padding: var(--s-2) var(--s-3) var(--s-3); border-radius: var(--radius);
  border: 1px solid var(--cmx-hair); background: var(--surface-2);
  box-shadow: 0 20px 50px -24px rgb(0 0 0 / .9);
  animation: cmxTrayIn .28s var(--ease-out);
}
.cmxTray[hidden] { display: none; }
@keyframes cmxTrayIn { from { opacity: 0; translate: 0 8px; } to { opacity: 1; translate: 0 0; } }
.cmxTray__tabs { display: flex; gap: .25rem; border-bottom: 1px solid var(--cmx-hair); padding-bottom: var(--s-2); }
.cmxTray__tabs button { border: 0; background: none; cursor: pointer; padding: .4rem .8rem; border-radius: var(--radius-pill);
  color: var(--text-mid); font: inherit; font-size: var(--fs-sm); font-weight: 600; }
.cmxTray__tabs button.is-on { background: rgb(240 198 74 / .14); color: var(--accent-ink); }
.cmxTray__pane[hidden] { display: none !important; }
.cmxTray__pane { max-height: 15rem; overflow-y: auto; }
/* Stickers tab: GIPHY sticker search on top, the quick stickers below. */
.cmxTray__pane--stickers { display: block; }
.cmxTray__pane { max-height: 18rem; }
.cmxQuick { display: grid; grid-template-columns: repeat(auto-fill, minmax(4.4rem, 1fr)); gap: .3rem; }
.cmxStk { display: grid; grid-template-columns: repeat(auto-fill, minmax(5.2rem, 1fr)); gap: .3rem; }
.cmxStk[hidden] { display: none; }
.cmxStk .cmxTray__hint { grid-column: 1 / -1; }
/* A sticker is a cut-out: no box behind it, just a soft highlight on hover. */
.cmxStk .cmxGif--stk { aspect-ratio: 1; margin: 0; display: grid; place-items: center; padding: .25rem;
  background: none; border-radius: 12px; transition: background-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring); }
.cmxStk .cmxGif--stk:hover { background: var(--cmx-pane2); scale: 1.06; }
.cmxStk .cmxGif--stk img { width: 100%; height: 100%; object-fit: contain; }
.cmxTray__sub { margin: var(--s-3) 0 var(--s-2); max-width: none; font-size: .7rem; letter-spacing: .08em;
  text-transform: uppercase; color: var(--text-lo); }
.cmxTray__pane--emoji { display: grid; grid-template-columns: repeat(auto-fill, minmax(2.4rem, 1fr)); gap: .15rem; }
.cmxTraySticker {
  display: grid; justify-items: center; gap: .1rem; padding: .35rem .2rem; cursor: pointer;
  border: 1px solid transparent; border-radius: var(--radius); background: none; font-size: 1.7rem; line-height: 1.1;
  transition: background-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring);
}
.cmxTraySticker i { font-style: normal; font-size: .62rem; color: var(--text-lo); white-space: nowrap; }
.cmxTraySticker:hover { background: var(--cmx-pane2); scale: 1.08; }
.cmxEmo { border: 0; background: none; cursor: pointer; font-size: 1.35rem; padding: .3rem; border-radius: var(--radius);
  transition: background-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring); }
.cmxEmo:hover { background: var(--cmx-pane2); scale: 1.15; }
.cmxTray__pane--emoji .cmxTray__hint { grid-column: 1 / -1; }
.cmxGifQ { width: 100%; margin-bottom: var(--s-2); padding: .5rem .8rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--surface-1); color: var(--text-hi); font: inherit; font-size: var(--fs-sm); }
.cmxGifQ[hidden] { display: none; }
.cmxGifs { columns: 3 7rem; column-gap: .35rem; }
.cmxGif { display: block; width: 100%; margin: 0 0 .35rem; padding: 0; border: 0; border-radius: 8px; overflow: hidden;
  cursor: pointer; background: var(--cmx-pane); break-inside: avoid; }
.cmxGif img { display: block; width: 100%; height: auto; }
.cmxGif.is-busy { opacity: .5; }
.cmxGif.is-fail { outline: 1px solid var(--y-red); }
.cmxTray__hint { margin: var(--s-2) 0; max-width: none; color: var(--text-mid); font-size: .8rem; line-height: 1.5; }
.cmxTray__hint b { color: var(--text-hi); }
.cmxTray__credit { margin: .2rem 0 0; max-width: none; text-align: right; font-size: .66rem; color: var(--text-lo); }
.cmxIcon--media svg { width: 1.25rem; height: 1.25rem; }
.cmxForm.is-drop { outline: 2px dashed rgb(240 198 74 / .6); outline-offset: 4px; border-radius: var(--radius); }

.cmxForm { display: flex; align-items: center; gap: var(--s-2); }
.cmxForm input {
  flex: 1; min-width: 0; padding: .75rem 1rem;
  border-radius: var(--radius-pill); border: 1px solid var(--cmx-hair);
  background: var(--surface-2); color: var(--text-hi);
  font: inherit; font-size: var(--fs-sm); outline: none;
  transition: border-color var(--dur) var(--ease-out);
}
.cmxForm input:focus { border-color: var(--y-yellow); }
.cmxSend {
  width: 2.6rem; height: 2.6rem; flex: none; display: grid; place-items: center;
  border: 0; border-radius: 50%; cursor: pointer;
  background: var(--y-yellow); color: var(--y-black); font-size: .9rem;
  transition: transform var(--dur) var(--ease-spring), box-shadow var(--dur) var(--ease-out);
}
.cmxSend:hover { transform: scale(1.08) translateX(1px); box-shadow: var(--glow-yellow); }
.cmxSend:active { transform: scale(.94); }

/* ── SIDE ──────────────────────────────────────────────────────────────── */
.cmxSide { grid-area: side; min-height: 0; display: flex; flex-direction: column; gap: var(--s-3); }
.cmxSide__h {
  font-family: var(--font-mono); font-size: var(--fs-xs); letter-spacing: var(--tracking-eyebrow);
  text-transform: uppercase; color: var(--text-lo); margin: 0; font-weight: 500;
}
.cmxPeople { list-style: none; margin: 0; padding: 0; display: grid; gap: .2rem;
  overflow-y: auto; min-height: 0; }
.cmxPerson a {
  display: flex; align-items: center; gap: var(--s-3);
  padding: .45rem; border-radius: var(--radius); text-decoration: none;
  transition: background-color var(--dur) var(--ease-out);
}
.cmxPerson a:hover { background: var(--cmx-pane); }
.cmxPerson span { min-width: 0; }
.cmxPerson b { display: block; font-size: var(--fs-sm); color: var(--text-hi); font-weight: 500;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxPerson i { display: block; font-style: normal; font-size: .62rem; color: var(--text-lo);
  font-family: var(--font-mono); }
.cmxSide__note {
  margin: 0; padding: var(--s-3); border-radius: var(--radius);
  border: 1px dashed var(--cmx-hair); background: var(--cmx-pane);
  font-size: var(--fs-xs); color: var(--text-lo); line-height: var(--lh-body);
}
.cmxSide__note b { color: var(--text-mid); }

.cmxFlag {
  text-align: center; margin: 0; padding: var(--s-3);
  font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--text-lo);
}
.cmxFlag code { background: var(--surface-2); padding: .1rem .35rem; border-radius: 4px; }

/* ══════════════════════════════════════════════════════════════════════════
   PROFILE + DISCOVER
   ══════════════════════════════════════════════════════════════════════════ */
.cmpWrap { max-width: 62rem; margin-inline: auto; padding: var(--s-10) var(--gutter) var(--s-20); }

/* ══════════════════════════════════════════════════════════════════════════
   PROFILE HEADER
   A banner, an avatar sitting on its edge, identity in the middle, and ONE
   fixed-width action column on the right where every button is the same width.
   ══════════════════════════════════════════════════════════════════════════ */
/* NO overflow:hidden here. It was clipping the ••• menu, which is positioned
   inside this card — half the panel simply was not painted. The banner rounds
   its OWN top corners instead, which is the only thing the clip was for. */
.cmpHead {
  position: relative;
  border-radius: var(--radius-lg);
  border: 1px solid var(--cmx-hair);
  background: var(--surface-1);
}

/* Drawn, never an upload. An empty cover-photo slot on a profile nobody has
   filled in looks broken; this cannot. */
.cmpHead__banner {
  height: 7rem;
  border-radius: calc(var(--radius-lg) - 1px) calc(var(--radius-lg) - 1px) 0 0;
  overflow: hidden;
  background:
    radial-gradient(120% 180% at 12% 0%, rgb(240 198 74 / .30), transparent 58%),
    radial-gradient(90% 160% at 88% 10%, rgb(232 84 74 / .18), transparent 60%),
    linear-gradient(180deg, var(--cmx-pane2), transparent),
    var(--surface-2);
  position: relative;
}
.cmpHead__banner::after {
  content: ''; position: absolute; inset: 0;
  background-image:
    linear-gradient(to right, rgb(255 255 255 / .05) 1px, transparent 1px),
    linear-gradient(to bottom, rgb(255 255 255 / .05) 1px, transparent 1px);
  background-size: 34px 34px;
  mask-image: linear-gradient(180deg, #000, transparent);
}

.cmpHead__row {
  display: grid;
  grid-template-columns: auto minmax(0, 1fr) auto;
  gap: var(--s-5);
  align-items: start;
  padding: 0 var(--s-6) var(--s-6);
  /* The avatar rides up onto the banner. Negative margin on the ROW rather than
     on the avatar, so the two side columns keep a shared baseline. */
  margin-top: -2.6rem;
}

.cmpHead__pic {
  border: 0; padding: 0; background: none; position: relative;
  border-radius: 50%; line-height: 0;
  /* The ring is what separates a dark portrait from a dark banner. */
  box-shadow: 0 0 0 4px var(--surface-1), 0 0 0 5px var(--cmx-hair);
  transition: box-shadow var(--dur) var(--ease-out);
}
button.cmpHead__pic { cursor: zoom-in; }
button.cmpHead__pic:hover, button.cmpHead__pic:focus-visible {
  box-shadow: 0 0 0 4px var(--surface-1), 0 0 0 6px rgb(240 198 74 / .75);
  outline: none;
}
/* The affordance. Without it a picture that opens is indistinguishable from
   one that does not — and the flat variant genuinely does not. */
.cmpHead__zoom {
  position: absolute; right: .1rem; bottom: .1rem;
  width: 1.9rem; height: 1.9rem; display: grid; place-items: center;
  border-radius: 50%; background: var(--y-yellow); color: var(--y-black);
  box-shadow: 0 0 0 3px var(--surface-1);
  opacity: 0; scale: .8;
  transition: opacity var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring);
}
.cmpHead__zoom svg { width: .95rem; height: .95rem; }
button.cmpHead__pic:hover .cmpHead__zoom,
button.cmpHead__pic:focus-visible .cmpHead__zoom { opacity: 1; scale: 1; }

.cmpHead__id { min-width: 0; padding-top: 3rem; }
.cmpHead__name { font-family: var(--font-display); font-size: var(--fs-h2); font-weight: 500;
  color: var(--text-hi); margin: 0; line-height: 1.05; }
.cmpHead__at {
  display: flex; align-items: center; flex-wrap: wrap; gap: var(--s-2);
  margin: .35rem 0 0; font-family: var(--font-mono); font-size: var(--fs-sm);
  color: var(--accent-ink);
}
.cmpHead__bio { margin: var(--s-3) 0 0; color: var(--text-mid); font-size: var(--fs-body);
  line-height: var(--lh-body); max-width: 52ch; }
.cmpHead__meta { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: var(--s-4) 0 0; }

/* Chips, not a run-on line. Three facts separated by spaces read as one
   sentence with the punctuation missing. */
.cmpChip {
  padding: .25rem .6rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane);
  font-size: var(--fs-xs); color: var(--text-mid);
}
.cmpTag {
  padding: .15rem .5rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane);
  /* --font-body. There is no --font-sans in tokens.css, and an undefined
     custom property invalidates the WHOLE declaration silently — so this tag
     was inheriting the mono stack from .cmpHead__at above it. */
  font-family: var(--font-body); font-size: .62rem; font-weight: 600;
  color: var(--text-lo); letter-spacing: 0;
}
.cmpTag--on { border-color: rgb(62 158 95 / .45); color: var(--y-green); background: rgb(62 158 95 / .1); }
.cmpTag--lock { border-color: rgb(240 198 74 / .4); color: var(--accent-ink); }

/* ── THE ACTION COLUMN ──────────────────────────────────────────────────────
   One fixed width so every button is the same width, ordered by how often it
   is wanted. Destructive actions are NOT here — they are behind the ••• menu.
   Disconnect and Block used to sit out in the open as yellow underlined links,
   louder than the primary button and adjacent to each other, which is a
   misclick away from severing a relationship. Rare and irreversible belongs one
   deliberate step away, not one accidental one. -------------------------- */
.cmpAct {
  width: 13rem; flex: none; padding-top: 3rem;
  display: grid; gap: var(--s-2); align-content: start;
}
.cmpAct .cmxBtn { width: 100%; }
.cmpAct__conn { display: grid; gap: var(--s-2); }
.cmpAct__note {
  margin: 0; font-size: var(--fs-xs); color: var(--text-lo);
  line-height: var(--lh-body); text-align: center;
}

.cmpMore { position: relative; justify-self: center; }
.cmpMore__btn { width: auto !important; padding: .35rem 1rem; letter-spacing: .1em; min-height: 2.75rem; }   /* level with Follow / Connect beside it */
.cmpMore__panel {
  position: absolute; right: 0; top: calc(100% + .4rem); z-index: 30;
  width: 17rem; padding: .35rem;
  border-radius: var(--radius); border: 1px solid var(--cmx-hair);
  background: var(--surface-2); backdrop-filter: var(--cmx-glass);
  box-shadow: 0 24px 60px -20px rgb(0 0 0 / .9);
  animation: cmxRise .18s var(--ease-out);
}
.cmpMore__panel[hidden] { display: none; }
.cmpMore__item {
  display: grid; gap: .15rem; width: 100%; text-align: left;
  padding: .55rem .7rem; border: 0; border-radius: var(--radius);
  background: none; cursor: pointer; font: inherit;
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-hi);
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.cmpMore__item i { font-style: normal; font-size: var(--fs-xs); font-weight: 400;
  color: var(--text-lo); line-height: var(--lh-body); }
.cmpMore__item:hover { background: var(--cmx-pane2); }
.cmpMore__item.is-danger:hover { background: rgb(232 84 74 / .14); color: var(--y-red); }
.cmpMore__item.is-danger { color: var(--y-red); }
/* Sheet-only parts: hidden on desktop, where the menu is a small dropdown. */
.cmpMore__title, .cmpMore__cancel, .cmpMore__veil { display: none; }

/* ── PHONES: the ••• menu becomes a bottom sheet ───────────────────────────
   As a dropdown it was pinned to a narrow column and its text wrapped one word
   per line. A sheet is full width, sits where the thumb is, has a title so
   you know whose profile you are acting on, a real backdrop (tapping it closes
   without clicking whatever is underneath) and an explicit Cancel. */
@media (max-width: 46rem) {
  .cmpMore__panel {
    position: fixed; left: 0; right: 0; bottom: 0; top: auto; z-index: 910;
    width: auto; max-height: 80dvh; overflow-y: auto;
    padding: .6rem .75rem calc(.9rem + env(safe-area-inset-bottom));
    border-radius: 20px 20px 0 0; border-bottom: 0;
    animation: cmpSheetUp .24s var(--ease-out);
  }
  .cmpMore__panel::before {                 /* the grab handle */
    content: ""; display: block; width: 2.6rem; height: 4px; margin: .1rem auto .7rem;
    border-radius: 99px; background: var(--cmx-hair);
  }
  .cmpMore__title { display: block; margin: 0 .7rem .4rem; max-width: none; text-align: center;
    font-size: var(--fs-xs); letter-spacing: .06em; color: var(--text-lo); }
  .cmpMore__item { padding: .9rem 1rem; font-size: var(--fs-body); }
  .cmpMore__item i { font-size: var(--fs-sm); }
  .cmpMore__cancel { display: block; margin-top: .4rem; text-align: center;
    border: 1px solid var(--cmx-hair); color: var(--text-mid); }
  .cmpMore__veil { position: fixed; inset: 0; z-index: 905; border: 0; padding: 0;
    background: rgb(0 0 0 / .55); backdrop-filter: blur(2px); }
  .cmpMore__panel:not([hidden]) + .cmpMore__veil { display: block; }
}
@keyframes cmpSheetUp { from { translate: 0 100%; } to { translate: 0 0; } }

/* ── STATS ─────────────────────────────────────────────────────────────── */
.cmpStats {
  display: grid; grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: var(--s-3); margin: var(--s-4) 0 var(--s-3);
}
.cmpStat {
  padding: var(--s-4) var(--s-5); border-radius: var(--radius-lg);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane);
  transition: border-color var(--dur) var(--ease-out);
}
/* Connections is marked, because it is the one people will assume is just the
   other two added up. */
.cmpStat--key { border-color: rgb(240 198 74 / .35);
  background: linear-gradient(160deg, rgb(240 198 74 / .1), transparent 70%), var(--cmx-pane); }
.cmpStat b { display: block; font-family: var(--font-display); font-size: var(--fs-h2);
  font-weight: 500; color: var(--text-hi); line-height: 1; }
.cmpStat--key b { color: var(--accent-ink); }
.cmpStat span { font-size: var(--fs-xs); color: var(--text-lo); }

.cmpLegend {
  margin: 0 0 var(--s-6); padding: var(--s-3) var(--s-5);
  border-radius: var(--radius); border: 1px dashed var(--cmx-hair);
  font-size: var(--fs-xs); color: var(--text-lo); line-height: var(--lh-body);
}
.cmpLegend b { color: var(--text-mid); }

.cmpPrivate {
  text-align: center; padding: var(--s-20) var(--gutter);
  display: grid; justify-items: center; gap: var(--s-3);
}
.cmpPrivate__lock { font-size: 2.5rem; margin: 0; }
.cmpPrivate h2 { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  color: var(--text-hi); margin: 0; }
.cmpPrivate p { color: var(--text-mid); margin: 0; max-width: 44ch; }

/* discover */
.cmpSearch { display: flex; gap: var(--s-3); margin-bottom: var(--s-6); }
.cmpSearch input {
  flex: 1; padding: .85rem 1.2rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--surface-1); color: var(--text-hi);
  font: inherit; font-size: var(--fs-body); outline: none;
}
.cmpSearch input:focus { border-color: var(--y-yellow); }

.cmpGrid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-4); }
/* Same rule as .cmxRoom: no geometry change on its own hover. The lift moves
   to a shadow and a glow, which are paint-only and cannot move the hit area
   out from under the cursor. */
.cmpCard {
  display: grid; gap: var(--s-3); padding: var(--s-5);
  border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: var(--surface-1);
  transition: border-color var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.cmpCard:hover {
  border-color: rgb(240 198 74 / .45);
  background: var(--cmx-pane);
  box-shadow: 0 10px 30px -18px rgb(0 0 0 / .8), 0 0 0 1px rgb(240 198 74 / .12);
}
.cmpCard__top { display: flex; align-items: center; gap: var(--s-3); }
.cmpCard__top a { text-decoration: none; min-width: 0; }
.cmpCard__top b { display: block; font-size: var(--fs-body); color: var(--text-hi); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmpCard__top i { font-style: normal; font-family: var(--font-mono); font-size: .65rem; color: var(--accent-ink); }
.cmpCard__bio { margin: 0; font-size: var(--fs-sm); color: var(--text-mid); line-height: var(--lh-body); }
.cmpCard__meta { margin: 0; font-size: var(--fs-xs); color: var(--text-lo); }

/* ══════════════════════════════════════════════════════════════════════════
   RESPONSIVE
   Each breakpoint restates the grid AREAS it changes. An element left holding
   an area name that no longer exists is auto-placed into an implicit row — a
   silent gap that reports no error.
   ══════════════════════════════════════════════════════════════════════════ */
@media (max-width: 78rem) {
  .cmxShell {
    grid-template-columns: var(--cmx-rail) minmax(0, 1fr);
    grid-template-areas: "rail main";
  }
  /* The people list is the first thing to go: it repeats what the avatar
     stack in the header already says. */
  .cmxSide { display: none; }
  .cmpGrid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}

@media (max-width: 56rem) {
  .cmxShell {
    grid-template-columns: minmax(0, 1fr);
    grid-template-areas: "main";
    height: calc(100svh - 4.5rem);
  }
  .cmxRail { display: none; }
  /* Two columns: the avatar keeps its slot, the actions drop to a full-width
     row underneath. A 13rem action column beside a 30rem identity block is
     what makes a tablet layout feel cramped. */
  .cmpHead__row { grid-template-columns: auto minmax(0, 1fr); }
  /* Explicit columns, not auto-columns. With `grid-auto-columns: 1fr` the •••
     took an equal third of the row, so the two real actions were squeezed to
     pay for a control nobody uses often. It gets `auto`; they split the rest. */
  .cmpAct { grid-column: 1 / -1; width: 100%; padding-top: 0;
    grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    grid-auto-flow: column; align-items: start; }
  .cmpAct__note { grid-column: 1 / -1; text-align: left; }
  .cmpMore { justify-self: end; }
  .cmpMore__btn { width: auto !important; }
}

@media (max-width: 46rem) {
  .cmxShell { padding-inline: var(--s-3); }
  .cmxTop { padding: var(--s-3) var(--s-4); gap: var(--s-2); }
  /* The stack stays — it was never the cause of the overflow here; an
     undeclared grid column on .cmxMain was. These are just to keep a long room
     name from pushing the bell off the row. */
  .cmxTop__body h2, .cmxTop__t { font-size: var(--fs-body-lg); overflow: hidden;
    text-overflow: ellipsis; white-space: nowrap; }
  .cmxWho { min-width: 0; }
  .cmxWho__txt { overflow: hidden; text-overflow: ellipsis; }
  .cmxLog { padding: var(--s-4) var(--s-4) var(--s-2); }
  .cmxComposer { padding: var(--s-3) var(--s-4) var(--s-4); }
  .cmxForm input { font-size: 1rem; }   /* 16px — below it iOS zooms on focus */
  .cmpGrid { grid-template-columns: minmax(0, 1fr); }
  .cmpStats { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--s-2); }
  .cmpStat { padding: var(--s-3); text-align: center; }
  .cmpStat b { font-size: var(--fs-h3); }
  /* The whole row used to be display:none here, which meant delete, report and
     reactions did not exist at all on a phone — on the device most of these
     students actually use. Now the reaction emoji drop (they are in the •••
     menu on touch) and the ••• itself stays, permanently visible because there
     is no hover to reveal it with. */
  .cmxMsg__tools {
    display: flex; opacity: 1; translate: 0 0; pointer-events: auto;
    border: 0; background: none; box-shadow: none; padding: 0;
    top: .05rem;
  }
  .cmxMsg__tools .cmxTool[data-react],
  .cmxMsg__tools .cmxTool__sep { display: none; }
  .cmxTool--more {
    width: 1.9rem; height: 1.9rem;
    border: 1px solid var(--cmx-hair); background: var(--surface-1); color: var(--text-lo);
  }
}

@media (max-width: 40rem) {
  /* Centre everything. A 104px avatar beside a wrapped name in a 360px column
     leaves neither enough room to be the thing you look at first. */
  .cmpHead__row { grid-template-columns: minmax(0, 1fr); justify-items: center;
    text-align: center; padding-inline: var(--s-4); }
  .cmpHead__id { padding-top: 0; }
  .cmpHead__at, .cmpHead__meta { justify-content: center; }
  .cmpHead__bio { margin-inline: auto; }
  /* ONE ROW: [Follow] [Connect / Message] [•••]. Stacking them put the •••
     alone on a line of its own, off to one side. Notes span the full width
     underneath. Your own profile has no •••, so its buttons share the row. */
  .cmpAct { width: 100%; grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) auto;
    grid-auto-flow: row; align-items: start; }
  .cmpAct:not(:has(.cmpMore)) { grid-template-columns: repeat(auto-fit, minmax(7.5rem, 1fr)); }
  .cmpAct__note { grid-column: 1 / -1; text-align: center; }
  /* The Connect box's own note sat squeezed under its one column; letting
     the box dissolve into the grid puts the button in the row and the note
     full-width beneath it. */
  .cmpAct__conn { display: contents; }
  .cmpAct__conn .cmpAct__note { text-align: center; grid-column: 1 / -1; order: 10; }
  .cmpMore { justify-self: end; }
  .cmpMore__btn { min-height: 2.75rem; }
  .cmpLegend { padding: var(--s-3); }
}

@media (prefers-reduced-motion: reduce) {
  .cmpMore__panel { animation: none; }
  .cmxSky__orb, .cmxDot, .cmxMsg, .cmxSticker, .cmxSys,
  .cmxTyping span, .cmxTray, .cmxRx__chip.is-bump { animation: none; }
  .cmxRoom::before, .cmxRoom__emoji, .cmxBtn { transition: none; }
  .cmxLog { scroll-behavior: auto; }
}

/* ══════════════════════════════════════════════════════════════════════════
   SCROLLBARS
   The default macOS/Chrome bar is a grey slab that sits outside the design
   language of everything else on the page. These are scoped to the community
   pages rather than set globally — a scrollbar restyle is the kind of change
   that quietly breaks a page nobody was looking at.

   Both syntaxes, deliberately. `scrollbar-width`/`scrollbar-color` is the
   standard and the only thing Firefox honours; ::-webkit-scrollbar is
   non-standard but is what Chrome and Safari actually use, and it is the only
   one of the two that can do a gradient or a hover state.
   ══════════════════════════════════════════════════════════════════════════ */
.page-cmx, .page-cmp {
  scrollbar-width: thin;
  scrollbar-color: rgb(240 198 74 / .45) transparent;
}

.page-cmx ::-webkit-scrollbar,
.page-cmp ::-webkit-scrollbar,
.page-cmx::-webkit-scrollbar,
.page-cmp::-webkit-scrollbar { width: 10px; height: 10px; }

.page-cmx ::-webkit-scrollbar-track,
.page-cmp ::-webkit-scrollbar-track,
.page-cmx::-webkit-scrollbar-track,
.page-cmp::-webkit-scrollbar-track {
  background: transparent;
}

.page-cmx ::-webkit-scrollbar-thumb,
.page-cmp ::-webkit-scrollbar-thumb,
.page-cmx::-webkit-scrollbar-thumb,
.page-cmp::-webkit-scrollbar-thumb {
  border-radius: 999px;
  background: linear-gradient(180deg, rgb(240 198 74 / .5), rgb(240 198 74 / .22));
  /* A transparent border with background-clip is the only way to inset a
     webkit thumb — there is no padding on the pseudo-element. */
  border: 3px solid transparent;
  background-clip: content-box;
  transition: background-color var(--dur) var(--ease-out);
}

.page-cmx ::-webkit-scrollbar-thumb:hover,
.page-cmp ::-webkit-scrollbar-thumb:hover,
.page-cmx::-webkit-scrollbar-thumb:hover,
.page-cmp::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, var(--y-yellow), rgb(240 198 74 / .5));
  background-clip: content-box;
}

/* The corner where a vertical and a horizontal bar meet renders as an opaque
   grey square by default, which is the single most visible piece of unstyled
   chrome on a dark page. */
.page-cmx ::-webkit-scrollbar-corner,
.page-cmp ::-webkit-scrollbar-corner { background: transparent; }

/* The message log gets a slimmer one — a 10px bar beside a 4px-tall reaction
   chip looks like furniture rather than an affordance. */
.cmxLog::-webkit-scrollbar,
.cmxRooms::-webkit-scrollbar,
.cmxPeople::-webkit-scrollbar { width: 8px; }
.cmxLog { scrollbar-color: rgb(240 198 74 / .35) transparent; }

/* ══════════════════════════════════════════════════════════════════════════
   NOTIFICATIONS
   ══════════════════════════════════════════════════════════════════════════ */
.cmxBell { position: relative; flex: none; }
.cmxBell__badge {
  position: absolute; top: -.2rem; right: -.2rem;
  min-width: 1.05rem; height: 1.05rem; padding: 0 .2rem;
  display: grid; place-items: center; border-radius: 999px;
  background: var(--y-red); color: var(--y-white);
  font-family: var(--font-mono); font-size: .55rem; font-weight: 700;
  box-shadow: 0 0 0 2px var(--surface-1);
  animation: cmxPing .5s var(--ease-spring);
}
.cmxBell__badge[hidden] { display: none; }
@keyframes cmxPing { from { scale: 0; } to { scale: 1; } }

.cmxBell__panel {
  position: absolute; top: calc(100% + .6rem); right: 0; z-index: 40;
  width: min(22rem, 82vw); max-height: 24rem; overflow-y: auto;
  border-radius: var(--radius); border: 1px solid var(--cmx-hair);
  background: var(--surface-1); box-shadow: 0 30px 70px -30px rgb(0 0 0 / .9);
  animation: cmxDrop .24s var(--ease-out);
}
.cmxBell__panel[hidden] { display: none; }
@keyframes cmxDrop { from { opacity: 0; translate: 0 -8px; } to { opacity: 1; translate: 0 0; } }

.cmxBell__panel header {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--cmx-hair);
  position: sticky; top: 0; background: var(--surface-1);
}
.cmxBell__panel header b { font-size: var(--fs-sm); color: var(--text-hi); }
.cmxBell__panel ul { list-style: none; margin: 0; padding: 0; }
.cmxBell__panel li { border-bottom: 1px solid var(--cmx-hair); }
.cmxBell__panel li:last-child { border-bottom: 0; }
.cmxBell__panel a {
  display: grid; gap: .15rem; padding: var(--s-3) var(--s-4); text-decoration: none;
  transition: background-color var(--dur) var(--ease-out);
}
.cmxBell__panel a:hover { background: var(--cmx-pane); }
.cmxBell__row b { font-size: var(--fs-sm); color: var(--text-hi); }
.cmxBell__row i { font-style: normal; font-size: var(--fs-xs); color: var(--text-mid); display: block;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxBell__row time { font-family: var(--font-mono); font-size: .55rem; color: var(--text-lo); }
/* Unread carries a left bar. Colour alone would be the only signal, and it is
   the one a colour-blind reader cannot use. */
.cmxBell__panel li.is-unread a { border-left: 3px solid var(--y-yellow); background: rgb(240 198 74 / .05); }
.cmxBell__empty { padding: var(--s-8) var(--s-4); text-align: center; color: var(--text-lo); font-size: var(--fs-sm); }

@media (prefers-reduced-motion: reduce) {
  .cmxBell__badge, .cmxBell__panel { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   LIVE SEARCH RESULTS
   ══════════════════════════════════════════════════════════════════════════ */
.cmpLive { display: grid; gap: .3rem; margin-top: var(--s-4); }
.cmpLive[hidden] { display: none; }
.cmpLive__row {
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--s-4);
  padding: var(--s-3) var(--s-4); border-radius: var(--radius);
  border: 1px solid transparent; text-decoration: none;
  animation: cmpRowIn .28s var(--ease-out) backwards;
  transition: background-color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out), translate var(--dur) var(--ease-out);
}
@keyframes cmpRowIn { from { opacity: 0; translate: 0 6px; } to { opacity: 1; translate: 0 0; } }
.cmpLive__row:hover {
  background: var(--cmx-pane); border-color: rgb(240 198 74 / .35); translate: 3px 0;
}
.cmpLive__body { min-width: 0; display: grid; gap: .1rem; }
.cmpLive__body b { font-size: var(--fs-body); color: var(--text-hi); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmpLive__body i { font-style: normal; font-family: var(--font-mono); font-size: .65rem;
  color: var(--accent-ink); }
.cmpLive__bio { font-size: var(--fs-xs); color: var(--text-lo);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmpLive__meta { font-family: var(--font-mono); font-size: .62rem; color: var(--text-lo);
  white-space: nowrap; }
.cmpLive__empty { padding: var(--s-10) 0; text-align: center; color: var(--text-lo); margin: 0; }

/* ══════════════════════════════════════════════════════════════════════════
   PROFILE PEEK
   ══════════════════════════════════════════════════════════════════════════ */
.cmxMsg__av.is-linked, .cmxMsg__who b.is-linked { cursor: pointer; }
.cmxMsg__av.is-linked .ycAv { transition: scale var(--dur) var(--ease-spring); }
.cmxMsg__av.is-linked:hover .ycAv { scale: 1.1; }
.cmxMsg__who b.is-linked:hover { color: var(--accent-ink); text-decoration: underline;
  text-underline-offset: 3px; }
.cmxMsg__av.is-linked:focus-visible, .cmxMsg__who b.is-linked:focus-visible {
  outline: 2px solid var(--y-yellow); outline-offset: 3px; border-radius: var(--radius-sm);
}

.cmxPeek {
  position: fixed; inset: 0; z-index: 340;
  display: grid; place-items: center; padding: var(--gutter);
  animation: cmxFadeIn .22s var(--ease-out);
}
.cmxPeek[hidden] { display: none; }
@keyframes cmxFadeIn { from { opacity: 0; } to { opacity: 1; } }

.cmxPeek__veil {
  position: absolute; inset: 0; border: 0; cursor: zoom-out;
  background: rgb(4 4 6 / .8); backdrop-filter: blur(10px);
}
.cmxPeek__box {
  position: relative; z-index: 1;
  width: min(22rem, 100%); padding: var(--s-8) var(--s-6) var(--s-6);
  display: grid; justify-items: center; gap: var(--s-2); text-align: center;
  border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: linear-gradient(170deg, var(--cmx-pane2), transparent 55%), var(--surface-1);
  box-shadow: 0 40px 100px -35px rgb(0 0 0 / .95);
  animation: cmxPeekIn .3s var(--ease-spring);
}
@keyframes cmxPeekIn { from { opacity: 0; scale: .92; translate: 0 10px; } to { opacity: 1; scale: 1; translate: 0 0; } }

.cmxPeek__x {
  position: absolute; top: var(--s-3); right: var(--s-3);
  width: 1.9rem; height: 1.9rem; display: grid; place-items: center; cursor: pointer;
  border-radius: 50%; border: 1px solid var(--cmx-hair); background: none;
  color: var(--text-lo); font-size: 1.1rem; line-height: 1;
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              rotate var(--dur) var(--ease-out);
}
.cmxPeek__x:hover { background: var(--y-red); color: var(--y-white); border-color: transparent; rotate: 90deg; }

.cmxPeek__av .ycAv { box-shadow: 0 0 0 3px rgb(240 198 74 / .25); }
.cmxPeek__name { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  color: var(--text-hi); margin: var(--s-2) 0 0; line-height: 1.2; }
.cmxPeek__at { margin: 0; font-family: var(--font-mono); font-size: var(--fs-xs); color: var(--accent-ink); }
.cmxPeek__bio { margin: var(--s-2) 0 0; font-size: var(--fs-sm); color: var(--text-mid);
  line-height: var(--lh-body); }
.cmxPeek__bio[hidden] { display: none; }
.cmxPeek__meta { margin: 0; font-size: var(--fs-xs); color: var(--text-lo); }

.cmxPeek__stats {
  display: flex; flex-wrap: wrap; justify-content: center; gap: var(--s-4);
  margin: var(--s-3) 0; padding: var(--s-3) 0;
  border-block: 1px solid var(--cmx-hair); width: 100%;
  font-size: var(--fs-xs); color: var(--text-lo);
}
.cmxPeek__stats b { color: var(--text-hi); font-size: var(--fs-body); }
.cmxPeek__stats [hidden] { display: none; }

.cmxPeek__act { display: grid; gap: var(--s-2); width: 100%; }
.cmxPeek__act [hidden] { display: none; }

@media (prefers-reduced-motion: reduce) {
  .cmxPeek, .cmxPeek__box, .cmpLive__row { animation: none; }
  .cmpLive__row:hover, .cmxMsg__av.is-linked:hover .ycAv { translate: none; scale: 1; }
}

/* ── ANONYMOUS PEEK + REVEAL ─────────────────────────────────────────────── */
.cmxPeek__anon { width: 100%; display: grid; gap: var(--s-2); margin: var(--s-2) 0; }
.cmxPeek__anon[hidden] { display: none; }
.cmxPeek__anonLead {
  margin: 0; font-size: var(--fs-sm); color: var(--text-hi);
  padding: var(--s-2) var(--s-3); border-radius: var(--radius);
  background: var(--surface-2); border: 1px dashed var(--cmx-hair);
}
.cmxPeek__anonLead b { color: var(--accent-ink); }
.cmxPeek__anonBody { margin: 0; font-size: var(--fs-xs); color: var(--text-lo);
  line-height: var(--lh-body); text-align: left; }
.cmxPeek__askState {
  margin: 0; font-size: var(--fs-xs); color: var(--text-lo);
  padding: var(--s-2); border-radius: var(--radius); background: var(--cmx-pane);
}
.cmxPeek__askState[hidden] { display: none; }

.cmxBell__ask {
  padding: var(--s-4); background: rgb(240 198 74 / .07);
  border-left: 3px solid var(--y-yellow);
}
.cmxBell__ask p { margin: 0 0 var(--s-3); font-size: var(--fs-sm); color: var(--text-mid);
  line-height: var(--lh-body); }
.cmxBell__ask p b { color: var(--text-hi); }
.cmxBell__askAct { display: grid; gap: var(--s-2); }
.cmxBell__askAct .cmxBtn { width: 100%; font-size: var(--fs-xs); padding: .5rem .9rem; }
.cmxBell__askDone { margin: 0 !important; font-size: var(--fs-xs); color: var(--accent-ink); }

/* ── ATTACHMENTS ─────────────────────────────────────────────────────────── */
.cmxClip { position: relative; cursor: pointer; }
.cmxClip input { position: absolute; inset: 0; opacity: 0; width: 0; height: 0; }
.cmxClip:focus-within { border-color: var(--y-yellow); color: var(--accent-ink); }

.cmxShot {
  /* A block box does not answer to text-align, so an image inside a
     right-aligned message stayed pinned to the left of its own bubble column.
     margin-inline-start: auto is what actually moves it.

     THE BOX HAS ITS PHOTO'S SHAPE BEFORE THE PHOTO ARRIVES. It used to size
     to its content, and its content was a lazy image sized to it — so both
     were 0×0, a 0×0 lazy image is never "near the viewport", and the photo
     never loaded at all. --arn (width ÷ height, from cm_media_ratio) gives it
     a real width and height up front: tall photos get a narrower, taller box
     so they show whole instead of being cropped into a zoomed-in slice. */
  --arn: 1.3333;
  display: block; box-sizing: border-box;
  width: min(18rem, calc(100vw - 9.5rem), calc(21rem * var(--arn)));
  aspect-ratio: var(--arn);
  border-radius: var(--radius);
  overflow: hidden; border: 1px solid var(--cmx-hair);
  background: linear-gradient(110deg, var(--surface-2) 30%, rgb(255 255 255 / .06) 50%, var(--surface-2) 70%) 0 0 / 250% 100%;
  animation: cmxShotWait 1.4s linear infinite;
  transition: border-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-out);
}
.cmxShot:has(img[data-ok]) { animation: none; background: var(--surface-2); }
@keyframes cmxShotWait { to { background-position: -150% 0; } }
.cmxShot:hover { border-color: rgb(240 198 74 / .5); scale: 1.01; }
.cmxShot img {
  display: block; width: 100%; height: 100%; max-width: none; max-height: none;
  object-fit: cover;
}

.cmxUp {
  display: flex; align-items: center; gap: var(--s-3);
  margin-bottom: var(--s-2); padding: var(--s-2) var(--s-3);
  border-radius: var(--radius); border: 1px solid var(--cmx-hair);
  background: var(--cmx-pane); font-size: var(--fs-xs); color: var(--text-mid);
}
.cmxUp[hidden] { display: none; }
.cmxUp__bar { flex: 0 0 4rem; height: 3px; border-radius: 3px; background: var(--surface-2); overflow: hidden; display: none; }
.cmxUp.is-busy .cmxUp__bar { display: block; }
/* Indeterminate on purpose: fetch() gives no upload progress without dropping
   to XHR, and a fake percentage that jumps to 90 and waits is worse than an
   honest shuttle. */
.cmxUp__bar i {
  display: block; width: 40%; height: 100%;
  background: linear-gradient(to right, var(--y-yellow-700), var(--y-yellow));
  animation: cmxShuttle 1.1s var(--ease-inout) infinite;
}
@keyframes cmxShuttle { from { translate: -100% 0; } to { translate: 250% 0; } }

@media (prefers-reduced-motion: reduce) {
  .cmxUp__bar i { animation: none; width: 100%; }
  .cmxShot:hover { scale: 1; }
}

.cmxMsg.is-mine .cmxShot  { margin-left: auto; }
.cmxMsg.is-mine .cmxSticker,
.cmxMsg.is-mine .cmxGif    { margin-left: auto; }
/* The sticker is a text glyph, so it DOES follow text-align — but the GIF card
   is inline-flex and the sticker sits in a block, so both are pinned
   explicitly rather than relying on which one happens to inherit. */

/* ── NOTIFICATION AVATARS ────────────────────────────────────────────────── */
.cmxBell__panel a.cmxBell__row {
  grid-template-columns: auto minmax(0, 1fr);
  align-items: start; gap: var(--s-3);
}
.cmxBell__av { flex: none; }
.cmxBell__txt { min-width: 0; display: grid; gap: .1rem; }
.cmxBell__askHead { display: flex; align-items: flex-start; gap: var(--s-3); margin-bottom: var(--s-3); }
.cmxBell__askHead p { margin: 0 !important; }

/* ══════════════════════════════════════════════════════════════════════════
   IMAGE VIEWER
   ══════════════════════════════════════════════════════════════════════════ */
button.cmxShot { position: relative; padding: 0; cursor: zoom-in; border: 1px solid var(--cmx-hair); }
.cmxShot__hint {
  position: absolute; top: .5rem; right: .5rem;
  width: 1.7rem; height: 1.7rem; display: grid; place-items: center;
  border-radius: 50%; background: rgb(8 8 11 / .7); backdrop-filter: blur(6px);
  color: var(--y-white); font-size: .8rem;
  opacity: 0; transition: opacity var(--dur) var(--ease-out);
}
.cmxShot:hover .cmxShot__hint, .cmxShot:focus-visible .cmxShot__hint { opacity: 1; }

.cmxView {
  position: fixed; inset: 0; z-index: 400;
  display: grid; place-items: center; padding: clamp(.4rem, 2vw, 1.5rem);
  animation: cmxFadeIn .22s var(--ease-out);
}
.cmxView[hidden] { display: none; }
.cmxView__veil {
  position: absolute; inset: 0; border: 0; cursor: zoom-out;
  background: rgb(3 3 5 / .93); backdrop-filter: blur(10px);
}
.cmxView__shell {
  position: relative; z-index: 1;
  width: min(78rem, 100%); height: min(50rem, 94dvh);
  display: grid;
  /* The COLUMN is declared, not just the rows. With rows alone the implicit
     column is max-content and the toolbar's unwrapped width would stretch the
     shell past the viewport — the same trap the flashcard popup hit. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--cmx-hair); background: var(--surface-1);
  box-shadow: 0 50px 130px -40px rgb(0 0 0 / .95);
  animation: cmxPeekIn .3s var(--ease-out);
}

.cmxView__bar {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--cmx-hair);
  background: var(--cmx-pane);
}
.cmxView__by { flex: 1; min-width: 0; display: flex; align-items: baseline; gap: var(--s-2); }
.cmxView__by b { font-size: var(--fs-sm); color: var(--text-hi);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxView__by time { font-family: var(--font-mono); font-size: .6rem; color: var(--text-lo); }

.cmxView__tools { display: flex; align-items: center; gap: .4rem; flex: none; }
.cmxView__tools .cmxIcon { width: 2.1rem; height: 2.1rem; text-decoration: none; }

/* Every control is a drawn path, not a text glyph. ⭳ has no glyph in the macOS
   UI font and rendered as a solid black bar — the most important button in the
   viewer looked like a rendering fault. A path is a path on every device. */
.cmxView svg, .cmxPhoto svg {
  width: 1.05rem; height: 1.05rem; fill: none;
  stroke: currentColor; stroke-width: 1.9;
  stroke-linecap: round; stroke-linejoin: round;
}

/* Zoom is one grouped control rather than three loose circles, so the row
   parses as [zoom] [rotate] [link] [DOWNLOAD] [close] at a glance. */
.cmxView__zoom {
  display: flex; align-items: center; gap: .1rem;
  padding: .15rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane);
}
.cmxView__zoom .cmxIcon { border: 0; background: none; width: 1.8rem; height: 1.8rem; }
.cmxView__pct {
  font-family: var(--font-mono); font-size: .62rem; color: var(--text-lo);
  min-width: 2.9rem; text-align: center; user-select: none;
}

/* DOWNLOAD is the only labelled control in the bar, and that is the point.
   Saving the picture is what people open a viewer for; leaving it as one
   ambiguous icon among six makes the common action the hardest to find. */
.cmxView__dl {
  display: inline-flex; align-items: center; gap: .45rem; flex: none;
  padding: .45rem .85rem; border-radius: var(--radius-pill);
  background: var(--y-yellow); color: var(--y-black);
  font-size: var(--fs-xs); font-weight: 700; text-decoration: none; cursor: pointer;
  box-shadow: var(--glow-yellow);
  transition: filter var(--dur) var(--ease-out), box-shadow var(--dur) var(--ease-out);
}
.cmxView__dl:hover { filter: brightness(1.08); box-shadow: var(--glow-yellow), 0 6px 18px -6px rgb(240 198 74 / .5); }
.cmxView__dl svg { stroke-width: 2.2; }

/* Paging arrows, over the image. Keyboard has ← →, touch has swipe; a mouse
   had neither. Hidden by the script when there is only one image. */
.cmxView__nav {
  position: absolute; top: 50%; translate: 0 -50%; z-index: 2;
  width: 2.6rem; height: 2.6rem; display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  border: 1px solid var(--cmx-hair); background: rgb(8 8 11 / .72);
  backdrop-filter: blur(8px); color: var(--text-hi);
  opacity: 0; transition: opacity var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}
.cmxView__nav[hidden] { display: none; }
.cmxView__nav--prev { left: var(--s-3); }
.cmxView__nav--next { right: var(--s-3); }
.cmxView__pane:hover .cmxView__nav, .cmxView__nav:focus-visible { opacity: 1; }
.cmxView__nav:hover { background: rgb(240 198 74 / .9); color: var(--y-black); }

.cmxView__pane {
  position: relative;
  overflow: hidden; min-height: 0; display: grid; place-items: center;
  /* A checker under a transparent PNG, so alpha reads as alpha rather than as
     the page showing through a hole in the image. */
  background:
    repeating-conic-gradient(rgb(255 255 255 / .022) 0% 25%, transparent 0% 50%) 50% / 24px 24px,
    #06070a;
  cursor: default;
}
.cmxView__pane.is-zoomed { cursor: grab; }
.cmxView__pane.is-panning { cursor: grabbing; }
/* The picture FILLS the pane's box and is fitted inside it. It used to be
   max-height:100% — which resolves against an auto grid row, i.e. to nothing —
   so only the width was bounded: a portrait photo rendered taller than the
   pane and was cut off top and bottom, which read as "zoomed in".
   scale-down = the whole picture always, and never blown up past its real
   size (a small image stays sharp instead of turning to mush). */
.cmxView__pane img {
  position: absolute; inset: 0;
  width: 100%; height: 100%; max-width: none; max-height: none;
  object-fit: scale-down;
  /* Composed from custom properties the script writes — it never sets
     `transform` itself, so nothing here can be clobbered by another writer. */
  translate: var(--x, 0px) var(--y, 0px);
  scale: var(--z, 1);
  rotate: var(--r, 0deg);
  transition: scale .18s var(--ease-out), rotate .3s var(--ease-out);
  user-select: none; -webkit-user-drag: none;
}
.cmxView__pane.is-panning img { transition: none; }

.cmxView__foot {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-top: 1px solid var(--cmx-hair);
  font-family: var(--font-mono); font-size: .62rem; color: var(--text-lo);
}
.cmxView__keys kbd {
  padding: .05rem .3rem; border-radius: 4px;
  border: 1px solid var(--cmx-hair); background: var(--surface-2);
}

/* ── viewer on a tablet ─────────────────────────────────────────────────── */
@media (max-width: 60rem) {
  /* Copy-link is a desktop habit — there is nowhere obvious to paste it on a
     tablet, and the share sheet is a long-press away on the image itself. */
  .cmxView__tools [data-view-copy] { display: none; }
}

/* ── viewer on a phone ──────────────────────────────────────────────────── */
@media (max-width: 46rem) {
  .cmxView { padding: 0; }
  /* Edge to edge. A 12px gutter around a photo on a 375px screen costs 6% of
     the only thing the user opened this to look at. */
  .cmxView__shell { width: 100%; height: 100dvh; border-radius: 0; border: 0; }

  .cmxView__bar { padding: var(--s-3); gap: var(--s-2); }
  .cmxView__by b { font-size: var(--fs-xs); }
  /* The keyboard hints name keys a phone does not have. */
  .cmxView__keys { display: none; }
  .cmxView__foot { padding: var(--s-2) var(--s-3); }

  /* Zoom and rotate go: pinch already zooms, double-tap already toggles, and
     the device rotates the whole screen. What is left is the one control a
     phone genuinely cannot do for itself — DOWNLOAD — plus close. */
  .cmxView__zoom,
  .cmxView__tools [data-view-rotate] { display: none; }
  .cmxView__dl { padding: .5rem .9rem; }

  /* Arrows are permanently visible on touch, where there is no hover to
     reveal them — but only when there is more than one image to page to. */
  .cmxView__nav { opacity: 1; width: 2.3rem; height: 2.3rem; }
}

@media (prefers-reduced-motion: reduce) {
  .cmxView, .cmxView__shell { animation: none; }
  .cmxView__pane img { transition: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   PROFILE PHOTO — 1:1
   A portrait wants none of the image viewer's machinery. No zoom, no rotate,
   no paging: just the picture, big, square, with a way to save it.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxPhoto {
  position: fixed; inset: 0; z-index: 420;
  display: grid; place-items: center; padding: clamp(1rem, 5vw, 3rem);
  animation: cmxFadeIn .2s var(--ease-out);
}
.cmxPhoto[hidden] { display: none; }
.cmxPhoto__veil {
  position: absolute; inset: 0; border: 0; cursor: zoom-out;
  background: rgb(3 3 5 / .92); backdrop-filter: blur(14px);
}
.cmxPhoto__box {
  position: relative; z-index: 1; margin: 0;
  width: min(30rem, 100%);
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--cmx-hair); background: var(--surface-1);
  box-shadow: 0 50px 130px -40px rgb(0 0 0 / .95);
  animation: cmxPeekIn .28s var(--ease-out);
}

/* THE SQUARE. aspect-ratio: 1 on the frame and object-fit: cover on the image,
   so a portrait or a landscape upload is CENTRE-CROPPED to 1:1 rather than
   letterboxed. A square frame with bars down the sides is not a square frame,
   it is a rectangle with a border. */
.cmxPhoto__frame {
  aspect-ratio: 1; width: 100%;
  background:
    repeating-conic-gradient(rgb(255 255 255 / .02) 0% 25%, transparent 0% 50%) 50% / 22px 22px,
    #06070a;
}
.cmxPhoto__frame img {
  width: 100%; height: 100%; object-fit: cover; object-position: 50% 30%;
  display: block; user-select: none; -webkit-user-drag: none;
}

.cmxPhoto__cap {
  display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-top: 1px solid var(--cmx-hair);
  background: var(--cmx-pane);
}
.cmxPhoto__cap b { font-size: var(--fs-sm); color: var(--text-hi); min-width: 0;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxPhoto__dl {
  display: inline-flex; align-items: center; gap: .4rem; flex: none;
  padding: .4rem .8rem; border-radius: var(--radius-pill);
  background: var(--y-yellow); color: var(--y-black);
  font-size: var(--fs-xs); font-weight: 700; text-decoration: none; cursor: pointer;
  transition: filter var(--dur) var(--ease-out);
}
.cmxPhoto__dl:hover { filter: brightness(1.08); }
.cmxPhoto__x {
  position: absolute; top: var(--s-3); right: var(--s-3); z-index: 2;
  background: rgb(8 8 11 / .72); backdrop-filter: blur(8px);
}
.cmxPhoto__x:hover { background: var(--y-red); color: var(--y-white); border-color: transparent; }


@media (max-width: 46rem) {
  .cmxPhoto { padding: var(--s-3); }
  .cmxPhoto__box { width: 100%; }
}
@media (prefers-reduced-motion: reduce) {
  .cmxPhoto, .cmxPhoto__box { animation: none; }
}

/* ══════════════════════════════════════════════════════════════════════════
   DIRECT MESSAGES
   Two panes on a desktop; on a phone the list IS the page and the conversation
   replaces it. A 320px column trying to hold both is neither.
   ══════════════════════════════════════════════════════════════════════════ */
.dmShell {
  position: relative; z-index: 1;
  max-width: 76rem; margin-inline: auto;
  padding: var(--s-5) var(--gutter) 0;
  display: grid;
  grid-template-columns: 21rem minmax(0, 1fr);
  gap: var(--s-4);
  height: calc(100svh - 5.5rem);
}

/* ── the list ──────────────────────────────────────────────────────────── */
.dmList {
  min-height: 0; display: grid;
  /* Column declared, not just rows — with rows alone the implicit column
     resolves to max-content and the rows size to their longest preview. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto minmax(0, 1fr);
  border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: var(--surface-1); overflow: hidden;
}
.dmList__head {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-4) var(--s-4) var(--s-3);
}
.dmList__head h1 { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  color: var(--text-hi); margin: 0; }

.dmTabs { display: flex; gap: .2rem; padding: 0 var(--s-3) var(--s-3); }
.dmTab {
  flex: 1; display: inline-flex; align-items: center; justify-content: center; gap: .35rem;
  padding: .45rem .3rem; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid transparent; background: none; font: inherit;
  font-size: var(--fs-xs); font-weight: 600; color: var(--text-lo);
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.dmTab:hover { color: var(--text-hi); background: var(--cmx-pane); }
.dmTab.is-on { background: var(--cmx-pane2); color: var(--text-hi); border-color: var(--cmx-hair); }
.dmTab__n {
  font-family: var(--font-mono); font-size: .58rem; padding: .05rem .3rem;
  border-radius: var(--radius-pill); background: var(--y-yellow); color: var(--y-black);
}

.dmPane { display: none; overflow-y: auto; min-height: 0; padding: 0 var(--s-2) var(--s-3); }
.dmPane.is-on { display: block; }
.dmEmpty { margin: var(--s-5) var(--s-3); font-size: var(--fs-sm); color: var(--text-lo);
  line-height: var(--lh-body); }

.dmRow {
  position: relative; isolation: isolate;
  display: grid; grid-template-columns: auto minmax(0, 1fr) auto;
  align-items: center; gap: var(--s-3);
  padding: var(--s-3); border-radius: var(--radius); text-decoration: none;
  transition: background-color var(--dur) var(--ease-out);
}
/* Same no-jitter rule as the room list: the accent bar moves, the row does not. */
.dmRow::before {
  content: ''; position: absolute; z-index: -1; left: 0; top: .5rem; bottom: .5rem;
  width: 3px; border-radius: 3px; background: var(--y-yellow);
  scale: 1 0; transition: scale var(--dur) var(--ease-spring);
}
.dmRow:hover { background: var(--cmx-pane); }
.dmRow:hover::before, .dmRow.is-on::before { scale: 1 1; }
.dmRow.is-on { background: var(--cmx-pane2); }
.dmRow__body { min-width: 0; }
.dmRow__body b { display: block; font-size: var(--fs-sm); color: var(--text-hi); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dmRow__body i { display: block; font-style: normal; font-size: var(--fs-xs); color: var(--text-lo);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dmRow__meta { display: grid; gap: .25rem; justify-items: end; flex: none; }
.dmRow__meta time { font-family: var(--font-mono); font-size: .58rem; color: var(--text-lo); }
.dmRow__n {
  min-width: 1.15rem; padding: .05rem .3rem; border-radius: var(--radius-pill);
  background: var(--y-yellow); color: var(--y-black);
  font-family: var(--font-mono); font-size: .58rem; font-weight: 700; text-align: center;
}
.dmRow__go { font-size: .62rem; color: var(--accent-ink); font-weight: 600; }

.dmReq {
  display: grid; grid-template-columns: auto minmax(0, 1fr);
  gap: var(--s-3) var(--s-3); align-items: center;
  padding: var(--s-3); border-radius: var(--radius);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane);
  margin-bottom: var(--s-2);
}
.dmReq__body { min-width: 0; }
.dmReq__body b { display: block; font-size: var(--fs-sm); color: var(--text-hi);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dmReq__body i {
  display: block; font-style: normal; font-size: var(--fs-xs); color: var(--text-lo);
  /* One line. A college name wrapping to two pushes the row taller and leaves
     the avatar floating against the middle of a paragraph. */
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.dmReq__act { grid-column: 1 / -1; display: flex; gap: var(--s-2); }
.dmReq__act .cmxBtn { flex: 1; }
.cmxBtn--sm { padding: .4rem .7rem; font-size: var(--fs-xs); }
.dmReq__done { grid-column: 1 / -1; margin: 0; font-size: var(--fs-xs); color: var(--text-lo); }

/* ── the conversation ──────────────────────────────────────────────────── */
.dmMain {
  min-height: 0; min-width: 0; display: grid;
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: linear-gradient(180deg, var(--cmx-pane2), transparent 30%), var(--surface-1);
  overflow: hidden;
}
.dmBlank {
  grid-row: 1 / -1; display: grid; place-content: center; justify-items: center;
  gap: var(--s-3); padding: var(--s-8); text-align: center;
}
.dmBlank__ico { font-size: 2.4rem; margin: 0; }
.dmBlank h2 { font-family: var(--font-display); font-size: var(--fs-h3); font-weight: 500;
  color: var(--text-hi); margin: 0; }
.dmBlank p { margin: 0; color: var(--text-mid); max-width: 38ch; line-height: var(--lh-body); }

.dmTop {
  display: flex; align-items: center; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); border-bottom: 1px solid var(--cmx-hair);
}
.dmTop__back { display: none; font-size: 1.4rem; text-decoration: none; }
.dmTop__who { display: flex; align-items: center; gap: var(--s-3); text-decoration: none;
  min-width: 0; flex: 1; }
.dmTop__who b { display: block; font-size: var(--fs-body); color: var(--text-hi); font-weight: 600;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.dmTop__who i { font-style: normal; font-family: var(--font-mono); font-size: .62rem;
  color: var(--accent-ink); }
.dmTop__tag { flex: none; font-size: .62rem; color: var(--text-lo);
  padding: .2rem .5rem; border-radius: var(--radius-pill); background: var(--cmx-pane); }

.dmLog {
  overflow-y: auto; min-height: 0; scroll-behavior: smooth;
  padding: var(--s-5) var(--s-5) var(--s-2);
  display: flex; flex-direction: column; gap: var(--s-3);
}
.dmLog__start { margin: auto auto var(--s-5); text-align: center; font-size: var(--fs-sm);
  color: var(--text-lo); max-width: 34ch; line-height: var(--lh-body); }
.dmLog__start b { color: var(--text-mid); }

.dmMsg {
  max-width: min(72%, 34rem); align-self: flex-start;
  display: grid; gap: .2rem; justify-items: start;
  animation: cmxRise .26s var(--ease-out) both;
}
.dmMsg.is-mine { align-self: flex-end; justify-items: end; }
.dmBubble {
  padding: .6rem .9rem; border-radius: 1.1rem 1.1rem 1.1rem .3rem;
  background: var(--surface-2); color: var(--text-hi);
  font-size: var(--fs-sm); line-height: var(--lh-body);
  overflow-wrap: anywhere;
}
.dmMsg.is-mine .dmBubble {
  border-radius: 1.1rem 1.1rem .3rem 1.1rem;
  background: var(--y-yellow); color: var(--y-black); font-weight: 500;
}
.dmMsg__meta { margin: 0; display: flex; align-items: center; gap: .35rem;
  font-family: var(--font-mono); font-size: .55rem; color: var(--text-lo); }
.dmTick { color: var(--text-lo); }
.dmTick.is-read { color: var(--y-green); }
.dmShot { width: min(16rem, calc(100vw - 6rem), calc(20rem * var(--arn))); }

.dmComposer { padding: var(--s-3) var(--s-4) var(--s-4); border-top: 1px solid var(--cmx-hair); }
.dmGone { margin: 0; padding: var(--s-3); text-align: center; font-size: var(--fs-sm);
  color: var(--text-lo); border-radius: var(--radius); background: var(--cmx-pane); }

/* ── DM responsive ─────────────────────────────────────────────────────── */
@media (max-width: 64rem) {
  .dmShell { grid-template-columns: 17rem minmax(0, 1fr); }
}

@media (max-width: 52rem) {
  /* ONE PANE AT A TIME. Which one is decided by the SERVER — .is-open is set
     when a thread is open — rather than by a script, so the right pane is
     painted on the first frame instead of flashing the wrong one. */
  .dmShell {
    grid-template-columns: minmax(0, 1fr);
    padding-inline: var(--s-3);
    height: calc(100svh - 4.5rem);
  }
  .dmShell .dmMain { display: none; }
  .dmShell.is-open .dmList { display: none; }
  .dmShell.is-open .dmMain { display: grid; }
  .dmTop__back { display: grid; }
  .dmMsg { max-width: 84%; }
  .dmLog { padding: var(--s-4) var(--s-3) var(--s-2); }
  .dmComposer { padding: var(--s-3); }
  .dmList { border-radius: var(--radius); }
  /* 16px — anything smaller and iOS zooms the whole page on focus. */
  .dmComposer input[type="text"] { font-size: 1rem; }
}

/* Copy-link confirmation. A class, not a text swap: the button's content is an
   inline <svg>, and writing "✓" into it would delete the icon permanently. */
.cmxView__tools [data-view-copy].is-done { color: var(--y-green); border-color: var(--y-green); }
.cmxView__tools [data-view-copy].is-done svg { stroke-dasharray: 0; }

/* The way into Messages, with the only two counts on the page that mean
   "something is waiting on you". */
.cmxRail__msgs { display: flex; align-items: center; justify-content: center; gap: var(--s-2); }
.cmxRail__n {
  min-width: 1.2rem; padding: .05rem .35rem; border-radius: var(--radius-pill);
  background: var(--y-yellow); color: var(--y-black);
  font-family: var(--font-mono); font-size: .6rem; font-weight: 700;
}
.cmxRail__foot { display: grid; gap: var(--s-2); }

/* Follow and Connect sit side by side on a discover card. Same reason they are
   two buttons everywhere else: two different decisions. */
.cmpCard__act { display: flex; gap: var(--s-2); flex-wrap: wrap; }
.cmpCard__act .cmxBtn { flex: 1; min-width: 6rem; }


/* Connect / Message inside the profile peek card. */
.cmxPeek__row { display: flex; gap: var(--s-2); }
.cmxPeek__row .cmxBtn { flex: 1; }
.cmxPeek__connState {
  margin: 0; font-size: var(--fs-xs); color: var(--text-lo);
  line-height: var(--lh-body); text-align: center;
}
.cmxPeek__connState[hidden] { display: none; }



/* ══════════════════════════════════════════════════════════════════════════
   DELETED MESSAGES
   A tombstone, never a gap. A conversation that silently loses a line leaves
   people re-reading for something that is not there any more.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxGone {
  display: inline-flex; align-items: center; gap: .4rem;
  padding: .5rem .8rem; border-radius: 1.1rem;
  border: 1px dashed var(--cmx-hair); background: transparent;
  font-size: var(--fs-sm); font-style: italic; color: var(--text-lo);
}
.cmxGone span { font-style: normal; opacity: .7; }

/* The delete menu. Two items, each saying what it actually does — "delete" on
   its own is the word people mean two different things by. */
.cmxDel {
  position: fixed; z-index: 500; width: 17rem; padding: .35rem;
  border-radius: var(--radius); border: 1px solid var(--cmx-hair);
  background: var(--surface-2); backdrop-filter: var(--cmx-glass);
  box-shadow: 0 24px 60px -20px rgb(0 0 0 / .9);
  animation: cmxRise .16s var(--ease-out);
}
.cmxDel__item {
  display: grid; gap: .15rem; width: 100%; text-align: left;
  padding: .55rem .7rem; border: 0; border-radius: var(--radius);
  background: none; cursor: pointer; font: inherit;
  transition: background-color var(--dur) var(--ease-out);
}
.cmxDel__item b { font-size: var(--fs-sm); font-weight: 600; color: var(--text-hi); }
.cmxDel__item i { font-style: normal; font-size: var(--fs-xs); color: var(--text-lo);
  line-height: var(--lh-body); }
.cmxDel__item:hover { background: var(--cmx-pane2); }
.cmxDel__item.is-danger:hover { background: rgb(232 84 74 / .14); }
.cmxDel__item.is-danger:hover b { color: var(--y-red); }
.cmxDel__cancel {
  width: 100%; padding: .45rem; margin-top: .2rem; border: 0; border-radius: var(--radius);
  background: none; cursor: pointer; font: inherit; font-size: var(--fs-xs);
  color: var(--text-lo); border-top: 1px solid var(--cmx-hair);
}
.cmxDel__cancel:hover { color: var(--text-hi); }

/* The DM ••• — the same control, in the same place relative to the bubble, as
   the rooms. One gesture to learn rather than two.

   It hangs in the gutter on the OUTER side of the bubble, so it never covers
   the text. .dmMsg is capped at 72% width, which is what guarantees the gutter
   exists to hang it in. */
.dmMsg { position: relative; }
.dmMsg__more {
  position: absolute; top: .35rem; z-index: 3;
  width: 1.7rem; height: 1.7rem; display: grid; place-items: center;
  border-radius: 50%; cursor: pointer;
  border: 1px solid var(--cmx-hair); background: var(--surface-2); color: var(--text-lo);
  opacity: 0;
  transition: opacity var(--dur) var(--ease-out), color var(--dur) var(--ease-out),
              border-color var(--dur) var(--ease-out);
}
.dmMsg__more svg { width: .95rem; height: .95rem; }
.dmMsg.is-mine .dmMsg__more { left: -2.2rem; }
.dmMsg:not(.is-mine) .dmMsg__more { right: -2.2rem; }
.dmMsg:hover .dmMsg__more, .dmMsg__more:focus-visible { opacity: 1; }
.dmMsg__more:hover { color: var(--text-hi); border-color: rgb(240 198 74 / .5); }

@media (hover: none) {
  /* Permanently visible, because there is no hover to reveal it with — and
     tucked INSIDE the bubble column, since a 2.2rem gutter does not exist at
     phone widths and the button would sit off the screen edge. */
  .dmMsg__more { opacity: .5; top: -.35rem; }
  .dmMsg.is-mine .dmMsg__more { left: auto; right: -.2rem; }
  .dmMsg:not(.is-mine) .dmMsg__more { right: auto; left: -.2rem; }
}

/* Reactions inside the menu, for touch — there is no hover row to tap there. */
/* A grid, not a flex row: the touch menu carries the whole CM_REACTIONS set
   now (24), and a single row of flex:1 buttons squeezed them to slivers. */
.cmxDel__rx {
  display: grid; grid-template-columns: repeat(6, 1fr); gap: .2rem; padding: .35rem .3rem;
  border-bottom: 1px solid var(--cmx-hair); margin-bottom: .2rem;
}
.cmxDel__emo {
  padding: .35rem 0; border: 0; border-radius: var(--radius);
  background: none; cursor: pointer; font-size: 1.15rem; line-height: 1;
  transition: background-color var(--dur) var(--ease-out), scale var(--dur) var(--ease-spring);
}
.cmxDel__emo:active { scale: 1.25; }
.cmxDel__emo:hover { background: var(--cmx-pane2); }

/* ══════════════════════════════════════════════════════════════════════════
   QUESTIONS AND ANSWERS
   A question is a message wearing a frame — same log, same poll, same delete.
   Only the chrome differs, so only the chrome is styled here.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxQ {
  display: grid; gap: var(--s-2);
  padding: var(--s-4); border-radius: var(--radius);
  border: 1px solid rgb(240 198 74 / .28);
  background: linear-gradient(160deg, rgb(240 198 74 / .07), transparent 60%), var(--surface-2);
}
/* Answered is the state worth colouring: it is the proof the room works. */
.cmxQ.is-solved {
  border-color: rgb(62 158 95 / .38);
  background: linear-gradient(160deg, rgb(62 158 95 / .08), transparent 60%), var(--surface-2);
}
.cmxQ__tag { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; margin: 0; }
.cmxQ__badge {
  padding: .1rem .45rem; border-radius: var(--radius-pill);
  background: var(--y-yellow); color: var(--y-black);
  font-size: .55rem; font-weight: 700; letter-spacing: .06em; text-transform: uppercase;
}
.cmxQ.is-solved .cmxQ__badge { background: var(--y-green); color: var(--y-white); }
.cmxQ__unit, .cmxQ__n {
  font-family: var(--font-mono); font-size: .58rem; color: var(--text-lo);
}
.cmxQ__body { font-size: var(--fs-body); color: var(--text-hi); line-height: var(--lh-body); }
.cmxQ__reply {
  justify-self: start; padding: .35rem .8rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--cmx-pane); cursor: pointer;
  font: inherit; font-size: var(--fs-xs); font-weight: 600; color: var(--accent-ink);
  transition: border-color var(--dur) var(--ease-out), background-color var(--dur) var(--ease-out);
}
.cmxQ__reply:hover { border-color: var(--y-yellow); background: var(--cmx-pane2); }

/* ── the answers panel ──────────────────────────────────────────────────── */
.cmxAns {
  position: fixed; inset: 0; z-index: 430;
  display: grid; place-items: center; padding: clamp(.5rem, 3vw, 2rem);
  animation: cmxFadeIn .2s var(--ease-out);
}
.cmxAns[hidden] { display: none; }
.cmxAns__veil { position: absolute; inset: 0; border: 0; cursor: pointer;
  background: rgb(3 3 5 / .85); backdrop-filter: blur(10px); }
.cmxAns__box {
  position: relative; z-index: 1; width: min(38rem, 100%); max-height: 90dvh;
  display: grid; grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto minmax(0, 1fr) auto;
  border-radius: var(--radius-lg); overflow: hidden;
  border: 1px solid var(--cmx-hair); background: var(--surface-1);
  box-shadow: 0 50px 130px -40px rgb(0 0 0 / .95);
  animation: cmxPeekIn .26s var(--ease-out);
}
.cmxAns__box > header {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-4) var(--s-5); border-bottom: 1px solid var(--cmx-hair);
  background: var(--cmx-pane);
}
.cmxAns__q { margin: .35rem 0 0; font-size: var(--fs-body); color: var(--text-hi);
  line-height: var(--lh-body); }
.cmxAns__list { overflow-y: auto; min-height: 0; padding: var(--s-4) var(--s-5); display: grid; gap: var(--s-4);
  scrollbar-width: thin; scrollbar-color: rgb(240 198 74 / .45) transparent; }
.cmxAns__empty { margin: 0; font-size: var(--fs-sm); color: var(--text-lo); line-height: var(--lh-body); }

.cmxAns__one { display: grid; grid-template-columns: auto minmax(0, 1fr); gap: var(--s-3); }
.cmxAns__one.is-accepted {
  padding: var(--s-3); margin: calc(var(--s-3) * -1);
  border-radius: var(--radius);
  background: linear-gradient(160deg, rgb(62 158 95 / .1), transparent 60%);
  border: 1px solid rgb(62 158 95 / .3);
}
.cmxAns__body { min-width: 0; display: grid; gap: .25rem; }
.cmxAns__who { display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap; margin: 0; }
.cmxAns__who b { font-size: var(--fs-sm); color: var(--text-hi); }
.cmxAns__who time { font-family: var(--font-mono); font-size: .58rem; color: var(--text-lo); }
.cmxAns__tick { font-size: .6rem; font-weight: 700; color: var(--y-green); }
.cmxAns__text { margin: 0; font-size: var(--fs-sm); color: var(--text-mid);
  line-height: var(--lh-body); overflow-wrap: anywhere; }
.cmxAns__accept {
  justify-self: start; margin-top: .35rem;
  padding: .3rem .7rem; border-radius: var(--radius-pill); cursor: pointer;
  border: 1px solid rgb(62 158 95 / .45); background: rgb(62 158 95 / .1);
  font: inherit; font-size: var(--fs-xs); font-weight: 600; color: var(--y-green);
}
.cmxAns__accept:hover { background: rgb(62 158 95 / .2); }

.cmxAns__form { padding: var(--s-4) var(--s-5); border-top: 1px solid var(--cmx-hair);
  background: var(--cmx-pane); display: grid; gap: var(--s-3); }
.cmxAns__form textarea {
  width: 100%; padding: .7rem .9rem; border-radius: var(--radius); resize: vertical;
  border: 1px solid var(--cmx-hair); background: var(--surface-0); color: var(--text-hi);
  font: inherit; font-size: var(--fs-sm); line-height: var(--lh-body); outline: none;
}
.cmxAns__form textarea:focus { border-color: var(--y-yellow); }
.cmxAns__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3); }
.cmxAns__hint { font-size: var(--fs-xs); color: var(--text-lo); }

/* ── reputation ─────────────────────────────────────────────────────────────
   One number, counting one thing: answers somebody else said solved their
   problem. Not messages sent — a score that rewards volume produces volume.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxRep {
  padding: .05rem .4rem; border-radius: var(--radius-pill);
  font-size: .55rem; font-weight: 700; letter-spacing: .05em; text-transform: uppercase;
  border: 1px solid var(--cmx-hair); color: var(--text-lo);
}
.cmxRep.is-helper  { border-color: rgb(62 158 95 / .45);  color: var(--y-green); }
.cmxRep.is-regular { border-color: rgb(240 198 74 / .5);  color: var(--accent-ink); }
.cmxRep.is-legend  { border-color: rgb(240 198 74 / .8);  color: var(--y-black);
  background: var(--y-yellow); }

@media (max-width: 46rem) {
  .cmxAns { padding: 0; }
  .cmxAns__box { width: 100%; height: 100dvh; max-height: none; border-radius: 0; border: 0; }
  .cmxAns__form textarea { font-size: 1rem; }
}
@media (prefers-reduced-motion: reduce) { .cmxAns, .cmxAns__box { animation: none; } }

/* Why somebody is being suggested. "Suggested" with no reason feels arbitrary,
   and an arbitrary recommendation is one people scroll past. */
.cmpWhy {
  margin: 0; font-size: var(--fs-xs); font-weight: 600; color: var(--accent-ink);
}

/* ══════════════════════════════════════════════════════════════════════════
   COMMUNITY SUB-NAV
   Six surfaces that were reachable only from whichever one you landed on.
   One strip on all of them; a count on the two things waiting on the user and
   nothing else, because a badge on everything is a badge on nothing.
   ══════════════════════════════════════════════════════════════════════════ */
.cnBar {
  position: relative; z-index: 2;
  border-bottom: 1px solid var(--cmx-hair);
  background: linear-gradient(180deg, var(--cmx-pane), transparent);
}
.cnBar__inner {
  max-width: var(--cmx-max); margin-inline: auto;
  display: flex; gap: .2rem; align-items: center;
  padding: var(--s-2) var(--gutter);
  overflow-x: auto; scrollbar-width: none;
}
.cnBar__inner::-webkit-scrollbar { display: none; }
.cnBar__a {
  display: inline-flex; align-items: center; gap: .45rem; flex: none;
  padding: .5rem .85rem; border-radius: var(--radius-pill);
  text-decoration: none; white-space: nowrap;
  font-size: var(--fs-sm); font-weight: 600; color: var(--text-lo);
  transition: background-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out);
}
.cnBar__a:hover { color: var(--text-hi); background: var(--cmx-pane2); }
.cnBar__a.is-on { color: var(--text-hi); background: var(--cmx-pane2);
  box-shadow: inset 0 -2px 0 var(--y-yellow); }
.cnBar__i { font-size: .95rem; line-height: 1; }
.cnBar__a--end { margin-left: auto; }
.cnBar__n {
  min-width: 1.15rem; padding: .05rem .3rem; border-radius: var(--radius-pill);
  background: var(--y-yellow); color: var(--y-black);
  font-family: var(--font-mono); font-size: .58rem; font-weight: 700; text-align: center;
}
@media (max-width: 46rem) {
  .cnBar__inner { padding-inline: var(--s-3); }
  .cnBar__a { padding: .45rem .7rem; font-size: var(--fs-xs); }
  /* On a phone the strip already scrolls; pinning "You" to the right would
     push it permanently off the end. */
  .cnBar__a--end { margin-left: 0; }
}

/* Told where the consequence is felt, not only in settings. Somebody who
   cannot find themselves in a people search should learn why on that page. */
.cmpAgeNudge {
  margin: 0 0 var(--s-4); padding: var(--s-4) var(--s-5);
  border-radius: var(--radius); border: 1px dashed rgb(240 198 74 / .4);
  background: rgb(240 198 74 / .06);
  font-size: var(--fs-sm); color: var(--text-lo); line-height: var(--lh-body);
}
.cmpAgeNudge b { color: var(--text-hi); }

/* ── endless people feed ──────────────────────────────────────────────────── */
/* RENAMED from .cmpMore (2026-09-25): that name is also the profile's ••• menu
   wrapper, so this rule — dashed border, full width, big top margin — was
   drawn around the ••• on every profile and squeezed its menu into a sliver. */
.cmpFeedMore {
  display: block; width: 100%; margin: var(--s-8) 0 0;
  padding: var(--s-5); border-radius: var(--radius);
  border: 1px dashed var(--cmx-hair, rgb(255 255 255 / .12));
  background: none; cursor: pointer;
  font: inherit; font-size: var(--fs-sm); color: var(--text-lo);
}
.cmpFeedMore[hidden], .cmpEnd[hidden] { display: none; }

/* ── the invite that closes the feed ───────────────────────────────────── */
.cmpInvite {
  margin: var(--s-10) 0 0; padding: var(--s-8);
  border-radius: var(--radius-lg);
  border: 1px solid rgb(240 198 74 / .28);
  background:
    radial-gradient(80% 120% at 0% 0%, rgb(240 198 74 / .08), transparent 60%),
    var(--surface-1);
  text-align: center;
}
.cmpInvite[hidden] { display: none; }
.cmpInvite__k {
  margin: 0 0 var(--s-2);
  font-family: var(--font-mono); font-size: .62rem; letter-spacing: .2em;
  text-transform: uppercase; color: var(--text-lo);
}
.cmpInvite__h {
  margin: 0 0 var(--s-2);
  font-family: var(--font-display); font-size: var(--fs-h2);
  letter-spacing: -.02em; color: var(--text-hi);
}
.cmpInvite__p {
  margin: 0 auto var(--s-6); max-width: 46ch;
  font-size: var(--fs-sm); line-height: var(--lh-body); color: var(--text-lo);
}
.cmpInvite__row {
  display: flex; flex-wrap: wrap; gap: var(--s-2); justify-content: center;
}
.cmpShare {
  display: inline-flex; align-items: center; justify-content: center;
  padding: .6rem 1.1rem; border-radius: var(--radius-pill);
  border: 1px solid var(--surface-line); background: var(--surface-2);
  cursor: pointer; font: inherit; font-size: var(--fs-sm); font-weight: 600;
  color: var(--text-hi); text-decoration: none;
  transition: border-color var(--dur) var(--ease-out),
              background-color var(--dur) var(--ease-out);
}
.cmpShare:hover { border-color: var(--y-yellow); background: rgb(240 198 74 / .1); }
.cmpShare[hidden] { display: none; }
.cmpShare--wa:hover { border-color: #25d366; background: rgb(37 211 102 / .12); }
.cmpShare--native, .cmpShare--copy { background: var(--y-yellow); color: var(--y-black); border-color: transparent; }
.cmpShare--native:hover, .cmpShare--copy:hover { background: #ffd965; border-color: transparent; }

@media (max-width: 46rem) {
  .cmpInvite { padding: var(--s-5); }
  /* Full-width targets on a phone — a row of five pills wraps into a ragged
     block that is hard to hit. */
  .cmpInvite__row { flex-direction: column; }
  .cmpShare { width: 100%; }
}

/* The whole card is a click target. cursor:pointer is the only affordance that
   says so — without it the card looks inert and nobody tries. */
.cmpCard.is-clickable { cursor: pointer; }
/* Everything genuinely interactive inside keeps the normal arrow/pointer, so
   the two are visually distinguishable. */
.cmpCard.is-clickable a,
.cmpCard.is-clickable button,
.cmpCard.is-clickable [role="button"] { cursor: pointer; }

/* ══════════════════════════════════════════════════════════════════════════
   DELETE — CONFIRM STEP + UNDO TOAST
   The message is hidden (not removed) for the undo window; see schedule() in
   community.js. Red is the brand's alert colour and appears only on the one
   button that destroys something.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxBtn--danger { background: var(--y-red); color: #fff; border-color: transparent; }
.cmxBtn--danger:hover { filter: brightness(1.08); }

.is-deleting { display: none !important; }

.cmxDel.is-confirm { padding: .9rem; }
.cmxDel__q  { margin: 0 0 .3rem; max-width: none; font-weight: 600; color: var(--text-hi); font-size: var(--fs-sm); }
.cmxDel__qn { margin: 0 0 .8rem; max-width: none; color: var(--text-mid); font-size: .78rem; line-height: 1.45; }
.cmxDel__qrow { display: flex; justify-content: flex-end; gap: .4rem; }

.cmxToasts {
  position: fixed; left: 50%; bottom: max(1.25rem, env(safe-area-inset-bottom)); z-index: 600;
  translate: -50% 0; display: grid; gap: .5rem; justify-items: center;
  pointer-events: none; width: min(26rem, calc(100vw - 2rem));
}
.cmxToast {
  position: relative; overflow: hidden; pointer-events: auto;
  display: flex; align-items: center; gap: 1rem; width: 100%;
  padding: .75rem .75rem .75rem 1.1rem; border-radius: var(--radius);
  border: 1px solid var(--cmx-hair); background: var(--surface-2);
  backdrop-filter: var(--cmx-glass); box-shadow: 0 24px 60px -20px rgb(0 0 0 / .9);
  color: var(--text-hi); font-size: var(--fs-sm);
  animation: cmxRise .18s var(--ease-out);
  transition: opacity .2s var(--ease-out), translate .2s var(--ease-out);
}
.cmxToast span { flex: 1; }
.cmxToast.is-out { opacity: 0; translate: 0 6px; }
.cmxToast.is-error { border-color: rgb(232 84 74 / .45); }
.cmxToast__undo {
  border: 0; background: none; cursor: pointer; padding: .35rem .7rem; border-radius: var(--radius-pill);
  color: var(--y-yellow); font-weight: 700; font-size: var(--fs-sm);
}
.cmxToast__undo:hover { background: var(--cmx-pane2); }
.cmxToast__bar {
  position: absolute; left: 0; bottom: 0; height: 2px; width: 100%;
  background: var(--y-yellow); transform-origin: left;
  animation: cmxCountdown linear forwards;
}
@keyframes cmxCountdown { from { transform: scaleX(1); } to { transform: scaleX(0); } }
@media (prefers-reduced-motion: reduce) { .cmxToast__bar { animation: none; } }

/* Referenced by the ••• menu, the reaction picker, the toast and others, but
   never defined — so none of them animated. One definition, here. */
@keyframes cmxRise { from { opacity: 0; translate: 0 6px; } to { opacity: 1; translate: 0 0; } }

/* DM — "Load earlier messages" and a failed send you can retry. */
.dmLog__older { display: flex; justify-content: center; max-width: none; margin: 0 0 var(--s-4); }
.dmMsg.is-failed { cursor: pointer; }
.dmMsg.is-failed .dmBubble { opacity: .6; outline: 1px dashed rgb(232 84 74 / .6); outline-offset: 2px; }
.dmMsg.is-failed .dmMsg__meta time { color: var(--y-red); }

/* ── Followers / following: clickable counts + the list dialog ─────────── */
button.cmpStat { font: inherit; color: inherit; text-align: inherit; cursor: pointer; width: 100%; }
.cmpStat.is-open { transition: border-color .25s var(--ease-out), translate .25s var(--ease-out); }
.cmpStat.is-open:hover { border-color: rgb(240 198 74 / .45); translate: 0 -2px; }
.cmpStat.is-open span::after { content: " →"; color: var(--accent-ink); }
.flDlg { position: fixed; inset: 0; z-index: 800; display: grid; place-items: center; padding: var(--s-4); }
.flDlg__veil { position: absolute; inset: 0; border: 0; background: rgb(0 0 0 / .6); backdrop-filter: blur(4px); }
.flDlg__box { position: relative; width: min(26rem, 100%); max-height: min(34rem, calc(100dvh - 2rem));
  display: flex; flex-direction: column; border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: var(--surface-2); box-shadow: 0 30px 80px -30px rgb(0 0 0 / .9); animation: cmxRise .18s var(--ease-out); }
.flDlg__top { display: flex; align-items: center; justify-content: space-between; padding: var(--s-4) var(--s-5);
  border-bottom: 1px solid var(--cmx-hair); }
.flDlg__top h2 { margin: 0; font-size: var(--fs-body-lg); }
.flDlg__x { border: 0; background: none; color: var(--text-mid); font-size: 1.5rem; line-height: 1; cursor: pointer; }
.flDlg__list { overflow-y: auto; padding: var(--s-2); display: grid; gap: .15rem; }
.flDlg__note { margin: var(--s-4); max-width: none; color: var(--text-mid); font-size: var(--fs-sm); text-align: center; }
.flRow { display: flex; align-items: center; gap: .75rem; padding: .55rem .7rem; border-radius: var(--radius);
  color: inherit; text-decoration: none; }
a.flRow:hover { background: var(--cmx-pane2); }
.flRow__av { flex: none; display: grid; }
.flRow__body { display: grid; min-width: 0; }
.flRow__body b { color: var(--text-hi); font-size: var(--fs-sm); font-weight: 600; }
.flRow__body i { font-style: normal; color: var(--text-lo); font-size: .78rem; }

/* ── YOUR OWN PROFILE — the action block with no ••• menu ─────────────────
   /profile/ (Edit profile · Settings · Messages · "See it as others do") and
   your own /community/profile/ (Edit profile · Go private · Your messages).
   The generic one-row rule squeezed all of these into equal columns, so
   "Edit profile" wrapped to two lines and the link was crammed into a column.
     tablet: the three buttons in one row, link / note centred underneath
     phone:  the primary action full width, the other two side by side,
             link / note centred underneath */
.cmpAct .cmxBtn { white-space: nowrap; }
@media (max-width: 56rem) {
  .cmpAct:not(:has(.cmpMore)) { grid-template-columns: repeat(3, minmax(0, 1fr)); grid-auto-flow: row; }
  .cmpAct > .cmxLink, .cmpAct > .cmpAct__note { grid-column: 1 / -1; justify-self: center; text-align: center; }
  .cmpAct > .cmxLink { margin-top: .25rem; }
}
@media (max-width: 40rem) {
  .cmpAct:not(:has(.cmpMore)) { grid-template-columns: repeat(2, minmax(0, 1fr)); }
  .cmpAct:not(:has(.cmpMore)) > .cmxBtn:first-child { grid-column: 1 / -1; }
}

/* Stat cards: one alignment for all three. Followers / following became
   <button>s (they open the lists) and stopped matching the Connections <div>
   beside them — left vs centred in the same row. Explicit, not inherited. */
.cmpStats > .cmpStat { text-align: left; }
@media (max-width: 46rem) { .cmpStats > .cmpStat { text-align: center; } }

/* ── Private chats: typing, sound toggle, broken images ─────────────────── */
.dmTyping { margin: 0; padding: .35rem 1.25rem .15rem; }
.dmTop__who i.is-typing { color: #3ecf8e; font-style: italic; }
.dmRow.is-typing .dmRow__body i { color: var(--accent-ink); font-style: italic; }
.dmTop__sound { margin-left: auto; }
.dmTop__sound .is-off-ico { display: none; }
.dmTop__sound.is-off .is-on-ico { display: none; }
.dmTop__sound.is-off .is-off-ico { display: block; }
.dmTop__sound.is-off { color: var(--text-lo); }
.dmTop__sound svg { width: 1.15rem; height: 1.15rem; }
.cmxShot.is-broken { min-height: 0; aspect-ratio: auto; cursor: default; }
.cmxShot__gone { display: block; padding: .8rem 1rem; font-size: var(--fs-sm); color: var(--text-lo); font-style: italic; }

/* ══════════════════════════════════════════════════════════════════════════
   REPLIES, EDITS, DM REACTIONS  (chat-compose.js)
   ══════════════════════════════════════════════════════════════════════════ */
/* The quoted message — WhatsApp's shape: a coloured edge, the name in colour,
   one line of the original. A button: tapping it jumps to the original. */
.cmxQuote {
  display: grid; gap: .1rem; width: fit-content; max-width: min(28rem, 100%); margin: 0 0 .3rem;
  padding: .45rem .75rem .45rem .7rem; border: 0; border-left: 3px solid var(--y-yellow);
  border-radius: 10px; background: rgb(240 198 74 / .08); cursor: pointer; text-align: left; font: inherit;
}
.cmxQuote b { font-size: .78rem; font-weight: 700; color: var(--accent-ink); }
.cmxQuote span { font-size: .82rem; color: var(--text-mid); overflow: hidden; text-overflow: ellipsis;
  white-space: nowrap; max-width: 26rem; }
.cmxQuote:hover { background: rgb(240 198 74 / .14); }
.cmxQuote.is-gone span::after { content: " · not loaded"; color: var(--text-lo); }
.is-mine .cmxQuote { margin-left: auto; }
.dmMsg .cmxQuote { max-width: min(24rem, 80%); }

.cmxEdited { font-size: .72em; color: var(--text-lo); font-style: italic; white-space: nowrap; }
.is-mine .dmBubble .cmxEdited { color: rgb(0 0 0 / .45); }

/* Jumped-to message: a brief glow so the eye lands on it. */
@keyframes cmxFlash { 0%, 100% { background: transparent; } 20% { background: rgb(240 198 74 / .16); } }
.is-flash { animation: cmxFlash 1.4s ease; border-radius: 12px; }

/* The bar above the composer: "Replying to …" / "Editing message". */
.cmxReplyBar { display: flex; align-items: center; gap: .7rem; margin: 0 0 .5rem; padding: .5rem .6rem .5rem .8rem;
  border-radius: 12px; border-left: 3px solid var(--y-yellow); background: var(--surface-2);
  animation: cmxRise .16s var(--ease-out); }
.cmxReplyBar[hidden] { display: none; }
.cmxReplyBar.is-edit { border-left-color: #3ecf8e; }
.cmxReplyBar__ico { font-size: 1rem; color: var(--accent-ink); }
.cmxReplyBar.is-edit .cmxReplyBar__ico { color: #3ecf8e; }
.cmxReplyBar__txt { flex: 1; min-width: 0; display: grid; gap: .05rem; }
.cmxReplyBar__txt b { font-size: .78rem; color: var(--text-hi); }
.cmxReplyBar__txt i { font-style: normal; font-size: .8rem; color: var(--text-mid);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.cmxReplyBar__x { border: 0; background: none; color: var(--text-lo); font-size: 1.4rem; line-height: 1; cursor: pointer; padding: 0 .3rem; }
.cmxReplyBar__x:hover { color: var(--text-hi); }

/* Swipe-to-reply: past the threshold the message shows a ↩ hint. */
[data-msg], [data-dmid] { position: relative; touch-action: pan-y; }
.is-swipe-ready::before { content: "↩"; position: absolute; left: -1.6rem; top: 50%; translate: 0 -50%;
  color: var(--accent-ink); font-size: 1.1rem; }

/* Reactions under a private message, on the bubble's side. */
.dmMsg .cmxRx { margin-top: .25rem; }
.dmMsg.is-mine .cmxRx { justify-content: flex-end; }
.cmxDel__item--reply b { font-weight: 600; }

/* ── Who reacted — the sheet a reaction chip opens (chat-reactors.js) ─────
   A popover-sized card on a desk, a bottom sheet on a phone. Tabs per emoji,
   one row per person, named the way they post in that room. */
.rxw { position: fixed; inset: 0; z-index: 900; display: grid; place-items: center; padding: var(--s-4); }
.rxw__veil { position: absolute; inset: 0; border: 0; padding: 0; background: rgb(0 0 0 / .55);
  backdrop-filter: blur(3px); -webkit-backdrop-filter: blur(3px); animation: rxwFade .18s ease both; cursor: default; }
.rxw__box { position: relative; width: min(24rem, 100%); max-height: min(30rem, calc(100dvh - 2rem));
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: var(--radius-lg); border: 1px solid var(--cmx-hair);
  background: linear-gradient(180deg, rgb(240 198 74 / .06), transparent 7rem), var(--surface-2);
  box-shadow: 0 30px 80px -30px rgb(0 0 0 / .9); animation: rxwPop .22s var(--ease-spring, cubic-bezier(.3,1.4,.5,1)) both; }
.rxw.is-closing .rxw__box { animation: rxwOut .18s ease both; }
.rxw.is-closing .rxw__veil { animation: rxwFade .18s ease reverse both; }
.rxw__grab { display: none; }
.rxw__top { display: flex; align-items: center; justify-content: space-between; padding: var(--s-4) var(--s-5) var(--s-2); }
.rxw__top h2 { margin: 0; font-size: var(--fs-body-lg, 1.05rem); color: var(--text-hi); letter-spacing: -.01em; }
.rxw__x { width: 2.25rem; height: 2.25rem; border-radius: 50%; border: 1px solid var(--cmx-hair);
  background: transparent; color: var(--text-mid); font-size: 1.3rem; line-height: 1; cursor: pointer; }
.rxw__x:hover { color: var(--text-hi); border-color: var(--text-lo); }

.rxw__tabs { display: flex; gap: .4rem; padding: var(--s-2) var(--s-5) var(--s-3); overflow-x: auto;
  scrollbar-width: none; border-bottom: 1px solid var(--cmx-hair); }
.rxw__tabs::-webkit-scrollbar { display: none; }
.rxw__tab { flex: none; display: inline-flex; align-items: center; gap: .35rem; min-height: 2.25rem;
  padding: .3rem .8rem; border-radius: var(--radius-pill); border: 1px solid var(--cmx-hair);
  background: var(--surface-1); color: var(--text-mid); font: inherit; font-size: var(--fs-sm); cursor: pointer;
  transition: background-color .2s, border-color .2s, color .2s; }
.rxw__tab i { font-style: normal; font-family: var(--font-mono); font-size: .7rem; color: var(--text-lo); }
.rxw__tabEmo { font-size: 1.1rem; line-height: 1; }
.rxw__tab.is-on { background: rgb(240 198 74 / .14); border-color: rgb(240 198 74 / .55); color: var(--text-hi); }
.rxw__tab.is-on i { color: var(--y-yellow); }

.rxw__list { list-style: none; margin: 0; padding: var(--s-2) var(--s-3) var(--s-4); overflow-y: auto;
  overscroll-behavior: contain; display: grid; gap: 2px; }
.rxw__load { padding: var(--s-6) var(--s-4); text-align: center; color: var(--text-lo); font-size: var(--fs-sm); }
.rxw__row { display: flex; align-items: center; gap: var(--s-3); padding: .55rem var(--s-3);
  border-radius: var(--radius); animation: rxwRow .28s var(--ease-out) both; animation-delay: calc(var(--i, 0) * 28ms); }
.rxw__row:hover { background: var(--surface-1); }
.rxw__row.is-mine { cursor: pointer; background: rgb(240 198 74 / .06); }
.rxw__row.is-mine:hover { background: rgb(240 198 74 / .12); }
.rxw__row.is-mine:focus-visible { outline: 2px solid var(--y-yellow); outline-offset: -2px; }
.rxw__av { flex: none; display: grid; }
.rxw__av .ycAv, .rxw__av img, .rxw__av svg { width: 36px; height: 36px; border-radius: 50%; }
.rxw__who { min-width: 0; flex: 1; display: flex; flex-wrap: wrap; align-items: baseline; gap: .1rem .45rem; }
.rxw__name { color: var(--text-hi); font-weight: 600; font-size: var(--fs-sm); text-decoration: none;
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
a.rxw__name:hover { color: var(--y-yellow); }
.rxw__who small { flex-basis: 100%; font-size: .7rem; color: var(--text-lo); }
.rxw__emo { flex: none; font-size: 1.35rem; line-height: 1; animation: cmxBump .45s var(--ease-spring, ease) both;
  animation-delay: calc(var(--i, 0) * 28ms + 120ms); }
.rxw__addRow { padding: var(--s-2) var(--s-3) 0; }
.rxw__add { width: 100%; min-height: 2.6rem; display: inline-flex; align-items: center; justify-content: center; gap: .4rem;
  border-radius: var(--radius-pill); border: 1px dashed rgb(240 198 74 / .5); background: transparent;
  color: var(--y-yellow); font: inherit; font-size: var(--fs-sm); cursor: pointer; }
.rxw__add:hover { background: rgb(240 198 74 / .08); }
.rxw__add span { font-size: 1.1rem; }
.cmxRx__chip { cursor: pointer; }
html.rxw-lock, html.rxw-lock body { overflow: hidden; }

@keyframes rxwFade { from { opacity: 0; } to { opacity: 1; } }
@keyframes rxwPop { from { opacity: 0; transform: translateY(8px) scale(.97); } to { opacity: 1; transform: none; } }
@keyframes rxwOut { to { opacity: 0; transform: translateY(8px) scale(.98); } }
@keyframes rxwRow { from { opacity: 0; transform: translateY(6px); } to { opacity: 1; transform: none; } }
@keyframes rxwUp { from { transform: translateY(100%); } to { transform: none; } }
@keyframes rxwDown { to { transform: translateY(100%); } }

@media (max-width: 46rem) {
  .rxw { place-items: end stretch; padding: 0; }
  .rxw__box { width: 100%; max-height: 72dvh; border-radius: 1.25rem 1.25rem 0 0; border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom); animation: rxwUp .26s cubic-bezier(.2,.9,.3,1) both; }
  .rxw.is-closing .rxw__box { animation: rxwDown .18s ease both; }
  .rxw__grab { display: block; width: 2.5rem; height: 4px; margin: .6rem auto 0; border-radius: 4px; background: var(--text-lo); opacity: .5; }
  .rxw__top { padding-top: var(--s-2); }
  .rxw__row { padding: .65rem var(--s-3); }
}
@media (prefers-reduced-motion: reduce) {
  .rxw__box, .rxw__veil, .rxw__row, .rxw__emo { animation: none !important; }
}

/* Rooms — "Load earlier messages" (24 hours back at most). */
.cmxOlder { display: flex; justify-content: center; max-width: none; margin: 0 0 var(--s-4); }
.cmxOlder--end { font-size: var(--fs-xs); color: var(--text-lo); font-family: var(--font-mono); letter-spacing: .04em; }

/* ══════════════════════════════════════════════════════════════════════════
   NOTIFICATION ROWS (.ntx) — the 🔔 panel and /community/notifications/.
   Drawn by notif-rows.js and, identically, by the Alerts page in PHP.
   ══════════════════════════════════════════════════════════════════════════ */
.ntx { list-style: none; position: relative; }
.ntx.is-unread::before { content: ""; position: absolute; left: 0; top: .7rem; bottom: .7rem; width: 3px;
  border-radius: 0 3px 3px 0; background: var(--y-yellow); }
.cmxBell__panel .ntx .ntx__main, .ntx__main {
  display: flex; align-items: flex-start; gap: var(--s-3);
  padding: var(--s-3) var(--s-4); text-decoration: none; color: inherit; border: 0; background: none; }
.cmxBell__panel li.ntx.is-unread a { border-left: 0; }
.cmxBell__panel .ntx.is-unread { background: rgb(240 198 74 / .05); }
a.ntx__main:hover { background: var(--cmx-pane, var(--surface-2)); }
.ntx__av { position: relative; flex: none; display: grid; }
.ntx__av > .ycAv, .ntx__av > img, .ntx__av > svg { width: 34px; height: 34px; border-radius: 50%; }
.ntx__kind { position: absolute; right: -4px; bottom: -4px; min-width: 1.1rem; height: 1.1rem; padding: 0 .15rem;
  display: grid; place-items: center; border-radius: 999px; font-size: .6rem; line-height: 1;
  background: var(--surface-2); border: 1px solid var(--cmx-hair); color: var(--y-yellow); }
.ntx__txt { min-width: 0; flex: 1; display: grid; gap: .15rem; }
.ntx__line { margin: 0; max-width: none; font-size: var(--fs-sm); line-height: 1.4; color: var(--text-mid); }
.ntx__line b { color: var(--text-hi); font-weight: 600; }
.ntx__sub { margin: 0; max-width: none; font-size: var(--fs-xs); color: var(--text-lo);
  overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ntx__note { margin: 0; max-width: none; font-size: var(--fs-xs); color: var(--accent-ink, var(--y-yellow)); }
.ntx__ago { font-family: var(--font-mono); font-size: .58rem; color: var(--text-lo); }
.ntx__act { display: flex; gap: var(--s-2); padding: 0 var(--s-4) var(--s-3) calc(var(--s-4) + 34px + var(--s-3)); }
.ntx__act .cmxBtn { flex: 1; min-height: 2.4rem; justify-content: center; }
.ntx__act .cmxBtn.is-busy { opacity: .6; pointer-events: none; }
.ntx.is-connected .ntx__line { color: var(--text-hi); }
.ntx.is-ignored { opacity: .75; }
.ntx.is-answered { animation: ntxIn .35s var(--ease-out) both; }
@keyframes ntxIn { from { background: rgb(240 198 74 / .16); } to { background: transparent; } }

/* The bell panel's extra chrome. */
.cmxBell__hdAct { display: inline-flex; align-items: center; gap: var(--s-3); }
.cmxBell__x { display: none; width: 2rem; height: 2rem; border-radius: 50%; border: 1px solid var(--cmx-hair);
  background: transparent; color: var(--text-mid); font-size: 1.2rem; line-height: 1; cursor: pointer; }
.cmxBell__grab { display: none; }
.cmxBell__all { display: block; padding: var(--s-3) var(--s-4); text-align: center; font-size: var(--fs-xs);
  color: var(--y-yellow); text-decoration: none; border-top: 1px solid var(--cmx-hair);
  position: sticky; bottom: 0; background: var(--surface-1); }
.cmxBell__panel > a.cmxBell__all { display: block; }

/* PHONE: a bottom sheet, not a dropdown. The bell sits mid-header, and a
   dropdown anchored to its right edge ran off the left of the screen. */
@media (max-width: 46rem) {
  .cmxBell__panel {
    position: fixed; top: auto; left: 0; right: 0; bottom: 0; z-index: 950;
    width: 100%; max-height: 78dvh; border-radius: 1.25rem 1.25rem 0 0; border-bottom: 0;
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 0 0 100vmax rgb(0 0 0 / .55), 0 -20px 60px -20px rgb(0 0 0 / .8);
    animation: rxwUp .26s cubic-bezier(.2,.9,.3,1);
  }
  .cmxBell__grab { display: block; width: 2.5rem; height: 4px; margin: .6rem auto 0; border-radius: 4px;
    background: var(--text-lo); opacity: .5; }
  .cmxBell__x { display: inline-grid; place-items: center; }
  .cmxBell__panel header { padding: var(--s-3) var(--s-4); }
  html.cmxBell-open, html.cmxBell-open body { overflow: hidden; }
}

/* ── PHONE: the message ••• menu and the full emoji picker are bottom sheets ──
   Anchored to the button they floated beside the message, and with the whole
   reaction set plus Reply / Edit / Delete the menu is taller than a phone
   screen — it opened clipped at the top. A sheet is where a thumb already is.
   (The JS still writes top/left; the !important here is what overrides it.) */
@media (max-width: 46rem) {
  .cmxDel, .cmxPick {
    left: .5rem !important; right: .5rem; top: auto !important;
    bottom: calc(.5rem + env(safe-area-inset-bottom));
    width: auto; max-height: min(80dvh, 34rem); overflow-y: auto; overscroll-behavior: contain;
    padding: .5rem; border-radius: 1.1rem;
    box-shadow: 0 0 0 100vmax rgb(0 0 0 / .5), 0 -10px 40px -10px rgb(0 0 0 / .7);
    animation: rxwUp .24s cubic-bezier(.2,.9,.3,1);
  }
  .cmxDel__rx { grid-template-columns: repeat(8, minmax(0, 1fr)); padding: .4rem .2rem .55rem; }
  .cmxDel__emo { min-height: 2.6rem; font-size: 1.4rem; }
  .cmxDel__item { min-height: 3rem; padding: .7rem .85rem; align-content: center; }
  .cmxDel__item b { font-size: var(--fs-body, 1rem); }
  .cmxDel__cancel { min-height: 2.9rem; font-size: var(--fs-sm); }
  .cmxPick { grid-template-columns: repeat(8, minmax(0, 1fr)); }
  .cmxPick__emo { min-height: 2.8rem; font-size: 1.45rem; }
}
@media (max-width: 22rem) {
  .cmxDel__rx, .cmxPick { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* ── The chat fills exactly the screen below the header + tab bar ─────────
   --cmx-top is measured by chat-compose.js; dvh tracks the phone's browser
   bar. The fixed rem guesses above stay as the fallback before JS runs. */
@supports (height: 100dvh) {
  .cmxShell, .dmShell { height: calc(100dvh - var(--cmx-top, 5.5rem)); min-height: 20rem; }
}

/* ── Connect cooldown (profile) — greyed, locked, counting down ──────────── */
.cmpCool {
  position: relative; overflow: hidden; gap: .45rem;
  background: var(--surface-1) !important; border: 1px dashed var(--cmx-hair) !important;
  color: var(--text-lo) !important; cursor: not-allowed; box-shadow: none !important;
  filter: saturate(.6);
}
.cmpCool:hover { translate: none !important; }
.cmpCool__ico { display: inline-block; animation: cmpCoolTip 3.2s ease-in-out infinite; }
.cmpCool__bar { position: absolute; left: 1.4rem; right: 1.4rem; bottom: 5px; height: 2px;
  border-radius: 2px; background: rgb(255 255 255 / .08); }
.cmpCool__bar::after { content: ""; position: absolute; inset: 0 auto 0 0; width: var(--p, 0%);
  border-radius: inherit; background: linear-gradient(90deg, rgb(240 198 74 / .35), var(--y-yellow)); }
@keyframes cmpCoolTip { 0%, 80%, 100% { rotate: 0deg; } 88% { rotate: 180deg; } }
@media (prefers-reduced-motion: reduce) { .cmpCool__ico { animation: none; } }

/* ── Breathing room between the chat and the site footer ─────────────────
   The shell ended flush against the footer rule, so the message box looked
   glued to it when you scrolled past. */
.cmxShell, .dmShell { margin-bottom: var(--s-10, 2.5rem); }
@media (max-width: 46rem) { .cmxShell, .dmShell { margin-bottom: var(--s-8, 2rem); } }

/* DM — "N unread messages", where the conversation opens. */
.dmUnread { display: flex; align-items: center; gap: var(--s-3); max-width: none; margin: var(--s-2) 0;
  font-family: var(--font-mono); font-size: .66rem; letter-spacing: .08em; text-transform: uppercase; color: var(--y-yellow); }
.dmUnread::before, .dmUnread::after { content: ""; flex: 1; height: 1px; background: rgb(240 198 74 / .35); }
.dmUnread span { padding: .2rem .7rem; border-radius: var(--radius-pill); background: rgb(240 198 74 / .1);
  border: 1px solid rgb(240 198 74 / .3); }
.cnBar__n[hidden] { display: none; }

/* ── Forward picker (chat-forward.js) — shares the .rxw sheet shell ─────── */
.fwd__qWrap { padding: 0 var(--s-5) var(--s-3); border-bottom: 1px solid var(--cmx-hair); }
.fwd__q { width: 100%; min-width: 0; padding: .6rem .9rem; border-radius: var(--radius-pill);
  border: 1px solid var(--cmx-hair); background: var(--surface-1); color: var(--text-hi); font: inherit; font-size: var(--fs-sm); }
.fwd__q:focus { outline: none; border-color: rgb(240 198 74 / .55); }
.fwd__row { cursor: pointer; }
.fwd__row small { flex-basis: 100%; font-size: .7rem; color: var(--text-lo); }
.fwd__tick { flex: none; width: 1.35rem; height: 1.35rem; border-radius: 50%; border: 2px solid var(--text-lo);
  display: grid; place-items: center; transition: background-color .15s, border-color .15s; }
.fwd__row.is-on { background: rgb(240 198 74 / .08); }
.fwd__row.is-on .fwd__tick { background: var(--y-yellow); border-color: var(--y-yellow); }
.fwd__row.is-on .fwd__tick::after { content: "✓"; color: var(--y-black, #111); font-size: .8rem; font-weight: 700; }
.fwd__row.is-off { opacity: .45; }
.fwd__row:focus-visible { outline: 2px solid var(--y-yellow); outline-offset: -2px; }
.fwd__foot { display: flex; align-items: center; justify-content: space-between; gap: var(--s-3);
  padding: var(--s-3) var(--s-5) var(--s-4); border-top: 1px solid var(--cmx-hair); }
.fwd__n { font-size: var(--fs-xs); color: var(--text-lo); font-family: var(--font-mono); }
.fwd__send { min-width: 7rem; justify-content: center; }
.fwd .rxw__box { max-height: min(36rem, calc(100dvh - 2rem)); }
@media (max-width: 46rem) { .fwd .rxw__box { max-height: 85dvh; } .fwd__send { min-height: 2.8rem; } }

/* "↪ Forwarded", above a forwarded message's content. */
.dmFwd { display: block; margin: 0 0 .2rem; font-size: .68rem; font-style: italic; color: var(--text-lo); letter-spacing: .01em; }
.dmMsg.is-mine .dmFwd { text-align: right; }

/* ── "You appear as ▾" — per-room identity switch ───────────────────────── */
.cmxAs { display: flex; align-items: center; gap: .4rem; flex-wrap: wrap; }
.cmxAs__btn { display: inline-flex; align-items: center; gap: .35rem; padding: .2rem .5rem .2rem .6rem; min-height: 1.9rem;
  border-radius: 999px; border: 1px solid var(--cmx-hair); background: var(--surface-1); color: var(--text-hi);
  font: inherit; font-size: inherit; cursor: pointer; transition: border-color .2s, background-color .2s; }
.cmxAs__btn:hover, .cmxAs__btn[aria-expanded="true"] { border-color: rgb(240 198 74 / .55); background: rgb(240 198 74 / .07); }
.cmxAs__btn svg { color: var(--text-lo); transition: rotate .2s; }
.cmxAs__btn[aria-expanded="true"] svg { rotate: 180deg; }
.cmxAs__btn .cmxAnon[hidden] { display: none; }
.cmxAsMenu { position: fixed; z-index: 800; width: 19rem; padding: .45rem; display: grid; gap: .25rem;
  border-radius: var(--radius); border: 1px solid var(--cmx-hair); background: var(--surface-2);
  box-shadow: 0 24px 60px -20px rgb(0 0 0 / .9); animation: cmxRise .16s var(--ease-out); }
.cmxAsMenu__h { margin: .2rem .5rem .2rem; max-width: none; font-family: var(--font-mono); font-size: .62rem; letter-spacing: .12em;
  text-transform: uppercase; color: var(--text-lo); }
.cmxAsMenu__it { display: grid; gap: .1rem; width: 100%; padding: .6rem .75rem; border-radius: var(--radius-sm, 8px);
  border: 1px solid transparent; background: none; color: inherit; font: inherit; text-align: left; cursor: pointer; }
.cmxAsMenu__it:hover, .cmxAsMenu__it:focus-visible { background: var(--surface-1); outline: none; }
.cmxAsMenu__it.is-on { border-color: rgb(240 198 74 / .45); background: rgb(240 198 74 / .07); }
.cmxAsMenu__it b { font-size: .68rem; letter-spacing: .08em; text-transform: uppercase; color: var(--accent-ink, var(--y-yellow)); }
.cmxAsMenu__name { font-size: var(--fs-body, 1rem); font-weight: 600; color: var(--text-hi); }
.cmxAsMenu__it.is-on .cmxAsMenu__name::after { content: "  ✓"; color: var(--y-yellow); }
.cmxAsMenu__it i { font-style: normal; font-size: .74rem; line-height: 1.4; color: var(--text-lo); }
.cmxAsMenu__foot { margin: .2rem .5rem .25rem; max-width: none; font-size: .68rem; color: var(--text-lo); line-height: 1.4; }
@media (max-width: 46rem) {
  .cmxAsMenu { left: .5rem !important; right: .5rem; top: auto !important; bottom: calc(.5rem + env(safe-area-inset-bottom));
    width: auto; padding: .6rem; border-radius: 1.1rem; box-shadow: 0 0 0 100vmax rgb(0 0 0 / .5);
    animation: rxwUp .24s cubic-bezier(.2,.9,.3,1); }
  .cmxAsMenu__it { padding: .8rem .9rem; }
}

/* ══════════════════════════════════════════════════════════════════════════
   PHONES — THE APP SHELL
   Three options were weighed for the community on a phone:
     1. keep the sideways-scrolling strip  — Messages and Alerts, the two with
        counts, sat off the right edge where nobody saw them;
     2. a six-icon strip along the top     — always visible, but it stacks a
        second bar under the site header and pushes every chat further down;
     3. a bottom TAB BAR on the hub pages + FULL-SCREEN conversations — the
        model Instagram and WhatsApp have already taught every student.
   (3) won: the tabs sit where the thumb is, and a conversation gets the
   whole screen, with a back button, instead of a small box under two bars.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxTop__back { display: none; }
.cnBar__svg {
  display: none; width: 1.4rem; height: 1.4rem; fill: none; stroke: currentColor;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round;
}

@media (max-width: 46rem) {
  /* ── the tab bar ───────────────────────────────────────────────────────── */
  .cnBar {
    position: fixed; left: 0; right: 0; bottom: 0; z-index: 90;
    border-bottom: 0; border-top: 1px solid var(--cmx-hair);
    background: rgb(11 12 17 / .88); backdrop-filter: blur(18px) saturate(1.4);
    -webkit-backdrop-filter: blur(18px) saturate(1.4);
    padding-bottom: env(safe-area-inset-bottom);
    box-shadow: 0 -12px 30px -18px rgb(0 0 0 / .9);
  }
  .cnBar__inner {
    display: grid; grid-auto-flow: column; grid-auto-columns: minmax(0, 1fr);
    gap: 0; padding: .3rem .3rem; overflow: visible;
  }
  .cnBar__a--chat, .cnBar__a--you { display: none; }
  .cnBar__a {
    position: relative; isolation: isolate;
    flex-direction: column; justify-content: center; gap: .22rem;
    min-height: 3.3rem; padding: .35rem .1rem .3rem; border-radius: 14px;
    font-size: .62rem; font-weight: 600; letter-spacing: .01em; color: var(--text-lo);
    -webkit-tap-highlight-color: transparent;
  }
  .cnBar__a:hover { background: none; }
  .cnBar__a:active .cnBar__svg { transform: scale(.88); }
  .cnBar__a--end { margin-left: 0; }
  .cnBar__i { display: none; }
  .cnBar__svg { display: block; transition: transform .15s var(--ease-out); }
  .cnBar__a.is-on { color: var(--y-yellow); background: none; box-shadow: none; }
  /* The active tab's pill — the Material 3 / iOS 18 "you are here" cue. */
  .cnBar__a.is-on::before {
    content: ''; position: absolute; z-index: -1; top: .3rem; left: 50%; translate: -50% 0;
    width: 3.1rem; height: 1.95rem; border-radius: 99px;
    background: rgb(240 198 74 / .16); box-shadow: inset 0 0 0 1px rgb(240 198 74 / .22);
    animation: cnPill .35s cubic-bezier(.34, 1.56, .64, 1);
  }
  @keyframes cnPill { from { transform: scaleX(.4); opacity: 0; } }
  .cnBar__a.is-on .cnBar__svg { stroke-width: 2.1; }
  .cnBar__n {
    position: absolute; top: .15rem; left: calc(50% + .3rem);
    min-width: 1.1rem; padding: .08rem .28rem; font-size: .55rem; line-height: 1.2;
    box-shadow: 0 0 0 2px rgb(11 12 17);
  }

  /* Room for the bar under every hub page, and for anything else that sits
     at the bottom edge (install card, toasts). */
  body:has(.cnBar:not(.is-convo)) { padding-bottom: calc(4.1rem + env(safe-area-inset-bottom)); }
  body:has(.cnBar:not(.is-convo)) .ycInst,
  body:has(.cnBar:not(.is-convo)) .ycInst__toast,
  body:has(.cnBar:not(.is-convo)) .stkToast,
  body:has(.cnBar:not(.is-convo)) .rmToast { bottom: calc(4.8rem + env(safe-area-inset-bottom)); }

  /* ── inside a conversation the bar steps aside ─────────────────────────── */
  .cnBar.is-convo { display: none; }

  /* ── FULL-SCREEN CONVERSATION ───────────────────────────────────────────
     The chat covers the site header and footer (z 150 — under every sheet,
     menu and viewer, all of which are 340+). --app-h / --app-top follow the
     VISUAL viewport (community.js), so when the keyboard opens the composer
     rides up with it instead of hiding behind it. */
  /* THE KEYBOARD. iOS does not resize the page when the keyboard opens — it
     SCROLLS the page to bring the text box into view. overflow:hidden alone
     does not stop that, so the site footer behind the chat (the address
     block) slid up under the composer and showed through the translucent
     keyboard bar, and the caret jumped as the page moved while typing.
     So while a conversation is open the page itself is pinned (body fixed —
     there is nothing to scroll), the header and footer are not rendered at
     all (nothing to show through), and the background behind everything is
     the chat's own colour. The chat box follows the visible area instead
     (--app-h / --app-top, set from visualViewport in community.js). */
  html:has(.cmxShell[data-chat]), html:has(.dmShell.is-open) {
    height: 100%; overflow: hidden; overscroll-behavior: none; background: var(--surface-0);
  }
  body:has(.cmxShell[data-chat]), body:has(.dmShell.is-open) {
    position: fixed; inset: 0; width: 100%; height: 100%;
    overflow: hidden; overscroll-behavior: none; background: var(--surface-0);
  }
  body:has(.cmxShell[data-chat]) > .nav, body:has(.dmShell.is-open) > .nav,
  body:has(.cmxShell[data-chat]) > .footer, body:has(.dmShell.is-open) > .footer,
  body:has(.cmxShell[data-chat]) > .skip-link, body:has(.dmShell.is-open) > .skip-link,
  body:has(.cmxShell[data-chat]) .cmxSky, body:has(.dmShell.is-open) .cmxSky { display: none; }
  /* With the keyboard up, the home-indicator gap is under the keyboard, not
     under the composer — drop it, or a strip of dead space sits above the keys. */
  html.kb-open .cmxShell[data-chat] .cmxComposer,
  html.kb-open .dmShell.is-open .dmComposer { padding-bottom: .5rem; }

  .cmxShell[data-chat], .dmShell.is-open {
    position: fixed; left: 0; right: 0; top: var(--app-top, 0); z-index: 150;
    height: var(--app-h, 100dvh); max-width: none; margin: 0 !important;
    padding: 0; gap: 0; background: var(--surface-0);
  }
  .cmxShell[data-chat] .cmxMain,
  .dmShell.is-open .dmMain { height: 100%; min-height: 0; border-radius: 0; border: 0; box-shadow: none; }

  /* Header: back · room (tap for info) · actions. Clears the notch. */
  .cmxShell[data-chat] .cmxTop, .dmShell.is-open .dmTop {
    padding: calc(.5rem + env(safe-area-inset-top)) .6rem .5rem .35rem; gap: .35rem;
    background: rgb(14 15 21 / .92); backdrop-filter: blur(14px);
  }
  .cmxTop__back, .dmShell.is-open .dmTop__back {
    display: grid; place-items: center; flex: none;
    width: 2.5rem; height: 2.5rem; border-radius: 50%; border: 0;
    background: none; color: var(--text-hi); font-size: 1.6rem; text-decoration: none;
  }
  .cmxTop__back:active, .dmTop__back:active { background: var(--cmx-pane2); }
  .cmxTop__back svg { width: 1.35rem; height: 1.35rem; fill: none; stroke: currentColor; stroke-width: 2.2;
    stroke-linecap: round; stroke-linejoin: round; }
  .cmxTop__id { gap: .6rem; margin: 0; padding: .25rem .35rem; }
  .cmxTop__emoji {
    width: 2.4rem; height: 2.4rem; display: grid; place-items: center; font-size: 1.3rem;
    border-radius: 12px; background: rgb(240 198 74 / .12);
  }
  .cmxTop__t { font-size: 1rem; line-height: 1.25; }
  .cmxTop__s { display: block; font-size: .7rem; }
  /* The avatar stack is duplicated by Room info on a phone, and it was what
     squeezed the room name to three letters. The live count stays. */
  .cmxShell[data-chat] .cmxStack { display: none; }
  .cmxShell[data-chat] .cmxWho__txt { font-size: .66rem; white-space: nowrap; }
  .cmxShell[data-chat] .cmxTop > .cmxBtn--sm { display: none; }  /* ⚙ Manage lives in Room info */

  .cmxShell[data-chat] .cmxLog { padding: .9rem .75rem .5rem; overscroll-behavior: contain; }
  .dmShell.is-open .dmLog { padding: .9rem .75rem .5rem; overscroll-behavior: contain; }

  /* Composer: flush to the bottom, above the home indicator. */
  .cmxShell[data-chat] .cmxComposer, .dmShell.is-open .dmComposer {
    padding: .5rem .6rem calc(.55rem + env(safe-area-inset-bottom));
    background: rgb(14 15 21 / .94); border-top: 1px solid var(--cmx-hair);
  }

  /* ── the DM inbox (no conversation open) scrolls with the page ───────── */
  .dmShell:not(.is-open) { height: auto; min-height: 0; }
  .dmShell:not(.is-open) .dmList { max-height: none; overflow: visible; }
}

/* ══════════════════════════════════════════════════════════════════════════
   UPDATE REPLIES IN DMs — a story-reply card
   "Replied to your update", a tiny preview of the update itself (its colours
   and words, or its photo), then the message bubble underneath. It used to be
   a bare "↩ 🍷" above the bubble, which read like a stray emoji.
   ══════════════════════════════════════════════════════════════════════════ */
.dmUpd {
  display: grid; gap: .35rem; justify-items: start;
  margin-bottom: .3rem; text-decoration: none; color: inherit;
}
.dmMsg.is-mine .dmUpd { justify-items: end; margin-left: auto; }
.dmUpd__lbl {
  display: inline-flex; align-items: center; gap: .35rem;
  font-size: .68rem; font-weight: 600; color: var(--text-lo); letter-spacing: .01em;
}
.dmUpd__lbl svg { width: .85rem; height: .85rem; fill: none; stroke: currentColor;
  stroke-width: 2.2; stroke-linecap: round; stroke-linejoin: round; }
.dmUpd__row { display: flex; align-items: center; gap: .55rem; }
.dmMsg.is-mine .dmUpd__row { flex-direction: row-reverse; }
/* A quote bar on the side the bubble hangs from, like any other reply. */
.dmUpd__row::before {
  content: ''; align-self: stretch; width: 3px; border-radius: 3px;
  background: rgb(240 198 74 / .55);
}

/* The thumbnail: a 9:16 miniature of the update, story-shaped. */
.dmUpd__thumb {
  position: relative; flex: none; overflow: hidden;
  width: 3.4rem; aspect-ratio: 9 / 16; border-radius: 10px;
  display: grid; place-items: center; padding: .25rem;
  background: linear-gradient(160deg, var(--a, #3a3d4a), var(--b, #1b1d26));
  box-shadow: 0 8px 22px -10px rgb(0 0 0 / .8);
  transition: transform .25s cubic-bezier(.34, 1.56, .64, 1);
}
a.dmUpd:hover .dmUpd__thumb, a.dmUpd:focus-visible .dmUpd__thumb { transform: scale(1.06) rotate(-2deg); }
.dmMsg.is-mine a.dmUpd:hover .dmUpd__thumb { transform: scale(1.06) rotate(2deg); }
.dmUpd__thumb img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; max-width: none; }
.dmUpd__words {
  position: relative; z-index: 1; text-align: center; color: #fff;
  font-size: .42rem; font-weight: 700; line-height: 1.25; text-shadow: 0 1px 3px rgb(0 0 0 / .5);
  display: -webkit-box; -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden;
  overflow-wrap: anywhere;
}
.dmUpd__cap {
  position: absolute; left: .2rem; right: .2rem; bottom: .25rem; z-index: 1;
  color: #fff; font-size: .38rem; font-weight: 600; text-align: center; text-shadow: 0 1px 3px #000;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
/* The same gradient ring the tray uses, so it reads as "an update". */
.dmUpd__ring {
  position: absolute; inset: 0; border-radius: inherit; pointer-events: none;
  box-shadow: inset 0 0 0 1.5px rgb(255 255 255 / .35);
}
.dmUpd.is-gone .dmUpd__thumb {
  width: 2.4rem; background: var(--cmx-pane); border: 1px dashed var(--cmx-hair); box-shadow: none;
}
.dmUpd__gone { font-size: .9rem; opacity: .6; }
.dmUpd__note { font-size: .7rem; font-style: italic; color: var(--text-lo); }
.dmUpd.is-gone .dmUpd__row::before { background: var(--cmx-hair); }

@media (prefers-reduced-motion: reduce) { .dmUpd__thumb { transition: none; } }

/* ══════════════════════════════════════════════════════════════════════════
   THE MESSAGE BOX GROWS (rooms + DMs)
   It was a one-line <input>: on a phone a long message ran off the right edge
   and the only way back to the start was dragging the caret — which barely
   works on iOS, so editing the middle of a sentence was close to impossible.
   Now it wraps and grows with the text, like WhatsApp: one line → up to about
   five, then it scrolls inside. The pill softens into a rounded box as it
   grows, and the buttons stay pinned to the bottom line beside it.
   ══════════════════════════════════════════════════════════════════════════ */
.cmxForm { align-items: flex-end; }
.cmxForm .cmxField {
  flex: 1; min-width: 0; box-sizing: border-box; display: block;
  height: 2.9rem; min-height: 2.9rem; max-height: 9.2rem;
  padding: .78rem 1.05rem; margin: 0;
  border-radius: 1.45rem; border: 1px solid var(--cmx-hair);
  background: var(--surface-2); color: var(--text-hi);
  font: inherit; font-size: var(--fs-sm); line-height: 1.35;
  resize: none; outline: none; overflow-y: hidden; overflow-x: hidden;
  overflow-wrap: anywhere; white-space: pre-wrap;
  scrollbar-width: thin; scrollbar-color: var(--cmx-hair) transparent;
  -webkit-user-select: text; user-select: text; touch-action: auto;
  transition: border-color var(--dur) var(--ease-out), border-radius .2s ease, box-shadow var(--dur) var(--ease-out);
}
.cmxForm .cmxField::placeholder { color: var(--text-lo); }
.cmxForm .cmxField:focus { border-color: var(--y-yellow); box-shadow: 0 0 0 3px rgb(240 198 74 / .12); }
.cmxForm .cmxField.is-multi { border-radius: 1.1rem; }
.cmxForm .cmxField.is-scroll { overflow-y: auto; overscroll-behavior: contain; }
/* The buttons beside it keep their height and sit on the last line. */
.cmxForm > .cmxIcon, .cmxForm > label.cmxIcon, .cmxForm > .cmxSend { flex: none; margin-bottom: .2rem; }

/* Line breaks typed in the box show in the bubble. */
.cmxBubble, .dmBubble { white-space: pre-line; }

@media (max-width: 46rem) {
  .cmxForm .cmxField { font-size: 1rem; }       /* 16px — below it iOS zooms the page on focus */
}
/* DM header: "🤝 Connected" is also the disconnect control */
.dmTop__tag--btn { border: 1px solid transparent; font: inherit; font-size: .62rem; cursor: pointer;
  transition: border-color var(--dur) var(--ease-out), color var(--dur) var(--ease-out); }
.dmTop__tag--btn:hover, .dmTop__tag--btn:focus-visible { color: var(--y-red); border-color: rgb(232 84 74 / .45); }
