<template>
  <GuidePageFrame
    :active-tab="activeTopTab"
    variant="static"
    :show-top-tabs="false"
    :audio-player-avoid-bottom-nav="false"
    :audio-player-closable="false"
    :show-audio-player-host="true"
    audio-player-mode="expanded"
    @back="handleBack"
  >
    <view class="explain-detail-page" :class="{ 'with-audio-player': showAudioPlayer }">
      <scroll-view
        class="content"
        :class="{ 'with-fixed-player': isDetailAudioDockVisible || showAudioPlayer }"
        scroll-y
        :show-scrollbar="false"
      >
        <view class="immersive-hero" :class="{ 'has-real-image': heroImage }">
          <image
            v-if="heroImage"
            class="hero-real-image"
            :src="heroImage"
            mode="aspectFill"
          />
          <view class="hero-image-shade"></view>
          <view class="hero-back" @tap.stop="handleBack">
            <text class="hero-back-icon">‹</text>
          </view>
          <view class="hero-copy">
            <text class="detail-title">{{ exhibit.title }}</text>
            <text class="detail-meta">{{ heroSubtitle }}</text>

            <view class="language-anchor" v-if="contentLanguageOptions.length">
              <view
                v-for="option in contentLanguageOptions"
                :key="option.value"
                class="language-anchor-option"
                :class="{ active: selectedAudioLanguage === option.value }"
                @tap="handleLanguageChange(option.value)"
              >
                <text class="language-anchor-text">{{ option.label }}</text>
              </view>
            </view>
          </view>
        </view>

        <view class="content-panel">
          <view class="content-section">
            <text class="section-text">{{ currentDetailText }}</text>
            <text v-if="currentDetailTextLoading" class="section-hint">正在加载讲解正文</text>
          </view>
        </view>
      </scroll-view>

      <view
        v-if="isDetailAudioDockVisible"
        class="detail-audio-dock"
      >
        <view class="detail-audio-play" @tap="handlePlayAudio">
          <svg width="26" height="26" viewBox="0 0 24 24" fill="none">
            <path d="M8 5V19L19 12L8 5Z" fill="currentColor"/>
          </svg>
        </view>
        <view class="detail-audio-main" @tap="handlePlayAudio">
          <text class="detail-audio-title">{{ audioDockTitle }}</text>
          <text class="detail-audio-subtitle">{{ audioDockSubtitle }}</text>
          <view class="detail-audio-progress">
            <view class="detail-audio-progress-fill"></view>
          </view>
        </view>
      </view>

      <view v-if="isLocationActionVisible" class="action-bar">
        <view
          class="location-btn"
          :class="{ disabled: !hallLocationId }"
          @tap="handleNavigate"
        >
          <svg class="location-icon" width="19" height="19" viewBox="0 0 24 24" fill="none">
            <path d="M12 21C12 21 18 14.9 18 9.8C18 6.5 15.3 4 12 4C8.7 4 6 6.5 6 9.8C6 14.9 12 21 12 21Z" stroke="currentColor" stroke-width="1.7"/>
            <circle cx="12" cy="10" r="2.2" stroke="currentColor" stroke-width="1.7"/>
          </svg>
          <text class="location-text">查看位置</text>
        </view>
      </view>
    </view>
  </GuidePageFrame>
</template>

<script setup lang="ts">
import { computed, ref, watch } from 'vue'
import { onLoad, onUnload } from '@dcloudio/uni-app'
import GuidePageFrame from '@/components/navigation/GuidePageFrame.vue'
import type { AudioItem } from '@/components/audio/AudioPlayer.vue'
import { useGlobalAudioPlayer } from '@/composables/useGlobalAudioPlayer'
import {
  explainUseCase
} from '@/usecases/explainUseCase'
import {
  guideUseCase
} from '@/usecases/guideUseCase'
import {
  toExplainDetailPageViewModel,
  type ExplainDetailPageViewModel
} from '@/view-models/explainViewModels'
import type {
  AudioPlayTargetType
} from '@/domain/museum'
import type {
  AudioLanguage
} from '@/repositories/AudioPlayInfoRepository'
import {
  isGuideTopTab,
  type GuideTopTab
} from '@/utils/guideTopTabs'
import {
  EXPLAIN_DETAIL_PLACEHOLDER_IMAGE
} from '@/utils/placeholders'

