/* -------------------------------------------------------
                          CHAT
   ------------------------------------------------------- */
/* Per-author colors (getChatUserColorVar, chat.js) — fixed in the sense
   that they never depend on the viewer's own chosen accent
   (--secondary-color, styles.css, IS meant to vary per viewer — exactly why
   chat.js deliberately avoids reusing it here), unlike that they DO still
   need a light-theme override (below) purely for contrast: these bright,
   saturated values read fine against the near-black dark-theme
   .chat-message/.chat-message.own backgrounds (--main-theme/--card-theme,
   styles.css) but were nearly invisible against light theme's much lighter
   mid-gray versions of those same two — darkened well past what still
   "looks like the same color" would suggest, since a light gray background
   needs a much lower-luminance color to hit real contrast than a
   near-black one does. Named for the actual color rather than a semantic
   role (unlike --task-done/--priority-one/etc.) since that's all these are
   for — telling authors apart at a glance, nothing else. */
:root {
  --chat-user-color-yellow: #d4a900;
  --chat-user-color-green: #2ecc71;
  --chat-user-color-red: #e74c3c;
}
:root.light-theme {
  --chat-user-color-yellow: #5c4500;
  --chat-user-color-green: #0d4a1e;
  --chat-user-color-red: #7a1f16;
}
/* Bottom-right anchored (bottom+right, not top+left) so growing it via its
   own .resize-handle (bottom-right corner, same grip as a link container's
   — styles.css) expands upward/leftward, away from the .chat-btn it drops
   down from — see shared/scripts/chat.js. */
.chat-panel {
  position: fixed;
  z-index: 999;
  display: flex;
  flex-direction: column;
  min-width: 260px;
  min-height: 260px;
  max-width: 90vw;
  max-height: 80vh;
  padding: 10px;
  border-radius: 10px;
  background-color: var(--main-theme-darker);
  box-shadow:
    -8px 0 15px -2px rgba(0, 0, 0, 0.5),
    0 8px 15px -2px rgba(0, 0, 0, 0.5);
  border-top: 2px solid var(--secondary-color);
  border-bottom: 2px solid var(--secondary-color);
  overflow: hidden;
}

/* Phone mode (issue #184's roadmap): full-screen overlay instead of a
   floating/draggable/resizable box - the drag handle (.chat-drag-svg) and
   resize grip (.resize-handle) are both mouse-only (mousedown-based,
   shared/scripts/chat.js) and inert on touch anyway, so neither makes sense
   to keep visible once there's nothing left to drag/resize INTO (the panel
   already fills the whole screen). !important needed on position/size -
   openChatPanel/clampChatPanelToViewport (chat.js) are both gated off on
   phone and skip writing these as inline styles in the first place, but
   right/bottom/width/height restored from a PRE-phone-mode session (an
   existing homepage_settings.chatPanel value) could otherwise still be
   sitting there as stale inline styles from a desktop session. */
html.phone-mode .chat-panel {
  position: fixed !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  max-width: none;
  max-height: none;
  border-radius: 0;
  /* Overrides inset/height's own top:0/height:100% just above (later in
     the same rule wins at equal specificity/importance) WHILE the on-screen
     keyboard is open - _syncChatPanelToVisualViewport (shared/scripts/
     chat.js) sets these two custom properties on the panel's own inline
     style to track window.visualViewport's real offset/height, fixing a
     real reported bug (the header/subtitle bar getting pushed off the top
     of the screen once the keyboard opened). Routed through a custom
     property rather than setting top/height directly inline - a PLAIN
     inline style can never beat an !important stylesheet rule no matter
     when it's set, but an inline-set custom property still correctly wins
     when resolving var() here, regardless of the !important on the
     property actually consuming it. Falls back to the same 0/100% as
     before whenever JS hasn't set either (not phone mode, no
     visualViewport support, or simply not synced yet on this exact paint). */
  top: var(--phone-chat-panel-top, 0) !important;
  height: var(--phone-chat-panel-height, 100%) !important;
}
html.phone-mode .chat-panel .chat-drag-svg,
html.phone-mode .chat-panel .resize-handle {
  display: none;
}
/* Same reasoning as the chat panel itself - positionChatSideFlyout (chat.js)
   is gated off on phone (no "beside the panel" room once it's full-screen),
   so these become their own full-screen overlay instead, stacked above the
   chat panel underneath. */
html.phone-mode .chat-users-flyout,
html.phone-mode .chat-requests-flyout {
  position: fixed !important;
  inset: 0 !important;
  width: 100% !important;
  height: 100% !important;
  border-radius: 0;
  z-index: 1000;
}

.chat-panel .chat-header-wrapper {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  margin-bottom: 8px;
}

.chat-panel .chat-drag-svg {
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: var(--cursor-pointer, pointer);
  height: 30px;
  width: 32px;
  flex-shrink: 0;
  border-radius: 5px;
  background-color: var(--main-theme-lighter);
  padding: 5px;
  color: var(--secondary-color);
  transition: background-color 150ms;
  /* Was .chat-rate-limit's job (now removed) — pushes itself + closeBtn
     after it to the right edge of .chat-header-wrapper's flex row. */
  margin-left: auto;
}
.chat-panel .chat-drag-svg:active {
  cursor: var(--cursor-pointer, grabbing);
}

/* position:relative already comes from the global "h2" rule (styles.css),
   which .chat-refresh-btn below relies on as its containing block — restated
   explicitly here since that's easy to miss reading this file alone.
   Extra left padding clears space for that overlaid button so the centered
   title text never runs under it. */
