<!doctype html>
<html lang="zh-CN">

<head>
  <meta charset="utf-8" />
  <meta name="viewport" content="width=device-width,initial-scale=1" />
  <title>NaviGlass 导盲系统可视化</title>
  <link rel="icon" type="image/png" href="/static/favicon.png">
  <link rel="preconnect" href="https://fonts.googleapis.com">
  <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
  <style>
    :root {
      --bg: #0b0f14;
      --card: #121821;
      --text: #e6edf3;
      --muted: #9fb0c3;
      --ok: #7ee787;
      --err: #ff8080;
      --warn: #fbbf24;
      --line: #1f2937;
      --panel: rgba(18, 24, 33, .75);
      --primary: #3b82f6;
      --primary-glow: rgba(59, 130, 246, 0.4);
    }

    * {
      box-sizing: border-box
    }

    body {
      margin: 0;
      background: linear-gradient(180deg, #0b0f14, #0b0f14 60%, #0e1621);
      color: var(--text);
      font: 16px/1.6 system-ui, -apple-system, Segoe UI, Roboto, "Noto Sans", "PingFang SC", "Hiragino Sans GB", "Microsoft YaHei", Arial;
    }

    /* 两栏布局 - Day 20 修复：防止滚动条 */
    html,
    body {
      height: 100%;
      overflow: hidden;
    }

    .app {
      display: grid;
      grid-template-columns: 1fr 700px;
      gap: 16px;
      height: 100vh;
      padding: 16px;
      box-sizing: border-box;
      overflow: hidden;
    }

    .stage {
      position: relative;
      border: 1px solid var(--line);
      border-radius: 14px;
      background: var(--card);
      /* Day 20 修复: 统一使用 --card 背景色 */
      box-shadow: 0 10px 30px rgba(0, 0, 0, .25);
      overflow: hidden;
    }

    .canvas-wrap,
    #canvas {
      position: absolute;
      inset: 1px;
      /* Day 20 修复: 留出边框空间 */
      width: calc(100% - 2px);
      height: calc(100% - 2px);
      display: block;
      background: #0a1017;
      /* 视频区域保持深色 */
      border-radius: 13px;
      /* 比外框小 1px */
      z-index: 10
    }

    /* 右上角：参数 + 日志 */
    .tri-panels {
      position: absolute;
      right: 12px;
      top: 12px;
      z-index: 30;
      display: grid;
      grid-template-columns: 1fr 1fr;
      gap: 12px;
      max-width: min(720px, 60vw);
    }

    .box {
      border: 2px dashed rgba(255, 255, 255, .35);
      border-radius: 12px;
      padding: 10px;
      background: var(--panel);
      backdrop-filter: blur(8px) saturate(130%);
      box-shadow: inset 0 0 0 1px rgba(255, 255, 255, .06);
    }

    .box h4 {
      margin: 0 0 6px 0;
      font-size: 12px;
      color: #ffd769
    }

    .kv {
      display: flex;
      justify-content: space-between;
      align-items: center;
      border-top: 1px dashed rgba(255, 255, 255, .15);
      padding-top: 6px;
      margin-top: 6px;
      color: var(--muted);
      font-size: 12px
    }

    /* 左上角 IMU 浮窗 */
    .imu-float {
      position: absolute;
      left: 12px;
      top: 12px;
      z-index: 35;
      width: 600px;
      /* 恢复合理尺寸 */
      border: 1px solid rgba(255, 255, 255, 0.15);
      border-radius: 12px;
      background: rgba(18, 24, 33, 0.9);
      backdrop-filter: blur(12px) saturate(140%);
      box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
      padding: 12px;
      transition: all 0.3s ease;
    }

    .imu-float:hover {
      box-shadow: 0 12px 40px rgba(0, 0, 0, 0.5);
    }

    /* Day 20: 优化折叠状态 - 只显示标题和按钮 */
    .imu-float.collapsed {
      width: 180px;
      height: 40px;
      padding: 8px 12px;
      overflow: hidden;
    }

    .imu-float.collapsed .imu-header {
      margin: 0;
      font-size: 12px;
    }

    .imu-float.collapsed .imu-row,
    .imu-float.collapsed #imu_top_status {
      display: none !important;
    }

    .imu-toggle {
      position: absolute;
      top: 4px;
      right: 8px;
      z-index: 10;
      width: 32px;
      height: 32px;
      border-radius: 8px;
      background: rgba(255, 255, 255, .1);
      border: none;
      cursor: pointer;
      color: var(--muted);
      font-size: 16px;
      display: flex;
      align-items: center;
      justify-content: center;
      transition: all 0.2s ease;
    }

    .imu-toggle:hover {
      background: rgba(255, 255, 255, .2);
      color: var(--text);
    }

    .imu-row {
      display: grid;
      grid-template-columns: 1.5fr 1fr;
      /* 左侧 3D 模型稍大 */
      gap: 12px;
      align-items: stretch;
      min-height: 200px;
    }

    #imu_view {
      position: relative;
      min-height: 200px;
      background: #0a1017;
      border-radius: 10px;
    }

    #imu_hud {
      border-radius: 10px;
      background: rgba(18, 24, 33, 0.8);
      border: none;
      padding: 8px;
      overflow: visible;
      /* Day 20: 允许内容自然显示 */
      min-width: 150px;
      /* Day 20: 缩小最小宽度 */
      max-height: 300px;
    }

    #imu_hud::-webkit-scrollbar {
      width: 8px;
      /* 滚动条的宽度 */
    }

    #imu_hud::-webkit-scrollbar-thumb {
      background-color: #2a6df4;
      /* 滑块的颜色 */
      border-radius: 4px;
      /* 滑块的圆角 */
    }

    #imu_hud::-webkit-scrollbar-track {
      background-color: #111a2e;
      /* 滚动条轨道的颜色 */
      border-radius: 4px;
      /* 滚动条轨道的圆角 */
    }

    #imu_top_status {
      position: absolute;
      top: 10px;
      right: 10px;
      z-index: 2
    }

    #imu_top_status .badge {
      background: rgba(0, 0, 0, .35);
      border-color: #2d3b50
    }

    /* 角标 + 当前指令（角标在浮窗下） */
    .badge-tag {
      position: absolute;
      left: 12px;
      top: calc(12px + 300px + 28px);
      z-index: 20;
      background: linear-gradient(120deg, rgba(47, 134, 255, .9), rgba(26, 88, 255, .9));
      color: #fff;
      padding: 6px 10px;
      font-weight: 700;
      font-size: 12px;
      border-radius: 999px;
      box-shadow: 0 6px 16px rgba(47, 134, 255, .35)
    }

    .command {
      position: absolute;
      left: 50%;
      bottom: 46px;
      transform: translateX(-50%);
      z-index: 25;
      display: flex;
      gap: 10px;
      align-items: center;
      padding: 12px 18px;
      border-radius: 999px;
      background: rgba(18, 24, 33, .75);
      border: 1px solid rgba(255, 255, 255, .14);
      backdrop-filter: blur(8px) saturate(140%);
      box-shadow: 0 12px 28px rgba(0, 0, 0, .6)
    }

    .dot {
      width: 9px;
      height: 9px;
      border-radius: 50%;
      background: #2f86ff;
      box-shadow: 0 0 16px #2f86ff
    }

    /* 右侧聊天（左右气泡）- Day 20 修复：移除 height:100vh */
    .chat {
      display: flex;
      flex-direction: column;
      border: 1px solid var(--line);
      border-radius: 14px;
      background: var(--card);
      box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
      overflow: hidden;
      /* 不再使用 height:100vh，让 grid 自动分配高度 */
    }

    .chat-head {
      display: flex;
      align-items: center;
      justify-content: space-between;
      padding: 12px 14px;
      border-bottom: 1px solid var(--line)
    }

    .badges {
      display: flex;
      gap: 8px;
      flex-wrap: wrap
    }

    /* Day 20: 美化状态 badge */
    .badge {
      font-size: 11px;
      font-weight: 500;
      padding: 5px 10px;
      border-radius: 8px;
      border: 1px solid rgba(255, 255, 255, .1);
      color: var(--muted);
      background: rgba(255, 255, 255, .05);
      display: flex;
      align-items: center;
      gap: 6px;
      transition: all 0.2s ease;
    }

    .badge::before {
      content: '';
      width: 6px;
      height: 6px;
      border-radius: 50%;
      background: currentColor;
      flex-shrink: 0;
    }

    .badge.ok {
      color: var(--ok);
      border-color: rgba(126, 231, 135, .3);
      background: rgba(126, 231, 135, .1);
    }

    .badge.ok::before {
      animation: pulse 2s infinite;
    }

    .badge.err {
      color: var(--err);
      border-color: rgba(255, 128, 128, .3);
      background: rgba(255, 128, 128, .1);
    }

    .badge.connecting {
      color: var(--warn);
      border-color: rgba(251, 191, 36, .3);
      background: rgba(251, 191, 36, .1);
    }

    .badge.connecting::before {
      animation: blink 1s infinite;
    }

    @keyframes pulse {

      0%,
      100% {
        opacity: 1;
        transform: scale(1);
      }

      50% {
        opacity: .6;
        transform: scale(1.2);
      }
    }

    @keyframes blink {

      0%,
      100% {
        opacity: 1;
      }

      50% {
        opacity: .2;
      }
    }

    .chat-list {
      flex: 1;
      overflow: auto;
      padding: 12px;
      display: flex;
      flex-direction: column;
      gap: 10px;
      min-height: 0;
      /* 确保 .chat-list 不会被外部内容撑大 */
    }

    .live {
      padding: 12px;
      border: 1px dashed #2d3b50;
      border-radius: 12px;
      background: #0c121a
    }

    .live h2 {
      margin: 0 0 8px 0;
      font-size: 14px;
      color: var(--muted);
      font-weight: 600
    }

    .partial {
      font-size: 20px;
      min-height: 2.2em;
      letter-spacing: .2px
    }

    .finals {
      padding: 12px;
      border: 1px solid #1f2937;
      border-radius: 12px;
      background: #0c121a;
      display: flex;
      flex-direction: column;
      flex: 1;
      /* 使用 flex: 1 而不是 flex-grow: 1 */
      min-height: 0;
      /* 允许收缩到小于内容高度 */
    }

    .finals h2 {
      margin: 0 0 8px 0;
      font-size: 14px;
      color: var(--muted);
      font-weight: 600
    }

    .finals ul {
      list-style: none;
      margin: 0;
      padding: 0;
      display: flex;
      flex-direction: column;
      gap: 10px
    }

    .bubble {
      max-width: 82%;
      padding: 10px 12px;
      border-radius: 14px;
      border: 1px solid #1f2937
    }

    .from-bot {
      align-self: flex-start;
      background: #0d1729
    }

    .from-me {
      align-self: flex-end;
      background: #12263a
    }

    .controls {
      display: flex;
      gap: 8px
    }

    button {
      background: linear-gradient(135deg, #2563eb, #1d4ed8);
      border: none;
      color: #fff;
      border-radius: 10px;
      padding: 8px 14px;
      font-weight: 600;
      cursor: pointer;
      transition: all 0.2s ease;
      box-shadow: 0 2px 8px rgba(37, 99, 235, .3);
    }

    button:hover {
      transform: translateY(-1px);
      box-shadow: 0 4px 12px rgba(37, 99, 235, .4);
    }

    button:active {
      transform: translateY(0);
    }

    .ghost {
      background: transparent;
      border: 1px solid #304057;
      color: #9fb0c3;
      box-shadow: none;
    }

    .ghost:hover {
      background: rgba(255, 255, 255, .05);
      border-color: #405570;
    }

    /* 隐藏但保留：校准/滑杆（供 main.js 使用） */
    .hidden-controls {
      display: none
    }

    /* Day 20: 改善移动端适配 */
    @media (max-width:1100px) {
      .app {
        grid-template-columns: 1fr;
        height: auto;
        padding: 8px;
        overflow: visible;
      }

      /* 关键修复：确保视频区域有足够高度 */
      .stage {
        min-height: 50vh;
        height: 50vh;
        position: relative;
      }

      .chat {
        height: 50vh;
        min-height: 400px;
      }

      /* 移动端 IMU 浮窗缩小并移到右下角 */
      .imu-float {
        width: 160px;
        left: auto;
        right: 8px;
        top: auto;
        bottom: 8px;
        padding: 8px;
      }

      /* 移动端默认折叠状态 */
      .imu-float:not(.expanded) {
        width: 160px;
        height: 40px;
        overflow: hidden;
      }

      .imu-float:not(.expanded) .imu-row,
      .imu-float:not(.expanded) #imu_top_status {
        display: none !important;
      }

      .imu-row {
        grid-template-columns: 1fr;
      }

      #imu_view {
        min-height: 200px;
      }
    }

    @media (max-width:600px) {
      .badges {
        gap: 4px;
      }

      .badge {
        font-size: 10px;
        padding: 4px 8px;
      }

      .controls {
        flex-wrap: wrap;
      }

      button {
        padding: 6px 10px;
        font-size: 12px;
      }

      /* 手机端视频区域更大 */
      .stage {
        min-height: 45vh;
        height: 45vh;
      }

      .chat {
        height: 45vh;
        min-height: 300px;
      }

      /* 手机端 IMU 浮窗始终小巧 */
      .imu-float {
        width: 140px;
        right: 6px;
        bottom: 6px;
        left: auto;
        top: auto;
        padding: 6px;
      }

      .imu-float.collapsed, 
      .imu-float:not(.expanded) {
        width: 140px;
        height: 36px;
      }
    }
  </style>
