<template>
  <div class="workshop-container">
    <div ref="container" class="three-container"></div>

    <!-- 虚拟光标 -->
    <div v-if="cursorVisible" class="virtual-cursor" :style="{ left: cursorPosition.x + 'px', top: cursorPosition.y + 'px' }">
      <div class="cursor-dot"></div>
      <div class="cursor-ring"></div>
    </div>

    <!-- 手势控制开关按钮 - 放在顶部更明显 -->
    <div class="gesture-control-wrapper">
      <button v-if="!gestureEnabled" @click="toggleGesture" class="gesture-toggle-btn">
        🎥 启用手势控制
      </button>
      <div v-else class="gesture-info">
        <span>{{ gestureStatus }}</span>
        <button @click="toggleGesture" class="toggle-btn">关闭</button>
      </div>
    </div>

    <!-- 摄像头预览窗口 - 始终渲染但可能隐藏 -->
    <div class="camera-preview" :class="{ hidden: !gestureEnabled }">
      <video ref="videoElement" autoplay playsinline></video>
      <canvas ref="canvasElement"></canvas>
    </div>

    <!-- 信息面板 -->
    <div v-if="selectedCube" class="info-panel">
      <div class="info-header">
        <h3>设备信息</h3>
        <button @click="selectedCube = null" class="close-btn">×</button>
      </div>
      <div class="info-content">
        <p><strong>设备名称:</strong> {{ selectedCube.name }}</p>
        <p><strong>设备ID:</strong> {{ selectedCube.id }}</p>
        <p><strong>状态:</strong> <span :style="{ color: selectedCube.statusColor }">{{ selectedCube.status }}</span></p>
        <p><strong>位置:</strong> X: {{ selectedCube.position.x.toFixed(2) }}, Y: {{ selectedCube.position.y.toFixed(2) }}, Z: {{ selectedCube.position.z.toFixed(2) }}</p>
        <p><strong>温度:</strong> {{ selectedCube.temperature }}°C</p>
        <p><strong>运行时长:</strong> {{ selectedCube.runtime }}小时</p>
      </div>
    </div>
  </div>
</template>

<script setup>
import * as THREE from 'three'
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
import { onMounted, onBeforeUnmount, ref } from 'vue'

const container = ref(null)
const selectedCube = ref(null)
const videoElement = ref(null)
const canvasElement = ref(null)
const gestureEnabled = ref(false)
const gestureStatus = ref('正在加载模型...')
const cursorVisible = ref(false)
const cursorPosition = ref({ x: 0, y: 0 })

let scene, camera, renderer, controls, animationId
let cubes = []
let raycaster, mouse
let robotModel = null

// 手势控制相关变量
let videoStream = null
let gestureAnimationId = null
let handposeModel = null
let previousHandPosition = null
let isPinching = false
let isTwoHandMode = false
let palmOpenStartTime = null
let smoothedDelta = { x: 0, y: 0 }

// 手势识别参数
const PINCH_THRESHOLD = 50  // 捏合判定阈值（提高以更容易保持捏合状态）
const DEAD_ZONE = 0.002  // 死区（降低以提高响应）
const SMOOTH_FACTOR = 0.6  // 平滑系数（提高以更平滑）
const ROTATE_SPEED = 2.5  // 旋转速度（继续提高）
const PAN_SPEED = 4.0  // 平移速度（继续提高）
const PALM_RESET_DURATION = 1500  // 手掌重置需要停留的时间（提高到1.5秒避免误触）
const PALM_OPEN_THRESHOLD = 120  // 手掌张开判定阈值（提高以减少误识别）

// 设备数据
const cubeData = [
  { id: 'DEV-001', name: '加工中心A', status: '运行中', statusColor: '#52c41a', temperature: 45, runtime: 1234, color: 0x1890ff },
  { id: 'DEV-002', name: '加工中心B', status: '待机', statusColor: '#faad14', temperature: 28, runtime: 2456, color: 0x52c41a },
  { id: 'DEV-003', name: '装配机器人', status: '运行中', statusColor: '#52c41a', temperature: 38, runtime: 3678, color: 0xf5222d },
  { id: 'DEV-004', name: '质检设备', status: '故障', statusColor: '#f5222d', temperature: 65, runtime: 890, color: 0xfa8c16 },
  { id: 'DEV-005', name: '包装机', status: '运行中', statusColor: '#52c41a', temperature: 42, runtime: 5432, color: 0x722ed1 }
]