.chat-panel .chat-header {
  position: relative;
  margin: 0;
  padding: 6px 10px 6px 36px;
  font-size: 16px;
  color: var(--font-color-clear);
}

.chat-panel .chat-header-text {
  display: block;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* Its own row below .chat-header-wrapper (chat.js) — usersBtn/searchBtn
   flank the current conversation's name (updateChatSubtitle) instead of
   sitting in the header itself. */
.chat-subtitle-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  border-bottom: 2px solid
    color-mix(in srgb, var(--secondary-color) 40%, transparent);
  border-radius: 10px;
  padding: 0px 5px 5px 5px;
}
.chat-subtitle-text {
  flex: 1;
  text-align: center;
  font-size: 12px;
  font-weight: bold;
  color: var(--highlight);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.chat-close-btn {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  border: none;
  background: none;
  color: var(--font-color);
  font-size: 14px;
}
.chat-close-btn:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
/* Identical to .icon-manager-refresh-btn/.history-refresh-btn (styles.css)
   — same circular outlined button, just placed here instead since it lives
   inside .chat-header rather than a dialog's own toolbar row. */
.chat-refresh-btn {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--secondary-color);
  border-radius: 50%;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 12px;
}
.chat-refresh-btn:not(:disabled):hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
.chat-refresh-btn:disabled {
  opacity: 0.6;
}
/* Reuses the same spin animation the Icon Manager / Backup History refresh
   buttons already define (icon-fetch-spin, styles/styles.css) rather than
   redeclaring an identical keyframe here. */
.chat-refresh-btn.loading i {
  animation: icon-fetch-spin 700ms linear infinite;
}
/* Lives inside .chat-header itself (chat.js) — overlaid at its left edge,
   the same "refresh button anchored to the title's own box" trick as
   .icon-manager-refresh-btn/.history-refresh-btn (styles.css), just scaled
   down for this panel's much narrower title. */
.chat-header .chat-refresh-btn {
  position: absolute;
  left: 6px;
  top: 50%;
  transform: translateY(-50%);
}

/* Same circular button look as .chat-refresh-btn above — a plain flex
   sibling in .chat-header-wrapper (chat.js) rather than overlaid on the
   title, since there's no shared corner it needs to anchor to. */
.chat-search-btn {
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--secondary-color);
  border-radius: 50%;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-search-btn:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}

/* Same "grow from 0 height, fade in" open animation as the Backup History
   dialog's own .history-search-row (styles/styles.css) — rebuilt here
   rather than folded into that shared selector group since this panel's
   own stylesheet is otherwise fully self-contained. */
.chat-search-row {
  display: flex;
  align-items: center;
  gap: 8px;
  flex-shrink: 0;
  background-color: var(--main-theme-darker);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  overflow: hidden;
  max-height: 0;
  margin-bottom: 0;
  padding: 0 10px;
  opacity: 0;
  transform: translateY(-6px);
  visibility: hidden;
  pointer-events: none;
  transition:
    max-height 220ms ease,
    margin-bottom 220ms ease,
    opacity 180ms ease,
    padding 220ms ease,
    transform 220ms ease,
    visibility 0s linear 220ms;
}
.chat-search-row.visible {
  max-height: 60px;
  margin-bottom: 8px;
  padding: 8px 10px;
  opacity: 1;
  transform: translateY(0);
  visibility: visible;
  pointer-events: auto;
  transition-delay: 0s;
}
.chat-search-input-wrapper {
  position: relative;
  flex: 1;
  display: flex;
  align-items: center;
}
.chat-search-input-wrapper > i {
  position: absolute;
  left: 9px;
  color: var(--secondary-color);
  font-size: 12px;
  pointer-events: none;
}
.chat-search-input {
  width: 100%;
  background-color: var(--input-color);
  color: var(--font-color-clear);
  border: none;
  border-bottom: 2px solid var(--secondary-color);
  border-radius: 6px;
  outline: none;
  padding: 4px 8px 4px 30px;
  font-size: 12px;
}
/* Same circular icon-button treatment as .history-search-close-btn
   (styles/styles.css) — repurposed here for prev/next result navigation
   instead of closing the row, since closing this one instead just toggles
   .chat-search-btn again (or Escape), same as opening it. */
.chat-search-prev-btn,
.chat-search-next-btn {
  width: 24px;
  height: 24px;
  flex-shrink: 0;
  padding: 0;
  border: none;
  border-radius: 50%;
  background-color: var(--main-theme-lighter);
  color: var(--font-color-clear);
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-search-prev-btn:not(:disabled):hover,
.chat-search-next-btn:not(:disabled):hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
.chat-search-prev-btn:disabled,
.chat-search-next-btn:disabled {
  opacity: 0.4;
}

/* Same circular button look as .chat-search-btn — opens/closes the
   users/direct-messages flyout (chat.js). position: relative (like
   .chat-requests-btn just below) — a plain inline flex circle, so its own
   .has-update::after dot needs this to have a containing block. */
.chat-users-btn {
  position: relative;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--secondary-color);
  border-radius: 50%;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-users-btn:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
/* Aggregate "something's unread in here" dot (chatHasAnyUnread, chat.js) —
   same shape/color/positioning as .chat-requests-btn.has-update::after
   right below, just for the users flyout instead of pending requests. */
.chat-users-btn.has-update::after {
  content: "";
  position: absolute;
  top: -3px;
  right: -3px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--chat-user-color-red);
  border: 2px solid var(--main-theme-darker);
}
/* "One of your own outgoing invites was just accepted" (hasUnseenAcceptedOutgoingInvite/
   syncChatRequestsBadge, chat.js) — deliberately its own class (has-invite-
   update), not has-update above: that one already means "unread message"
   and needs to keep working completely independently, so this gets its own
   corner (left, not right) and its own color (yellow, --chat-user-color-
   yellow — invitation news, never the fixed red messages use) rather than
   fighting the same spot for the same dot. */