</head>

<body>

  <div class="app">
    <!-- 左侧主舞台 -->
    <section class="stage">
      <div class="canvas-wrap"><canvas id="canvas"></canvas></div>

      <!-- 左上角：IMU 浮窗（横向）Day 20 优化 -->
      <div class="imu-float" id="imuFloat">
        <button class="imu-toggle" id="imuToggle" title="折叠/展开">−</button>
        <div class="imu-header" style="font-size:12px;color:#61dafb;margin-bottom:8px;font-weight:600;">📊 IMU 姿态可视化
        </div>
        <div class="imu-row">
          <div id="imu_view"></div>
          <div id="imu_hud"><!-- JS 会把“IMU 实时数据面板”插到这里 --></div>
        </div>
        <div id="imu_top_status" style="display: none;">
          <span class="badge">UDP: <code>12345</code></span>
          <span class="badge">Browser WS: <code>/ws</code></span>
          <span class="badge" id="imu_ws_state">connecting…</span>
        </div>
      </div>



    </section>

    <!-- 右侧聊天 -->
    <aside class="chat">
      <div class="chat-head">
        <div class="badges">
          <span id="camStatus" class="badge">Camera: connecting…</span>
          <span id="asrStatus" class="badge">ASR: connecting…</span>
          <span id="fps" class="badge">FPS: --</span>
        </div>
        <div class="controls">
          <button class="ghost" id="btnClear">清空 Final</button>
          <button id="btnReconnect">重连</button>
        </div>
      </div>
      <div class="chat-list">
        <div class="live">
          <h2>流式识别（Partial）</h2>
          <div class="partial" id="partial">（等待音频…）</div>
        </div>
        <div class="finals">
          <h2>最终文本（Final）</h2>
          <ul id="finalList"></ul>
        </div>
      </div>
    </aside>
  </div>

  <!-- 隐藏但保留：校准/滑杆（供 main.js 使用） -->
  <div class="hidden-controls">
    <button id="btn_zero"></button><button id="btn_reset"></button><button id="btn_bias_now"></button>
    <input id="auto_rezero" type="checkbox" checked /><input id="auto_bias" type="checkbox" checked />
    <input id="use_proj" type="checkbox" checked /><input id="freeze_still" type="checkbox" checked />
    <select id="medn">
      <option>3</option>
      <option selected>5</option>
      <option>7</option>
    </select>
    <select id="ang_ema">
      <option value="0">0</option>
      <option value="0.15" selected>0.15</option>
      <option value="0.30">0.30</option>
      <option value="0.5">0.5</option>
    </select>
    <select id="grav_beta">
      <option value="0.95">0.95</option>
      <option value="0.97">0.97</option>
      <option value="0.98" selected>0.98</option>
      <option value="0.99">0.99</option>
    </select>
    <select id="yaw_db">
      <option value="0.05">0.05</option>
      <option value="0.08" selected>0.08</option>
      <option value="0.15">0.15</option>
      <option value="0.30">0.30</option>
    </select>
    <select id="still_w">
      <option value="0.4" selected>0.4</option>
      <option value="0.6">0.6</option>
      <option value="1.0">1.0</option>
    </select>
    <select id="yaw_leak">
      <option value="0">0</option>
      <option value="0.1">0.1</option>
      <option value="0.2" selected>0.2</option>
      <option value="0.5">0.5</option>
    </select>
    <input id="roll_sl" type="range"><span id="roll_val"></span>
    <input id="pitch_sl" type="range"><span id="pitch_val"></span>
    <input id="yaw_sl" type="range"><span id="yaw_val"></span>
    <input id="gx_sl" type="range"><span id="gx_val"></span>
    <input id="gy_sl" type="range"><span id="gy_val"></span>
    <input id="gz_sl" type="range"><span id="gz_val"></span>
    <input id="ax_sl" type="range"><span id="ax_val"></span>
    <input id="ay_sl" type="range"><span id="ay_val"></span>
    <input id="az_sl" type="range"><span id="az_val"></span>
  </div>

  <!-- three.js importmap -->
  <script type="importmap">{
  "imports": { "three": "https://unpkg.com/three@0.155.0/build/three.module.js" }
}</script>

  <!-- 主脚本 -->
  <script type="module" src="/static/main.js"></script>
</body>

</html>