onMounted(() => {
  console.log('[Workshop3D] 组件已挂载')
  console.log('[Workshop3D] gestureEnabled 初始值:', gestureEnabled.value)

  initScene()
  createCubes()
  setupInteraction()
  animate()

  window.addEventListener('resize', onWindowResize)
})

onBeforeUnmount(() => {
  cancelAnimationFrame(animationId)
  window.removeEventListener('resize', onWindowResize)
  window.removeEventListener('click', onMouseClick)
  stopGestureControl()
  controls.dispose()
  renderer.dispose()
  scene.clear()
})

function initScene() {
  // 场景
  scene = new THREE.Scene()
  scene.background = new THREE.Color(0xf0f2f5)

  // 相机
  camera = new THREE.PerspectiveCamera(
    75,
    container.value.clientWidth / container.value.clientHeight,
    0.1,
    1000
  )
  camera.position.set(8, 8, 8)
  camera.lookAt(0, 0, 0)

  // 渲染器
  renderer = new THREE.WebGLRenderer({ antialias: true })
  renderer.setSize(container.value.clientWidth, container.value.clientHeight)
  renderer.shadowMap.enabled = true
  container.value.appendChild(renderer.domElement)

  // 轨道控制器
  controls = new OrbitControls(camera, renderer.domElement)
  controls.enableDamping = true
  controls.dampingFactor = 0.05

  // 启用鼠标中键平移
  controls.mouseButtons = {
    LEFT: THREE.MOUSE.ROTATE,    // 左键旋转
    MIDDLE: THREE.MOUSE.PAN,     // 中键（滚轮）平移
    RIGHT: THREE.MOUSE.PAN       // 右键平移
  }

  // 光源
  const ambientLight = new THREE.AmbientLight(0xffffff, 0.6)
  scene.add(ambientLight)

  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8)
  directionalLight.position.set(10, 10, 5)
  directionalLight.castShadow = true
  scene.add(directionalLight)

  // 地面
  const groundGeometry = new THREE.PlaneGeometry(20, 20)
  const groundMaterial = new THREE.MeshStandardMaterial({ color: 0xcccccc })
  const ground = new THREE.Mesh(groundGeometry, groundMaterial)
  ground.rotation.x = -Math.PI / 2
  ground.receiveShadow = true
  scene.add(ground)

  // 网格辅助线
  const gridHelper = new THREE.GridHelper(20, 20, 0x888888, 0xdddddd)
  scene.add(gridHelper)
}

function createCubes() {
  const positions = [
    { x: -4, y: 1, z: -4 },
    { x: 4, y: 1, z: -4 },
    { x: 0, y: 1, z: 0 },
    { x: -4, y: 1, z: 4 },
    { x: 4, y: 1, z: 4 }
  ]

  cubeData.forEach((data, index) => {
    // 装配机器人（索引2）使用 GLB 模型
    if (index === 2) {
      loadRobotModel(positions[index], data)
    } else {
      const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5)
      const material = new THREE.MeshStandardMaterial({
        color: data.color,
        metalness: 0.3,
        roughness: 0.4
      })
      const cube = new THREE.Mesh(geometry, material)

      cube.position.set(positions[index].x, positions[index].y, positions[index].z)
      cube.castShadow = true
      cube.receiveShadow = true

      // 存储设备数据
      cube.userData = {
        ...data,
        position: positions[index],
        originalColor: data.color
      }

      scene.add(cube)
      cubes.push(cube)
    }
  })
}

