<template>
  <div class="live-room">
    <header class="live-room-top">
      <router-link to="/" class="live-room-back">← 返回首页</router-link>
    </header>

    <main class="live-room-main">
      <h1 class="live-room-title">{{ pageTitle }}</h1>

      <section class="live-block" aria-label="本站直播">
        <h2 class="live-block-title">本站直播（WebRTC）</h2>
        <p class="live-no-login-hint">观看直播无需登录；注册/登录用于发弹幕与送礼物（虚拟礼物，无扣款）。</p>
        <div class="live-quality-row">
          <label class="live-quality-label" for="live-cap-q">采集画质</label>
          <select id="live-cap-q" v-model="captureQualityPref" class="live-quality-select">
            <option v-for="o in qualityOptions" :key="o.value" :value="o.value">
              {{ o.label }}
            </option>
          </select>
        </div>
        <p class="live-watch-status">{{ watchStatus }}</p>
        <div ref="liveStageRef" class="live-stage">
          <div class="live-stage-inner">
            <div class="live-stage-media">
              <div class="live-video-wrap">
                <video
                  ref="watchVideoRef"
                  class="live-room-video live-room-video--watch live-room-video--fill"
                  playsinline
                  autoplay
                  @volumechange="syncVolumeUIFromVideo"
                  @loadeddata="onVideoMediaNudge"
                  @canplay="onVideoMediaNudge"
                ></video>
                <div class="live-dm-layer" aria-hidden="true">
                  <div
                    v-for="d in dmItems"
                    :key="d.id"
                    class="live-dm-line"
                    :style="{ top: d.top + '%' }"
                  >
                    <span class="live-dm-from">{{ d.from }}：</span>{{ d.text }}
                  </div>
                </div>
                <div class="live-gift-layer" aria-hidden="true">
                  <div
                    v-for="g in giftFxList"
                    :key="g.id"
                    class="live-gift-burst"
                    :class="[
                      'live-gift-burst--' + g.gift,
                      g.tier === 'big' ? 'live-gift-burst--mega' : ''
                    ]"
                    :style="{ '--gift-x': g.offsetX + 'px' }"
                  >
                    <span class="live-gift-emoji">{{ g.emoji }}</span>
                    <span class="live-gift-from">{{ g.from }} 送出</span>
                  </div>
                </div>
                <!-- 控制条放右上，避免底栏占满与抖音类布局冲突 -->
                <div class="live-video-overlay live-video-overlay--corner" role="toolbar" aria-label="播放与音量">
                  <div class="live-video-overlay-inner">
                    <button type="button" class="live-overlay-btn" @click="toggleMute">
                      {{ displayMuted ? '开声音' : '静音' }}
                    </button>
                    <label class="live-vol-wrap">
                      <span class="live-vol-label">音量</span>
                      <input
                        class="live-vol-range"
                        type="range"
                        min="0"
                        max="100"
                        step="1"
                        :value="volumePercent"
                        aria-label="音量"
                        @input="onVolumeRangeInput"
                      />
                      <span class="live-vol-value" aria-hidden="true">{{ volumePercent }}%</span>
                    </label>
                    <button type="button" class="live-overlay-btn" @click="toggleStageFullscreen">
                      {{ stageFullscreen ? '退出全屏' : '全屏' }}
                    </button>
                  </div>
                </div>
              </div>
            </div>
            <aside class="live-room-side" aria-label="互动">
              <div class="live-dm-auth-row">
                <template v-if="dmLoggedIn">
                  <span class="live-dm-user">已登录：{{ dmDisplayName }}</span>
                  <button type="button" class="live-dm-auth-btn" @click="logoutDmUser">退出</button>
                </template>
                <template v-else>
                  <router-link class="live-dm-auth-link" to="/live/login">登录发弹幕</router-link>
                  <span class="live-dm-auth-sep">·</span>
                  <router-link class="live-dm-auth-link" to="/live/register">注册</router-link>
                </template>
              </div>
              <div class="live-dm-bar">
                <input
                  v-model="dmDraft"
                  class="live-dm-input"
                  type="text"
                  maxlength="120"
                  :placeholder="dmLoggedIn ? '发条弹幕…' : '登录后可发弹幕'"
                  autocomplete="off"
                  :disabled="!dmLoggedIn"
                  @keydown.enter.prevent="sendDm"
                />
                <button type="button" class="live-dm-send" :disabled="!dmLoggedIn" @click="sendDm">发送</button>
              </div>
              <p v-if="dmHint" class="live-dm-hint">{{ dmHint }}</p>
            </aside>
          </div>
          <!-- 礼物条：全宽横条，贴在画面区域下方（类抖音底栏） -->
          <div class="live-gift-dock" aria-label="礼物">
            <div v-if="dmLoggedIn" class="live-gift-row live-gift-row--scroll">
              <span class="live-gift-hint">送礼</span>
              <div class="live-gift-strip">
                <button
                  v-for="g in LIVE_GIFTS"
                  :key="g.id"
                  type="button"
                  class="live-gift-chip"
                  :class="{ 'live-gift-chip--big': g.tier === 'big' }"
                  :title="g.label"
                  @click="sendGift(g.id)"
                >
                  <span class="live-gift-chip-emoji" aria-hidden="true">{{ g.emoji }}</span>
                  <span class="live-gift-chip-label">{{ g.label }}</span>
                </button>
              </div>
            </div>
            <div v-else class="live-gift-row live-gift-row--guest">
              <span class="live-gift-hint-muted">登录后可送礼物（含火箭·跑车·飞机·嘉年华等）</span>
            </div>
          </div>
        </div>
        <div v-if="dmToast" class="live-dm-toast" role="status">{{ dmToast }}</div>
      </section>
    </main>
  </div>