const defaultDetail: ExplainDetailPageViewModel = {
  id: '',
  title: '讲解内容',
  subtitle: '位置待补充',
  contentType: 'exhibit',
  coverImages: [],
  summary: '该讲解内容待补充。',
  body: '该讲解内容待补充。',
  audio: {
    status: 'unavailable',
    unavailableReason: '当前语言暂无语音讲解。'
  },
  chapters: [],
  relatedItems: []
}

const exhibit = ref<ExplainDetailPageViewModel>(defaultDetail)
const globalAudioPlayer = useGlobalAudioPlayer()
const activeTopTab = ref<GuideTopTab>('explain')
const resolvedHallId = ref('')
const retryingAudio = ref(false)
const selectedAudioLanguage = ref<AudioLanguage>('zh-CN')
const detailEntryRequest = ref<{
  exhibitId: string
  targetType?: AudioPlayTargetType
  targetId?: string
} | null>(null)
const detailTextByLanguage = ref<Record<AudioLanguage, string>>({
  'zh-CN': defaultDetail.body,
  'en-US': 'English narration text is not available yet.'
})
const detailTextLoadingByLanguage = ref<Record<AudioLanguage, boolean>>({
  'zh-CN': false,
  'en-US': false
})
const detailTextLoadedByLanguage = ref<Record<AudioLanguage, boolean>>({
  'zh-CN': false,
  'en-US': false
})
// 讲解详情页位置入口暂未开放，避免误导用户进入位置预览流程。
const isLocationActionVisible = false

const contentLanguageOptions: Array<{
  value: AudioLanguage
  label: string
}> = [
  {
    value: 'zh-CN',
    label: '中文'
  },
  {
    value: 'en-US',
    label: 'English'
  }
]

const isAudioLanguage = (value: unknown): value is AudioLanguage => (
  value === 'zh-CN' || value === 'en-US'
)

const isRealHeroImage = (image?: string) => (
  Boolean(image && image !== EXPLAIN_DETAIL_PLACEHOLDER_IMAGE)
)

const heroImage = computed(() => exhibit.value.coverImages.find(isRealHeroImage))
const detailMeta = computed(() => [
  exhibit.value.hallName,
  exhibit.value.floorLabel
].filter(Boolean).join(' · '))
const hallLocationId = computed(() => exhibit.value.hallId || resolvedHallId.value)
const heroSubtitle = computed(() => [
  detailMeta.value
].filter(Boolean).join(' · '))
const currentAudioTarget = computed(() => ({
  targetType: exhibit.value.audio.playTargetType || 'ITEM',
  targetId: exhibit.value.audio.playTargetId || exhibit.value.id,
  lang: selectedAudioLanguage.value
}))
const isCurrentDetailAudio = computed(() => globalAudioPlayer.isCurrentSource(currentAudioTarget.value))
const showAudioPlayer = computed(() => (
  globalAudioPlayer.visible.value && globalAudioPlayer.hasAudio.value
))
const isDetailAudioDockVisible = computed(() => (
  exhibit.value.audio.status === 'playable'
  && !isCurrentDetailAudio.value
))
const audioDockTitle = computed(() => (
  selectedAudioLanguage.value === 'en-US' ? 'English audio' : '中文讲解音频'
))
const audioDockSubtitle = computed(() => [
  exhibit.value.title,
  exhibit.value.audio.durationLabel
].filter(Boolean).join(' · ') || '讲解音频')
const isCurrentDetailAudioTarget = computed(() => {
  const source = globalAudioPlayer.currentSource.value
  const targetType = exhibit.value.audio.playTargetType || 'ITEM'
  const targetId = exhibit.value.audio.playTargetId || exhibit.value.id

  return Boolean(
    globalAudioPlayer.currentAudio.value
    && source?.targetType === targetType
    && source.targetId === targetId
  )
})
// 讲解详情页只定位到所属展厅，不再追踪具体展品点位。
const resolveHallGuidePoi = async () => {
  return guideUseCase.resolveContentLocationPreviewTarget({
    directPoiId: exhibit.value.location?.poiId,
    location: exhibit.value.location,
    hallId: exhibit.value.hallId,
    hallName: exhibit.value.hallName,
    targetName: exhibit.value.title
  })
}