.chat-users-btn.has-invite-update::before {
  content: "";
  position: absolute;
  top: -3px;
  left: -3px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--chat-user-color-yellow);
  border: 2px solid var(--main-theme-darker);
}

/* Same circular shell as .chat-users-btn — opens/closes the pending
   cross-org connection requests flyout (chat.js). position: relative isn't
   redundant here the way it would be for .chat-btn (already position:fixed,
   styles.css) — this button is a plain inline flex circle, so its own
   .has-update::after badge below needs this to have a containing block. */
.chat-requests-btn {
  position: relative;
  width: 28px;
  height: 28px;
  flex-shrink: 0;
  padding: 0;
  border: 1px solid var(--secondary-color);
  border-radius: 50%;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-requests-btn:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
/* This button is invitation-only (there's nothing else the "Requests"
   flyout ever shows), so its one dot is always the fixed, always-visible-
   regardless-of-accent yellow every other invitation signal on this page
   uses (--chat-user-color-yellow) — never red, which is reserved for actual
   unread chat messages elsewhere (.chat-btn/.chat-users-btn's own
   .has-update above). Lit whenever there's >=1 pending INCOMING request
   (syncChatRequestsBadge, chat.js) — outgoing ones don't need to grab
   attention the same way. */
.chat-requests-btn.has-update::after {
  content: "";
  position: absolute;
  top: -3px;
  right: -3px;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background-color: var(--chat-user-color-yellow);
  border: 2px solid var(--main-theme-darker);
}

/* Vertical flyout listing General + every known user (chat.js) — a
   satellite panel that follows .chat-panel around exactly like the tasks
   page's own task-preview/edit-panel pairing (positionCreateEditorPanel,
   tasks/scripts/tasks.js) — same box/border/shadow language as .chat-panel
   itself so the pairing reads as one connected unit. Deliberately NO
   left/top/height transition declared here — chat.js's own
   positionChatUsersFlyout applies one inline, and only for the "settle"
   moment at drag/resize-end, so live drag/resize frames move this in
   perfect lockstep instead of lagging behind through an eased transition.
   The opacity/transform pair below is a separate, always-on entrance/exit
   fade (toggled via .visible), unrelated to and non-conflicting with that
   inline position transition since it animates different properties. */
.chat-users-flyout {
  position: fixed;
  width: 380px;
  z-index: 999;
  display: flex;
  flex-direction: column;
  padding: 10px;
  border-radius: 10px;
  background-color: var(--main-theme-darker);
  border-top: 2px solid var(--secondary-color);
  border-bottom: 2px solid var(--secondary-color);
  box-shadow:
    -8px 0 15px -2px rgba(0, 0, 0, 0.5),
    0 8px 15px -2px rgba(0, 0, 0, 0.5);
  overflow: hidden;
  opacity: 0;
  transform: scale(0.95);
  transition:
    opacity 180ms ease,
    transform 180ms ease;
}
.chat-users-flyout.visible {
  opacity: 1;
  transform: scale(1);
}
.chat-users-flyout-header {
  flex-shrink: 0;
  font-size: 13px;
  font-weight: bold;
  color: var(--font-color-clear);
  padding: 4px 6px 8px;
  border-bottom: 2px solid
    color-mix(in srgb, var(--secondary-color) 40%, transparent);
  margin-bottom: 6px;
}
/* Shared layout for both the users flyout's own list AND the requests
   flyout's own list — deliberately two independent classes on two
   different elements, NOT one class applied to both, so a global
   document.querySelector(".chat-users-list") (renderChatUsersList, chat.js)
   can never accidentally match the wrong flyout's list (e.g. one still
   mid fade-out after "closing" — see chat-requests-list's own comment,
   chat.js, for exactly that bug). */
.chat-users-list,
.chat-requests-list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
/* Same look/animation as .icon-manager-save-spinner/.history-save-spinner/
   .profile-available-spinner (styles.css) — chat.js/chat.css are homepage-
   only, always loaded alongside styles.css, so reusing its --secondary-
   color/--main-theme/@keyframes icon-fetch-spin here is safe. Shown in
   place of the other users' entries while renderChatUsersList's own
   userlist fetch (fetchProfileDirectory/fetchConnections, openChatUsersFlyout,
   chat.js) is still in flight — reused as-is by the Requests flyout's own
   equivalent wait (openChatRequestsFlyout). */
.chat-users-list-spinner {
  display: none;
  align-self: center;
  margin: 10px 0;
  width: 12px;
  height: 12px;
  border: 2px solid var(--main-theme);
  border-top-color: var(--secondary-color);
  border-radius: 50%;
  animation: icon-fetch-spin 700ms linear infinite;
}
.chat-users-list-spinner.visible {
  display: block;
}
.chat-user-entry:first-of-type {
  margin-bottom: 10px;
}
.chat-user-entry {
  position: relative;
  text-align: left;
  padding: 6px 8px;
  border: none;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.5);
  color: var(--font-color-clear);
  font-size: 13px;
}
.chat-user-entry:hover {
  background-color: var(--main-theme-lighter);
  cursor: var(--cursor-pointer);
}
/* The currently-selected conversation (General or a specific user,
   switchChatTarget in chat.js) — same "persistent accent highlight" look
   as other on/off toggles in this app (e.g. .icon-manager-color-toggle-btn
   .color-toggle-on, styles.css), not a hover-only state. */