</template>

<script setup>
import { ref, computed, watch, onMounted, onUnmounted, nextTick } from 'vue'
import { apiBase } from '../config'
import { LIVE_QUALITY_OPTIONS, LIVE_CAPTURE_QUALITY_STORAGE_KEY } from '../utils/liveQuality'
import { startViewing, liveDanmakuWsURL } from '../utils/liveWebRTC'
import { getSiteDmToken, getSiteDmUsername, clearSiteDmSession } from '../utils/siteUserAuth'
import { LIVE_GIFTS, liveGiftEmoji, liveGiftTier } from '../utils/liveGifts'

const watchVideoRef = ref(null)
const liveStageRef = ref(null)
const stageFullscreen = ref(false)
/** 与 video.volume / muted 同步（浏览器自动播放可能先静音） */
const volumePercent = ref(100)
const displayMuted = ref(false)
const pageTitle = ref('视频直播')
const watchStatus = ref('正在检测本站直播…')
const qualityOptions = LIVE_QUALITY_OPTIONS
const captureQualityPref = ref('source')
const dmDraft = ref('')
const dmHint = ref('')
const dmItems = ref([])
let dmIdSeq = 0
let dmWs = null
let dmIntentionalClose = false
let dmReconnectTimer = null
let dmReconnectAttempt = 0
/** 先于 close 旧连接递增，避免主动换线时 onclose 误触发重连 */
let dmGen = 0
const dmSendQueue = []
/** 递增以触发登录态 computed（同页退出后刷新 UI） */
const dmAuthRev = ref(0)
const dmLoggedIn = computed(() => {
  dmAuthRev.value
  return !!getSiteDmToken()
})
const dmDisplayName = computed(() => {
  dmAuthRev.value
  return getSiteDmUsername() || '用户'
})

const dmToast = ref('')
let dmToastTimer = null

let viewSession = null

const giftFxList = ref([])
let giftFxSeq = 0

function onVideoMediaNudge() {
  viewSession?.nudgePlay?.()
}

function onPageVisibilityPlay() {
  if (document.visibilityState === 'visible') {
    viewSession?.nudgePlay?.()
  }
}

function pushGiftFx(gift, from) {
  const id = ++giftFxSeq
  const emoji = liveGiftEmoji(gift)
  const tier = liveGiftTier(gift)
  const offsetX = Math.round(Math.random() * 160 - 80)
  const fromLabel = typeof from === 'string' && from.trim() ? from.trim() : '***'
  giftFxList.value = [...giftFxList.value, { id, gift, from: fromLabel, emoji, tier, offsetX }].slice(
    -16
  )
  const ms = tier === 'big' ? 4200 : 3200
  window.setTimeout(() => {
    giftFxList.value = giftFxList.value.filter((x) => x.id !== id)
  }, ms)
}

function sendGift(giftId) {
  if (!getSiteDmToken()) {
    dmHint.value = '送礼请先登录或注册'
    return
  }
  if (dmWs && dmWs.readyState === WebSocket.OPEN) {
    try {
      dmWs.send(JSON.stringify({ type: 'gift', gift: giftId }))
      dmHint.value = ''
    } catch (_) {
      dmHint.value = '礼物发送失败'
    }
    return
  }
  dmHint.value = '弹幕通道未连接，请稍后再试'
  if (!dmIntentionalClose) {
    connectDanmaku()
  }
}

function loadCaptureQualityPref() {
  try {
    const v = localStorage.getItem(LIVE_CAPTURE_QUALITY_STORAGE_KEY)
    if (v && qualityOptions.some((o) => o.value === v)) {
      captureQualityPref.value = v
      return
    }
  } catch (_) {}
  captureQualityPref.value = 'source'
}

watch(captureQualityPref, (v) => {
  try {
    localStorage.setItem(LIVE_CAPTURE_QUALITY_STORAGE_KEY, v)
  } catch (_) {}
})

