@import url("./tokens.css");

*,
*::before,
*::after {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
}

body {
  margin: 0;
  min-height: 100dvh;
  font-family: var(--font-ui);
  font-size: var(--text-base);
  line-height: var(--leading-normal);
  color: var(--text-default);
  background: var(--surface-page);
}

/* Blazor's FocusOnNavigate moves focus to the <h1> after every navigation so a screen reader
   announces the new page. The browser then draws its focus ring around that heading, which to a
   sighted user reads as a rendering fault: a blue box around the page title on first load.
   Only tabindex="-1" elements are silenced — keyboard users cannot reach those by tabbing, so no
   focus indicator that anyone navigates by is being removed. */
[tabindex="-1"]:focus {
  outline: none;
}

/* Numerals in a proportional Arabic face jitter as counts change; the mono face holds the width. */
.numeric,
time,
.count {
  font-family: var(--font-numeric);
  font-variant-numeric: tabular-nums;
}

h1, h2, h3 {
  margin: 0;
  color: var(--text-strong);
  line-height: var(--leading-tight);
  font-weight: 600;
  letter-spacing: -0.01em;
}

a {
  color: var(--accent-strong);
  text-underline-offset: 0.2em;
}

/* ── Sign-in ─────────────────────────────────────────────────────────────────
   An editorial split rather than a centred card on a gradient: the left panel
   states what the product is, the right one asks for two fields and nothing else. */

.auth {
  display: grid;
  grid-template-columns: 1.1fr 1fr;
  min-height: 100dvh;
}

/* The story panel carries the brand, so it is the brand colour — not --surface-inverted, which is
   the ink value, mixed to be *text*. A flat near-black half-screen is the most inert thing a
   sign-in page can put beside a form.

   The wash is two very large radial gradients rather than an image: no request, no CSP question,
   no asset to keep in step with the palette, and it gives the panel a light source so the type
   sits on something instead of floating on a void. */
.auth__story {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: var(--space-8);
  padding: var(--space-12) clamp(var(--space-8), 6vw, var(--space-12));
  color: var(--nav-ink);
  background:
    radial-gradient(120% 80% at 85% 0%, oklch(from var(--accent) l c h / 0.20), transparent 60%),
    radial-gradient(90% 70% at 10% 100%, oklch(from var(--brand-600) l c h / 0.45), transparent 55%),
    var(--brand-900);
  overflow: hidden;
}

/* The headline block sits a little above the optical centre. Dead-centred between a wordmark at the
   top and a note at the bottom, it reads as the leftover space rather than as the point of the
   panel. */
.auth__story > div { margin-block: auto; }

/* The wordmark takes the same lockup as the sidebar's, so the page you sign in on and the product
   you arrive in are recognisably one thing. */
.auth__wordmark .wordmark__mark {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  padding: 5px;
  border-radius: 0.55rem;
  background: var(--accent);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.22);
}

/* Three claims under the lede. The panel was a headline, a sentence and a footnote spread over
   900px of vertical space by space-between — mostly emptiness, which reads as a page that has not
   finished loading rather than as restraint. */
.auth__points {
  display: flex;
  flex-direction: column;
  gap: var(--space-3);
  margin: var(--space-6) 0 0;
  padding: 0;
  list-style: none;
  max-width: 34ch;
}

.auth__point {
  display: flex;
  align-items: start;
  gap: var(--space-3);
  font-size: var(--text-sm);
  color: oklch(from var(--nav-ink) l c h / 0.82);
}

.auth__point .ico {
  margin-block-start: 2px;
  color: var(--accent);
}

.auth__wordmark {
  font-size: var(--text-lg);
  font-weight: 600;
  letter-spacing: 0.02em;
}

.auth__headline {
  font-size: var(--text-display);
  font-weight: 600;
  color: var(--nav-ink);
  max-width: 20ch;
  text-wrap: balance;
  letter-spacing: -0.02em;
}

.auth__lede {
  margin: var(--space-4) 0 0;
  max-width: 42ch;
  color: oklch(from var(--nav-ink) l c h / 0.74);
}

.auth__note {
  font-size: var(--text-sm);
  color: oklch(from var(--nav-ink) l c h / 0.55);
}

/* The column: the form plus anything that belongs beside it.
   Both selectors, because only the sign-in page has the wrapper. When the padding and background
   moved off .auth__form and onto .auth__panel, the two pages that never got a wrapper — set-password
   and forgot-password — lost their side padding entirely and have been rendering edge to edge ever
   since, on every screen size. Measured at 390px: heading, label and field all at x=0.
   The second selector is deliberately `.auth > .auth__form`, a direct child, so the form inside the
   wrapper does not take the padding twice. */
.auth__panel,
.auth > .auth__form {
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: var(--space-12) clamp(var(--space-6), 5vw, var(--space-12));
  background: var(--surface-raised);
}

.auth__form {
  display: flex;
  flex-direction: column;
  gap: var(--space-6);
}

/* One measure for the column. Without it the fields stretch across the full half-screen, and on a
   wide monitor the eye travels a long way from a label to the field it belongs to — the form reads
   as unfinished rather than spacious. */
.auth__panel > *,
.auth__form > * {
  width: 100%;
  max-width: 26rem;
  margin-inline: auto;
}

.auth__form h1 {
  font-size: var(--text-2xl);
  font-weight: 600;
  letter-spacing: -0.015em;
}

.auth__form .button {
  padding-block: var(--space-3);
  font-size: var(--text-base);
}

/* The fields want a little more presence than a nav search box: this is the only thing on the
   column, and a 1px hairline on a near-white ground reads as a placeholder for a field. */
.auth__form .field > input {
  padding-block: var(--space-3);
  background: var(--surface-raised);
  border-color: var(--border-strong);
}

.auth__culture {
  margin-block-start: var(--space-8);
}

.field {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
}

.field > label {
  font-size: var(--text-sm);
  font-weight: 500;
  color: var(--text-strong);
}

.field > input {
  padding: var(--space-3) var(--space-4);
  font: inherit;
  color: var(--text-strong);
  background: var(--surface-page);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}

