/* Apps::Chat — view-specific styles. */

/*
 * Online presence dot
 *
 * Avoid Bootstrap's `.rounded-circle` here — custom.css overrides that
 * utility with `min-width: 40px; min-height: 40px;`, which inflates the
 * dot to avatar size. This dedicated class keeps the dot tiny and
 * positioned at the avatar's bottom-right corner.
 *
 * Visibility is JS-driven: server renders with `.d-none`; the
 * `apps--chat--online-status` Stimulus controller toggles `.is-online`
 * (and `.d-none`) per user based on /apps/chat/presence/online.
 */
.chat-online-dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background-color: #198754; /* bg-success */
  border: 1px solid #fff;
  pointer-events: none;
}

.chat-online-dot--lg {
  width: 10px;
  height: 10px;
  border-width: 2px;
}

/*
 * Reactions row
 *
 * Server-rendered initial state lives in
 * `app/views/apps/chat/messages/_reaction_pills.html.erb`. Live updates land
 * via Pusher `message:reacted` and replace the row's innerHTML wholesale.
 * Mirrors the visual language of `_platform_reaction_bar` (small pill with
 * emoji + count) but without the platform partial's tooltip / picker
 * scaffolding — the chat picker has its own floating pill UI.
 */
.chat-message__reactions {
  min-height: 1.75rem;
}

.chat-message__reactions form.d-inline {
  margin: 0;
  padding: 0;
  display: inline-flex;
}

.chat-reaction-pill {
  display: inline-flex;
  align-items: center;
  padding: 2px 8px;
  border-radius: 999px;
  border: 1px solid var(--bs-border-color, #dee2e6);
  background: #fff;
  line-height: 1;
  font-size: 0.875rem;
}

.chat-reaction-pill--mine {
  background: rgba(25, 135, 84, 0.12); /* bg-success-subtle */
  border-color: rgba(25, 135, 84, 0.5);
  color: #157347;
}

.chat-reaction-pill__emoji {
  font-size: 1rem;
}

.chat-reaction-pill__count {
  font-size: 0.8125rem;
}

/*
 * Add-reaction trigger + floating picker pill
 *
 * The trigger is a small icon button kept visible inside `.chat-message`
 * but de-emphasized; on hover (or when the message itself is hovered) it
 * lights up. The floating pill is positioned above the trigger by inline
 * styles in the partial; this rule only covers visual chrome.
 */
.chat-reaction-add-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 28px;
  height: 28px;
  padding: 0;
  border-radius: 999px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--bs-secondary-color, #6c757d);
  opacity: 0.55;
  transition: opacity 120ms ease, background-color 120ms ease;
}

