<template>
  <view
    class="poi-search-panel"
    :class="[`variant-${variant}`, { expanded: showSearchContent, 'is-collapsed': isHomeCollapsed }]"
    @touchstart="handlePanelTouchStart"
    @touchend="handlePanelTouchEnd"
    @touchcancel="resetPanelTouch"
    @mousedown="handlePanelMouseStart"
    @mousemove="handlePanelMouseMove"
    @mouseup="handlePanelMouseEnd"
    @mouseleave="resetPanelMouse"
  >
    <view v-if="variant === 'home' && showSearchContent" class="home-fullscreen-nav">
      <view class="home-back-button" @tap.stop="collapseHomePanel">
        <text class="home-back-icon">‹</text>
      </view>
      <text class="home-fullscreen-title">点位搜索</text>
      <view class="home-nav-spacer"></view>
    </view>

    <view class="search-box" @tap="handleSearchBoxTap" @click="handleSearchBoxTap">
      <view class="search-icon"></view>
      <input
        class="search-input"
        type="text"
        confirm-type="search"
        :focus="searchInputFocused"
        placeholder="请输入地点进行搜索"
        placeholder-class="search-placeholder"
        :value="searchDraftKeyword"
        @mousedown="handleSearchBoxTap"
        @click="handleSearchBoxTap"
        @tap="handleSearchBoxTap"
        @focus="handleSearchFocus"
        @input="handleSearchInput"
        @confirm="handleSearchConfirm"
      />
      <view v-if="searchDraftKeyword" class="clear-button" @tap.stop="handleSearchClear">
        <text class="clear-text">×</text>
      </view>
      <view v-else class="voice-icon"></view>
    </view>

    <view v-if="variant === 'home' && !showSearchContent" class="home-category-strip">
      <view
        v-for="item in primaryFacilities"
        :key="item.id"
        class="home-category-chip"
        @tap="handleFacilityShortcut(item)"
      >
        <view class="home-category-icon line-icon" :class="`icon-${item.icon}`">
          <view class="icon-part a"></view>
          <view class="icon-part b"></view>
          <view class="icon-part c"></view>
        </view>
        <text class="home-category-label">{{ item.label }}</text>
      </view>
    </view>

    <view v-if="showSearchContent" class="poi-search-content">
      <view v-if="variant === 'home'" class="home-collapse-handle" @tap.stop="collapseHomePanel">
        <view class="home-collapse-bar"></view>
      </view>

      <view class="category-scroll">
        <view class="category-grid">
          <view
            v-for="item in primaryFacilityColumns"
            :key="item.id"
            class="category-item"
            :class="{ active: searchKeyword === item.label }"
            @tap="handleFacilityShortcut(item)"
          >
            <view class="line-icon" :class="`icon-${item.icon}`">
              <view class="icon-part a"></view>
              <view class="icon-part b"></view>
              <view class="icon-part c"></view>
            </view>
            <text class="category-label">{{ item.label }}</text>
          </view>
        </view>
      </view>

      <view class="section-title-row">
        <text class="section-title">服务设施</text>
      </view>

      <view class="facility-chip-row">
        <view
          v-for="item in serviceFacilities"
          :key="item.id"
          class="facility-chip"
          :class="{ active: searchKeyword === item.label }"
          @tap="handleFacilityShortcut(item)"
        >
          <view class="chip-icon" :class="`chip-${item.icon}`"></view>
          <text class="facility-chip-text">{{ item.label }}</text>
        </view>
      </view>

      <view class="result-card">
        <view class="result-card-header">
          <text class="museum-title">深圳自然博物馆</text>
          <text class="result-count">{{ floorResults.length }} 个点位</text>
        </view>

        <view class="result-body">
          <view class="floor-tabs">
            <view
              v-for="floor in displayFloors"
              :key="floor.label"
              class="floor-tab"
              :class="{ active: activeFloor === floor.label }"
              @tap="handleFloorChange(floor.label)"
            >
              <text class="floor-tab-text">{{ floor.label }}</text>
            </view>
          </view>

          <view class="result-list">
            <view
              v-for="item in floorResults"
              :key="item.id"
              class="result-row"
              @tap="handleResultTap(item)"
            >
              <view class="pin-icon"></view>
              <view class="result-copy">
                <text class="result-name">{{ item.name }}</text>
                <text class="result-meta">{{ item.floorLabel }} · {{ item.primaryCategory.label }}</text>
              </view>
              <text class="result-arrow">›</text>
            </view>

            <view v-if="!floorResults.length" class="empty-state">
              <text class="empty-title">{{ emptyTitle }}</text>
              <text class="empty-desc">{{ emptyDesc }}</text>
            </view>
          </view>
        </view>
      </view>
    </view>
  </view>
</template>

<script setup lang="ts">
import { computed, nextTick, onMounted, onUnmounted, ref, watch } from 'vue'
import type {
  MuseumFloor,
  MuseumPoi
} from '@/domain/museum'
import {
  compareFloorsTopToBottom,
  isIndoorNavigableFloor,
  isPoiOnIndoorNavigableFloor
} from '@/domain/guideFloor'
import {
  guideUseCase
} from '@/usecases/guideUseCase'

export interface PoiSearchShortcut {
  label: string
  keywords: string[]
}

interface ShortcutItem extends PoiSearchShortcut {
  id: string
  icon: string
}

