/*
 * Plamenu web UI — modern vanilla CSS, no build step.
 *
 * Conventions: design tokens as custom properties, `light-dark()` for theming
 * (the document opts in with `color-scheme: light dark`), native nesting, and
 * logical properties throughout. No magic numbers — spacing, sizing, type and
 * colour all come from the tokens below.
 *
 * Theming: every rule in this file lives in the `app` cascade layer (the
 * whole file is wrapped in `@layer app { … }`, un-reindented to keep diffs
 * readable). The operator stylesheet — /custom.css, loaded after this one —
 * is unlayered, so ANY selector in it outranks ANY rule here, no specificity
 * fights. A theme therefore starts by overriding the `:root` tokens below
 * (palette, spacing, radii, type scale) and only reaches for component
 * selectors when a token can't express the change. Class names follow
 * BEM-ish `block__element--modifier` and are part of that theme surface —
 * rename only with care.
 *
 * Width invariant: nothing may make the page wider than the viewport. This is
 * enforced STRUCTURALLY, not by clipping — on mobile a single un-shrinkable
 * element (a <select> sized to its longest option, a grid column sized to its
 * widest child) grows the layout viewport itself, and no `overflow` guard can
 * shrink an ICB that already grew. So the real rules are:
 *   1. Every grid/flex track that holds content uses `minmax(0, …)` / can shrink
 *      (see the single-column-grid rule below and `.app-shell`).
 *   2. Controls with an intrinsic width (<select>, the language combo) are
 *      capped with `max-inline-size: 100%` and allowed to shrink.
 *   3. Anything holding arbitrary-width content (a code block, a wide table, a
 *      long token) wraps or scrolls *inside its own box*.
 * `html { overflow-x: clip }` below only tidies stray painting overflow (a
 * shadow, a sub-pixel rounding); it is NOT what keeps the page in bounds.
 */

/* Everything below is layered (see the theming note above). The block is not
   re-indented; it closes at the very end of the file. */