.chat-reaction-add-btn:hover,
.chat-reaction-add-btn:focus,
.chat-message:hover .chat-reaction-add-btn {
  opacity: 1;
  background: var(--bs-light-bg-subtle, #f8f9fa);
  border-color: var(--bs-border-color, #dee2e6);
}

.chat-reaction-add-btn__plus {
  position: absolute;
  bottom: -2px;
  right: -2px;
  font-size: 0.55rem;
  background: #fff;
  border-radius: 999px;
  padding: 1px;
  line-height: 1;
}

.chat-reaction-picker {
  display: inline-flex;
  align-items: center;
  gap: 2px;
}

.chat-reaction-picker .reaction-pill-btn {
  width: 32px;
  height: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  background: transparent;
  border-radius: 999px;
  transition: background-color 120ms ease, transform 120ms ease;
}

.chat-reaction-picker .reaction-pill-btn:hover {
  background: var(--bs-light-bg-subtle, #f8f9fa);
  transform: scale(1.15);
}

.chat-reaction-picker .reaction-pill-btn--mine {
  background: rgba(25, 135, 84, 0.18);
}

.chat-reaction-picker .reaction-emoji {
  font-size: 1.1rem;
  line-height: 1;
}

/*
 * Attachments row + cards
 *
 * Server-rendered initial state lives in
 * `app/views/apps/chat/messages/_attachments.html.erb`. Live-broadcast
 * state is rendered by `room_controller.js#_renderAttachments`. The two
 * MUST emit byte-aligned HTML for these styles to apply identically.
 *
 * Image attachments render the :preview variant (800px max dimension via
 * libvips) inline, capped to a max-width that fits the message column.
 * Non-image attachments render as a small card with FA icon + filename
 * link + size.
 */
.chat-message__attachments {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.chat-attachment {
  max-width: 100%;
}

.chat-attachment--image {
  max-width: 360px;
}

.chat-attachment__img {
  display: block;
  max-width: 100%;
  max-height: 320px;
  height: auto;
  border-radius: 8px;
  object-fit: cover;
}

.chat-attachment__caption {
  margin-top: 4px;
  font-size: 0.75rem;
  /* Long, unbroken filenames (e.g. underscore_heavy_names) must wrap inside the
     image/video attachment column instead of overflowing the message bubble. */
  overflow-wrap: anywhere;
  word-break: break-word;
}

.chat-attachment--file {
  max-width: 320px;
  background: #fff;
}

/* The file card's filename column uses `min-w-0` in markup, but Bootstrap 5
   ships no `.min-w-0` utility (only `.min-width-0` exists in this project). So
   the inner flex child keeps the default `min-width: auto`, refuses to shrink,
   and the `.text-truncate` filename overflows the 320px card and breaks out of
   the message column. Force the shrink here so truncation actually engages.
   Applies to server-, optimistic-, and live-rendered cards (same markup). */
.chat-attachment--file .flex-grow-1 {
  min-width: 0;
}

.chat-attachment__name {
  text-decoration: none;
  font-size: 0.9rem;
  color: var(--bs-body-color, #212529);
}

.chat-attachment__name:hover {
  text-decoration: underline;
}

/*
 * Optimistic-bubble variants — the local createObjectURL preview lives
 * here while the upload is in flight. The faded look hints at "not yet
 * sent" without flashing a hard placeholder. Once the live message
 * arrives, the entire optimistic <li> is replaced (see
 * room_controller.js#_onMessageCreated) and `URL.revokeObjectURL` is
 * fired on each `data-blob-object-url="true"` <img> so the browser
 * frees the file from memory.
 */
.chat-attachment--optimistic {
  opacity: 0.85;
}

/*
 * Whole-room drag-drop overlay (Slack/Discord pattern)
 *
 * Driven by `apps--chat--drop-zone` Stimulus controller. Hidden by
 * default; the controller toggles `.is-active` (and Bootstrap's
 * `.d-none`) on dragenter / dragleave / drop. Sits absolutely over the
 * room container so the user can drop anywhere inside the room — not
 * just on the small dropzone in the attachments panel.
 */
.chat-drop-overlay {
  position: absolute;
  inset: 0;
  z-index: 1050;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(13, 110, 253, 0.10);
  border: 3px dashed rgba(13, 110, 253, 0.55);
  border-radius: 0.5rem;
  pointer-events: none; /* allow the underlying drop event to fire on the room */
  transition: opacity 120ms ease;
}

.chat-drop-overlay__inner {
  text-align: center;
  color: #0a58ca;
  background: rgba(255, 255, 255, 0.92);
  padding: 1.5rem 2rem;
  border-radius: 0.5rem;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
}

.chat-drop-overlay__title {
  font-size: 1.25rem;
  font-weight: 600;
}

.chat-drop-overlay__hint {
  margin-top: 0.25rem;
  color: #495057;
}

/*
 * Threaded replies (Phase 3.4)
 *
 * Two surfaces:
 *   1. The Reply button + replies chip on each top-level message bubble
 *      in the main timeline.
 *   2. The offcanvas-end thread panel (chat-thread-offcanvas) that opens
 *      when the user clicks Reply or the chip.
 *
 * The Reply button uses the same opacity-on-hover idiom as
 * .chat-reaction-add-btn — keeps the bubble chrome quiet at rest, lights
 * up when the user is interacting with the message. Replies chip stays
 * visible whenever a count > 0; clicking opens the same panel.
 */

.chat-message__actions {
  display: flex;
  align-items: center;
  gap: 0.25rem;
}

.chat-reply-btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  padding: 0.15rem 0.55rem;
  border-radius: 999px;
  background: transparent;
  border: 1px solid transparent;
  color: var(--bs-secondary-color, #6c757d);
  opacity: 0.55;
  transition: opacity 120ms ease, background-color 120ms ease, border-color 120ms ease;
  font-size: 0.8rem;
  line-height: 1.2;
}

.chat-reply-btn:hover,
.chat-reply-btn:focus,
.chat-message:hover .chat-reply-btn {
  opacity: 1;
  background: var(--bs-light-bg-subtle, #f8f9fa);
  border-color: var(--bs-border-color, #dee2e6);
}

.chat-replies-chip-btn {
  text-decoration: none;
  font-size: 0.85rem;
  color: var(--bs-primary, #0d6efd);
}

.chat-replies-chip-btn:hover {
  text-decoration: underline;
}

.chat-replies-chip-btn__label {
  margin-left: 0.25rem;
}

/*
 * Thread offcanvas — wider than the default 400px (we render full message
 * bubbles with attachments inside, which need horizontal room). Capped at
 * 95vw on tiny viewports per Bootstrap convention.
 */
.chat-thread-offcanvas {
  width: 480px;
  max-width: 95vw;
}

.chat-thread-panel {
  min-height: 100%;
}

.chat-thread-panel__parent {
  background: var(--bs-light-bg-subtle, #f8f9fa);
}

.chat-thread-panel__empty {
  color: var(--bs-secondary-color, #6c757d);
}

/*
 * Message rows — match the MangoApps desktop messenger client.
 *
 * The server partial (app/views/apps/chat/messages/_message.html.erb) and the
 * live JS renderer (room_controller.js #_renderMessage / #_renderOptimistic)
 * emit the SAME shape, so these rules style both render paths identically
 * (KEEP IN LOCKSTEP):
 *
 *     <li class="chat-message chat-message--mine|--theirs">
 *       <div class="chat-message__meta"> sender name </div>      ← above bubble
 *       <div class="chat-message__row">                          ← horizontal
 *         <div class="chat-message__body"> … </div>              ← the bubble
 *         <div class="chat-message__hover">                      ← hover cluster
 *           <div class="chat-message__reactions">…</div>            pills + ☺+
 *           <div class="chat-message__menu dropdown">…</div>        ⋮ menu
 *           <div class="chat-message__time"><time>…</time> ✓</div>  time + tick
 *         </div>
 *       </div>
 *       [attachments] [replies-chip] [seen-by]
 *     </li>
 *
 * Layout: the <li> is a flex column (meta, row, attachments, chip, seen-by).
 * `.chat-message__row` is a full-width horizontal flex — bubble on the outer
 * edge, hover cluster on the inner side. Mine mirrors theirs via
 * `flex-direction: row-reverse`, so the cluster order (reactions → menu → time)
 * reads as time → menu → reactions on the sent side, like the reference client.
 * The cluster is hidden at rest and revealed on hover/focus; it reserves its
 * width so revealing it never reflows the timeline.
 */
.chat-message {
  display: flex;
  flex-direction: column;
  align-items: flex-start; /* theirs: hug the left edge */
  max-width: 100%;
  margin-bottom: 0.4rem;
}

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

.chat-message--mine {
  align-items: flex-end; /* mine: hug the right edge */
}

/* Sender name above the bubble — group/channel received messages only. */
.chat-message__meta {
  margin-bottom: 0.15rem;
  padding: 0 0.25rem;
  font-size: 0.75rem;
  line-height: 1.2;
}

.chat-message__author {
  font-weight: 600;
  color: var(--bs-body-color, #212529);
}

/* Own messages + 1:1 DMs don't repeat a name on every bubble (alignment / tint
   signal "mine"; the DM header already names the other person). The room
   container carries the type attribute, so the DM rule needs no DOM change. */
.chat-message--mine .chat-message__meta,
[data-apps--chat--room-room-type-value="direct"] .chat-message__meta {
  display: none;
}

/* The horizontal row: bubble + hover cluster. */
.chat-message__row {
  display: flex;
  width: 100%;
  align-items: center;
  gap: 0.5rem;
}

.chat-message--mine .chat-message__row {
  flex-direction: row-reverse;
}

/* The bubble. max-width leaves ~170px for the hover cluster so the row never
   overflows the (narrower) group/channel column. */
.chat-message__body {
  max-width: min(560px, calc(100% - 170px));
  padding: 0.45rem 0.7rem;
  border-radius: 12px 12px 12px 4px; /* theirs: squared tail bottom-left */
  background: var(--bs-secondary-bg, #f1f3f5);
  color: var(--bs-body-color, #212529);
  text-align: left;
  line-height: 1.4;
  word-break: break-word;
  overflow-wrap: anywhere;
}

.chat-message--mine .chat-message__body {
  border-radius: 12px 12px 4px 12px; /* mine: squared tail bottom-right */
  background: var(--bs-primary-bg-subtle, #cfe2ff);
}

/* render_message_body runs simple_format → <p>. Strip the default trailing
   margin; keep a small gap between paragraphs in multi-line messages. */
.chat-message__body p {
  margin-bottom: 0;
}

.chat-message__body p + p {
  margin-top: 0.4rem;
}

/*
 * Hover cluster — reactions + ⋮ menu + time/tick. Hidden at rest, revealed on
 * hover/focus (and kept visible while the dropdown / reaction picker is open).
 * `visibility:hidden` (not display:none) reserves the cluster's width so the
 * reveal is reflow-free. Mirrored for mine via row-reverse.
 */
.chat-message__hover {
  display: inline-flex;
  align-items: center;
  gap: 0.375rem;
  visibility: hidden;
  opacity: 0;
  transition: opacity 0.12s ease;
}

.chat-message--mine .chat-message__hover {
  flex-direction: row-reverse;
}

.chat-message:hover .chat-message__hover,
.chat-message:focus-within .chat-message__hover,
.chat-message:has(.dropdown-menu.show) .chat-message__hover,
.chat-message:has(.chat-reaction-picker:not([hidden])) .chat-message__hover {
  visibility: visible;
  opacity: 1;
}

/* Inline timestamp + sent tick. */
.chat-message__time {
  display: inline-flex;
  align-items: center;
  gap: 0.25rem;
  font-size: 0.75rem;
  color: var(--bs-secondary-color, #6c757d);
  white-space: nowrap;
}

.chat-message__tick {
  font-size: 0.7rem;
  color: var(--bs-secondary-color, #6c757d);
}

/* Control buttons (⋮ menu + ☺+ add-reaction) — small boxed icon buttons. The
   add-reaction button also has base rules higher up (round pill); these later
   rules win and square it off to match the menu button. */
.chat-message__menu-btn,
.chat-reaction-add-btn {
  width: 28px;
  height: 28px;
  padding: 0;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  border-radius: 6px;
  border: 1px solid var(--bs-border-color, #dee2e6);
  background: var(--bs-body-bg, #fff);
  color: var(--bs-secondary-color, #6c757d);
  line-height: 1;
  opacity: 1;
}

.chat-message__menu-btn:hover,
.chat-message__menu-btn:focus,
.chat-reaction-add-btn:hover,
.chat-reaction-add-btn:focus,
.chat-message:hover .chat-reaction-add-btn {
  background: var(--bs-tertiary-bg, #f8f9fa);
  border-color: var(--bs-border-color, #dee2e6);
  color: var(--bs-body-color, #212529);
}

.chat-message__menu .dropdown-menu {
  font-size: 0.875rem;
  min-width: 11rem;
}

/* The reactions row lives inside the inline cluster — drop its reserved
   min-height and the _reaction_pills wrapper's top margin so the controls sit
   centred on the bubble baseline. */
.chat-message__hover .chat-message__reactions {
  min-height: 0;
}

.chat-message__hover .chat-message__reactions > div {
  margin-top: 0 !important;
}

/* Optimistic (in-flight) bubble dims until the echo replaces it; a send failure
   tints it red. `--optimistic` / `--failed` set by room_controller.js. */
.chat-message--optimistic .chat-message__body {
  opacity: 0.7;
}

.chat-message--failed .chat-message__body {
  background: var(--bs-danger-bg-subtle, #f8d7da);
  border: 1px solid var(--bs-danger-border-subtle, #f1aeb5);
}

/*
 * Day-group date divider — a centred date with a hairline rule on each side.
 * Emitted by rooms/show.html.erb (server) and room_controller.js
 * (#_maybeInsertDateDivider) between messages whose calendar day differs.
 */
.chat-date-divider {
  display: flex;
  align-items: center;
  gap: 0.75rem;
  margin: 0.85rem 0;
  color: var(--bs-secondary-color, #6c757d);
  font-size: 0.78rem;
}

.chat-date-divider::before,
.chat-date-divider::after {
  content: "";
  flex: 1 1 auto;
  border-top: 1px solid var(--bs-border-color, #dee2e6);
}

.chat-date-divider span {
  flex: 0 0 auto;
}
 * Search page — highlighted snippet text from Postgres ts_headline.
 * Subtle yellow background so the matched terms read but the row layout
 * stays calm. Inherits font weight from the surrounding <small>. The
 * <mark> is emitted by ts_headline inside sanitized HTML, so it can't
 * carry a class — this element selector is the only way to style it.
 */
.chat-search-snippet mark {
  background: var(--bs-warning-bg-subtle, #fff3cd);
  color: var(--bs-warning-text-emphasis, #664d03);
  padding: 0 2px;
  border-radius: 2px;
}
