/*
 * Plural Profiles — application styles
 */

/* Ensure the HTML `hidden` attribute always wins, even when an element has
   an explicit display value set by other rules. */
[hidden] { display: none !important; }

:root {
  --page-bg: #0b221b;
  --pane-bg: #133b2f;
  --header-bg: #0e2e24;
  --header-title-text: #5ea389;
  --header-text: #5ea389;
  --header-link: #3ab580;
  --pane-border: #02120e;
  --pane-title-text: #5ea389;
  --pane-text: #5ea389;
  --pane-link: #3ab580;
  --spoiler: #3A3A3A;
  --primary-button-bg: #12684e;
  --primary-button-text: #4ec59a;
  --secondary-button-bg: #155642;
  --secondary-button-text: #4dbb8f;
  --primary-button-border: var(--primary-button-text);
  --secondary-button-border: var(--secondary-button-text);
  --danger-button-bg: #9d1d46;
  --danger-button-text: #d8b5c0;
  --danger-button-border: var(--danger-button-text);
  --input-label: #599981;
  --input-bg: #263a2e;
  --input-border: #3c6f5f;
  --input-text: var(--pane-text);
  --notice-bg: var(--pane-bg);
  --notice-border: var(--pane-text);
  --notice-text: var(--pane-text);
  --alert-bg: #11694a;
  --alert-border: #58cc9d;
  --alert-text: var(--alert-border);
  --warning-bg: #a81d49;
  --warning-border: #e6c4cf;
  --warning-text: var(--warning-border);
  --tree-guide: color-mix(in srgb, var(--pane-text) 30%, transparent);
  --avatar-placeholder-border: color-mix(in srgb, var(--pane-text) 50%, transparent);

  /* Chat's own palette. Each one is a var() pointing at the profile colour it
     inherits from, generated from Theme::THEMEABLE_PROPERTIES' fallbacks — so
     an un-themed chat page can never show a chat colour that differs from the
     profile colour the designer is told it follows. (These used to be literal
     hexes duplicated by hand, which is exactly how they drift.)

     var() here is safe despite the caveat in Theme#to_css_properties about
     custom properties resolving at :root. That only bites when body overrides
     a referenced property but not the one referencing it. A themed page emits
     every chat colour concretely alongside its profile counterpart, shadowing
     these entirely; an un-themed page overrides nothing, so each var() resolves
     against :root's own profile values, which is exactly what's wanted. */
  --chat-header-bg: var(--header-bg);
  --chat-header-title-text: var(--header-title-text);
  --chat-header-link: var(--header-link);
  --chat-rail-bg: var(--pane-border);
  --chat-rail-text: var(--pane-text);
  --chat-rail-active: var(--pane-title-text);
  --chat-rail-link: var(--pane-link);
  --chat-rail-unread-dot: var(--pane-text);
  --chat-divider: var(--pane-border);
  --chat-sidebar-bg: var(--pane-bg);
  --chat-sidebar-text: var(--pane-text);
  --chat-sidebar-title-text: var(--pane-title-text);
  --chat-sidebar-link: var(--pane-link);
  --chat-sidebar-unread-dot: var(--pane-text);
  --chat-topbar-bg: var(--pane-bg);
  --chat-topbar-text: var(--pane-text);
  --chat-topbar-title-text: var(--pane-title-text);
  --chat-pane-bg: var(--pane-bg);
  --chat-pane-text: var(--pane-text);
  --chat-pane-title-text: var(--pane-title-text);
  --chat-pane-link: var(--pane-link);
  --chat-spoiler: var(--spoiler);
  --chat-composer-bg: var(--pane-bg);
  --chat-composer-text: var(--pane-text);
  --chat-composer-highlight: var(--pane-bg);
  --chat-input-bg: var(--input-bg);
  --chat-input-border: var(--input-border);
  --chat-input-text: var(--input-text);
  --chat-tree-guide: color-mix(in srgb, var(--chat-pane-text) 30%, transparent);
  --chat-avatar-placeholder-border: color-mix(in srgb, var(--chat-pane-text) 50%, transparent);
}

html {
  background-color: #444;
}

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

body {
  margin: 0;
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  background-color: var(--page-bg);
  color: var(--pane-text);
  line-height: 1.6;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  overscroll-behavior: none;
}

.site-header {
  background-color: var(--header-bg);
  color: var(--header-text);
  padding: 0.5rem 1.25rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  border-bottom: 1px solid var(--pane-border);
}

.site-header__brand {
  display: flex;
  align-items: center;
  gap: 0.85rem;
}

.site-header .logo {
  font-size: 1.05rem;
  font-weight: 700;
  color: var(--header-title-text);
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.site-header .logo svg {
  width: 1.4em;
  height: 1.4em;
}

.site-header nav a,
.site-header nav .link-button {
  color: var(--header-link);
  text-decoration: none;
  margin-left: 1.5rem;
  font-size: 0.9rem;
}

.site-header nav a:hover,
.site-header nav .link-button:hover {
  text-decoration: underline;
  background-color: transparent;
}

.site-header__signout {
  display: inline;
}

.link-button {
  background: none;
  border: none;
  padding: 0;
  font: inherit;
  cursor: pointer;
}

/* Forced-colors mode (e.g. Firefox/Windows high-contrast) applies its
   link-text color to real <a> elements but not to <button>-based
   look-alikes like .link-button, so it's left showing our themed color
   instead of the OS-forced one. Force it back to CanvasText to match. */
@media (forced-colors: active) {
  .site-header nav .link-button {
    color: CanvasText;
  }
}

.admin-badge {
  margin-left: 0.5rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0.2em 0.5em;
  border-radius: 3px;
  background: var(--alert-bg);
  color: var(--alert-text);
  border: 1px solid var(--alert-border);
  vertical-align: middle;
  text-transform: uppercase;
}

.site-header .action-dropdown__menu a {
  margin-left: 0;
}

@media (forced-colors: active) {
  .admin-badge {
    border: 1px solid ButtonText;
    color: ButtonText;
    background: ButtonFace;
  }
}

.container {
  width: 100%;
  max-width: 960px;
  margin: 2rem auto;
  padding: 0 1.5rem;
  flex: 1;
}

.container:has(.layout),
.container:has(.explorer) {
  max-width: 1200px;
}

.container.narrow {
  max-width: 540px;
}

/* Layout: sidebar + main */
.layout {
  display: grid;
  grid-template-columns: 320px 1fr;
  gap: 2rem;
}

/* The wrapper draws the sidebar's box, and only the nav inside it scrolls,
   below the top padding. That keeps the scrollbar (which Firefox draws over
   the content) clear of the hide button in the corner. */
.sidebar-wrap {
  position: sticky;
  top: 2rem;
  align-self: start;
  background-color: var(--pane-bg);
  border-radius: 8px;
  padding-top: 2rem;
  border: 1px solid var(--pane-border);
}

.sidebar {
  overflow-y: auto;
  max-height: calc(100vh - 6rem);
  padding: 0 0.75rem 0.75rem;
  border-radius: 0 0 8px 8px;
}

/* Hiding a sidebar (sidebar_toggle_controller.js) gives the main content
   the whole width, which matters most with large text on a small screen.
   Its show button then takes a slim column of its own, so it never covers
   anything. All of this is under html.js: the server renders the hidden
   class from a cookie, and without JavaScript nothing could bring the
   sidebar back, so it stays shown and the toggle buttons stay away. */
.js .layout.layout--sidebar-hidden,
.js .explorer.explorer--sidebar-hidden {
  grid-template-columns: auto minmax(0, 1fr);
  gap: 0.75rem;
}

.js .layout--sidebar-hidden > .sidebar-wrap,
.js .explorer--sidebar-hidden > .explorer__sidebar-wrap {
  display: none;
}

/* Round buttons in the default (.btn--secondary) button colours, and so its
   forced-colours styles too. Their names are in visually hidden text, with a
   matching title as a tooltip. 2rem is still comfortably above the 24px
   minimum target size. */
.sidebar-toggle {
  display: none;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  border-radius: 50%;
}

.js .sidebar-toggle--hide,
.js .layout--sidebar-hidden > .sidebar-toggle--show,
.js .explorer--sidebar-hidden > .sidebar-toggle--show {
  display: flex;
}

.sidebar-toggle svg {
  flex: none;
}

/* Nestled in the sidebar's top-right corner, just overlapping its edges,
   in the padding above its contents. */
.sidebar-toggle--hide {
  position: absolute;
  z-index: 1;
  top: -0.5rem;
  right: -0.5rem;
}

/* Reorder mode's on/off button (sidebar_reorder_controller.js), in the
   same round style just left of the hide button. It shows a tick while
   reordering, to finish. */
.js .sidebar-reorder-toggle {
  display: flex;
}

.sidebar-reorder-toggle {
  position: absolute;
  z-index: 1;
  top: -0.5rem;
  right: 2rem;
}

.sidebar-reorder-toggle__done,
.sidebar-reorder-toggle[aria-pressed="true"] .sidebar-reorder-toggle__start {
  display: none;
}

.sidebar-reorder-toggle[aria-pressed="true"] .sidebar-reorder-toggle__done {
  display: block;
}

/* Follows the page as it scrolls, as the sidebar itself would. */
.sidebar-toggle--show {
  position: sticky;
  top: 2rem;
  align-self: start;
}

.sidebar hr {
  border: none;
  border-top: 1px solid var(--pane-border);
  margin: 0.5rem 0.5rem;
}

.sidebar__search {
  padding: 0 0.5rem;
}

.sidebar__search input[type="search"] {
  font-size: 0.9rem;
  padding: 0.4rem 0.6rem;
}

.form-divider {
  border: none;
  border-top: 1px solid color-mix(in srgb, var(--input-border) 40%, transparent);
  margin: 2rem 0;
}

.form-section-summary {
  cursor: pointer;
  font-size: 1rem;
  font-weight: 600;
  color: var(--pane-title-text);
  list-style: none;
  user-select: none;
  padding: 0.25rem 0;
}

.form-section-summary::-webkit-details-marker,
.form-section-summary::marker {
  display: none;
}

.form-section-summary::before {
  content: "▶ ";
  font-size: 0.75rem;
  opacity: 0.6;
}

details[open] > .form-section-summary::before {
  content: "▼ ";
}

@media (forced-colors: active) {
  /* No always-on outline. The ▶/▼ marker already shows these are
     disclosures, and boxing every heading drew a rectangle inside a
     rectangle once the theme form's sections nested. Keyboard focus still
     gets a clear system-colour ring. */
  .form-section-summary:focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }
}

.terms {
  background: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 6px;
  padding: 1rem 1.2rem;
  margin-bottom: 1rem;
  font-size: 0.92rem;
  line-height: 1.5;
}

.terms p {
  margin: 0 0 0.4rem;
}

.terms .checkbox-label {
  margin-top: 1rem;
}

.sidebar details {
  margin-bottom: 0.5rem;
}

.sidebar details:last-child {
  margin-bottom: 0;
}

.sidebar summary {
  cursor: pointer;
  font-weight: 700;
  color: var(--pane-title-text);
  font-size: 0.85rem;
  letter-spacing: 0.05em;
  padding: 0.4rem 0.5rem;
  list-style: none;
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  user-select: none;
}

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

.sidebar summary::before {
  content: "▶";
  font-size: 0.75rem;
  display: inline-block;
  width: 1rem;
  text-align: center;
  line-height: 1;
  transition: transform 0.15s ease;
  margin-top: 0.3rem;
}

.sidebar details[open] > summary::before {
  transform: rotate(90deg);
}

.sidebar__list {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
}

.sidebar__item {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.35rem 0.5rem 0.35rem 1.25rem;
  border-radius: 4px;
  font-size: 0.9rem;
}

.sidebar__item:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.sidebar__item--active {
  background-color: color-mix(in srgb, var(--pane-text) 20%, transparent);
}

.sidebar__item--active .sidebar__link {
  color: var(--pane-title-text);
  font-weight: 600;
}

.sidebar__avatar {
  width: 24px;
  height: 24px;
  border-radius: 25%;
  flex-shrink: 0;
  object-fit: cover;
}

.sidebar__avatar--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
}

.sidebar__link {
  color: var(--pane-link);
  text-decoration: none;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.sidebar__link:hover {
  color: var(--pane-title-text);
}

.sidebar__link--action {
  color: var(--pane-link);
  font-size: 0.85rem;
  font-weight: 600;
}

.sidebar__link--action:hover {
  color: var(--pane-title-text);
}

/* ---- Sidebar tree ---- */
.sidebar-tree {
  list-style: none;
  margin: 0.25rem 0 0;
  padding: 0;
}

.sidebar-tree__controls {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem;
  padding-left: 2rem;
}

.sidebar-tree__control-btn {
  background: none;
  border: none;
  padding: 0 0.2rem;
  font-size: 0.75rem;
  color: var(--pane-link);
  cursor: pointer;
}

@media (forced-colors: active) {
  .sidebar-tree__control-btn {
    color: LinkText;
  }
}

.sidebar-tree,
.sidebar-tree__folder,
.sidebar-tree__children {
  --st-indent: 0.875rem;
  --st-arrow-width: 1rem;
  --st-row-center: 0.875rem;
  --st-guide-inset: 0;
  --st-leaf-connector: 0.8rem;
}

.sidebar-tree__children {
  list-style: none;
  margin: 0;
  padding: 0;
  padding-left: var(--st-indent);
}

/* Tree guide lines */
.sidebar-tree__children > li {
  position: relative;
}

.sidebar-tree__children > li::before {
  content: "";
  position: absolute;
  left: var(--st-guide-inset);
  top: 0;
  bottom: 0;
  border-left: 1px solid var(--tree-guide);
  pointer-events: none;
}

.sidebar-tree__children > .sidebar-tree__leaf::after {
  content: "";
  position: absolute;
  left: var(--st-guide-inset);
  top: var(--st-row-center);
  width: var(--st-leaf-connector);
  border-top: 1px solid var(--tree-guide);
  pointer-events: none;
}

.sidebar-tree__children > li:last-child::before {
  bottom: auto;
  height: var(--st-row-center);
}

.sidebar-tree__folder {
  margin: 0;
}

.sidebar-tree__leaf {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 0.4rem;
  padding: 0.3rem 0.4rem;
  padding-left: var(--st-arrow-width);
  border-radius: 4px;
  font-size: 0.9rem;
}

.sidebar-tree__leaf:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.sidebar-tree__leaf--active {
  background-color: color-mix(in srgb, var(--pane-text) 20%, transparent);
}

.sidebar-tree__leaf--active .sidebar-tree__label {
  color: var(--pane-title-text);
  font-weight: 600;
}

/* A live broadcast has no way to know whether the recipient is already
   looking at this exact channel, so it may push an "unread" dot here even
   while they're actively reading it live. The active row's own class is set
   at page render time and untouched by that broadcast (it only replaces the
   small dot element below), so hiding the dot whenever the row is active is
   what actually suppresses the false positive. */
.sidebar-tree__leaf--active .unread-dot {
  display: none;
}

.sidebar-tree__row {
  display: flex;
  align-items: flex-start;
  flex-wrap: wrap;
  gap: 0.4rem;
  border-radius: 4px;
}

/* Higher specificity (0,2,0) to beat .sidebar summary (0,1,1) */
.sidebar .sidebar-tree__row {
  font-weight: normal;
  color: var(--pane-text);
  font-size: 0.9rem;
  letter-spacing: normal;
  padding: 0.3rem 0.4rem;
}

.sidebar-tree__row:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
  text-decoration: none;
}

.sidebar-tree__row--active {
  background-color: color-mix(in srgb, var(--pane-text) 20%, transparent);
}

.sidebar-tree__row--active .sidebar-tree__link {
  color: var(--pane-title-text);
  font-weight: 600;
}

.sidebar-tree__avatar {
  width: 24px;
  height: 24px;
  border-radius: 25%;
  flex-shrink: 0;
  object-fit: cover;
}

.sidebar-tree__avatar--placeholder {
  width: 24px;
  height: 24px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  border-radius: 25%;
  min-width: 24px;
  min-height: 24px;
}

.sidebar-tree__link {
  color: var(--pane-link);
  text-decoration: none;
  overflow: visible;
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  flex: 1;
  min-width: 0;
}

.sidebar-tree__link:hover {
  color: var(--pane-title-text);
  text-decoration: none;
}

.sidebar-tree__label {
  width: 100%;
  min-width: 0;
  overflow-wrap: break-word;
}

.sidebar-tree__subtitle {
  font-size: 0.75rem;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
  width: 100%;
  min-width: 0;
  overflow-wrap: break-word;
}

.sidebar-tree__label--repeated {
  opacity: 0.55;
  font-style: italic;
}

@media (forced-colors: active) {
  .sidebar-tree__label--repeated {
    opacity: 1;
    color: GrayText;
  }

  .sidebar-tree__children > li::before,
  .sidebar-tree__children > .sidebar-tree__leaf::after {
    border-color: CanvasText;
  }

  .sidebar-tree__leaf--active,
  .sidebar-tree__row--active {
    background: Highlight;
    color: HighlightText;
  }

  .sidebar-tree__leaf--active .sidebar-tree__label,
  .sidebar-tree__leaf--active .sidebar-tree__link,
  .sidebar-tree__row--active .sidebar-tree__label,
  .sidebar-tree__row--active .sidebar-tree__link {
    color: HighlightText;
  }
}

/* Reorder mode (sidebar_reorder_controller.js). It needs JavaScript, so
   its hint and messages only show under html.js; the handles are added by
   the controller, and the Sort A–Z buttons only show while reordering. */
.sidebar__reorder {
  display: none;
  padding: 0 0.5rem;
}

.js .sidebar__reorder {
  display: block;
}

.sidebar__reorder-hint {
  margin: 0.5rem 0 0;
  font-size: 0.75rem;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
}

.sidebar__reorder-error {
  margin: 0.25rem 0 0;
  font-size: 0.8rem;
  /* As the manage page's save errors (.tree-editor__save-indicator--error) */
  color: var(--danger-button-text);
}

.sidebar__reorder-error:empty {
  display: none;
}

.reorder-only {
  display: none;
}

.sidebar--reordering .sidebar-tree__controls.reorder-only {
  display: flex;
}

.sidebar--reordering button.reorder-only {
  display: inline-block;
}

.sidebar .reorder-handle {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: none;
  /* In rem so it grows with larger text sizes, and is never smaller than
     a 24px target */
  width: 1.5rem;
  height: 1.5rem;
  padding: 0;
  background: none;
  border: none;
  border-radius: 4px;
  color: var(--pane-text);
  line-height: 1;
  cursor: grab;
  /* Leaves touch dragging to SortableJS instead of scrolling the sidebar */
  touch-action: none;
}

.sidebar .reorder-handle:hover {
  background-color: color-mix(in srgb, var(--pane-text) 15%, transparent);
}

.sidebar .reorder-handle:focus-visible {
  outline: 2px solid var(--pane-link);
  outline-offset: 1px;
}

.sidebar .reorder-handle:active {
  cursor: grabbing;
}

.reorder-handle svg {
  width: 0.75rem;
  height: 1rem;
}

/* SortableJS: the gap where the dragged item will land */
.sidebar .sortable-ghost {
  opacity: 0.4;
}