function showDmToast(msg) {
  const text = typeof msg === 'string' && msg.trim() ? msg.trim() : '提示'
  dmToast.value = text
  if (dmToastTimer) clearTimeout(dmToastTimer)
  dmToastTimer = window.setTimeout(() => {
    dmToast.value = ''
    dmToastTimer = null
  }, 4200)
}

async function loadHomepage() {
  try {
    const url = apiBase ? `${apiBase}/api/web/homepage` : '/api/web/homepage'
    const res = await fetch(url)
    if (!res.ok) return
    const json = await res.json()
    if (typeof json.live_room_title === 'string' && json.live_room_title.trim()) {
      pageTitle.value = json.live_room_title.trim()
    }
    document.title = `${pageTitle.value} - 宇恒一号`
  } catch (_) {}
}

function syncVolumeUIFromVideo() {
  const v = watchVideoRef.value
  if (!v) return
  displayMuted.value = v.muted
  volumePercent.value = Math.round(Math.min(1, Math.max(0, v.volume)) * 100)
}

function onVolumeRangeInput(e) {
  volumePercent.value = Number(e.target.value)
  const v = watchVideoRef.value
  if (!v) return
  v.volume = volumePercent.value / 100
  v.muted = volumePercent.value === 0
  v.play().catch(() => {})
  displayMuted.value = v.muted
}

function toggleMute() {
  const v = watchVideoRef.value
  if (!v) return
  if (v.muted) {
    v.muted = false
    if (v.volume === 0) {
      v.volume = 1
      volumePercent.value = 100
    }
  } else {
    v.muted = true
  }
  v.play().catch(() => {})
  syncVolumeUIFromVideo()
}

function syncStageFullscreenFlag() {
  const d = document
  const el = liveStageRef.value
  if (!el) {
    stageFullscreen.value = false
    return
  }
  stageFullscreen.value =
    d.fullscreenElement === el || d.webkitFullscreenElement === el
}

/** 全屏含右侧互动栏，避免全屏后无法发弹幕 */
function toggleStageFullscreen() {
  const stage = liveStageRef.value
  const v = watchVideoRef.value
  if (!stage) return
  const doc = document
  const fsEl = doc.fullscreenElement || doc.webkitFullscreenElement
  if (fsEl) {
    doc.exitFullscreen?.() || doc.webkitExitFullscreen?.()
    return
  }
  if (typeof stage.requestFullscreen === 'function') {
    Promise.resolve(stage.requestFullscreen())
      .then(() => syncStageFullscreenFlag())
      .catch(() => {
        if (typeof stage.webkitRequestFullscreen === 'function') {
          try {
            stage.webkitRequestFullscreen()
          } catch (_) {
            if (v && typeof v.webkitEnterFullscreen === 'function') v.webkitEnterFullscreen()
          }
        } else if (v && typeof v.webkitEnterFullscreen === 'function') {
          v.webkitEnterFullscreen()
        }
      })
    return
  }
  if (typeof stage.webkitRequestFullscreen === 'function') {
    try {
      stage.webkitRequestFullscreen()
    } catch (_) {
      if (v && typeof v.webkitEnterFullscreen === 'function') v.webkitEnterFullscreen()
    }
    return
  }
  if (v && typeof v.webkitEnterFullscreen === 'function') {
    v.webkitEnterFullscreen()
  }
}

function pushDmLine(text, fromRaw) {
  const from =
    typeof fromRaw === 'string' && fromRaw.trim() !== '' ? fromRaw.trim() : '***'
  const id = ++dmIdSeq
  const top = 8 + Math.floor(Math.random() * 55)
  dmItems.value = [...dmItems.value, { id, text, top, from }]
  setTimeout(() => {
    dmItems.value = dmItems.value.filter((x) => x.id !== id)
  }, 12000)
}

function flushDmSendQueue() {
  while (dmSendQueue.length && dmWs && dmWs.readyState === WebSocket.OPEN) {
    const line = dmSendQueue.shift()
    try {
      dmWs.send(JSON.stringify({ text: line }))
    } catch (_) {
      dmSendQueue.unshift(line)
      break
    }
  }
}

function scheduleDmReconnect() {
  if (dmIntentionalClose) return
  if (dmReconnectTimer) {
    clearTimeout(dmReconnectTimer)
    dmReconnectTimer = null
  }
  const delay = Math.min(2000 * Math.pow(1.45, dmReconnectAttempt), 28000)
  dmReconnectTimer = window.setTimeout(() => {
    dmReconnectTimer = null
    if (dmIntentionalClose) return
    connectDanmaku()
  }, delay)
}