.chat-user-entry.active {
  background-color: var(--main-theme-lighter);
  color: var(--secondary-color);
  font-weight: bold;
}
/* Per-conversation unread dot (conversationHasUnread, chat.js) — same fixed
   red (--chat-user-color-red) as .chat-btn.has-update above, for the same
   "shouldn't depend on the viewer's own accent, and unread reads as more
   urgent as a fixed always-red dot" reasoning, just smaller and inline
   here rather than overlaid on a floating button corner. */
.chat-user-entry.has-update::after {
  content: "";
  position: absolute;
  top: 50%;
  right: 8px;
  transform: translateY(-50%);
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: var(--chat-user-color-red);
}
/* .chat-user-entry-connection's own row (an accepted connection, chat.js)
   has .chat-connection-remove-btn sitting flush against the same right
   edge (its own flex-shrink: 0, pushed there by the row's justify-content:
   space-between) - the base dot's right: 8px above lands right on top of
   that button's own right portion. Nudged left just far enough to clear
   the button's own left edge (8px inset + its 20px width = 28px) with a
   little breathing room. */
.chat-user-entry-connection.has-update::after {
  right: 32px;
}

/* Section header inside the users/requests flyout lists (renderChatUsersList/
   renderChatRequestsList, chat.js) — "Your family"/"Connections"/"Find
   people", "Incoming"/"Outgoing". Plain uppercase label, not a button —
   nothing to click, just groups what follows it. */
.chat-users-group-label {
  font-size: 10px;
  font-weight: bold;
  text-transform: uppercase;
  letter-spacing: 0.03em;
  color: var(--secondary-color);
  opacity: 0.75;
  padding: 6px 8px 2px;
}
/* A discovery entry (chat.js's "Find people" group) — a name/owner label
   plus its own Invite button, unlike a plain .chat-user-entry which is
   itself the click target. flex row instead of the plain block/text-align
   layout .chat-user-entry uses, to fit the button on the same line. */
.chat-user-entry-findable {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  cursor: default;
}
.chat-user-entry-findable:hover {
  background-color: transparent;
}
/* An accepted connection's row (label + its own remove button, Group 2 —
   renderChatUsersList) — same flex shell as .chat-user-entry-findable
   above, minus its hover:transparent override: THIS row, unlike a
   findable/search-result one, still switches to that conversation on
   click (via its own label) and can be .active, so it needs the base
   .chat-user-entry:hover/.active look to keep applying normally. */
.chat-user-entry-connection {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
}
.chat-user-entry-label {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  cursor: var(--cursor-pointer);
}
.chat-user-entry-label.overflowing {
  transition: transform 2000ms linear;
}
.chat-user-entry-connection:hover .chat-user-entry-label.overflowing {
  text-overflow: initial;
  overflow: visible;
}
.chat-connection-remove-btn {
  flex-shrink: 0;
  padding: 0;
  width: 20px;
  height: 20px;
  border: none;
  background: none;
  color: var(--secondary-color);
  font-size: 11px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: var(--cursor-pointer);
}
.chat-connection-remove-btn:hover:not(:disabled) {
  color: var(--chat-user-color-red);
}
.chat-connection-remove-btn:disabled {
  opacity: 0.6;
  cursor: default;
}
/* Free-plan stand-in for the "Find people" group's real list
   (renderChatUsersList, chat.js) — plain wrapped text, not a click target,
   same "explain why, don't just hide it" reasoning as .plan-gated-locked's
   own toolbar buttons elsewhere in the app (styles.css). */
.chat-user-entry-locked {
  cursor: default;
  opacity: 0.75;
  font-size: 12px;
  white-space: normal;
  line-height: 1.4;
}
.chat-user-entry-locked:hover {
  background-color: rgba(0, 0, 0, 0.5);
}
.chat-invite-btn {
  flex-shrink: 0;
  padding: 3px 8px;
  border: 1px solid var(--secondary-color);
  border-radius: 6px;
  background-color: var(--main-theme-lighter);
  color: var(--font-color-clear);
  font-size: 11px;
  font-family: inherit;
  cursor: var(--cursor-pointer);
}
.chat-invite-btn:hover:not(:disabled) {
  color: var(--highlight);
}
.chat-invite-btn:disabled {
  opacity: 0.6;
  cursor: default;
}
/* Plain, non-clickable label next to a search result that's already
   connected or has a pending request either way (renderChatUsersList,
   chat.js) — stands in for .chat-invite-btn on exactly those rows, since
   there's no action left to offer there. */
.chat-invite-status {
  flex-shrink: 0;
  font-size: 11px;
  color: var(--secondary-color);
  opacity: 0.8;
}

/* The "Find people" search box (Group 3, renderChatUsersList) — looks up
   ONE exact GitHub login at a time rather than browsing a list (see that
   function's own comment for why). Same underline-input look as
   .username-gate-input (styles.css), scaled down to fit this narrow
   flyout column, paired with a circular icon button matching
   .chat-search-btn's own shell (this file). */
.chat-search-profile-row {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 0 5px;
  margin-bottom: 4px;
}
.chat-search-profile-input {
  flex: 1;
  min-width: 0;
  padding: 6px 8px;
  background-color: var(--input-color);
  border: none;
  border-bottom: 2px solid var(--secondary-color);
  border-radius: 4px;
  outline: none;
  color: var(--font-color-clear);
  font-size: 12px;
  font-family: inherit;
}
/* focusVisible.css's project-wide `input:focus-visible { outline: 2px
   solid ... !important }` draws that ring OUTSIDE the input's own box
   (default outline-offset: 0) — flush against this flyout's own tight
   10px padding, that 2px overshoot gets clipped by .chat-users-list's
   overflow. Not fighting the !important'd outline color/width itself,
   just pulling it inward so it can never poke past this input's own
   edges regardless of how little surrounding padding there is. */