@layer app {

:root {
  color-scheme: light dark;

  /* Spacing scale (rem). */
  --space-1: 0.25rem;
  --space-2: 0.5rem;
  --space-3: 0.75rem;
  --space-4: 1rem;
  --space-5: 1.25rem;
  --space-6: 1.5rem;
  --space-8: 2rem;

  /* Type scale. */
  --text-xs: 0.75rem;
  --text-sm: 0.85rem;
  --text-base: 0.975rem;
  --text-lg: 1.15rem;
  --text-xl: 1.5rem;

  --radius: 0.625rem;
  --radius-lg: 1rem;
  --radius-pill: 999px;

  --measure: 40rem; /* main column width */
  --sidebar: 15rem; /* desktop nav width */

  /* Palette resolves per active colour scheme. */
  --bg: light-dark(#f4f6f9, #0f1419);
  --surface: light-dark(#ffffff, #1a1f27);
  --surface-2: light-dark(#eef1f5, #232a34);
  --surface-hover: light-dark(#e7ebf1, #2a313c);
  --border: light-dark(#e0e4ea, #2c333d);
  --text: light-dark(#15191e, #e9edf2);
  --text-muted: light-dark(#5c6672, #93a0ad);
  --accent: light-dark(#3550d6, #7c97f6);
  --accent-soft: light-dark(#eaeefc, #1f2740);
  --accent-contrast: #ffffff;
  --danger: light-dark(#c93a2b, #ec7064);
  --danger-soft: color-mix(in srgb, var(--danger) 12%, var(--surface));
  --boost: light-dark(#179a63, #41c98c);
  --favourite: light-dark(#d4892b, #f0b45c);

  --shadow-sm: 0 1px 2px light-dark(rgba(20, 30, 50, 0.06), rgba(0, 0, 0, 0.4));
  --shadow: 0 4px 20px light-dark(rgba(20, 30, 50, 0.08), rgba(0, 0, 0, 0.5));

  --transition: 140ms ease;
}

* {
  box-sizing: border-box;
}

html {
  -webkit-text-size-adjust: 100%;
  /* Tidies stray painting overflow only (shadows, sub-pixel rounding). It does
     NOT clamp a layout viewport that already grew to fit un-shrinkable content
     — that is prevented structurally (see the width-invariant note above).
     `clip` (not `hidden`) doesn't establish a scroll container, so the sticky
     sidebar keeps sticking to the viewport. */
  overflow-x: clip;
}

body {
  margin: 0;
  background: var(--bg);
  color: var(--text);
  font-family:
    system-ui, -apple-system, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  font-size: var(--text-base);
  line-height: 1.55;
}

a {
  color: var(--accent);
  text-decoration: none;

  &:hover {
    text-decoration: underline;
  }
}

h1 {
  font-size: var(--text-xl);
  line-height: 1.2;
}

h2 {
  font-size: var(--text-lg);
}

button {
  font: inherit;
  cursor: pointer;
  border: 1px solid transparent;
  border-radius: var(--radius-pill);
  padding: var(--space-2) var(--space-5);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  transition:
    filter var(--transition),
    background var(--transition);

  &:hover {
    filter: brightness(1.06);
  }
}

input,
textarea,
select {
  font: inherit;
  width: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
}

select {
  width: auto;
  /* A native <select> sizes to its longest option and, by default, refuses to
     shrink (`min-width: auto`). Inside an auto-sized grid/flex column that
     max-content drags the column — and, on mobile, the whole layout viewport —
     past the screen. Let it shrink and cap it to its container so it never can.
     This is the load-bearing width fix: an overflow guard can't undo an ICB
     that already grew to fit an un-shrinkable control. */
  min-inline-size: 0;
  max-inline-size: 100%;
  border-radius: var(--radius-pill);
  padding-inline: var(--space-3);
}

/* The pill radius reads as a dropdown; a multi-line list box is a block. */
select[multiple] {
  border-radius: var(--radius);
}

/* Checkboxes and radios are inline controls — exempt them from the 100%
   width above, everywhere, instead of re-overriding per component.
   `justify-self` keeps them from stretching inside `display: grid` labels. */
input[type="checkbox"],
input[type="radio"] {
  width: auto;
  justify-self: start;
}

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

.icon {
  inline-size: 1.3em;
  block-size: 1.3em;
  flex: none;
  vertical-align: -0.2em;
}

/* Custom emoji images swapped in for :shortcode: references. */
img.emoji {
  inline-size: auto;
  block-size: 1.3em;
  vertical-align: -0.3em;
  object-fit: contain;
}

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

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

.empty {
  padding: var(--space-8) var(--space-4);
  text-align: center;
}

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

.app-shell {
  display: grid;
  grid-template-columns: var(--sidebar) minmax(0, var(--measure));
  justify-content: center;
  gap: var(--space-6);
  max-width: calc(var(--sidebar) + var(--measure) + var(--space-6));
  margin-inline: auto;
  padding-inline: var(--space-4);
}

.app-main {
  min-width: 0;
  padding-block: var(--space-5) var(--space-8);
}

/* Chrome-free pages (OAuth consent): a single centered column, no nav. */
.focused-main {
  width: 100%;
  max-width: 26rem;
  margin-inline: auto;
  padding: var(--space-4);
}

.column {
  display: grid;
  gap: var(--space-4);
}

/* Width invariant, structural half: single-column content grids pin their track
   to `minmax(0, 1fr)` so it sizes to the container, never to a child's
   max-content. A grid's default implicit `auto` column grows to its widest item
   (a status card with a code block, a search result with a long handle), which
   drags the list — and, on mobile, the whole layout viewport — past the screen.
   `.app-shell`'s minmax tracks can't help once an inner grid re-introduces an
   `auto` column, and no `overflow` guard can shrink an ICB that already grew to
   fit un-shrinkable content. Pair with the <select>/combo caps above. */
.feed,
.notifications,
.thread__ancestors,
.column,
.results,
.account-list,
.relationships-list {
  grid-template-columns: minmax(0, 1fr);
}

/* ---- Sidebar ---------------------------------------------------------- */

.sidebar {
  position: sticky;
  top: 0;
  align-self: start;
  /* Fixed to the viewport height so the account row's `margin-block-start:auto`
     can pin it to the bottom (a bare `max-block-size` would collapse to content
     height and drop that anchor). `overflow-y:auto` then lets the column scroll
     on its own whenever its content is taller than the viewport — a short or
     zoomed screen, a large font, or the extra staff "Admin" row — so the
     compose button and the account/sign-out row can never become unreachable.
     `overscroll-behavior:contain` stops that scroll from chaining to the feed.
     `padding-inline` is required BECAUSE of the scroll: with `overflow-x`
     defaulting to `visible`, setting `overflow-y:auto` promotes the used
     `overflow-x` to `auto` too (CSS Overflow 3), so the flush-to-edge nav
     links / brand / compose button would otherwise have their 2px+2px
     `:focus-visible` outline (and the compose pill's shadow) clipped at the
     scrollport edge. The inset keeps the full focus ring visible and clears
     the scrollbar gutter — the same reason the drawer panel carries it. */
  block-size: 100dvh;
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  padding-block: var(--space-5);
  padding-inline: var(--space-2);
  overflow-y: auto;
  overscroll-behavior: contain;
  scrollbar-width: thin;
}

.brand {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  color: var(--text);
  font-weight: 800;
  font-size: var(--text-lg);

  &:hover {
    text-decoration: none;
  }

  .brand__mark {
    display: grid;
    place-items: center;
    inline-size: 2rem;
    block-size: 2rem;
    border-radius: var(--radius);
    background: var(--accent);
    color: var(--accent-contrast);
  }
}

.nav {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  margin-block-start: var(--space-3);
}

.nav-link {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-pill);
  color: var(--text);
  font-weight: 600;
  transition: background var(--transition);

  &:hover {
    background: var(--surface-2);
    text-decoration: none;
  }

  .icon {
    inline-size: 1.5rem;
    block-size: 1.5rem;
  }
}

/* The notifications bell in the nav and tab bar: a wrapper the unread dot
   anchors to. The dot is pure chrome — screen readers get the hidden "(new)"
   text / title on the link instead. */
.bell {
  position: relative;
  display: inline-flex;
  flex-shrink: 0;
}

.bell__dot {
  position: absolute;
  inset-block-start: -0.1rem;
  inset-inline-end: -0.1rem;
  inline-size: 0.55rem;
  block-size: 0.55rem;
  border-radius: 50%;
  background: var(--accent);
}

.compose-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-2);
  margin-block: var(--space-3);
  padding: var(--space-3);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 700;
  box-shadow: var(--shadow-sm);

  &:hover {
    text-decoration: none;
    filter: brightness(1.06);
  }
}

.account {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: auto;
  padding: var(--space-2);
  border-radius: var(--radius);

  &:hover {
    background: var(--surface-2);
  }

  .account__link {
    display: grid;
    flex: 1;
    min-width: 0;
    color: var(--text);
    line-height: 1.3;

    &:hover {
      text-decoration: none;
    }
  }

  .account__switch {
    display: inline-flex;
    padding: var(--space-2);
    border-radius: var(--radius);
    color: var(--text-muted);

    &:hover {
      background: var(--surface-hover);
      color: var(--text);
      text-decoration: none;
    }
  }

  .account__name {
    font-weight: 700;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .account__handle {
    color: var(--text-muted);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.logout__btn {
  margin-inline-start: auto;
  padding: var(--space-2);
  background: transparent;
  color: var(--text-muted);

  &:hover {
    background: var(--surface-hover);
    color: var(--text);
    filter: none;
  }
}

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

.tabbar {
  display: none;
}

/* ---- Auth ------------------------------------------------------------- */

.auth-card {
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  padding: var(--space-8);
  box-shadow: var(--shadow-sm);
  margin-block-start: var(--space-8);

  h1 {
    margin-block-start: 0;
  }

  .auth-form {
    display: grid;
    gap: var(--space-4);

    label {
      display: grid;
      gap: var(--space-2);
      font-weight: 600;
    }

    /* The agreement checkbox sits inline with its text, not stacked. */
    label.form-check {
      display: flex;
      align-items: baseline;
    }

    button {
      justify-self: start;
    }
  }

  .form-error {
    color: var(--danger);
    font-weight: 600;
  }

  .consent-lead {
    margin-block-start: 0;
    color: var(--text-muted);
  }

  .consent-scopes {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block: var(--space-4);
    padding: 0;
    list-style: none;

    code {
      display: inline-block;
      padding: var(--space-1) var(--space-3);
      background: var(--surface-2);
      border: 1px solid var(--border);
      border-radius: var(--radius-pill);
      font-size: 0.85em;
    }
  }

  .oob-code {
    margin-block: var(--space-4) 0;
    padding: var(--space-4);
    background: var(--surface-2);
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow-wrap: anywhere;
    white-space: pre-wrap;
    user-select: all;
  }

  /* The "other paths" footer links (sign in ↔ register, forgot password). */
  .auth-card__alt {
    margin-block: var(--space-4) 0;
    color: var(--text-muted);
    font-size: var(--text-sm);
  }

  /* ---- Account chooser (multi-account switch) ------------------------- */

  .account-chooser {
    display: grid;
    gap: var(--space-3);
    margin-block: var(--space-4);
    padding: 0;
    list-style: none;
  }

  /* Each entry is a full-width .account-card (an <a> for the current account,
     a submit <button> for the ones to switch into). */
  .account-card--button {
    inline-size: 100%;
    font: inherit;
    text-align: start;
    cursor: pointer;
  }

  .account-card--current {
    border-color: var(--accent);
  }

  .account-chooser__badge {
    margin-inline-start: auto;
    padding: var(--space-1) var(--space-3);
    background: var(--surface-2);
    border-radius: var(--radius-pill);
    color: var(--text-muted);
    font-size: var(--text-sm);
    white-space: nowrap;
  }

  .auth-add-account {
    margin-block-start: var(--space-4);

    summary {
      cursor: pointer;
      font-weight: 600;
    }

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

/* ---- Section selector (view::tab_strip) ------------------------------- *
 * Replaces the old horizontally-scrolling tab strip. A <details> disclosure
 * whose summary shows the current section and whose panel lists the rest —
 * one control for the two-item timeline switch and the twenty-item admin nav
 * alike, with no sideways scrolling and no width blowout. Works with no JS
 * (the browser toggles the disclosure); app.js adds outside-click / Escape
 * closing and viewport-aware placement, reusing the status-menu machinery. */

.nav-select {
  margin-block-end: var(--space-4);
  padding-block-end: var(--space-3);
  border-block-end: 1px solid var(--border);
}

.nav-select__menu {
  position: relative;
  display: inline-block;
  max-inline-size: 100%;
}

/* The summary reads as a select trigger: current value + caret. */
.nav-select__current {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  max-inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--text);
  font-weight: 600;
  cursor: pointer;
  list-style: none;
  transition:
    background var(--transition),
    border-color var(--transition);
}

.nav-select__current::-webkit-details-marker {
  display: none;
}

.nav-select__current:hover {
  background: var(--surface-2);
}

.nav-select__menu[open] > .nav-select__current {
  background: var(--surface-2);
  border-color: var(--accent);
}

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

.nav-select__caret {
  display: inline-flex;
  flex: none;
  opacity: 0.6;
  transition: transform var(--transition);

  .icon {
    inline-size: 1.1rem;
    block-size: 1.1rem;
  }
}

.nav-select__menu[open] > .nav-select__current .nav-select__caret {
  transform: rotate(180deg);
}

/* Self-contained popup shell (the shared one right-aligns for the status "…"
   menu; this opens from the start edge). JS may add is-above / is-end. */
.nav-select__pop {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: calc(100% + var(--space-1));
  z-index: 20;
  min-inline-size: 14rem;
  max-inline-size: min(22rem, 92vw);
  max-block-size: min(70vh, 28rem);
  overflow-y: auto;
  scrollbar-width: thin;
  padding: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.nav-select__pop.is-above {
  inset-block-start: auto;
  inset-block-end: calc(100% + var(--space-1));
}

.nav-select__pop.is-end {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

.nav-select__option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  color: var(--text);
  font-weight: 600;

  &:hover,
  &:focus-visible {
    background: var(--surface-hover);
    text-decoration: none;
  }
}

.nav-select__option-label {
  flex: 1;
}

.nav-select__option-check {
  display: inline-flex;
  flex: none;
  opacity: 0;
  color: var(--accent);

  .icon {
    inline-size: 1.1rem;
    block-size: 1.1rem;
  }
}

.nav-select__option.is-active {
  color: var(--accent);

  .nav-select__option-check {
    opacity: 1;
  }
}

/* ---- Compose ---------------------------------------------------------- */

.compose {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);

  textarea {
    resize: vertical;
    min-block-size: 3.5rem;
    background: var(--bg);
    border-radius: var(--radius);
  }

  .compose__field {
    display: grid;
    gap: var(--space-2);
  }

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

  .compose__count {
    font-size: var(--text-sm);
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;

    &[data-over] {
      color: var(--danger);
      font-weight: 700;
    }
  }
}

/* The footer toolbar (M29.3): icon toggles + selectors on the left, the
   character count and publish button pinned to the right. */
.compose__toolbar {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  flex-wrap: wrap;
  padding-block-start: var(--space-2);
  border-block-start: 1px solid var(--border);
}

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

.compose__actions {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  margin-inline-start: auto;
}

/* Preview sits next to Publish as the secondary action (F6). */
.compose__preview-btn {
  background: var(--surface-2);
  color: var(--text);
  border-color: var(--border);

  &:hover {
    filter: none;
    background: var(--surface);
  }
}

/* The server-rendered draft preview below the composer. Empty (no preview yet)
   it collapses away, so the border/spacing only show once there's a card. */
.compose__preview:not(:empty) {
  margin-block-start: var(--space-4);
  padding-block-start: var(--space-4);
  border-block-start: 1px solid var(--border);
}

.compose__preview-heading {
  font-size: 0.9rem;
  color: var(--text-muted);
  margin-block-end: var(--space-3);
}

/* Validation banner shown when a submit fails and the composer re-renders. */
.compose__error {
  margin-block-end: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border-radius: var(--radius);
  background: var(--danger-soft);
  color: var(--danger);
}

/* "Posting to !group" note above the composer for a group submission (F4b). */
.compose__group-note {
  margin-block-end: var(--space-3);
  color: var(--text-muted);
}

/* The group composer's Title / Link fields, above the body. */
.compose__group-fields {
  display: grid;
  gap: var(--space-3);
  margin-block-end: var(--space-3);
}

/* A one-line hint under a composer field (e.g. "a link needs a title"). */
.compose__hint {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* A compact icon button in the toolbar (media / poll / CW toggles). Hidden
   until the `.js` root reveals it — without JS its section is always open, so
   a toggle would do nothing. */
.compose__tool {
  display: none;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background var(--transition),
    color var(--transition);

  &:hover {
    background: var(--surface-2);
    color: var(--text);
  }

  &.is-active {
    color: var(--accent);
    background: var(--accent-soft);
    border-color: var(--accent);
  }
}

.js .compose__tool {
  display: inline-flex;
}

/* Collapsible groups (content warning, media, poll). Visible by default so the
   no-JS form keeps every field; under `.js` they collapse and only their
   toolbar toggle reopens them. */
.js .compose__section {
  display: none;
}

.js .compose__section.is-open {
  display: grid;
  gap: var(--space-2);
}

/* ---- Composer selectors (M29.5) -------------------------------------- *
 * Visibility, quote policy and language each render a native <select> (the
 * submitted value + no-JS fallback) plus a custom trigger and popup that can
 * show per-option icons — impossible in a native <select>. Without JS the
 * select shows and the custom chrome is hidden; the `.js` root flips that.
 * The language combo also serves the settings forms' language fields. */
.compose__menu,
.compose__combo,
.compose__emoji {
  position: relative;
  display: inline-flex;
  /* Same rule as native selects: these wrap a control (and, for the combo, a
     long language name) whose intrinsic width must not inflate an auto-sized
     column past the viewport. */
  min-inline-size: 0;
  max-inline-size: 100%;
}

/* The native selects inside the combo/menu fallbacks size to their longest
   option too — cap them so they can't spill their (now capped) wrapper. */
.compose__menu-native select,
.compose__combo-native select {
  max-inline-size: 100%;
}

/* Native selects: styled so the no-JS fallback still reads as a control,
   hidden once JS swaps in the custom trigger. */
.compose__menu-native select,
.compose__combo-native select {
  appearance: none;
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text);
  font: inherit;
  cursor: pointer;
}

.js .compose__menu-native,
.js .compose__combo-native {
  display: none;
}

/* Triggers are the JS-only replacements: hidden until the `.js` root reveals
   them (the native select is shown otherwise). */
.compose__menu-trigger,
.compose__combo-trigger {
  display: none;
}

.js .compose__menu-trigger,
.js .compose__combo-trigger {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: var(--space-1) var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-muted);
  font: inherit;
  cursor: pointer;
  transition:
    background var(--transition),
    color var(--transition),
    border-color var(--transition);
}

.js .compose__menu-trigger:hover,
.js .compose__combo-trigger:hover,
.compose__menu-trigger[aria-expanded="true"],
.compose__combo-trigger[aria-expanded="true"] {
  background: var(--surface);
  color: var(--text);
  border-color: var(--accent);
}

.compose__menu-icon {
  display: inline-flex;
  color: var(--text);
}

.compose__menu-caret,
.compose__combo-caret {
  display: inline-flex;
  opacity: 0.55;
}

.compose__menu-caret .icon,
.compose__combo-caret .icon {
  inline-size: 1rem;
  block-size: 1rem;
}

/* The language pill shows the current label, clipped so a long name can't
   stretch the toolbar (the full name still shows in the popup). */
.compose__combo-label {
  max-inline-size: 9rem;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* Shared floating popup shell. Opens downward from the start edge by default
   (before JS measures, or with JS absent); JS adds `is-above` when the space
   over the trigger is the roomier side, and `is-end` to right-align when a
   start-aligned popup would spill past the viewport. (JS also stamps
   `is-below` for symmetry, but downward is the default so it needs no rule.) */
.compose__menu-pop,
.compose__combo-pop,
.compose__emoji-pop,
.status__menu-pop {
  position: absolute;
  inset-inline-start: 0;
  inset-block-start: calc(100% + var(--space-1));
  z-index: 20;
  min-inline-size: 15rem;
  max-inline-size: min(21rem, 92vw);
  padding: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

/* The visibility/quote menus are short, fixed lists — they should never
   scroll; they size to their content and the flip logic keeps them on screen.
   (The language combo scrolls inside its own list, which holds 200+ entries.) */
.compose__menu-pop {
  max-block-size: none;
}

.compose__menu-pop.is-above,
.compose__combo-pop.is-above,
.compose__emoji-pop.is-above,
.status__menu-pop.is-above {
  inset-block-start: auto;
  inset-block-end: calc(100% + var(--space-1));
}

.compose__menu-pop.is-end,
.compose__combo-pop.is-end,
.compose__emoji-pop.is-end,
.status__menu-pop.is-end {
  inset-inline-start: auto;
  inset-inline-end: 0;
}

.compose__menu-pop[hidden],
.compose__combo-pop[hidden],
.compose__emoji-pop[hidden] {
  display: none;
}

/* A visibility/quote option: icon, label + description stack, trailing check. */
.compose__menu-option {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2);
  border: none;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.compose__menu-option:hover,
.compose__menu-option:focus-visible {
  background: var(--surface-hover);
}

.compose__menu-option-icon {
  display: inline-flex;
  margin-block-start: 0.1rem;
  color: var(--text-muted);
}

.compose__menu-option-text {
  display: grid;
  gap: 0.1rem;
  flex: 1;
}

.compose__menu-option-label {
  font-weight: 600;
}

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

.compose__menu-option-check {
  display: inline-flex;
  margin-block-start: 0.1rem;
  color: var(--accent);
  opacity: 0;
}

.compose__menu-option[aria-selected="true"] {
  color: var(--accent);
}

.compose__menu-option[aria-selected="true"] .compose__menu-option-icon,
.compose__menu-option[aria-selected="true"] .compose__menu-option-check {
  color: var(--accent);
  opacity: 1;
}

/* Language/emoji popups: a search box (moved inside, not sitting beside the
   control) over a scrolling list built by JS. */
.compose__combo-search,
.compose__emoji-search {
  inline-size: 100%;
  margin-block-end: var(--space-1);
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);
  font: inherit;
}

.compose__combo-list {
  list-style: none;
  margin: 0;
  padding: 0;
  max-block-size: 15rem;
  overflow-y: auto;
}

.compose__combo-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2);
  border: none;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.compose__combo-option:hover,
.compose__combo-option:focus-visible {
  background: var(--surface-hover);
}

.compose__combo-option[aria-selected="true"] {
  color: var(--accent);
  font-weight: 600;
}

.compose__combo-empty {
  padding: var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* ---- Custom emoji picker (M29.6, JS only) ----------------------------- *
 * The popup body under the search box: category headings over grids of
 * image-only emoji buttons, scrolling as one list. */
.compose__emoji-list {
  max-block-size: 15rem;
  overflow-y: auto;
}

.compose__emoji-heading {
  padding: var(--space-2) var(--space-1) var(--space-1);
  font-size: var(--text-sm);
  font-weight: 600;
  color: var(--text-muted);
}

.compose__emoji-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(2.4rem, 1fr));
}

.compose__emoji-option {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: var(--space-1);
  border: none;
  border-radius: var(--radius);
  background: transparent;
  cursor: pointer;
}

.compose__emoji-option:hover,
.compose__emoji-option:focus-visible {
  background: var(--surface-hover);
}

.compose__emoji-option img {
  inline-size: 1.6rem;
  block-size: 1.6rem;
  object-fit: contain;
}

/* Loading / error / empty message rows. */
.compose__emoji-note {
  margin: 0;
  padding: var(--space-2);
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* ---- Autocomplete suggestions (M29.7, JS only) ------------------------ */

/* The panel anchors to the field's label, right under the field (JS inserts
   it as the field's next sibling, so `top: 100%` of the label is the field's
   bottom edge — the field is the label's last child). Positioning the labels
   only under `.js` keeps the no-JS layout untouched. */
.js .compose .compose__field {
  position: relative;
}

.compose__suggest {
  position: absolute;
  inset-block-start: 100%;
  inset-inline-start: 0;
  z-index: 20;
  min-inline-size: 15rem;
  max-inline-size: min(24rem, 92vw);
  padding: var(--space-1);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow);
}

.compose__suggest[hidden] {
  display: none;
}

.compose__suggest-option {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: 100%;
  padding: var(--space-2);
  border: none;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  text-align: start;
  cursor: pointer;
}

.compose__suggest-option:hover,
.compose__suggest-option.is-active {
  background: var(--surface-hover);
}

.compose__suggest-avatar {
  inline-size: 1.8rem;
  block-size: 1.8rem;
  border-radius: var(--radius);
  object-fit: cover;
  background: var(--bg);
}

.compose__suggest-emoji {
  inline-size: 1.6rem;
  block-size: 1.6rem;
  object-fit: contain;
}

.compose__suggest-name {
  font-weight: 600;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.compose__suggest-detail {
  color: var(--text-muted);
  font-size: var(--text-sm);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* The inline composer's file input sits between the textarea and controls. */
.compose__media-slot input[type="file"] {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* One attachment slot: a file picker paired with its own alt-text input
   (the no-JS fallback stack). */
.compose__media-slot {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-2);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
}

/* ---- Edit mode (M30.7) ------------------------------------------------ */

/* The locked visibility chip on the edit composer's toolbar: same footprint
   as a tool button, but inert — visibility can't change after posting. */
.compose__static-vis {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2.25rem;
  block-size: 2.25rem;
  color: var(--text-muted);

  .icon {
    inline-size: 1.25rem;
    block-size: 1.25rem;
  }
}

/* The kept-attachments block on the edit composer. Deliberately not a
   `compose__section`: it has no toolbar toggle and must stay visible. */
.compose__edit-media {
  display: grid;
  gap: var(--space-2);
}

/* One kept attachment on the edit composer: thumbnail beside the keep
   checkbox and its alt-text input. */
.edit-media__row {
  display: flex;
  gap: var(--space-3);
  align-items: flex-start;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);

  & + & {
    margin-block-start: var(--space-2);
  }
}

.edit-media__thumb {
  flex: none;
  inline-size: 4rem;
  block-size: 4rem;
  border-radius: var(--radius);
  object-fit: cover;
  background: var(--surface-2);
}

a.edit-media__thumb--file {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
}

.edit-media__fields {
  display: grid;
  flex: 1;
  gap: var(--space-2);
  min-inline-size: 0;
}

/* ---- Dynamic media attachments (M29.4, JS only) ---------------------- */

/* The click/drag/paste target the media manager injects in place of the
   static slots. It's a full-width dashed well; hidden once the attachment
   cap is reached (its `hidden` attribute). */
.compose__dropzone {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-1);
  inline-size: 100%;
  padding: var(--space-4);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text-muted);
  font-size: var(--text-sm);
  text-align: center;
  cursor: pointer;
  transition:
    border-color var(--transition),
    background var(--transition),
    color var(--transition);

  &:hover {
    border-color: var(--accent);
    color: var(--text);
  }

  small {
    color: var(--text-muted);
  }

  .icon {
    inline-size: 1.5rem;
    block-size: 1.5rem;
  }
}

/* While files are dragged over the composer the whole form highlights. */
.compose.is-dragover {
  outline: 2px dashed var(--accent);
  outline-offset: 2px;
}

.compose__media-list {
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
  list-style: none;
}

/* One attached file: thumbnail, alt-text editor and a remove button. */
.compose__attachment {
  display: grid;
  grid-template-columns: auto 1fr auto;
  gap: var(--space-3);
  align-items: start;
  padding: var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface-2);
}

.compose__thumb {
  display: flex;
  align-items: center;
  justify-content: center;
  inline-size: 4rem;
  block-size: 4rem;
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--bg);
  color: var(--text-muted);

  img {
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }
}

.compose__attachment-body {
  min-inline-size: 0;
}

.compose__alt {
  inline-size: 100%;
  resize: vertical;
  min-block-size: 3rem;
  background: var(--bg);
  border-radius: var(--radius);
}

.compose__attachment-remove,
.compose__poll-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  inline-size: 2rem;
  block-size: 2rem;
  padding: 0;
  border: 1px solid transparent;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text-muted);
  cursor: pointer;
  transition:
    background var(--transition),
    color var(--transition);

  &:hover {
    background: var(--danger-soft);
    color: var(--danger);
  }

  &:disabled {
    opacity: 0.4;
    cursor: default;
    background: transparent;
    color: var(--text-muted);
  }
}

/* Dynamic poll builder (M29.5): a list of choice rows the JS grows/shrinks up
   to the instance `max_options`, plus an "Add option" button. The no-JS
   fallback (.compose__poll-static) renders every row at once instead. */
.compose__poll-list {
  list-style: none;
  display: grid;
  gap: var(--space-2);
  margin: 0;
  padding: 0;
}

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

.compose__poll-input {
  flex: 1;
}

.compose__poll-add {
  justify-self: start;
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-1) var(--space-3);
  border: 1px dashed var(--border);
  border-radius: var(--radius-pill);
  background: transparent;
  color: var(--text-muted);
  font-size: var(--text-sm);
  cursor: pointer;
  transition:
    color var(--transition),
    border-color var(--transition);

  &:hover {
    color: var(--text);
    border-color: var(--text-muted);
  }

  &[hidden] {
    display: none;
  }
}

/* A collapsible group (content warning, media, poll). `display: grid` is the
   open state; `.js` collapses it to `display: none` until its toggle adds
   `.is-open` (see the toolbar rules above). */
.compose__section {
  display: grid;
  gap: var(--space-2);
}

.compose__poll-body,
.compose__media-body {
  display: grid;
  gap: var(--space-2);
}

.compose__poll-controls {
  display: flex;
  gap: var(--space-4);
  flex-wrap: wrap;
  align-items: center;
}

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

.compose__context {
  opacity: 0.85;
}

/* ---- Feed & status cards ---------------------------------------------- */

.feed {
  display: grid;
  gap: var(--space-3);
}

.status {
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.status__boost {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0 0 var(--space-2);
  font-size: var(--text-sm);
  color: var(--boost);
  font-weight: 600;
  min-width: 0;

  /* The booster's name link must ellipsize, not push the row past the card. */
  a {
    color: inherit;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .icon {
    flex-shrink: 0;
    inline-size: 1em;
    block-size: 1em;
  }
}

.status__head {
  display: grid;
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: var(--space-3);
}

.status__avatar img {
  display: block;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  border-radius: var(--radius-pill);
  object-fit: cover;
}

.status__author {
  display: flex;
  flex-direction: column;
  min-width: 0;
  line-height: 1.3;

  .status__name {
    font-weight: 700;
    color: var(--text);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;

    &:hover {
      text-decoration: underline;
    }
  }

  .status__acct {
    color: var(--text-muted);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.status__time {
  color: var(--text-muted);
  font-size: var(--text-sm);
  white-space: nowrap;
}

.status__meta {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  align-self: start;
  color: var(--text-muted);
}

.status__handle {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  min-width: 0;
}

/* Account flags (bot, follows-need-approval) beside the handle. */
.status__flag {
  display: inline-flex;
  color: var(--text-muted);

  .icon {
    inline-size: 1em;
    block-size: 1em;
  }
}

/* Visibility glyph in the card corner; the label lives in its title. */
.status__vis {
  display: inline-flex;

  .icon {
    inline-size: 1.1em;
    block-size: 1.1em;
  }
}

/* Post-language chip ("en"), full name in its title. */
.status__lang {
  font-size: var(--text-xs);
  line-height: 1.4;
  text-transform: uppercase;
  letter-spacing: 0.03em;
}

.status__edited {
  font-size: var(--text-xs);
  font-style: italic;
  white-space: nowrap;
  color: inherit;
  text-decoration: none;
}

a.status__edited:hover {
  text-decoration: underline;
}

/* "Replying to @…" context line under the header. */
.status__reply-to {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);

  a {
    color: inherit;

    &:hover {
      color: var(--text);
    }
  }

  .icon {
    inline-size: 1em;
    block-size: 1em;
  }
}

/* Thread detail view: full timestamp plus compact metadata chips. */
.status__detail-meta {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  margin-block-start: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-muted);

  > * + *::before {
    content: "·";
    margin-inline: var(--space-2);
  }
}

/* The detail view's "Edited …" link into the history page. */
.status__detail-edited {
  color: inherit;
  text-decoration: none;
}

.status__detail-edited:hover {
  text-decoration: underline;
}

/* ---- Edit history (M30.7) --------------------------------------------- */

/* One version card on the /history page, newest first. */
.history__version {
  padding: var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);

  & + & {
    margin-block-start: var(--space-3);
  }
}

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

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

.history__spoiler {
  margin: var(--space-3) 0 0;
  font-weight: 600;
}

.history__media {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
}

.history__thumb {
  inline-size: 4rem;
  block-size: 4rem;
  border-radius: var(--radius);
  object-fit: cover;
  background: var(--surface-2);
}

span.history__thumb--file {
  display: flex;
  align-items: center;
  justify-content: center;
  color: var(--text-muted);
}

/* The owner's per-quote revoke control on the quotes engagement list. */
.quote-revoke {
  display: flex;
  justify-content: flex-end;
  margin-block: calc(-1 * var(--space-2)) var(--space-2);
}

.quote-revoke__btn {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--danger);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}

.quote-revoke__btn:hover {
  background: var(--surface-2);
}

/* ---- F3 content-universe extras: title, external link, event facts ---- */

/* The post's real title (Lemmy/PeerTube/Article/Event `name`). */
.status__title {
  margin-block: var(--space-3) 0;
  font-size: var(--text-lg);
  font-weight: 700;
  line-height: 1.3;
  overflow-wrap: anywhere;
}

/* A link post's target URL — a bordered pill under the body. */
.status__external-link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);
  text-decoration: none;
  color: var(--accent);

  &:hover {
    background: var(--surface-2);
  }

  span {
    overflow-wrap: anywhere;
  }

  svg {
    flex-shrink: 0;
  }
}

/* Event facts: date span, place and ical status. */
.status__event {
  display: flex;
  align-items: flex-start;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  font-size: var(--text-sm);

  svg {
    flex-shrink: 0;
    margin-block-start: 2px;
  }
}

.status__event-facts {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  min-inline-size: 0;

  span {
    overflow-wrap: anywhere;
  }
}

.status__event-cancelled {
  color: var(--danger);
  font-weight: 600;
}

.status__content {
  margin-block-start: var(--space-3);
  overflow-wrap: anywhere;

  & :is(p, ul, ol, pre, blockquote) {
    margin-block: var(--space-2);
  }

  & :first-child {
    margin-block-start: 0;
  }

  & :last-child {
    margin-block-end: 0;
  }

  a {
    overflow-wrap: anywhere;
  }

  /* A code block keeps its formatting (`white-space: pre` never wraps), so it
     scrolls inside its own box instead of stretching the card — the classic
     "a post with a wide code block widened the whole feed" bug. */
  pre {
    max-inline-size: 100%;
    overflow-x: auto;
    scrollbar-width: thin;
    padding: var(--space-3);
    border-radius: var(--radius);
    background: var(--surface-2);
  }

  /* Any embedded block that can carry its own width shrinks to the card and
     scrolls rather than overflow it. */
  :is(img, video, table) {
    max-inline-size: 100%;
  }

  table {
    display: block;
    overflow-x: auto;
    scrollbar-width: thin;
  }
}

.status__cw {
  margin-block-start: var(--space-3);

  summary {
    cursor: pointer;
    font-weight: 600;
    padding: var(--space-2) var(--space-3);
    background: var(--surface-2);
    border-radius: var(--radius);
  }

  .status__content {
    margin-block-start: var(--space-2);
  }
}

/* ---- Media ------------------------------------------------------------ */

.status__media {
  display: grid;
  gap: var(--space-1);
  margin-block-start: var(--space-3);
  border-radius: var(--radius);
  overflow: hidden;
  /* minmax(0, …): a bare 1fr is minmax(auto, 1fr), and the images' intrinsic
     widths would blow the first track past half and collapse the second. */
  grid-template-columns: repeat(2, minmax(0, 1fr));

  &[data-count="1"] {
    grid-template-columns: minmax(0, 1fr);
  }

  .media {
    margin: 0;
    aspect-ratio: 16 / 10;
    /* Anchors the ALT/GIF badge overlay. */
    position: relative;

    /* The thumbnail link must span the figure, or the img's percentage
       height resolves against an auto-height anchor and collapses. */
    .media__link {
      block-size: 100%;
    }

    & :is(img, video) {
      inline-size: 100%;
      block-size: 100%;
      object-fit: cover;
      display: block;
    }

    /* Blurhash placeholder painted by JS behind the lazy thumbnail. */
    & img[data-blurhash] {
      background-size: cover;
      background-position: center;
    }
  }

  /* Audio has no visual to tile: the player spans the full row at its
     natural height. */
  .media--audio {
    aspect-ratio: auto;
    grid-column: 1 / -1;
    display: grid;
    align-content: center;
    padding: var(--space-2);
    background: var(--surface-2);

    & audio {
      inline-size: 100%;
      display: block;
    }
  }

  &[data-count="1"] .media {
    aspect-ratio: auto;
    max-block-size: 32rem;

    /* Let height follow the intrinsic width/height ratio (rather than filling
       the parent) so the reserved box matches the loaded image — no CLS. */
    & :is(img, video) {
      block-size: auto;
      max-block-size: 32rem;
      object-fit: contain;
      /* Longhand: the `background` shorthand would reset background-size back
         to `auto` (and repeat), tiling the 32px blurhash placeholder painted
         by the img[data-blurhash] rule instead of letting it cover. */
      background-color: var(--surface-2);
    }
  }

  &[data-count="3"] .media:first-child {
    grid-row: span 2;
  }
}

/* ---- Quote card ------------------------------------------------------- */

.quote-card {
  margin-block-start: var(--space-3);
  padding: var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);

  .quote-card__head {
    display: flex;
    align-items: center;
    gap: var(--space-2);
    color: var(--text);
    font-size: var(--text-sm);
    /* Let the name/handle shrink instead of a long remote handle growing the
       card (and, on mobile, the whole layout viewport). */
    min-width: 0;

    &:hover {
      text-decoration: none;
    }
  }

  .quote-card__avatar {
    flex: 0 0 auto;
    inline-size: 1.25rem;
    block-size: 1.25rem;
    border-radius: var(--radius-pill);
  }

  /* Both sit on one flex row; each shrinks and ellipsizes like the main
     card's byline (.status__name / .status__acct) so neither can overflow. */
  .quote-card__name,
  .quote-card__acct {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .quote-card__name {
    font-weight: 700;
  }

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

  .status__content {
    margin-block-start: var(--space-2);
    font-size: var(--text-sm);
  }
}

/* Non-embedded quote states: pending / deleted / revoked, or a bare link. */
.quote-card--placeholder {
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-style: italic;
}

/* ---- Link preview card (M30.4) ---------------------------------------- */

/* The whole card is one external link; spans inside stay valid phrasing
   content and are laid out as blocks here. */
.preview-card {
  display: flex;
  align-items: stretch;
  margin-block-start: var(--space-3);
  overflow: hidden;
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--bg);
  color: var(--text);

  &:hover {
    text-decoration: none;
    background: var(--surface-hover);
  }

  .preview-card__image {
    flex: none;
    inline-size: 6.5rem;
    min-block-size: 6.5rem;
    object-fit: cover;
    background: var(--surface-2);
  }

  .preview-card__body {
    display: flex;
    flex-direction: column;
    gap: var(--space-1);
    min-inline-size: 0;
    padding: var(--space-3);
    font-size: var(--text-sm);
  }

  .preview-card__provider {
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .preview-card__title {
    font-weight: 700;
  }

  .preview-card__title,
  .preview-card__desc {
    display: -webkit-box;
    overflow: hidden;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    line-clamp: 2;
  }

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

  .preview-card__author {
    color: var(--text-muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

/* Video/photo cards: the thumbnail spans the card on top (Mastodon's large
   layout) instead of the side strip. */
.preview-card--large {
  flex-direction: column;

  .preview-card__image {
    inline-size: 100%;
    aspect-ratio: 16 / 9;
    min-block-size: 0;
  }
}

/* ---- Poll ------------------------------------------------------------- */

.poll {
  display: grid;
  gap: var(--space-2);
  margin-block-start: var(--space-3);
}

.poll__choice {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  cursor: pointer;

  &:hover {
    background: var(--surface-2);
  }
}

.poll__vote {
  justify-self: start;
}

.poll__result {
  position: relative;
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-2) var(--space-3);
  border-radius: var(--radius);
  overflow: hidden;
  isolation: isolate;
  background: var(--surface-2);

  .poll__bar {
    position: absolute;
    inset-block: 0;
    inset-inline-start: 0;
    inline-size: var(--pct, 0%);
    background: var(--accent-soft);
    z-index: -1;
    transition: inline-size var(--transition);
  }

  &.is-own .poll__bar {
    background: color-mix(in srgb, var(--accent) 35%, transparent);
  }

  .poll__title {
    flex: 1;
  }

  .poll__pct {
    color: var(--text-muted);
    font-variant-numeric: tabular-nums;
    font-weight: 600;
  }
}

.poll__footer {
  margin: var(--space-2) 0 0;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

/* ---- Action row (M30.1) ------------------------------------------------ *
 * One coherent row in Mastodon's order — reply / boost / quote / favourite /
 * bookmark / "…" — spread evenly up to a cap so the controls don't drift
 * apart on wide cards. Every counter rides inside its control. */

.status__actions {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-1);
  max-inline-size: 28rem;
  margin-block-start: var(--space-3);
  color: var(--text-muted);
}

.action-form {
  margin: 0;
}

.action {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--space-1);
  min-inline-size: 2.25rem;
  min-block-size: 2.25rem;
  padding: var(--space-2);
  border-radius: var(--radius-pill);
  background: transparent;
  border: none;
  color: var(--text-muted);
  font-size: var(--text-sm);
  font-weight: 600;
  transition:
    background var(--transition),
    color var(--transition);

  &:hover {
    background: var(--surface-2);
    color: var(--text);
    text-decoration: none;
    filter: none;
  }
}

/* Finger-sized targets where there's no pointer precision. */
@media (pointer: coarse) {
  .action {
    min-inline-size: 2.75rem;
    min-block-size: 2.75rem;
  }
}

/* Seven controls share one narrow card, and flex items never shrink below
   their min-inline-size — on a phone-width viewport the pill padding and
   width floors would push the trailing "…" past the card edge. Trade them
   for the row's own space-between spacing; target height stays full-size. */
@media (max-width: 26rem) {
  .action {
    min-inline-size: 0;
    padding-inline: var(--space-1);
  }
}

.action__count {
  font-variant-numeric: tabular-nums;
  min-inline-size: 1ch;
}

.action--quote:hover,
[data-action="reblog"] .is-active {
  color: var(--boost);
}

[data-action="favourite"] .is-active {
  color: var(--favourite);
}

[data-action="bookmark"] .is-active {
  color: var(--accent);
}

.action.is-disabled {
  opacity: 0.4;
}

/* ---- Group-post vote cluster (F4b.3) ----------------------------------- *
 * Lemmy's ▲ n ▼ in place of the favourite star on group posts: two toggle
 * forms around the score. Upvote rides the favourite store and colour;
 * downvote gets its own. */

.status__votes {
  display: inline-flex;
  align-items: center;
}

.status__score {
  font-size: var(--text-sm);
  font-weight: 600;
  font-variant-numeric: tabular-nums;
  min-inline-size: 1ch;
  text-align: center;
}

[data-action="upvote"] .is-active {
  color: var(--favourite);
}

[data-action="downvote"] .is-active {
  color: var(--danger);
}

/* ---- Emoji reaction chips (M30.6) -------------------------------------- *
 * Pleroma-style reaction chips under the action bar: emoji + count pills,
 * the viewer's own reactions highlighted. Each chip is a plain POST form;
 * the row only renders once a post has reactions. The picker button in the
 * action bar is JS-only and reuses the composer picker's popup. */

.status__reactions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-1);
  margin-block-start: var(--space-1);
}

.status__reactions:empty {
  display: none;
}

.reaction-form {
  margin: 0;
  display: inline-flex;
}

.reaction {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  padding: 0.15rem var(--space-2);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  color: var(--text-muted);
  font: inherit;
  font-size: var(--text-sm);
  line-height: 1.4;
}

button.reaction {
  cursor: pointer;
  transition:
    background var(--transition),
    border-color var(--transition),
    color var(--transition);
}

button.reaction:hover,
button.reaction:focus-visible {
  background: var(--surface);
  border-color: var(--accent);
  color: var(--text);
}

.reaction.is-active {
  border-color: var(--accent);
  color: var(--accent);
  background: var(--surface);
}

.reaction__emoji {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-inline-size: 1.2rem;
}

img.reaction__emoji {
  inline-size: 1.2rem;
  block-size: 1.2rem;
  object-fit: contain;
}

.reaction__count {
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

/* The "add a reaction" picker rides in the action bar between favourite and
   bookmark. It is inherently scripted: hidden until the `.js` root reveals
   it. Anchors the shared emoji popup shell. */
.reaction-picker {
  display: none;
}

.js .reaction-picker {
  display: inline-flex;
  position: relative;
}

.action--react[aria-expanded="true"] {
  background: var(--surface-2);
  color: var(--accent);
}

/* ---- Overflow menu (M30.1) --------------------------------------------- *
 * A <details> disclosure styled as a floating menu, so the secondary verbs
 * open without JavaScript too; JS adds outside-click/Escape closing and
 * viewport-aware placement (the shared popup-shell rules above). */

.status__menu {
  position: relative;
}

.status__menu > summary {
  list-style: none;
  cursor: pointer;
}

.status__menu > summary::-webkit-details-marker {
  display: none;
}

.status__menu[open] > summary {
  background: var(--surface-2);
  color: var(--text);
}

/* The trigger sits at the row's end, so the panel right-aligns by default
   (the shared shell left-aligns; JS placement may still flip it). */
.status__menu-pop {
  inset-inline-start: auto;
  inset-inline-end: 0;
  min-inline-size: 16rem;
}

.status__menu-form {
  margin: 0;
}

/* The engagement lists (boosts / quotes / favs) as one horizontal menu row. */
.status__menu-row {
  display: flex;

  .status__menu-item {
    flex: 1 1 0;
    justify-content: center;
    text-align: center;
  }
}

/* A section heading inside the menu (e.g. "Moderate"), set off by a rule. */
.status__menu-label {
  display: flex;
  align-items: center;
  gap: var(--space-1);
  margin: var(--space-1) 0 0;
  padding: var(--space-2) var(--space-3) var(--space-1);
  border-block-start: 1px solid var(--border);
  color: var(--text-muted);
  font-size: var(--text-xs);
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.02em;

  svg {
    inline-size: 1em;
    block-size: 1em;
  }
}

.status__menu-item {
  display: flex;
  align-items: center;
  inline-size: 100%;
  padding: var(--space-2) var(--space-3);
  border: none;
  border-radius: var(--radius);
  background: transparent;
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  text-align: start;
  cursor: pointer;
}

.status__menu-item:hover,
.status__menu-item:focus-visible {
  background: var(--surface-hover);
  text-decoration: none;
}

.status__menu-item.is-danger {
  color: var(--danger);
}

.status__menu-item.is-disabled {
  color: var(--text-muted);
  opacity: 0.6;
  cursor: default;
}

.status__menu-item.is-disabled:hover {
  background: transparent;
}

/* Inherently scripted entries (copy link) stay hidden until JS is running. */
.status__menu-item--js {
  display: none;
}

.js .status__menu-item--js {
  display: flex;
}

/* The owner's "who can quote" row: label + select stacked, apply beside. */
.status__menu-policy {
  display: flex;
  align-items: flex-end;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
}

.status__menu-policy-label {
  display: flex;
  flex: 1;
  flex-direction: column;
  gap: 0.15rem;
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.status__menu-policy-apply {
  padding: var(--space-1) var(--space-3);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font: inherit;
  font-size: var(--text-sm);
  font-weight: 600;
  cursor: pointer;
}

.status__menu-policy-apply:hover {
  background: var(--surface-2);
}

/* A card whose author (or server) the reader just muted or blocked in
   place: dimmed as feedback, but still fully interactable so the verb is
   one click to take back. Hover / focus mostly restores it for reading. */
.status.is-moderated {
  opacity: 0.45;
  transition: opacity 0.15s ease;
}

.status.is-moderated:hover,
.status.is-moderated:focus-within {
  opacity: 0.85;
}

/* ---- Pager ------------------------------------------------------------ */

.pager {
  text-align: center;
  padding-block: var(--space-2);
}

.pager__more {
  display: inline-block;
  padding: var(--space-2) var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  color: var(--text);
  background: var(--surface);
  font-weight: 600;

  &:hover {
    background: var(--surface-2);
    text-decoration: none;
  }
}

/* ---- Profile ---------------------------------------------------------- */

/* The section selector and the Activity filter selector side by side on one
   row. The nav-selects drop their usual underline delimiter here — the
   column gap already separates the row from the feed below. */
.profile-nav {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2);

  &:empty {
    display: none;
  }

  .nav-select {
    margin-block-end: 0;
    padding-block-end: 0;
    border-block-end: none;
  }
}

.profile {
  padding: var(--space-4) var(--space-5);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);

  /* Avatar beside the name/handle block, not stacked above it. */
  .profile__top {
    display: flex;
    align-items: center;
    gap: var(--space-3);
  }

  .profile__id {
    flex: 1;
    min-inline-size: 0;
  }

  .profile__avatar {
    inline-size: 4.5rem;
    block-size: 4.5rem;
    border-radius: var(--radius-pill);
    object-fit: cover;
  }

  /* Handle and join date share one muted line under the name. */
  .profile__meta {
    margin-block: var(--space-1) 0;
    color: var(--text-muted);
  }

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

  /* The overflow menu sits at the far end of the name row. */
  .profile__menu {
    margin-inline-start: auto;
  }

  .profile__note {
    margin-block-start: var(--space-3);
    overflow-wrap: anywhere;
  }

  .profile__name-row {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: var(--space-2);

    .profile__name {
      margin: 0;
      /* A single unbreakable display-name token must wrap within the row
         rather than stretch the profile header past the viewport. */
      min-width: 0;
      overflow-wrap: anywhere;
    }
  }

  .profile__badge {
    display: inline-flex;
    align-items: center;
    padding: 0 var(--space-2);
    border-radius: var(--radius-pill);
    font-size: var(--text-sm);
    font-weight: 700;
    line-height: 1.6;
    background: var(--surface-2);
    color: var(--text-muted);
    border: 1px solid var(--border);
  }

  .profile__badge--locked {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: transparent;
  }

  .profile__badge--warn {
    background: var(--accent-soft);
    color: var(--accent);
    border-color: transparent;
  }

  .profile__badge--danger {
    background: var(--danger-soft);
    color: var(--danger);
    border-color: transparent;
  }

  /* The moderation-state row: viewer sanctions (muted/blocked/…) and admin
     sanctions (suspended/limited/…) as compact badges under the handle. */
  .profile__moderation {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-2);
    margin-block-start: var(--space-2);
  }

  .profile__fields {
    display: grid;
    gap: 1px;
    margin: var(--space-3) 0 0;
    border: 1px solid var(--border);
    border-radius: var(--radius);
    overflow: hidden;
    background: var(--border);
  }

  .profile__field {
    display: grid;
    grid-template-columns: minmax(6rem, 0.4fr) 1fr;
    gap: 1px;
  }

  .profile__field-name,
  .profile__field-value {
    margin: 0;
    padding: var(--space-2) var(--space-3);
    background: var(--surface);
    overflow-wrap: anywhere;
  }

  .profile__field-name {
    color: var(--text-muted);
    font-weight: 600;
  }

  /* A rel="me"-verified profile link (M33.4). */
  .profile__field.is-verified {
    .profile__field-value {
      background: color-mix(in srgb, var(--boost) 10%, var(--surface));
    }
  }

  .profile__field-verified,
  .settings-field__verified {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    color: var(--boost);
    font-size: var(--text-sm);
    font-weight: 600;
    white-space: nowrap;

    svg {
      width: 1em;
      height: 1em;
    }
  }

  /* Stats and the follow button share one shallow row; the follow-settings
     disclosure wraps to a full-width line of its own. */
  .profile__footer {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2) var(--space-4);
    margin-block-start: var(--space-3);
  }

  .profile__stats {
    display: flex;
    flex-wrap: wrap;
    gap: var(--space-1) var(--space-2);
    margin: 0;
  }

  .profile__stat {
    display: flex;
    align-items: baseline;
    gap: var(--space-1);
    padding: var(--space-1) var(--space-2);
    border-radius: var(--radius);
    color: inherit;

    .profile__stat-value {
      font-weight: 700;
    }

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

  a.profile__stat--link:hover {
    background: var(--surface-2);
    text-decoration: none;

    .profile__stat-label {
      color: var(--accent);
    }
  }

  /* The action cluster (Manage group, Follow) sits opposite the stats. */
  .profile__actions {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: var(--space-2);
    margin-inline-start: auto;
  }

  /* Secondary action, distinct from the accent Follow button beside it. */
  .profile__manage {
    display: inline-flex;
    align-items: center;
    gap: var(--space-1);
    padding: var(--space-2) var(--space-4);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    background: var(--surface);
    color: var(--text);
    font-weight: 600;

    &:hover {
      background: var(--surface-2);
      text-decoration: none;
    }
  }

  .follow {
    button {
      width: auto;
    }
  }

  .follow-settings {
    flex-basis: 100%;

    summary {
      color: var(--text-muted);
      cursor: pointer;
    }

    .follow-settings__form {
      display: grid;
      gap: var(--space-2);
      margin-block-start: var(--space-2);

      button {
        width: auto;
        justify-self: start;
      }
    }

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

    .follow-settings__languages {
      display: grid;
      gap: var(--space-1);
    }

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

/* ---- Thread ----------------------------------------------------------- */

.thread__ancestors {
  display: grid;
  gap: var(--space-3);
}

.thread__focus .status {
  border-color: color-mix(in srgb, var(--accent) 45%, var(--border));
}

/* ---- Notifications ---------------------------------------------------- */

.notifications {
  display: grid;
  gap: var(--space-3);
}

.notification {
  display: grid;
  gap: var(--space-2);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.notification__label {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  margin: 0;
  color: var(--text-muted);
  min-width: 0;

  .notification__icon {
    display: inline-flex;
    flex-shrink: 0;
    color: var(--accent);
  }

  /* Long actor names ellipsize instead of overflowing the label row. */
  .notification__actor {
    color: var(--text);
    font-weight: 700;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

.notification .status {
  border: none;
  padding: 0;
  background: transparent;
  box-shadow: none;
}

/* ---- Search & results ------------------------------------------------- */

.search {
  display: flex;
  gap: var(--space-2);

  input {
    flex: 1;
    border-radius: var(--radius-pill);
  }
}

.results {
  display: grid;
  gap: var(--space-3);

  h2 {
    margin: 0;
    font-size: var(--text-base);
    color: var(--text-muted);
  }
}

.account-list {
  display: grid;
  gap: var(--space-2);
}

/* Relationships manager (M33.4): a checkbox next to each account card. */
.relationships-list {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-3) 0;
  padding: 0;
  list-style: none;
}

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

  .account-card {
    flex: 1;
    min-width: 0;
  }
}

.relationships-list__check {
  display: flex;
  align-items: center;

  input {
    inline-size: 1.1rem;
    block-size: 1.1rem;
  }
}

/* A collection member's trailing controls: a pending badge plus Remove. */
.relationships-list__aside {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex-shrink: 0;
}

/* A blocked domain shown as plain text beside its unblock checkbox. */
.relationships-list__domain {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  font-weight: 600;
}

/* A remote collection member whose actor we haven't resolved yet. */
.relationships-list__unresolved {
  flex: 1;
  min-width: 0;
  overflow-wrap: anywhere;
  color: var(--text-muted);
  font-size: var(--text-sm);
}

/* Featured-hashtag suggestions (M33.4): a row of one-click feature buttons. */
.featured-tags__suggestions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin: var(--space-2) 0;

  button {
    background: var(--surface-2);
    color: inherit;
    border: 1px solid var(--border);
  }
}

.account-card {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  color: inherit;
  box-shadow: var(--shadow-sm);

  &:hover {
    background: var(--surface-2);
    text-decoration: none;
  }

  .account-card__avatar {
    inline-size: 2.75rem;
    block-size: 2.75rem;
    border-radius: var(--radius-pill);
    object-fit: cover;
  }

  .account-card__body {
    display: grid;
    min-width: 0;
    line-height: 1.3;
  }

  .account-card__name {
    font-weight: 700;
    /* Match __acct: a long display name must ellipsize inside the card body
       (which is min-width:0), not widen the list. */
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }

  .account-card__acct {
    color: var(--text-muted);
    font-size: var(--text-sm);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
  }
}

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

  .hashtag {
    display: inline-block;
    padding: var(--space-2) var(--space-4);
    background: var(--surface);
    border: 1px solid var(--border);
    border-radius: var(--radius-pill);
    font-weight: 600;

    &:hover {
      background: var(--surface-2);
      text-decoration: none;
    }
  }
}

.tag-title {
  margin-block: 0 var(--space-2);
}

/* ---- JS-enhanced niceties --------------------------------------------- */

/* A row injected by infinite scroll fades in. */
.feed > .status {
  animation: rise var(--transition);
}

@keyframes rise {
  from {
    opacity: 0;
    transform: translateY(var(--space-2));
  }
}

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: 0.001ms !important;
    transition-duration: 0.001ms !important;
  }
}

/* ---- Responsive: tablet & phone --------------------------------------- */

@media (max-width: 56rem) {
  :root {
    --sidebar: 4.5rem;
  }

  .brand__name,
  .nav-link__label,
  .compose-btn span,
  .account {
    display: none;
  }

  .brand,
  .nav-link,
  .compose-btn {
    justify-content: center;
  }
}

/* ---- Settings --------------------------------------------------------- */

.settings__title {
  margin-block: var(--space-2) var(--space-4);
}

.settings__saved {
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-4);
  background: var(--accent-soft);
  border-radius: var(--radius);
  color: var(--accent);
  font-weight: 600;
}

