/* ============================================
   Speech-to-text: toast + dictation preview
   ============================================
   The mic BUTTON states (.stt-connecting, .listening, .stt-active, .stt-idle)
   keep living in each surface's own stylesheet — assets/js/stt/stt-bind.js
   paints exactly those classes. This file holds what is new with the shared
   rail: the error toast the engine shows, and the skin of the inline "ghost"
   preview the chat composers use (assets/js/stt/stt-ghost.js). The editor
   draft preview (stt-preview.js) is real text and needs no styling. */

/* ── Error toast (shown by the engine) ───────────────────────────────────── */

.ss-stt-toast {
  position: fixed;
  bottom: calc(env(safe-area-inset-bottom, 0px) + 16px);
  left: 50%;
  transform: translateX(-50%) translateY(20px);
  background: #14213d;
  color: #fff;
  font-family: Inter, system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  font-size: 14px;
  font-weight: 600;
  line-height: 1.4;
  padding: 10px 20px;
  border-radius: 12px;
  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.18);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.25s, transform 0.25s;
  z-index: 10500;
  max-width: calc(100vw - 32px);
  text-align: center;
}

.ss-stt-toast--visible {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

/* ── Inline dictation preview (ghost) ────────────────────────────────────────
   stt-ghost.js stamps .ss-stt-ghost-host + .chat-stt-ghosting on the box that
   holds the textarea and appends the overlay to it. The overlay is absolute,
   positioned by the script (text-indent = measured end of the committed text)
   so the spoken words continue the sentence INLINE, never as a phantom line
   below. Display-only: the words never enter the value until final. */

.ss-stt-ghost-host.chat-stt-ghosting {
  position: relative;
}

.ss-stt-ghost-host .chat-stt-interim {
  display: none;
}

.ss-stt-ghost-host .chat-stt-interim--visible {
  display: block;
  position: absolute;
  font-style: italic;
  color: #6b7280;
  pointer-events: none;
  user-select: none;
  white-space: pre-wrap;
  word-break: break-word;
  overflow: hidden;
  z-index: 1;
}

.ss-stt-ghost-host .chat-stt-interim-mirror {
  position: absolute;
  visibility: hidden;
  inset-inline-start: -9999px;
  top: 0;
  white-space: pre-wrap;
  word-break: break-word;
  pointer-events: none;
}

/* While dictating the ghost IS the composer text: a placeholder under it
   reads as double text. ID-level phantom selector so it beats any class
   chain a composer stylesheet pins with !important. */
.ss-stt-ghost-host.chat-stt-ghosting:not(#_stt) textarea::placeholder,
.ss-stt-ghost-host.chat-stt-ghosting:not(#_stt) input::placeholder {
  color: transparent !important;
}

/* The ghost draws its own caret at the tail of the spoken words; park the
   real one ONLY while the ghost is actually painting, so the field never
   shows two carets and never none. */
.ss-stt-ghost-host.chat-stt-ghosting:has(.chat-stt-interim--visible) textarea,
.ss-stt-ghost-host.chat-stt-ghosting:has(.chat-stt-interim--visible) input[type="text"] {
  caret-color: transparent;
}

.ss-stt-ghost-host .chat-stt-interim--visible::after {
  content: '';
  display: inline-block;
  width: 1px;
  height: 1em;
  margin-inline-start: 1px;
  vertical-align: -0.15em;
  background: #6b7280;
  animation: chat-stt-caret-blink 1.06s steps(2, start) infinite;
}

@keyframes chat-stt-caret-blink {
  0%, 49% { opacity: 1; }
  50%, 100% { opacity: 0; }
}

/* The composer's own min-height may be pinned with !important; growth to fit
   a wrapping ghost rides a scoped class + variable set by the script. */
.ss-stt-ghost-host.chat-stt-ghosting .chat-stt-grown {
  min-height: var(--stt-ghost-minh, 24px) !important;
}

/* The unfinished words are a soft blurred shape with a light-sweep — you see
   THAT words are coming, not which. Gradient-clip + blur sit on the WORD
   spans so the caret mark (::after on the element) stays sharp. */
.ss-stt-ghost-host .chat-stt-interim--visible .w {
  color: transparent;
  background-image: linear-gradient(100deg,
    #6b7280 0%,
    #6b7280 38%,
    #cbd5e1 50%,
    #6b7280 62%,
    #6b7280 100%);
  background-size: 220% 100%;
  -webkit-background-clip: text;
  background-clip: text;
  filter: blur(2.75px);
  opacity: 0.8;
  animation: chat-stt-shimmer 2s linear infinite;
}

@keyframes chat-stt-shimmer {
  from { background-position: 200% 0; }
  to { background-position: -20% 0; }
}

.ss-stt-ghost-host .chat-stt-interim--visible .w.newest {
  animation: chat-stt-word-settle 420ms ease-out,
    chat-stt-shimmer 2s linear infinite;
}

@keyframes chat-stt-word-settle {
  from { filter: blur(5px); opacity: 0.3; }
  to { filter: blur(2.75px); opacity: 0.8; }
}

/* Dictation gap for a textarea: the preview must never cover the sentence
   that follows the caret. The composer's own text is made transparent for
   the utterance and this stand-in is drawn over it — head, a spacer as wide
   as the ghost, then the tail — so the tail moves aside. Every metric that
   can differ per composer is copied from the live element by the script.
   Nothing is drawn when the caret is at the end of the message. */
.ss-stt-ghost-host .chat-stt-gapmirror {
  position: absolute;
  margin: 0;
  padding: 0;
  border: 0;
  white-space: pre-wrap;
  word-break: break-word;
  overflow: hidden;
  pointer-events: none;
  z-index: 0;
}

.ss-stt-ghost-host .chat-stt-gapspacer {
  display: inline-block;
  height: 0;
  vertical-align: baseline;
}

@media (prefers-reduced-motion: reduce) {
  .ss-stt-ghost-host .chat-stt-interim--visible::after,
  .ss-stt-ghost-host .chat-stt-interim--visible .w,
  .ss-stt-ghost-host .chat-stt-interim--visible .w.newest {
    animation: none !important;
  }
}