function loadRobotModel(position, data) {
  const loader = new GLTFLoader()
  loader.load(
    '/models/robot.glb',
    (gltf) => {
      robotModel = gltf.scene
      robotModel.position.set(position.x, position.y, position.z)

      // 调整模型大小，放大200%（2倍）
      robotModel.scale.set(2, 2, 2)

      // 启用阴影
      robotModel.traverse((child) => {
        if (child.isMesh) {
          child.castShadow = true
          child.receiveShadow = true
        }
      })

      // 存储设备数据
      robotModel.userData = {
        ...data,
        position: position,
        originalColor: data.color,
        isModel: true
      }

      scene.add(robotModel)
      cubes.push(robotModel)
    },
    (progress) => {
      console.log('模型加载进度:', Math.round((progress.loaded / progress.total) * 100) + '%')
    },
    (error) => {
      console.error('模型加载失败:', error)
      // 加载失败时使用立方体作为备用
      const geometry = new THREE.BoxGeometry(1.5, 1.5, 1.5)
      const material = new THREE.MeshStandardMaterial({
        color: data.color,
        metalness: 0.3,
        roughness: 0.4
      })
      const cube = new THREE.Mesh(geometry, material)
      cube.position.set(position.x, position.y, position.z)
      cube.castShadow = true
      cube.receiveShadow = true
      cube.userData = {
        ...data,
        position: position,
        originalColor: data.color
      }
      scene.add(cube)
      cubes.push(cube)
    }
  )
}

function setupInteraction() {
  raycaster = new THREE.Raycaster()
  mouse = new THREE.Vector2()

  window.addEventListener('click', onMouseClick)
}

function onMouseClick(event) {
  const rect = container.value.getBoundingClientRect()
  mouse.x = ((event.clientX - rect.left) / rect.width) * 2 - 1
  mouse.y = -((event.clientY - rect.top) / rect.height) * 2 + 1

  raycaster.setFromCamera(mouse, camera)

  // 递归检测所有子对象（包括 GLB 模型的子网格）
  const intersects = raycaster.intersectObjects(cubes, true)

  // 重置所有立方体颜色
  cubes.forEach(cube => {
    if (cube.material) {
      // 立方体
      cube.material.color.setHex(cube.userData.originalColor)
      cube.material.emissive.setHex(0x000000)
    } else if (cube.userData.isModel) {
      // GLB 模型，重置所有子网格的发光效果
      cube.traverse((child) => {
        if (child.isMesh && child.material) {
          child.material.emissive.setHex(0x000000)
        }
      })
    }
  })

  if (intersects.length > 0) {
    const clickedObject = intersects[0].object

    // 找到顶层对象（立方体或 GLB 模型）
    let targetObject = clickedObject
    while (targetObject.parent && !cubes.includes(targetObject)) {
      targetObject = targetObject.parent
    }

    // 高亮选中的对象
    if (targetObject.material) {
      // 立方体
      targetObject.material.emissive.setHex(0x555555)
    } else if (targetObject.userData.isModel) {
      // GLB 模型，高亮所有子网格
      targetObject.traverse((child) => {
        if (child.isMesh && child.material) {
          child.material.emissive.setHex(0x555555)
        }
      })
    }

    // 显示信息面板
    selectedCube.value = targetObject.userData
  } else {
    selectedCube.value = null
  }
}

function onWindowResize() {
  camera.aspect = container.value.clientWidth / container.value.clientHeight
  camera.updateProjectionMatrix()
  renderer.setSize(container.value.clientWidth, container.value.clientHeight)
}

// 手势控制相关函数
async function toggleGesture() {
  console.log('[Workshop3D] toggleGesture 被调用')
  console.log('[Workshop3D] 当前 gestureEnabled 值:', gestureEnabled.value)

  if (gestureEnabled.value) {
    console.log('[Workshop3D] 准备停止手势控制')
    stopGestureControl()
  } else {
    console.log('[Workshop3D] 准备启动手势控制')
    await startGestureControl()
  }
}