const props = withDefaults(defineProps<{
  initialKeyword?: string
  variant?: 'home' | 'page'
  autofocus?: boolean
}>(), {
  initialKeyword: '',
  variant: 'page',
  autofocus: false
})

const emit = defineEmits<{
  resultTap: [poi: MuseumPoi]
  'expanded-change': [expanded: boolean]
}>()

const primaryFacilities: ShortcutItem[] = [
  { id: 'exhibition', label: '展览', icon: 'exhibition', keywords: ['展览', '展厅', '展馆', 'exhibition_hall', 'temp_exhibition'] },
  { id: 'cinema', label: '影院', icon: 'cinema', keywords: ['影院', '剧场', '影厅', 'cinema', 'theater'] },
  { id: 'restroom', label: '洗手间', icon: 'restroom', keywords: ['洗手间', '卫生间', 'toilet', 'accessible_toilet'] },
  { id: 'restaurant', label: '餐厅', icon: 'restaurant', keywords: ['餐厅', '餐饮', '咖啡', 'restaurant', 'commercial'] },
  { id: 'ticket-office', label: '售票处', icon: 'ticket', keywords: ['售票处', '售票', '票务', 'ticket_office'] },
  { id: 'nursing', label: '母婴室', icon: 'nursing', keywords: ['母婴室', 'mother_baby_room'] },
  { id: 'service-center', label: '服务中心', icon: 'service', keywords: ['服务中心', '服务台', '咨询台', 'service_desk', 'service_space'] },
  { id: 'elevator', label: '电梯', icon: 'elevator', keywords: ['电梯', 'elevator'] },
  { id: 'escalator', label: '扶梯', icon: 'escalator', keywords: ['扶梯', 'escalator'] },
  { id: 'stairs', label: '楼梯', icon: 'stairs', keywords: ['楼梯', 'stairs', 'stair'] }
]

const primaryFacilityVisibleColumnCount = 4
const primaryFacilityRowCount = 2

const primaryFacilityColumns = computed(() => {
  const firstScreenItems = primaryFacilities.slice(0, primaryFacilityVisibleColumnCount * primaryFacilityRowCount)
  const overflowItems = primaryFacilities.slice(primaryFacilityVisibleColumnCount * primaryFacilityRowCount)
  const orderedItems: ShortcutItem[] = []

  for (let columnIndex = 0; columnIndex < primaryFacilityVisibleColumnCount; columnIndex += 1) {
    const topItem = firstScreenItems[columnIndex]
    const bottomItem = firstScreenItems[columnIndex + primaryFacilityVisibleColumnCount]
    if (topItem) orderedItems.push(topItem)
    if (bottomItem) orderedItems.push(bottomItem)
  }

  return orderedItems.concat(overflowItems)
})

const serviceFacilities: ShortcutItem[] = [
  { id: 'drink', label: '饮料售卖机', icon: 'drink', keywords: ['饮料售卖机', '饮料', '售卖机', 'vending'] },
  { id: 'ticket', label: '票务售卖机', icon: 'ticket', keywords: ['票务售卖机', '票务', '售票', 'ticket_office'] },
  { id: 'souvenir', label: '纪念品售卖机', icon: 'bag', keywords: ['纪念品售卖机', '纪念品', '商店', 'souvenir'] }
]

const searchKeyword = ref('')
const searchDraftKeyword = ref('')
const searchInputFocused = ref(false)
const homeExpanded = ref(false)
const touchStartX = ref(0)
const touchStartY = ref(0)
const isPanelTouching = ref(false)
const isPanelMouseDown = ref(false)
const homeContentTapBlockedUntil = ref(0)
const floors = ref<MuseumFloor[]>([])
const activeFloor = ref('1F')
const pois = ref<MuseumPoi[]>([])
const isLoading = ref(false)
const initialSpaceLoadError = ref('')
let searchRequestSeq = 0

const showSearchContent = computed(() => props.variant === 'page' || homeExpanded.value)
const isHomeCollapsed = computed(() => props.variant === 'home' && !showSearchContent.value)
const collapseDragThreshold = 48
const homeExpandTapGuardMs = 360

const displayFloors = computed(() => [...floors.value]
  .filter(isIndoorNavigableFloor)
  .sort(compareFloorsTopToBottom))

const floorResults = computed(() => {
  const matched = pois.value.filter((poi) => poi.floorLabel === activeFloor.value)
  return matched.slice(0, props.variant === 'home' ? 8 : 12)
})

const emptyTitle = computed(() => (
  isLoading.value ? '正在读取点位' : initialSpaceLoadError.value || '暂无匹配点位'
))

const emptyDesc = computed(() => (
  initialSpaceLoadError.value
    ? '请检查 SGS 空间点位数据接口后重试'
    : '可切换楼层或搜索服务设施'
))

const isVisiblePoi = (poi: MuseumPoi) => poi.primaryCategory.id !== 'touring_poi'
  && isPoiOnIndoorNavigableFloor(poi)

const dedupePois = (items: MuseumPoi[]) => {
  const seenIds = new Set<string>()
  return items.filter((poi) => {
    if (!poi.id || seenIds.has(poi.id)) return false
    seenIds.add(poi.id)
    return true
  })
}