@media (forced-colors: active) {
  /* Not the filled look forced colours give other buttons: just the grip */
  .sidebar .reorder-handle {
    background-color: Canvas;
    color: CanvasText;
    border: none;
  }

  .sidebar .reorder-handle:focus-visible {
    outline-color: Highlight;
  }

  .sidebar .sortable-ghost {
    opacity: 1;
    outline: 2px dashed CanvasText;
  }
}

.main-content {
  min-width: 0;
}

h1, h2, h3 {
  color: var(--pane-title-text);
  margin-top: 0;
}

h1 { font-size: 1.75rem; }
h2 { font-size: 1.4rem; }

/* Page title row with inline label badges */
.profile-title {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.profile-title h1 {
  margin-bottom: 0;
}

.profile-title .label-badges {
  margin-top: 0;
}

/* Inline name + labels row inside card headers */
.name-with-labels {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.name-with-labels h1,
.name-with-labels h2 {
  margin-bottom: 0;
}

.name-with-labels .label-badges {
  margin-top: 0;
  transform: translateY(-0.15em);
}

.subtitle {
  margin: 0 0 0.5rem;
  font-size: 0.9rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
}

.tag-line {
  margin: 0 0 0.75rem;
  font-size: 1.1rem;
  font-style: italic;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
}

.profile-card .tag-line {
  margin-top: auto;
  padding-top: 0.5rem;
  font-style: italic;
  font-size: 1rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
  text-align: center;
}

h2 a { color: inherit; }
h2 a:hover { text-decoration: none; opacity: 0.8; }

a {
  color: var(--pane-link);
  text-decoration: none;
}

a:hover {
  text-decoration: underline;
}

a.btn:hover {
  text-decoration: none;
}

/* Flash messages */
.flash {
  padding: 0.75rem 1rem;
  border-radius: 6px;
  margin-bottom: 1.5rem;
  font-size: 0.9rem;
}

.flash--notice {
  background-color: var(--notice-bg);
  border: 1px solid var(--notice-border);
  color: var(--notice-text);
}

.flash--alert {
  background-color: var(--alert-bg);
  border: 1px solid var(--alert-border);
  color: var(--alert-text);
}

.flash--warning {
  background-color: var(--warning-bg);
  border: 1px solid var(--warning-border);
  color: var(--warning-text);
}

/* Forms */
.form-group {
  margin-bottom: 1.25rem;
}

fieldset.form-group {
  border: 1px solid var(--input-border);
  border-radius: 6px;
  padding: 0.75rem 1rem;
}

fieldset.form-group legend {
  font-size: 0.9rem;
  color: var(--input-label);
  padding: 0 0.35rem;
}

.checkbox-list {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.checkbox-label {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  cursor: pointer;
  font-size: 0.95rem;
}

.checkbox-label input[type="checkbox"] {
  width: auto;
  margin-top: 0.4rem;
}

.checkbox-label--disabled {
  cursor: not-allowed;
  opacity: 0.6;
}

@media (forced-colors: active) {
  .checkbox-label--disabled {
    opacity: 1;
    color: GrayText;
  }
}

.checkbox-label__text {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.4rem;
}

.checkbox-label__subtitle {
  display: block;
  width: 100%;
  font-size: 0.8rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
}

label {
  display: block;
  margin-bottom: 0.35rem;
  font-size: 0.9rem;
  color: var(--input-label);
}

.datetime-picker {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.datetime-picker__field.datetime-picker__field--month select {
  width: 10rem;
}

.datetime-picker__field.datetime-picker__field--day select,
.datetime-picker__field.datetime-picker__field--hour select,
.datetime-picker__field.datetime-picker__field--minute select {
  width: 4.5rem;
}

.datetime-picker__field.datetime-picker__field--year input[type="number"] {
  width: 6rem;
}

.datetime-picker__separator {
  color: var(--input-label);
}

input[type="text"],
input[type="email"],
input[type="password"],
input[type="url"],
input[type="number"],
input[type="search"],
textarea,
select {
  width: 100%;
  padding: 0.6rem 0.75rem;
  background-color: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 6px;
  color: var(--input-text);
  font-family: inherit;
  font-size: 1rem;
  outline: none;
  transition: border-color 0.2s;
}

input:focus,
textarea:focus,
select:focus {
  border-color: var(--pane-link);
}

/* Chrome autofill: override the forced white background */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-text-fill-color: var(--input-text);
  -webkit-box-shadow: 0 0 0 1000px var(--input-bg) inset;
  border-color: var(--input-border);
  caret-color: var(--input-text);
  transition: background-color 5000s ease-in-out 0s;
}

textarea {
  resize: vertical;
  min-height: 100px;
}

input[type="file"] {
  color: var(--pane-text);
}

input[type="file"]::file-selector-button {
  background-color: var(--primary-button-bg);
  color: var(--primary-button-text);
  border: 1px solid var(--primary-button-border);
  border-radius: 6px;
  padding: 0.4rem 1rem;
  font-size: 0.9rem;
  cursor: pointer;
  margin-right: 0.75rem;
  transition: background-color 0.2s;
}

input[type="file"]::file-selector-button:hover {
  background-color: color-mix(in srgb, var(--primary-button-bg) 60%, transparent);
}

/* Disabled form controls */
input:disabled,
textarea:disabled,
select:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

input[type="checkbox"]:disabled,
input[type="radio"]:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

input[type="submit"]:disabled,
button:disabled,
.btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}

input[type="submit"],
button,
.btn {
  display: inline-block;
  padding: 0.6rem 1.5rem;
  background-color: var(--primary-button-bg);
  color: var(--primary-button-text);
  border: 1px solid var(--primary-button-border);
  border-radius: 6px;
  font-size: 0.95rem;
  font-family: inherit;
  line-height: 1.6;
  cursor: pointer;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color 0.2s;
}

.form-group input[type="submit"] + .btn,
.form-group .btn + .btn,
.form-group .btn + input[type="submit"] {
  margin-left: 0.75rem;
}

input[type="submit"]:hover,
button:hover,
.btn:hover {
  background-color: color-mix(in srgb, var(--primary-button-bg) 60%, transparent);
}

.btn--secondary {
  background-color: var(--secondary-button-bg);
  border: 1px solid var(--secondary-button-border);
  color: var(--secondary-button-text);
}

.btn--secondary:hover {
  background-color: color-mix(in srgb, var(--secondary-button-bg) 60%, transparent);
  color: var(--secondary-button-text);
}

/* Card */
/* --card-padding, not a plain padding, because edge-to-edge parts (the
   .card__header banner, .theme-list) cancel it with a negative margin: on
   smaller screens the padding shrinks, and they need to shrink with it or
   they stick out past the card's sides. */
.card {
  --card-padding: 2rem;
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  padding: var(--card-padding);
  margin-bottom: 1.5rem;
}

/* Error list */
.error-messages p {
  color: var(--error);
  margin: 0.25rem 0;
  font-size: 0.9rem;
}

/* Helper links below forms */
.form-links {
  margin-top: 1.5rem;
  font-size: 0.9rem;
}

.form-links p {
  margin: 0.35rem 0;
}

/* Dashboard grid */
.dashboard-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 1.5rem;
}

/* Card list */
.card-list {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.card--row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1rem 1.5rem;
}

.card__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
  margin-top: 0.6rem;
}

.card__header {
  display: flex;
  align-items: center;
  gap: 0.75rem;
}

.card__header h1 {
  margin-bottom: 0.5rem;
}

.card__header h2,
.card__header h3 {
  margin: 0;
}

.card__actions {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  flex-shrink: 0;
}

.card__tags + .card__actions {
  margin-top: 1rem;
}

.btn--outline {
  background-color: var(--secondary-button-bg);
  border: 1px solid var(--secondary-button-border);
  color: var(--secondary-button-text);
}

.btn--outline:hover {
  background-color: color-mix(in srgb, var(--secondary-button-bg) 60%, transparent);
  color: var(--secondary-button-text);
}

/* Stats grid */
.stats-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
  gap: 1rem;
}

.stats-card {
  text-align: center;
  padding: 2rem 1rem;
}

.stats-card__number {
  font-size: 2.5rem;
  font-weight: 700;
  line-height: 1;
  color: var(--pane-link);
}

.stats-card__label {
  margin-top: 0.5rem;
  font-size: 0.95rem;
  color: var(--text-muted);
}

/* Profile grid (public group view) */
.profile-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
  gap: 1rem;
}

.profile-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  padding: 1.5rem 1rem;
  cursor: pointer;
  color: var(--pane-text);
}

.profile-card:hover {
  color: var(--pane-text);
}

@media (forced-colors: active) {
  .profile-card {
    border: 1px solid CanvasText;
  }
}

.profile-card h3 {
  margin: 0.75rem 0 0.25rem;
  font-size: 1rem;
}

.profile-card h3 a {
  color: inherit;
}

.profile-card h3 a:hover {
  text-decoration: none;
}

/* Private cards in the authenticated .layout view: stretch the h3 link to
   cover the whole card so clicking anywhere navigates (not just the name).
   Spoilers still intercept clicks because .spoiler has z-index: 1 (above
   the z-index: auto ::after overlay). Action buttons use z-index: 1 too. */
.layout .profile-card {
  position: relative;
}

.layout .profile-card h3 a::after {
  content: "";
  position: absolute;
  inset: 0;
}

.layout .profile-card .btn {
  position: relative;
  z-index: 1;
}

.profile-card .label-badges,
.profile-card .pronouns {
  justify-content: center;
}

.profile-card .btn {
  margin-top: auto;
}

.profile-card__action-spacer {
  flex: 1;
  min-height: 1rem;
}

/* Profile header (show pages) — edge-to-edge banner inside .card */
.card > .card__header {
  background-color: var(--header-bg);
  color: var(--header-text);
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 1.25rem;
  margin: calc(-1 * var(--card-padding)) calc(-1 * var(--card-padding)) 1.5rem;
  padding: var(--card-padding);
  border-radius: 7px 7px 0 0;
}

/* Overrides the global h1/h2/h3, .subtitle, .tag-line and .pronouns rules,
   which otherwise assume they're sitting on --pane-bg. */
.card > .card__header h1,
.card > .card__header h2,
.card > .card__header h3 {
  color: var(--header-title-text);
}

.card > .card__header .subtitle,
.card > .card__header .tag-line,
.card > .card__header .pronouns {
  color: color-mix(in srgb, var(--header-text) 80%, transparent);
}

/* A link's own `color` rule always wins over an inherited one, so the
   .subtitle/.tag-line/.pronouns override above doesn't reach an <a> nested
   inside them — it needs its own override to pick up --header-link instead
   of the global (--pane-link) rule. */
.card > .card__header a {
  color: var(--header-link);
}

.card > .card__header > .tag-line {
  flex-basis: 100%;
  margin: -0.5rem 0 0;
}

.card__header {
  display: flex;
  align-items: center;
  gap: 1.25rem;
  margin-bottom: 1.5rem;
}

.profile-description,
.group-description {
  margin-bottom: 1.5rem;
}

@media (forced-colors: active) {
  .card > .card__header {
    border-bottom: 1px solid CanvasText;
  }
}

.profile-description details summary,
.group-description details summary {
  cursor: pointer;
  text-decoration: underline;
}

/* "Click to close" hint shown at the bottom of open <details> blocks */
.details-close {
  display: none;
  background: none;
  border: none;
  padding: 0;
  color: inherit;
  font-size: 0.8em;
  text-align: left;
  opacity: 0.8;
  margin-top: 0.25rem;
  cursor: pointer;
}

details[open] .details-close {
  display: block;
}

@media (forced-colors: active) {
  .details-close {
    color: CanvasText;
  }
}

/* Inline formatting tags allowed in descriptions */
.profile-description b,
.group-description b {
  font-weight: 700;
}

.profile-description i,
.group-description i {
  font-style: italic;
}

.profile-description u,
.group-description u {
  text-decoration: underline;
}

.profile-description s,
.group-description s {
  text-decoration: line-through;
}

/* Tables in descriptions */
.profile-description table,
.group-description table {
  border-collapse: collapse;
}

.profile-description td,
.profile-description th,
.group-description td,
.group-description th {
  padding: 0 1em 0.5em 0;
}

.profile-description td:last-child,
.profile-description th:last-child,
.group-description td:last-child,
.group-description th:last-child {
  padding-right: 0;
}

/* Image/text layout utility classes usable in descriptions */

/* Side-by-side layout: <div class="img-row"><img ...><p>text</p></div> */
.profile-description .img-row,
.group-description .img-row {
  display: flex;
  gap: 1em;
  align-items: flex-start;
  flex-wrap: wrap;
}

/* simple_format turns newlines inside the div into <br> tags; suppress them
   so they don't become phantom flex children that introduce a leading gap. */
.profile-description .img-row > br,
.group-description .img-row > br {
  display: none;
}

/* Float an image left with text wrapping around it.
   Wrap image + text in a block element (e.g. <div>) to clear the float. */
.profile-description img.float-left,
.group-description img.float-left {
  float: left;
  margin: 0 1em 0.5em 0;
}

.profile-description img.float-right,
.group-description img.float-right {
  float: right;
  margin: 0 0 0.5em 1em;
}

/* Use on a wrapper div to clear floated images inside it */
.profile-description .clearfix,
.group-description .clearfix {
  display: flow-root;
}

/* Spoiler text (Discord-style ||hidden||) */
.spoiler {
  background-color: var(--spoiler);
  color: transparent;
  border: 1px solid var(--spoiler);
  border-radius: 3px;
  padding: 0 0.2em;
  cursor: pointer;
  transition: background-color 0.2s ease, color 0.2s ease;
  user-select: none;
  box-decoration-break: clone;
  -webkit-box-decoration-break: clone;
  text-decoration-line: none !important;
  /* Ensure spoilers sit above stretched-link ::after overlays on cards */
  position: relative;
  z-index: 1;
}

a:hover .spoiler {
  text-decoration-line: none !important;
}

.spoiler:focus-visible {
  outline: 2px solid var(--spoiler);
  outline-offset: 1px;
}

.spoiler--revealed {
  background-color: color-mix(in srgb, var(--spoiler) 10%, transparent);
  color: inherit;
  user-select: auto;
}

a .spoiler {
  /* Don't show "click to reveal" cursor on spoilers inside links, since the
     whole link is already clickable. */
  cursor: inherit;
}

/* Inside a link, a revealed spoiler signals "click to navigate" */
a .spoiler--revealed {
  cursor: pointer;
}

.spoiler img {
  visibility: hidden;
}

.spoiler--revealed img {
  visibility: visible;
}

@media (forced-colors: active) {
  .spoiler {
    forced-color-adjust: none;
    background-color: var(--spoiler);
    color: transparent;
    border: 1px solid CanvasText;
  }

  .spoiler:focus-visible {
    outline-color: CanvasText;
  }

  .spoiler--revealed {
    background-color: Canvas;
    color: CanvasText;
    border-color: CanvasText;
  }
}

/* Spoiler hint tooltip (||secret||[hint] or [hint]||secret||) */
.spoiler--with-hint {
  position: relative;
}

.spoiler--with-hint[aria-expanded="false"]::after {
  content: attr(data-spoiler-hint);
  position: absolute;
  bottom: calc(100% + 5px);
  left: 50%;
  transform: translateX(-50%);
  /* Fallback for older browsers: layer pane-bg over page-bg */
  background-color: var(--page-bg);
  background-image: linear-gradient(var(--pane-bg), var(--pane-bg));
  /* Modern browsers (Chrome 119+, FF 128+, Safari 16.4+): force alpha to 1
     via CSS Relative Color Syntax so an 8-digit hex --pane-bg with alpha
     renders fully opaque. The background shorthand intentionally overrides
     the fallback lines above when supported. */
  background: oklch(from var(--pane-bg) l c h / 1);
  border: 1px solid var(--pane-border);
  color: var(--pane-text);
  border-radius: 4px;
  padding: 2px 8px;
  box-shadow: 0 2px 6px color-mix(in srgb, var(--pane-border) 50%, transparent);
  font-size: 0.75rem;
  font-weight: 500;
  font-style: normal;
  width: max-content;
  max-width: min(16rem, 90vw);
  white-space: normal;
  word-wrap: break-word;
  pointer-events: none;
  z-index: 10;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.25s ease;
}

/* Desktop: show on hover or focus */
.spoiler--with-hint[aria-expanded="false"]:hover::after,
.spoiler--with-hint[aria-expanded="false"]:focus-visible::after {
  visibility: visible;
  opacity: 1;
}

/* Touch state 2: hint explicitly shown by JS after first tap */
.spoiler--with-hint.spoiler--hint-showing::after {
  visibility: visible;
  opacity: 1;
}

@media (forced-colors: active) {
  .spoiler--with-hint[aria-expanded="false"]::after {
    forced-color-adjust: none;
    background: Canvas;
    color: CanvasText;
    border-color: CanvasText;
  }
}

.profile-actions {
  display: flex;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1.5rem;
}

/* Avatars */
.avatar {
  border-radius: 25%;
  object-fit: cover;
}

.avatar--small {
  width: 40px;
  height: 40px;
  font-size: 1rem;
}

.avatar--large {
  width: 96px;
  height: 96px;
  font-size: 2.5rem;
}

.avatar--inline {
  vertical-align: middle;
  margin-right: 0.35em;
}

.avatar--placeholder,
.sidebar__avatar--placeholder,
.sidebar-tree__avatar--placeholder,
.tree__avatar--placeholder {
  color: var(--pane-text);
  border: 1px solid var(--avatar-placeholder-border);
}

/* Overrides the rule above — .avatar--placeholder is the only one of these
   ever used inside a header_bg banner (.card > .card__header,
   .mini-profile__header), where it needs to read against --header-bg
   instead. Reuses the same 50% mix as --avatar-placeholder-border, just
   against the header's own text colour rather than the pane's body text. */
.card > .card__header .avatar--placeholder,
.mini-profile__header .avatar--placeholder {
  color: var(--header-text);
  border-color: color-mix(in srgb, var(--header-text) 50%, transparent);
}

.avatar--placeholder svg,
.sidebar__avatar--placeholder svg,
.sidebar-tree__avatar--placeholder svg,
.tree__avatar--placeholder svg {
  width: 75%;
  height: 75%;
}

.avatar--placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 25%;
  width: 64px;
  height: 64px;
  min-width: 64px;
  min-height: 64px;
  flex-shrink: 0;
}

.profile-card .avatar--placeholder {
  margin-left: auto;
  margin-right: auto;
}

.avatar--small.avatar--placeholder {
  width: 40px;
  height: 40px;
  min-width: 40px;
  min-height: 40px;
}

.avatar--large.avatar--placeholder {
  width: 96px;
  height: 96px;
}

/* Pronouns */
.group-list-subtitle {
  display: block;
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
}

.pronouns {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  font-style: italic;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
  font-size: 0.9rem;
}

/* .pronouns is a flex container, so a trailing space in a text node right
   before another flex-item child (e.g. an inline link) collapses away —
   same reason .pronouns__emotes below carries its own margin-left
   rather than relying on a space in the markup. */
.pronouns a {
  margin-left: 0.3em;
}

/* Avatar shape modifiers */
.avatar--circle,
.avatar--circle.avatar--placeholder,
.sidebar-tree__avatar--circle,
.tree__avatar.tree__avatar--circle {
  border-radius: 50%;
}

.avatar--square,
.avatar--square.avatar--placeholder,
.sidebar-tree__avatar--square,
.tree__avatar.tree__avatar--square {
  border-radius: 0;
}