async function loadTensorFlowJS() {
  return new Promise((resolve, reject) => {
    if (window.tf && window.handpose) {
      resolve()
      return
    }

    // 加载 TensorFlow.js
    const tfScript = document.createElement('script')
    tfScript.src = 'https://cdn.jsdelivr.net/npm/@tensorflow/tfjs@3.11.0/dist/tf.min.js'
    tfScript.onload = () => {
      // 加载 HandPose 模型
      const handposeScript = document.createElement('script')
      handposeScript.src = 'https://cdn.jsdelivr.net/npm/@tensorflow-models/handpose@0.0.7/dist/handpose.min.js'
      handposeScript.onload = () => resolve()
      handposeScript.onerror = () => reject(new Error('HandPose 加载失败'))
      document.head.appendChild(handposeScript)
    }
    tfScript.onerror = () => reject(new Error('TensorFlow.js 加载失败'))
    document.head.appendChild(tfScript)
  })
}

async function startGestureControl() {
  try {
    gestureStatus.value = '正在加载 TensorFlow.js...'

    // 加载 TensorFlow.js 和 HandPose 模型
    await loadTensorFlowJS()

    gestureStatus.value = '正在加载手势识别模型...'
    handposeModel = await window.handpose.load()

    gestureStatus.value = '正在启动摄像头...'
    videoStream = await navigator.mediaDevices.getUserMedia({
      video: { width: 640, height: 480, facingMode: 'user' }
    })

    videoElement.value.srcObject = videoStream
    gestureEnabled.value = true

    // 等待视频加载
    await new Promise(resolve => {
      videoElement.value.onloadedmetadata = resolve
    })

    // 设置 canvas 尺寸
    canvasElement.value.width = videoElement.value.videoWidth
    canvasElement.value.height = videoElement.value.videoHeight

    gestureStatus.value = '手势控制已启用'

    // 开始检测手势
    detectHandGesture()
  } catch (error) {
    console.error('手势控制启动失败:', error)
    gestureStatus.value = '启动失败: ' + error.message
    stopGestureControl()
    alert('手势控制启动失败: ' + error.message)
  }
}

function stopGestureControl() {
  if (videoStream) {
    videoStream.getTracks().forEach(track => track.stop())
    videoStream = null
  }

  if (gestureAnimationId) {
    cancelAnimationFrame(gestureAnimationId)
    gestureAnimationId = null
  }

  gestureEnabled.value = false
  previousHandPosition = null
  isPinching = false
  isTwoHandMode = false
  palmOpenStartTime = null
  smoothedDelta = { x: 0, y: 0 }
}

async function detectHandGesture() {
  if (!gestureEnabled.value || !handposeModel) return

  try {
    const predictions = await handposeModel.estimateHands(videoElement.value)

    // 绘制手部关键点
    const ctx = canvasElement.value.getContext('2d')
    ctx.clearRect(0, 0, canvasElement.value.width, canvasElement.value.height)
    ctx.drawImage(videoElement.value, 0, 0, canvasElement.value.width, canvasElement.value.height)

    if (predictions.length > 0) {
      // 绘制所有检测到的手
      predictions.forEach(hand => drawHand(ctx, hand.landmarks))

      // 判断是单手还是双手模式
      if (predictions.length >= 2) {
        // 双手模式 - 平移
        processTwoHandGesture(predictions)
      } else {
        // 单手模式 - 旋转或重置
        processSingleHandGesture(predictions[0].landmarks)
      }
    } else {
      gestureStatus.value = '未检测到手部'
      resetGestureState()
    }
  } catch (error) {
    console.error('手势检测错误:', error)
  }

  gestureAnimationId = requestAnimationFrame(detectHandGesture)
}

function resetGestureState() {
  previousHandPosition = null
  isPinching = false
  isTwoHandMode = false
  palmOpenStartTime = null
  smoothedDelta = { x: 0, y: 0 }
}

function isPinchGesture(landmarks) {
  const thumbTip = landmarks[4]
  const indexTip = landmarks[8]

  const distance = Math.sqrt(
    Math.pow(thumbTip[0] - indexTip[0], 2) +
    Math.pow(thumbTip[1] - indexTip[1], 2)
  )

  return distance < PINCH_THRESHOLD
}