.settings__error {
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-4);
  background: var(--danger-soft);
  border: 1px solid color-mix(in srgb, var(--danger) 35%, var(--border));
  border-radius: var(--radius);
  color: var(--danger);
  font-weight: 600;
}

/* ---- Group "New post" button (F4b) ------------------------------------- *
 * Links to the shared composer scoped to the group (/compose?group=id),
 * styled as the same pill button the profile header uses. */

.group-post-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  margin-block-end: var(--space-4);
  padding: var(--space-2) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius-pill);
  background: var(--surface);
  color: var(--text);
  font-weight: 600;

  &:hover {
    background: var(--surface-2);
    text-decoration: none;
  }
}

.settings-form {
  display: grid;
  gap: var(--space-5);
}

.settings-form__group {
  display: grid;
  gap: var(--space-4);
  padding: var(--space-5);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  background: var(--surface);

  legend {
    padding-inline: var(--space-2);
    font-weight: 700;
  }

  &:disabled {
    opacity: 0.65;
  }
}

.settings-form__group--danger {
  border-color: color-mix(in srgb, var(--danger) 38%, var(--border));
  background: color-mix(in srgb, var(--danger) 7%, var(--surface));
}

.settings-field {
  display: grid;
  gap: var(--space-2);
}

.settings-field__label {
  font-weight: 600;
}

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