function connectDanmaku() {
  if (dmIntentionalClose) return
  const myGen = ++dmGen
  if (dmReconnectTimer) {
    clearTimeout(dmReconnectTimer)
    dmReconnectTimer = null
  }
  if (dmWs) {
    try {
      dmWs.close()
    } catch (_) {}
    dmWs = null
  }
  const url = liveDanmakuWsURL(getSiteDmToken())
  dmHint.value =
    dmReconnectAttempt > 0 ? `弹幕重连中（约 ${dmReconnectAttempt} 次失败后重试）…` : '弹幕通道连接中…'
  try {
    dmWs = new WebSocket(url)
  } catch (_) {
    dmHint.value = '无法创建弹幕连接（请查 Nginx 是否已反代 /api/web/live/danmaku/ws）'
    dmReconnectAttempt += 1
    scheduleDmReconnect()
    return
  }
  dmWs.onopen = () => {
    if (myGen !== dmGen) return
    dmReconnectAttempt = 0
    dmHint.value = dmSendQueue.length ? '已连接，正在发送队列中的弹幕…' : ''
    flushDmSendQueue()
    if (!dmSendQueue.length) dmHint.value = ''
  }
  dmWs.onerror = () => {
    if (!dmIntentionalClose) {
      dmHint.value = '弹幕 WebSocket 异常（请确认 Nginx 对 danmaku/ws 配置了 Upgrade）'
    }
  }
  dmWs.onmessage = (ev) => {
    let j
    try {
      j = JSON.parse(ev.data)
    } catch {
      return
    }
    if (j.type === 'dm' && typeof j.text === 'string' && j.text) {
      pushDmLine(j.text, j.from)
      return
    }
    if (j.type === 'gift' && typeof j.gift === 'string' && j.gift) {
      pushGiftFx(j.gift, j.from)
      return
    }
    if (j.type === 'error' && j.code === 'login_required') {
      dmHint.value = typeof j.message === 'string' ? j.message : '请先登录后再发弹幕或礼物'
    }
    if (j.type === 'error' && j.code === 'bad_gift') {
      dmHint.value = typeof j.message === 'string' ? j.message : '礼物无效'
    }
  }
  dmWs.onclose = () => {
    if (myGen !== dmGen) return
    dmWs = null
    if (dmIntentionalClose) return
    dmHint.value = '弹幕已断开，自动重连中…'
    dmReconnectAttempt += 1
    scheduleDmReconnect()
  }
}

function logoutDmUser() {
  clearSiteDmSession()
  dmAuthRev.value += 1
  dmSendQueue.length = 0
  connectDanmaku()
}

function sendDm() {
  const t = dmDraft.value.trim()
  if (!t) return
  if (!getSiteDmToken()) {
    dmHint.value = '发弹幕请先登录或注册（观看无需登录）'
    return
  }
  dmDraft.value = ''
  if (dmWs && dmWs.readyState === WebSocket.OPEN) {
    try {
      dmWs.send(JSON.stringify({ text: t }))
      dmHint.value = ''
      flushDmSendQueue()
    } catch (_) {
      dmSendQueue.push(t)
      dmHint.value = '发送失败，已排队，恢复连接后自动发出'
    }
    return
  }
  dmSendQueue.push(t)
  dmHint.value =
    '弹幕未连接，已加入发送队列；恢复后将自动发出。若长期失败请检查 Nginx：`/api/web/live/danmaku/ws` 的 WebSocket 反代。'
  if (!dmIntentionalClose && (!dmWs || dmWs.readyState === WebSocket.CLOSED)) {
    connectDanmaku()
  }
}

onMounted(async () => {
  dmIntentionalClose = false
  document.addEventListener('fullscreenchange', syncStageFullscreenFlag)
  document.addEventListener('webkitfullscreenchange', syncStageFullscreenFlag)
  document.addEventListener('visibilitychange', onPageVisibilityPlay)
  loadCaptureQualityPref()
  loadHomepage()
  await nextTick()
  connectDanmaku()
  viewSession = startViewing(watchVideoRef.value, {
    muted: false,
    onStatus: (s) => {
      watchStatus.value = s
    }
  })
  await nextTick()
  syncVolumeUIFromVideo()
  viewSession?.nudgePlay?.()
})

onUnmounted(() => {
  document.removeEventListener('fullscreenchange', syncStageFullscreenFlag)
  document.removeEventListener('webkitfullscreenchange', syncStageFullscreenFlag)
  document.removeEventListener('visibilitychange', onPageVisibilityPlay)
  if (dmToastTimer) {
    clearTimeout(dmToastTimer)
    dmToastTimer = null
  }
  dmIntentionalClose = true
  dmSendQueue.length = 0
  if (dmReconnectTimer) {
    clearTimeout(dmReconnectTimer)
    dmReconnectTimer = null
  }
  viewSession?.stop()
  try {
    dmWs?.close()
  } catch (_) {}
  dmWs = null
})
</script>

