/*
 * 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: 1.5rem;
  font-size: 0.7rem;
  font-weight: 700;
  letter-spacing: 0.08em;
  padding: 0.2em 0.5em;
  border-radius: 3px;
  background: color-mix(in srgb, var(--danger-button-bg) 85%, transparent);
  color: var(--danger-button-text);
  border: 1px solid color-mix(in srgb, var(--danger-button-border) 40%, transparent);
  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;
}

.sidebar {
  position: sticky;
  overflow-y: auto;
  top: 2rem;
  max-height: calc(100vh - 4rem);
  align-self: start;
  background-color: var(--pane-bg);
  border-radius: 8px;
  padding: 0.75rem;
  padding-top: 2rem;
  border: 1px solid var(--pane-border);
}

.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;
  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;
  }
}

.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__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 {
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  padding: 2rem;
  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: -2rem -2rem 1.5rem -2rem;
  padding: 2rem;
  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 .heart-display__grid 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;
}

.explorer__sidebar {
  background-color: var(--pane-bg);
  border: 1px solid var(--pane-border);
  border-radius: 8px;
  padding: 0.5rem;
  padding-top: 1.5rem;
  overflow-y: auto;
  max-height: 80vh;
  position: sticky;
  top: 2rem;
  align-self: start;
}

.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 {
    padding: 1.5rem;
  }

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

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

  .sidebar {
    position: static;
  }

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

  .explorer__sidebar {
    position: static;
    max-height: none;
  }

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

  .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;
}

/* ── Heart emoji picker ────────────────────────────────── */

.heart-picker__grid,
.heart-display__grid {
  display: flex;
  flex-wrap: wrap;
  gap: 0.25rem;
  margin-left: 0.5rem;
}

.heart-picker__heart {
  position: relative;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  padding: 4px;
  border: 2px solid transparent;
  border-radius: 0.5rem;
  background: none;
  cursor: pointer;
  transition: opacity 0.15s, border-color 0.15s, transform 0.15s;
}

.heart-picker__heart:has(input:checked) {
  opacity: 1;
  border-color: var(--input-border);
}

.heart-picker__heart img {
  pointer-events: none;
}

/* Checkbox is always hidden — the label itself is the interactive control */
.heart-picker__checkbox {
  -webkit-appearance: none;
  appearance: none;
  position: absolute;
  inset: 0;
  opacity: 0;
  margin: 0;
  cursor: pointer;
  outline: none;
}

.heart-picker__heart:has(input:focus-visible) {
  outline: 3px solid var(--input-border);
  outline-offset: 2px;
}

.heart-display__heart {
  display: flex;
  align-items: center;
  justify-content: center;
}

.heart-display {
  margin: 1rem 0;
}

.heart-display h3 {
  margin-bottom: 0.5rem;
}

@media (forced-colors: active) {
  .heart-picker__heart {
    border: none;
    opacity: 1;
    background: Canvas;
  }

  .heart-picker__heart:has(input:checked) {
    border: 2px solid CanvasText;
    background: Canvas;
  }

  .heart-picker__heart:has(input:focus-visible) {
    outline: 3px solid CanvasText;
    outline-offset: 2px;
  }

  .heart-picker__checkbox,
  .heart-picker__checkbox:checked {
    forced-color-adjust: none;
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
    min-width: 0;
    min-height: 0;
    border: 0;
    background: none;
    opacity: 0;
  }

  .heart-picker__checkbox:checked::after {
    content: none;
  }
}

@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 heart emojis ────────────────── */

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

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

/* ── 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 ─────────────────────── */

.theme-designer__color-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
}