.settings-field__pair {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 2fr);
  gap: var(--space-3);
}

.settings-toggle {
  display: flex;
  align-items: start;
  gap: var(--space-3);

  input[type="checkbox"],
  input[type="radio"] {
    margin-block-start: 0.2em;
  }

  &.is-disabled {
    opacity: 0.65;
  }
}

.settings-toggle__text {
  display: grid;
  gap: var(--space-1);
}

.settings-toggle__label {
  font-weight: 600;
}

.settings-form__actions {
  display: flex;
  justify-content: flex-end;
}

.settings-button--danger {
  background: var(--danger);
  border-color: var(--danger);
  color: white;

  &:hover {
    background: color-mix(in srgb, var(--danger) 88%, black);
    border-color: color-mix(in srgb, var(--danger) 88%, black);
  }
}

.settings-button--plain {
  background: transparent;
  border-color: transparent;
  color: var(--text-muted);

  &:hover {
    background: var(--surface-2);
    text-decoration: none;
  }
}

/* User lists (web UI): the index of a viewer's lists and each list's header. */
.list-head {
  margin-block-end: var(--space-3);

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

.list-index {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-3) 0;
  padding: 0;
  list-style: none;
}

.list-index__item {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius-lg);
  box-shadow: var(--shadow-sm);
}