.chat-search-profile-input:focus-visible {
  outline-offset: -2px;
}
.chat-search-profile-btn {
  flex-shrink: 0;
  width: 26px;
  height: 26px;
  padding: 0;
  border: 1px solid var(--secondary-color);
  border-radius: 50%;
  background-color: var(--main-theme-darker);
  color: var(--font-color-clear);
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 11px;
}
.chat-search-profile-btn:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
/* Its own column (renderSearchResults, chat.js) rather than appending
   result rows straight into .chat-users-list — lets a stale result set get
   cleared (the search input's own "input" listener) by touching just this,
   without rebuilding the search row/input above it on every keystroke.
   Same row gap as .chat-users-list itself, so a result row lines up
   identically to any other chat-user-entry above it. */
.chat-search-profile-results {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

/* Requests flyout is a second instance of .chat-users-flyout's own shell
   (chat.js gives it both classes) — no size/position/fade overrides needed,
   just its own list-row layout below. */
.chat-request-entry {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 6px 8px;
  border-radius: 6px;
  background: rgba(0, 0, 0, 0.5);
  color: var(--font-color-clear);
  font-size: 13px;
}
/* Groups Accept+Decline (or Cancel alone) as one unit, pushed to the row's
   right edge by .chat-request-entry's own space-between above — without
   this wrapper, that same space-between would spread the two buttons
   apart from EACH OTHER too, not just away from the label (buildRow,
   chat.js). */
.chat-request-actions {
  display: flex;
  align-items: center;
  gap: 3px;
  flex-shrink: 0;
}
.chat-request-accept-btn,
.chat-request-decline-btn {
  flex-shrink: 0;
  padding: 3px 8px;
  border: 1px solid var(--secondary-color);
  border-radius: 6px;
  background-color: var(--main-theme-lighter);
  color: var(--font-color-clear);
  font-size: 11px;
  font-family: inherit;
  cursor: var(--cursor-pointer);
}
.chat-request-accept-btn:hover:not(:disabled) {
  color: var(--chat-user-color-green);
}
.chat-request-decline-btn:hover:not(:disabled) {
  color: var(--chat-user-color-red);
}
.chat-request-accept-btn:disabled,
.chat-request-decline-btn:disabled {
  opacity: 0.6;
  cursor: default;
}
/* A request row whose accept/decline/cancel just came back "not_pending" —
   the other side already resolved it (accepted/declined/canceled) in the
   moments since this flyout's own last fetch. Swapped in briefly (respond(),
   chat.js) so the click that failed gets an actual explanation right where
   it happened, instead of the button just going clickable again with no
   feedback at all - before the row disappears for good on the fetch right
   behind it. */
.chat-request-entry-stale {
  color: var(--font-color-weak);
  font-style: italic;
}

.chat-messages {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  display: flex;
  flex-direction: column;
  gap: 6px;
  padding: 4px 2px;
  /* Same scroll-chaining fix as .spinner-sidebar-strip's own identical
     overscroll-behavior-x (styles/styles.css - that rule's own comment has
     the fuller "what this actually stops" reasoning), just the vertical
     axis here - without it, scrolling this list all the way to either end
     and continuing to swipe in that direction (completely normal - it's how
     you find out you've reached the end) handed the rest of that gesture
     off to whatever's actually behind the panel (the kanban board/Today's
     Overview), scrolling THAT instead - confirmed as a real, reported bug,
     most noticeable reaching the bottom while the on-screen keyboard is
     open (phone), though the underlying cause is this list's own missing
     overscroll containment, not the keyboard itself. Scrolls exactly as
     before; it just never chains past its own top/bottom edges anymore. */
  overscroll-behavior-y: contain;
}

.chat-empty-message {
  margin: auto;
  color: var(--font-color-weak);
  font-size: 13px;
}

.chat-message {
  background-color: var(--main-theme);
  border-radius: 8px;
  padding: 6px 8px;
  max-width: 90%;
}
/* Whichever message the search row's own prev/next navigation (chat.js)
   currently sits on — an outline rather than a background swap so it stays
   legible over both .chat-message's own default and .own bubble colors. */
.chat-message.chat-message-search-active {
  outline: 2px solid var(--secondary-color);
  outline-offset: -1px;
}
/* Own messages align right, like a typical chat bubble layout, so a
   glance at the panel is enough to tell who said what without reading
   every author label. */
.chat-message.own {
  align-self: flex-end;
  background-color: var(--card-theme);
}

.chat-message-meta {
  display: flex;
  justify-content: space-between;
  gap: 10px;
  font-size: 11px;
  /* --font-color-weak (used elsewhere for secondary text) is tuned for
     contrast against the app's own background, not a .chat-message bubble
     (--main-theme/--card-theme, both mid-gray in light mode) — barely
     legible there. --font-color-clear is already proven readable against
     both bubble colors (it's what .chat-message-text itself uses, below) —
     faded via color-mix (not element opacity, which would also fade the
     separator border below along with it) so it still reads as
     secondary/small without losing contrast the way a flat gray would. */
  color: color-mix(in srgb, var(--font-color-clear) 65%, transparent);
  margin-bottom: 3px;
  padding-bottom: 2px;
  /* One reliable shade darker than .chat-message's own --main-theme
     background — a flat, always-distinct theme color instead of
     --main-theme-lighter (identical to .chat-message.own's --card-theme in
     light mode, so it'd vanish there) or a color-mix'd near-transparent
     line (too close to either bubble color to actually read as a line
     rather than nothing). Overridden for .own below since that bubble uses
     a different, lighter background. */
  border-bottom: 1px solid var(--main-theme-darker);
}
.chat-message.own .chat-message-meta {
  border-bottom-color: var(--card-theme-darker);
}

.chat-message-text {
  font-size: 13px;
  color: var(--font-color-clear);
  white-space: pre-wrap;
  word-break: break-word;
  /* Overrides the app-wide *:not(input) { user-select: none } reset
     (styles.css) — message text is the one thing in this panel worth
     copy-pasting (a link, an error someone reported, ...), unlike the
     surrounding chrome that reset is meant for. */
  user-select: text;
  /* var(--cursor-text), not a literal "text" — this app-wide custom
     property is what rs mode repoints to its own OSRS cursor .cur file
     (scripts/settings.js), so this stays the correct "selectable text"
     cursor in both states instead of only outside rs mode. */
  cursor: var(--cursor-text);
}

/* A system-generated row (icon-share notifications, pollIconShareNotifications/
   renderChatMessages, chat.js) rather than something either party actually
   typed — italicized and muted so it reads as an event log line, not a
   real chat message, same distinction most chat apps draw for "X joined"/
   "X shared a file" rows. */
.chat-message-text.chat-message-system {
  font-style: italic;
  color: var(--font-color);
  opacity: 0.8;
}
.chat-message-text.chat-message-system i.fa-share-nodes {
  color: var(--secondary-color);
  margin-right: 2px;
}

/* The app-wide `a` rule (styles.css) is written for the homepage's own
   link-container buttons (bold, 16px, flex, width: 100%, ...) — it isn't
   scoped to those, so it bleeds straight into an inline link inside a chat
   bubble too, both sizing the text up AND forcing it into its own full-width
   flex row instead of flowing inline with the rest of the message. Reset
   back to plain inline text here rather than a button. */
/* Emoji sent as part of a message (linkifyChatText, chat.js, wraps each
   one individually) render a bit larger than the surrounding text - same
   "reactions should read as reactions, not just more small text" reasoning
   as bumping .chat-emoji-picker-item's own size. */
.chat-message-emoji {
  font-size: 1.4em;
  line-height: 1;
  vertical-align: -3px;
}

.chat-message-text a {
  color: var(--secondary-color);
  font-size: inherit;
  font-weight: inherit;
  display: inline;
  width: auto;
  min-height: 0;
  text-decoration: underline;
}

.chat-status {
  min-height: 16px;
  font-size: 12px;
  color: var(--highlight);
  flex-shrink: 0;
}

.chat-no-token-hint {
  display: none;
  font-size: 12px;
  color: var(--font-color-weak);
  margin-bottom: 6px;
  flex-shrink: 0;
}

.chat-input-wrapper {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

/* Just the textarea's own positioning context — .chat-emoji-btn anchors to
   this, not .chat-input-wrapper as a whole, so it overlaps the textarea's
   own corner rather than sitting in the gap before .chat-send-btn. */
.chat-input-field-wrapper {
  position: relative;
  flex: 1;
  display: flex;
}

.chat-input {
  flex: 1;
  resize: none;
  min-height: 32px;
  max-height: 140px;
  overflow-y: auto;
  border-radius: 6px;
  border: none;
  padding: 6px 30px 6px 8px;
  background-color: var(--input-color);
  color: var(--font-color-clear);
  font-family: inherit;
  font-size: 13px;
}
.chat-input:disabled {
  opacity: 0.6;
}

/* Overlaid on .chat-input's own bottom-right corner — same "button overlaid
   on an input" trick as .header-search-clear-btn/.header-search-input
   (styles.css/webSearch.js), just anchored to the bottom (not vertically
   centered) since this textarea grows upward as its content wraps
   (max-height: 140px above) while staying anchored to the input row's own
   baseline. */
.chat-emoji-btn {
  position: absolute;
  right: 10px;
  bottom: 8px;
  border: none;
  background: none;
  color: var(--secondary-color);
  opacity: 0.7;
  font-size: 14px;
  line-height: 1;
  padding: 0;
}
.chat-emoji-btn:hover {
  opacity: 1;
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}

.chat-send-btn {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: none;
  border-radius: 6px;
  background-color: var(--main-theme-lighter);
  color: var(--secondary-color);
  display: flex;
  align-items: center;
  justify-content: center;
}
.chat-send-btn:not(:disabled):hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
.chat-send-btn:disabled {
  opacity: 0.6;
}

/* Same box shape as .chat-send-btn (both flex siblings in
   .chat-input-wrapper) but a plainer, unfilled look — a secondary action,
   not the primary send button it sits beside. */
.chat-attach-btn {
  width: 32px;
  height: 32px;
  flex-shrink: 0;
  border: none;
  border-radius: 6px;
  background: none;
  color: var(--secondary-color);
  opacity: 0.8;
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 14px;
}
.chat-attach-btn:hover {
  opacity: 1;
  color: var(--highlight);
  background-color: var(--main-theme-lighter);
  cursor: var(--cursor-pointer);
}

/* The picked/pasted file waiting to be sent (stageChatAttachment, chat.js) —
   sits directly above .chat-input-wrapper, same rounded-card language as
   .chat-message-attachment-file below rather than a new visual style. */
.chat-staged-attachment {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  padding: 4px 8px;
  background-color: var(--main-theme);
  border-radius: 8px;
  flex-shrink: 0;
  font-size: 12px;
  color: var(--font-color-clear);
}
.chat-staged-attachment img {
  width: 28px;
  height: 28px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}
.chat-staged-attachment i {
  color: var(--secondary-color);
  width: 28px;
  text-align: center;
  font-size: 16px;
  flex-shrink: 0;
}
.chat-staged-attachment-name {
  flex: 1;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-staged-attachment-remove {
  flex-shrink: 0;
  border: none;
  background: none;
  color: var(--font-color-clear);
  opacity: 0.7;
  padding: 2px;
}
.chat-staged-attachment-remove:hover {
  opacity: 1;
  color: var(--priority-one, #ff1414);
  cursor: var(--cursor-pointer);
}

/* One message's own attachment (buildChatAttachmentElement, chat.js) —
   sits below .chat-message-text, inside the same bubble. Border/cursor/
   click-to-open-full-size still come from .desc-image-thumb (shared/styles/
   descriptionTypes.css, carried on the same element) - this only adds the
   chat-specific spacing below the message text, plus its own fixed size
   (below), deliberately overriding .desc-image-thumb's own sizing instead
   of just leaving it alone the way this rule used to.
   width/height: 120px/80px, not desc-image-thumb's own max-width/max-height
   + width/height: auto (which size the box to the image's own natural
   dimensions, once known) - a real photo's own real size isn't known until
   either resolveChatAttachmentUrl resolves a real src (async - the img has
   no size at all before that) or, even once a src is set, until the browser
   actually finishes decoding it - both are genuine reflows the previous,
   content-driven sizing left nothing to fix, confirmed as a real, reported
   bug (the whole message bubble visibly jumping/flickering as an attachment
   loads). A fixed box here reserves the exact same footprint from the very
   first frame, before any src is even set, all the way through actually
   finishing - object-fit: contain (desc-image-thumb's own rule) still fits
   the real image inside it without distorting it, just possibly letterboxed
   if its own aspect ratio doesn't match. .chat-message-attachment-expired
   below matches these same dimensions - together, every one of an
   attachment's three possible states (still loading, loaded, failed/
   expired) occupies the exact same space, so nothing here ever needs to
   reflow switching between them. .desc-image-thumb.chat-message-attachment-
   image (compound, not a bare .chat-message-attachment-image) so this wins
   over that shared rule's own conflicting width/height regardless of which
   of the two files happens to load first - specificity, not load order,
   settles it. */
.desc-image-thumb.chat-message-attachment-image {
  margin-top: 6px;
  width: 120px;
  height: 80px;
}
.chat-message-attachment-file {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 6px;
  padding: 5px 8px;
  background-color: var(--main-theme-darker);
  border-radius: 6px;
  color: var(--secondary-color);
  font-size: 12px;
  text-decoration: none;
  width: fit-content;
  max-width: 100%;
}
.chat-message-attachment-file span {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.chat-message-attachment-file:hover {
  color: var(--highlight);
  cursor: var(--cursor-pointer);
}
/* Same fixed 120x80 footprint as .desc-image-thumb.chat-message-attachment-
   image above (that rule's own comment has the fuller "why a fixed size at
   all" reasoning) - this is what an image attachment's own <img> gets
   replaced by (buildChatAttachmentLoadFailedElement's own img.replaceWith
   call, chat.js) on a genuine load failure, or renders as from the very
   start instead of an <img> at all (attachment.expired, same file) -
   matching its size here is what actually stops either swap from visibly
   resizing the message bubble around it. Centered (not the plain left-
   aligned italic line this used to be) so the text reads properly in the
   middle of a box this much bigger now, instead of clinging to one corner
   of it; box-sizing: border-box keeps its own padding from pushing the
   rendered box past that same fixed 120x80. */
.chat-message-attachment-expired {
  margin-top: 6px;
  width: 120px;
  height: 80px;
  box-sizing: border-box;
  padding: 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  background-color: var(--main-theme-darker);
  border-radius: 6px;
  font-size: 12px;
  font-style: italic;
  opacity: 0.6;
}

/* Unread indicator — same dot pattern as .tasks-changelog-btn.has-update
   (tasks/styles/tasksChangelog.css), reused here rather than shared since
   both are small pieces of state co-located with their own feature.
   No position: relative needed here — .chat-btn is already position: fixed
   (styles/styles.css), which already establishes a containing block for
   this ::after; redeclaring position here previously overrode that fixed
   rule instead (same specificity, this file loads later), dropping the
   button back into normal document flow. Fixed red (--chat-user-color-red,
   above), not --secondary-color — same "shouldn't depend on the viewer's
   own customizable accent" reasoning as the per-author message colors, and
   "unread" reads as more urgent in a fixed, always-red dot than in
   whatever color someone happens to have picked. Bigger (14px, was 6px)
   and nudged in from the corner (top/right 2px, was 4px) so it still clears
   the button's own edge at this size instead of hanging half off it. */
.chat-btn.has-update::after {
  content: "";
  position: absolute;
  top: -4px;
  right: -4px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background-color: var(--chat-user-color-red);
  border: 2px solid var(--main-theme-darker);
}

/* "You have invitation news" — a pending incoming request, or one of your
   own outgoing invites just got accepted (hasUnseenAcceptedOutgoingInvite/
   syncChatRequestsBadge, chat.js). Mirrored on the LEFT (not right) in the
   fixed invitation yellow (--chat-user-color-yellow, not the messages-only
   red above) specifically so it can never be mistaken for, or visually
   collide with, .has-update's own unread-message dot on the opposite
   corner — both are meant to be able to light up on this same button at
   once, independently, without either hiding the other. */
.chat-btn.has-invite-update::before {
  content: "";
  position: absolute;
  top: -4px;
  left: -4px;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background-color: var(--chat-user-color-yellow);
  border: 2px solid var(--main-theme-darker);
}

/* Same glow technique as a favorited task's watermark
   (.task-card-container:has(.fa-star.rotate) .card-category-watermark,
   shared/styles/taskCardActions.css) — layered, increasingly-blurred
   text-shadow rings — applied to the chat icon glyph itself instead, in the
   same fixed red as the dot above so the whole "you have an unread message"
   signal reads as one consistent color. */
.chat-btn.has-update .btn-icon i {
  text-shadow:
    0 0 3px var(--chat-user-color-red),
    0 0 6px var(--chat-user-color-red),
    0 0 12px var(--chat-user-color-red),
    0 0 20px var(--chat-user-color-red);
}

/* "A background poll just happened" feedback (pulseChatButton, chat.js) —
   the only chat-related element that's on screen regardless of whether the
   panel itself is open, so it's the one place a poll tick can always show
   itself. Animates opacity, not transform — .chat-btn already carries its
   own transform (the .tasks-panel-shift translateX above) whenever a side
   panel is docked, and an animation touching the same property would fight
   that instead of layering on top of it. 500ms to match the setTimeout that
   removes the class again in pulseChatButton. */
@keyframes chat-btn-poll-pulse {
  0%,
  100% {
    opacity: 1;
  }
  50% {
    opacity: 0.45;
  }
}
.chat-btn.polling .btn-icon i {
  animation: chat-btn-poll-pulse 500ms ease;
}

/* --------------------------------------------------- */
/*                    EMOJI PICKER                      */
/* --------------------------------------------------- */
/* Appended to document.body, not .chat-panel (shared/scripts/chat.js) —
   .chat-panel is overflow: hidden, which would clip this popup whenever the
   panel is small or docked near a screen edge. position: fixed + a z-index
   above the panel's own (999) so it always renders on top regardless of
   where it lands. */
.chat-emoji-picker {
  position: fixed;
  z-index: 1000;
  display: grid;
  grid-template-columns: repeat(6, 1fr);
  gap: 2px;
  padding: 6px;
  background-color: var(--main-theme);
  border: 1px solid var(--secondary-color);
  border-radius: 8px;
  box-shadow: 0 6px 16px rgba(0, 0, 0, 0.5);
}
.chat-emoji-picker-item {
  border: none;
  background: none;
  font-size: 20px;
  line-height: 1;
  padding: 4px;
  border-radius: 4px;
}
.chat-emoji-picker-item:hover {
  background-color: var(--main-theme-lighter);
  cursor: var(--cursor-pointer);
}
/* Between each curated group (CHAT_EMOJI_GROUPS, chat.js) - grid-column:
   1 / -1 spans every one of the grid's own 6 columns regardless of exactly
   how many columns wide it actually is, so this always reads as one full-
   width rule rather than sitting inside a single cell. */
.chat-emoji-picker-divider {
  grid-column: 1 / -1;
  height: 1px;
  margin: 4px 0;
  background-color: var(--secondary-color);
  opacity: 0.3;
}

/* --------------------------------------------------- */
/*              ICON SHARE NOTIFICATION BANNER          */
/* --------------------------------------------------- */
/* Driven by pollIconShareNotifications/showIconShareBanner, chat.js — same
   shell/entrance as .update-available-banner (shared/styles/threads.css),
   just its own vertical slot (that one already claims bottom: 70px) since
   the two could in theory both be up at once. */
.icon-share-banner {
  position: fixed;
  left: 50%;
  bottom: 130px;
  z-index: 9999;
  transform: translate(-50%, 120px) scale(0.85);
  display: flex;
  align-items: center;
  gap: 12px;
  max-width: 90vw;
  padding: 10px 14px;
  background-color: var(--secondary-color);
  border-radius: 8px;
  border: 1px solid var(--secondary-color);
  box-shadow: 0px 8px 24px rgba(0, 0, 0, 0.5);
  color: var(--font-color-clear);
  font-size: 12px;
  opacity: 0;
  pointer-events: none;
  transition:
    opacity 400ms ease,
    transform 400ms cubic-bezier(0.34, 1.56, 0.64, 1),
    background-color 600ms ease;
}
.icon-share-banner.visible {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  background-color: var(--main-theme-lighter);
  pointer-events: auto;
}
.icon-share-banner > i.fa-share-nodes {
  color: var(--secondary-color);
  font-size: 16px;
  flex: none;
}
.icon-share-banner-text {
  white-space: nowrap;
}
.icon-share-banner-show {
  flex: none;
  border: none;
  border-left: 2px solid var(--secondary-color);
  border-right: 2px solid var(--secondary-color);
  border-radius: 10px;
  background-color: var(--main-theme-darker);
  color: var(--font-color);
  padding: 5px 12px;
  font-size: 12px;
  cursor: var(--cursor-pointer, pointer);
  user-select: none;
}
.icon-share-banner-show:hover {
  color: var(--highlight);
}
.icon-share-banner-dismiss {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 22px;
  height: 22px;
  border: none;
  background: none;
  color: var(--font-color);
  cursor: var(--cursor-pointer, pointer);
}
.icon-share-banner-dismiss:hover {
  color: var(--highlight);
}
@media (max-width: 600px) {
  .icon-share-banner {
    flex-wrap: wrap;
    justify-content: center;
  }
  .icon-share-banner-text {
    white-space: normal;
    text-align: center;
  }
}
