/* ============================================================================
   TalkLive - chat extras (reply / emoji / GIF / reactions).

   Shared by the voice app (style.css) and the text app (chat.css). It only ever
   reads theme tokens both stylesheets define - --accent, --text, --muted,
   --bg-1, --bg-2 - and supplies a literal fallback for the two that only one of
   them has, so it drops into either page unchanged.

   Everything animates on transform/opacity only, and nothing here sets a
   filter, blur or box-shadow on a scrolling surface: this has to stay smooth on
   a cheap phone, where a backdrop-filter on the message list is the single
   easiest way to drop a chat to 15fps.
   ========================================================================== */

.cx-host { flex: 0 0 auto; }

/* --- composer buttons ----------------------------------------------------- */
.cx-btn {
  flex: 0 0 auto;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  min-width: 40px;
  height: 40px;
  padding: 0 8px;
  border: 0;
  border-radius: 999px;
  background: transparent;
  color: var(--muted);
  font-size: 19px;
  line-height: 1;
  cursor: pointer;
  transition: transform 0.12s ease, background 0.15s ease, color 0.15s ease;
}
.cx-btn:active { transform: scale(0.92); }
.cx-btn.on { background: var(--accent-soft, rgba(108, 92, 231, 0.16)); color: var(--accent); }
.cx-gif-btn { font-size: 12.5px; font-weight: 800; letter-spacing: 0.04em; }

/* Every component here is toggled with the `hidden` property, and each one also
   sets its own `display` - which beats the user agent's low-specificity
   [hidden] rule. Stating it once keeps a hidden component actually hidden. */
.cx-host [hidden], .cx-menu[hidden] { display: none !important; }

/* --- reply bar above the composer ---------------------------------------- */
.cx-reply-bar {
  display: flex;
  align-items: center;
  gap: 10px;
  padding: 8px 12px;
  border-top: 1px solid var(--line, rgba(138, 144, 168, 0.16));
  background: var(--bg-2);
}
.cx-reply-bar::before {
  content: '';
  flex: 0 0 3px;
  align-self: stretch;
  border-radius: 3px;
  background: var(--accent);
}
.cx-reply-body { flex: 1 1 auto; min-width: 0; }
.cx-reply-to {
  display: block;
  font-size: 11.5px;
  font-weight: 700;
  color: var(--accent);
}
.cx-reply-text {
  display: block;
  font-size: 13px;
  color: var(--muted);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.cx-reply-cancel {
  flex: 0 0 auto;
  width: 30px;
  height: 30px;
  border: 0;
  border-radius: 50%;
  background: transparent;
  color: var(--muted);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
}

/* --- emoji / GIF panel ---------------------------------------------------- */
.cx-panel {
  height: min(46vh, 290px);
  overflow: hidden;
  border-top: 1px solid var(--line, rgba(138, 144, 168, 0.16));
  background: var(--bg-2);
}
.cx-emoji,
.cx-gif {
  height: 100%;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}

.cx-emoji-head {
  position: sticky;
  top: 0;
  z-index: 1;
  padding: 8px 12px 4px;
  font-size: 11px;
  font-weight: 800;
  letter-spacing: 0.06em;
  text-transform: uppercase;
  color: var(--muted);
  background: var(--bg-2);
}
.cx-emoji-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(42px, 1fr));
  padding: 0 6px 6px;
}
.cx-emoji-cell {
  border: 0;
  background: none;
  padding: 0;
  height: 42px;
  font-size: 25px;
  line-height: 1;
  cursor: pointer;
  border-radius: 10px;
  transition: transform 0.1s ease, background 0.12s ease;
}
.cx-emoji-cell:active { transform: scale(0.86); background: var(--accent-soft, rgba(108, 92, 231, 0.16)); }