.field > input:hover {
  border-color: var(--border-strong);
}

.field > input:focus-visible {
  outline: none;
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.button {
  padding: var(--space-3) var(--space-6);
  font: inherit;
  font-weight: 600;
  color: var(--text-inverted);
  background: var(--accent-strong);
  border: 1px solid transparent;
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: transform var(--duration-fast) var(--ease-out),
              background var(--duration-fast) var(--ease-out);
}

.button:hover {
  background: var(--accent);
}

.button:active {
  transform: translateY(1px);
}

.button:focus-visible {
  outline: none;
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.button--quiet {
  color: var(--text-default);
  background: transparent;
  border-color: var(--border-subtle);
}

.button--quiet:hover {
  background: var(--surface-sunken);
}

.notice {
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius-md);
  font-size: var(--text-sm);
}

.notice--error {
  color: var(--danger);
  background: var(--danger-wash);
}

.notice--ok {
  color: var(--accent-strong);
  background: var(--accent-wash);
}

.muted {
  color: var(--text-muted);
  font-size: var(--text-sm);
}

.stack {
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

.row {
  display: flex;
  align-items: center;
  gap: var(--space-3);
}

@media (max-width: 860px) {
  /* The story panel becomes a header rather than disappearing: on a phone the product still has
     to say what it is before asking for a password. But only a header — measured at 390×844 it was
     taking 408px, half the screen, to say three lines, and the field it introduces started below
     the fold. Sized to its content, it takes what it needs and the form is visible on arrival. */
  .auth {
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr;
  }

  .auth__story {
    gap: var(--space-3);
    padding: var(--space-6);
  }

  .auth__headline {
    font-size: var(--text-xl);
  }

  .auth__note {
    display: none;
  }

  /* The column no longer needs the tall breathing room it has beside a full-height story panel. */
  .auth__panel,
  .auth > .auth__form {
    padding-block: var(--space-8);
  }
}

/* ── Workspace ───────────────────────────────────────────────────────────────
   Two columns, both full height, with the conversation owning the scroll. */

.workspace {
  display: grid;
  grid-template-columns: var(--nav-width) 1fr;
  height: 100dvh;
}

/* ── The navigation panel ────────────────────────────────────────────────────
   Three bands that do not scroll together: an identity header, a scrolling list
   of places, and a footer that is always the person using the product. Before
   this, all three were one column in document order, so the sign-out control
   moved as channels were added and the account you were signed in as scrolled
   off the top of its own panel. A person needs to find both of those in the
   same place every time, which means neither can live in the scroll.

   Colour comes from --nav-*, not from re-tinting the page tokens. See tokens.css. */
.sidebar {
  display: grid;

  /* The single column has to be named. A grid with no grid-template-columns gets one IMPLICIT
     column sized `auto`, and an auto track resolves to max-content when its content asks for
     more — it does not clamp to the container. So a long channel name or a wide wordmark made
     every band 283px wide inside a 272px panel, and eleven pixels of navigation painted over the
     conversation on every screen, in both languages. minmax(0, 1fr) is the clamp. */
  grid-template-columns: minmax(0, 1fr);
  grid-template-rows: auto auto 1fr auto;
  min-height: 0;
  color: var(--nav-ink);
  background: var(--nav-bg);
}

/* Identity. The mark is a real square with the brand behind it rather than a 20px favicon floating
   in text: at nav sizes an outlined glyph on a dark ground disappears, and a product that cannot
   be seen in its own sidebar has no presence anywhere else. */
.nav__brand {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-4) var(--space-4) var(--space-3);
  border-bottom: 1px solid var(--nav-border);
}

.nav__logo {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 0.6rem;
  background: var(--accent);
  box-shadow: inset 0 1px 0 oklch(100% 0 0 / 0.22);
}

.nav__logo img { display: block; width: 1.25rem; height: 1.25rem; }

.nav__brandtext { display: flex; flex-direction: column; min-width: 0; gap: 1px; }

.nav__wordmark {
  font-size: var(--text-base);
  font-weight: 600;
  letter-spacing: -0.01em;
  color: var(--nav-ink);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.nav__space {
  font-size: var(--text-xs);
  color: var(--nav-ink-faint);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Search is a button, not a field. A text input in a navigation panel invites typing into it and
   then does nothing until you press a key it never advertised; a button says what it opens and
   leaves the row it sits in. */
.nav__quick { padding: var(--space-3) var(--space-3) var(--space-2); }

.qsearch {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  text-align: start;
  color: var(--nav-ink-muted);
  background: oklch(100% 0 0 / 0.06);
  border: 1px solid var(--nav-border);
  border-radius: var(--radius-md);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              border-color var(--duration-fast) var(--ease-out);
}

.qsearch:hover { background: var(--nav-hover); border-color: oklch(100% 0 0 / 0.16); }
.qsearch:focus-visible { outline: none; box-shadow: var(--ring-nav); }
.qsearch__label { flex: 1; }

.nav__body {
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: 0 var(--space-3) var(--space-4);
  display: flex;
  flex-direction: column;
  gap: var(--space-4);
}

/* ── A section of the list ───────────────────────────────────────────────────
   The heading is the smallest, faintest thing in the panel on purpose. It is a
   label for a group, read once; the channel names under it are read every time.
   Making them the same weight — which is what the product did — inverts that. */
.navsec { display: flex; flex-direction: column; gap: var(--space-1); }

.navsec__head {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-2) var(--space-1);
}

.navsec__title {
  flex: 1;
  margin: 0;
  font-size: var(--text-xs);
  font-weight: 600;
  letter-spacing: 0.06em;
  color: var(--nav-ink-faint);
}

.navsec__count {
  font-family: var(--font-numeric);
  font-size: var(--text-xs);
  color: var(--nav-ink-faint);
}

/* ── Icon button ─────────────────────────────────────────────────────────────
   The four create-actions used to be underlined grey words in a wrapping row:
   "+ قناة جديدة"، "+ محادثة"، "+ دعوة زميل"، "صورتي". Underlined text is the
   convention for something that navigates, and none of them do — they open a
   panel in place. As icons with a hit area they read as controls, they stop
   consuming two lines of the panel, and the row stops growing as the product does. */
.iconbtn {
  display: inline-grid;
  place-items: center;
  width: 1.875rem;
  height: 1.875rem;
  padding: 0;
  color: inherit;
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.iconbtn--nav { color: var(--nav-ink-faint); }
.iconbtn--nav:hover { color: var(--nav-ink); background: var(--nav-hover); }
.iconbtn--nav:focus-visible { outline: none; box-shadow: var(--ring-nav); }
.iconbtn--nav[aria-pressed="true"] { color: var(--nav-ink); background: var(--nav-active); }

.iconbtn--page { color: var(--text-muted); }
.iconbtn--page:hover { color: var(--text-strong); background: var(--surface-sunken); }
.iconbtn--page:focus-visible { outline: none; box-shadow: var(--ring); }

.channel-list {
  display: flex;
  flex-direction: column;
  gap: 1px;
  margin: 0;
  padding: 0;
  list-style: none;
}

/* ── A place you can go ──────────────────────────────────────────────────────
   Rows, not buttons-that-look-like-links. The unread weight is carried by the
   *name* going to full ink and semibold, not only by the count on the end — a
   number at the far edge of a 17rem panel is on the opposite side of the row
   from the word that tells you whether you care. */
.channel {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2) var(--space-3);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--nav-ink-muted);
  text-align: start;
  background: none;
  border: 0;
  border-radius: var(--radius-md);
  cursor: pointer;
  position: relative;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.channel:hover { color: var(--nav-ink); background: var(--nav-hover); }
.channel:focus-visible { outline: none; box-shadow: var(--ring-nav); }

.channel--current,
.channel--current:hover {
  color: var(--nav-ink);
  font-weight: 600;
  background: var(--nav-active);
}

/* A lit edge on the leading side of the current row. On a dark panel a fill alone shifts by a few
   percent of lightness and is easy to lose; a saturated 2px rule is unmistakable and costs no
   contrast anywhere else. */
.channel--current::before {
  content: "";
  position: absolute;
  inset-block: 20%;
  inset-inline-start: 0;
  width: 2px;
  border-radius: 2px;
  background: var(--accent);
}

.channel--unread { color: var(--nav-ink); font-weight: 600; }

.channel__glyph {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  color: var(--nav-ink-faint);
}

.channel--current .channel__glyph,
.channel--unread .channel__glyph { color: var(--nav-ink-muted); }

.channel__title {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.badge {
  min-width: 1.25rem;
  padding: 1px var(--space-1);
  font-size: var(--text-xs);
  font-weight: 600;
  line-height: 1.4;
  text-align: center;
  color: var(--text-inverted);
  background: var(--accent-strong);
  border-radius: var(--radius-full);
}

/* A mention is a different kind of debt from an unread message, so it gets its own colour rather
   than a bigger number in the same one. */
.badge--mention { background: var(--mention); color: oklch(16% 0.05 65); }

.badge--muted {
  color: var(--nav-ink-faint);
  background: oklch(100% 0 0 / 0.08);
  border: 1px solid var(--nav-border);
}

/* ── The footer: who am I, and how do I leave ────────────────────────────────
   Both were previously a word in the header row — "خروج", underlined, beside the
   product name — with no indication of which account was signed in. On a shared
   office machine that is the difference between leaving and thinking you left. */
/* Two rows, not one.

   As a single row this was the person and four buttons competing for 248px, and the buttons win
   because they have fixed widths: the name got about 74 of those pixels and rendered "سارة القح…".
   The account block exists to answer "who am I signed in as" — on a shared machine that is the
   whole reason it is there — and it was the one part of the panel that could not finish a sentence.

   A menu would have collapsed the four into one button, at the cost of a click and a popup layer
   for four things that are one tap today. A second row costs about 34px of a panel with vertical
   room to spare, and it also says something true: who you are and what you can do are two
   different kinds of thing. */
.nav__foot {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-3);
  border-top: 1px solid var(--nav-border);
  background: var(--nav-bg-raised);
}

.userchip {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 0;
  padding: var(--space-1) var(--space-2);
  font: inherit;
  text-align: start;
  color: var(--nav-ink);
  background: none;
  border: 0;
  border-radius: var(--radius-md);
}

.userchip__text { display: flex; flex-direction: column; min-width: 0; gap: 1px; }

.userchip__name {
  font-size: var(--text-sm);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.userchip__meta { font-size: var(--text-xs); color: var(--nav-ink-faint); }

.nav__tools { display: flex; align-items: center; gap: 2px; }
.nav__tools form { display: contents; }

/* Signing out goes to the far end, away from the three that are ordinary. display: contents on the
   form means the button itself is the flex item, so the margin belongs on the button. */
.nav__tools > form:last-of-type > .iconbtn { margin-inline-start: auto; }

/* Presence as a ring cut out of the panel behind it, so the dot reads as attached to the avatar
   rather than floating over the name next to it. */
.avatar--presence { position: relative; }

.avatar--presence::after {
  content: "";
  position: absolute;
  inset-block-end: -1px;
  inset-inline-end: -1px;
  width: 0.625rem;
  height: 0.625rem;
  border-radius: 50%;
  background: var(--presence-off);
  box-shadow: 0 0 0 2px var(--nav-bg-raised);
}

.avatar--online::after { background: var(--presence-online); }

/* ── Panels the nav opens in place ───────────────────────────────────────────
   Light cards on the navy. They set their own ink rather than inheriting the
   panel's, which is what stopped "إزالة الصورة" from being white-on-white. */
.navpanel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  margin: 0 var(--space-1) var(--space-2);
  padding: var(--space-3);
  color: var(--text-default);
  background: var(--surface-raised);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-mid);
}

.navpanel h3 {
  margin: 0;
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-strong);
}