/* When JS is active, hide the avatar editor dialog unless it's open as a modal.
   Without JS, the dialog has [open] and renders inline so users can access the fields. */
.js .avatar-editor-dialog:not(:modal) {
  display: none;
}

/* Without JS, override the browser's default `position: absolute` on <dialog>
   so it flows inline with the form instead of overlapping other content. */
html:not(.js) .avatar-editor-dialog {
  position: static;
  width: auto;
  max-height: none;
  margin: 0;
}

html:not(.js) .avatar-editor__current,
html:not(.js) .avatar-editor-dialog__actions {
  display: none;
}

/* Avatar editor dialog */
.avatar-editor__current {
  display: flex;
  align-items: center;
  gap: 1rem;
}

.avatar-editor-dialog {
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  color: var(--pane-text);
  padding: 1.5rem;
  width: min(480px, 90vw);
  max-height: 90vh;
  overflow-y: auto;
}

.avatar-editor-dialog::backdrop {
  background-color: color-mix(in srgb, var(--page-bg) 60%, transparent);
}

.avatar-editor-dialog__title {
  font-size: 1.2rem;
  font-weight: 600;
  color: var(--pane-title-text);
  margin: 0 0 1.25rem;
}

.avatar-editor-dialog__preview {
  display: flex;
  align-items: center;
  justify-content: center;
  margin-bottom: 1.25rem;
}

.avatar-editor-dialog__preview img,
.avatar-editor-dialog__preview .avatar--placeholder {
  width: 96px;
  height: 96px;
}

.avatar-shape-picker {
  display: flex;
  gap: 1rem;
  margin-bottom: 1.25rem;
}

.avatar-shape-picker__option {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  font-size: 0.85rem;
  color: var(--pane-text);
}

.avatar-shape-picker__option input[type="radio"] {
  position: absolute;
  opacity: 0;
  pointer-events: none;
}

.avatar-shape-picker__sample {
  width: 48px;
  height: 48px;
  background-color: var(--input-border);
  outline: 2px solid transparent;
  outline-offset: 2px;
  transition: outline-color 0.1s;
}

.avatar-shape-picker__sample--rounded {
  border-radius: 25%;
}

.avatar-shape-picker__sample--circle {
  border-radius: 50%;
}

.avatar-shape-picker__sample--square {
  border-radius: 0;
}

.avatar-shape-picker__option input[type="radio"]:checked ~ .avatar-shape-picker__sample {
  outline-color: var(--pane-link);
}

.avatar-shape-picker__option input[type="radio"]:focus-visible ~ .avatar-shape-picker__sample {
  outline-color: var(--pane-link);
}

.avatar-editor-dialog__actions {
  display: flex;
  gap: 0.75rem;
  margin-top: 1.25rem;
}

@media (forced-colors: active) {
  .avatar-editor-dialog {
    border: 2px solid ButtonText;
  }

  .avatar-shape-picker__sample {
    background-color: ButtonFace;
    border: 2px solid ButtonText;
  }

  .avatar-shape-picker__option input[type="radio"]:checked ~ .avatar-shape-picker__sample {
    outline-color: Highlight;
    background-color: Highlight;
  }
}

/* Explorer layout (public group tree view) */
.explorer {
  display: none;
  grid-template-columns: 280px 1fr;
  gap: 1.5rem;
  min-height: 400px;
}

.explorer--active {
  display: grid;
}

.explorer__fallback[hidden] {
  display: none;
}

.js .explorer__fallback {
  display: none;
}

/* As with .sidebar-wrap: the wrapper draws the box, and only the tree inside
   it scrolls, below the top padding, clear of the hide button. */
.explorer__sidebar-wrap {
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  padding-top: 1.5rem;
  position: sticky;
  top: 2rem;
  align-self: start;
}

.explorer__sidebar {
  padding: 0 0.5rem 0.5rem;
  border-radius: 0 0 8px 8px;
  overflow-y: auto;
  max-height: calc(80vh - 1.5rem);
}

.explorer__content {
  min-width: 0;
}

/* Tree */
.tree,
.tree__children {
  --tree-indent: 1rem;
  --tree-arrow-width: 1.4rem;
  --tree-row-center: 0.95rem;        /* vertical midpoint of a row */
  --tree-guide-inset: -0.35rem;      /* guide line offset within indent */
  --tree-leaf-connector: 1.6rem;     /* horizontal line reaching into leaf */

  list-style: none;
  margin: 0;
  padding: 0;
}

.tree__children {
  padding-left: var(--tree-indent);
}

/* Tree guide lines */
.tree__children > li {
  position: relative;
}

.tree__children > li::before {
  content: "";
  position: absolute;
  left: var(--tree-guide-inset);
  top: 0;
  bottom: 0;
  border-left: 1px solid var(--tree-guide);
  pointer-events: none;
}

.tree__children > .tree__leaf::after {
  content: "";
  position: absolute;
  left: var(--tree-guide-inset);
  top: var(--tree-row-center);
  width: var(--tree-leaf-connector);
  border-top: 1px solid var(--tree-guide);
  pointer-events: none;
}

.tree__children > li:last-child::before {
  bottom: auto;
  height: var(--tree-row-center);
}

.tree__folder {
  margin: 0;
}

.tree__leaf {
  margin: 0;
  padding-left: var(--tree-arrow-width);
}

.tree__row {
  display: flex;
  align-items: flex-start;
}

.tree__arrow-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: var(--tree-arrow-width);
  flex-shrink: 0;
  padding: 0.6rem 0;
  border: none;
  background: none;
  color: var(--pane-text);
  cursor: pointer;
  border-radius: 4px;
  user-select: none;
}

.tree__arrow-btn:hover {
  background-color: color-mix(in srgb, var(--pane-text) 15%, transparent);
}

.tree__item {
  display: flex;
  align-items: flex-start;
  gap: 0.4rem;
  flex: 1;
  min-width: 0;
  padding: 0.3rem 0.5rem;
  border: none;
  border-radius: 4px;
  background: none;
  color: var(--pane-text);
  cursor: pointer;
  text-align: left;
  text-decoration: none;
  overflow: visible;
}

.tree__item:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
  text-decoration: none;
}

.tree__item--active {
  background-color: color-mix(in srgb, var(--pane-text) 20%, transparent);
  color: var(--pane-title-text);
  font-weight: 600;
}

.tree__arrow {
  font-size: 0.75rem;
  flex-shrink: 0;
  width: 1rem;
  text-align: center;
  display: inline-block;
  line-height: 1;
  vertical-align: middle;
  transition: transform 0.15s ease;
}

.tree__arrow--open {
  transform: rotate(90deg);
}

.tree__avatar {
  width: 24px;
  height: 24px;
  border-radius: 25%;
  flex-shrink: 0;
  object-fit: cover;
}

.tree__avatar--placeholder {
  display: inline-flex;
  align-items: center;
  justify-content: center;
}

.tree__label-block {
  display: flex;
  flex-direction: column;
  overflow: visible;
  flex: 1;
  min-width: 0;
}

.tree__label {
  min-width: 0;
  overflow-wrap: break-word;
}

.tree__label--repeated {
  opacity: 0.55;
  font-style: italic;
}

.tree__subtitle {
  font-size: 0.75rem;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
  min-width: 0;
  overflow-wrap: break-word;
}

@media (forced-colors: active) {
  .tree__label--repeated {
    opacity: 1;
    color: GrayText;
  }
}

.tree__overlapping-hint {
  font-size: 0.7rem;
  opacity: 0.6;
  flex-shrink: 0;
  margin-left: auto;
  padding-left: 0.25rem;
  cursor: help;
}

/* ── Tree editor (authenticated group management) ──────── */

.tree-editor {
  margin-bottom: 2rem;
}

.tree-editor__hint {
  font-size: 0.9rem;
  opacity: 0.8;
  margin-bottom: 1.25rem;
}

/* ── Layout variables (mirrors public tree) ──────────── */
.tree-editor__folder,
.tree-editor__children {
  --te-indent: 1.5rem;
  --te-arrow-width: 1.4rem;
  --te-row-center: 0.95rem;
  --te-guide-inset: -0.35rem;
  --te-leaf-connector: 1.6rem;
}

/* ── Folder (details/summary) ────────────────────────── */
.tree-editor__folder {
  margin: 0;
  position: relative;
}

.tree-editor__summary {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  cursor: pointer;
  list-style: none;
  border-radius: 4px;
}

.tree-editor__summary::-webkit-details-marker {
  display: none;
}

.tree-editor__summary::before {
  content: "▶";
  font-size: 0.75rem;
  flex-shrink: 0;
  display: inline-block;
  transition: transform 0.15s ease;
  width: var(--te-arrow-width);
  text-align: center;
  line-height: 1;
}

details[open] > .tree-editor__summary::before {
  transform: rotate(90deg);
}

.tree-editor__summary:hover,
.tree-editor__leaf:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.tree-editor__summary .tree-editor__item-info {
  flex: 1;
  min-width: 0;
}

.tree-editor__summary .tree-editor__actions {
  margin-left: auto;
}

/* ── Children container (indented with guide lines) ──── */
.tree-editor__children {
  padding-left: var(--te-indent);
  margin: 0;
}

/* Vertical + horizontal guide lines on direct children */
.tree-editor__children > .tree-editor__folder,
.tree-editor__children > .tree-editor__leaf {
  position: relative;
}

/* Vertical guide line running down the left */
.tree-editor__children > .tree-editor__folder::before,
.tree-editor__children > .tree-editor__leaf::before {
  content: "";
  position: absolute;
  left: var(--te-guide-inset);
  top: 0;
  bottom: 0;
  border-left: 1px solid var(--tree-guide);
  pointer-events: none;
}

/* Last child: vertical line stops at the row center */
.tree-editor__children > .tree-editor__folder:last-child::before,
.tree-editor__children > .tree-editor__leaf:last-child::before {
  bottom: auto;
  height: var(--te-row-center);
}

/* Horizontal connector line into leaf items */
.tree-editor__children > .tree-editor__leaf::after {
  content: "";
  position: absolute;
  left: var(--te-guide-inset);
  top: var(--te-row-center);
  width: var(--te-leaf-connector);
  border-top: 1px solid var(--tree-guide);
  pointer-events: none;
}

/* ── Leaf items (profiles + groups without children) ─── */
.tree-editor__leaf {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.3rem 0.5rem;
  padding-left: calc(var(--te-arrow-width) + 0.5rem);
  margin: 0;
}

/* ── Common elements ─────────────────────────────────── */
.tree-editor__toggle-form {
  display: contents;
}

.tree-editor__item-info {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  min-width: 0;
}

.tree-editor__item-info .btn {
  margin-left: 0.6rem;
}

.tree-editor__name-block {
  display: inline-flex;
  flex-direction: column;
  overflow: hidden;
  min-width: 0;
}

.tree-editor__name {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  color: var(--pane-title-text);
}

.tree-editor__subtitle {
  font-size: 0.75rem;
  opacity: 0.75;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.tree-editor__node--hidden .tree-editor__name {
  font-style: italic;
  opacity: 0.5;
}

.tree-editor__actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-shrink: 0;
  margin-left: auto;
  line-height: 24px;
}

.tree-editor__hide-label {
  color: var(--pane-text);
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  cursor: pointer;
  font-size: 0.85rem;
  white-space: nowrap;
  user-select: none;
  line-height: 24px;
  vertical-align: middle;
  margin-bottom: 0;
}

.tree-editor__hide-label input[type="checkbox"] {
  flex-shrink: 0;
}

/* ── Status tags / badges ──────────────────────────────── */
.tag {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 4px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
}

.tag--active, .tag--default {
  background-color: color-mix(in srgb, var(--primary-button-bg) 35%, transparent);
  color: var(--pane-text);
}

@media (forced-colors: active) {
  .tag--active, .tag--default {
    outline: 1px solid CanvasText;
  }
}

/* Descriptive tags shown on theme cards */
.tag--theme {
  background-color: color-mix(in srgb, var(--input-border) 20%, transparent);
  color: var(--pane-text);
  border: 1px solid color-mix(in srgb, var(--input-border) 50%, transparent);
}

@media (forced-colors: active) {
  .tag--theme {
    outline: 1px solid CanvasText;
  }
}



.tag--filter {
  background-color: color-mix(in srgb, var(--input-border) 15%, transparent);
  color: var(--pane-text);
  border: 1px solid color-mix(in srgb, var(--input-border) 40%, transparent);
  text-decoration: none;
  cursor: pointer;
  transition: background-color 0.1s ease, border-color 0.1s ease;
}

.tag--filter:hover {
  background-color: color-mix(in srgb, var(--input-border) 30%, transparent);
  text-decoration: none;
}

.tag--filter-active {
  background-color: color-mix(in srgb, var(--pane-link) 25%, transparent);
  color: var(--pane-title-text);
  border-color: color-mix(in srgb, var(--pane-link) 60%, transparent);
  font-weight: 600;
}

.tag--filter-active:hover {
  background-color: color-mix(in srgb, var(--pane-link) 35%, transparent);
}

@media (forced-colors: active) {
  .tag--filter {
    outline: 1px solid ButtonText;
  }

  .tag--filter-active {
    outline: 2px solid Highlight;
    color: HighlightText;
    background-color: Highlight;
  }
}



.tree-editor__tag {
  display: inline-block;
  padding: 0.1rem 0.45rem;
  border-radius: 4px;
  font-size: 0.7rem;
  font-weight: 600;
  letter-spacing: 0.03em;
  white-space: nowrap;
  margin-left: 0.35rem;
  flex-shrink: 0;
}

.tree-editor__tag--hidden {
  display: none;
  background-color: color-mix(in srgb, var(--danger-button-bg) 35%, transparent);
  color: var(--danger-button-text);
}

.tree-editor__node--hidden .tree-editor__tag--hidden {
  display: inline-block;
}

/* Duplication preview tags (new copy / existing copy) */
.tree-editor__tag--new {
  background-color: color-mix(in srgb, var(--primary-button-bg) 35%, transparent);
  color: var(--pane-text);
}

.tree-editor__tag--reuse {
  background-color: color-mix(in srgb, var(--input-border) 25%, transparent);
  color: var(--pane-text);
}

/* ── Save indicator ──────────────────────────────────── */
.tree-editor__save-indicator {
  font-size: 0.8rem;
  color: var(--primary-button-text);
  opacity: 0;
  transition: opacity 0.2s;
  flex-shrink: 0;
  white-space: nowrap;
}

.tree-editor__save-indicator--visible {
  opacity: 1;
}

.tree-editor__save-indicator--error {
  color: var(--danger-button-text);
  opacity: 1;
}

@media (max-width: 600px) {
  .tree-editor__summary {
    flex-wrap: wrap;
  }

  .tree-editor__leaf {
    flex-wrap: wrap;
  }

  .tree-editor__children {
    padding-left: 0.5rem;
  }
}

/* ── Forced-colors overrides for tree editor ───────────── */
@media (forced-colors: active) {
  .tree-editor__name {
    color: CanvasText;
  }

  .tree-editor__tag {
    background-color: Canvas;
    border: 1px solid CanvasText;
    color: CanvasText;
  }

  .tree-editor__tag--hidden {
    background-color: Canvas;
    border-color: CanvasText;
    color: CanvasText;
  }

  .tree-editor__tag--new,
  .tree-editor__tag--reuse {
    background-color: Canvas;
    border: 1px solid CanvasText;
    color: CanvasText;
  }

  .tree-editor__children > .tree-editor__folder::before,
  .tree-editor__children > .tree-editor__leaf::before,
  .tree-editor__children > .tree-editor__leaf::after {
    border-color: CanvasText;
  }

  .tree-editor__save-indicator {
    color: CanvasText;
  }

  .tree-editor__save-indicator--error {
    color: CanvasText;
  }

  .tree-editor__summary {
    color: CanvasText;
  }

  .tree-editor__summary:hover,
  .tree-editor__leaf:hover {
    outline: 1px solid GrayText;
  }
}

/* Metadata */
.profile-chat-proxy code {
  background-color: color-mix(in srgb, var(--input-border) 15%, transparent);
  border: 1px solid color-mix(in srgb, var(--input-border) 40%, transparent);
  border-radius: 4px;
  padding: 0.1rem 0.4rem;
}

.metadata {
  font-size: 0.85rem;
  border-top: 1px solid var(--input-border);
  padding-top: 1rem;
  margin-bottom: 1.5rem;
}

.metadata p {
  margin: 0.25rem 0;
}

.metadata strong {
  color: var(--pane-title-text);
}

.metadata time {
  margin-left: 0.25rem;
}

.metadata .time-ago {
  opacity: 0.7;
}

/* Share URL styles */
.share-url {
  display: flex;
  gap: 0.5rem;
}

.share-url input[type="text"] {
  flex: 1;
  font-size: 0.85rem;
}

.share-url-hint {
  font-size: 0.85rem;
  margin-top: 0.75rem;
  margin-bottom: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.5rem;
}

/* Text helpers */
.text-muted {
  opacity: 0.7;
  font-size: 0.9rem;
}

.form-hint {
  font-size: 0.85rem;
  opacity: 0.7;
  margin-top: 0.5rem;
}

