<script setup lang="ts">
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useBackgroundRefresh } from '@/composables/useBackgroundRefresh'
import { useVoiceRecorder } from '@/composables/useVoiceRecorder'
import { useVoiceAudio } from '@/composables/useVoiceAudio'
import { useChatStore } from '@/stores/chat'
import { useSettingsStore } from '@/stores/settings'
import ChatMessage from '@/components/ChatMessage.vue'
import WeatherCard from '@/components/WeatherCard.vue'
import BriefingSetupWizard from '@/components/BriefingSetupWizard.vue'
import {
  apiGet,
  getBriefingConfig,
  getBriefingConversations,
  getBriefingToday,
  getBriefingConvMessages,
  triggerBriefingSlot,
  postRssReaction,
  deleteRssReaction,
  getNewsItems,
  transcribeAudio,
  synthesiseSpeech,
  type BriefingConversation,
  type BriefingMessage,
} from '@/api/client'
import type { Message } from '@/types/chat'
import type { NewsItem } from '@/types/news'

interface WeatherData {
  location: string
  fetched_at: string
  current_temp: number
  condition: string
  today_high: number | null
  today_low: number | null
  yesterday_high: number | null
  yesterday_low: number | null
  wind_unit?: string
  forecast: { day: string; condition: string; high: number; low: number; precip_probability: number | null; precip_mm: number | null; windspeed_max: number }[]
}

const chatStore = useChatStore()
const settingsStore = useSettingsStore()

// Setup wizard
const showWizard = ref(false)
const wizardChecked = ref(false)

async function checkSetup() {
  const config = await getBriefingConfig()
  if (!config.enabled) showWizard.value = true
  wizardChecked.value = true
}

async function onWizardDone() {
  showWizard.value = false
  await loadAll()
}

// Conversations list for the dropdown
const conversations = ref<BriefingConversation[]>([])
const selectedConvId = ref<number | null>(null)
const todayConvId = ref<number | null>(null)

const isToday = computed(() => selectedConvId.value === todayConvId.value)

// Messages for selected conversation
const messages = ref<BriefingMessage[]>([])
const loadingMessages = ref(false)

// Weather panel (left column)
const weatherData = ref<WeatherData[]>([])
const tempUnit = ref<string>('C')

async function loadWeather() {
  try {
    const data = await apiGet<{ locations: WeatherData[]; temp_unit: string }>('/api/briefing/weather')
    weatherData.value = data.locations ?? []
    tempUnit.value = data.temp_unit ?? 'C'
  } catch { /* silent */ }
}

// News panel (right column)
const newsItems = ref<NewsItem[]>([])

async function loadNews() {
  try {
    const data = await getNewsItems({ days: 2, limit: 40 })
    newsItems.value = data.items
    // Seed reactions from API response
    for (const item of data.items) {
      if (reactions.value[item.id] === undefined) {
        reactions.value[item.id] = item.reaction
      }
    }
  } catch { /* silent */ }
}

// Scroll to bottom of messages
const messagesEl = ref<HTMLElement | null>(null)

function scrollToBottom() {
  nextTick(() => {
    if (messagesEl.value) {
      messagesEl.value.scrollTop = messagesEl.value.scrollHeight
    }
  })
}

async function loadAll() {
  const [convList, today] = await Promise.all([
    getBriefingConversations(),
    getBriefingToday().catch(() => null),
    loadWeather(),
    loadNews(),
  ])
  conversations.value = convList
  if (today) {
    todayConvId.value = today.id
    if (!convList.find((c) => c.id === today.id)) {
      conversations.value = [
        { id: today.id, title: today.title ?? 'Today', briefing_date: null, message_count: 0, created_at: new Date().toISOString() },
        ...convList,
      ]
    }
    selectedConvId.value = today.id
    messages.value = today.messages
    await chatStore.fetchConversation(today.id)
  }
  scrollToBottom()
}