<style scoped>
.live-room {
  min-height: 100vh;
  padding: 24px 20px 48px;
  background: radial-gradient(ellipse at 30% 20%, rgba(74, 26, 107, 0.25) 0%, transparent 45%),
    radial-gradient(ellipse at 70% 80%, rgba(30, 58, 95, 0.3) 0%, transparent 50%),
    #0a0a12;
  color: #fff;
  font-family: 'Noto Sans SC', system-ui, sans-serif;
}
.live-room-top {
  max-width: min(960px, 100%);
  margin: 0 auto 24px;
  padding: 0 12px;
  box-sizing: border-box;
}
.live-room-back {
  color: rgba(255, 255, 255, 0.55);
  text-decoration: none;
  font-size: 14px;
  transition: color 0.2s;
}
.live-room-back:hover {
  color: #00d4ff;
}
.live-room-main {
  max-width: min(1180px, 100%);
  margin: 0 auto;
  padding: 0 12px 32px;
  box-sizing: border-box;
  text-align: center;
}
.live-room-title {
  font-size: clamp(1.5rem, 4vw, 2rem);
  letter-spacing: 0.12em;
  margin-bottom: 28px;
  background: linear-gradient(90deg, #fff, #00d4ff, #ff2d95, #fff);
  background-size: 200% auto;
  -webkit-background-clip: text;
  -webkit-text-fill-color: transparent;
  background-clip: text;
}
.live-block {
  text-align: left;
  margin-bottom: 36px;
}
.live-block--divider {
  padding-top: 28px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}
.live-block-title {
  font-size: 1rem;
  letter-spacing: 0.08em;
  color: rgba(255, 255, 255, 0.92);
  margin: 0 0 10px;
}
.live-no-login-hint {
  margin: 0 0 12px;
  font-size: 13px;
  line-height: 1.5;
  color: rgba(255, 255, 255, 0.55);
}
.live-quality-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 10px;
  margin-bottom: 8px;
}
.live-quality-label {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.65);
}
.live-quality-select {
  min-width: 200px;
  padding: 8px 12px;
  border-radius: 8px;
  border: 1px solid rgba(0, 212, 255, 0.35);
  background: rgba(0, 0, 0, 0.35);
  color: #fff;
  font-size: 14px;
}
.live-watch-status {
  font-size: 13px;
  color: #00d4ff;
  margin: 0 0 14px;
  min-height: 1.4em;
}
.live-stage {
  width: 100%;
  max-width: 100%;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  align-items: stretch;
  gap: 12px;
}
.live-gift-dock {
  width: 100%;
  box-sizing: border-box;
  padding: 10px 12px 12px;
  border-radius: 0 0 14px 14px;
  background: rgba(0, 0, 0, 0.42);
  border: 1px solid rgba(255, 255, 255, 0.08);
  border-top: 1px solid rgba(0, 212, 255, 0.12);
}
.live-gift-dock .live-gift-row {
  max-width: none;
  margin: 0;
  justify-content: center;
}
.live-gift-dock .live-gift-row--guest {
  justify-content: center;
  padding: 6px 0;
}
.live-gift-dock .live-gift-hint-muted {
  text-align: center;
}
.live-stage-inner {
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 16px;
  width: 100%;
  text-align: left;
}
.live-stage-media {
  flex: 1;
  min-width: 0;
}
.live-room-side {
  width: min(300px, 32vw);
  flex-shrink: 0;
  box-sizing: border-box;
  padding: 12px 14px 16px;
  border-radius: 14px;
  background: rgba(0, 0, 0, 0.38);
  border: 1px solid rgba(255, 255, 255, 0.08);
}
.live-room-side .live-dm-auth-row,
.live-room-side .live-dm-bar,
.live-room-side .live-dm-hint {
  max-width: none;
  margin-left: 0;
  margin-right: 0;
  justify-content: flex-start;
}
@media (max-width: 900px) {
  .live-stage-inner {
    flex-direction: column;
  }
  .live-room-side {
    width: 100%;
  }
}
.live-dm-toast {
  position: fixed;
  bottom: max(24px, env(safe-area-inset-bottom, 0px));
  left: 50%;
  transform: translateX(-50%);
  z-index: 200;
  max-width: min(92vw, 420px);
  padding: 12px 18px;
  border-radius: 12px;
  font-size: 14px;
  line-height: 1.45;
  color: #fff;
  background: rgba(180, 40, 40, 0.92);
  border: 1px solid rgba(255, 200, 200, 0.35);
  box-shadow: 0 8px 32px rgba(0, 0, 0, 0.45);
  pointer-events: none;
  text-align: center;
}
.live-video-wrap {
  position: relative;
  width: 100%;
  margin: 0 auto;
  aspect-ratio: 16 / 9;
  overflow: hidden;
  border-radius: 14px;
  background: #000;
}
.live-stage:fullscreen,
.live-stage:-webkit-full-screen {
  max-width: none;
  width: 100%;
  height: 100%;
  margin: 0;
  padding: 10px 14px calc(12px + env(safe-area-inset-bottom, 0px));
  box-sizing: border-box;
  background: #0a0a12;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  gap: 10px;
}
.live-stage:fullscreen .live-gift-dock,
.live-stage:-webkit-full-screen .live-gift-dock {
  flex-shrink: 0;
  border-radius: 12px;
  margin-top: 0;
}
.live-stage:fullscreen .live-stage-inner,
.live-stage:-webkit-full-screen .live-stage-inner {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: row;
  align-items: stretch;
  gap: 12px;
}
.live-stage:fullscreen .live-stage-media,
.live-stage:-webkit-full-screen .live-stage-media {
  flex: 1;
  min-width: 0;
  min-height: 0;
  display: flex;
  flex-direction: column;
}
.live-stage:fullscreen .live-room-side,
.live-stage:-webkit-full-screen .live-room-side {
  width: min(280px, 36vw);
  flex-shrink: 0;
  align-self: stretch;
  overflow-y: auto;
  max-height: 100%;
}
.live-stage:fullscreen .live-video-wrap,
.live-stage:-webkit-full-screen .live-video-wrap {
  flex: 1;
  min-height: 0;
  max-width: none;
  width: 100%;
  margin: 0;
  display: flex;
  flex-direction: column;
}
.live-stage:fullscreen .live-video-wrap,
.live-stage:-webkit-full-screen .live-video-wrap {
  aspect-ratio: unset;
  border-radius: 12px;
}
.live-stage:fullscreen .live-room-video.live-room-video--fill,
.live-stage:-webkit-full-screen .live-room-video.live-room-video--fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  aspect-ratio: unset;
  object-fit: cover;
}
.live-stage:fullscreen .live-dm-auth-row,
.live-stage:fullscreen .live-dm-bar,
.live-stage:fullscreen .live-dm-hint,
.live-stage:fullscreen .live-gift-dock .live-gift-row,
.live-stage:-webkit-full-screen .live-dm-auth-row,
.live-stage:-webkit-full-screen .live-dm-bar,
.live-stage:-webkit-full-screen .live-dm-hint,
.live-stage:-webkit-full-screen .live-gift-dock .live-gift-row {
  max-width: none;
}
.live-video-overlay {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  z-index: 6;
  padding: 20px 10px 10px;
  border-radius: 0 0 14px 14px;
  background: linear-gradient(to top, rgba(0, 0, 0, 0.82) 0%, rgba(0, 0, 0, 0.45) 55%, transparent 100%);
  pointer-events: auto;
}
/* 右上条：不占满底栏，避免与右侧互动区「头重脚轻」 */
.live-video-overlay--corner {
  left: auto;
  bottom: auto;
  top: 0;
  right: 0;
  width: auto;
  max-width: min(100%, 400px);
  padding: 10px 10px 12px 16px;
  border-radius: 0 14px 0 16px;
  background: linear-gradient(
    to left,
    rgba(0, 0, 0, 0.78) 0%,
    rgba(0, 0, 0, 0.35) 65%,
    transparent 100%
  );
}
.live-video-overlay-inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: flex-end;
  gap: 8px 12px;
}
.live-overlay-btn {
  padding: 6px 14px;
  border-radius: 8px;
  border: 1px solid rgba(0, 212, 255, 0.5);
  background: rgba(0, 0, 0, 0.45);
  color: #00d4ff;
  font-size: 13px;
  font-weight: 600;
  cursor: pointer;
  transition: background 0.2s, border-color 0.2s;
}
.live-overlay-btn:hover {
  background: rgba(0, 212, 255, 0.18);
  border-color: rgba(0, 212, 255, 0.85);
}
.live-vol-wrap {
  display: flex;
  align-items: center;
  gap: 8px;
  min-width: 0;
  flex: 1;
  justify-content: flex-end;
  max-width: min(280px, 58vw);
}
.live-vol-label {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.75);
  flex-shrink: 0;
}
.live-vol-value {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.65);
  width: 2.5em;
  text-align: right;
  flex-shrink: 0;
}
.live-vol-range {
  flex: 1;
  min-width: 72px;
  height: 6px;
  border-radius: 3px;
  appearance: none;
  background: rgba(255, 255, 255, 0.2);
  accent-color: #00d4ff;
}
.live-vol-range::-webkit-slider-thumb {
  appearance: none;
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #00d4ff;
  cursor: pointer;
  box-shadow: 0 0 0 2px rgba(0, 0, 0, 0.35);
}
.live-vol-range::-moz-range-thumb {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  background: #00d4ff;
  cursor: pointer;
  border: none;
}
.live-dm-layer {
  pointer-events: none;
  position: absolute;
  left: 0;
  right: 0;
  top: 0;
  bottom: 0;
  overflow: hidden;
  border-radius: 14px;
  z-index: 2;
}
.live-gift-layer {
  pointer-events: none;
  position: absolute;
  inset: 0;
  overflow: hidden;
  border-radius: 14px;
  z-index: 4;
}
.live-gift-burst {
  --gift-x: 0px;
  position: absolute;
  left: 50%;
  bottom: 16%;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 6px;
  text-align: center;
  animation: live-gift-pop 3.1s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.live-gift-burst--mega {
  bottom: 12%;
  animation: live-gift-pop-mega 4.2s cubic-bezier(0.22, 1, 0.36, 1) forwards;
}
.live-gift-burst--mega .live-gift-emoji {
  font-size: clamp(56px, 15vw, 96px);
}
.live-gift-emoji {
  font-size: clamp(44px, 12vw, 76px);
  line-height: 1;
  filter: drop-shadow(0 0 14px rgba(255, 210, 120, 0.55));
}
.live-gift-from {
  font-size: 13px;
  font-weight: 700;
  color: #fff;
  text-shadow: 0 0 10px #000, 0 1px 3px #000;
  white-space: nowrap;
  max-width: 90vw;
  overflow: hidden;
  text-overflow: ellipsis;
}
.live-gift-burst--rocket .live-gift-emoji {
  filter: drop-shadow(0 0 18px rgba(0, 212, 255, 0.75));
}
.live-gift-burst--sports_car .live-gift-emoji {
  filter: drop-shadow(0 0 16px rgba(255, 140, 60, 0.7));
}
.live-gift-burst--plane .live-gift-emoji {
  filter: drop-shadow(0 0 16px rgba(180, 200, 255, 0.75));
}
.live-gift-burst--carnival .live-gift-emoji {
  filter: drop-shadow(0 0 20px rgba(255, 80, 200, 0.65));
}
.live-gift-burst--heart .live-gift-emoji {
  filter: drop-shadow(0 0 16px rgba(255, 80, 120, 0.65));
}
.live-gift-burst--fireworks .live-gift-emoji {
  filter: drop-shadow(0 0 18px rgba(255, 200, 80, 0.7));
}
@keyframes live-gift-pop {
  0% {
    opacity: 0;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(48px) scale(0.35);
  }
  12% {
    opacity: 1;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(0) scale(1.12);
  }
  28% {
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(-6px) scale(1);
  }
  72% {
    opacity: 1;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(-12px) scale(1);
  }
  100% {
    opacity: 0;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(-42px) scale(0.88);
  }
}
@keyframes live-gift-pop-mega {
  0% {
    opacity: 0;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(64px) scale(0.22);
  }
  14% {
    opacity: 1;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(0) scale(1.2);
  }
  30% {
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(-10px) scale(1.05);
  }
  70% {
    opacity: 1;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(-18px) scale(1.02);
  }
  100% {
    opacity: 0;
    transform: translateX(calc(-50% + var(--gift-x, 0px))) translateY(-52px) scale(0.85);
  }
}
.live-dm-line {
  position: absolute;
  left: 0;
  right: 0;
  white-space: nowrap;
  overflow: visible;
  text-align: right;
  font-size: 14px;
  font-weight: 600;
  color: #fff;
  text-shadow: 0 0 6px #000, 0 0 2px #000;
  animation: live-dm-marquee 12s linear forwards;
}
.live-dm-from {
  color: #7fdbff;
  font-weight: 700;
  margin-right: 2px;
}
@keyframes live-dm-marquee {
  from {
    transform: translateX(105%);
  }
  to {
    transform: translateX(-105%);
  }
}
.live-dm-auth-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 10px 14px;
  max-width: min(920px, 100%);
  margin: 12px auto 0;
  font-size: 13px;
  color: rgba(255, 255, 255, 0.7);
}
.live-gift-row {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 8px 10px;
  max-width: min(920px, 100%);
  margin: 10px auto 0;
}
.live-gift-row--scroll {
  flex-wrap: nowrap;
  align-items: stretch;
  gap: 10px;
  padding: 4px 0 2px;
}
.live-gift-row--scroll .live-gift-hint {
  flex-shrink: 0;
  align-self: center;
  margin-right: 0;
  padding-left: 2px;
}
.live-gift-strip {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-wrap: nowrap;
  gap: 8px;
  overflow-x: auto;
  overflow-y: hidden;
  padding: 4px 12px 10px 4px;
  scroll-snap-type: x proximity;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: thin;
  scrollbar-color: rgba(0, 212, 255, 0.35) transparent;
  mask-image: linear-gradient(
    to right,
    black 0,
    black calc(100% - 28px),
    transparent 100%
  );
}
.live-gift-strip::-webkit-scrollbar {
  height: 5px;
}
.live-gift-strip::-webkit-scrollbar-thumb {
  background: rgba(0, 212, 255, 0.35);
  border-radius: 3px;
}
.live-gift-hint {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.55);
  margin-right: 4px;
}
.live-gift-hint-muted {
  font-size: 12px;
  color: rgba(255, 255, 255, 0.4);
  text-align: center;
}
.live-gift-chip {
  flex: 0 0 auto;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 2px;
  min-width: 52px;
  min-height: 52px;
  padding: 6px 8px;
  border-radius: 10px;
  border: 1px solid rgba(0, 212, 255, 0.45);
  background: rgba(0, 0, 0, 0.4);
  cursor: pointer;
  scroll-snap-align: start;
  transition: transform 0.15s, border-color 0.2s, box-shadow 0.2s;
}
.live-gift-chip-emoji {
  font-size: 22px;
  line-height: 1;
}
.live-gift-chip-label {
  font-size: 10px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.78);
  max-width: 5em;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.live-gift-chip--big {
  min-width: 58px;
  min-height: 56px;
  border-width: 2px;
  border-color: rgba(255, 200, 100, 0.55);
  background: linear-gradient(165deg, rgba(255, 100, 180, 0.14), rgba(0, 212, 255, 0.1));
  box-shadow: 0 0 14px rgba(255, 180, 80, 0.12);
}
.live-gift-chip--big .live-gift-chip-emoji {
  font-size: 26px;
}
.live-gift-chip--big .live-gift-chip-label {
  color: rgba(255, 230, 180, 0.95);
}
.live-gift-chip:hover {
  transform: translateY(-2px);
  border-color: rgba(255, 45, 149, 0.65);
  box-shadow: 0 6px 20px rgba(0, 212, 255, 0.2);
}
.live-dm-user {
  color: rgba(255, 255, 255, 0.85);
}
.live-dm-auth-link {
  color: #00d4ff;
  text-decoration: none;
  font-weight: 600;
}
.live-dm-auth-link:hover {
  text-decoration: underline;
}
.live-dm-auth-sep {
  color: rgba(255, 255, 255, 0.35);
  user-select: none;
}
.live-dm-auth-btn {
  padding: 4px 12px;
  border-radius: 8px;
  border: 1px solid rgba(255, 255, 255, 0.25);
  background: rgba(255, 255, 255, 0.06);
  color: rgba(255, 255, 255, 0.85);
  font-size: 12px;
  cursor: pointer;
}
.live-dm-auth-btn:hover {
  border-color: rgba(0, 212, 255, 0.5);
  color: #00d4ff;
}
.live-dm-bar {
  display: flex;
  gap: 10px;
  justify-content: center;
  align-items: center;
  max-width: min(920px, 100%);
  margin: 14px auto 0;
}
.live-dm-input {
  flex: 1;
  min-width: 0;
  padding: 10px 14px;
  border-radius: 10px;
  border: 1px solid rgba(255, 255, 255, 0.2);
  background: rgba(255, 255, 255, 0.06);
  color: #fff;
  font-size: 14px;
}
.live-dm-input::placeholder {
  color: rgba(255, 255, 255, 0.35);
}
.live-dm-input:disabled {
  opacity: 0.55;
  cursor: not-allowed;
}
.live-dm-send {
  flex-shrink: 0;
  padding: 10px 18px;
  border-radius: 10px;
  border: 1px solid rgba(0, 212, 255, 0.45);
  background: rgba(0, 212, 255, 0.15);
  color: #00d4ff;
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
}
.live-dm-send:hover {
  background: rgba(0, 212, 255, 0.25);
}
.live-dm-send:disabled {
  opacity: 0.45;
  cursor: not-allowed;
}
.live-dm-hint {
  max-width: min(920px, 100%);
  margin: 10px auto 0;
  font-size: 12px;
  line-height: 1.55;
  color: #ffb86c;
  text-align: left;
}
.live-room-actions {
  margin-top: 8px;
}
.live-room-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 14px 32px;
  border-radius: 12px;
  font-size: 15px;
  font-weight: 700;
  letter-spacing: 0.08em;
  cursor: pointer;
  border: none;
  transition: transform 0.2s, box-shadow 0.2s, opacity 0.2s;
}
.live-room-btn:disabled {
  opacity: 0.45;
  cursor: not-allowed;
  transform: none;
}
.live-room-btn--primary {
  background: linear-gradient(135deg, #00d4ff, #ff2d95);
  color: #0a0a12;
  box-shadow: 0 8px 28px rgba(0, 212, 255, 0.25);
}
.live-room-btn--primary:not(:disabled):hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 36px rgba(0, 212, 255, 0.35);
}
.live-room-video {
  display: block;
  background: #000;
}
.live-room-video--watch {
  margin-top: 0;
}
.live-room-video.live-room-video--fill {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  border-radius: 14px;
  border: 1px solid rgba(0, 212, 255, 0.25);
  object-fit: cover;
}
</style>