function isPalmOpen(landmarks) {
  // 检测手掌是否张开：所有手指尖端距离手掌中心的距离都较大
  const palmCenter = landmarks[0]
  const fingerTips = [landmarks[4], landmarks[8], landmarks[12], landmarks[16], landmarks[20]]

  let totalDistance = 0
  fingerTips.forEach(tip => {
    const distance = Math.sqrt(
      Math.pow(tip[0] - palmCenter[0], 2) +
      Math.pow(tip[1] - palmCenter[1], 2)
    )
    totalDistance += distance
  })

  const avgDistance = totalDistance / fingerTips.length
  return avgDistance > PALM_OPEN_THRESHOLD // 使用更高的阈值减少误识别
}

function processSingleHandGesture(landmarks) {
  const pinching = isPinchGesture(landmarks)
  const palmOpen = isPalmOpen(landmarks)

  if (pinching) {
    // 捏合状态 - 旋转模式
    const thumbTip = landmarks[4]
    const indexTip = landmarks[8]
    const pinchCenter = [(thumbTip[0] + indexTip[0]) / 2, (thumbTip[1] + indexTip[1]) / 2]

    // 更新虚拟光标位置（映射到屏幕坐标）
    const screenX = (pinchCenter[0] / canvasElement.value.width) * window.innerWidth
    const screenY = (pinchCenter[1] / canvasElement.value.height) * window.innerHeight
    cursorPosition.value = { x: screenX, y: screenY }
    cursorVisible.value = true

    if (isPinching && previousHandPosition) {
      // 计算移动量
      let deltaX = (pinchCenter[0] - previousHandPosition[0]) / canvasElement.value.width
      let deltaY = (pinchCenter[1] - previousHandPosition[1]) / canvasElement.value.height

      // 平滑处理
      smoothedDelta.x = smoothedDelta.x * (1 - SMOOTH_FACTOR) + deltaX * SMOOTH_FACTOR
      smoothedDelta.y = smoothedDelta.y * (1 - SMOOTH_FACTOR) + deltaY * SMOOTH_FACTOR

      // 死区处理
      if (Math.abs(smoothedDelta.x) < DEAD_ZONE) smoothedDelta.x = 0
      if (Math.abs(smoothedDelta.y) < DEAD_ZONE) smoothedDelta.y = 0

      // 应用旋转
      applyRotation(smoothedDelta.x, smoothedDelta.y)

      gestureStatus.value = '捏合旋转'
    }

    previousHandPosition = pinchCenter
    isPinching = true
    palmOpenStartTime = null
  } else if (palmOpen) {
    // 手掌张开 - 检测是否需要重置
    cursorVisible.value = false

    if (palmOpenStartTime === null) {
      palmOpenStartTime = Date.now()
    } else {
      const duration = Date.now() - palmOpenStartTime
      if (duration >= PALM_RESET_DURATION) {
        // 重置视角
        resetCameraView()
        gestureStatus.value = '视角已重置'
        palmOpenStartTime = null
      } else {
        gestureStatus.value = `保持手掌张开 ${((PALM_RESET_DURATION - duration) / 1000).toFixed(1)}s 重置视角`
      }
    }
    isPinching = false
    previousHandPosition = null
  } else {
    // 既不是捏合也不是张开手掌
    cursorVisible.value = false
    resetGestureState()
    gestureStatus.value = '等待手势...'
  }
}