const fallbackTextForLanguage = (lang: AudioLanguage) => (
  lang === 'en-US'
    ? 'English narration text is not available yet.'
    : '当前语言暂无讲解词。'
)

const applyDetailText = (lang: AudioLanguage, text?: string) => {
  const normalized = (text || '').trim()
  detailTextByLanguage.value = {
    ...detailTextByLanguage.value,
    [lang]: normalized || fallbackTextForLanguage(lang)
  }
  detailTextLoadedByLanguage.value = {
    ...detailTextLoadedByLanguage.value,
    [lang]: true
  }
}

const buildTextSourceFromViewModel = (viewModel: ExplainDetailPageViewModel) => ({
  id: viewModel.id,
  name: viewModel.title,
  hallId: viewModel.hallId,
  hallName: viewModel.hallName,
  floorId: viewModel.floorId,
  floorLabel: viewModel.floorLabel,
  image: viewModel.coverImages[0],
  description: viewModel.summary,
  guideText: viewModel.summary,
  audioLanguage: viewModel.audio.language,
  audioHasText: viewModel.audio.hasText,
  playTargetType: viewModel.audio.playTargetType,
  playTargetId: viewModel.audio.playTargetId
})

const loadFullDetailTextForLanguage = async (
  request: NonNullable<typeof detailEntryRequest.value>,
  lang: AudioLanguage,
  preferredViewModel?: ExplainDetailPageViewModel
) => {
  detailTextLoadingByLanguage.value = {
    ...detailTextLoadingByLanguage.value,
    [lang]: true
  }

  try {
    const viewModel = preferredViewModel || toExplainDetailPageViewModel(await explainUseCase.enterExplainDetail({
      ...request,
      lang
    }))

    if (viewModel.audio.hasText) {
      const selection = await explainUseCase.loadExplainDetailText(buildTextSourceFromViewModel(viewModel))
      if (selection.available) {
        const nextViewModel = toExplainDetailPageViewModel(selection.exhibit)
        applyDetailText(lang, nextViewModel.body || nextViewModel.summary)
        return
      }
    }

    applyDetailText(lang, viewModel.body || viewModel.summary)
  } catch (error) {
    console.warn('讲解正文加载失败:', lang, error)
    applyDetailText(lang)
  } finally {
    detailTextLoadingByLanguage.value = {
      ...detailTextLoadingByLanguage.value,
      [lang]: false
    }
  }
}

const loadBilingualDetailText = async (
  request: NonNullable<typeof detailEntryRequest.value>,
  currentLang: AudioLanguage,
  currentViewModel: ExplainDetailPageViewModel
) => {
  await loadFullDetailTextForLanguage(request, currentLang, currentViewModel)
  const nextLang: AudioLanguage = currentLang === 'zh-CN' ? 'en-US' : 'zh-CN'
  void loadFullDetailTextForLanguage(request, nextLang)
}

const ensureDetailTextForLanguage = async (lang: AudioLanguage) => {
  if (detailTextLoadedByLanguage.value[lang] || detailTextLoadingByLanguage.value[lang]) return
  if (!detailEntryRequest.value) {
    applyDetailText(lang)
    return
  }

  await loadFullDetailTextForLanguage(detailEntryRequest.value, lang)
}

const loadExplainDetail = async (
  request: NonNullable<typeof detailEntryRequest.value>,
  lang: AudioLanguage
) => {
  const exhibitData = await explainUseCase.enterExplainDetail({
    ...request,
    lang
  })

  exhibit.value = toExplainDetailPageViewModel(exhibitData)
  selectedAudioLanguage.value = lang

  if (exhibitData.hallId) {
    resolvedHallId.value = exhibitData.hallId
  }

  void loadBilingualDetailText(request, lang, exhibit.value)
}