.navpanel .linklike { color: var(--text-muted); background: none; }
.navpanel .linklike:hover { color: var(--text-strong); }

.navpanel input,
.navpanel .composer__input {
  color: var(--text-strong);
  background: var(--surface-page);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.navpanel input::placeholder { color: var(--text-muted); }

.navpanel .channel { color: var(--text-default); }
.navpanel .channel:hover { color: var(--text-strong); background: var(--surface-sunken); }
.navpanel .channel__glyph { color: var(--text-muted); }

.sidebar__empty {
  padding-inline: var(--space-3);
  font-size: var(--text-sm);
  color: var(--nav-ink-faint);
}

.linklike {
  padding: 0;
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-muted);
  background: none;
  border: 0;
  cursor: pointer;
  text-decoration: underline;
  text-underline-offset: 0.2em;
}

.linklike:hover { color: var(--text-strong); }

/* ── One measure for the column ──────────────────────────────────────────────
   The header, the transcript and the composer are three separate elements, and
   each used to set its own inline padding. On a 1440px screen that put the
   channel name hard against one edge, the member count hard against the other,
   and the send button about eleven hundred pixels from the caret — three
   elements that belong to one column, each finding its own edges.

   The padding formula centres a fixed measure and falls back to a plain gutter
   once the window is narrower than it, so there is no breakpoint to maintain and
   nothing to keep in step: change --conv-max and all three move together. */