const isPoiMatchingKeywords = (poi: MuseumPoi, keywords: string[]) => {
  const searchableText = [
    poi.name,
    poi.floorId,
    poi.floorLabel,
    poi.primaryCategory.id,
    poi.primaryCategory.label,
    poi.primaryCategory.iconType,
    ...poi.categories.flatMap((category) => [
      category.id,
      category.label,
      category.iconType
    ])
  ]
    .filter(Boolean)
    .join(' ')
    .toLowerCase()

  return keywords.some((keyword) => searchableText.includes(keyword.trim().toLowerCase()))
}

const findShortcutByKeyword = (keyword: string) => {
  const normalized = keyword.trim().toLowerCase()
  if (!normalized) return null

  return [...primaryFacilities, ...serviceFacilities].find((shortcut) => (
    shortcut.label.toLowerCase() === normalized
    || shortcut.keywords.some((item) => item.trim().toLowerCase() === normalized)
  )) || null
}

const syncActiveFloor = () => {
  if (!displayFloors.value.length) {
    activeFloor.value = ''
    return
  }

  if (displayFloors.value.some((floor) => floor.label === activeFloor.value)) return

  activeFloor.value = displayFloors.value.find((floor) => floor.label === '1F')?.label
    || displayFloors.value[0].label
}

const syncActiveFloorToResults = (items: MuseumPoi[]) => {
  if (!displayFloors.value.length) {
    activeFloor.value = ''
    return
  }

  const resultFloorLabels = new Set(items.map((poi) => poi.floorLabel).filter(Boolean))
  if (activeFloor.value && resultFloorLabels.has(activeFloor.value)) return

  const firstFloorWithResult = displayFloors.value.find((floor) => resultFloorLabels.has(floor.label))
  if (firstFloorWithResult) {
    activeFloor.value = firstFloorWithResult.label
    return
  }

  syncActiveFloor()
}

const loadFloors = async () => {
  try {
    floors.value = await guideUseCase.getFloors()
    syncActiveFloor()
  } catch (error) {
    console.error('加载点位搜索楼层失败:', error)
    floors.value = []
    activeFloor.value = ''
  }
}

const loadPois = async (keyword = '') => {
  const requestSeq = ++searchRequestSeq
  isLoading.value = true
  initialSpaceLoadError.value = ''
  pois.value = []
  try {
    const result = await guideUseCase.searchPois(keyword)
    if (requestSeq !== searchRequestSeq) return
    const visiblePois = result.filter(isVisiblePoi)
    pois.value = visiblePois
    syncActiveFloorToResults(visiblePois)
  } catch (error) {
    if (requestSeq !== searchRequestSeq) return
    console.error('加载点位搜索结果失败:', error)
    pois.value = []
    uni.showToast({
      title: '点位数据读取失败',
      icon: 'none'
    })
  } finally {
    if (requestSeq === searchRequestSeq) {
      isLoading.value = false
    }
  }
}

const loadInitialSpacePoints = async () => {
  const requestSeq = ++searchRequestSeq
  isLoading.value = true
  initialSpaceLoadError.value = ''
  pois.value = []

  try {
    const result = await guideUseCase.getInitialSearchSpacePoints()
    if (requestSeq !== searchRequestSeq) return
    const visiblePois = result.filter(isVisiblePoi)
    pois.value = visiblePois
    syncActiveFloorToResults(visiblePois)
  } catch (error) {
    if (requestSeq !== searchRequestSeq) return
    console.error('加载 SDK 空间点位失败:', error)
    pois.value = []
    initialSpaceLoadError.value = '空间点位读取失败'
    uni.showToast({
      title: '空间点位读取失败',
      icon: 'none'
    })
  } finally {
    if (requestSeq === searchRequestSeq) {
      isLoading.value = false
    }
  }
}

const focusSearchInput = async () => {
  searchInputFocused.value = false
  await nextTick()
  searchInputFocused.value = true
}

const expandHomePanel = () => {
  if (props.variant !== 'home') return

  if (!homeExpanded.value) {
    homeContentTapBlockedUntil.value = Date.now() + homeExpandTapGuardMs
  }
  homeExpanded.value = true
}

const shouldBlockHomeContentTap = () => (
  props.variant === 'home'
  && Date.now() < homeContentTapBlockedUntil.value
)

// #ifdef H5
const homeSearchLockClass = 'guide-home-search-fullscreen-lock'
let removeViewportResizeListener: (() => void) | null = null

const updateHomeSearchViewportHeight = () => {
  if (typeof document === 'undefined') return

  const viewportHeight = window.visualViewport?.height || window.innerHeight
  document.documentElement.style.setProperty('--home-search-viewport-height', `${viewportHeight}px`)
}

const setH5HomeSearchLock = (locked: boolean) => {
  if (typeof document === 'undefined') return

  document.documentElement.classList.toggle(homeSearchLockClass, locked)
  document.body?.classList.toggle(homeSearchLockClass, locked)

  if (!locked) {
    document.documentElement.style.removeProperty('--home-search-viewport-height')
    removeViewportResizeListener?.()
    removeViewportResizeListener = null
    return
  }

  updateHomeSearchViewportHeight()

  if (!removeViewportResizeListener) {
    const target = window.visualViewport || window
    target.addEventListener('resize', updateHomeSearchViewportHeight)
    removeViewportResizeListener = () => {
      target.removeEventListener('resize', updateHomeSearchViewportHeight)
    }
  }
}
// #endif