.theme-designer__override-label {
  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 {
  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 200px, and wide enough that the mock's longest channel
     name plus its unread dot stays on one line — a wrapped row misreads as a
     styling bug rather than a narrow column. */
  width: 186px;
  padding: 0.85rem 0.6rem;
  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__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;
}

.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: 2rem 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 2rem card padding
   on left, right, and bottom. Do NOT use overflow:hidden — it clips dropdowns. */
.theme-list {
  margin: 0 -2rem -2rem;
}

.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 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-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 {
  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.7rem;
  padding: 0.85rem 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-dot {
  display: inline-block;
  align-self: center;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--chat-sidebar-unread-dot);
  flex-shrink: 0;
}

/* 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 {
  position: absolute;
  top: 0;
  right: 0;
  background-color: var(--chat-rail-unread-dot);
  border: 2px solid var(--chat-rail-bg);
}

@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;
  }

  .unread-dot--rail {
    border-color: Canvas;
  }
}

.channel-pane {
  width: 200px;
  flex-shrink: 0;
  background-color: var(--chat-sidebar-bg);
  color: var(--chat-sidebar-text);
  border-right: 1px solid var(--chat-divider);
  padding: 1rem 0.75rem;
  overflow-y: auto;
}

/* 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;
}

/* 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);
}

.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) {
  .channel-pane .sidebar-tree__leaf--active {
    background: Highlight;
    color: HighlightText;
  }
}

.channel-pane__server-name {
  display: block;
  font-weight: 700;
  color: var(--chat-sidebar-title-text);
  text-decoration: none;
  margin-bottom: 0.75rem;
}

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

.channel-pane__add-channel {
  display: block;
  margin-top: 0.6rem;
  padding-left: 1.1rem;
  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);
}

.chat-channel-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.6rem 1.25rem 0.9rem;
  /* 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);
}

.chat-channel-header h1 {
  margin: 0;
  font-size: 1.15rem;
  flex-shrink: 0;
  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 and description share one line rather than stacking — the description is
   the one that gives way (ellipsis) since the channel name is what
   identifies the channel and should never get cut off. The subtitle, if
   present, runs on its own line underneath. */
.chat-channel-header__title-text {
  display: flex;
  align-items: baseline;
  gap: 0.6rem;
  min-width: 0;
}

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

.chat-channel-header__title-column .subtitle {
  margin: 0.15rem 0 0;
}

.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;
  }
}

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

/* 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: 1rem;
}

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

.chat-date-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  padding-top: 1.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.5rem;
  flex-wrap: wrap;
}

.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;
  color: color-mix(in srgb, var(--chat-pane-text) 75%, transparent);
}

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

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

/* 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.55rem;
}

.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 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%;
  }
}

/* ── Heart input: picker button, autocomplete menu, picker dialog ──────────
   ApplicationHelper#heart_field wraps a text field that accepts heart codes;
   heart_input_controller.js adds the autocomplete menu inside the wrapper and
   one shared picker dialog at the end of <body>. */

.heart-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 .heart-input--line > input,
.js .heart-input--line > .field_with_errors > input {
  padding-right: 2.75rem;
}

.js .heart-input--area > textarea,
.js .heart-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. */
.heart-input > textarea,
.heart-input > .field_with_errors > textarea {
  display: block;
}

.heart-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;
}

.heart-input__button:hover,
.heart-input__button:focus-visible {
  background-color: color-mix(in srgb, var(--input-text) 12%, transparent);
  opacity: 1;
}

.heart-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. */
.heart-input--area .heart-input__button {
  top: 0.4rem;
  right: 1rem;
  transform: none;
}

.heart-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);
}

.heart-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;
}

.heart-input__option img {
  flex-shrink: 0;
}

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

.heart-input__option--active,
.heart-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 .heart-input {
  flex: 1;
  min-width: 0;
}

.composer-input-row .heart-input__button {
  top: auto;
  bottom: 0.3rem;
  right: 0.4rem;
  color: var(--chat-input-text);
}

.composer-input-row .heart-input__button:hover,
.composer-input-row .heart-input__button:focus-visible {
  background-color: color-mix(in srgb, var(--chat-input-text) 12%, transparent);
}

.composer-input-row .heart-input__button:focus-visible {
  outline-color: var(--chat-pane-link);
}

.composer .heart-input__menu {
  background-color: var(--chat-composer-bg);
  border-color: var(--chat-divider);
  color: var(--chat-composer-text);
}

.composer .heart-input__option:hover {
  background-color: color-mix(in srgb, var(--chat-composer-text) 10%, transparent);
}

.composer .heart-input__option--active,
.composer .heart-input__option--active:hover {
  background-color: color-mix(in srgb, var(--chat-pane-link) 30%, transparent);
  color: var(--chat-composer-text);
}

.heart-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);
}