.chat-bracket-fields {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.chat-bracket-fields input {
  width: auto;
  flex: 1 1 0;
  min-width: 0;
  text-align: center;
}

.chat-bracket-fields__sample {
  flex-shrink: 0;
  opacity: 0.7;
}

/* Button sizes */
input[type="submit"].btn--small,
.btn--small {
  padding: 0.35rem 0.75rem;
  font-size: 0.8rem;
}

.btn--danger {
  background-color: var(--danger-button-bg);
  border: 1px solid var(--danger-button-border);
  color: var(--danger-button-text);
}

.btn--danger:hover {
  background-color: color-mix(in srgb, var(--danger-button-bg) 80%, transparent);
  color: var(--danger-button-text);
}

/* Utility classes (for user-generated content) */
.font-bold { font-weight: 700; }
.font-italic { font-style: italic; }
.text-center { text-align: center; }
.text-left { text-align: left; }
.text-lg { font-size: 1.2rem; }
.text-right { text-align: right; }
.text-sm { font-size: 0.85rem; }

/* ---- Forced-colors: form controls & buttons ---- */
@media (forced-colors: active) {
  /* Text inputs, textareas, selects — take full control */
  input[type="text"],
  input[type="email"],
  input[type="password"],
  input[type="url"],
  input[type="number"],
  input[type="search"],
  textarea,
  select {
    forced-color-adjust: none;
    background-color: Canvas;
    color: CanvasText;
    border: 1px solid CanvasText;
  }

  /* Prevent Firefox autofill from overriding */
  input:-moz-autofill,
  textarea:-moz-autofill,
  select:-moz-autofill,
  input:autofill,
  textarea:autofill,
  select:autofill {
    forced-color-adjust: none;
    background-color: Canvas !important;
    color: CanvasText !important;
    -webkit-text-fill-color: CanvasText;
    border-color: CanvasText !important;
  }

  /* Override Chrome autofill box-shadow trick for forced-colors */
  input:-webkit-autofill,
  input:-webkit-autofill:hover,
  input:-webkit-autofill:focus,
  textarea:-webkit-autofill,
  select:-webkit-autofill {
    -webkit-text-fill-color: CanvasText;
    -webkit-box-shadow: none;
    background-color: Canvas !important;
    border-color: CanvasText;
  }

  input[type="text"]:focus,
  input[type="email"]:focus,
  input[type="password"]:focus,
  input[type="url"]:focus,
  input[type="number"]:focus,
  input[type="search"]:focus,
  textarea:focus,
  select:focus {
    border: 1px solid CanvasText;
    outline: 2px solid CanvasText;
  }

  /* Labels & legends */
  label,
  fieldset.form-group legend {
    color: CanvasText;
  }

  /* Checkboxes — replace native control, manual system-color styling. */
  input[type="checkbox"],
  .checkbox-label input[type="checkbox"] {
    forced-color-adjust: none;
    -webkit-appearance: none;
    appearance: none;
    width: 1.15em;
    height: 1.15em;
    min-width: 1.15em;
    min-height: 1.15em;
    margin: 0;
    flex-shrink: 0;
    background-color: Canvas;
    border: 2px solid CanvasText;
    border-radius: 3px;
    vertical-align: middle;
    cursor: pointer;
  }

  input[type="checkbox"]:checked {
    background-color: CanvasText;
    border-color: CanvasText;
    /* Use a mask so the Canvas background shows through as the checkmark */
    background-image: none;
    position: relative;
  }

  input[type="checkbox"]:checked::after {
    content: "";
    display: block;
    width: 100%;
    height: 100%;
    background-color: Canvas;
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5 6.5 11.5 12.5 4.5'/%3E%3C/svg%3E");
    -webkit-mask-size: 100% 100%;
    -webkit-mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'%3E%3Cpath fill='none' stroke='black' stroke-width='2.5' stroke-linecap='round' stroke-linejoin='round' d='M3.5 8.5 6.5 11.5 12.5 4.5'/%3E%3C/svg%3E");
    mask-size: 100% 100%;
    mask-repeat: no-repeat;
    mask-position: center;
  }

  input[type="checkbox"]:focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }

  /* Radio buttons — pure CSS dot using box-shadow, no SVG needed */
  input[type="radio"] {
    forced-color-adjust: none;
    -webkit-appearance: none;
    appearance: none;
    width: 1.15em;
    height: 1.15em;
    margin: 0;
    flex-shrink: 0;
    background-color: Canvas;
    border: 2px solid CanvasText;
    border-radius: 50%;
    vertical-align: middle;
    cursor: pointer;
  }

  input[type="radio"]:checked {
    border: 2px solid CanvasText;
    background-color: CanvasText;
    /* Inset Canvas ring creates the gap around the CanvasText dot */
    box-shadow: inset 0 0 0 0.13em Canvas;
  }

  input[type="radio"]:focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }

  /* Disabled controls in forced-colors */
  input:disabled,
  textarea:disabled,
  select:disabled {
    color: GrayText;
    border-color: GrayText;
    cursor: not-allowed;
  }

  input[type="checkbox"]:disabled,
  input[type="radio"]:disabled {
    border-color: GrayText;
    background-color: GrayText;
    cursor: not-allowed;
  }

  input[type="submit"]:disabled,
  button:disabled,
  .btn:disabled {
    background-color: GrayText;
    border-color: GrayText;
    color: Canvas;
    cursor: not-allowed;
  }

  /* Primary buttons */
  input[type="submit"],
  button,
  .btn {
    forced-color-adjust: none;
    background-color: CanvasText;
    color: Canvas;
    border: 1px solid CanvasText;
  }

  /* File input browse button */
  input[type="file"] {
    color: CanvasText;
  }

  input[type="file"]::file-selector-button {
    forced-color-adjust: none;
    background-color: CanvasText;
    color: Canvas;
    border: 1px solid CanvasText;
    border-radius: 4px;
    padding: 0.3rem 0.75rem;
    cursor: pointer;
  }

  input[type="file"]::file-selector-button:hover {
    background-color: Canvas;
    color: CanvasText;
  }

  input[type="submit"]:hover,
  button:hover,
  .btn:hover {
    background-color: Canvas;
    color: CanvasText;
  }

  input[type="submit"]:focus-visible,
  button:focus-visible,
  .btn:focus-visible {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }

  /* Secondary / outline buttons */
  .btn--secondary,
  .btn--outline {
    background-color: Canvas;
    color: CanvasText;
    border: 1px solid CanvasText;
  }

  .btn--secondary:hover,
  .btn--outline:hover {
    background-color: CanvasText;
    color: Canvas;
  }

  /* Fieldset borders */
  fieldset.form-group,
  fieldset.inclusion-radios,
  fieldset.inclusion-checkboxes,
  fieldset.inclusion-profiles {
    border-color: CanvasText;
  }

  /* Bare chrome-less buttons (tree controls, etc.) */
  .tree__arrow-btn,
  .tree__item {
    background-color: transparent;
    color: CanvasText;
    border-color: transparent;
  }

  .tree__arrow-btn:hover,
  .tree__item:hover {
    opacity: 0.7;
  }

  .tree__item--active {
    outline: 2px solid CanvasText;
    font-weight: 700;
  }
}

/* ---- Responsive ---- */

/* Tablet (768px and below) */
@media (max-width: 768px) {
  .layout {
    grid-template-columns: 280px 1fr;
    gap: 1.5rem;
  }

  .container {
    padding: 0 1rem;
  }

  .card {
    --card-padding: 1.5rem;
  }

  .site-header {
    padding: 0.5rem 1rem;
  }
}

/* Mobile (600px and below) */
@media (max-width: 600px) {
  .layout {
    grid-template-columns: 1fr;
  }

  /* Relative, not sticky, but still holding the hide button in its corner.
     top goes back to auto, or relative positioning would shift the sidebar
     down over the content below it. */
  .sidebar-wrap {
    position: relative;
    top: auto;
  }

  .explorer--active {
    grid-template-columns: 1fr;
  }

  .explorer__sidebar-wrap {
    position: relative;
    top: auto;
  }

  .explorer__sidebar {
    max-height: none;
  }

  .container {
    margin: 1rem auto;
    padding: 0 1rem;
  }

  .card {
    --card-padding: 1.25rem;
  }

  h1 { font-size: 1.4rem; }
  h2 { font-size: 1.2rem; }

  .site-header {
    flex-direction: column;
    gap: 0.5rem;
    text-align: center;
  }

  .site-header nav a {
    margin-left: 0;
    margin-right: 1rem;
  }

  .site-header nav a:last-child {
    margin-right: 0;
  }

  .profile-grid {
    grid-template-columns: repeat(auto-fill, minmax(140px, 1fr));
  }

  .card__header {
    flex-direction: column;
    text-align: center;
  }

  .share-url {
    flex-direction: column;
    align-items: center;
  }

  .dashboard-grid {
    grid-template-columns: 1fr;
  }

  .profile-actions {
    justify-content: center;
  }
}

/* Invite codes */
.invite-code-list {
  list-style: none;
  padding: 0;
  margin: 0 0 1rem 0;
  display: flex;
  flex-direction: row;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.invite-code-item {
  display: flex;
  align-items: center;
  background-color: var(--input-bg);
  border: 1px solid var(--input-border);
  border-radius: 4px;
}

.invite-code {
  font-family: monospace;
  font-size: 1.1rem;
  letter-spacing: 0.1em;
  padding: 0.4rem 0.75rem;
}

.invite-code-delete {
  padding: 0.4rem 0.6rem;
  margin: 0 0.5rem;
  line-height: 1;
  font-size: 1rem;
  background-color: transparent;
  color: var(--pane-text);
}

.invite-code-delete:hover {
  background-color: transparent;
  color: var(--error);
}

.invite-code-input {
  font-family: monospace;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.muted {
  opacity: 0.65;
  font-style: italic;
}

/* ── Profile emotes (shown next to pronouns) ───────────── */

/* Upright rather than italic like the pronouns, and spaced from them with a
   margin: .pronouns is a flex container, so a space in the markup collapses. */
.pronouns__emotes {
  font-style: normal;
}

.pronouns > * + .pronouns__emotes {
  margin-left: 0.5rem;
}

@media (forced-colors: active) {
  .invite-code-item {
    border: 1px solid CanvasText;
    background-color: Canvas;
  }

  .invite-code {
    color: CanvasText;
  }

  .invite-code-delete {
    border-left-color: CanvasText;
  }

  .form-divider {
    border-top-color: GrayText;
  }

  .sidebar hr {
    border-top-color: CanvasText;
  }
}

/* ── Inline emotes ─────────────────────── */

.emote-inline {
  display: inline;
  vertical-align: -0.35em;
  width: auto;
}

h1 .emote-inline {
  vertical-align: -0.1em;
}

/* An emote on a line of its own is shown larger (see replace_emote_codes) */
.emote-inline--large {
  vertical-align: middle;
}

/* ── Admin ─────────────────────────────── */

.admin-breadcrumbs {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
  font-size: 0.9rem;
}

.admin-links {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
  gap: 0 1.5rem;
}

.admin-links__item h2 {
  margin: 0 0 0.5rem;
  font-size: 1.2rem;
}

.admin-links__item p {
  margin: 0;
}

/* ── Admin: emotes ─────────────────────── */

.admin-emotes__intro p,
.admin-emotes__hint {
  margin: 0 0 0.75rem;
  font-size: 0.95rem;
}

.admin-emotes__hint {
  font-size: 0.85rem;
  opacity: 0.85;
}

.admin-emotes__toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem 1.5rem;
  margin-bottom: 1rem;
}

.admin-emotes__filter {
  flex: 0 1 20rem;
  margin: 0;
}

.admin-emotes__filter span {
  display: block;
  margin-bottom: 0.35rem;
}

.admin-emotes__status {
  flex: 1 1 12rem;
  margin: 0;
  font-size: 0.85rem;
  min-height: 1.4em;
}

.emote-sets {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.emote-sets__set {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.5rem 1rem;
  padding-bottom: 0.75rem;
  border-bottom: 1px solid var(--input-border);
}

.emote-sets__set:last-child {
  padding-bottom: 0;
  border-bottom: none;
}

.emote-sets__form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-end;
  gap: 0.5rem;
}

.emote-sets__field label {
  margin-bottom: 0.2rem;
  font-size: 0.8rem;
}

.emote-sets__field input[type="text"] {
  padding: 0.35rem 0.5rem;
  font-size: 0.95rem;
}

.emote-sets__actions {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.emote-sets__count {
  font-size: 0.85rem;
  opacity: 0.85;
}

.emote-section + .emote-section {
  margin-top: 1.75rem;
}

.emote-section__heading {
  margin: 0 0 0.5rem;
  font-size: 1.1rem;
  color: var(--pane-title-text);
}

summary.emote-section__heading {
  cursor: pointer;
}

.emote-section__count {
  font-weight: normal;
  font-size: 0.9rem;
  opacity: 0.8;
}

.emote-section__empty {
  margin: 0;
  font-size: 0.9rem;
  opacity: 0.85;
}

.emote-list {
  list-style: none;
  margin: 0;
  padding: 0;
  /* The rows lay themselves out by the list's own width, not the screen's:
     beside the sidebar, with larger text, the list can be narrow even on a
     wide screen. */
  container: emote-list / inline-size;
}

.emote-row {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr) auto;
  align-items: center;
  gap: 0.5rem 1rem;
  padding: 0.6rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--input-border) 60%, transparent);
}

.emote-row[hidden] {
  display: none;
}

.emote-row__image {
  width: 48px;
  height: 48px;
  object-fit: contain;
}

/* Fields wrap onto more lines as space runs out, whether from a narrow
   screen or larger text, rather than overflowing into the Archive button. */
.emote-row__form {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem 0.75rem;
  min-width: 0;
}

.emote-row__field {
  flex: 1 1 10rem;
  min-width: 0;
}

.emote-row__field--name {
  flex-grow: 1.3;
}

.emote-row__field--set {
  flex: 0.7 1 8rem;
}

.emote-row__field select {
  width: 100%;
}

.emote-row__field label {
  margin-bottom: 0.2rem;
  font-size: 0.8rem;
}

.emote-row__field input[type="text"],
.emote-row__field select {
  padding: 0.35rem 0.5rem;
  font-size: 0.95rem;
}

.emote-row__field input[aria-invalid="true"] {
  border-color: var(--error, var(--warning-border));
}

.emote-row__field input:disabled {
  opacity: 0.75;
  cursor: default;
}

.emote-row__code {
  display: flex;
  align-items: center;
  gap: 0.15rem;
}

.emote-row__colon {
  opacity: 0.7;
}

label.emote-row__override {
  display: flex;
  align-items: center;
  gap: 0.35rem;
  margin: 0.3rem 0 0;
  cursor: pointer;
}

.emote-row__override input[type="checkbox"] {
  width: auto;
  margin: 0;
}

.emote-row__errors {
  flex-basis: 100%;
}

.emote-row__errors p {
  margin: 0;
  color: var(--error, var(--warning-border));
  font-size: 0.85rem;
}

.emote-row__aside {
  grid-column: 3;
  grid-row: 1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 0.5rem;
}

.emote-row__aliases {
  grid-column: 2 / -1;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  font-size: 0.85rem;
}

.emote-row__alias {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.1rem 0.2rem 0.1rem 0.45rem;
  border: 1px solid var(--input-border);
  border-radius: 999px;
}

.emote-row__alias-remove {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 1.5rem;
  height: 1.5rem;
  border-radius: 50%;
  color: var(--pane-link);
  text-decoration: none;
  font-size: 1rem;
  line-height: 1;
}

.emote-row__alias-remove:hover,
.emote-row__alias-remove:focus-visible {
  background-color: color-mix(in srgb, var(--pane-link) 20%, transparent);
}

.emote-row__summary {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0.25rem 0.75rem;
}

.emote-row__name {
  font-weight: 600;
}

/* In em, not px, so larger text switches to these layouts sooner. */
@container emote-list (max-width: 41em) {
  .emote-row {
    grid-template-columns: 48px minmax(0, 1fr);
    align-items: start;
    padding: 0.85rem 0;
  }

  /* Line the image up with the first row of inputs rather than the labels.
     Archived rows have no labels, so they don't need it. */
  .emote-row:not(.emote-row--archived) .emote-row__image {
    margin-top: 1.1rem;
  }

  /* Name and code share the first line; the set gets a full line of its
     own, rather than stretching to only part of one. */
  .emote-row__field--set {
    flex-basis: 100%;
  }

  .emote-row__aside {
    grid-column: 2;
    grid-row: auto;
    justify-content: flex-start;
  }

  .emote-row__aliases {
    grid-column: 2;
  }
}

/* Too narrow for fields side by side: one field per line, full width. */
@container emote-list (max-width: 30em) {
  .emote-row__form {
    flex-direction: column;
    align-items: stretch;
  }

  .emote-row__field,
  .emote-row__field--set {
    flex: none;
  }
}

/* Very narrow: the image moves above the fields so they get the whole width. */
@container emote-list (max-width: 20em) {
  .emote-row {
    grid-template-columns: minmax(0, 1fr);
  }

  .emote-row .emote-row__image {
    margin-top: 0;
  }

  .emote-row__aside,
  .emote-row__aliases {
    grid-column: 1;
  }
}

/* ── Admin: emote upload ─────────────────── */

.emote-upload__dropzone {
  padding: 1.25rem;
  border: 2px dashed var(--input-border);
  border-radius: 8px;
  transition: border-color 0.2s, background-color 0.2s;
}

.emote-upload__dropzone--active {
  border-color: var(--pane-link);
  background-color: color-mix(in srgb, var(--pane-link) 10%, transparent);
}

.emote-upload__hint {
  margin: 0.5rem 0 0;
  font-size: 0.85rem;
  opacity: 0.85;
}

.emote-upload__status {
  margin: 0.5rem 0 0;
  font-size: 0.9rem;
  min-height: 1.4em;
}

.upload-review {
  list-style: none;
  margin: 0 0 1.5rem;
  padding: 0;
}

.upload-review__row {
  display: grid;
  grid-template-columns: 48px minmax(0, 1fr);
  gap: 0.5rem 1rem;
  padding: 0.75rem 0;
  border-bottom: 1px solid color-mix(in srgb, var(--input-border) 60%, transparent);
}

.upload-review__row:last-child {
  border-bottom: none;
}

.upload-review__preview {
  width: 48px;
  height: 48px;
  object-fit: contain;
}

.upload-review__main {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  min-width: 0;
}

.upload-review__fields {
  display: flex;
  flex-wrap: wrap;
  align-items: flex-start;
  gap: 0.5rem 0.75rem;
}

.upload-review__field {
  flex: 1 1 8rem;
  min-width: 0;
}

.upload-review__field--name {
  flex: 1.4 1 10rem;
}

.upload-review__field label {
  margin-bottom: 0.2rem;
  font-size: 0.8rem;
}

.upload-review__field input[type="text"],
.upload-review__field select {
  padding: 0.35rem 0.5rem;
  font-size: 0.95rem;
}

.upload-review__code {
  margin: 0.3rem 0 0;
  font-size: 0.85rem;
}

.upload-review__heading {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
  margin: 0;
}

.upload-review__existing {
  width: 32px;
  height: 32px;
  object-fit: contain;
}

.upload-review__choices {
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
  margin: 0;
  padding: 0;
  border: none;
}

label.upload-review__choice {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  margin: 0;
  font-size: 0.95rem;
  color: var(--pane-text);
  cursor: pointer;
}

.upload-review__choice input[type="radio"] {
  width: auto;
  margin: 0;
}

.upload-review__choices .upload-review__fields {
  margin-left: 1.6rem;
}

.upload-review__errors,
.upload-review__errors p {
  margin: 0;
  color: var(--error, var(--warning-border));
  font-size: 0.9rem;
}

/* ── Utilities ─────────────────────────────────────────── */

.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip: rect(0, 0, 0, 0);
  white-space: nowrap;
  border: 0;
}

/* ── Theme designer ────────────────────────────────────── */

.theme-designer-container {
  max-width: 1400px;
}

.theme-designer {
  display: grid;
  grid-template-columns: 380px 1fr;
  gap: 2rem;
  align-items: start;
}

.theme-designer__controls {
  position: sticky;
  top: 2rem;
  max-height: calc(100vh - 4rem);
  overflow-y: auto;
}

.theme-designer__controls details[open] > summary {
  margin-bottom: 0.75rem;
}

.theme-designer__color-group {
  margin-bottom: 0.75rem;
}