const handleSearchFocus = () => {
  if (props.variant === 'home') {
    expandHomePanel()
  }
}

const handleSearchBoxTap = () => {
  if (props.variant !== 'home' || homeExpanded.value) return

  expandHomePanel()
  void focusSearchInput()
}

const collapseHomePanel = () => {
  if (props.variant !== 'home') return

  homeExpanded.value = false
  searchInputFocused.value = false
  uni.hideKeyboard()
}

const resetPanelTouch = () => {
  isPanelTouching.value = false
  touchStartX.value = 0
  touchStartY.value = 0
}

const resetPanelMouse = () => {
  isPanelMouseDown.value = false
  touchStartX.value = 0
  touchStartY.value = 0
}

const getGesturePoint = (event: any) => {
  const touchPoint = event.changedTouches?.[0] || event.touches?.[0]
  if (touchPoint) return touchPoint

  if (typeof event.clientX === 'number' && typeof event.clientY === 'number') {
    return event
  }

  return null
}

const handlePanelTouchStart = (event: any) => {
  if (props.variant !== 'home' || !homeExpanded.value) return

  const point = getGesturePoint(event)
  if (!point) return

  touchStartX.value = point.clientX
  touchStartY.value = point.clientY
  isPanelTouching.value = true
}

const handlePanelTouchEnd = (event: any) => {
  if (!isPanelTouching.value || props.variant !== 'home' || !homeExpanded.value) {
    resetPanelTouch()
    return
  }

  const point = getGesturePoint(event)
  if (!point) {
    resetPanelTouch()
    return
  }

  const deltaX = point.clientX - touchStartX.value
  const deltaY = point.clientY - touchStartY.value
  const isIntentionalDownSwipe = deltaY > collapseDragThreshold
    && deltaY > Math.abs(deltaX) * 1.2

  if (isIntentionalDownSwipe) {
    collapseHomePanel()
  }

  resetPanelTouch()
}

const handlePanelMouseStart = (event: any) => {
  if (props.variant !== 'home' || !homeExpanded.value) return

  const point = getGesturePoint(event)
  if (!point) return

  touchStartX.value = point.clientX
  touchStartY.value = point.clientY
  isPanelMouseDown.value = true
}

const handlePanelMouseMove = (event: any) => {
  if (!isPanelMouseDown.value || props.variant !== 'home' || !homeExpanded.value) return
  const point = getGesturePoint(event)
  if (!point) return

  const deltaX = point.clientX - touchStartX.value
  const deltaY = point.clientY - touchStartY.value
  const isIntentionalDownDrag = deltaY > collapseDragThreshold
    && deltaY > Math.abs(deltaX) * 1.2

  if (isIntentionalDownDrag) {
    collapseHomePanel()
    resetPanelMouse()
  }
}

const handlePanelMouseEnd = (event: any) => {
  if (!isPanelMouseDown.value || props.variant !== 'home' || !homeExpanded.value) {
    resetPanelMouse()
    return
  }

  const point = getGesturePoint(event)
  if (!point) {
    resetPanelMouse()
    return
  }

  const deltaX = point.clientX - touchStartX.value
  const deltaY = point.clientY - touchStartY.value
  const isIntentionalDownDrag = deltaY > collapseDragThreshold
    && deltaY > Math.abs(deltaX) * 1.2

  if (isIntentionalDownDrag) {
    collapseHomePanel()
  }

  resetPanelMouse()
}

const handleSearchInput = (event: any) => {
  searchDraftKeyword.value = event.detail.value
  if (props.variant === 'home') {
    expandHomePanel()
  }
}

const handleSearchClear = async () => {
  searchDraftKeyword.value = ''
  searchKeyword.value = ''
  await loadInitialSpacePoints()
  await focusSearchInput()
}

const handleSearchConfirm = async (event?: any) => {
  const value = typeof event?.detail?.value === 'string'
    ? event.detail.value
    : searchDraftKeyword.value

  searchKeyword.value = value.trim()
  searchDraftKeyword.value = searchKeyword.value
  expandHomePanel()

  const matchedShortcut = findShortcutByKeyword(searchKeyword.value)
  if (matchedShortcut) {
    await searchShortcut(matchedShortcut)
    return
  }

  await loadPois(searchKeyword.value)
}

const searchShortcut = async (shortcut: PoiSearchShortcut) => {
  const requestSeq = ++searchRequestSeq
  searchKeyword.value = shortcut.label
  searchDraftKeyword.value = shortcut.label
  expandHomePanel()
  isLoading.value = true
  initialSpaceLoadError.value = ''
  pois.value = []

  try {
    const groups = await Promise.all(shortcut.keywords.map((keyword) => guideUseCase.searchPois(keyword)))
    if (requestSeq !== searchRequestSeq) return
    const matchedPois = dedupePois(groups.flat())
      .filter(isVisiblePoi)
      .filter((poi) => isPoiMatchingKeywords(poi, shortcut.keywords))
    pois.value = matchedPois
    syncActiveFloorToResults(matchedPois)
  } catch (error) {
    if (requestSeq !== searchRequestSeq) return
    console.error('加载点位分类搜索结果失败:', error)
    pois.value = []
    uni.showToast({
      title: '点位数据读取失败',
      icon: 'none'
    })
  } finally {
    if (requestSeq === searchRequestSeq) {
      isLoading.value = false
    }
  }
}