.list-index__link {
  display: flex;
  align-items: center;
  gap: var(--space-2);
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: inherit;

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

.list-index__manage {
  flex: none;
  font-size: 0.9rem;
}

/* The "add to lists" panel reached from a profile: a column of list toggles. */
.list-membership {
  display: grid;
  gap: var(--space-2);
  margin: var(--space-3) 0;
}

/* The "Featured" account collection (endorsements) shown on a profile. */
/* A silenced account's stripped anonymous profile: handle + notice only. */
.profile-silenced {
  display: grid;
  gap: var(--space-3);
  justify-items: start;
  padding: var(--space-5) var(--space-4);
}

.profile-silenced__handle {
  font-size: 1.25rem;
  overflow-wrap: anywhere;
}

.profile-silenced__notice {
  color: var(--text-muted);
  max-inline-size: 40rem;
}

.profile-silenced__link {
  font-weight: 600;
}

/* The notice above a silenced account's full profile for logged-in viewers. */
.profile-silenced-banner {
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-4);
  background: color-mix(in srgb, var(--danger) 10%, var(--surface));
  border: 1px solid color-mix(in srgb, var(--danger) 32%, var(--border));
  border-radius: var(--radius);
  color: var(--danger);
  font-weight: 600;
}

.profile-featured {
  margin-block: var(--space-4);
}