.conversation {
  --conv-max: 60rem;
  --conv-gutter: max(var(--space-6), calc((100% - var(--conv-max)) / 2));

  /* A note for the next person: --space-5 does not exist in this scale (1,2,3,4,6,8,12). One
     undefined var() inside a max() makes the whole custom property invalid at computed-value
     time — not the max(), the property — so every padding built on it silently resolves to
     nothing. It fails as a missing gutter, not as a broken rule, which is the hard kind to see. */

  display: grid;
  grid-template-rows: auto 1fr auto auto;
  min-height: 0;
  background: var(--surface-page);
}

/* ── The channel header ──────────────────────────────────────────────────────
   A title, what the room is for, and the things you do to a room. It carried the
   first two and, for the third, a grey "12 عضوًا" — a fact rather than a control,
   at the far end of a 1100px bar from the name it describes. Members, pinned
   items and search are all one click away in every product people arrive from;
   here they were nowhere. */
.conversation__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--conv-gutter, var(--space-6));
  border-bottom: 1px solid var(--border-subtle);
  background: var(--surface-raised);
}

/* The channel glyph, set as a mark. A bare "#" in running text is punctuation; in a tinted square
   it is the room's icon, and it gives the header a fixed left edge that a variable-length title
   cannot provide on its own. */
.conv__mark,
.thread__mark {
  display: grid;
  place-items: center;
  flex-shrink: 0;
  width: 2rem;
  height: 2rem;
  color: var(--accent-strong);
  background: var(--accent-wash);
  border-radius: var(--radius-md);
}

.conversation__heading { min-width: 0; flex: 1; }

.conversation__title {
  font-size: var(--text-lg);
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.conversation__topic {
  margin: 1px 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.conv__actions {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  flex-shrink: 0;
}

/* The member count as a control rather than a caption: it is the fastest way to answer "who is in
   here", which is the question the number raises and used to leave unanswered. */
.conv__members {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-2);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-muted);
  background: none;
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.conv__members:hover { color: var(--text-strong); border-color: var(--border-strong); }
.conv__members:focus-visible { outline: none; box-shadow: var(--ring); }

.conversation__empty {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  align-items: center;
  justify-content: center;
  grid-row: 1 / -1;
  text-align: center;
}

.messages {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-6) var(--conv-gutter, var(--space-6)) var(--space-4);
  overflow-y: auto;
  min-height: 0;
}

/* Newest against the composer. A short conversation otherwise pins itself to the top of a tall
   window and leaves a field of empty space between the last thing anyone said and the box you say
   the next thing in — the two parts of the screen that belong together, pushed apart.

   An auto margin on the first child, NOT justify-content: flex-end on the container. The two look
   identical while everything fits and diverge completely when it does not: with flex-end the
   overflow goes off the *start* edge, where a scroll container cannot reach it. Measured with
   twenty messages in a 672px column — scrollHeight equalled clientHeight, so the browser reported
   no overflow at all, the first message sat 1929px above the top of the list, and scrollTop would
   not move off zero. Every message older than one screenful was unreachable, on desktop as much as
   on a phone.

   An auto margin resolves to zero as soon as there is no free space, so the tall case scrolls
   normally and the short case still sits at the bottom. */
.messages > *:first-child {
  margin-block-start: auto;
}

.messages__empty {
  margin: auto;
  text-align: center;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  align-items: center;
}

/* The mark, quiet. An empty screen wants something to rest on, but a full-strength logo in the
   middle of a conversation you are about to start is an advertisement. */
.empty__mark {
  margin: 0 0 var(--space-2);
  line-height: 0;
  opacity: 0.5;
}

.empty__mark img {
  display: block;
}

.empty__lead {
  margin: 0;
  font-weight: 600;
  color: var(--text-strong);
}

/* The mark sits on the baseline of the name it belongs to, not floating above it. */
.sidebar__wordmark,
.auth__wordmark {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
}

.wordmark__mark {
  border-radius: 5px;
  flex-shrink: 0;
}

/* Avatar in its own column, everything else in the second, so wrapped lines align under the first
   line of text instead of running back under the avatar. */
/* ── A day, named ────────────────────────────────────────────────────────────
   Twenty messages with twenty timestamps and no divisions is a wall. One rule per
   day, with the date sitting on it, is the cheapest structure a transcript can
   have — and it answers "was that today?" without reading a single clock. */
.daysep {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin: var(--space-4) 0 var(--space-2);
  font-size: var(--text-xs);
  font-weight: 600;
  color: var(--text-muted);
}

.daysep::before,
.daysep::after {
  content: "";
  flex: 1;
  height: 1px;
  background: var(--border-subtle);
}

.daysep__label {
  padding: 2px var(--space-3);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
}