.theme-designer__color-input {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

/* No-JS: native colour input */
.theme-designer__color-input input[type="color"] {
  width: 48px;
  height: 36px;
  padding: 2px;
  border: 1px solid var(--input-border);
  border-radius: 4px;
  background-color: var(--input-bg);
  cursor: pointer;
}

.theme-designer__color-input input[type="color"]::-webkit-color-swatch-wrapper {
  padding: 2px;
}

.theme-designer__color-input input[type="color"]::-webkit-color-swatch {
  border: none;
  border-radius: 2px;
}

/* JS-enhanced: Coloris wraps the hex input in .clr-field */
.theme-designer__color-input .clr-field {
  flex: 1;
}

/* Swatch button: left-aligned, wider, inset inside the field border */
.theme-designer__color-input .clr-field button {
  left: 2px;
  right: auto;
  width: 2.8rem;
  height: calc(100% - 4px);
  border-radius: 4px 0 0 4px;
}

.theme-designer__hex-input {
  width: calc(9ch + 2.8rem);
  font-family: monospace;
  font-size: 0.85rem;
  text-transform: uppercase;
}

/* When inside Coloris wrapper, fill the flex space with padding for swatch */
.clr-field .theme-designer__hex-input {
  width: 100%;
  padding-left: calc(2.8rem + 10px);
  padding-right: 0.75rem;
  box-sizing: border-box;
}

/* Coloris picker z-index — sit above form elements */
.clr-picker {
  z-index: 9999;
}

@media (forced-colors: active) {
  .theme-designer__color-input .clr-field button {
    forced-color-adjust: none;
  }
}

.theme-designer__css-output {
  font-family: monospace;
  font-size: 0.8rem;
  resize: vertical;
  min-height: 80px;
}

/* ── Theme preview panel ───────────────────────────────── */

.theme-preview {
  background-color: var(--page-bg);
  color: var(--pane-text);
  border-radius: 8px;
  padding: 1.5rem;
  border: 1px solid var(--pane-border);
}

/* Scoped to the profile panel rather than the whole preview. At
   `.theme-preview a` this is (0,1,1), which outranks the chat mock's own
   `:where(.chat-body) a` (0,0,1) and painted every link in the mock — message
   links included — with the *profile* link colour. The mock is meant to be
   governed entirely by the real chat rules; this rule is profile-preview
   chrome and has no business reaching into it. */
.theme-preview__panel[data-preview-panel="profile"] a {
  color: var(--pane-link);
}

.theme-preview__panel[data-preview-panel="profile"] h2,
.theme-preview__panel[data-preview-panel="profile"] h3 {
  color: var(--pane-title-text);
}

/* A card__header heading (the mock's own "Header title text" h2, but also
   "Form controls" and "Buttons", which sit in a card__header same as a real
   banner does) tracks --header-title-text everywhere else in the app — see
   .card > .card__header h1, h2, h3 above. That rule ties on specificity with
   the pane-title one just above, and a tie is won by whichever comes later in
   the file, which silently gave every card__header heading in the preview the
   pane colour instead. The extra .card__header class outranks both rules on
   purpose, so this holds regardless of source order. */
.theme-preview__panel[data-preview-panel="profile"] .card__header h2,
.theme-preview__panel[data-preview-panel="profile"] .card__header h3 {
  color: var(--header-title-text);
}

.theme-preview__section {
  margin-bottom: 1.5rem;
}

/* ── Theme designer: inherit toggles ─────────────────────── */

/* The Override box always stays beside the colour's name. When space runs
   short (a narrow screen, or larger text), the name wraps onto more lines
   rather than pushing the box onto a line of its own, which broke the flow
   from name to box to colour. */
.theme-designer__color-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

.theme-designer__color-head > label:first-child {
  min-width: 0;
  overflow-wrap: anywhere;
}

.theme-designer__override-label {
  flex: none;
  font-size: 0.78rem;
  margin: 0;
  white-space: nowrap;
}

/* An inheriting row must show the colour actually in effect — that's the whole
   point of "Following X". Dimming the row used to dim the Coloris swatch with
   it, so a near-black inherited colour rendered as a washed-out grey in the
   form while the preview showed the true near-black: the form contradicted the
   preview. The swatch is pinned at full opacity so no disabled-state rule can
   distort it; inheritance is already signalled by the disabled (dimmed) hex
   text, the unticked Override box and the "Following X" hint. */
.theme-designer__color-group--inheriting .theme-designer__color-input .clr-field button {
  opacity: 1;
}

.theme-designer__inherit-hint {
  margin: 0.2rem 0 0;
  font-size: 0.78rem;
}

.theme-designer__group {
  margin-left: 0.25rem;
}

/* ── Theme designer: preview tabs ────────────────────────── */

.theme-designer__preview-tabs {
  display: flex;
  gap: 0.35rem;
  /* Inset past the panel's 8px corner radius, so the first tab meets a
     straight edge rather than sitting on the curve. */
  padding-left: 0.85rem;
  /* Flush against the preview below: the tabs have a top-only radius and read
     as tabs *of* the panel, so any gap leaves them floating above nothing. */
  margin-bottom: -1px;
}

.theme-designer__preview-tab {
  background: none;
  border: 1px solid var(--pane-border);
  border-radius: 6px 6px 0 0;
  padding: 0.4rem 0.9rem;
  font: inherit;
  font-size: 0.85rem;
  color: var(--pane-text);
  cursor: pointer;
}

.theme-designer__preview-tab--active {
  background-color: var(--pane-bg);
  color: var(--pane-title-text);
  font-weight: 600;
  /* Hides the panel's own top border behind the selected tab, joining the two
     into one shape. */
  border-bottom-color: var(--pane-bg);
}

/* The tabs are <button>s, and the base forced-colors rule gives every button
   forced-color-adjust: none, so the theme colours above would show exactly as
   written in high-contrast mode — a dark themed fill on the selected tab rather
   than a system colour. Restate them on system colours, following the app's
   forced-colors conventions: an unselected tab reads like a secondary button,
   and the selected one is filled with Highlight, as selected rows are
   elsewhere — but keeps the same CanvasText outline as its neighbours, so it
   still reads as a tab rather than a floating block of colour. :hover is
   restated too, or the base button:hover rule (0,1,1) would flip the selected
   tab back to Canvas. */
@media (forced-colors: active) {
  .theme-designer__preview-tab,
  .theme-designer__preview-tab:hover {
    background-color: Canvas;
    color: CanvasText;
    border: 1px solid CanvasText;
  }

  .theme-designer__preview-tab--active,
  .theme-designer__preview-tab--active:hover {
    background-color: Highlight;
    color: HighlightText;
    border-color: CanvasText;
  }
}

/* ── Theme designer: chat preview mock ───────────────────── */

/* The chat mock reuses the real chat class names so every colour rule applies
   to it unchanged. What doesn't transfer is the layout: the real thing fills
   the viewport and scrolls internally, which inside a preview panel would
   either collapse to nothing or run off the page. Everything below cancels
   layout only — no colours — so the mock can't drift from what chat actually
   looks like. */
/* Chat fills the whole window, so its mock bleeds to the edges of the preview
   panel rather than floating in the middle of it — cancelling .theme-preview's
   1.5rem padding, which would otherwise frame the mock in the *profile* page
   colour and imply a chat page background that no longer exists. */
.theme-preview__panel[data-preview-panel="chat"] {
  margin: -1.5rem;
}

/* .theme-preview scopes this to (0,2,0) so it beats .chat-body's
   `height: 100dvh` (0,1,0). Matching the single-class specificity wouldn't be
   enough: .chat-body is declared several hundred lines further down, so on a
   tie it wins and the mock fills the viewport. */
.theme-preview .theme-preview__chat {
  /* Height comes from the contents (see .chat-shell's min-height below) so the
     mock is just tall enough to show every element rather than running off the
     bottom of the screen.

     The profile preview paints the theme's background image on .theme-preview
     itself, so an opaque colour here keeps the image out of a chat mock that
     should never show one. Nothing of it is actually visible — the header and
     the shell cover it entirely. */
  height: auto;
  background-color: var(--chat-pane-bg);
  display: flex;
  flex-direction: column;
  /* Matches .theme-preview's own radius, less its 1px border. */
  border-radius: 7px;
  overflow: hidden;
  font-size: 0.85rem;
}

.theme-preview__chat .site-header {
  padding: 0.5rem 0.9rem;
  font-size: 0.8rem;
  flex-shrink: 0;
}

.theme-preview__chat .site-header .logo {
  font-size: 0.85rem;
}

.theme-preview__chat .site-header nav a {
  margin-left: 0;
}

/* Enough room for the rail's icons, the channel list, and a date divider plus
   one message — the shortest the mock can be and still show everything that
   can be themed. */
.theme-preview__chat .chat-shell {
  flex: 1;
  min-height: 190px;
}

/* The real rail and channel list are sized for a full window; at preview width
   they'd leave almost nothing for the messages. */
.theme-preview__chat .server-rail {
  --dot-inset: calc((44px - 28px) / 2 + 2px);
  width: 44px;
  gap: 0.5rem;
  padding: 0.85rem 0;
}

.theme-preview__chat .server-rail__icon,
.theme-preview__chat .server-rail__icon .avatar,
.theme-preview__chat .server-rail__icon .avatar--placeholder {
  width: 28px;
  height: 28px;
  min-width: 28px;
  min-height: 28px;
}

.theme-preview__chat .channel-pane {
  /* Close to the real 224px, and wide enough that the mock's longest channel
     name stays on one line — a cut-short row misreads as a styling bug
     rather than a narrow column. */
  --dot-inset: 0.6rem;
  width: 186px;
  padding: 0.85rem var(--dot-inset);
  overflow: hidden;
}

.theme-preview__chat .chat-main {
  padding: 0;
  overflow: hidden;
}

.theme-preview__chat .chat-channel-header {
  padding: 0.55rem 0.9rem 0.65rem;
}

.theme-preview__chat .chat-channel-header h1 {
  font-size: 0.95rem;
}

/* Real chat has a screenful of empty space under the newest message; without
   a floor here the mock packs the composer right up against it and reads as
   cramped. */
.theme-preview__chat .chat-messages-scroll {
  padding: 0.8rem 0.9rem;
  /* Comfortably taller than the mock's own content (~127px with the date
     divider), so the empty space below the last message is real rather than a
     rounding error. */
  min-height: 190px;
  overflow: hidden;
}

.theme-preview__chat .chat-message__avatar .avatar,
.theme-preview__chat .chat-message__avatar .avatar--placeholder {
  width: 26px;
  height: 26px;
  min-width: 26px;
  min-height: 26px;
}

.theme-preview__chat .composer {
  padding: 0.6rem 0.9rem 0.75rem;
}

.theme-preview__chat .composer-input-row textarea {
  min-height: 2rem;
  padding: 0.35rem 0.5rem;
  font-size: 0.8rem;
}

.theme-preview__chat .composer-send {
  width: 2rem;
  height: 2rem;
}

.theme-preview__section:last-child {
  margin-bottom: 0;
}

.theme-preview__buttons {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
}

.theme-preview__header {
  background-color: var(--header-bg);
  color: var(--header-text);
  border: 1px solid var(--pane-border);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
}

.theme-preview__logo {
  font-weight: 700;
  color: var(--header-title-text);
  display: flex;
  align-items: center;
  gap: 0.4rem;
}

.theme-preview__logo svg {
  width: 1.4em;
  height: 1.4em;
}

.theme-preview__header nav a {
  color: var(--header-link);
  text-decoration: none;
  margin-left: 1rem;
  font-size: 0.9rem;
}

.theme-bg-preview {
  display: block;
  border-radius: 4px;
  border: 1px solid var(--pane-border);
  object-fit: cover;
  margin-top: 0.5rem;
}

@media (forced-colors: active) {
  body {
    background-image: none !important;
  }
}

/* ── Import theme dialog ────────────────────────────────── */



.import-dialog {
  background-color: var(--pane-bg);
  color: var(--pane-text);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  padding: 2rem;
  max-width: 560px;
  width: 90vw;
}

.import-dialog::backdrop {
  background-color: color-mix(in srgb, black 60%, transparent);
}

.import-dialog h2 {
  color: var(--pane-title-text);
  margin-top: 0;
  margin-bottom: 0.5rem;
}

.import-dialog p {
  margin-top: 0;
  margin-bottom: 0.75rem;
}

.import-dialog__textarea {
  font-family: monospace;
  font-size: 0.8rem;
  width: 100%;
  box-sizing: border-box;
  resize: vertical;
  min-height: 200px;
  background-color: var(--input-bg);
  color: var(--input-text);
  border: 1px solid var(--input-border);
  border-radius: 4px;
  padding: 0.5rem;
}

.import-dialog__error {
  color: var(--alert-text);
  background-color: var(--alert-bg);
  border: 1px solid var(--alert-border);
  border-radius: 4px;
  padding: 0.5rem 0.75rem;
  font-size: 0.9rem;
  margin-top: 0.5rem;
  margin-bottom: 0;
}

.import-dialog__error:empty {
  display: none;
}

.import-dialog__actions {
  display: flex;
  gap: 0.5rem;
  margin-top: 1rem;
  justify-content: flex-end;
}

@media (forced-colors: active) {
  .import-dialog {
    border: 2px solid ButtonText;
  }
}

/* Responsive: stack on narrow screens */
@media (max-width: 900px) {
  .theme-designer {
    grid-template-columns: 1fr;
  }

  .theme-designer__controls {
    position: static;
    max-height: none;
  }
}

@media (max-width: 600px) {
  .theme-designer-container {
    max-width: 100%;
  }
}

/* ── Duplication wizard ──────────────────────────────────── */
.duplicate-comparison {
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 1rem;
  margin: 1rem 0;
}

.duplicate-comparison__card {
  padding: 1rem;
  border: 1px solid var(--pane-border);
  border-radius: 0.5rem;
  background: var(--pane-bg);
}

.duplicate-comparison__item {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.duplicate-comparison__description {
  font-size: 0.875rem;
  color: var(--pane-text);
  opacity: 0.8;
}

.radio-group {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 0.5rem;
  margin-bottom: 0.75rem;
}

.radio-group input[type="radio"] {
  flex-shrink: 0;
}

.radio-group label {
  flex: 1;
}

.radio-group .form-hint {
  width: 100%;
  margin-top: 0.25rem;
  margin-bottom: 0;
}

@media (max-width: 600px) {
  .duplicate-comparison {
    grid-template-columns: 1fr;
  }
}

@media (forced-colors: active) {
  .duplicate-comparison__card {
    border-color: CanvasText;
  }
  .radio-group input[type="radio"] {
    accent-color: Highlight;
  }
}

/* ── Label badges ───────────────────────────────────────── */
.label-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-top: 0.25rem;
}

.label-badge {
  display: inline-block;
  padding: 0.125rem 0.5rem;
  border-radius: 999px;
  font-size: 0.75rem;
  background: color-mix(in srgb, var(--primary-button-bg) 60%, transparent);
  color: var(--primary-button-text);
  border: 1px solid color-mix(in srgb, var(--primary-button-border) 40%, transparent);
}

.label-badge--sidebar {
  font-size: 0.65rem;
  padding: 0.1rem 0.35rem;
  font-weight: normal;
}

@media (forced-colors: active) {
  .label-badge {
    border: 1px solid CanvasText;
    background: Canvas;
    color: CanvasText;
  }
}

/* ── Label filter bar ───────────────────────────────────── */
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  margin-bottom: 1rem;
}

.filter-bar__label {
  font-size: 0.875rem;
  color: var(--pane-text);
}

.filter-bar__clear {
  font-size: 0.8rem;
  color: var(--pane-link);
  text-decoration: underline;
}

/* ── Theme credit footer bar ─────────────────────────────── */
.theme-credit {
  background-color: var(--pane-bg);
  border-top: 1px solid var(--pane-border);
  padding: 0.75rem 2rem;
  text-align: center;
  font-size: 0.85rem;
  color: var(--pane-text);
}

.theme-credit__name {
  font-weight: 600;
}

.theme-credit a {
  color: var(--pane-link);
  font-weight: bold;
}

@media (forced-colors: active) {
  .theme-credit {
    color: CanvasText;
    border-top: 1px solid CanvasText;
  }
  .theme-credit a {
    color: LinkText;
  }
}

/* ── Theme index: rows ──────────────────────────────────── */

.theme-row {
  display: flex;
  align-items: flex-start;
  gap: 1rem;
  padding: 0.875rem 2rem;
  border-bottom: 1px solid var(--pane-border);
}

.theme-row:last-child {
  border-bottom: none;
}

.theme-row--active {
  border-left: 3px solid var(--pane-link);
  padding-left: calc(2rem - 3px);
}

.theme-row__info {
  flex: 1;
  min-width: 0;
}

.theme-row__name-line {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
}

.theme-row__name-line h3 {
  margin: 0;
  font-size: 1rem;
}

.theme-row__credit {
  font-size: 0.85rem;
  margin: 0.15rem 0 0;
}

.theme-row__notes {
  font-size: 0.85rem;
  font-style: italic;
  margin: 0.2rem 0 0;
  white-space: pre-wrap;
}

.theme-card__notes {
  white-space: pre-wrap;
}

.theme-row__tags {
  display: flex;
  flex-wrap: wrap;
  gap: 0.3rem;
  margin-top: 0.4rem;
}

.theme-row__end {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-shrink: 0;
  padding-top: 0.1rem;
}

/* ── Theme swatches ─────────────────────────────────────── */

.theme-swatches {
  display: flex;
  flex-shrink: 0;
}

.theme-swatches__dot {
  width: 18px;
  height: 18px;
  border-radius: 0;
  border: 1px solid color-mix(in srgb, var(--pane-border) 60%, transparent);
  margin-left: -1px;
}

.theme-swatches__dot:first-child {
  border-radius: 3px 0 0 3px;
  margin-left: 0;
}

.theme-swatches__dot:last-child {
  border-radius: 0 3px 3px 0;
}

@media (forced-colors: active) {
  .theme-swatches {
    display: none;
  }
}

/* ── Action dropdown ────────────────────────────────────── */

.action-dropdown {
  position: relative;
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
}

.action-dropdown__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  padding: 0.35rem 0.65rem;
  border-radius: 4px;
  cursor: pointer;
  color: var(--secondary-button-text);
  background-color: var(--secondary-button-bg);
  border: 1px solid var(--secondary-button-border);
  font-size: 0.85rem;
  font-weight: 500;
  line-height: 1.4;
  list-style: none;
  user-select: none;
  white-space: nowrap;
}

.action-dropdown__trigger:hover {
  background-color: color-mix(in srgb, var(--secondary-button-bg) 80%, transparent);
}

.action-dropdown__trigger::after {
  content: " ▾";
  font-size: 0.7rem;
  opacity: 0.8;
}

.action-dropdown__trigger::-webkit-details-marker,
.action-dropdown__trigger::marker {
  display: none;
}

.action-dropdown__menu {
  position: absolute;
  right: 0;
  top: calc(100% + 4px);
  z-index: 10;
  min-width: 170px;
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  padding: 0.35rem 0;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--pane-border) 25%, transparent);
}

/* The domain switcher sits at the left edge of the header now, next to the
   logo — right: 0 (the default, for menus that hang off a right-aligned nav
   item) would make it spill further left past its trigger. Open it flush
   with the trigger's left edge instead. */
.site-header__brand .action-dropdown__menu {
  left: 0;
  right: auto;
}

.action-dropdown__item {
  display: block;
  padding: 0.5rem 1rem;
  color: var(--pane-text);
  text-decoration: none;
  font-size: 0.9rem;
  white-space: nowrap;
}

.action-dropdown__item:hover {
  background-color: color-mix(in srgb, var(--pane-text) 8%, transparent);
  text-decoration: none;
  color: var(--pane-text);
}

.action-dropdown__divider {
  border: none;
  border-top: 1px solid var(--pane-border);
  margin: 0.35rem 0;
}

@media (forced-colors: active) {
  .action-dropdown__trigger {
    border: 1px solid CanvasText;
  }

  .action-dropdown__trigger:hover {
    outline: 1px solid CanvasText;
  }

  .action-dropdown__menu {
    border: 2px solid CanvasText;
  }

  .action-dropdown__divider {
    border-top-color: CanvasText;
  }

  .action-dropdown__item:hover {
    font-weight: bold;
    background-color: Highlight;
  }
}

/* ── Theme heading card ─────────────────────────────────── */