onLoad(async (options: any = {}) => {
  const tab = Array.isArray(options.tab) ? options.tab[0] : options.tab
  if (isGuideTopTab(tab)) {
    activeTopTab.value = tab
  }

  const exhibitId = Array.isArray(options.id) ? options.id[0] : options.id
  if (!exhibitId) return

  const rawTargetType = Array.isArray(options.targetType) ? options.targetType[0] : options.targetType
  const targetType: AudioPlayTargetType | undefined = rawTargetType === 'ITEM'
    ? 'ITEM'
    : rawTargetType === 'STOP' || rawTargetType === 'GUIDE_STOP'
      ? 'STOP'
      : undefined
  const rawTargetId = Array.isArray(options.targetId) ? options.targetId[0] : options.targetId
  const targetId = rawTargetType === 'GUIDE_STOP'
    ? String(exhibitId)
    : rawTargetId ? String(rawTargetId) : undefined
  const rawLang = Array.isArray(options.lang) ? options.lang[0] : options.lang
  const lang = isAudioLanguage(rawLang) ? rawLang : 'zh-CN'

  try {
    detailEntryRequest.value = {
      exhibitId,
      targetType,
      targetId
    }
    await loadExplainDetail(detailEntryRequest.value, lang)
  } catch (error) {
    console.error('讲解详情加载失败:', error)
    uni.showToast({
      title: '讲解详情加载失败，请稍后重试',
      icon: 'none'
    })
  }
})

onUnload(() => {
  globalAudioPlayer.close()
})

const detailTextFor = (lang: AudioLanguage) => {
  return detailTextByLanguage.value[lang] || fallbackTextForLanguage(lang)
}
const currentDetailText = computed(() => detailTextFor(selectedAudioLanguage.value))
const currentDetailTextLoading = computed(() => detailTextLoadingByLanguage.value[selectedAudioLanguage.value])

const replaceDetailRouteLanguage = (lang: AudioLanguage) => {
  if (typeof window === 'undefined') return

  const nextHash = `#${buildDetailRoute(lang)}`
  if (window.location.hash === nextHash) return

  window.history.replaceState(null, '', `${window.location.pathname}${window.location.search}${nextHash}`)
}

const handleLanguageChange = async (lang: AudioLanguage, options: { syncAudio?: boolean } = {}) => {
  if (lang === selectedAudioLanguage.value) return

  const shouldSyncAudio = options.syncAudio !== false && isCurrentDetailAudioTarget.value
  selectedAudioLanguage.value = lang
  replaceDetailRouteLanguage(lang)
  void ensureDetailTextForLanguage(lang)

  if (shouldSyncAudio && globalAudioPlayer.currentSource.value?.lang !== lang) {
    await globalAudioPlayer.switchLanguage(lang)
  }
}

watch(
  () => globalAudioPlayer.currentSource.value,
  (source) => {
    const lang = source?.lang
    const targetType = exhibit.value.audio.playTargetType || 'ITEM'
    const targetId = exhibit.value.audio.playTargetId || exhibit.value.id

    if (
      isAudioLanguage(lang)
      && source?.targetType === targetType
      && source.targetId === targetId
    ) {
      selectedAudioLanguage.value = lang
      replaceDetailRouteLanguage(lang)
      void ensureDetailTextForLanguage(lang)
    }
  }
)

function buildDetailRoute(lang: AudioLanguage = selectedAudioLanguage.value) {
  const request = detailEntryRequest.value
  const params = new URLSearchParams({
    id: request?.exhibitId || exhibit.value.id,
    tab: activeTopTab.value
  })
  if (currentAudioTarget.value.targetType) {
    params.set('targetType', currentAudioTarget.value.targetType)
  }
  if (currentAudioTarget.value.targetId) {
    params.set('targetId', currentAudioTarget.value.targetId)
  }
  if (lang) {
    params.set('lang', lang)
  }
  return `/pages/exhibit/detail?${params.toString()}`
}

const toAudioItem = (
  selection: NonNullable<Awaited<ReturnType<typeof explainUseCase.selectAudioForExplainDetail>>>
): AudioItem | null => {
  if (!selection.playable || !selection.media?.url) return null

  return {
    id: selection.media.id,
    name: selection.playInfo?.title || selection.exhibit.name,
    audioUrl: selection.media.url,
    image: heroImage.value,
    duration: selection.media.duration,
    language: selection.media.language as AudioLanguage,
    supportedLanguages: (selection.exhibit.supportedLanguages || exhibit.value.audio.supportedLanguages || [])
      .filter(isAudioLanguage)
  }
}