.cx-gif-bar {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 10px;
  position: sticky;
  top: 0;
  z-index: 1;
  background: var(--bg-2);
}
.cx-gif-search {
  flex: 1 1 auto;
  min-width: 0;
  border: 1px solid var(--line, rgba(138, 144, 168, 0.16));
  border-radius: 999px;
  background: var(--bg-1);
  color: var(--text);
  padding: 9px 14px;
  font-size: 16px; /* >=16px keeps iOS from zooming the page on focus */
}
.cx-gif-search:focus { outline: 1px solid var(--accent); }
.cx-gif-credit { margin: 0; padding: 0 10px 10px; text-align: right; font-size: 10.5px; color: var(--muted); }
.cx-gif-status { margin: 0; padding: 14px; text-align: center; font-size: 13px; color: var(--muted); }
.cx-gif-status[hidden] { display: none; }
.cx-gif-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(104px, 1fr));
  gap: 6px;
  padding: 0 8px 10px;
}
.cx-gif-tile {
  border: 0;
  padding: 0;
  margin: 0;
  /* Fallback for browsers without aspect-ratio (older WebViews): the tile still
     has a box to fill instead of collapsing to nothing. */
  min-height: 90px;
  border-radius: 10px;
  overflow: hidden;
  background: var(--bg-1);
  cursor: pointer;
  transition: transform 0.12s ease;
}
.cx-gif-tile:active { transform: scale(0.95); }
.cx-gif-tile img { display: block; width: 100%; height: 100%; object-fit: cover; }