function processTwoHandGesture(predictions) {
  const hand1 = predictions[0].landmarks
  const hand2 = predictions[1].landmarks

  const pinching1 = isPinchGesture(hand1)
  const pinching2 = isPinchGesture(hand2)

  if (pinching1 && pinching2) {
    // 双手都在捏合 - 平移模式
    const thumbTip1 = hand1[4]
    const indexTip1 = hand1[8]
    const thumbTip2 = hand2[4]
    const indexTip2 = hand2[8]

    const center1 = [(thumbTip1[0] + indexTip1[0]) / 2, (thumbTip1[1] + indexTip1[1]) / 2]
    const center2 = [(thumbTip2[0] + indexTip2[0]) / 2, (thumbTip2[1] + indexTip2[1]) / 2]
    const twoHandCenter = [(center1[0] + center2[0]) / 2, (center1[1] + center2[1]) / 2]

    // 更新虚拟光标位置（双手模式）
    const screenX = (twoHandCenter[0] / canvasElement.value.width) * window.innerWidth
    const screenY = (twoHandCenter[1] / canvasElement.value.height) * window.innerHeight
    cursorPosition.value = { x: screenX, y: screenY }
    cursorVisible.value = true

    if (isTwoHandMode && previousHandPosition) {
      // 计算移动量
      let deltaX = (twoHandCenter[0] - previousHandPosition[0]) / canvasElement.value.width
      let deltaY = (twoHandCenter[1] - previousHandPosition[1]) / canvasElement.value.height

      // 平滑处理
      smoothedDelta.x = smoothedDelta.x * (1 - SMOOTH_FACTOR) + deltaX * SMOOTH_FACTOR
      smoothedDelta.y = smoothedDelta.y * (1 - SMOOTH_FACTOR) + deltaY * SMOOTH_FACTOR

      // 死区处理
      if (Math.abs(smoothedDelta.x) < DEAD_ZONE) smoothedDelta.x = 0
      if (Math.abs(smoothedDelta.y) < DEAD_ZONE) smoothedDelta.y = 0

      // 应用平移
      applyPanning(smoothedDelta.x, smoothedDelta.y)

      gestureStatus.value = '双手平移'
    }

    previousHandPosition = twoHandCenter
    isTwoHandMode = true
    isPinching = false
  } else {
    // 不是双手捏合，回到单手模式
    isTwoHandMode = false
    processSingleHandGesture(predictions[0].landmarks)
  }
}

function applyRotation(deltaX, deltaY) {
  // 水平和垂直旋转角度
  const horizontalAngle = -deltaX * Math.PI * ROTATE_SPEED
  const verticalAngle = -deltaY * Math.PI * ROTATE_SPEED

  // 获取相机到目标的向量
  const offset = camera.position.clone().sub(controls.target)
  const distance = offset.length()

  // 转换为球坐标
  let theta = Math.atan2(offset.x, offset.z)
  let phi = Math.acos(Math.max(-1, Math.min(1, offset.y / distance)))

  // 应用旋转
  theta += horizontalAngle
  phi += verticalAngle

  // 限制垂直角度
  phi = Math.max(0.1, Math.min(Math.PI - 0.1, phi))

  // 转换回笛卡尔坐标
  camera.position.x = controls.target.x + distance * Math.sin(phi) * Math.sin(theta)
  camera.position.y = controls.target.y + distance * Math.cos(phi)
  camera.position.z = controls.target.z + distance * Math.sin(phi) * Math.cos(theta)

  camera.lookAt(controls.target)
}

function applyPanning(deltaX, deltaY) {
  // 根据相机方向计算平移向量
  const right = new THREE.Vector3()
  const up = new THREE.Vector3()
  right.setFromMatrixColumn(camera.matrix, 0)
  up.setFromMatrixColumn(camera.matrix, 1)

  // 应用平移
  controls.target.add(right.multiplyScalar(-deltaX * PAN_SPEED))
  controls.target.add(up.multiplyScalar(deltaY * PAN_SPEED))
}

function resetCameraView() {
  // 重置相机到初始位置
  camera.position.set(8, 8, 8)
  controls.target.set(0, 0, 0)
  camera.lookAt(controls.target)
}

function drawHand(ctx, landmarks) {
  // 绘制手部关键点
  ctx.fillStyle = '#00ff00'
  landmarks.forEach(point => {
    ctx.beginPath()
    ctx.arc(point[0], point[1], 5, 0, 2 * Math.PI)
    ctx.fill()
  })

  // 绘制连接线
  const connections = [
    [0, 1], [1, 2], [2, 3], [3, 4], // 拇指
    [0, 5], [5, 6], [6, 7], [7, 8], // 食指
    [0, 9], [9, 10], [10, 11], [11, 12], // 中指
    [0, 13], [13, 14], [14, 15], [15, 16], // 无名指
    [0, 17], [17, 18], [18, 19], [19, 20], // 小指
    [5, 9], [9, 13], [13, 17] // 手掌
  ]

  ctx.strokeStyle = '#00ff00'
  ctx.lineWidth = 2
  connections.forEach(([start, end]) => {
    ctx.beginPath()
    ctx.moveTo(landmarks[start][0], landmarks[start][1])
    ctx.lineTo(landmarks[end][0], landmarks[end][1])
    ctx.stroke()
  })
}