const refreshCurrentAudioOnce = async (message: string) => {
  if (retryingAudio.value || exhibit.value.audio.status !== 'playable') return null

  retryingAudio.value = true
  const selection = await explainUseCase.selectAudioForExplainDetail({
    id: exhibit.value.id,
    name: exhibit.value.title,
    image: heroImage.value,
    description: exhibit.value.summary,
    guideText: exhibit.value.summary,
    audioUrl: exhibit.value.audio.url,
    audioDuration: exhibit.value.audio.duration,
    audioStatus: 'READY',
    audioLanguage: selectedAudioLanguage.value,
    playTargetType: exhibit.value.audio.playTargetType,
    playTargetId: exhibit.value.audio.playTargetId
  }, {
    refreshPlayInfo: true
  })

  retryingAudio.value = false
  const retryAudio = toAudioItem(selection)
  if (retryAudio) return retryAudio

  exhibit.value = {
    ...exhibit.value,
    audio: {
      ...exhibit.value.audio,
      status: 'unavailable',
      url: undefined,
      unavailableReason: selection.unavailableMessage || message || '音频加载失败，当前提供图文讲解。'
    }
  }
  return null
}

const handlePlayAudio = async () => {
  if (isCurrentDetailAudio.value) {
    if (globalAudioPlayer.playing.value) {
      globalAudioPlayer.pause()
      return
    }

    void globalAudioPlayer.resume()
    return
  }

  const selection = exhibit.value.id
    ? await explainUseCase.selectAudioForExplainDetail({
      id: exhibit.value.id,
      name: exhibit.value.title,
      image: heroImage.value,
      description: exhibit.value.summary,
      guideText: exhibit.value.summary,
      audioUrl: exhibit.value.audio.url,
      audioDuration: exhibit.value.audio.duration,
      audioStatus: exhibit.value.audio.status === 'playable' ? 'READY' : 'MISSING',
      audioLanguage: selectedAudioLanguage.value,
      audioUnavailableReason: exhibit.value.audio.unavailableReason,
      playTargetType: exhibit.value.audio.playTargetType,
      playTargetId: exhibit.value.audio.playTargetId
    })
    : null

  if (!selection?.playable || !selection.media?.url) {
    uni.showToast({
      title: selection?.unavailableMessage || exhibit.value.audio.unavailableReason || '该讲解暂无可播放音频',
      icon: 'none'
    })
    return
  }

  const audio = toAudioItem(selection)
  if (!audio) return

  await globalAudioPlayer.play(audio, {
    source: {
      exhibitId: exhibit.value.id,
      targetType: currentAudioTarget.value.targetType,
      targetId: currentAudioTarget.value.targetId,
      lang: selectedAudioLanguage.value,
      title: audio.name,
      detailRoute: buildDetailRoute()
    },
    retryOnError: refreshCurrentAudioOnce,
    displayMode: 'expanded'
  })
}

const handleNavigate = async () => {
  const matchedHallPoi = await resolveHallGuidePoi()
  if (!matchedHallPoi) {
    uni.showToast({
      title: '该讲解暂无所属展厅位置数据',
      icon: 'none'
    })
    return
  }

  resolvedHallId.value = matchedHallPoi.id
  exhibit.value = {
    ...exhibit.value,
    hallId: matchedHallPoi.id,
    hallName: matchedHallPoi.hallName || matchedHallPoi.name,
    location: exhibit.value.location || {
      status: 'hallFallback',
      poiId: matchedHallPoi.id,
      sourcePoiId: matchedHallPoi.id,
      actionText: '查看所属展厅',
      previewOnly: true,
      floorId: matchedHallPoi.floorId,
      floorLabel: matchedHallPoi.floorLabel,
      note: '已定位到该讲解所属展厅。'
    }
  }

  uni.navigateTo({
    url: `/pages/route/detail?facilityId=${encodeURIComponent(matchedHallPoi.id)}&target=${encodeURIComponent(exhibit.value.hallName || exhibit.value.title)}&state=preview`
  })
}

const fallbackToExplainHome = () => {
  uni.redirectTo({
    url: '/pages/index/index?tab=explain',
    fail: () => {
      uni.reLaunch({
        url: '/pages/index/index?tab=explain'
      })
    }
  })
}