.message {
  position: relative;
  display: grid;
  grid-template-columns: 2.25rem 1fr;
  align-items: start;
  gap: 2px var(--space-3);
  padding: var(--space-1) var(--space-2);
  border-radius: var(--radius-md);
}

.message:hover {
  background: var(--surface-sunken);
}

/* ── Consecutive messages from one person ────────────────────────────────────
   Ten lines from the same colleague inside a minute were ten avatars and ten
   repetitions of their name — the transcript spent more vertical space saying
   who was talking than saying what they said. A continuation drops both and
   keeps the text column exactly where it was, so the paragraph still lines up.
   The time reappears on hover, in the gutter the avatar left, so nothing is lost. */
.message--cont { margin-block-start: -2px; }

.message--cont .message__meta { display: none; }

.message__gutter {
  grid-column: 1;
  align-self: start;
  padding-block-start: 3px;
  font-size: var(--text-xs);
  color: var(--text-muted);
  text-align: center;
  opacity: 0;
  transition: opacity var(--duration-fast) var(--ease-out);
}

.message:hover .message__gutter { opacity: 1; }

/* The avatar spans every row of its own message, and everything else stacks in the second column —
   so a wrapped line, a reaction row and an attachment all align under the first line of text rather
   than sliding back under the avatar. */
.message > .avatar {
  grid-row: 1 / -1;
  grid-column: 1;
}

.message > *:not(.avatar):not(.message__gutter):not(.message__tools) {
  grid-column: 2;
}

/* Edit, delete, reply and the reaction picker appear on the message you are pointing at. Shown on
   every row at once they read as part of what each person said — four controls of visual weight
   under every sentence, on a screen whose whole job is to make sentences easy to read.
   Reactions people have already left are not controls and stay visible.
   :focus-within keeps them reachable by keyboard, where there is no pointer to hover with. */
.message__tools {
  position: absolute;
  inset-block-start: calc(var(--space-2) * -1);
  inset-inline-end: var(--space-3);
  display: flex;
  align-items: center;
  gap: 1px;
  padding: 2px;
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-mid);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--duration-fast) var(--ease-out);
  z-index: 2;
}

.message:hover .message__tools,
.message:focus-within .message__tools {
  opacity: 1;
  pointer-events: auto;
}

/* A touch screen has no hover state, so hiding behind one would hide them for good.

   Visible AND floating is the combination that fails, though, and it took the message grouping to
   show it. A card at inset-block-start: -8px overlaps the row above by design — harmless while
   only the row under the pointer is showing one, and on a phone every row shows one at once. On
   six grouped replies, six cards stacked into a single pile of overlapping white rectangles down
   the side of the thread, covering each other and the text.

   So on touch they come out of the overlay and sit in the flow: end-aligned under the message they
   belong to, one row each, nothing on top of anything. */
@media (hover: none) {
  .message__tools {
    position: static;
    grid-column: 2;
    justify-self: end;

    /* Last, not first. It is written before the body in the markup — where it belongs when it
       floats over the top edge of the row — and in the flow that put the controls above the
       sentence they act on. */
    order: 1;

    opacity: 1;
    pointer-events: auto;
    margin-block-start: var(--space-1);

    /* Nothing to lift off any more, so the card goes and the buttons stand on their own. A
       shadowed, bordered box under every single message is more furniture than the controls
       inside it are worth. */
    box-shadow: none;
    background: none;
    border: 0;
    padding: 0;
  }
}

.avatar {
  display: grid;
  place-items: center;
  width: 2.25rem;
  height: 2.25rem;
  border-radius: 50%;
  font-size: var(--text-sm);
  font-weight: 600;
  line-height: 1;
  color: oklch(30% 0.06 var(--avatar-hue, 265));
  background: oklch(88% 0.07 var(--avatar-hue, 265));
  user-select: none;

  /* The circle is the mask. A photograph is almost never square, and object-fit crops it to one
     without squashing anybody's face. */
  overflow: hidden;
  flex-shrink: 0;
}