const handleFacilityShortcut = (shortcut: ShortcutItem) => {
  void searchShortcut(shortcut)
}

const handleFloorChange = (floor: string) => {
  activeFloor.value = floor
}

const encodeQueryValue = (value: string) => encodeURIComponent(value)

const createPoiLocationQuery = (poi: MuseumPoi) => (
  `id=${encodeQueryValue(poi.id)}`
  + `&target=${encodeQueryValue(poi.name)}`
  + `&floorId=${encodeQueryValue(poi.floorId)}`
  + `&floorLabel=${encodeQueryValue(poi.floorLabel)}`
)

const handleResultTap = (poi: MuseumPoi) => {
  if (shouldBlockHomeContentTap()) return

  emit('resultTap', poi)

  if (poi.primaryCategory.id === 'operation_experience') {
    uni.navigateTo({
      url: `/pages/route/detail?facilityId=${encodeQueryValue(poi.id)}&target=${encodeQueryValue(poi.name)}&floorId=${encodeQueryValue(poi.floorId)}&state=preview`
    })
    return
  }

  uni.navigateTo({
    url: `/pages/facility/detail?${createPoiLocationQuery(poi)}`
  })
}

const applyInitialKeyword = async (keyword: string) => {
  const normalizedKeyword = keyword.trim()
  searchKeyword.value = normalizedKeyword
  searchDraftKeyword.value = normalizedKeyword
  if (props.variant === 'page') {
    homeExpanded.value = true
  } else if (normalizedKeyword) {
    expandHomePanel()
  } else {
    homeExpanded.value = false
  }

  if (normalizedKeyword) {
    await loadPois(normalizedKeyword)
    return
  }

  await loadInitialSpacePoints()
}

watch(() => props.initialKeyword, (keyword) => {
  void applyInitialKeyword(keyword || '')
})

watch(showSearchContent, (expanded) => {
  emit('expanded-change', expanded)
  // #ifdef H5
  setH5HomeSearchLock(props.variant === 'home' && expanded)
  // #endif
}, { immediate: true })

onMounted(async () => {
  await loadFloors()
  await applyInitialKeyword(props.initialKeyword)
  if (props.autofocus) {
    void focusSearchInput()
  }
})

onUnmounted(() => {
  // #ifdef H5
  setH5HomeSearchLock(false)
  // #endif
})

defineExpose({
  expandHomePanel,
  collapseHomePanel,
  focusSearchInput,
  searchShortcut
})
</script>

<style scoped lang="scss">
/* #ifdef H5 */
:global(html.guide-home-search-fullscreen-lock),
:global(body.guide-home-search-fullscreen-lock) {
  height: 100%;
  overflow: hidden;
  overscroll-behavior: none;
}
/* #endif */

.poi-search-panel {
  width: 100%;
  max-width: 100%;
  box-sizing: border-box;
  color: #262421;
  font-family: '鸿蒙黑体', 'HarmonyOS Sans SC', 'HarmonyOS Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  overflow-x: hidden;
}

.variant-home.expanded {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  overscroll-behavior: contain;
}

.variant-page {
  height: 100%;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
}

.home-fullscreen-nav {
  height: 38px;
  flex: 0 0 38px;
  display: grid;
  grid-template-columns: 38px minmax(0, 1fr) 38px;
  align-items: center;
  margin-bottom: 10px;
}

.home-back-button,
.home-nav-spacer {
  width: 38px;
  height: 38px;
}

.home-back-button {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 8px;
  background: #ffffff;
  border: 1px solid #e1e4da;
}

.home-back-icon {
  display: flex;
  width: 18px;
  height: 18px;
  align-items: center;
  justify-content: center;
  font-size: 0;
  line-height: 0;
  font-weight: 600;
  color: #151713;
}

.home-back-icon::before {
  content: '';
  width: 10px;
  height: 10px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: translateX(2px) rotate(45deg);
}

.home-fullscreen-title {
  min-width: 0;
  font-size: 17px;
  line-height: 22px;
  font-weight: 800;
  color: #151713;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.search-box {
  height: 48px;
  flex: 0 0 48px;
  display: flex;
  align-items: center;
  padding: 0 14px;
  box-sizing: border-box;
  background: rgba(255, 255, 255, 0.96);
  border: 1px solid #d8dbd0;
  border-radius: 8px;
  box-shadow: 0 8px 18px rgba(36, 49, 42, 0.08);
}

.variant-home .search-box {
  background: #f5f5ed;
  border-color: rgba(224, 225, 0, 0.72);
  box-shadow: none;
}

.variant-home.is-collapsed .search-box {
  height: 38px;
  flex-basis: 38px;
  padding: 0 12px;
  border-radius: 7px;
}

.home-category-strip {
  margin-top: 10px;
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
}

.variant-home.is-collapsed .home-category-strip {
  margin-top: 7px;
  gap: 7px;
  padding-bottom: 1px;
}

.home-category-strip::-webkit-scrollbar {
  display: none;
}

.home-category-chip {
  min-width: 112px;
  height: 58px;
  flex: 0 0 112px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 8px;
  padding: 0 11px;
  box-sizing: border-box;
  background: #e0df00;
  border: 1px solid rgba(0, 0, 0, 0.08);
  border-radius: 8px;
  background-image:
    linear-gradient(116deg, rgba(255, 255, 255, 0.22) 0 32%, rgba(255, 255, 255, 0) 33%),
    linear-gradient(180deg, rgba(255, 255, 255, 0.12), rgba(0, 0, 0, 0.02));
  box-shadow: 0 8px 18px rgba(0, 0, 0, 0.16);
}