.profile-featured__title {
  font-size: 1rem;
  margin-block-end: var(--space-2);
}

.profile-featured__grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(100%, 16rem), 1fr));
  gap: var(--space-2);
}

/* The profile Media tab: attachments as a tightly packed square-tile wall.
   A tile is a link to the containing post; JS upgrades image/gifv tiles into
   the lightbox. */
.media-wall {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(min(9rem, 28vw), 1fr));
  gap: var(--space-1);
}

.media-wall__tile {
  position: relative;
  display: block;
  aspect-ratio: 1;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--surface-2);

  img {
    display: block;
    inline-size: 100%;
    block-size: 100%;
    object-fit: cover;
  }

  /* A sensitive (or blur-filtered) post's thumbnails stay but blur out;
     scaling hides the blur's translucent edges. */
  &.is-sensitive img {
    filter: blur(24px);
    transform: scale(1.15);
  }
}

/* An attachment with nothing to thumbnail (audio, unknown types). */
.media-wall__placeholder {
  display: grid;
  place-items: center;
  block-size: 100%;
  color: var(--text-muted);

  .icon {
    inline-size: 2rem;
    block-size: 2rem;
  }
}

/* The security page's account-access tables (sessions/apps/history) can be
   wider than the settings column; let them scroll rather than overflow it. */