const handleBack = () => {
  if (typeof window !== 'undefined' && window.history.length > 1) {
    const currentHref = window.location.href
    window.history.back()

    // H5 深链或异常历史栈下 history.back 可能不改变页面，兜底回到讲解首页。
    window.setTimeout(() => {
      if (window.location.href === currentHref) {
        fallbackToExplainHome()
      }
    }, 800)
    return
  }

  uni.navigateBack({
    delta: 1,
    fail: fallbackToExplainHome
  })
}
</script>

<style scoped lang="scss">
.explain-detail-page {
  position: relative;
  width: 100%;
  height: 100%;
  overflow: hidden;
  background: #f9fafb;
}

.content {
  height: 100%;
  padding: 84px 20px calc(104px + env(safe-area-inset-bottom));
  box-sizing: border-box;
}

.content.without-location-action {
  padding-bottom: calc(28px + env(safe-area-inset-bottom));
}

.hero-image,
.hero-placeholder {
  width: 100%;
  height: 250px;
  border-radius: 8px;
}

.hero-image {
  background: #e9ece5;
}

.hero-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background: #e9ece5;
}

.hero-placeholder-text {
  font-size: 20px;
  line-height: 28px;
  font-weight: 800;
  color: #68725d;
}

.detail-body {
  padding-top: 22px;
}

.detail-title,
.detail-meta,
.audio-time,
.section-title,
.section-text,
.location-text {
  display: block;
}

.detail-title {
  font-size: 29px;
  line-height: 38px;
  font-weight: 800;
  color: #151713;
}

.detail-meta {
  margin-top: 8px;
  font-size: 16px;
  line-height: 22px;
  color: #555c51;
}