.variant-home.is-collapsed .home-category-chip {
  min-width: 76px;
  height: 42px;
  flex-basis: 76px;
  justify-content: center;
  gap: 5px;
  padding: 0 7px;
  border-radius: 7px;
  box-shadow: none;
}

.home-category-chip:nth-child(2) {
  background-color: #18c2b6;
}

.home-category-chip:nth-child(3) {
  background-color: #b99cf0;
}

.home-category-chip:nth-child(4) {
  background-color: #8ad9ae;
}

.home-category-chip:nth-child(5) {
  background-color: #ffc0a1;
}

.home-category-chip:nth-child(6) {
  background-color: #5ed0e4;
}

.home-category-chip:nth-child(7) {
  background-color: #c9a463;
}

.home-category-chip:nth-child(8) {
  background-color: #f4f5ef;
}

.home-category-chip:active {
  transform: translateY(1px);
}

.home-category-icon {
  width: 24px;
  height: 23px;
  flex: 0 0 24px;
  order: 2;
  color: #050704;
}

.home-category-icon.line-icon {
  width: 24px;
  height: 23px;
}

.home-category-icon .icon-part {
  transform: scale(0.8);
  transform-origin: center;
}

.variant-home.is-collapsed .home-category-icon,
.variant-home.is-collapsed .home-category-icon.line-icon {
  width: 20px;
  height: 19px;
  flex-basis: 20px;
}

.variant-home.is-collapsed .home-category-icon .icon-part {
  transform: scale(0.68);
}

.home-category-label {
  min-width: 0;
  max-width: 70px;
  font-size: 14px;
  line-height: 18px;
  font-weight: 800;
  color: #050704;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.variant-home.is-collapsed .home-category-label {
  max-width: 42px;
  font-size: 11px;
  line-height: 14px;
}

.search-icon {
  width: 19px;
  height: 19px;
  position: relative;
  flex-shrink: 0;
  margin-right: 14px;
  border: 2px solid #151713;
  border-radius: 50%;
  box-sizing: border-box;
}

.search-icon::after {
  content: '';
  position: absolute;
  right: -7px;
  bottom: -5px;
  width: 10px;
  height: 2px;
  background: #151713;
  border-radius: 2px;
  transform: rotate(45deg);
}

.variant-home.is-collapsed .search-icon {
  width: 17px;
  height: 17px;
  margin-right: 11px;
  border-width: 2px;
}

.variant-home.is-collapsed .search-icon::after {
  right: -6px;
  bottom: -4px;
  width: 9px;
}

.search-input {
  min-width: 0;
  flex: 1;
  height: 100%;
  font-size: 16px;
  line-height: 22px;
  font-weight: 500;
  color: #262421;
}

.search-input :deep(input),
.search-input :deep(.uni-input-input) {
  font-size: 16px;
  line-height: 22px;
}

.variant-home.is-collapsed .search-input,
.variant-home.is-collapsed .search-input :deep(input),
.variant-home.is-collapsed .search-input :deep(.uni-input-input) {
  font-size: 14px;
  line-height: 19px;
}

.search-placeholder {
  color: #8b8d85;
}

.variant-home.is-collapsed .search-placeholder {
  font-size: 14px;
}

.clear-button,
.voice-icon {
  width: 26px;
  height: 26px;
  flex-shrink: 0;
}

.clear-button {
  display: flex;
  align-items: center;
  justify-content: center;
  border-radius: 50%;
  background: #eef0e8;
}

.variant-home.is-collapsed .clear-button,
.variant-home.is-collapsed .voice-icon {
  width: 32px;
  height: 32px;
}

.variant-home.is-collapsed .clear-text {
  font-size: 17px;
  line-height: 18px;
}

.clear-text {
  font-size: 18px;
  line-height: 20px;
  color: #5b6058;
}

.voice-icon {
  position: relative;
}

.variant-home.is-collapsed .voice-icon::before {
  left: 11px;
  top: 7px;
  width: 10px;
  height: 13px;
}

.variant-home.is-collapsed .voice-icon::after {
  left: 10px;
  bottom: 6px;
  width: 12px;
  height: 7px;
}

.voice-icon::before {
  content: '';
  position: absolute;
  left: 8px;
  top: 4px;
  width: 10px;
  height: 14px;
  border: 2px solid #151713;
  border-radius: 999px;
  box-sizing: border-box;
}

.voice-icon::after {
  content: '';
  position: absolute;
  left: 7px;
  bottom: 3px;
  width: 12px;
  height: 7px;
  border-bottom: 2px solid #151713;
  border-left: 2px solid transparent;
  border-right: 2px solid transparent;
  border-radius: 0 0 999px 999px;
  box-sizing: border-box;
}

.poi-search-content {
  margin-top: 12px;
}

.variant-page .poi-search-content {
  margin-top: 14px;
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-bottom: env(safe-area-inset-bottom);
}

.home-collapse-handle {
  height: 18px;
  margin: -4px 0 6px;
  display: flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
}