.settings-scroll {
  overflow-x: auto;
  scrollbar-width: thin;
  -webkit-overflow-scrolling: touch;
}

/* A per-row revoke control sitting inside a table cell — no block margins, and
   its submit reads as a text link rather than a full button. */
.settings-inline-form {
  margin: 0;
}

.link-button {
  padding: 0;
  border: 0;
  background: none;
  color: var(--danger);
  font: inherit;
  cursor: pointer;

  &:hover {
    text-decoration: underline;
  }
}

/* The report form (M30.8) rides the settings-form classes; these cover its
   post picker rows and the cancel/submit footer. */
.report-form__status-time {
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.report-form__status-excerpt {
  overflow-wrap: anywhere;
}

.report-form__actions {
  align-items: center;
  gap: var(--space-4);
}

/* The post-submission "take action" shortcuts. */
.report-done__actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-3);
  margin-block: var(--space-4);
}

/* The posting-languages checklist (M29.2): a dense multi-column grid. */
.settings-langs {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(13rem, 1fr));
  gap: var(--space-2) var(--space-4);
}

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

/* The JS-only select-all / deselect-all pair above the checklist. */
.settings-langs__controls {
  display: flex;
  gap: var(--space-2);

  button {
    padding: var(--space-1) var(--space-3);
    font-size: var(--text-sm);
    background: var(--surface);
    color: var(--text);
    border-color: var(--border);
  }
}

/* Data export download list (M23.5). */
.export-list {
  list-style: none;
  margin: var(--space-4) 0 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.export-list__item {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-4);
  padding: var(--space-3) var(--space-4);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  background: var(--surface);
}

.export-list__meta {
  display: grid;
  gap: var(--space-1);
}

.export-list__label {
  font-weight: 600;
}

.export-list__download {
  display: inline-flex;
  align-items: center;
  gap: var(--space-1);
  flex: none;
  padding: var(--space-2) var(--space-4);
  border-radius: var(--radius-pill);
  background: var(--accent);
  color: var(--accent-contrast);
  font-weight: 600;
  text-decoration: none;
  transition: filter var(--transition);

  &:hover {
    filter: brightness(1.06);
  }

  & svg {
    width: 1em;
    height: 1em;
  }
}

/* Data import controls (M23.6): section headings, the recent-imports action
   buttons, the review summary and merge/overwrite choices. */
.settings__subtitle {
  margin: 0 0 var(--space-2);
  font-size: 1rem;
}

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

.settings-summary {
  display: grid;
  grid-template-columns: minmax(0, max-content) 1fr;
  gap: var(--space-1) var(--space-4);
  margin: var(--space-4) 0;

  & dt {
    font-weight: 600;
    color: var(--text-muted);
    overflow-wrap: anywhere;
  }

  & dd {
    margin: 0;
  }
}

.settings-field__choice {
  display: flex;
  align-items: baseline;
  gap: var(--space-2);
  padding: var(--space-1) 0;
}

/* The TOTP enrolment QR (its SVG carries a white quiet zone of its own, so it
   stays scannable in dark mode) and the one-time recovery-code sheet. */
.two-factor__qr svg {
  display: block;
  border-radius: var(--radius);
}

.two-factor__codes {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(9rem, 1fr));
  gap: var(--space-2);
  margin: var(--space-3) 0;
  padding: 0;
  list-style: none;
}

/* Security-key list and the JS-revealed add/authenticate controls (M23.4). */
.two-factor__keys {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.two-factor__key {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
}

.two-factor__key-name {
  min-inline-size: 0;
  overflow-wrap: anywhere;
  font-weight: 600;
}

.settings-form--inline {
  margin: 0;
}

.webauthn-register {
  display: grid;
  gap: var(--space-2);
  margin-top: var(--space-2);
}

.auth-webauthn {
  display: grid;
  gap: var(--space-1);
  margin-bottom: var(--space-3);
}

@media (max-width: 40rem) {
  .app-shell {
    grid-template-columns: minmax(0, 1fr);
    gap: 0;
    padding-inline: 0;
  }

  .settings-field__pair {
    grid-template-columns: minmax(0, 1fr);
  }

  .sidebar {
    display: none;
  }

  .app-main {
    padding: var(--space-3) var(--space-3) calc(var(--space-8) + 3.5rem);
  }

  .status,
  .notification,
  .account-card,
  .compose,
  .profile,
  .auth-card {
    border-radius: var(--radius);
  }

  .tabbar {
    position: fixed;
    inset-inline: 0;
    inset-block-end: 0;
    display: flex;
    justify-content: space-around;
    align-items: center;
    padding: var(--space-2) var(--space-2)
      calc(var(--space-2) + env(safe-area-inset-bottom, 0px));
    /* No backdrop-filter: it would establish a containing block for the
       drawer's position:fixed scrim, trapping the overlay inside this thin
       bar. A near-opaque background stands in for the blur. */
    background: color-mix(in srgb, var(--surface) 96%, transparent);
    border-block-start: 1px solid var(--border);
    z-index: 10;
  }

  .tabbar__link {
    display: grid;
    place-items: center;
    padding: var(--space-2);
    color: var(--text-muted);
    border-radius: var(--radius-pill);

    .icon {
      inline-size: 1.6rem;
      block-size: 1.6rem;
    }

    &:hover {
      color: var(--text);
    }

    &.tabbar__link--accent {
      color: var(--accent-contrast);
      background: var(--accent);
      padding-inline: var(--space-4);
    }
  }
}

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

/* The drawer only ever renders inside the tab bar, which is mobile-only, so
   these rules are inert on desktop. `<details>` hides the scrim until open. */
.drawer__toggle {
  list-style: none;
  cursor: pointer;

  &::-webkit-details-marker {
    display: none;
  }
}

.drawer__scrim {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  background: color-mix(in srgb, var(--text) 45%, transparent);
  animation: drawer-fade var(--transition) ease;
}

.drawer__panel {
  display: flex;
  flex-direction: column;
  gap: var(--space-1);
  inline-size: min(20rem, 82vw);
  block-size: 100%;
  max-block-size: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  padding: var(--space-5) var(--space-4)
    calc(var(--space-5) + env(safe-area-inset-bottom, 0px));
  background: var(--surface);
  border-inline-end: 1px solid var(--border);
  box-shadow: 0 0 40px rgb(0 0 0 / 0.3);
  animation: drawer-slide var(--transition) ease;

  /* The ≤56rem breakpoint collapses the desktop sidebar to an icon-only rail —
     hiding labels and the account block and centering the icons. The drawer is
     a full menu, so it opts back into labels, left alignment and the account
     row (these two-class selectors outrank the breakpoint's one-class rules). */
  .brand__name,
  .nav-link__label,
  .compose-btn span {
    display: inline;
  }

  .brand,
  .nav-link,
  .compose-btn {
    justify-content: flex-start;
  }

  /* Unlike the desktop sidebar, the account row sits directly under the nav —
     no auto-spacer pushing it to the bottom, so no big empty gap. */
  .account {
    display: flex;
    margin-block-start: var(--space-2);
  }
}

@keyframes drawer-fade {
  from {
    opacity: 0;
  }
}

@keyframes drawer-slide {
  from {
    transform: translateX(-100%);
  }
}

/* ---- Admin dashboard -------------------------------------------------- */

.admin__title {
  margin-block: var(--space-2) var(--space-4);
}

.admin__lead {
  color: var(--text-muted);
  margin-block-end: var(--space-4);
}

.admin__stats {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
}

.admin-update {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-3);
  margin-bottom: var(--space-4);
  padding: var(--space-3) var(--space-4);
  background: var(--surface);
  border: 1px solid var(--accent);
  border-radius: var(--radius);
}

.admin-update--urgent {
  border-color: var(--danger, #b3261e);
}

.admin-update__label {
  font-weight: 600;
}

.admin-update__link {
  font-size: var(--text-sm);
}

.admin-stat {
  display: grid;
  gap: var(--space-1);
  padding: var(--space-4);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: inherit;
}

.admin-stat__value {
  font-size: var(--text-xl);
  font-weight: 700;
  font-variant-numeric: tabular-nums;
}

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

.admin-stat--link:hover {
  text-decoration: none;
  border-color: var(--accent);
}

.admin-stat__delta {
  font-size: var(--text-xs);
  color: var(--text-muted);
  font-variant-numeric: tabular-nums;
}

.admin-dashboard__heading {
  margin-block-start: var(--space-5);
}

.admin-dashboard__dimensions {
  display: grid;
  gap: var(--space-4);
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  margin-block-start: var(--space-4);
}

.admin-dimension__value {
  text-align: end;
  font-variant-numeric: tabular-nums;
}

.role-swatch {
  display: inline-block;
  inline-size: 0.75em;
  block-size: 0.75em;
  border-radius: 50%;
}

.admin-tos__text,
.static-page__text {
  white-space: pre-wrap;
  overflow-x: auto;
}

.static-page__text {
  white-space: normal;
}

.admin-flash {
  padding: var(--space-3) var(--space-4);
  margin-block-end: var(--space-4);
  background: var(--accent-soft);
  border-radius: var(--radius);
  color: var(--accent);
  font-weight: 600;

  &.is-error {
    background: var(--danger-soft);
    color: var(--danger);
  }
}

.admin-filter {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: var(--space-3);
  margin-block: var(--space-4);

  & label {
    display: grid;
    gap: var(--space-1);
    font-size: 0.85em;
    color: var(--text-muted);
  }
}

/* The generic data table — despite the name it's shared by the user-facing
   settings pages (sessions, invites, aliases, featured hashtags) too. */
.admin-table {
  width: 100%;
  border-collapse: collapse;

  & th,
  & td {
    padding: var(--space-2) var(--space-3);
    text-align: left;
    border-block-end: 1px solid var(--border);
    vertical-align: top;
    /* Long unbreakable cell values (user-agents, domains, emails, URLs) break
       to fit rather than force the table — and the page — past the viewport. */
    overflow-wrap: anywhere;
  }

  & th {
    color: var(--text-muted);
    font-size: 0.8em;
    text-transform: uppercase;
    letter-spacing: 0.04em;
  }
}

.admin-table__sub {
  display: block;
  color: var(--text-muted);
  font-size: 0.85em;
}

.admin-pager {
  margin-block: var(--space-4);
}

.admin-badge {
  display: inline-block;
  padding: 0 var(--space-2);
  border-radius: var(--radius-pill);
  background: var(--surface-2);
  border: 1px solid var(--border);
  font-size: 0.75em;
  font-weight: 700;

  &.is-suspended,
  &.is-disabled {
    color: var(--danger);
    border-color: color-mix(in srgb, var(--danger) 40%, var(--border));
  }

  &.is-silenced,
  &.is-pending {
    color: var(--accent);
    border-color: color-mix(in srgb, var(--accent) 40%, var(--border));
  }
}

.admin-back {
  margin-block-end: var(--space-3);
}

.admin-detail__grid {
  display: grid;
  grid-template-columns: minmax(0, max-content) 1fr;
  gap: var(--space-2) var(--space-4);
  margin-block: var(--space-3);

  & dt {
    color: var(--text-muted);
    overflow-wrap: anywhere;
  }

  & dd {
    margin: 0;
    overflow-wrap: anywhere;
  }
}

.admin-actions {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2);
  margin-block: var(--space-4);
}