.avatar__image {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Big enough to judge a photograph by before accepting it — the 36px one in a message row tells
   you almost nothing about how the crop landed. */
.avatar--large {
  width: 4.5rem;
  height: 4.5rem;
  font-size: var(--text-lg);
}

.photo-row {
  display: flex;
  align-items: center;
  gap: var(--space-4);
}

/* The native file input cannot be styled across browsers, so the label is the control. Hidden by
   size rather than display:none, which would take it out of the keyboard's reach. */
.photo-row label input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.message__meta {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  font-size: var(--text-sm);
}

.message__author {
  font-weight: 600;
  color: var(--text-strong);
}

.message__time,
.message__flag { color: var(--text-muted); font-size: var(--text-xs); }

.message__body {
  margin: 0;
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.message__body--deleted {
  color: var(--text-muted);
  font-style: italic;
}

.message__thread {
  margin: 0;
  font-size: var(--text-sm);
  color: var(--accent-strong);
}

.typing {
  margin: 0;
  padding: 0 var(--conv-gutter, var(--space-6)) var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ── The composer ────────────────────────────────────────────────────────────
   One box that contains everything you do to a message, rather than three
   controls sitting in a row on the page: a circular attach button, a pill-shaped
   field, and a rectangular send button pinned to the far edge of the window —
   three shapes, three alignments, and eleven hundred pixels between the caret
   and the button that sends what you typed.

   The frame takes the focus ring, so pressing into the field lights the whole
   control and the relationship between the parts is stated by the design rather
   than inferred from proximity. */
.composer {
  padding: var(--space-3) var(--conv-gutter, var(--space-6)) var(--space-6);
  background: var(--surface-raised);
  border-top: 1px solid var(--border-subtle);
}

.composer__box {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-2) var(--space-2) var(--space-1);
  background: var(--surface-page);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-lg);
  transition: border-color var(--duration-fast) var(--ease-out),
              box-shadow var(--duration-fast) var(--ease-out);
}

.composer__box:focus-within {
  border-color: var(--accent);
  box-shadow: 0 0 0 3px var(--accent-wash);
}

.composer__input {
  width: 100%;
  padding: var(--space-2) var(--space-2);
  font: inherit;
  color: var(--text-strong);
  background: none;
  border: 0;
}

.composer__input:focus-visible { outline: none; }

.composer__bar {
  display: flex;
  align-items: center;
  gap: var(--space-1);
}

.composer__spacer { flex: 1; }

/* Send is the one filled thing on the screen. It is round because it is a single action with no
   label — and it is disabled, not hidden, when there is nothing to send, so its position never
   moves under the pointer already travelling towards it. */
.composer__send {
  display: grid;
  place-items: center;
  width: 2rem;
  height: 2rem;
  color: var(--text-inverted);
  background: var(--accent-strong);
  border: 0;
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              opacity var(--duration-fast) var(--ease-out);
}

.composer__send:hover:not(:disabled) { background: var(--accent); }
.composer__send:focus-visible { outline: none; box-shadow: var(--ring); }

.composer__send:disabled {
  opacity: 0.35;
  cursor: default;
  background: var(--text-muted);
}

/* The send glyph points along the writing direction. Mirrored in RTL, an arrow that means "away
   from me" otherwise points back at the person who drew it. */
html[dir="rtl"] .composer__send .ico { transform: scaleX(-1); }

.composer__hint {
  padding-inline: var(--space-2);
  font-size: var(--text-xs);
  color: var(--text-muted);
}

.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* On a wide screen the list never goes away, so there is nothing to go back to. */
.pane-back { display: none; }

@media (max-width: 720px) {
  /* Two screens, not two columns. The previous rule stacked them and capped the sidebar at 40dvh,
     which on a 844px-tall phone spent 338px on navigation — most of it empty — and left the
     conversation 305px between a header and a composer: room for one message. Worse, the stacked
     grid did not fill, leaving a 29px band of page background between the two panes.
     Now one pane occupies the screen and the other is out of the flow, so the conversation gets the
     whole height and the list gets a full-height list. */
  .workspace {
    grid-template-columns: 1fr;
    grid-template-rows: 1fr;
  }

  .workspace > .sidebar,
  .workspace > .conversation {
    grid-area: 1 / 1;
    min-height: 0;
  }

  .workspace[data-pane="conversation"] > .sidebar,
  .workspace[data-pane="channels"] > .conversation {
    display: none;
  }

  .pane-back {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    min-height: 44px;
    margin-inline-start: calc(var(--space-2) * -1);
    font-size: var(--text-lg);
    color: var(--text-default);
    background: none;
    border: 0;
    border-radius: var(--radius-md);
    cursor: pointer;
  }

  .pane-back:hover { background: var(--surface-sunken); }

  /* The head is a row of three now — back, title, count — and the title is the part that gives way,
     because a long channel name is the only one of the three that can be truncated and still leave
     the screen usable. */
  .conversation__head { gap: var(--space-2); }

  .conversation__heading { min-width: 0; }

  .conversation__title {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  /* The panes are the full height of the screen, so the ends of both sit under the home indicator
     on a phone that has one. env() is zero everywhere else, so this costs nothing on a device
     without the notch. */
  .composer { padding-bottom: max(var(--space-6), env(safe-area-inset-bottom)); }

  .sidebar { padding-bottom: max(var(--space-4), env(safe-area-inset-bottom)); }

  /* A message column at 390px cannot spare 24px a side and still hold a sentence. */
  .messages { padding-block: var(--space-4); }

  .conversation,
  .conversation--threading { --conv-gutter: var(--space-4); }

  /* Rubber-banding the whole page while scrolling a message list reads as the app coming loose.
     Contained, the bounce stops at the end of the list. */
  .messages,
  .sidebar,
  .thread__body { overscroll-behavior: contain; }
}

/* ── Threads, reactions, attachments ─────────────────────────────────────── */

/* Wraps rather than distributing. space-between spread three actions across the sidebar acceptably
   and four of them into a cramped line of near-touching links; a fifth would have been unreadable.
   Wrapping keeps each action legible at any sidebar width and lets the set grow. */
.sidebar__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2) var(--space-4);
  padding-inline: var(--space-2);
}

/* The language switch is not a workspace action; it goes to the end of the row, or onto its own
   line when the others fill the first. */
.sidebar__actions > form:last-child {
  margin-inline-start: auto;
}

/* These create things — they are the primary actions of the sidebar, and underlined grey text is
   what you use for an aside. Given a shape and a hit area they read as things to press, and the
   underline goes: it belongs to links that navigate, and none of these do. */
.sidebar__actions .linklike {
  padding: var(--space-1) var(--space-2);
  border-radius: 0.375rem;
  text-decoration: none;
}

.new-channel {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding: var(--space-3);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

/* The thread is a third column, not a modal: a modal would hide the channel the thread belongs to,
   and reading a reply almost always means glancing back at what it replies to.

   The two placement rules under it are the whole of it, and without them this class did not open a
   column — it scrambled the screen. .conversation is a FOUR-ROW, one-column grid; adding a second
   column here left every child to auto-placement, which fills row-major: the header took (1,1),
   the message list took (1,2) — beside the header, not under it — the composer dropped to (2,1)
   in the middle of the empty page, and the thread landed under the messages. Opening any thread
   rearranged the conversation into four unrelated boxes. Measured on the first live round that
   ever pressed the reply button. */
.conversation--threading {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(300px, 380px);
}

/* Everything the conversation already had stays in one column and keeps the row layout it has
   without the thread open — the header on its auto row, the messages on the 1fr, the composer
   under them. */
.conversation--threading > *:not(.thread) { grid-column: 1; }

/* The thread is the full height of the pane beside it, not a box on one of its rows. */
.conversation--threading > .thread {
  grid-column: 2;
  grid-row: 1 / -1;
}

.thread {
  display: grid;
  grid-template-rows: auto 1fr auto;
  min-height: 0;
  background: var(--surface-raised);
  border-inline-start: 1px solid var(--border-subtle);

  /* Stated rather than inherited. The composer's padding is written in --conv-gutter, which the
     conversation sets to half of whatever the window has spare beyond 60rem — a number computed
     for a full-width reading column and meaningless in a 380px sidebar. It happened to clamp to
     the floor here, which is a right answer arrived at by accident and would stop being right the
     moment the conversation's own measure changed. */
  --conv-gutter: var(--space-4);
}

/* The header answers the same two questions the channel header does — what am I looking at, and
   where is it — in the same shapes: a marked glyph, a title with its address under it, and the
   control set at the far end. */
.thread__head {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3) var(--space-4);
  border-bottom: 1px solid var(--border-subtle);
}