.home-collapse-bar {
  width: 42px;
  height: 4px;
  background: #d5d8cf;
  border-radius: 999px;
}

.variant-home .poi-search-content {
  max-height: min(50vh, 430px);
  overflow-y: auto;
  padding-right: 2px;
}

.variant-home.expanded .poi-search-content {
  flex: 1;
  min-height: 0;
  max-height: none;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  padding-right: 0;
}

.category-scroll {
  width: 100%;
  max-width: 100%;
  overflow-x: auto;
  overflow-y: hidden;
  padding-bottom: 4px;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  touch-action: pan-x;
}

.category-scroll::-webkit-scrollbar {
  display: none;
}

.category-grid {
  --category-gap: 8px;
  display: grid;
  grid-auto-flow: column;
  grid-template-rows: repeat(2, minmax(70px, auto));
  grid-auto-columns: calc((100% - 32px) / 5);
  gap: var(--category-gap);
  width: calc(125% + 2px);
  min-width: calc(125% + 2px);
}

.category-item {
  min-width: 0;
  min-height: 70px;
  position: relative;
  padding: 9px 6px 10px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 7px;
  box-sizing: border-box;
  overflow: hidden;
  background: #fffef2;
  border: 1px solid #ecebd2;
  border-radius: 8px;
}

.category-item.active {
  background: #fffddc;
  border-color: #d8d900;
}

.category-item.active::after {
  content: '';
  position: absolute;
  left: 14px;
  right: 14px;
  bottom: 0;
  height: 4px;
  background: var(--museum-accent);
}

.line-icon {
  width: 26px;
  height: 24px;
  position: relative;
  color: #151713;
}

.category-item.active .line-icon {
  color: #151713;
}

.icon-part {
  position: absolute;
  box-sizing: border-box;
}

.icon-service .icon-part.a,
.icon-exhibition .icon-part.a,
.icon-cinema .icon-part.a,
.icon-ticket .icon-part.a,
.icon-drink .icon-part.a,
.icon-bag .icon-part.a {
  left: 4px;
  right: 4px;
  top: 5px;
  bottom: 5px;
  border: 2px solid currentColor;
  border-radius: 3px;
}

.icon-service .icon-part.b,
.icon-exhibition .icon-part.b,
.icon-cinema .icon-part.b,
.icon-ticket .icon-part.b,
.icon-drink .icon-part.b,
.icon-bag .icon-part.b {
  left: 9px;
  right: 9px;
  top: 1px;
  height: 5px;
  border: 2px solid currentColor;
  border-bottom: 0;
  border-radius: 6px 6px 0 0;
}

.icon-elevator .icon-part.a,
.icon-escalator .icon-part.a,
.icon-restroom .icon-part.a,
.icon-nursing .icon-part.a,
.icon-restaurant .icon-part.a,
.icon-stairs .icon-part.a,
.icon-entrance .icon-part.a {
  left: 4px;
  right: 4px;
  top: 3px;
  bottom: 3px;
  border: 2px solid currentColor;
  border-radius: 4px;
}

.icon-elevator .icon-part.b {
  left: 9px;
  top: 7px;
  width: 8px;
  height: 10px;
  border-left: 2px solid currentColor;
  border-right: 2px solid currentColor;
}

.icon-escalator .icon-part.b,
.icon-entrance .icon-part.b {
  left: 7px;
  top: 12px;
  width: 13px;
  height: 2px;
  background: currentColor;
  transform: rotate(-22deg);
}

.icon-restroom .icon-part.b,
.icon-nursing .icon-part.b {
  left: 8px;
  top: 8px;
  width: 4px;
  height: 4px;
  background: currentColor;
  border-radius: 50%;
  box-shadow: 8px 0 0 currentColor;
}

.icon-exhibition .icon-part.c {
  left: 6px;
  right: 6px;
  bottom: 2px;
  height: 2px;
  background: currentColor;
  border-radius: 2px;
}

.icon-cinema .icon-part.b {
  left: 2px;
  right: 2px;
  top: 4px;
  height: 4px;
  border: 0;
  border-top: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  border-radius: 0;
}

.icon-restaurant .icon-part.b {
  left: 7px;
  top: 6px;
  width: 2px;
  height: 12px;
  background: currentColor;
  box-shadow: 5px 0 0 currentColor, 10px 0 0 currentColor;
}

.icon-restaurant .icon-part.c {
  right: 3px;
  top: 5px;
  width: 7px;
  height: 15px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  border-radius: 0 0 0 5px;
}

.icon-stairs .icon-part.b {
  left: 6px;
  top: 7px;
  width: 14px;
  height: 12px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
}

.icon-stairs .icon-part.c {
  left: 10px;
  top: 11px;
  width: 10px;
  height: 8px;
  border-left: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
}