watch(selectedConvId, async (id) => {
  if (!id) return
  if (id === todayConvId.value) {
    await chatStore.fetchConversation(id)
    messages.value = (chatStore.currentConversation?.messages ?? []) as unknown as BriefingMessage[]
    scrollToBottom()
    return
  }
  loadingMessages.value = true
  try {
    messages.value = await getBriefingConvMessages(id)
    scrollToBottom()
  } finally {
    loadingMessages.value = false
  }
})

// Refresh messages after streaming ends
watch(() => chatStore.streaming, async (streaming) => {
  if (!streaming && selectedConvId.value === todayConvId.value && todayConvId.value) {
    const today = await getBriefingToday().catch(() => null)
    if (today) messages.value = today.messages
    scrollToBottom()
  }
})

// Input
const input = ref('')
const sending = ref(false)

async function send(overrideText?: string) {
  const text = (overrideText ?? input.value).trim()
  if (!text || !todayConvId.value || chatStore.streaming || sending.value) return
  if (chatStore.currentConversation?.id !== todayConvId.value) {
    await chatStore.fetchConversation(todayConvId.value)
  }
  input.value = ''
  sending.value = true
  try {
    await chatStore.sendMessage(text)
  } finally {
    sending.value = false
  }
}

async function discussArticle(item: NewsItem) {
  const snippet = item.snippet ? `\n\n"${item.snippet.slice(0, 400)}${item.snippet.length > 400 ? '…' : ''}"` : ''
  const text = `Summarize the following article excerpt for me in a few sentences and share your thoughts. Do not use any research or search tools — just respond conversationally based on what I've shared below.\n\n**${item.title}**${snippet}\n\nSource: ${item.source}`
  // Scroll the chat panel into view on narrow layouts, then send
  await nextTick(() => {
    document.querySelector('.briefing-chat')?.scrollIntoView({ behavior: 'smooth', block: 'nearest' })
  })
  await send(text)
}

function onKeydown(e: KeyboardEvent) {
  if (e.key === 'Enter' && !e.shiftKey) {
    e.preventDefault()
    send()
  }
}

// RSS reactions: map of rss_item_id -> 'up' | 'down' | null
const reactions = ref<Record<number, 'up' | 'down' | null>>({})

async function handleReaction(itemId: number, reaction: 'up' | 'down') {
  const current = reactions.value[itemId]
  reactions.value[itemId] = current === reaction ? null : reaction
  try {
    if (current === reaction) {
      await deleteRssReaction(itemId)
    } else {
      await postRssReaction(itemId, reaction)
    }
  } catch {
    reactions.value[itemId] = current ?? null
  }
}

function formatRelativeDate(iso: string | null): string {
  if (!iso) return ''
  const d = new Date(iso)
  const now = new Date()
  const diffH = (now.getTime() - d.getTime()) / 3_600_000
  if (diffH < 24) return `${Math.round(diffH)}h ago`
  if (diffH < 48) return 'Yesterday'
  return d.toLocaleDateString(undefined, { month: 'short', day: 'numeric' })
}

// Manual trigger
const triggering = ref(false)
async function triggerNow() {
  triggering.value = true
  try {
    await triggerBriefingSlot('compilation')
    await loadAll()
  } finally {
    triggering.value = false
  }
}

// Dropdown label
function convLabel(c: BriefingConversation): string {
  if (c.id === todayConvId.value) return 'Today'
  if (c.briefing_date) {
    const d = new Date(c.briefing_date)
    return d.toLocaleDateString(undefined, { weekday: 'short', month: 'short', day: 'numeric' })
  }
  return c.title || 'Briefing'
}

// ─── Voice ────────────────────────────────────────────────────────────────────
// Mic PTT needs STT; listen/speak mode also needs TTS
const voiceEnabled = computed(() => settingsStore.voiceSttReady)
const voiceTtsEnabled = computed(() => settingsStore.voiceTtsReady)
const listenMode = ref(false)
const transcribing = ref(false)
const synthesising = ref(false)