.heart-dialog[open] {
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

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

.heart-dialog__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
}

.heart-dialog__title {
  margin: 0;
  font-size: 1.2rem;
  color: var(--pane-title-text);
}

.heart-dialog__close {
  padding: 0.1rem 0.6rem;
  background-color: transparent;
  border-color: transparent;
  color: var(--pane-text);
  font-size: 1.4rem;
  line-height: 1;
}

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

.heart-dialog__grid {
  flex: 1;
  align-content: start;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(5.5rem, 1fr));
  gap: 0.35rem;
  min-height: 0;
  overflow-y: auto;
  padding: 3px;
}

.heart-dialog__heart {
  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;
}

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

.heart-dialog__heart:focus-visible {
  outline: 2px solid var(--pane-link);
  outline-offset: 0;
}

.heart-dialog__heart-name {
  overflow-wrap: anywhere;
  text-align: center;
}

.heart-dialog__empty {
  margin: 0;
}

.chat-body .heart-dialog {
  background-color: var(--chat-pane-bg);
  border-color: var(--chat-divider);
  color: var(--chat-pane-text);
}

.chat-body .heart-dialog__title,
.chat-body .heart-dialog__close,
.chat-body .heart-dialog__heart {
  color: var(--chat-pane-text);
}

.chat-body .heart-dialog__heart: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 hearts 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). */
.heart-dialog__grid,
.heart-input__menu {
  --heart-scrollbar-thumb: color-mix(in srgb, var(--pane-text) 45%, transparent);
  --heart-scrollbar-track: color-mix(in srgb, var(--pane-text) 10%, transparent);
}

.chat-body .heart-dialog__grid {
  --heart-scrollbar-thumb: color-mix(in srgb, var(--chat-pane-text) 45%, transparent);
  --heart-scrollbar-track: color-mix(in srgb, var(--chat-pane-text) 10%, transparent);
}

.composer .heart-input__menu {
  --heart-scrollbar-thumb: color-mix(in srgb, var(--chat-composer-text) 45%, transparent);
  --heart-scrollbar-track: color-mix(in srgb, var(--chat-composer-text) 10%, transparent);
}

.heart-dialog__grid::-webkit-scrollbar,
.heart-input__menu::-webkit-scrollbar {
  width: 10px;
}

.heart-dialog__grid::-webkit-scrollbar-track,
.heart-input__menu::-webkit-scrollbar-track {
  background-color: var(--heart-scrollbar-track);
  border-radius: 5px;
}

.heart-dialog__grid::-webkit-scrollbar-thumb,
.heart-input__menu::-webkit-scrollbar-thumb {
  background-color: var(--heart-scrollbar-thumb);
  border-radius: 5px;
}

@supports not selector(::-webkit-scrollbar) {
  .heart-dialog__grid,
  .heart-input__menu {
    scrollbar-width: thin;
    scrollbar-color: var(--heart-scrollbar-thumb) var(--heart-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. */
  .heart-input__button {
    color: CanvasText;
    opacity: 1;
  }

  .heart-input__button:focus-visible,
  .composer-input-row .heart-input__button:focus-visible,
  .heart-dialog__heart:focus-visible,
  .chat-body .heart-dialog__heart: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. */
  .heart-input__button,
  .heart-input__button:hover,
  .heart-input__button:focus-visible,
  .composer-input-row .heart-input__button,
  .composer-input-row .heart-input__button:hover,
  .composer-input-row .heart-input__button:focus-visible,
  .heart-dialog__close,
  .heart-dialog__close:hover,
  .heart-dialog__heart,
  .heart-dialog__heart:hover,
  .chat-body .heart-dialog__close,
  .chat-body .heart-dialog__heart {
    background-color: Canvas;
    color: CanvasText;
  }

  .heart-input__button:hover,
  .composer-input-row .heart-input__button:hover,
  .heart-dialog__close:hover,
  .heart-dialog__heart:hover {
    border-color: CanvasText;
  }

  .heart-input__menu,
  .heart-dialog {
    border-color: CanvasText;
  }

  .heart-input__option--active,
  .heart-input__option--active:hover {
    forced-color-adjust: none;
    background-color: Highlight;
    color: HighlightText;
  }
}