.category-label {
  max-width: 100%;
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
  color: #151713;
  text-align: center;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.category-item.active .category-label {
  color: #151713;
}

.section-title-row {
  position: relative;
  margin-top: 16px;
  margin-bottom: 10px;
  padding-left: 12px;
}

.section-title-row::before {
  content: '';
  position: absolute;
  left: 0;
  top: 3px;
  width: 4px;
  height: 16px;
  background: var(--museum-accent);
}

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

.facility-chip-row {
  display: flex;
  gap: 8px;
  overflow-x: auto;
  padding-bottom: 2px;
  scrollbar-width: none;
  overscroll-behavior-x: contain;
}

.facility-chip-row::-webkit-scrollbar {
  display: none;
}

.facility-chip {
  height: 34px;
  flex: 0 0 auto;
  position: relative;
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 0 12px;
  box-sizing: border-box;
  background: #fffef6;
  border: 1px solid #e8e7d2;
  border-radius: 8px;
}

.facility-chip.active {
  background: #fffddc;
  border-color: #d6d700;
}

.facility-chip.active::after {
  content: '';
  position: absolute;
  left: 12px;
  right: 12px;
  bottom: -1px;
  height: 3px;
  background: var(--museum-accent);
}

.chip-icon {
  width: 14px;
  height: 14px;
  flex: 0 0 14px;
  position: relative;
  border: 2px solid currentColor;
  border-radius: 3px;
  color: #151713;
  box-sizing: border-box;
}

.facility-chip.active .chip-icon {
  color: #151713;
}

.facility-chip-text {
  font-size: 12px;
  line-height: 16px;
  font-weight: 600;
  color: #151713;
  white-space: nowrap;
}

.facility-chip.active .facility-chip-text {
  color: #151713;
}

.result-card {
  margin-top: 14px;
  overflow: hidden;
  background: #fffef7;
  border: 1px solid #ecebd8;
  border-radius: 8px;
  box-shadow: 0 10px 24px rgba(75, 78, 60, 0.08);
}

.result-card-header {
  height: 46px;
  padding: 0 14px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 10px;
  box-sizing: border-box;
  border-bottom: 1px solid #efeedf;
}

.museum-title,
.result-count {
  font-size: 12px;
  line-height: 16px;
  font-weight: 700;
}

.museum-title {
  color: #151713;
}

.result-count {
  color: #6e746c;
  white-space: nowrap;
}

.result-body {
  display: flex;
  min-height: 168px;
}

.floor-tabs {
  width: 62px;
  flex: 0 0 62px;
  padding: 9px 0;
  box-sizing: border-box;
  background: #f6f5e8;
}

.floor-tab {
  position: relative;
  height: 31px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: #676c64;
}

.floor-tab.active {
  color: #151713;
  font-weight: 800;
}

.floor-tab.active::after {
  content: '';
  position: absolute;
  left: 8px;
  right: 8px;
  bottom: 2px;
  height: 3px;
  background: var(--museum-accent);
}

.floor-tab-text {
  font-size: 12px;
  line-height: 16px;
}

.result-list {
  min-width: 0;
  flex: 1;
}

.variant-page .category-grid,
.variant-page .category-scroll,
.variant-page .section-title-row,
.variant-page .facility-chip-row {
  flex: 0 0 auto;
}

.variant-page .result-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.variant-page .result-body {
  flex: 1;
  min-height: 0;
}

.variant-page .floor-tabs,
.variant-page .result-list {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.variant-page .result-list {
  padding-bottom: calc(env(safe-area-inset-bottom) + 10px);
  box-sizing: border-box;
}

.variant-home.expanded .category-grid,
.variant-home.expanded .category-scroll,
.variant-home.expanded .section-title-row,
.variant-home.expanded .facility-chip-row {
  flex: 0 0 auto;
}

.variant-home.expanded .result-card {
  flex: 1;
  min-height: 0;
  display: flex;
  flex-direction: column;
}

.variant-home.expanded .result-body {
  flex: 1;
  min-height: 0;
}

.variant-home.expanded .floor-tabs,
.variant-home.expanded .result-list {
  overflow-y: auto;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
}

.result-row {
  min-height: 58px;
  display: flex;
  align-items: center;
  gap: 9px;
  padding: 10px 12px;
  box-sizing: border-box;
  border-bottom: 1px solid #efefe1;
}

.result-row:last-child {
  border-bottom: 0;
}

.pin-icon {
  width: 15px;
  height: 18px;
  flex: 0 0 15px;
  position: relative;
  border: 2px solid #151713;
  border-radius: 8px 8px 8px 0;
  transform: rotate(-45deg);
  box-sizing: border-box;
}

.pin-icon::after {
  content: '';
  position: absolute;
  left: 3px;
  top: 3px;
  width: 4px;
  height: 4px;
  background: #151713;
  border-radius: 50%;
}

.result-copy {
  min-width: 0;
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.result-name,
.result-meta {
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.result-name {
  font-size: 13px;
  line-height: 18px;
  font-weight: 700;
  color: #151713;
}

.result-meta {
  font-size: 11px;
  line-height: 15px;
  color: #6c716a;
}

.result-arrow {
  font-size: 20px;
  line-height: 20px;
  color: #8b9188;
}

.empty-state {
  min-height: 112px;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 6px;
  padding: 20px;
  box-sizing: border-box;
}

.empty-title {
  font-size: 14px;
  line-height: 20px;
  font-weight: 700;
  color: #151713;
}

.empty-desc {
  font-size: 12px;
  line-height: 17px;
  color: #7a7f78;
}

@media (max-width: 360px) {
  .category-grid {
    grid-template-rows: repeat(2, minmax(62px, auto));
  }

  .variant-page .category-item,
  .variant-home.expanded .category-item {
    min-height: 62px;
    padding: 7px 4px;
    gap: 5px;
  }

  .floor-tabs {
    width: 52px;
    flex-basis: 52px;
  }
}

</style>