.admin-form,
.admin-notes,
.admin-strikes,
.admin-list {
  margin-block: var(--space-5);
}

.admin-form textarea,
.admin-notes textarea {
  width: 100%;
  margin-block: var(--space-2);
}

/* Stack each field's label text above its control so the label and value don't
   run together (the "All day" / "Status ids" mash). Inline checkboxes keep
   their own `.admin-check` layout. */
.admin-form label:not(.admin-check) {
  display: grid;
  gap: var(--space-1);
  margin-block: var(--space-3);
  font-size: var(--text-sm);
  color: var(--text-muted);
}

.admin-form__grid label:not(.admin-check) {
  /* The grid's own gap spaces these; drop the per-label block margin. */
  margin-block: 0;
}

.admin-form label:not(.admin-check) input:not([type="checkbox"], [type="radio"]),
.admin-form label:not(.admin-check) select {
  inline-size: 100%;
}

.admin-form__group {
  display: grid;
  gap: var(--space-2);
  padding: 0;
  margin-block: var(--space-3);
  border: 0;
}

.admin-form__grid {
  display: grid;
  gap: var(--space-3);
  grid-template-columns: repeat(auto-fit, minmax(12rem, 1fr));
}

.admin-form__checks {
  display: flex;
  flex-wrap: wrap;
  gap: var(--space-2) var(--space-4);
}

.admin-check {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2);
  inline-size: fit-content;
  color: var(--text-muted);
}

.admin-record {
  display: grid;
  gap: var(--space-3);
  padding: var(--space-4);
  margin-block: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.admin-record__head {
  display: flex;
  justify-content: space-between;
  align-items: center;
  gap: var(--space-3);
}

.admin-emoji__identity {
  display: flex;
  align-items: center;
  gap: var(--space-3);
  min-width: 0;
}

.admin-emoji__preview {
  inline-size: 3rem;
  block-size: 3rem;
  object-fit: contain;
  image-rendering: auto;
}

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

.admin-upload__preview {
  max-inline-size: 12rem;
  max-block-size: 6rem;
  object-fit: contain;
}

.auth-card__mascot {
  display: block;
  margin-inline: auto;
  max-block-size: 8rem;
  object-fit: contain;
}

.admin-danger {
  background: var(--danger);
  color: #ffffff;
}

.admin-notes__list {
  list-style: none;
  padding: 0;
  display: grid;
  gap: var(--space-2);
}

.admin-note {
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

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

.admin-note__meta {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-block-start: var(--space-2);
  color: var(--text-muted);
  font-size: 0.85em;
}

.admin-strikes__list {
  display: grid;
  gap: var(--space-2);
  padding-inline-start: var(--space-4);
}

.admin-report__comment p,
.admin-report__cw {
  white-space: pre-wrap;
  overflow-wrap: anywhere;
}

.admin-report__cw {
  font-weight: 600;
  color: var(--text-muted);
}

.admin-report__statuses {
  display: grid;
  gap: var(--space-3);
}

.admin-report__status {
  padding: var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
}

.admin-report__statuslink {
  margin-block-end: 0;
  font-size: 0.85em;
}

.admin-report__hint {
  color: var(--text-muted);
  font-size: 0.9em;
}

/* ---- M28: media preview, sensitive gating, follow lists -------------- */

/* Image thumbnails link to the full-size file; keep them block so the link
   wraps the picture cleanly. */
.media__link {
  display: block;
}

/* Sensitive media is collapsed behind a toggle until the viewer opens it. */
.status__media-sensitive {
  margin-block: var(--space-2);
}

.status__media-sensitive > summary {
  cursor: pointer;
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: 1px solid var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-weight: 600;
  list-style: none;
}

.status__media-sensitive > summary::-webkit-details-marker {
  display: none;
}

.status__media-sensitive[open] > summary {
  margin-block-end: var(--space-2);
}

/* ---- M30.9: filters ---------------------------------------------------- */

/* A warn-filter match collapses the post body behind this bar; the whole
   summary is the no-JS "show anyway" toggle. */
.status__filtered {
  margin-block-start: var(--space-3);
}

.status__filtered > summary {
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-2);
  padding: var(--space-2) var(--space-3);
  background: var(--surface);
  border: 1px dashed var(--border);
  border-radius: var(--radius);
  color: var(--text-muted);
  font-weight: 600;
  list-style: none;
}

.status__filtered > summary::-webkit-details-marker {
  display: none;
}

.status__filtered-show {
  font-weight: 400;
  font-size: 0.85em;
  white-space: nowrap;
}

.status__filtered[open] > summary {
  margin-block-end: var(--space-2);
}

.status__filtered[open] > summary .status__filtered-show {
  display: none;
}

/* ---- M30.2: media badges + lightbox ----------------------------------- */

/* ALT / GIF chips overlaid on a media tile's bottom-left corner. */
.media__badges {
  position: absolute;
  inset-block-end: var(--space-2);
  inset-inline-start: var(--space-2);
  display: flex;
  gap: var(--space-1);
  pointer-events: none;
}

.media__badge {
  font-size: 0.6875rem;
  font-weight: 700;
  letter-spacing: 0.04em;
  padding: 0.1rem 0.4rem;
  border-radius: var(--radius);
  background: rgb(0 0 0 / 0.65);
  color: #fff;
  pointer-events: auto;
  cursor: default;
}

/* An autoplaying gifv tile opens the lightbox under JS. */
.js .media__gifv {
  cursor: pointer;
}

/* Overlaid while a remote video warms up (first play triggers the download;
   script swaps the source in when the cached copy lands). */
.media__preparing {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-start: var(--space-2);
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.15rem 0.5rem;
  border-radius: var(--radius);
  background: rgb(0 0 0 / 0.65);
  color: #fff;
  pointer-events: none;
}

/* HLS video (PeerTube): app.js appends the quality <select> over the player's
   top-right corner once hls.js reports more than one rendition. */
.media--video {
  position: relative;
}
.video-quality {
  position: absolute;
  inset-block-start: var(--space-2);
  inset-inline-end: var(--space-2);
  max-inline-size: 7rem;
  font-size: 0.8125rem;
  padding: 0.1rem 0.35rem;
  border: 0;
  border-radius: var(--radius);
  background: rgb(0 0 0 / 0.65);
  color: #fff;
  opacity: 0.85;
  cursor: pointer;
}

/* The JS lightbox: a full-viewport dialog over a near-black backdrop. It is
   built and shown by script only, so no no-JS state needs styling. */
.lightbox {
  position: fixed;
  inset: 0;
  z-index: 40;
  display: flex;
  flex-direction: column;
  background: rgb(0 0 0 / 0.92);

  &[hidden] {
    display: none;
  }
}

.lightbox__stage {
  flex: 1;
  min-block-size: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  /* All panning/pinching is handled in script. */
  touch-action: none;
}

/* The displayed image or gifv clip. A plain click closes the dialog, so no
   zoom cursor — zooming is wheel/pinch; grab signals panning once zoomed. */
.lightbox__media {
  max-inline-size: 100%;
  max-block-size: 100%;
  user-select: none;
  -webkit-user-drag: none;

  &.is-zoomed {
    cursor: grab;
  }

  &[hidden] {
    display: none;
  }
}

/* gifv clips are small by nature: letterbox them up to the stage instead of
   floating at intrinsic size. */
.lightbox__media--video {
  inline-size: 100%;
  block-size: 100%;
  object-fit: contain;
}

.lightbox__button {
  position: absolute;
  inline-size: 2.75rem;
  block-size: 2.75rem;
  display: grid;
  place-items: center;
  border: none;
  border-radius: 50%;
  background: rgb(0 0 0 / 0.5);
  color: #fff;
  font-size: 1.5rem;
  line-height: 1;
  cursor: pointer;

  &:hover {
    background: rgb(255 255 255 / 0.2);
  }

  &:disabled {
    opacity: 0.3;
    cursor: default;
  }
}

.lightbox__close {
  inset-block-start: var(--space-3);
  inset-inline-end: var(--space-3);
}

.lightbox__nav--prev {
  inset-inline-start: var(--space-3);
  inset-block-start: 50%;
  translate: 0 -50%;
}

.lightbox__nav--next {
  inset-inline-end: var(--space-3);
  inset-block-start: 50%;
  translate: 0 -50%;
}

.lightbox__meta {
  padding: var(--space-3) var(--space-4);
  text-align: center;
  color: #ccc;

  .lightbox__counter {
    display: block;
    font-size: 0.8125rem;
    color: #999;
  }

  .lightbox__alt {
    margin: var(--space-1) 0 0;
    max-inline-size: 60ch;
    margin-inline: auto;
    font-size: 0.9375rem;

    &:empty {
      display: none;
    }
  }

  /* Media-wall tiles carry the containing post's URL through the lightbox. */
  .lightbox__post {
    display: inline-block;
    margin-block-start: var(--space-1);
    color: #fff;
    font-weight: 600;
    text-decoration: underline;

    &[hidden] {
      display: none;
    }
  }
}

/* The reacted emoji shown inline in an emoji-reaction notification. */
.notification__emoji {
  font-size: 1.1em;
}

/* Back-to-profile link above the follow lists. */
.profile__back {
  margin-block-end: var(--space-3);
}

.profile__back a {
  color: var(--text-muted);
  font-weight: 600;
  /* "← <display name>" must ellipsize rather than stretch the page. */
  display: block;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

} /* @layer app */