.detail-chip-row {
  margin-top: 12px;
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.detail-chip {
  max-width: 100%;
  height: 28px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  box-sizing: border-box;
  background: #f4f6ef;
  border: 1px solid #dde2d3;
  border-radius: 8px;
}

.detail-chip.shared {
  background: #151713;
  border-color: #151713;
}

.detail-chip-text {
  display: block;
  max-width: 100%;
  font-size: 13px;
  line-height: 18px;
  font-weight: 700;
  color: #555c51;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.detail-chip.shared .detail-chip-text {
  color: #e0df00;
}

.language-switch {
  width: 100%;
  height: 42px;
  margin-top: 16px;
  padding: 3px;
  display: flex;
  box-sizing: border-box;
  background: #ecefe6;
  border: 1px solid #dde2d3;
  border-radius: 8px;
}

.language-option {
  min-width: 0;
  flex: 1;
  height: 34px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border-radius: 6px;
  color: #555c51;
}

.language-option.active {
  background: #151713;
  color: #e0df00;
}

.language-option.disabled {
  opacity: 0.56;
}

.language-option-text {
  display: block;
  max-width: 100%;
  font-size: 14px;
  line-height: 20px;
  font-weight: 800;
  color: currentColor;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.audio-panel {
  height: 76px;
  margin-top: 24px;
  padding: 0 15px;
  display: flex;
  align-items: center;
  gap: 14px;
  box-sizing: border-box;
  background: #0b0c0a;
  border-radius: 8px;
  color: #ffffff;
}

.audio-play {
  flex-shrink: 0;
  width: 42px;
  height: 42px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  color: #151713;
  background: #e0df00;
}

.audio-time {
  flex-shrink: 0;
  font-size: 14px;
  line-height: 20px;
  color: #ffffff;
}

.audio-track {
  position: relative;
  flex: 1;
  height: 3px;
  background: rgba(255, 255, 255, 0.24);
  border-radius: 999px;
}

.audio-dot {
  position: absolute;
  top: 50%;
  left: 0;
  width: 12px;
  height: 12px;
  background: #e0df00;
  border-radius: 50%;
  transform: translateY(-50%);
}

.content-section {
  margin-top: 28px;
}

.section-title {
  font-size: 19px;
  line-height: 27px;
  font-weight: 800;
  color: #151713;
}

.section-text {
  margin-top: 13px;
  font-size: 16px;
  line-height: 28px;
  color: #2f352d;
  white-space: pre-line;
}

.text-load-btn {
  width: 100%;
  height: 44px;
  margin-top: 14px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  background: #151713;
  border-radius: 8px;
  color: #e0df00;
}

.text-load-btn.disabled {
  opacity: 0.62;
}

.text-load-btn-text {
  display: block;
  max-width: 100%;
  font-size: 15px;
  line-height: 21px;
  font-weight: 800;
  color: currentColor;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.section-hint {
  display: block;
  margin-top: 12px;
  font-size: 14px;
  line-height: 20px;
  color: #68725d;
}

.action-bar {
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 12px 20px calc(18px + env(safe-area-inset-bottom));
  box-sizing: border-box;
  background: rgba(249, 250, 251, 0.96);
}

.explain-detail-page.with-audio-player .action-bar {
  bottom: 88px;
}

.location-btn {
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  box-sizing: border-box;
  background: #ffffff;
  border: 1px solid #151713;
  border-radius: 8px;
  color: #151713;
}

.location-btn.disabled {
  color: #8d9488;
  border-color: #d8dcd3;
  background: #f2f4ef;
}

.location-icon {
  flex-shrink: 0;
}

.location-text {
  font-size: 16px;
  line-height: 22px;
  font-weight: 700;
  color: currentColor;
}

.explain-detail-page {
  background: #111a14;
}

.content {
  height: 100%;
  padding: 0;
  box-sizing: border-box;
  background: #111a14;
}

.content.with-fixed-player {
  padding-bottom: 0;
}

.immersive-hero {
  position: relative;
  min-height: 392px;
  padding: calc(env(safe-area-inset-top) + 20px) 24px 88px;
  box-sizing: border-box;
  overflow: hidden;
  background:
    radial-gradient(circle at 78% 18%, rgba(64, 88, 58, 0.3), rgba(64, 88, 58, 0) 30%),
    linear-gradient(180deg, #24351e 0%, #111a14 100%);
  color: #ffffff;
}

.hero-real-image {
  position: absolute;
  inset: 0;
  z-index: 0;
  width: 100%;
  height: 100%;
}

.hero-image-shade {
  position: absolute;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(180deg, rgba(12, 20, 14, 0.34) 0%, rgba(13, 22, 16, 0.7) 62%, #111a14 100%),
    linear-gradient(90deg, rgba(10, 16, 11, 0.72) 0%, rgba(10, 16, 11, 0.34) 58%, rgba(10, 16, 11, 0.68) 100%);
}

.immersive-hero.has-real-image {
  background: #111a14;
}

.immersive-hero.has-real-image .hero-image-shade {
  background:
    linear-gradient(180deg, rgba(10, 16, 11, 0.12) 0%, rgba(10, 16, 11, 0.38) 58%, rgba(17, 26, 20, 0.78) 100%),
    linear-gradient(90deg, rgba(10, 16, 11, 0.46) 0%, rgba(10, 16, 11, 0.1) 48%, rgba(10, 16, 11, 0.36) 100%);
}

.hero-back {
  position: relative;
  z-index: 4;
  width: 38px;
  height: 38px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
}

.hero-back-icon {
  display: block;
  font-size: 34px;
  line-height: 34px;
  font-weight: 600;
  color: currentColor;
}

.hero-copy {
  position: relative;
  z-index: 3;
  min-height: 250px;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

.detail-title,
.detail-meta {
  display: block;
}

.detail-title {
  max-width: 100%;
  font-size: 34px;
  line-height: 42px;
  font-weight: 900;
  color: #ffffff;
  text-shadow: 0 3px 0 rgba(0, 0, 0, 0.18);
  word-break: break-word;
}

.detail-meta {
  margin-top: 8px;
  font-size: 15px;
  line-height: 22px;
  font-weight: 600;
  color: rgba(255, 255, 255, 0.88);
}

.detail-chip-row {
  margin-top: 12px;
  gap: 7px;
}

.detail-chip {
  height: 28px;
  background: rgba(255, 255, 255, 0.1);
  border-color: rgba(255, 255, 255, 0.22);
  color: #ffffff;
}

.detail-chip.shared {
  background: rgba(231, 233, 0, 0.14);
  border-color: rgba(231, 233, 0, 0.38);
}

.detail-chip-text,
.detail-chip.shared .detail-chip-text {
  color: rgba(255, 255, 255, 0.9);
}

.language-anchor {
  width: min(315px, 100%);
  height: 64px;
  margin-top: 26px;
  padding: 6px;
  display: flex;
  box-sizing: border-box;
  background: rgba(17, 20, 15, 0.92);
  border-radius: 16px;
  box-shadow: 0 12px 26px rgba(0, 0, 0, 0.24);
}

.language-anchor-option {
  min-width: 0;
  flex: 1;
  height: 52px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 12px;
  color: #ffffff;
}

.language-anchor-option.active {
  background: #e7e900;
  color: #0f140d;
}

.language-anchor-text {
  max-width: 100%;
  font-size: 18px;
  line-height: 24px;
  font-weight: 900;
  color: currentColor;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.content-panel {
  position: relative;
  z-index: 2;
  min-height: calc(100vh - 318px);
  margin-top: -48px;
  padding: 34px 26px calc(172px + env(safe-area-inset-bottom));
  box-sizing: border-box;
  background: #f8f9f4;
  border-radius: 28px 28px 0 0;
  box-shadow: 0 -18px 34px rgba(0, 0, 0, 0.18);
}

.content-section {
  margin-top: 0;
  padding-top: 22px;
  scroll-margin-top: 18px;
}

.content-section + .content-section {
  margin-top: 52px;
}

.section-heading {
  display: flex;
  align-items: center;
  gap: 14px;
}

.section-heading.compact {
  gap: 10px;
}

.section-marker {
  flex-shrink: 0;
  width: 9px;
  height: 46px;
  border-radius: 999px;
  background: #e7e900;
}

.section-marker.english {
  background: #7aa678;
}

.section-marker.muted {
  height: 28px;
  background: #b8c6af;
}

.section-title {
  font-size: 24px;
  line-height: 32px;
  font-weight: 900;
  color: #111827;
}

.section-text {
  margin-top: 16px;
  display: block;
  font-size: 17px;
  line-height: 29px;
  color: #233044;
  white-space: pre-line;
  word-break: break-word;
}

.section-hint {
  color: #6d7568;
}

.detail-audio-dock {
  position: fixed;
  left: 50%;
  right: auto;
  bottom: calc(12px + env(safe-area-inset-bottom));
  z-index: 2201;
  width: calc(min(100vw, 430px) - 28px);
  min-height: 126px;
  padding: 16px 14px 16px 16px;
  display: flex;
  align-items: center;
  gap: 13px;
  box-sizing: border-box;
  background: #10140f;
  border: 1px solid rgba(231, 233, 0, 0.18);
  border-radius: 24px;
  box-shadow: 0 18px 34px rgba(0, 0, 0, 0.28);
  transform: translateX(-50%);
  color: #ffffff;
}

.detail-audio-play {
  flex-shrink: 0;
  width: 56px;
  height: 56px;
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #e7e900;
  color: #0b0f0a;
}

.detail-audio-main {
  min-width: 0;
  flex: 1;
}

.detail-audio-title,
.detail-audio-subtitle,
.detail-audio-lang-text {
  display: block;
}

.detail-audio-title {
  font-size: 17px;
  line-height: 23px;
  font-weight: 900;
  color: #ffffff;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.detail-audio-subtitle {
  margin-top: 3px;
  font-size: 13px;
  line-height: 18px;
  color: rgba(255, 255, 255, 0.72);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.detail-audio-progress {
  height: 5px;
  margin-top: 13px;
  overflow: hidden;
  background: rgba(255, 255, 255, 0.28);
  border-radius: 999px;
}

.detail-audio-progress-fill {
  width: 22%;
  height: 100%;
  background: #e7e900;
  border-radius: inherit;
}

.detail-audio-lang {
  flex-shrink: 0;
  min-width: 46px;
  height: 36px;
  padding: 0 10px;
  display: flex;
  align-items: center;
  justify-content: center;
  box-sizing: border-box;
  border-radius: 12px;
  border: 1px solid rgba(255, 255, 255, 0.46);
}

@media (max-width: 360px) {
  .immersive-hero {
    padding-left: 20px;
    padding-right: 20px;
  }

  .detail-title {
    font-size: 30px;
    line-height: 38px;
  }

  .content-panel {
    padding-left: 22px;
    padding-right: 22px;
  }

  .detail-audio-dock {
    gap: 10px;
    padding-left: 12px;
  }

}
</style>