.thread__heading { min-width: 0; flex: 1; }

.thread__title {
  font-size: var(--text-base);
  font-weight: 600;
}

.thread__meta {
  margin: 1px 0 0;
  font-size: var(--text-xs);
  color: var(--text-muted);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The same gap the message list uses, not four times it. At --space-4 every reply was its own
   island and the grouping rules the transcript follows could never show: two lines typed by one
   person a second apart sat as far apart as two people a day apart. */
.thread__body {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding: var(--space-4) var(--space-4) var(--space-2);
  overflow-y: auto;
  min-height: 0;
}

/* An invitation, not a report of absence. The rule above it already says there are no replies. */
.thread__prompt {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
  text-align: center;
}

.reactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);
  margin-top: var(--space-1);
}

.chip {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 2px var(--space-2);
  font: inherit;
  font-size: var(--text-sm);
  color: var(--text-default);
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-full);
  cursor: pointer;
  transition: border-color var(--duration-fast) var(--ease-out);
}

.chip:hover { border-color: var(--border-strong); }

/* Your own reaction is outlined rather than filled: a filled chip in an accent colour reads as
   "unread", and the reaction bar would then compete with the badge that means exactly that. */
.chip--mine {
  color: var(--accent-strong);
  border-color: var(--accent);
  background: var(--accent-wash);
}

.chip--add {
  color: var(--text-muted);
  background: transparent;
}

.chip-menu { position: relative; }

.picker {
  position: absolute;
  inset-block-end: calc(100% + var(--space-1));
  inset-inline-start: 0;
  display: flex;
  gap: var(--space-1);
  padding: var(--space-2);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-mid);
  z-index: 10;
}

.picker__item {
  padding: var(--space-1);
  font-size: var(--text-lg);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.picker__item:hover { background: var(--surface-sunken); }

.message__reply {
  margin-inline-start: auto;
  color: var(--accent-strong);
}

.attachments {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-1) 0 0;
  padding: 0;
  list-style: none;
}

.attachment {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-default);
  text-decoration: none;
  background: var(--surface-sunken);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
}

.attachment:hover { border-color: var(--border-strong); }

/* A preview, not a full-bleed photograph. Capped in both directions so one tall screenshot cannot
   push the rest of the conversation off the screen, and so a row of them stays a row.
   aspect-ratio is deliberately absent: a fixed one would crop, and the crop always takes the part
   of the screenshot the sender was pointing at. */
.attachment-image {
  display: inline-block;
  max-width: min(100%, 22rem);
  border-radius: var(--radius-md);
  overflow: hidden;
  border: 1px solid var(--border-subtle);
  background: var(--surface-sunken);
  line-height: 0;
}

.attachment-image:hover { border-color: var(--border-strong); }

.attachment-image img {
  display: block;
  width: 100%;
  max-height: 20rem;

  /* contain, not cover: the whole image, letterboxed, rather than a confident crop of the middle
     of somebody's screenshot. */
  object-fit: contain;
}

.attachment__size { color: var(--text-muted); font-size: var(--text-xs); }

@media (max-width: 900px) {
  /* Below this width the two panels would be about 45 characters each — narrower than a message
     wants to be. The thread takes the whole column instead. */
  .conversation--threading { grid-template-columns: minmax(0, 1fr); }

  /* Back to one column: without this the thread keeps asking for column 2, which no longer exists,
     and the grid grows an implicit one to hold it — off the side of a 390px screen. */
  .conversation--threading > .thread { grid-column: 1; grid-row: auto; }

  /* Direct children only. As a descendant selector this also caught the thread's OWN reply box,
     which is a .composer nested inside .thread — so on a phone you could open a thread, read every
     reply in it, and have nothing to answer with. It hid the one control the screen existed for. */
  .conversation--threading > .messages,
  .conversation--threading > .composer { display: none; }
}

/* Inside the box now, so it is a quiet icon among the other things you do to a message rather than
   an outlined pill competing with the send button for the eye. */
.composer__attach {
  display: inline-grid;
  place-items: center;
  width: 1.875rem;
  height: 1.875rem;
  color: var(--text-muted);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
  transition: background var(--duration-fast) var(--ease-out),
              color var(--duration-fast) var(--ease-out);
}

.composer__attach:hover { color: var(--text-strong); background: var(--surface-sunken); }
.composer__attach:focus-within { outline: none; box-shadow: var(--ring); }

/* The native file input is unstyleable across browsers, so the label is the control and the input
   is hidden behind it — hidden by size rather than by display:none, which would take it out of the
   keyboard's reach. */
.composer__attach input[type="file"] {
  position: absolute;
  width: 1px;
  height: 1px;
  opacity: 0;
}

.attachments--pending { padding: 0 var(--conv-gutter, var(--space-6)); }

.message__edit {
  display: flex;
  gap: var(--space-2);
  align-items: center;
}

/* ── Bell and search ─────────────────────────────────────────────────────── */

.bell { position: relative; }

.bell__panel {
  position: absolute;
  inset-block-start: calc(100% + var(--space-2));
  inset-inline-end: 0;
  width: min(360px, 80vw);
  max-height: 60dvh;
  overflow-y: auto;
  padding: var(--space-3);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-md);
  box-shadow: var(--shadow-high);
  z-index: 20;
}

.bell__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: var(--space-3);
  margin-bottom: var(--space-2);
}