const recorder = useVoiceRecorder()
const audio = useVoiceAudio()

// Read the latest assistant message aloud
async function listenToLatest() {
  const lastAssistant = [...messages.value].reverse().find((m) => m.role === 'assistant')
  if (!lastAssistant?.content) return
  await speakText(lastAssistant.content)
}

async function speakText(text: string) {
  if (!voiceTtsEnabled.value || synthesising.value) return
  // Strip markdown for cleaner TTS output
  const plain = text
    .replace(/```[\s\S]*?```/g, '')
    .replace(/`[^`]+`/g, (m) => m.slice(1, -1))
    .replace(/#{1,6}\s+/g, '')
    .replace(/\*\*([^*]+)\*\*/g, '$1')
    .replace(/\*([^*]+)\*/g, '$1')
    .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1')
    .replace(/^\s*[-*+]\s+/gm, '')
    .replace(/\n{2,}/g, ' ')
    .trim()
  if (!plain) return
  synthesising.value = true
  try {
    const blob = await synthesiseSpeech(plain)
    await audio.play(blob)
  } catch {
    // TTS failure is non-critical
  } finally {
    synthesising.value = false
  }
}

// PTT: hold to record, release to transcribe and send
async function startPtt() {
  if (!voiceEnabled.value || recorder.recording.value) return
  audio.stop()
  await recorder.startRecording()
}

async function stopPtt() {
  if (!recorder.recording.value) return
  transcribing.value = true
  try {
    const blob = await recorder.stopRecording()
    const { transcript } = await transcribeAudio(blob)
    if (transcript.trim()) {
      input.value = transcript.trim()
      await send()
    }
  } catch {
    // transcription failure — leave input empty
  } finally {
    transcribing.value = false
  }
}

// Auto-TTS when listen mode is on and streaming ends
watch(() => chatStore.streaming, async (streaming) => {
  if (!streaming && listenMode.value && voiceTtsEnabled.value) {
    // Small delay to let messages update after stream
    await new Promise((r) => setTimeout(r, 200))
    await listenToLatest()
  }
})

// Convert BriefingMessage to Message for ChatMessage component
function toMsg(m: BriefingMessage): Message {
  return {
    id: m.id,
    conversation_id: -1,
    role: m.role,
    content: m.content,
    context_note_id: null,
    context_note_title: null,
    created_at: m.created_at,
  }
}

// ─── Background refresh (no-flicker) ─────────────────────────────────────────
async function _backgroundRefreshMessages() {
  try {
    const today = await getBriefingToday()
    if (!today) return
    const fresh = today.messages
    const last = fresh[fresh.length - 1]
    const cur = messages.value[messages.value.length - 1]
    if (fresh.length !== messages.value.length || last?.content !== cur?.content) {
      messages.value = fresh
    }
    await loadNews()
  } catch { /* silent — don't disturb the UI on network hiccup */ }
}

useBackgroundRefresh(
  _backgroundRefreshMessages,
  60_000,
  () => !chatStore.streaming && isToday.value && !!todayConvId.value,
)

onMounted(async () => {
  await checkSetup()
  if (!showWizard.value) await loadAll()
})
</script>

<template>
  <div class="briefing-root">
    <!-- Setup wizard overlay -->
    <BriefingSetupWizard v-if="wizardChecked && showWizard" @done="onWizardDone" />

    <!-- Main view -->
    <div class="briefing-shell" v-if="wizardChecked && !showWizard">
      <!-- Header spans all columns -->
      <header class="briefing-header">
        <div class="briefing-header-left">
          <h1 class="briefing-title">Briefing</h1>
          <span class="briefing-today-badge">{{ new Date().toLocaleDateString(undefined, { weekday: 'long', month: 'long', day: 'numeric' }) }}</span>
        </div>
        <div class="briefing-header-right">
          <select v-if="conversations.length" v-model="selectedConvId" class="briefing-conv-select">
            <option v-for="c in conversations" :key="c.id" :value="c.id">{{ convLabel(c) }}</option>
          </select>
          <button
            class="btn-trigger"
            @click="triggerNow"
            :disabled="triggering"
            title="Manually trigger morning briefing now"
          >{{ triggering ? '…' : 'Refresh' }}</button>
        </div>
      </header>

      <!-- Left column: Weather -->
      <div class="briefing-left">
        <div class="panel-label">Weather</div>
        <template v-if="weatherData.length">
          <WeatherCard
            v-for="loc in weatherData"
            :key="(loc as WeatherData).location"
            :weather="loc"
            :temp-unit="tempUnit"
          />
        </template>
        <div v-else class="panel-empty">No weather configured</div>
      </div>

      <!-- Center column: Chat -->
      <div class="briefing-center">
        <div class="briefing-messages-wrap" ref="messagesEl">
          <div v-if="loadingMessages" class="briefing-loading">Loading…</div>
          <template v-else>
            <div v-if="!messages.length" class="briefing-empty">
              <p>No briefing yet for today.</p>
              <p class="briefing-empty-hint">Click "Refresh" to generate a briefing now, or wait for the scheduled slot.</p>
            </div>
            <div v-else class="briefing-messages">
              <template v-for="msg in messages" :key="msg.id">
                <ChatMessage
                  :message="toMsg(msg)"
                  :is-streaming="false"
                />
              </template>
              <!-- Live streaming bubble for today -->
              <ChatMessage
                v-if="isToday && chatStore.streaming && chatStore.streamingContent"
                :message="{
                  id: -1,
                  conversation_id: todayConvId ?? -1,
                  role: 'assistant',
                  content: chatStore.streamingContent,
                  context_note_id: null,
                  context_note_title: null,
                  created_at: new Date().toISOString(),
                }"
                :is-streaming="true"
              />
            </div>
          </template>
        </div>

        <!-- Input bar (today only) -->
        <div v-if="isToday" class="briefing-input-bar">
          <!-- Listen toggle -->
          <button
            v-if="voiceTtsEnabled"
            class="btn-icon"
            :class="{ 'btn-icon-active': listenMode, 'btn-icon-busy': synthesising || audio.playing.value }"
            @click="listenMode ? (listenMode = false) : (listenMode = true, listenToLatest())"
            :title="listenMode ? 'Stop auto-read' : 'Read replies aloud'"
            aria-label="Toggle listen mode"
          >
            <svg v-if="!synthesising && !audio.playing.value" width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M3 9v6h4l5 5V4L7 9H3zm13.5 3c0-1.77-1.02-3.29-2.5-4.03v8.05c1.48-.73 2.5-2.25 2.5-4.02zM14 3.23v2.06c2.89.86 5 3.54 5 6.71s-2.11 5.85-5 6.71v2.06c4.01-.91 7-4.49 7-8.77s-2.99-7.86-7-8.77z"/>
            </svg>
            <svg v-else width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M18 12c0-1.77-1.02-3.29-2.5-4.03v2.21l2.45 2.45c.03-.2.05-.41.05-.63zm2.5 0c0 .94-.2 1.82-.54 2.64l1.51 1.51C21.8 14.82 22 13.43 22 12c0-4.28-2.99-7.86-7-8.77v2.06c2.89.86 5 3.54 5 6.71zM4.27 3L3 4.27 7.73 9H3v6h4l5 5v-6.73l4.25 4.25c-.67.52-1.42.93-2.25 1.18v2.06c1.38-.31 2.63-.95 3.69-1.81L19.73 21 21 19.73l-9-9L4.27 3zM12 4L9.91 6.09 12 8.18V4z"/>
            </svg>
          </button>
          <!-- Stop TTS playback -->
          <button
            v-if="voiceTtsEnabled && (synthesising || audio.playing.value)"
            class="btn-icon btn-icon-danger"
            @click="audio.stop(); synthesising = false"
            title="Stop playback"
            aria-label="Stop playback"
          >
            <svg width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M6 6h12v12H6z"/>
            </svg>
          </button>
          <textarea
            v-model="input"
            class="briefing-textarea"
            :placeholder="transcribing ? 'Transcribing…' : recorder.recording.value ? 'Recording…' : 'Reply to your briefing…'"
            rows="1"
            @keydown="onKeydown"
            :disabled="transcribing || recorder.recording.value"
          ></textarea>
          <!-- Mic PTT button -->
          <button
            v-if="voiceEnabled && recorder.isSupported"
            class="btn-icon btn-mic-ptt"
            :class="{ 'mic-recording': recorder.recording.value, 'mic-transcribing': transcribing }"
            @mousedown.prevent="startPtt"
            @mouseup.prevent="stopPtt"
            @touchstart.prevent="startPtt"
            @touchend.prevent="stopPtt"
            :disabled="transcribing || chatStore.streaming || sending"
            :title="recorder.recording.value ? 'Release to send' : 'Hold to speak'"
            aria-label="Push to talk"
          >
            <svg v-if="!transcribing" width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5zm6 6c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/>
            </svg>
            <svg v-else width="17" height="17" viewBox="0 0 24 24" fill="currentColor">
              <path d="M12 4V1L8 5l4 4V6c3.31 0 6 2.69 6 6 0 1.01-.25 1.97-.7 2.8l1.46 1.46C19.54 15.03 20 13.57 20 12c0-4.42-3.58-8-8-8zm0 14c-3.31 0-6-2.69-6-6 0-1.01.25-1.97.7-2.8L5.24 7.74C4.46 8.97 4 10.43 4 12c0 4.42 3.58 8 8 8v3l4-4-4-4v3z"/>
            </svg>
          </button>
          <button
            class="btn-send"
            @click="send()"
            :disabled="!input.trim() || chatStore.streaming || sending"
            aria-label="Send"
          >&uarr;</button>
        </div>
      </div>

      <!-- Right column: News -->
      <div class="briefing-right">
        <div class="panel-label-row">
          <div class="panel-label">Today's News</div>
          <span v-if="newsItems.length" class="news-count">{{ newsItems.length }} items</span>
        </div>
        <div v-if="!newsItems.length" class="panel-empty">No articles in the last 2 days</div>
        <div
          v-for="item in newsItems"
          :key="item.id"
          class="news-card"
        >
          <div class="news-card-meta">
            <span class="news-source">{{ item.source }}</span>
            <span v-if="item.published_at" class="news-date">{{ formatRelativeDate(item.published_at) }}</span>
          </div>
          <a
            v-if="item.url"
            :href="item.url"
            target="_blank"
            rel="noopener noreferrer"
            class="news-title"
          >{{ item.title }}</a>
          <p v-else class="news-title news-title--plain">{{ item.title }}</p>
          <p v-if="item.snippet" class="news-snippet">{{ item.snippet }}</p>
          <div class="news-reactions">
            <button
              class="reaction-btn"
              :class="{ active: reactions[item.id] === 'up' }"
              @click="handleReaction(item.id, 'up')"
              title="Interested"
            >👍</button>
            <button
              class="reaction-btn"
              :class="{ active: reactions[item.id] === 'down' }"
              @click="handleReaction(item.id, 'down')"
              title="Not interested"
            >👎</button>
            <button
              v-if="isToday && todayConvId"
              class="reaction-btn discuss-btn"
              @click="discussArticle(item)"
              title="Discuss in briefing chat"
            >💬</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<style scoped>
.briefing-root {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.briefing-shell {
  display: grid;
  grid-template-columns: 1fr 2fr 1fr;
  grid-template-rows: auto 1fr;
  height: 100%;
  min-height: 0;
}

.briefing-header {
  grid-column: 1 / -1;
  grid-row: 1;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 1.25rem 1rem 1rem;
  border-bottom: 1px solid var(--color-border);
  flex-shrink: 0;
  gap: 1rem;
  flex-wrap: wrap;
}

.briefing-header-left {
  display: flex;
  align-items: baseline;
  gap: 0.75rem;
}

.briefing-title {
  font-family: 'Fraunces', Georgia, serif;
  font-size: 1.3rem;
  font-weight: 700;
  margin: 0;
  color: var(--color-text);
}

.briefing-today-badge {
  font-size: 0.82rem;
  color: var(--color-text-muted);
}

.briefing-header-right {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.briefing-conv-select {
  padding: 0.35rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg-card);
  color: var(--color-text);
  font-size: 0.82rem;
  cursor: pointer;
  font-family: inherit;
}

.btn-trigger {
  padding: 0.35rem 0.8rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-bg-card);
  color: var(--color-text-muted);
  font-size: 0.8rem;
  cursor: pointer;
  white-space: nowrap;
  transition: all 0.15s;
  font-family: inherit;
}
.btn-trigger:hover:not(:disabled) {
  border-color: var(--color-primary);
  color: var(--color-primary);
}
.btn-trigger:disabled { opacity: 0.5; cursor: not-allowed; }

/* ─── Left column (Weather) ──────────────────────────────────────────────── */

.briefing-left {
  grid-column: 1;
  grid-row: 2;
  border-right: 1px solid var(--color-border);
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.briefing-left :deep(.weather-card) {
  margin-bottom: 0;
}

/* ─── Center column (Chat) ───────────────────────────────────────────────── */

.briefing-center {
  grid-column: 2;
  grid-row: 2;
  display: flex;
  flex-direction: column;
  min-height: 0;
}

.briefing-messages-wrap {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  min-height: 0;
}

.briefing-loading,
.briefing-empty {
  text-align: center;
  padding: 3rem 1rem;
  color: var(--color-text-muted);
  font-size: 0.9rem;
}

.briefing-empty-hint {
  font-size: 0.8rem;
  opacity: 0.7;
  margin-top: 0.5rem;
}

.briefing-messages {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.briefing-input-bar {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 0.5rem 0.5rem 0.75rem;
  margin: 0 0.75rem 1rem;
  background: var(--color-input-bar-bg);
  border-radius: 20px;
  box-shadow: 0 2px 12px var(--color-shadow);
  flex-shrink: 0;
}

.briefing-textarea {
  flex: 1;
  resize: none;
  padding: 0.4rem 0.5rem;
  border: none;
  border-radius: 12px;
  font-family: inherit;
  font-size: 0.95rem;
  background: transparent;
  color: var(--color-input-bar-text);
  outline: none;
  max-height: 120px;
  overflow-y: auto;
  line-height: 1.4;
}
.briefing-textarea::placeholder { color: var(--color-input-bar-placeholder); }
.briefing-textarea:disabled { opacity: 0.5; }

.btn-send {
  width: 34px;
  min-width: 34px;
  height: 34px;
  padding: 0;
  display: flex;
  align-items: center;
  justify-content: center;
  background: linear-gradient(135deg, #6366f1, #4f46e5);
  color: #fff;
  border: none;
  border-radius: 50%;
  cursor: pointer;
  font-size: 1.1rem;
  flex-shrink: 0;
  transition: box-shadow 0.15s;
}
.btn-send:not(:disabled):hover { box-shadow: 0 0 14px rgba(129, 140, 248, 0.5); }
.btn-send:disabled { opacity: 0.35; cursor: default; }

/* ─── Right column (News) ────────────────────────────────────────────────── */

.briefing-right {
  grid-column: 3;
  grid-row: 2;
  border-left: 1px solid var(--color-border);
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
}

.panel-label {
  font-size: 0.72rem;
  font-weight: 700;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--color-primary);
  flex-shrink: 0;
}

.panel-label-row {
  display: flex;
  align-items: center;
  justify-content: space-between;
  flex-shrink: 0;
}

.news-count {
  font-size: 0.72rem;
  color: var(--color-text-muted);
}

.panel-empty {
  font-size: 0.82rem;
  color: var(--color-text-muted);
  padding: 0.5rem 0;
}

.news-card {
  background: var(--color-bg-card);
  border: 1px solid var(--color-border);
  border-radius: 10px;
  padding: 0.65rem 0.85rem;
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  flex-shrink: 0;
}

.news-card-meta {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}

.news-source {
  font-size: 0.72rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.04em;
  color: var(--color-primary);
}

.news-date {
  font-size: 0.72rem;
  color: var(--color-text-muted);
}

.news-title {
  font-size: 0.88rem;
  font-weight: 600;
  color: var(--color-text);
  line-height: 1.35;
  text-decoration: none;
  margin: 0;
}

a.news-title:hover { text-decoration: underline; color: var(--color-primary); }

.news-snippet {
  font-size: 0.78rem;
  color: var(--color-text-muted);
  line-height: 1.45;
  margin: 0;
  display: -webkit-box;
  -webkit-line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.news-reactions {
  display: flex;
  gap: 0.3rem;
  margin-top: 0.15rem;
}

.reaction-btn {
  background: none;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0.1rem 0.35rem;
  cursor: pointer;
  font-size: 0.82rem;
  line-height: 1.4;
  opacity: 0.55;
  transition: opacity 0.15s, border-color 0.15s;
}

.reaction-btn:hover {
  opacity: 1;
  border-color: var(--color-primary);
}

.reaction-btn.active {
  opacity: 1;
  border-color: var(--color-primary);
  background: color-mix(in srgb, var(--color-primary) 12%, transparent);
}

/* ─── Icon buttons (input bar) ───────────────────────────────────────────── */

.btn-icon {
  background: none;
  border: none;
  cursor: pointer;
  color: var(--color-input-bar-text);
  opacity: 0.5;
  padding: 0.25rem;
  display: flex;
  align-items: center;
  justify-content: center;
  flex-shrink: 0;
  transition: opacity 0.15s, color 0.15s;
}
.btn-icon:hover:not(:disabled) { opacity: 1; }
.btn-icon:disabled { opacity: 0.25; cursor: default; }
.btn-icon-active {
  color: var(--color-primary) !important;
  opacity: 1 !important;
}
.btn-icon-busy {
  color: var(--color-primary) !important;
  opacity: 0.8 !important;
  animation: icon-pulse 1.2s ease-in-out infinite;
}
.btn-icon-danger {
  color: #ef4444 !important;
  opacity: 0.8 !important;
}

.btn-mic-ptt {
  touch-action: none;
  user-select: none;
}
.btn-mic-ptt.mic-recording {
  color: #ef4444 !important;
  opacity: 1 !important;
  animation: icon-pulse 0.8s ease-in-out infinite;
}
.btn-mic-ptt.mic-transcribing {
  color: var(--color-primary) !important;
  opacity: 0.7 !important;
}
@keyframes icon-pulse {
  0%, 100% { opacity: 0.6; }
  50% { opacity: 1; }
}

/* ─── Responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 900px) {
  .briefing-shell {
    grid-template-columns: 1fr;
    grid-template-rows: auto auto 1fr auto;
  }
  .briefing-header {
    grid-column: 1;
    grid-row: 1;
  }
  .briefing-left {
    grid-column: 1;
    grid-row: 2;
    border-right: none;
    border-bottom: 1px solid var(--color-border);
    max-height: 220px;
  }
  .briefing-center {
    grid-column: 1;
    grid-row: 3;
  }
  .briefing-right {
    grid-column: 1;
    grid-row: 4;
    border-left: none;
    border-top: 1px solid var(--color-border);
    max-height: 260px;
  }
}
</style>