function animate() {
  animationId = requestAnimationFrame(animate)

  // 只旋转立方体，不旋转 GLB 模型
  cubes.forEach((cube, index) => {
    if (!cube.userData.isModel) {
      cube.rotation.y += 0.005 * (index % 2 === 0 ? 1 : -1)
    }
  })

  controls.update()
  renderer.render(scene, camera)
}
</script>

<style scoped>
.workshop-container {
  position: relative;
  width: 100%;
  height: 100vh;
  overflow: hidden;
}

.three-container {
  width: 100%;
  height: 100%;
}

.info-panel {
  position: absolute;
  top: 20px;
  right: 20px;
  width: 320px;
  background: rgba(255, 255, 255, 0.95);
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  overflow: hidden;
}

.info-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding: 16px 20px;
  background: #1890ff;
  color: white;
}

.info-header h3 {
  margin: 0;
  font-size: 18px;
  font-weight: 500;
}

.close-btn {
  background: none;
  border: none;
  color: white;
  font-size: 28px;
  cursor: pointer;
  padding: 0;
  width: 28px;
  height: 28px;
  line-height: 1;
  transition: opacity 0.2s;
}

.close-btn:hover {
  opacity: 0.8;
}

.info-content {
  padding: 20px;
}

.info-content p {
  margin: 12px 0;
  font-size: 14px;
  line-height: 1.6;
}

.info-content strong {
  color: #333;
  font-weight: 500;
}

.gesture-control-wrapper {
  position: absolute;
  top: 20px;
  left: 20px;
  z-index: 1000;
}

.gesture-info {
  display: flex;
  align-items: center;
  gap: 10px;
  background: rgba(0, 0, 0, 0.8);
  color: white;
  padding: 10px 16px;
  border-radius: 8px;
  font-size: 14px;
}

.camera-preview {
  position: absolute;
  bottom: 20px;
  left: 20px;
  width: 320px;
  background: rgba(0, 0, 0, 0.8);
  border-radius: 8px;
  overflow: hidden;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  z-index: 999;
}

.camera-preview.hidden {
  display: none;
}

.camera-preview video {
  width: 100%;
  height: auto;
  display: block;
}

.camera-preview canvas {
  position: absolute;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
}

.toggle-btn {
  background: #f5222d;
  color: white;
  border: none;
  padding: 6px 12px;
  border-radius: 4px;
  cursor: pointer;
  font-size: 12px;
  transition: background 0.2s;
}

.toggle-btn:hover {
  background: #cf1322;
}

.gesture-toggle-btn {
  background: #1890ff;
  color: white;
  border: none;
  padding: 12px 24px;
  border-radius: 8px;
  cursor: pointer;
  font-size: 16px;
  font-weight: bold;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
  transition: all 0.2s;
}

.gesture-toggle-btn:hover {
  background: #096dd9;
  transform: scale(1.05);
}

.virtual-cursor {
  position: fixed;
  pointer-events: none;
  z-index: 9999;
  transform: translate(-50%, -50%);
}

.cursor-dot {
  width: 12px;
  height: 12px;
  background: #1890ff;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  box-shadow: 0 0 8px rgba(24, 144, 255, 0.6);
}

.cursor-ring {
  width: 32px;
  height: 32px;
  border: 2px solid #1890ff;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  animation: pulse 1.5s ease-in-out infinite;
  opacity: 0.6;
}

@keyframes pulse {
  0%, 100% {
    transform: translate(-50%, -50%) scale(1);
    opacity: 0.6;
  }
  50% {
    transform: translate(-50%, -50%) scale(1.2);
    opacity: 0.3;
  }
}
</style>