.bell__list {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.bell__item {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: start;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-2);
  font: inherit;
  text-align: start;
  color: var(--text-default);
  background: none;
  border: 0;
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.bell__item:hover { background: var(--surface-sunken); }

/* Unread is marked with a rule on the leading edge rather than a filled row: a block of colour
   behind text is the first thing to fail a contrast check, and it makes the panel shout. */
.bell__item--unread {
  border-inline-start: 2px solid var(--mention);
  background: var(--mention-wash);
}

.bell__kind { color: var(--text-muted); }

.bell__text {
  display: flex;
  flex-direction: column;
  gap: 2px;
  overflow: hidden;
}

.bell__text > .muted {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.bell__time { color: var(--text-muted); font-size: var(--text-xs); }

.search {
  display: flex;
  flex-direction: column;
  gap: var(--space-2);
  padding-inline: var(--space-2);
}

.search__results {
  display: flex;
  flex-direction: column;
  gap: 2px;
  margin: 0;
  padding: 0;
  list-style: none;
}

.search__hit {
  display: flex;
  flex-direction: column;
  gap: 2px;
  width: 100%;
  padding: var(--space-2);
  font: inherit;
  text-align: start;
  color: var(--text-default);
  background: var(--surface-raised);
  border: 1px solid var(--border-subtle);
  border-radius: var(--radius-sm);
  cursor: pointer;
}

.search__hit:hover { border-color: var(--border-strong); }

.search__hint { font-size: var(--text-xs); color: var(--text-muted); }

.search__where { font-size: var(--text-xs); color: var(--text-muted); }

.search__what {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* ── Touch ───────────────────────────────────────────────────────────────────
   Conditioned on a coarse pointer OR a narrow viewport. The pointer test is the
   one that matters on a real phone; the width test is there because it is the
   one that can be checked in a desktop browser, and a rule that can only be
   verified on hardware nobody has to hand is a rule that goes unverified.
   The cost of the wider net is a narrow desktop window getting 16px fields and
   roomier controls, which is not a fault. */

/* Safari on iOS zooms the page whenever a field smaller than 16px takes focus, and it does not zoom
   back out. The UI scale is 15px by design, which meant every tap on the composer left the person
   reading a chat at 1.2x with the sidebar off screen — on the control they use most.
   Only the fields are raised, not the type scale, so nothing else in the design moves. */
@media (pointer: coarse), (max-width: 720px) {
  /* The class selectors are named explicitly, and this whole block sits at the end of the file.
     `input { font-size }` alone loses to `.composer__input { font: inherit }` on specificity, and
     even matching specificity loses to a rule written later — which is how the fix measured as
     applied and rendered as 15px. */
  input,
  textarea,
  select,
  .field > input,
  .composer__input {
    font-size: 1rem;
  }

  /* 44px is the smallest thing a finger hits reliably. Measured before this rule, eighteen controls
     were under it, the notification bell at 13×21 — a target smaller than the fingertip meant to
     find it. Padding rather than a fixed height, so a control still shrink-wraps its label. */
  .linklike,
  .chip,
  .picker__item,
  .iconbtn,
  .composer__send,
  .composer__attach {
    min-height: 44px;
    padding-block: var(--space-2);
    display: inline-flex;
    align-items: center;
  }

  .channel,
  .bell__item,
  .search__hit {
    min-height: 44px;
  }
}


/* ── Icons ───────────────────────────────────────────────────────────────────
   One rule for every glyph in the product: never shrink in a flex row, sit on
   the text baseline rather than the line box, and take the colour of whatever
   they are inside. See Components/Chat/Icon.razor for why they are inline SVG. */
.ico {
  flex-shrink: 0;
  display: block;
}

/* A smaller avatar for the footer chip and the thread head, where a 36px circle would out-weigh
   the name beside it. */
.avatar--sm {
  width: 1.75rem;
  height: 1.75rem;
  font-size: var(--text-xs);
}

/* ── Who is in this room ─────────────────────────────────────────────────────
   Opened from the count in the header. A strip rather than a modal: the question
   "who is in here" is almost always asked while reading the conversation, and a
   dialog that covers the conversation to answer it makes you close it to use it. */
.memberbar {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
  padding: var(--space-3) var(--conv-gutter, var(--space-6));
  background: var(--surface-raised);
  border-bottom: 1px solid var(--border-subtle);
}

.memberbar__item {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-default);
}

.memberbar__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  max-width: 14rem;
}

/* A thread that exists is a fact about the message, so it reads as one: quiet, on the row, in the
   accent that means "there is more of this here". */
.chip--thread {
  color: var(--accent-strong);
  background: var(--accent-wash);
  border-color: transparent;
}

.chip--thread:hover { border-color: var(--accent); }

/* Buttons hold an icon beside their label now, so they need to be a flex row rather than a block
   of text with padding. */
.button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
}

.empty__actions { margin-block-start: var(--space-4); }

/* ── The notification bell ───────────────────────────────────────────────────
   Its own positioning context, so the panel it opens hangs from it rather than
   from whatever ancestor happened to be positioned. The badge sits on the bell
   instead of beside it: a count next to an icon reads as a separate control, and
   at the top of a 17rem panel there is no room for two. */
.bell { position: relative; display: inline-flex; }

.bell__count {
  position: absolute;
  inset-block-start: -2px;
  inset-inline-end: -2px;
  min-width: 1rem;
  padding: 0 3px;
  font-family: var(--font-numeric);
  font-size: 0.625rem;
  font-weight: 600;
  line-height: 1rem;
  text-align: center;
  color: oklch(16% 0.05 65);
  background: var(--mention);
  border-radius: var(--radius-full);
  box-shadow: 0 0 0 2px var(--nav-bg);
}

/* A row in the browse-channels panel: the name, and the one thing you do to it. Not a .channel —
   that class means "somewhere you can go", and pressing one of these joins rather than navigates. */
.browserow {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  width: 100%;
  padding: var(--space-1) var(--space-2);
  font-size: var(--text-sm);
  color: var(--text-default);
}

.browserow .channel__glyph { color: var(--text-muted); }

.button--sm {
  padding: var(--space-1) var(--space-3);
  font-size: var(--text-sm);
}