.theme-heading__bar {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.theme-heading__active {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  flex: 1;
  min-width: 200px;
}

.theme-heading__active-label {
  font-size: 0.95rem;
}

.theme-heading__actions {
  display: flex;
  gap: 0.5rem;
  flex-wrap: wrap;
  flex-shrink: 0;
}

.card > .filter-bar {
  margin-bottom: 0;
}

/* ── Theme section cards (collapsible) ──────────────────── */

/* Padding inherited from .card — do NOT override to 0 or the
   card__header negative-margin trick breaks. Instead, .theme-list
   uses negative margins to bleed its rows to the card edges. */
.theme-section-card {
  /* inherits .card styles — no overrides needed */
}

.theme-section-card__header {
  cursor: pointer;
  list-style: none;
  user-select: none;
}

.card__header.theme-section-card__header {
  margin-bottom: 0;
}

.theme-section-card__header::-webkit-details-marker,
.theme-section-card__header::marker {
  display: none;
}

.theme-section-card__header h2 {
  margin: 0;
}

.theme-section-card__header h2::before {
  content: "▾ ";
  opacity: 0.7;
}

details.theme-section-card:not([open]) .theme-section-card__header h2::before {
  content: "▸ ";
}

details.theme-section-card:not([open]) {
  padding-bottom: 0;
}

details.theme-section-card:not([open]) .theme-section-card__header {
  margin-bottom: 0;
  border-radius: 7px;
}

/* Bleed theme rows to the card edges, cancelling the card padding on left,
   right, and bottom. Do NOT use overflow:hidden — it clips dropdowns. */
.theme-list {
  margin: 0 calc(-1 * var(--card-padding)) calc(-1 * var(--card-padding));
}

.theme-section-card__empty {
  margin: 1.5rem 0 0 0;
  font-size: 0.95rem;
}

/* ---- Chat ---- */

/* The shared `body` rule only sets min-height: 100vh, which lets the whole
   page grow past the viewport for tall content — fine for ordinary pages,
   but it means .chat-shell's flex:1 never gets a definite height to fill,
   so its own overflow:auto areas (the message history) never actually
   engage and the composer at the bottom ends up pushed off-screen instead.
   Pin chat pages to exactly the viewport height so the flex chain resolves
   properly and only the message history scrolls. */
.chat-body {
  height: 100vh;
  height: 100dvh;
  /* No chat-specific page background or text colour: the panes fill the
     window, so body is only visible behind the cards on the plain chat pages
     (server list, settings, invites), which keep the profile --page-bg and
     --pane-text that every other page in the app uses. The chat view sets its
     own text colour below. */
}

/* The chat page itself, rather than the theme designer's mock of it (which
   shares the class). The shared body rule's min-height: 100vh outranks the
   height above, and on a phone 100vh is the screen with the browser's bars
   hidden — taller than what's visible while they show. The page then
   scrolled as a whole: the composer slid under the browser's bottom bar, and
   the header scrolled off the top with no way back, since scrolling up only
   scrolls (and loads more of) the message history. With no minimum, the
   page is exactly the visible screen, and only the panes inside scroll. */
body.chat-body {
  min-height: 0;
}

/* The site header is the same markup in both layouts, so chat's version of it
   is a scoped override rather than a separate component. Specificity (0,2,0)
   beats the base .site-header rules (0,1,0) without !important; the nav rules
   below match the base selectors' shape for the same reason. */
.chat-body .site-header {
  background-color: var(--chat-header-bg);
  /* Nothing in the bar falls back to this — the logo, the domain switcher and
     Sign out all set their own colour — but without it anything added later
     would inherit a profile colour into chat chrome. There's no separate
     "header body text" key: the bar has no body text to colour, so this and
     the popover banner below reuse the title colour, which is the one already
     known to read against --chat-header-bg. */
  color: var(--chat-header-title-text);
  border-bottom-color: var(--chat-divider);
}

.chat-body .site-header .logo {
  color: var(--chat-header-title-text);
}

/* Chat fills the window, so every line the header takes is one less line of
   messages: thinner than the profile pages' header, with a smaller Chat menu
   button. */
.chat-body .site-header {
  padding-block: 0.2rem;
}

.chat-body .site-header .action-dropdown__trigger {
  padding: 0.1rem 0.5rem;
}

/* The profile pages stack the header on phones; chat keeps it to one row,
   wrapping only if it really has to. */
@media (max-width: 600px) {
  .chat-body .site-header {
    flex-direction: row;
    flex-wrap: wrap;
    text-align: left;
  }
}

.chat-body .site-header nav a,
.chat-body .site-header nav .link-button {
  color: var(--chat-header-link);
}

/* Media queries don't add specificity, so the rule above (0,3,1) outranks the
   base forced-colors override on .site-header nav .link-button (0,2,1) and
   would keep Sign out in the themed colour even in forced-colors mode. Same
   fix as the base rule, one level deeper. */
@media (forced-colors: active) {
  .chat-body .site-header nav .link-button {
    color: CanvasText;
  }
}

/* Shared components, scoped to the chat *view* rather than the chat layout.
   .chat-body also wraps ordinary pages — the server list, server and channel
   settings, the invite page — whose cards, headings and links are plain
   profile-page furniture. Colours picked to look right on the message pane
   look wrong there (a message-author colour chosen against a light chat pane
   landing on an invite card's heading, say), so those pages keep the profile
   palette and only the chat view proper is repainted.

   Elements whose colours aren't part of the chat palette (buttons, flash
   messages, form labels) stay shared everywhere. */
.chat-channel h1,
.chat-channel h2,
.chat-channel h3 {
  color: var(--chat-pane-title-text);
}

.chat-channel .subtitle {
  color: color-mix(in srgb, var(--chat-pane-text) 80%, transparent);
}

/* :where() keeps this at the base `a` rule's specificity (0,0,1) rather than
   (0,1,1). At (0,1,1) it would outrank every single-class rule that colours a
   link in chat — .channel-pane__server-name, .channel-pane__add-channel,
   .server-rail__icon, .chat-channel-back — and flatten them all to the link
   colour. This way it only replaces the global default, exactly as intended. */
:where(.chat-channel) a {
  color: var(--chat-pane-link);
}

.chat-channel .spoiler {
  background-color: var(--chat-spoiler);
  border-color: var(--chat-spoiler);
}

.chat-channel .spoiler:focus-visible {
  outline-color: var(--chat-spoiler);
}

.chat-channel .spoiler--revealed {
  background-color: color-mix(in srgb, var(--chat-spoiler) 10%, transparent);
}

/* In forced-colors mode the base .spoiler rules keep a spoiler legible with
   system colours, under forced-color-adjust: none, so author colours apply as
   written. The chat-scoped rules above are more specific and would win over
   them regardless of the media query: a hidden spoiler's edge would match its
   background and a revealed one would stay in theme colours. These mirror the
   base rules, with the chat spoiler colour standing in for the profile one. */
@media (forced-colors: active) {
  .chat-channel .spoiler {
    forced-color-adjust: none;
    background-color: var(--chat-spoiler);
    color: transparent;
    border: 1px solid CanvasText;
  }

  .chat-channel .spoiler:focus-visible {
    outline-color: CanvasText;
  }

  .chat-channel .spoiler--revealed {
    background-color: Canvas;
    color: CanvasText;
    border-color: CanvasText;
  }
}

/* `inherit` rather than a fixed colour: this outranks the per-region rules
   that set `color` on .server-rail, .channel-pane and .composer, so pinning it
   to the pane colour left a server with no avatar drawing its placeholder logo
   in the message-pane colour while sitting on the rail. Inheriting means each
   placeholder picks up the surface it's actually on, and the mini-profile
   header below still overrides it explicitly. */
.chat-body .server-rail .avatar--placeholder,
.chat-body .channel-pane .avatar--placeholder,
.chat-channel .avatar--placeholder,
.chat-body .mini-profile-popover .avatar--placeholder {
  color: inherit;
  border-color: var(--chat-avatar-placeholder-border);
}

/* The popover you get by clicking a message author — chat-specific markup that
   only ever renders from a message, so it needs no further scoping. Its header
   follows the chat header colours; this covers its body, border and shadow. */
.chat-body .mini-profile-popover {
  background-color: var(--chat-pane-bg);
  color: var(--chat-pane-text);
  border-color: var(--chat-divider);
  box-shadow: 0 8px 24px color-mix(in srgb, var(--chat-divider) 60%, transparent);
}

.chat-body .mini-profile__header {
  background-color: var(--chat-header-bg);
}

.chat-body .mini-profile__header .mini-profile__name {
  color: var(--chat-header-title-text);
}

.chat-body .mini-profile__header .subtitle,
.chat-body .mini-profile__header .tag-line,
.chat-body .mini-profile__header .pronouns {
  color: color-mix(in srgb, var(--chat-header-title-text) 80%, transparent);
}

.chat-body .mini-profile__header a {
  color: var(--chat-header-link);
}

.chat-body .mini-profile__header .avatar--placeholder {
  color: var(--chat-header-title-text);
  border-color: color-mix(in srgb, var(--chat-header-title-text) 50%, transparent);
}

/* The two dropdowns that belong to chat chrome: the header's domain switcher
   and the composer's "posting as" picker. Dropdowns on the settings pages are
   left alone with the rest of that furniture. */
.chat-body .site-header .action-dropdown__menu,
.chat-channel .action-dropdown__menu {
  background-color: var(--chat-pane-bg);
  border-color: var(--chat-divider);
  box-shadow: 0 4px 12px color-mix(in srgb, var(--chat-divider) 25%, transparent);
}

.chat-body .site-header .action-dropdown__item,
.chat-body .site-header .action-dropdown__item:hover,
.chat-channel .action-dropdown__item,
.chat-channel .action-dropdown__item:hover {
  color: var(--chat-pane-text);
}

.chat-body .site-header .action-dropdown__item:hover,
.chat-channel .action-dropdown__item:hover {
  background-color: color-mix(in srgb, var(--chat-pane-text) 8%, transparent);
}

.chat-body .site-header .action-dropdown__divider,
.chat-channel .action-dropdown__divider {
  border-top-color: var(--chat-divider);
}

/* The composer's "posting as" options are action-dropdown items too, so the
   generic channel rule above painted their names in the message-pane colour
   and the Chat footer text colour never reached them. One more ancestor class
   outranks it without relying on source order. */
.chat-channel .composer .action-dropdown__item,
.chat-channel .composer .action-dropdown__item:hover {
  color: var(--chat-composer-text);
}

.chat-channel .composer .action-dropdown__item:hover {
  background-color: color-mix(in srgb, var(--chat-composer-text) 8%, transparent);
}

/* Forced-colors parity for the chat dropdowns. The rules above outrank the
   base forced-colors ones for these parts, so a hovered item would lose its
   Highlight and the menu and divider their system-colour edges. */
@media (forced-colors: active) {
  .chat-body .site-header .action-dropdown__menu,
  .chat-channel .action-dropdown__menu {
    border: 2px solid CanvasText;
  }

  .chat-body .site-header .action-dropdown__divider,
  .chat-channel .action-dropdown__divider {
    border-top-color: CanvasText;
  }

  .chat-body .site-header .action-dropdown__item:hover,
  .chat-channel .action-dropdown__item:hover,
  .chat-channel .composer .action-dropdown__item:hover {
    font-weight: bold;
    background-color: Highlight;
  }
}

.server-list {
  list-style: none;
  padding: 0;
  margin: 0;
  display: flex;
  flex-direction: column;
  gap: 0.4rem;
}

.server-list__link {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  padding: 0.5rem 0.75rem;
  border-radius: 6px;
  color: var(--pane-text);
  text-decoration: none;
}

.server-list__link:hover {
  background-color: color-mix(in srgb, var(--pane-text) 8%, transparent);
}

.server-list__link .avatar {
  width: 32px;
  height: 32px;
  min-width: 32px;
  min-height: 32px;
}

.server-list__text {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1 1 auto;
}

.server-list__title-row {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  min-width: 0;
}

.server-list__name {
  font-weight: 600;
  flex-shrink: 0;
  overflow-wrap: anywhere;
}

.server-list__description {
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
  min-width: 0;
  flex: 1 1 auto;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.server-list__subtitle {
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.chat-shell {
  flex: 1;
  min-height: 0;
  display: flex;
}

.server-rail {
  /* From the rail's edge to the inside of the icons' 2px border, which is
     what the dot is positioned against (see .unread-dot) */
  --dot-inset: calc((72px - 42px) / 2 + 2px);
  width: 72px;
  flex-shrink: 0;
  background-color: var(--chat-rail-bg);
  color: var(--chat-rail-text);
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.6rem;
  padding: 0.6rem 0;
  overflow-y: auto;
}

.server-rail__icon {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 42px;
  height: 42px;
  flex-shrink: 0;
  border-radius: 25%;
  color: var(--chat-rail-text);
  text-decoration: none;
  border: 2px solid transparent;
}

/* Kept in sync with the avatar's own shape modifier (avatar_shape_class),
   so the ring below always hugs the same outline as the image inside it —
   a mismatched fixed radius here made the ring look broken on square
   avatars. */
.server-rail__icon--circle {
  border-radius: 50%;
}

.server-rail__icon--square {
  border-radius: 0;
}

.server-rail__icon .avatar {
  width: 42px;
  height: 42px;
  min-width: 42px;
  min-height: 42px;
}

.server-rail__icon:hover {
  border-color: color-mix(in srgb, var(--chat-rail-text) 40%, transparent);
}

.server-rail__icon--active {
  border-color: transparent;
  box-shadow: 0 0 0 2px var(--chat-rail-bg), 0 0 0 4px var(--chat-rail-active);
}

.server-rail__icon--add {
  border: 1px dashed color-mix(in srgb, var(--chat-rail-text) 40%, transparent);
  color: var(--chat-rail-link);
  font-size: 1.3rem;
  text-decoration: none;
}

/* Unread markers sit on the left edge of the rail or the channel list, level
   with their server or channel, with their outer half cut off by the edge:
   a half-dot that's easy to spot down the side without taking any room from
   the names. The edge clips it because both panes scroll (overflow), and
   --dot-inset is how far the item is from that edge, so the dot can reach
   back to it. In rem, so it grows with the text. */
.unread-dot {
  --unread-dot-size: 0.6rem;
  position: absolute;
  top: 50%;
  left: calc(-1 * var(--dot-inset) - var(--unread-dot-size) / 2);
  transform: translateY(-50%);
  width: var(--unread-dot-size);
  height: var(--unread-dot-size);
  border-radius: 50%;
  background-color: var(--chat-sidebar-unread-dot);
}

/* Same specificity as .unread-dot above and declared after it, so the rail's
   own colour wins. The two sit on different surfaces — chat_rail_bg and
   chat_sidebar_bg — which a theme may colour very differently. */
.unread-dot--rail {
  background-color: var(--chat-rail-unread-dot);
}

/* Arrived live, and waiting a moment in case it's cleared again straight
   away (chat_unread_controller.js). Hidden from screen readers too, along with
   its "(unread)" text. */
.unread-dot--pending {
  visibility: hidden;
}

@media (forced-colors: active) {
  /* Background colors are the first thing forced-colors mode flattens away,
     so without an explicit border the rail is indistinguishable from the
     panes either side of it. Same story for the active-server ring and the
     unread dots below — box-shadow is dropped outright in forced-colors
     mode, and background-color fills get replaced with the page background
     (Canvas), unless the color is an explicit system keyword. */
  .server-rail {
    border-right: 1px solid CanvasText;
  }

  .server-rail__icon--active {
    outline: 2px solid CanvasText;
    outline-offset: 2px;
  }

  .unread-dot {
    background-color: CanvasText;
  }
}

.channel-pane {
  /* From the pane's edge to the channel rows (see .unread-dot) */
  --dot-inset: 0.5rem;
  width: 224px;
  flex-shrink: 0;
  background-color: var(--chat-sidebar-bg);
  color: var(--chat-sidebar-text);
  border-right: 1px solid var(--chat-divider);
  padding: 0.6rem var(--dot-inset);
  overflow-y: auto;
  line-height: 1.4;
}

/* The channel list reuses the group/profile sidebar's tree-guide-line
   markup for visual consistency, but a channel list has no actual
   hierarchy to show — the connector lines just imply a nesting that isn't
   there, so drop them here while keeping the rest of the leaf styling. */
.channel-pane .sidebar-tree__children > li::before,
.channel-pane .sidebar-tree__children > .sidebar-tree__leaf::after {
  display: none;
}

/* The shared --st-indent left-padding exists to make room for the
   tree-guide line — with that line hidden above, it's just dead space. */
.channel-pane .sidebar-tree__children {
  padding-left: 0;
}

/* What each row's unread dot is positioned against */
.channel-pane .sidebar-tree__children > li {
  position: relative;
}

/* The channel rows reuse .sidebar-tree__leaf, which lives with the profile
   sidebar styles and so wasn't caught by the chat palette conversion. Channel
   names have always been link-coloured (they're bare <a>s with no colour rule
   of their own), so they follow --chat-sidebar-link to keep that; the hover
   and active tints follow --chat-sidebar-text. Both defaults match what the
   profile variables gave them before. */
.channel-pane .sidebar-tree__leaf {
  color: var(--chat-sidebar-link);
  flex-wrap: nowrap;
  align-items: center;
  padding: 0.3rem 0.4rem;
}

/* One line each, cut short with an ellipsis. The link's title shows the full
   name on hover; keyboard focus shows it by letting it wrap, since a title
   tooltip doesn't appear for the keyboard. (On a touch screen, tapping opens
   the channel, whose header always shows the name in full.) */
.channel-pane__name {
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.channel-pane .sidebar-tree__leaf:focus-visible .channel-pane__name {
  white-space: normal;
  overflow-wrap: anywhere;
}

.channel-pane .sidebar-tree__leaf:hover {
  background-color: color-mix(in srgb, var(--chat-sidebar-text) 10%, transparent);
}

.channel-pane .sidebar-tree__leaf--active {
  background-color: color-mix(in srgb, var(--chat-sidebar-text) 20%, transparent);
}

/* The base forced-colors rule marks the active row with Highlight and
   HighlightText; these channel-pane rules are more specific and would replace
   that with the themed tint, losing the active row's contrast. Normal and
   hovered rows need no mirror: the base doesn't give them system colours in
   forced-colors mode either, so they already behave as they do elsewhere. */
@media (forced-colors: active) {
  /* Already in system colours, so nothing for the browser to adjust. Left to
     it, it puts a Canvas backplate behind the name, which hides
     HighlightText in themes where the two match. */
  .channel-pane .sidebar-tree__leaf--active {
    background: Highlight;
    color: HighlightText;
    forced-color-adjust: none;
  }
}

.channel-pane__server-name {
  display: block;
  font-weight: 700;
  color: var(--chat-sidebar-title-text);
  text-decoration: none;
  margin-bottom: 0.4rem;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.channel-pane__server-name:hover {
  text-decoration: underline;
}

/* In full while focused, like the channel names (the server's page shows it in
   full too) */
.channel-pane__server-name:focus-visible {
  white-space: normal;
  overflow-wrap: anywhere;
}

.channel-pane__add-channel {
  display: block;
  margin-top: 0.4rem;
  padding-left: 0.4rem;
  font-size: 0.85rem;
  color: var(--chat-sidebar-link);
  text-decoration: none;
}

.channel-pane__add-channel:hover {
  text-decoration: underline;
}

.chat-main {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
  padding: 1.5rem;
  overflow-y: auto;
  /* Left transparent (rather than --pane-bg) so plain pages — server list,
     server/channel settings, invite page — show the themed page background
     (colour and background-image) behind their .card content, same as every
     other themed page in the app. */
}

/* .chat-channel (channel#show) owns its own padding per-section so the
   composer can sit flush against the bottom of the pane — cancel .chat-main's
   padding only when it's present, leaving plain pages (server list/settings
   etc.) with their normal padding.

   It also restores the solid pane background that .chat-main otherwise
   leaves transparent: without it, the message pane falls through to the
   bare page background — which, depending on the active theme, can end up
   matching --spoiler exactly (spoilers are styled background === border ===
   --spoiler), making spoiler text completely invisible instead of an
   obviously-clickable redacted box. */
.chat-main:has(.chat-channel) {
  padding: 0;
  overflow-y: hidden;
  background-color: var(--chat-pane-bg);
}

/* Wraps the header, the scrollable history, and the composer for a single
   channel — a plain flex column so the composer can stay pinned to the
   bottom (flex-shrink: 0) while only the history area scrolls. */
.chat-channel {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  color: var(--chat-pane-text);
  /* Tighter than the page-wide 1.6, which at large text sizes spends a lot of
     height on the gaps between lines */
  line-height: 1.4;
}

.chat-channel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.35rem 1rem;
  /* Defaults to the message pane's own colour, so this reads as one surface
     with the history below it until a designer parts the two. */
  background-color: var(--chat-topbar-bg);
  /* Inherited by the channel description, which has no colour rule of its own. */
  color: var(--chat-topbar-text);
  border-bottom: 1px solid var(--chat-divider);
  flex-shrink: 0;
}

/* Beats .chat-body h1 (0,1,1), which would otherwise paint this the message
   pane's title colour — the header is its own surface with its own keys. */
.chat-body .chat-channel-header h1 {
  color: var(--chat-topbar-title-text);
}

/* Never squeezed by the subtitle or description beside it, but never wider
   than the header either: a name too long for the line wraps, rather than
   running underneath the Channel settings button. */
.chat-channel-header h1 {
  margin: 0;
  font-size: 1.15rem;
  flex-shrink: 0;
  max-width: 100%;
  overflow-wrap: anywhere;
}

.chat-channel-header__actions {
  display: flex;
  gap: 0.75rem;
  flex-shrink: 0;
}

.chat-channel-header__title {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  min-width: 0;
  flex: 1;
}

.chat-channel-header__title-column {
  display: flex;
  flex-direction: column;
  min-width: 0;
  flex: 1;
}

/* Name, subtitle and description share one line rather than stacking — the
   subtitle and description are the ones that give way (ellipsis, with the
   full text in their title) since the channel name is what identifies the
   channel and should never get cut off. */
.chat-channel-header__title-text {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  min-width: 0;
}

.chat-channel-header__title-text .subtitle,
.chat-channel-header__title-text .description {
  margin: 0;
  min-width: 0;
  overflow: hidden;
  white-space: nowrap;
  text-overflow: ellipsis;
}

.chat-channel-header__title-text .subtitle {
  flex: 0 1 auto;
}

/* No width of its own (a zero basis), so it takes only the room left over:
   it gives way completely before the subtitle starts to */
.chat-channel-header__title-text .description {
  flex: 1 1 0;
}

/* The alternative text after the slash keeps screen readers from announcing
   the dash */
.chat-channel-header__title-text .subtitle::before {
  content: "– " / "";
}

/* Only there while something's cut short (truncated_text_controller.js) */
.chat-channel-header__more {
  flex-shrink: 0;
  font-size: 0.85rem;
  color: var(--chat-topbar-text);
  text-decoration: underline;
}

/* Not the filled hover of the global button:hover rule; it's a text link */
.chat-channel-header__more:hover {
  background: none;
}

/* Opened with "more": everything in full, wrapping onto as many lines as it
   needs. A description too long for the room beside the name moves to a line
   of its own rather than wrapping in a thin column. */
.chat-channel-header__title-text--expanded {
  flex-wrap: wrap;
}

.chat-channel-header__title-text--expanded .subtitle,
.chat-channel-header__title-text--expanded .description {
  white-space: normal;
  overflow-wrap: anywhere;
}

.chat-channel-header__title-text--expanded .description {
  flex: 1 1 12rem;
}

.chat-body .chat-channel-header .subtitle {
  color: color-mix(in srgb, var(--chat-topbar-text) 80%, transparent);
}

/* Hidden by default — only shown at the small-screen breakpoint below,
   where the server rail and channel list collapse away and this is the
   only way back to them. */
.chat-channel-back {
  display: none;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border-radius: 6px;
  color: var(--chat-topbar-text);
  text-decoration: none;
  font-size: 1.5rem;
  line-height: 1;
}

.chat-channel-back:hover {
  background-color: color-mix(in srgb, var(--chat-topbar-text) 10%, transparent);
}

/* Mobile (600px and below): collapse to a single pane at a time, matching
   Discord's small-screen behaviour. Reading a specific channel's messages
   (.chat-shell--channel-open, set by the layout whenever @in_channel_chat is
   true) hides the server rail and channel list entirely and shows the back
   arrow above to return to them; everywhere else in chat is unaffected. */
@media (max-width: 600px) {
  .chat-shell--channel-open .server-rail,
  .chat-shell--channel-open .channel-pane {
    display: none;
  }

  .chat-channel-back {
    display: flex;
  }

  /* No room beside the name on a phone, so the subtitle and description go
     on a line of their own under it */
  .chat-channel-header__title-text {
    flex-wrap: wrap;
    row-gap: 0;
  }

  .chat-channel-header h1 {
    flex-basis: 100%;
  }

  /* Starting a line, the dash has nothing to separate */
  .chat-channel-header__title-text .subtitle::before {
    content: none;
  }
}

.chat-messages-scroll {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 0.6rem 1rem;
}

/* turbo-frame has no default display value browsers agree on. `display:
   contents` would make it transparent to layout, but Turbo's loading="lazy"
   detects scroll visibility with an IntersectionObserver on the frame
   element itself — an element with `display: contents` generates no box at
   all, so it can never intersect the viewport and the lazy load silently
   never fires. Use a plain block instead, and give messages/dividers their
   own margin (below) rather than relying on the parent's flex `gap`, so
   spacing still looks right regardless of the frame wrapping some of them. */
turbo-frame {
  display: block;
}

/* An unloaded lazy turbo-frame is empty, so as a plain block it collapses to
   ~0 height — an easy target to scroll straight past without it ever truly
   crossing into the viewport for the IntersectionObserver driving
   loading="lazy" to catch. Give it a reliable minimum height to scroll onto. */
#older-messages:not([complete]) {
  min-height: 40px;
}

.chat-messages {
  display: flex;
  flex-direction: column;
}

.chat-message,
.chat-date-divider {
  margin-bottom: 0.7rem;
}

.chat-messages > :last-child {
  margin-bottom: 0;
}

/* Gone once the first message arrives, whether live or just sent */
.chat-messages:has(.chat-message) > .chat-messages__empty {
  display: none;
}

.chat-date-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-top: 0.5rem;
  color: color-mix(in srgb, var(--chat-pane-text) 65%, transparent);
  font-size: 0.76rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.04em;
}

.chat-date-divider::before,
.chat-date-divider::after {
  content: "";
  flex: 1;
  /* Same muted, translucent tone as the sidebar tree-guide lines, rather
     than the harder --pane-border — and a border (not a background bar) so
     the same CanvasText override used for tree-guide lines elsewhere in
     forced-colors mode applies here too. */
  border-top: 1px solid var(--chat-tree-guide);
}

@media (forced-colors: active) {
  .chat-date-divider::before,
  .chat-date-divider::after {
    border-color: CanvasText;
  }
}

.chat-message {
  display: flex;
  gap: 0.65rem;
}

.chat-message__avatar .avatar {
  width: 34px;
  height: 34px;
  min-width: 34px;
  min-height: 34px;
}

.chat-message__content {
  flex: 1;
  min-width: 0;
}

.chat-message__who {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: wrap;
}

/* Name – pronouns – time, so they read as one line of who and when. The
   alternative text after the slash keeps screen readers from announcing the
   dashes. */
.chat-message__pronouns::before,
.chat-message__time::before {
  content: "– " / "";
  font-style: normal;
}

.chat-message__avatar-trigger,
.chat-message__name-link {
  background: none;
  border: none;
  margin: 0;
  padding: 0;
  font: inherit;
  color: inherit;
  cursor: pointer;
}

/* The global button:hover rule (for .btn-style buttons) has higher
   specificity than a plain class selector, so it wins over the reset
   above on hover without this. */
.chat-message__avatar-trigger:hover,
.chat-message__name-link:hover {
  background: none;
}

.chat-message__avatar-trigger {
  display: block;
  border-radius: 999px;
}

.chat-message__name-link:hover .chat-message__name {
  text-decoration: underline;
}

.chat-message__name {
  font-weight: 700;
  font-size: 0.9rem;
  color: var(--chat-pane-title-text);
}

/* Same forced-colors issue as .link-button above: the name is a <button>
   (it opens the mini-profile popover), so forced-colors mode leaves it in
   our themed --pane-title-text color instead of switching it to CanvasText. */
@media (forced-colors: active) {
  .chat-message__name-link .chat-message__name {
    color: CanvasText;
  }
}

.chat-message__name--deleted {
  font-style: italic;
  font-weight: 500;
  font-size: 0.9rem;
  color: color-mix(in srgb, var(--chat-pane-text) 65%, transparent);
}

.chat-message__pronouns,
.chat-message__time {
  font-size: 0.76rem;
  color: color-mix(in srgb, var(--chat-pane-text) 65%, transparent);
}

.chat-message__pronouns {
  font-style: italic;
}

.chat-message__subtitle {
  display: block;
  font-size: 0.78rem;
  font-style: italic;
  line-height: 1.3;
  color: color-mix(in srgb, var(--chat-pane-text) 75%, transparent);
}

.chat-message__body {
  font-size: 0.92rem;
  margin-top: 0.1rem;
}

.composer {
  flex-shrink: 0;
  background-color: var(--chat-composer-bg);
  color: var(--chat-composer-text);
  border-top: 1px solid var(--chat-divider);
  padding: 0.4rem 1rem 0.5rem;
}

/* Shared "post as" searchable profile picker — used by the composer's
   identity switcher and by any plain form field that needs to choose a
   profile (server creation, joining a server). Selection-context specifics
   (immediate PATCH vs. a hidden form field, upward vs. downward opening)
   live in profile_picker_controller.js / composer_controller.js and the
   .composer-posting-as modifier below, not here. */
.profile-picker {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  background-color: color-mix(in srgb, var(--pane-bg) 85%, var(--page-bg) 15%);
  border: 1px solid color-mix(in srgb, var(--pane-text) 35%, transparent);
  border-radius: 7px;
  padding: 0.3rem 0.6rem 0.3rem 0.4rem;
  font-size: 0.82rem;
}

/* .avatar--placeholder carries its own min-width/min-height (see the base
   rule) to stay visible at its default 64px size elsewhere — here it needs
   overriding too, or the placeholder ignores the width/height below and
   renders oversized next to profiles that do have a real avatar image. */
.profile-picker .avatar {
  width: 22px;
  height: 22px;
  min-width: 22px;
  min-height: 22px;
}

.profile-picker__option .avatar {
  width: 20px;
  height: 20px;
  min-width: 20px;
  min-height: 20px;
  margin-top: 0.1rem;
}

.profile-picker__trigger {
  display: inline-flex;
  align-items: center;
  gap: 0.4rem;
  cursor: pointer;
  list-style: none;
}

/* Without this, the wrapping span is a block box around an inline <img>,
   which leaves a few px of baseline whitespace below the image — inflating
   the span taller than the 22px avatar and pushing it toward the top of the
   trigger row instead of centering it. */
.profile-picker__trigger-avatar {
  display: flex;
}

.profile-picker__trigger-pronouns {
  margin-left: 0.3rem;
  font-size: 0.78rem;
  font-style: italic;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
}

.profile-picker__trigger::-webkit-details-marker,
.profile-picker__trigger::marker {
  display: none;
}

/* The default .action-dropdown__menu anchors to its trigger's right edge and
   grows leftward, which overflows off-screen whenever the trigger sits near
   the left edge of a narrower container (a form dialog, the composer at the
   bottom of the pane) — anchor left and grow rightward instead. */
.profile-picker .action-dropdown__menu {
  left: 0;
  right: auto;
}

/* The composer's picker additionally opens upward, since it sits at the very
   bottom of the pane and there's no room to open downward. */
.composer-posting-as {
  margin-bottom: 0.35rem;
  padding-block: 0.1rem;
}

.composer-posting-as .action-dropdown__menu {
  top: auto;
  bottom: calc(100% + 4px);
}

.profile-picker__menu {
  width: max-content;
  min-width: 260px;
  max-width: 340px;
  padding: 0.4rem;
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
}

.profile-picker__search {
  width: 100%;
  padding: 0.4rem 0.6rem;
  border-radius: 5px;
  border: 1px solid var(--pane-border);
  background-color: var(--page-bg);
  color: var(--pane-text);
  font-size: 0.85rem;
}

.profile-picker__options {
  max-height: 260px;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 0.1rem;
}

.profile-picker__option {
  display: flex;
  align-items: flex-start;
  gap: 0.5rem;
  white-space: normal;
  outline: none;
}

/* Explicit ring rather than relying on the browser's native default — a
   <button> and an <a> (the composer uses a link, the plain-form pickers use
   a button) don't reliably render the same default focus indicator. Outline
   only appears on focus (rather than an always-on "transparent" placeholder
   that flips color) because forced-colors mode refuses to treat a transparent
   outline as invisible — any non-"none" outline-style gets forced to a
   visible system color regardless of focus state, which would otherwise put
   a box around every option, focused or not. */
.profile-picker__option:focus-visible {
  outline: 2px solid var(--pane-link);
  outline-offset: -2px;
}

@media (forced-colors: active) {
  .profile-picker__option:focus-visible {
    outline-color: CanvasText;
  }
}

/* The composer's picker uses a link (it fires an immediate PATCH); the
   plain-form "post as" pickers use a <button> instead (there's nothing to
   navigate to, just a hidden field to fill in) — reset the UA button chrome
   without touching the font-size/color already set above for both.
   appearance: none keeps the button under our own styling (background,
   border, outline) in forced-colors mode too — without it, some browsers
   fall back to native widget rendering for real <button> elements there and
   ignore the authored focus ring entirely. */
button.profile-picker__option {
  width: 100%;
  text-align: left;
  background: none;
  border: none;
  appearance: none;
  font-family: inherit;
  cursor: pointer;
}

.profile-picker__option--current {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.profile-picker__option-info {
  min-width: 0;
  flex: 1;
}

.profile-picker__option-name-row {
  display: flex;
  align-items: baseline;
  gap: 0.4rem;
  flex-wrap: wrap;
}

.profile-picker__option-name {
  font-weight: 500;
}

.profile-picker__option-pronouns {
  font-size: 0.78rem;
  font-style: italic;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
}

.profile-picker__option-subtitle {
  display: block;
  font-size: 0.78rem;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
}

.profile-picker__empty {
  padding: 0.5rem 0.75rem;
  margin: 0;
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--pane-text) 65%, transparent);
}

/* Separates the profile options from the group options within the same
   picker menu — a heading rather than a second scrollable list, so the
   profile-filter controller's flat option list (and its "no matches" empty
   state) keeps working unmodified across both sections. */
.profile-picker__group-label {
  margin: 0.4rem 0 0.1rem;
  padding: 0 0.6rem;
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: color-mix(in srgb, var(--pane-text) 60%, transparent);
}

.profile-picker__group-label:first-child {
  margin-top: 0;
}

/* The composer's "posting as" picker takes the chat palette. The same
   .profile-picker markup is also used as a plain form field on the server
   create, join and membership pages, which are ordinary pages and keep the
   profile colours in the base rules above. Only the copy inside .composer is
   chat chrome, so the chat colours hang off that ancestor. */
.composer .profile-picker {
  background-color: var(--chat-composer-highlight);
  border: 1px solid color-mix(in srgb, var(--chat-composer-text) 35%, transparent);
}

.composer .profile-picker__trigger-pronouns {
  color: color-mix(in srgb, var(--chat-composer-text) 75%, transparent);
}

.composer .profile-picker__search {
  border: 1px solid var(--chat-divider);
  background-color: var(--chat-input-bg);
  color: var(--chat-composer-text);
}

.composer .profile-picker__option:focus-visible {
  outline: 2px solid var(--chat-pane-link);
}

.composer .profile-picker__option--current {
  background-color: color-mix(in srgb, var(--chat-composer-text) 10%, transparent);
}

.composer .profile-picker__option-pronouns {
  color: color-mix(in srgb, var(--chat-composer-text) 75%, transparent);
}

.composer .profile-picker__option-subtitle {
  color: color-mix(in srgb, var(--chat-composer-text) 75%, transparent);
}

.composer .profile-picker__empty {
  color: color-mix(in srgb, var(--chat-composer-text) 65%, transparent);
}

.composer .profile-picker__group-label {
  color: color-mix(in srgb, var(--chat-composer-text) 60%, transparent);
}

/* The override above is (0,3,0), which beats the base forced-colors rule
   for this selector (0,2,0) regardless of the media query, so it needs its
   own. Same trap as the chat-scoped Sign out link. */
@media (forced-colors: active) {
  .composer .profile-picker__option:focus-visible {
    outline-color: CanvasText;
  }
}

.composer-input-row {
  display: flex;
  gap: 0.6rem;
  align-items: flex-end;
}

/* The message box follows the chat input colours rather than the shared form
   ones, so a chat theme can size its composer to the pane behind it. Each
   defaults to the matching --input-* value, so this changes nothing until a
   designer sets them. */
.composer-input-row textarea {
  background-color: var(--chat-input-bg);
  border-color: var(--chat-input-border);
  color: var(--chat-input-text);
  flex: 1;
  line-height: 1.4;
  /* The shared textarea rule sets a 100px min-height sized for full-page
     forms — way more than the composer's own rows="1" needs, and on a
     fixed-viewport chat layout every extra pixel here is one less pixel for
     the message history. composer_controller.js#autoGrow drives the actual
     height as you type via an inline style; these are just the floor (one
     line) and ceiling (six lines) it's clamped between. */
  min-height: 2.6rem;
  max-height: 9.6rem;
  resize: none;
  overflow-y: auto;
}

/* The box starts one line tall, so a placeholder too long for the line would
   wrap onto a second one that's cut off half-way. Cut it short on the first. */
.composer-input-row textarea::placeholder {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Read-only while a message is on its way (composer_controller.js), dimmed
   like a disabled button */
.composer-input-row textarea:read-only {
  opacity: 0.6;
  cursor: not-allowed;
}

/* The send button: the default button style, square, and as tall as the
   message box on a single line. The row aligns to the bottom, so it stays
   beside the last line as the box grows. */
.composer-send {
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.6rem;
  height: 2.6rem;
  padding: 0;
}

.composer-send__icon {
  width: 1.25rem;
  height: 1.25rem;
}

/* The base forced-colors rule gives every text field Canvas and CanvasText
   under forced-color-adjust: none, so author colours apply as written. The
   composer's textarea and the posting-as search field override those with chat
   colours at higher specificity, which in high-contrast mode would leave them
   in arbitrary theme colours and potentially unreadable. Put them back on
   system colours, focus ring included. */
@media (forced-colors: active) {
  .composer-input-row textarea,
  .composer .profile-picker__search {
    background-color: Canvas;
    color: CanvasText;
    border-color: CanvasText;
  }

  .composer-input-row textarea:focus,
  .composer .profile-picker__search:focus {
    border-color: CanvasText;
    outline: 2px solid CanvasText;
  }
}

/* Server invites */
.invite-generator {
  display: flex;
  flex-direction: column;
  gap: 1rem;
}

.invite-generator__regenerate {
  margin-top: 1rem;
}

.invite-card {
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
  gap: 0.5rem;
  padding: 1rem 0;
}

.invite-card .avatar {
  margin-bottom: 0.25rem;
}

.invite-card__title {
  margin: 0;
}

.invite-card__subtitle,
.invite-card__description {
  margin: 0;
  font-size: 0.85rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
}

.invite-card__description {
  margin-bottom: 0.75rem;
}

.invite-card__accept-preview {
  pointer-events: none;
  opacity: 0.85;
}

/* ── Chat identity settings ────────────────────────────── */

.chat-identity-page {
  max-width: 1200px;
}

.back-link {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  color: color-mix(in srgb, var(--pane-text) 80%, transparent);
  text-decoration: none;
  font-size: 0.85rem;
  margin-bottom: 0.75rem;
}

.back-link:hover {
  color: var(--pane-text);
  text-decoration: underline;
}

.back-link span {
  font-size: 1.1rem;
}

.chat-identity-intro__sub {
  display: block;
  margin-top: 0.35rem;
  color: color-mix(in srgb, var(--pane-text) 75%, transparent);
  font-size: 0.85rem;
}

.chat-identity-layout {
  display: grid;
  grid-template-columns: 1fr 380px;
  gap: 2rem;
  align-items: start;
}

.chat-identity-preview-col {
  position: sticky;
  top: 2rem;
  max-height: calc(100vh - 4rem);
  overflow-y: auto;
}

/* Field cards: "Use main" / "Set for chat" per field */

.chat-identity-field__head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
}

.chat-identity-field__head h3 {
  margin: 0 0 0.25rem;
  font-size: 1.05rem;
}

.chat-identity-field__head .form-hint {
  margin: 0;
  max-width: 38ch;
}

.chat-identity-toggle {
  display: inline-flex;
  align-items: center;
  flex-shrink: 0;
  border: 1px solid var(--input-border);
  border-radius: 999px;
  padding: 3px;
  gap: 2px;
  background-color: var(--input-bg);
}

.chat-identity-toggle__option {
  position: relative;
  display: inline-block;
  margin-bottom: 0;
  cursor: pointer;
}

.chat-identity-toggle__input {
  position: absolute;
  inset: 0;
  opacity: 0;
  pointer-events: none;
}

.chat-identity-toggle__option-label {
  display: inline-block;
  line-height: 1;
  padding: 0.4rem 0.8rem;
  border-radius: 999px;
  font-size: 0.78rem;
  font-weight: 600;
  color: color-mix(in srgb, var(--input-text) 70%, transparent);
  white-space: nowrap;
  transition: background-color 0.12s ease, color 0.12s ease;
}

.chat-identity-toggle__option-label:hover {
  color: var(--input-label);
}

.chat-identity-toggle__input:checked + .chat-identity-toggle__option-label {
  background-color: var(--secondary-button-bg);
  color: var(--secondary-button-text);
}

.chat-identity-toggle__input:focus-visible + .chat-identity-toggle__option-label {
  outline: 2px solid var(--pane-link);
  outline-offset: 2px;
}

@media (forced-colors: active) {
  /* The checked/unchecked distinction is otherwise carried entirely by
     background-color, which forced-colors mode flattens away — nothing
     then shows which pill is selected. outline (not border) so it draws
     inward rather than shifting the pill's box size. */
  .chat-identity-toggle__input:checked + .chat-identity-toggle__option-label {
    outline: 2px solid CanvasText;
    outline-offset: -2px;
  }
}

.chat-identity-field__inherited {
  display: flex;
  align-items: baseline;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.6rem 0.85rem;
  border-radius: 6px;
  background-color: color-mix(in srgb, var(--pane-link) 12%, transparent);
  border: 1px solid color-mix(in srgb, var(--pane-link) 30%, transparent);
  font-size: 0.9rem;
}

/* The avatar card's "Use main" state is just the image — the tinted
   panel styling that works for a line of text looks like an unnecessary box
   around it, so this variant keeps the same element (still what the JS
   toggle shows/hides) without the surrounding chrome. */
.chat-identity-field__inherited--plain {
  padding: 0;
  border: none;
  background: none;
}

.chat-identity-field__value {
  min-width: 0;
}

.chat-identity-field__blank {
  font-style: italic;
  color: color-mix(in srgb, var(--pane-text) 65%, transparent);
}

.chat-identity-field__override {
  margin-top: 0.85rem;
}

.chat-identity-field__override .form-hint {
  margin-top: 0.4rem;
  margin-bottom: 0;
}

/* Live preview column */

.preview-block {
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  margin-bottom: 1rem;
  overflow: hidden;
}

.preview-block:last-child {
  margin-bottom: 0;
}

.preview-block__body {
  padding: 1rem;
}

.mini-profile__header {
  display: flex;
  flex-wrap: wrap;
  gap: 0.85rem;
  align-items: flex-start;
}

.mini-profile__header .avatar {
  flex-shrink: 0;
}

.mini-profile__header .tag-line {
  flex-basis: 100%;
  margin: 0;
}

/* Overrides the global rules, which otherwise assume they're sitting on
   --pane-bg — .mini-profile__header is always the header_bg banner. */
.mini-profile__header .mini-profile__name {
  color: var(--header-title-text);
}

.mini-profile__header .subtitle,
.mini-profile__header .tag-line,
.mini-profile__header .pronouns {
  color: color-mix(in srgb, var(--header-text) 80%, transparent);
}

.mini-profile__header a {
  color: var(--header-link);
}

/* Edge-to-edge banner, same technique as .card > .card__header — the
   negative margin equals whichever ancestor's own padding it needs to
   counteract, since the real popover and the settings-page preview wrap
   .mini-profile in differently-padded containers. */
.mini-profile-popover > .mini-profile > .mini-profile__header {
  background-color: var(--header-bg);
  margin: -1.25rem -1.25rem 1rem;
  padding: 1.25rem;
  border-radius: 9px 9px 0 0;
}

.preview-block__body > .mini-profile > .mini-profile__header {
  background-color: var(--header-bg);
  margin: -1rem -1rem 1rem;
  padding: 1rem;
  border-radius: 7px 7px 0 0;
}

/* No description and the full-page link is off (or this is a Group, which
   has no link-toggle-independent description either way) — the header is
   .mini-profile's only child, so there's nothing left to reserve the
   trailing margin for. Collapse it flush with the container's own bottom
   edge instead of leaving dead empty space below the header. */
.mini-profile-popover > .mini-profile > .mini-profile__header:last-child {
  margin-bottom: -1.25rem;
  border-radius: 9px;
}

.preview-block__body > .mini-profile > .mini-profile__header:last-child {
  margin-bottom: -1rem;
  border-radius: 7px;
}

@media (forced-colors: active) {
  .mini-profile-popover > .mini-profile > .mini-profile__header,
  .preview-block__body > .mini-profile > .mini-profile__header {
    border-bottom: 1px solid CanvasText;
  }
}

.mini-profile__name {
  margin: 0;
  font-size: 1.1rem;
  color: var(--pane-title-text);
}

.mini-profile__description {
  margin-top: 0.85rem;
  margin-bottom: 0.5rem;
  padding-top: 0.85rem;
  max-height: 12rem;
  overflow-y: auto;
}

.mini-profile__link {
  margin-top: 0.85rem;
  padding-top: 0.85rem;
  font-size: 0.85rem;
}

/* The real chat popover, fetched on demand — .mini-profile itself (above)
   is shared with the settings-page preview, which wraps it in .preview-block
   instead of this. Positioned via inline left/top set by
   mini_profile_popover_controller.js; inset/margin here override the
   browser's default centered [popover] placement so those take effect. */
.mini-profile-popover {
  /* Explicit, rather than relying on the browser's default [popover]
     hiding — that default apparently doesn't reliably apply to a
     <turbo-frame>, a custom element, so it rendered visibly inline above
     every message before ever being opened. */
  display: none;
  position: fixed;
  inset: auto;
  margin: 0;
  width: min(320px, calc(100vw - 1rem));
  max-height: min(28rem, calc(100vh - 1rem));
  overflow-y: auto;
  padding: 1.25rem;
  background-color: var(--pane-bg);
  /* The [popover] UA stylesheet also defaults color to CanvasText — without
     overriding it here too, anything inside that doesn't set its own color
     (e.g. .profile-description) inherits that instead of the theme. */
  color: var(--pane-text);
  border: 1px solid var(--pane-border);
  border-radius: 10px;
  box-shadow: 0 8px 24px color-mix(in srgb, var(--pane-border) 60%, transparent);
}

.mini-profile-popover:popover-open {
  display: block;
}

/* Responsive: stack the preview under the form on narrow screens */
@media (max-width: 900px) {
  .chat-identity-layout {
    grid-template-columns: 1fr;
  }

  .chat-identity-preview-col {
    position: static;
    max-height: none;
  }
}

@media (max-width: 600px) {
  .chat-identity-page {
    max-width: 100%;
  }
}

/* ── Emote input: picker button, autocomplete menu, picker dialog ──────────
   ApplicationHelper#emote_field wraps a text field that accepts emote codes;
   emote_input_controller.js adds the autocomplete menu inside the wrapper and
   one shared picker dialog at the end of <body>. */

.emote-input {
  position: relative;
}

/* Room for the button, only once JS is there to show it. A field that failed
   validation is wrapped in .field_with_errors (Rails' default
   field_error_proc), so allow for that wrapper too. */
.js .emote-input--line > input,
.js .emote-input--line > .field_with_errors > input {
  padding-right: 2.75rem;
}

.js .emote-input--area > textarea,
.js .emote-input--area > .field_with_errors > textarea {
  padding-right: 3.25rem;
}

/* Block, so the wrapper is exactly the field's height and the button lines up. */
.emote-input > textarea,
.emote-input > .field_with_errors > textarea {
  display: block;
}

.emote-input__button {
  position: absolute;
  top: 50%;
  right: 0.4rem;
  transform: translateY(-50%);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 2rem;
  height: 2rem;
  padding: 0;
  background-color: transparent;
  border: 1px solid transparent;
  border-radius: 4px;
  color: var(--input-text);
  line-height: 1;
  opacity: 0.7;
}

.emote-input__button:hover,
.emote-input__button:focus-visible {
  background-color: color-mix(in srgb, var(--input-text) 12%, transparent);
  opacity: 1;
}

.emote-input__button:focus-visible {
  outline: 2px solid var(--pane-link);
  outline-offset: 1px;
}

/* Top corner of a textarea, clear of the resize handle and most scrollbars. */
.emote-input--area .emote-input__button {
  top: 0.4rem;
  right: 1rem;
  transform: none;
}

.emote-input__menu {
  position: absolute;
  z-index: 20;
  width: max-content;
  min-width: min(14rem, 100%);
  max-width: 100%;
  /* About eight options before it scrolls. */
  max-height: 20.5rem;
  overflow-y: auto;
  margin: 0;
  padding: 0.35rem;
  list-style: none;
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  box-shadow: 0 4px 12px color-mix(in srgb, var(--pane-border) 25%, transparent);
  color: var(--pane-text);
}

.emote-input__option {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  min-height: 2.4rem;
  margin: 0;
  padding: 0.25rem 0.6rem;
  border-radius: 5px;
  cursor: pointer;
  white-space: nowrap;
}

.emote-input__option img {
  flex-shrink: 0;
}

.emote-input__option:hover {
  background-color: color-mix(in srgb, var(--pane-text) 8%, transparent);
}

.emote-input__option--active,
.emote-input__option--active:hover {
  background-color: color-mix(in srgb, var(--pane-link) 25%, transparent);
  color: var(--pane-title-text);
}

/* The composer: the wrapper takes the textarea's place in the flex row, and
   the button sits on the last line as the textarea grows. */
.composer-input-row .emote-input {
  flex: 1;
  min-width: 0;
}

.composer-input-row .emote-input__button {
  top: auto;
  bottom: 0.3rem;
  right: 0.4rem;
  color: var(--chat-input-text);
}

.composer-input-row .emote-input__button:hover,
.composer-input-row .emote-input__button:focus-visible {
  background-color: color-mix(in srgb, var(--chat-input-text) 12%, transparent);
}

.composer-input-row .emote-input__button:focus-visible {
  outline-color: var(--chat-pane-link);
}

.composer .emote-input__menu {
  background-color: var(--chat-composer-bg);
  border-color: var(--chat-divider);
  color: var(--chat-composer-text);
}

.composer .emote-input__option:hover {
  background-color: color-mix(in srgb, var(--chat-composer-text) 10%, transparent);
}

.composer .emote-input__option--active,
.composer .emote-input__option--active:hover {
  background-color: color-mix(in srgb, var(--chat-pane-link) 30%, transparent);
  color: var(--chat-composer-text);
}

.emote-dialog {
  width: min(560px, 92vw);
  /* A fixed height, so the dialog doesn't shrink and jump around the screen
     as a search narrows the grid. */
  height: min(40rem, 85vh);
  padding: 1.25rem;
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  color: var(--pane-text);
}

.emote-dialog[open] {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.emote-dialog::backdrop {
  background-color: color-mix(in srgb, var(--page-bg) 60%, transparent);
}

.emote-dialog__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.emote-dialog__title {
  margin: 0;
  font-size: 1.2rem;
  color: var(--pane-title-text);
}

.emote-dialog__close {
  padding: 0.1rem 0.6rem;
  background-color: transparent;
  border-color: transparent;
  color: var(--pane-text);
  font-size: 1.4rem;
  line-height: 1;
}

.emote-dialog__close:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

/* The scrolling area: one section per emote set (or one of search results),
   each with its own grid. */
.emote-dialog__grid {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: 3px;
}

.emote-dialog__set + .emote-dialog__set {
  margin-top: 1rem;
}

.emote-dialog__set-title {
  margin: 0 0 0.35rem;
  font-size: 0.95rem;
  color: var(--pane-title-text);
}

.emote-dialog__set-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
  gap: 0.35rem;
}

.emote-dialog__emote {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.2rem;
  padding: 0.45rem 0.25rem;
  background-color: transparent;
  border: 1px solid transparent;
  border-radius: 6px;
  color: var(--pane-text);
  font-size: 0.8rem;
  line-height: 1.2;
  /* Buttons don't wrap by default; full names like "passionate heart" need to. */
  white-space: normal;
}

.emote-dialog__emote:hover {
  background-color: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.emote-dialog__emote:focus-visible {
  outline: 2px solid var(--pane-link);
  outline-offset: 0;
}

.emote-dialog__emote-name {
  overflow-wrap: anywhere;
  text-align: center;
}

.emote-dialog__empty {
  margin: 0;
}

.chat-body .emote-dialog {
  background-color: var(--chat-pane-bg);
  border-color: var(--chat-divider);
  color: var(--chat-pane-text);
}

.chat-body .emote-dialog__title,
.chat-body .emote-dialog__set-title,
.chat-body .emote-dialog__close,
.chat-body .emote-dialog__emote {
  color: var(--chat-pane-text);
}

.chat-body .emote-dialog__emote:focus-visible {
  outline-color: var(--chat-pane-link);
}

/* Always-visible scrollbars on the picker grid and the autocomplete menu —
   with macOS's default overlay scrollbars there's otherwise no sign that
   more emotes are hidden below. Styling ::-webkit-scrollbar is what makes
   Chrome and Safari keep them showing; Chrome ignores it on any element that
   also sets the standard scrollbar-color/scrollbar-width, so those are only
   applied where the pseudo-element isn't supported (Firefox). */
.emote-dialog__grid,
.emote-input__menu {
  --emote-scrollbar-thumb: color-mix(in srgb, var(--pane-text) 45%, transparent);
  --emote-scrollbar-track: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.chat-body .emote-dialog__grid {
  --emote-scrollbar-thumb: color-mix(in srgb, var(--chat-pane-text) 45%, transparent);
  --emote-scrollbar-track: color-mix(in srgb, var(--chat-pane-text) 10%, transparent);
}

.composer .emote-input__menu {
  --emote-scrollbar-thumb: color-mix(in srgb, var(--chat-composer-text) 45%, transparent);
  --emote-scrollbar-track: color-mix(in srgb, var(--chat-composer-text) 10%, transparent);
}

.emote-dialog__grid::-webkit-scrollbar,
.emote-input__menu::-webkit-scrollbar {
  width: 10px;
}

.emote-dialog__grid::-webkit-scrollbar-track,
.emote-input__menu::-webkit-scrollbar-track {
  background-color: var(--emote-scrollbar-track);
  border-radius: 5px;
}

.emote-dialog__grid::-webkit-scrollbar-thumb,
.emote-input__menu::-webkit-scrollbar-thumb {
  background-color: var(--emote-scrollbar-thumb);
  border-radius: 5px;
}

@supports not selector(::-webkit-scrollbar) {
  .emote-dialog__grid,
  .emote-input__menu {
    scrollbar-width: thin;
    scrollbar-color: var(--emote-scrollbar-thumb) var(--emote-scrollbar-track);
  }
}

@media (forced-colors: active) {
  /* Text colour for the heart icon and both focus rings, rather than
     ButtonText or Highlight, so they read as part of the field. */
  .emote-input__button {
    color: CanvasText;
    opacity: 1;
  }

  .emote-input__button:focus-visible,
  .composer-input-row .emote-input__button:focus-visible,
  .emote-dialog__emote:focus-visible,
  .chat-body .emote-dialog__emote:focus-visible {
    outline: 2px solid CanvasText;
  }

  /* The global forced-colors button rule switches forced-color-adjust off (for
     filled CanvasText buttons), and these borderless buttons override its
     colours — so without this they'd keep their theme colours in
     high-contrast mode. A CanvasText border marks hover instead. */
  .emote-input__button,
  .emote-input__button:hover,
  .emote-input__button:focus-visible,
  .composer-input-row .emote-input__button,
  .composer-input-row .emote-input__button:hover,
  .composer-input-row .emote-input__button:focus-visible,
  .emote-dialog__close,
  .emote-dialog__close:hover,
  .emote-dialog__emote,
  .emote-dialog__emote:hover,
  .chat-body .emote-dialog__close,
  .chat-body .emote-dialog__emote {
    background-color: Canvas;
    color: CanvasText;
  }

  .emote-input__button:hover,
  .composer-input-row .emote-input__button:hover,
  .emote-dialog__close:hover,
  .emote-dialog__emote:hover {
    border-color: CanvasText;
  }

  .emote-input__menu,
  .emote-dialog {
    border-color: CanvasText;
  }

  .emote-input__option--active,
  .emote-input__option--active:hover {
    forced-color-adjust: none;
    background-color: Highlight;
    color: HighlightText;
  }
}