/* --- quoted reply inside a bubble ----------------------------------------- */
.cx-quote {
  display: block;
  width: 100%;
  margin-bottom: 5px;
  padding: 4px 8px;
  border-left: 3px solid currentColor;
  border-radius: 6px;
  background: rgba(255, 255, 255, 0.12);
  font-size: 12.5px;
  line-height: 1.3;
  opacity: 0.9;
  cursor: pointer;
}
[data-theme="light"] .cx-quote { background: rgba(0, 0, 0, 0.07); }
.cx-quote-who { display: block; font-weight: 700; font-size: 11px; }
.cx-quote-text {
  display: block;
  max-height: 2.6em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

/* A replied-to or GIF bubble stacks vertically instead of sitting on one line. */
.cx-has-quote, .cx-has-gif, .cx-has-reactions {
  display: flex;
  flex-direction: column;
  align-items: stretch;
}
.cx-gif-img {
  display: block;
  width: 100%;
  max-width: 220px;
  border-radius: 12px;
  margin-top: 2px;
  background: rgba(127, 127, 127, 0.18);
}
/* The voice app's delivery ticks sit inline at the end of a one-line bubble;
   once the bubble is a column they need pinning back to the trailing edge. */
.cx-has-quote > .chat-msg-ticks,
.cx-has-gif > .chat-msg-ticks,
.cx-has-reactions > .chat-msg-ticks { align-self: flex-end; }

/* Flash the target when a quote is tapped, so "which message was that" is
   answered without scrolling hunting. */
@keyframes cxFlash { 0%, 100% { filter: none; } 40% { filter: brightness(1.45); } }
.cx-flash { animation: cxFlash 0.9s ease; }

/* --- reaction chips ------------------------------------------------------- */
.cx-reactions {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
  margin: 5px -4px -3px;
}
.cx-reaction {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  border: 1px solid transparent;
  border-radius: 999px;
  padding: 1px 7px;
  background: rgba(255, 255, 255, 0.16);
  color: inherit;
  font-size: 12.5px;
  line-height: 1.5;
  cursor: pointer;
}
[data-theme="light"] .cx-reaction { background: rgba(0, 0, 0, 0.08); }
.cx-reaction.own { border-color: currentColor; }

/* --- gestures: swipe to reply, double tap to heart ------------------------ */
/* Every decorated bubble, and only those: system lines are never registered,
   so they never get a position, a transition or a gesture. */
[data-cx-id] {
  position: relative;
  z-index: 1;
  /* Vertical scrolling stays with the browser; horizontal is ours, and this is
     also what removes the 300ms double-tap-to-zoom delay on the heart. */
  touch-action: pan-y;
  transition: transform 0.22s cubic-bezier(0.22, 1, 0.36, 1);
}
/* While a finger is on it the bubble tracks the finger exactly - a transition
   here would make the drag feel like it is lagging behind the touch. */
[data-cx-id].cx-swiping { transition: none; }

/* The reply glyph sits in the list behind the bubbles and is uncovered as one
   slides off it, so nothing has to be inserted into the message itself. */
.cx-swipe-hint {
  position: absolute;
  z-index: 0;
  width: 30px;
  height: 30px;
  display: none;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: rgba(127, 127, 127, 0.16);
  color: var(--muted, #8a90a8);
  opacity: 0;
  transform: translateY(-50%) scale(0.6);
  pointer-events: none;
}
.cx-swipe-hint.is-visible { display: flex; }
.cx-swipe-hint svg { width: 17px; height: 17px; display: block; }
/* Armed: release now and the reply lands. The colour change is the whole
   promise of the gesture, so it is the accent and not a shade of grey. */
.cx-swipe-hint.is-armed {
  background: var(--accent-soft, rgba(108, 92, 231, 0.16));
  color: var(--accent, #6c5ce7);
}

/* The heart the double tap throws: it rises out of the bubble it was aimed at,
   because the chip it leaves behind lands under the message where nobody is
   looking at that moment. */
.cx-heart-burst {
  position: absolute;
  left: 50%;
  top: 50%;
  z-index: 2;
  font-size: 30px;
  line-height: 1;
  pointer-events: none;
  transform: translate(-50%, -50%);
  animation: cxHeartPop 0.85s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
@keyframes cxHeartPop {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.3); }
  35%  { opacity: 1; transform: translate(-50%, -62%) scale(1.15); }
  60%  { opacity: 1; transform: translate(-50%, -76%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -128%) scale(0.85); }
}

/* --- long-press action menu ----------------------------------------------- */
.cx-menu {
  position: fixed;
  z-index: 2000;
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding: 6px;
  border: 1px solid var(--line, rgba(138, 144, 168, 0.16));
  border-radius: 14px;
  background: var(--bg-2);
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.35);
  animation: cxMenuIn 0.13s ease both;
}
@keyframes cxMenuIn { from { opacity: 0; transform: scale(0.92); } to { opacity: 1; transform: none; } }
.cx-menu-reactions { display: flex; gap: 2px; }
.cx-menu-react {
  width: 40px;
  height: 40px;
  border: 1px solid transparent;
  border-radius: 50%;
  background: none;
  font-size: 22px;
  line-height: 1;
  cursor: pointer;
  transition: transform 0.12s ease;
}
.cx-menu-react:active { transform: scale(1.18); }
.cx-menu-react.on { border-color: var(--accent); background: var(--accent-soft, rgba(108, 92, 231, 0.16)); }
.cx-menu-item {
  border: 0;
  border-radius: 9px;
  background: none;
  color: var(--text);
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  padding: 9px 12px;
  cursor: pointer;
}
.cx-menu-item:active { background: var(--accent-soft, rgba(108, 92, 231, 0.16)); }

@media (prefers-reduced-motion: reduce) {
  .cx-menu, .cx-flash { animation: none; }
  .cx-btn, .cx-emoji-cell, .cx-gif-tile, .cx-menu-react { transition: none; }
  /* The swipe itself stays - it is direct manipulation, not decoration - but
     the spring back and the rising heart are both motion for motion's sake.
     The heart still appears and still fades, so the gesture is acknowledged. */
  [data-cx-id] { transition: none; }
  .cx-heart-burst { animation: cxHeartFade 0.85s ease forwards; }
  @keyframes cxHeartFade {
    0%, 70% { opacity: 1; transform: translate(-50%, -50%) scale(1); }
    100% { opacity: 0; transform: translate(-50%, -50%) scale(1); }
  }
}
