分享图
动画工坊
引擎就绪
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>视觉-意图预测引擎 · IFR原理动画</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=IBM+Plex+Mono:wght@300;400;500&display=swap" rel="stylesheet">
<style>
  :root {
    --bg: #060b18;
    --primary: #00ffc8;
    --primary-dim: #00ffc840;
    --accent: #ffaa00;
    --alert: #ff4757;
    --success: #00ff88;
    --text: #a8b8d0;
    --text-bright: #e8f0ff;
    --surface: #0c1424;
    --border: #162040;
  }

  *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }

  body {
    background: var(--bg);
    color: var(--text);
    font-family: 'IBM Plex Mono', monospace;
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    align-items: center;
    overflow-x: hidden;
  }

  /* 扫描线效果 */
  body::after {
    content: '';
    position: fixed;
    inset: 0;
    pointer-events: none;
    background: repeating-linear-gradient(
      0deg,
      transparent,
      transparent 2px,
      rgba(0, 255, 200, 0.015) 2px,
      rgba(0, 255, 200, 0.015) 4px
    );
    z-index: 9999;
  }

  .scene-wrapper {
    width: 100%;
    max-width: 1400px;
    flex: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 20px;
    position: relative;
  }

  #scene {
    width: 100%;
    height: auto;
    max-height: 80vh;
    border: 1px solid var(--border);
    border-radius: 12px;
    background: radial-gradient(ellipse at 30% 40%, #0a1628 0%, var(--bg) 70%);
  }

  /* 控制面板 */
  .controls {
    width: 100%;
    max-width: 1400px;
    padding: 16px 24px 24px;
    display: flex;
    gap: 32px;
    justify-content: center;
    flex-wrap: wrap;
    background: linear-gradient(180deg, transparent, rgba(6,11,24,0.95) 30%);
  }

  .control-group {
    display: flex;
    flex-direction: column;
    gap: 6px;
    min-width: 260px;
  }

  .control-label {
    font-family: 'Orbitron', sans-serif;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: 1px;
    color: var(--primary);
    text-transform: uppercase;
    display: flex;
    justify-content: space-between;
    align-items: baseline;
  }

  .control-value {
    font-family: 'IBM Plex Mono', monospace;
    font-weight: 400;
    color: var(--accent);
    font-size: 12px;
  }

  input[type="range"] {
    -webkit-appearance: none;
    appearance: none;
    width: 100%;
    height: 6px;
    background: var(--border);
    border-radius: 3px;
    outline: none;
    cursor: pointer;
  }

  input[type="range"]::-webkit-slider-thumb {
    -webkit-appearance: none;
    appearance: none;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--primary);
    border: 2px solid var(--bg);
    box-shadow: 0 0 10px var(--primary-dim);
    cursor: pointer;
  }

  input[type="range"]::-moz-range-thumb {
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: var(--primary);
    border: 2px solid var(--bg);
    box-shadow: 0 0 10px var(--primary-dim);
    cursor: pointer;
  }

  /* 标题栏 */
  .title-bar {
    width: 100%;
    max-width: 1400px;
    padding: 20px 24px 8px;
    display: flex;
    align-items: baseline;
    gap: 16px;
  }

  .title-bar h1 {
    font-family: 'Orbitron', sans-serif;
    font-size: 18px;
    font-weight: 900;
    color: var(--primary);
    letter-spacing: 2px;
  }

  .title-bar .subtitle {
    font-size: 12px;
    color: var(--text);
    opacity: 0.6;
  }

  /* 相位指示器 */
  .phase-strip {
    width: 100%;
    max-width: 1400px;
    padding: 0 24px 12px;
    display: flex;
    gap: 4px;
    overflow-x: auto;
  }

  .phase-chip {
    font-family: 'Orbitron', sans-serif;
    font-size: 9px;
    font-weight: 700;
    letter-spacing: 0.5px;
    padding: 4px 10px;
    border-radius: 4px;
    background: var(--surface);
    border: 1px solid var(--border);
    color: var(--text);
    white-space: nowrap;
    transition: all 0.4s ease;
  }

  .phase-chip.active {
    background: rgba(0, 255, 200, 0.12);
    border-color: var(--primary);
    color: var(--primary);
    box-shadow: 0 0 12px rgba(0, 255, 200, 0.15);
  }

  .phase-chip.done {
    background: rgba(0, 255, 136, 0.08);
    border-color: rgba(0, 255, 136, 0.3);
    color: var(--success);
  }

  @media (prefers-reduced-motion: reduce) {
    * { animation-duration: 0s !important; transition-duration: 0s !important; }
  }
</style>
</head>
<body>

<div class="title-bar">
  <h1>VISUAL-INTENT ENGINE</h1>
  <span class="subtitle">视觉-意图预测引擎 · IFR 原理动画</span>
</div>

<div class="phase-strip" id="phaseStrip"></div>

<div class="scene-wrapper">
  <svg id="scene" viewBox="0 0 1400 750" xmlns="http://www.w3.org/2000/svg">
    <defs>
      <!-- 发光滤镜 -->
      <filter id="glowPrimary" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="6" result="blur"/>
        <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
      </filter>
      <filter id="glowAccent" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="8" result="blur"/>
        <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
      </filter>
      <filter id="glowStrong" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="15" result="blur"/>
        <feMerge><feMergeNode in="blur"/><feMergeNode in="SourceGraphic"/></feMerge>
      </filter>
      <filter id="softBlur" x="-50%" y="-50%" width="200%" height="200%">
        <feGaussianBlur stdDeviation="3"/>
      </filter>

      <!-- 虹膜渐变 -->
      <radialGradient id="irisGrad" cx="50%" cy="50%" r="50%">
        <stop offset="0%" stop-color="#0a3d2d"/>
        <stop offset="40%" stop-color="#0d6b4f"/>
        <stop offset="70%" stop-color="#00ffc8"/>
        <stop offset="100%" stop-color="#008866"/>
      </radialGradient>

      <!-- 置信度渐变 -->
      <linearGradient id="confGrad" x1="0%" y1="0%" x2="100%" y2="0%">
        <stop offset="0%" stop-color="#ff4757"/>
        <stop offset="50%" stop-color="#ffaa00"/>
        <stop offset="100%" stop-color="#00ff88"/>
      </linearGradient>

      <!-- 灯光渐变 -->
      <radialGradient id="lampGlow" cx="50%" cy="0%" r="80%">
        <stop offset="0%" stop-color="#ffdd44" stop-opacity="0.8"/>
        <stop offset="40%" stop-color="#ffaa00" stop-opacity="0.3"/>
        <stop offset="100%" stop-color="#ffaa00" stop-opacity="0"/>
      </radialGradient>

      <!-- 注视光束渐变 -->
      <linearGradient id="beamGrad" x1="0%" y1="0%" x2="100%" y2="0%">
        <stop offset="0%" stop-color="#00ffc8" stop-opacity="0.8"/>
        <stop offset="50%" stop-color="#00ffc8" stop-opacity="0.4"/>
        <stop offset="100%" stop-color="#00ffc8" stop-opacity="0.1"/>
      </linearGradient>

      <!-- 网格图案 -->
      <pattern id="grid" width="40" height="40" patternUnits="userSpaceOnUse">
        <path d="M 40 0 L 0 0 0 40" fill="none" stroke="#0f1d35" stroke-width="0.5"/>
      </pattern>

      <!-- 扫描线图案 -->
      <pattern id="scanlines" width="4" height="4" patternUnits="userSpaceOnUse">
        <line x1="0" y1="0" x2="4" y2="0" stroke="rgba(0,255,200,0.02)" stroke-width="1"/>
      </pattern>
    </defs>

    <!-- 背景网格 -->
    <rect width="1400" height="750" fill="url(#grid)" opacity="0.7"/>
    <rect width="1400" height="750" fill="url(#scanlines)"/>

    <!-- ======================== 左侧:眼睛 ======================== -->
    <g id="eyeGroup" transform="translate(280, 320)">
      <!-- 眼眶外框装饰 -->
      <ellipse cx="0" cy="0" rx="110" ry="80" fill="none" stroke="#0f2040" stroke-width="1" stroke-dasharray="4 6"/>
      <ellipse cx="0" cy="0" rx="125" ry="92" fill="none" stroke="#0a1830" stroke-width="0.5" stroke-dasharray="2 8"/>

      <!-- 眼白 -->
      <path d="M-90,0 Q-50,-55 0,-60 Q50,-55 90,0 Q50,55 0,60 Q-50,55 -90,0 Z" fill="#dde4f0" stroke="#8899bb" stroke-width="1.5"/>

      <!-- 虹膜组(可移动) -->
      <g id="irisGroup">
        <!-- 虹膜 -->
        <circle cx="0" cy="0" r="36" fill="url(#irisGrad)" stroke="#006655" stroke-width="1"/>
        <!-- 虹膜纹理 -->
        <circle cx="0" cy="0" r="30" fill="none" stroke="#00aa88" stroke-width="0.5" opacity="0.4"/>
        <circle cx="0" cy="0" r="24" fill="none" stroke="#00cc99" stroke-width="0.3" opacity="0.3"/>
        <!-- 瞳孔 -->
        <circle id="pupil" cx="0" cy="0" r="14" fill="#050510"/>
        <!-- 高光 -->
        <ellipse cx="-10" cy="-10" rx="7" ry="4" fill="rgba(255,255,255,0.75)" transform="rotate(-20 -10 -10)"/>
        <ellipse cx="8" cy="6" rx="3" ry="2" fill="rgba(255,255,255,0.3)"/>
      </g>

      <!-- 上眼睑 -->
      <path id="upperLid" d="M-90,0 Q-50,-55 0,-60 Q50,-55 90,0" fill="var(--bg)" stroke="none"/>
      <!-- 下眼睑 -->
      <path id="lowerLid" d="M-90,0 Q-50,55 0,60 Q50,55 90,0" fill="var(--bg)" stroke="none"/>

      <!-- 眼睛轮廓重绘(在眼睑上方) -->
      <path d="M-90,0 Q-50,-55 0,-60 Q50,-55 90,0 Q50,55 0,60 Q-50,55 -90,0 Z" fill="none" stroke="#6680aa" stroke-width="2"/>

      <!-- 标注文字 -->
      <text x="0" y="-95" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="11" font-weight="700" fill="#00ffc8" letter-spacing="1.5" opacity="0.8">EYE TRACKING</text>
      <text x="0" y="-80" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="9" fill="#5a7a9a" opacity="0.6">红外眼动追踪模组</text>
    </g>

    <!-- ======================== 右侧:智能台灯 ======================== -->
    <g id="lampGroup" transform="translate(1060, 280)">
      <!-- 灯座 -->
      <rect x="-40" y="160" width="80" height="12" rx="6" fill="#1a2540" stroke="#2a3a5a" stroke-width="1.5"/>
      <!-- 灯杆 -->
      <rect x="-5" y="50" width="10" height="112" rx="3" fill="#1a2540" stroke="#2a3a5a" stroke-width="1"/>
      <!-- 灯臂 -->
      <line x1="0" y1="50" x2="40" y2="-10" stroke="#2a3a5a" stroke-width="6" stroke-linecap="round"/>
      <!-- 灯罩 -->
      <path d="M15,-10 L65,-10 L55,20 L25,20 Z" fill="#1e2d4a" stroke="#3a4a6a" stroke-width="1.5" stroke-linejoin="round"/>
      <!-- 灯泡区域 -->
      <circle id="bulb" cx="40" cy="18" r="8" fill="#2a3040" stroke="#3a4a5a" stroke-width="1"/>

      <!-- 灯光效果(初始隐藏) -->
      <g id="lampLight" opacity="0">
        <ellipse cx="40" cy="80" rx="120" ry="100" fill="url(#lampGlow)"/>
        <line x1="20" y1="22" x2="-20" y2="120" stroke="#ffdd44" stroke-width="0.5" opacity="0.3"/>
        <line x1="40" y1="24" x2="40" y2="130" stroke="#ffdd44" stroke-width="0.5" opacity="0.3"/>
        <line x1="60" y1="22" x2="100" y2="120" stroke="#ffdd44" stroke-width="0.5" opacity="0.3"/>
      </g>

      <!-- 标注文字 -->
      <text x="0" y="-50" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="11" font-weight="700" fill="#ffaa00" letter-spacing="1.5" opacity="0.8">SMART LAMP</text>
      <text x="0" y="-35" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="9" fill="#5a7a9a" opacity="0.6">目标智能设备</text>
    </g>

    <!-- ======================== 注视光束 ======================== -->
    <g id="beamGroup" opacity="0">
      <!-- 主光束线 -->
      <line id="beamLine" x1="370" y1="320" x2="1010" y2="280" stroke="url(#beamGrad)" stroke-width="2.5" filter="url(#glowPrimary)"/>
      <!-- 光束虚线层 -->
      <line id="beamDash" x1="370" y1="320" x2="1010" y2="280" stroke="#00ffc8" stroke-width="1" stroke-dasharray="8 12" opacity="0.5">
        <animate attributeName="stroke-dashoffset" from="0" to="-40" dur="1s" repeatCount="indefinite"/>
      </line>
      <!-- 光束粒子容器 -->
      <g id="beamParticles"></g>
    </g>

    <!-- ======================== 目标瞄准框 ======================== -->
    <g id="reticle" transform="translate(1060, 280)" opacity="0">
      <circle cx="40" cy="0" r="30" fill="none" stroke="#00ffc8" stroke-width="1.5" stroke-dasharray="6 4" opacity="0.6">
        <animateTransform attributeName="transform" type="rotate" from="0 40 0" to="360 40 0" dur="4s" repeatCount="indefinite"/>
      </circle>
      <circle cx="40" cy="0" r="22" fill="none" stroke="#00ffc8" stroke-width="0.8" opacity="0.4">
        <animateTransform attributeName="transform" type="rotate" from="360 40 0" to="0 40 0" dur="3s" repeatCount="indefinite"/>
      </circle>
      <!-- 十字线 -->
      <line x1="40" y1="-35" x2="40" y2="-26" stroke="#00ffc8" stroke-width="1.5" opacity="0.7"/>
      <line x1="40" y1="26" x2="40" y2="35" stroke="#00ffc8" stroke-width="1.5" opacity="0.7"/>
      <line x1="5" y1="0" x2="14" y2="0" stroke="#00ffc8" stroke-width="1.5" opacity="0.7"/>
      <line x1="66" y1="0" x2="75" y2="0" stroke="#00ffc8" stroke-width="1.5" opacity="0.7"/>
    </g>

    <!-- ======================== 停留计时环 ======================== -->
    <g id="dwellRing" transform="translate(1100, 280)" opacity="0">
      <circle cx="0" cy="0" r="42" fill="none" stroke="#1a2744" stroke-width="4"/>
      <circle id="dwellArc" cx="0" cy="0" r="42" fill="none" stroke="#00ffc8" stroke-width="4" stroke-linecap="round"
        stroke-dasharray="0 264" transform="rotate(-90)"/>
      <text id="dwellText" x="0" y="4" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="12" font-weight="500" fill="#00ffc8">0ms</text>
    </g>

    <!-- ======================== 微表情面板 ======================== -->
    <g id="expressionPanel" transform="translate(160, 530)" opacity="0">
      <rect x="0" y="0" width="200" height="100" rx="8" fill="rgba(12,20,36,0.9)" stroke="#1a2744" stroke-width="1"/>
      <text x="100" y="22" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="9" font-weight="700" fill="#ff4757" letter-spacing="1">MICRO-EXPRESSION</text>
      <line x1="10" y1="30" x2="190" y2="30" stroke="#1a2744" stroke-width="0.5"/>

      <!-- 表情图标 -->
      <g id="exprIcon" transform="translate(35, 62)">
        <circle cx="0" cy="0" r="18" fill="none" stroke="#ff4757" stroke-width="1.5"/>
        <!-- 眼睛 -->
        <circle cx="-6" cy="-4" r="2" fill="#ff4757"/>
        <circle cx="6" cy="-4" r="2" fill="#ff4757"/>
        <!-- 嘴巴(可变) -->
        <path id="exprMouth" d="M-7,6 Q0,4 7,6" fill="none" stroke="#ff4757" stroke-width="1.5" stroke-linecap="round"/>
      </g>

      <!-- 表情数据 -->
      <text id="exprLabel" x="85" y="52" font-family="'IBM Plex Mono', monospace" font-size="11" fill="#a8b8d0">中性</text>
      <text id="exprConf" x="85" y="70" font-family="'IBM Plex Mono', monospace" font-size="9" fill="#5a7a9a">置信度: --</text>
      <text x="85" y="86" font-family="'IBM Plex Mono', monospace" font-size="9" fill="#5a7a9a">情绪倾向: --</text>
    </g>

    <!-- ======================== 处理流水线 ======================== -->
    <g id="pipeline" transform="translate(250, 640)">
      <!-- 连接线 -->
      <line x1="90" y1="22" x2="280" y2="22" stroke="#1a2744" stroke-width="1.5" stroke-dasharray="4 4"/>
      <line x1="370" y1="22" x2="560" y2="22" stroke="#1a2744" stroke-width="1.5" stroke-dasharray="4 4"/>
      <line x1="650" y1="22" x2="840" y2="22" stroke="#1a2744" stroke-width="1.5" stroke-dasharray="4 4"/>

      <!-- 节点1: 眼动追踪 -->
      <g id="node1" class="pipeline-node">
        <rect x="0" y="0" width="90" height="44" rx="6" fill="#0c1424" stroke="#1a2744" stroke-width="1.5"/>
        <text x="45" y="17" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="8" font-weight="700" fill="#5a7a9a">GAZE</text>
        <text x="45" y="34" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" fill="#3a5a7a">眼动追踪</text>
      </g>

      <!-- 节点2: 微表情 -->
      <g id="node2" class="pipeline-node" transform="translate(280, 0)">
        <rect x="0" y="0" width="90" height="44" rx="6" fill="#0c1424" stroke="#1a2744" stroke-width="1.5"/>
        <text x="45" y="17" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="8" font-weight="700" fill="#5a7a9a">EXPR</text>
        <text x="45" y="34" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" fill="#3a5a7a">微表情</text>
      </g>

      <!-- 节点3: 边缘计算 -->
      <g id="node3" class="pipeline-node" transform="translate(560, 0)">
        <rect x="0" y="0" width="90" height="44" rx="6" fill="#0c1424" stroke="#1a2744" stroke-width="1.5"/>
        <text x="45" y="17" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="8" font-weight="700" fill="#5a7a9a">EDGE</text>
        <text x="45" y="34" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" fill="#3a5a7a">边缘计算</text>
      </g>

      <!-- 节点4: 意图判定 -->
      <g id="node4" class="pipeline-node" transform="translate(840, 0)">
        <rect x="0" y="0" width="90" height="44" rx="6" fill="#0c1424" stroke="#1a2744" stroke-width="1.5"/>
        <text x="45" y="17" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="8" font-weight="700" fill="#5a7a9a">INTENT</text>
        <text x="45" y="34" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" fill="#3a5a7a">意图判定</text>
      </g>

      <!-- 数据流动画线 -->
      <g id="flowParticles"></g>
    </g>

    <!-- ======================== 置信度仪表 ======================== -->
    <g id="confGauge" transform="translate(550, 530)" opacity="0">
      <rect x="0" y="0" width="300" height="70" rx="8" fill="rgba(12,20,36,0.9)" stroke="#1a2744" stroke-width="1"/>
      <text x="150" y="18" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="9" font-weight="700" fill="#ffaa00" letter-spacing="1">CONFIDENCE LEVEL</text>

      <!-- 进度条背景 -->
      <rect x="15" y="28" width="270" height="10" rx="5" fill="#0a1020"/>
      <!-- 进度条填充 -->
      <rect id="confBar" x="15" y="28" width="0" height="10" rx="5" fill="url(#confGrad)"/>

      <!-- 阈值标记 -->
      <line id="threshLine" x1="244.5" y1="24" x2="244.5" y2="42" stroke="#ff4757" stroke-width="1.5" stroke-dasharray="2 2"/>
      <text id="threshLabel" x="244.5" y="54" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="8" fill="#ff4757">阈值 85%</text>

      <!-- 数值 -->
      <text id="confValue" x="280" y="18" text-anchor="end" font-family="'IBM Plex Mono', monospace" font-size="14" font-weight="500" fill="#ffaa00">0%</text>
    </g>

    <!-- ======================== 系统提示气泡 ======================== -->
    <g id="sysPrompt" transform="translate(620, 380)" opacity="0">
      <rect x="0" y="0" width="280" height="60" rx="10" fill="rgba(0,255,200,0.08)" stroke="#00ffc8" stroke-width="1.5" filter="url(#glowPrimary)"/>
      <!-- 小三角 -->
      <polygon points="40,60 50,72 60,60" fill="rgba(0,255,200,0.08)" stroke="#00ffc8" stroke-width="1.5"/>
      <line x1="41" y1="60" x2="59" y2="60" stroke="rgba(12,20,36,1)" stroke-width="2"/>
      <text x="140" y="24" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="9" fill="#00ffc8" letter-spacing="1" opacity="0.7">SYSTEM PROMPT</text>
      <text x="140" y="46" text-anchor="middle" font-family="'IBM Plex Mono', monospace" font-size="14" font-weight="500" fill="#e8f0ff">"是否需要打开该设备?"</text>
    </g>

    <!-- ======================== 用户回复指示 ======================== -->
    <g id="userResponse" transform="translate(280, 460)" opacity="0">
      <rect x="-55" y="-16" width="110" height="32" rx="16" fill="rgba(0,255,136,0.1)" stroke="#00ff88" stroke-width="1.5"/>
      <text x="0" y="5" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="13" font-weight="700" fill="#00ff88" letter-spacing="2">NOD · 是</text>
    </g>

    <!-- ======================== 数据流粒子容器 ======================== -->
    <g id="dataParticles"></g>

    <!-- ======================== 装饰性元素 ======================== -->
    <!-- 左侧装饰线 -->
    <line x1="50" y1="100" x2="50" y2="650" stroke="#0f1d35" stroke-width="0.5"/>
    <circle cx="50" cy="100" r="2" fill="#0f1d35"/>
    <circle cx="50" cy="650" r="2" fill="#0f1d35"/>

    <!-- 右侧装饰线 -->
    <line x1="1350" y1="100" x2="1350" y2="650" stroke="#0f1d35" stroke-width="0.5"/>
    <circle cx="1350" cy="100" r="2" fill="#0f1d35"/>
    <circle cx="1350" cy="650" r="2" fill="#0f1d35"/>

    <!-- 角落装饰 -->
    <path d="M20,20 L60,20 M20,20 L20,60" stroke="#1a3050" stroke-width="1.5" fill="none"/>
    <path d="M1380,20 L1340,20 M1380,20 L1380,60" stroke="#1a3050" stroke-width="1.5" fill="none"/>
    <path d="M20,730 L60,730 M20,730 L20,690" stroke="#1a3050" stroke-width="1.5" fill="none"/>
    <path d="M1380,730 L1340,730 M1380,730 L1380,690" stroke="#1a3050" stroke-width="1.5" fill="none"/>

    <!-- IFR标签 -->
    <text x="700" y="725" text-anchor="middle" font-family="Orbitron, sans-serif" font-size="10" fill="#1a3050" letter-spacing="4">IDEAL FINAL RESULT · TRIZ</text>
  </svg>
</div>

<!-- 控制面板 -->
<div class="controls">
  <div class="control-group">
    <div class="control-label">
      <span>注视停留判定阈值</span>
      <span class="control-value" id="dwellVal">300ms</span>
    </div>
    <input type="range" id="dwellSlider" min="150" max="600" value="300" step="10"/>
  </div>
  <div class="control-group">
    <div class="control-label">
      <span>意图置信度阈值</span>
      <span class="control-value" id="confThreshVal">85%</span>
    </div>
    <input type="range" id="confThreshSlider" min="60" max="98" value="85" step="1"/>
  </div>
</div>

<script>
(function() {
  'use strict';

  // ====== 常量与配置 ======
  const PHASE_NAMES = ['IDLE', 'GAZE_LOCK', 'EXPRESSION', 'COMPUTE', 'CONFIRM', 'RESPOND', 'EXECUTE', 'RESET'];
  const PHASE_LABELS = ['待机', '视线锁定', '微表情捕捉', '边缘计算', '系统主动确认', '极简回复', '执行动作', '重置'];
  const PHASE_DURATIONS = [800, 2500, 2000, 2500, 2000, 1500, 2000, 1500]; // 基础时长(ms)

  // ====== 状态变量 ======
  let currentPhase = -1;
  let phaseTime = 0;
  let lastTimestamp = 0;
  let dwellThreshold = 300;
  let confidenceThreshold = 85;
  let confidence = 0;
  let dwellProgress = 0;
  let animationId = null;

  // ====== DOM 引用 ======
  const svg = document.getElementById('scene');
  const irisGroup = document.getElementById('irisGroup');
  const pupil = document.getElementById('pupil');
  const upperLid = document.getElementById('upperLid');
  const lowerLid = document.getElementById('lowerLid');
  const beamGroup = document.getElementById('beamGroup');
  const reticle = document.getElementById('reticle');
  const dwellRing = document.getElementById('dwellRing');
  const dwellArc = document.getElementById('dwellArc');
  const dwellText = document.getElementById('dwellText');
  const expressionPanel = document.getElementById('expressionPanel');
  const exprMouth = document.getElementById('exprMouth');
  const exprLabel = document.getElementById('exprLabel');
  const exprConf = document.getElementById('exprConf');
  const confGauge = document.getElementById('confGauge');
  const confBar = document.getElementById('confBar');
  const confValue = document.getElementById('confValue');
  const threshLine = document.getElementById('threshLine');
  const threshLabel = document.getElementById('threshLabel');
  const sysPrompt = document.getElementById('sysPrompt');
  const userResponse = document.getElementById('userResponse');
  const lampLight = document.getElementById('lampLight');
  const bulb = document.getElementById('bulb');
  const phaseStrip = document.getElementById('phaseStrip');

  // 流水线节点
  const nodes = [1,2,3,4].map(i => document.getElementById('node' + i));

  // ====== 初始化相位指示条 ======
  PHASE_LABELS.forEach((label, i) => {
    if (i === 0 || i === 7) return; // 跳过IDLE和RESET
    const chip = document.createElement('div');
    chip.className = 'phase-chip';
    chip.dataset.phase = i;
    chip.textContent = label;
    phaseStrip.appendChild(chip);
  });

  // ====== 滑块事件 ======
  const dwellSlider = document.getElementById('dwellSlider');
  const confThreshSlider = document.getElementById('confThreshSlider');
  const dwellValEl = document.getElementById('dwellVal');
  const confThreshValEl = document.getElementById('confThreshVal');

  dwellSlider.addEventListener('input', function() {
    dwellThreshold = parseInt(this.value);
    dwellValEl.textContent = dwellThreshold + 'ms';
  });

  confThreshSlider.addEventListener('input', function() {
    confidenceThreshold = parseInt(this.value);
    confThreshValEl.textContent = confidenceThreshold + '%';
    // 更新阈值标记位置
    const threshX = 15 + (confidenceThreshold / 100) * 270;
    threshLine.setAttribute('x1', threshX);
    threshLine.setAttribute('x2', threshX);
    threshLabel.setAttribute('x', threshX);
    threshLabel.textContent = '阈值 ' + confidenceThreshold + '%';
  });

  // ====== 工具函数 ======
  function lerp(a, b, t) { return a + (b - a) * Math.min(1, Math.max(0, t)); }
  function easeInOut(t) { return t < 0.5 ? 2 * t * t : -1 + (4 - 2 * t) * t; }
  function easeOut(t) { return 1 - Math.pow(1 - t, 3); }

  function setOpacity(el, val) { el.setAttribute('opacity', val); }

  function activateNode(index, active) {
    const node = nodes[index];
    if (!node) return;
    const rect = node.querySelector('rect');
    const texts = node.querySelectorAll('text');
    if (active) {
      rect.setAttribute('fill', 'rgba(0,255,200,0.1)');
      rect.setAttribute('stroke', '#00ffc8');
      texts.forEach(t => { t.setAttribute('fill', '#00ffc8'); });
    } else {
      rect.setAttribute('fill', '#0c1424');
      rect.setAttribute('stroke', '#1a2744');
      texts[0].setAttribute('fill', '#5a7a9a');
      texts[1].setAttribute('fill', '#3a5a7a');
    }
  }

  function updatePhaseStrip(activePhase) {
    const chips = phaseStrip.querySelectorAll('.phase-chip');
    chips.forEach(chip => {
      const p = parseInt(chip.dataset.phase);
      chip.classList.remove('active', 'done');
      if (p === activePhase) chip.classList.add('active');
      else if (p < activePhase) chip.classList.add('done');
    });
  }

  // ====== 眼球动画 ======
  let irisTargetX = 0, irisTargetY = 0;
  let irisCurrentX = 0, irisCurrentY = 0;

  function updateEye(dt) {
    // 平滑跟踪
    irisCurrentX = lerp(irisCurrentX, irisTargetX, 0.03);
    irisCurrentY = lerp(irisCurrentY, irisTargetY, 0.03);
    irisGroup.setAttribute('transform', 'translate(' + irisCurrentX.toFixed(1) + ',' + irisCurrentY.toFixed(1) + ')');
  }

  // ====== 眨眼 ======
  let blinkTimer = 0;
  let isBlinking = false;
  let blinkProgress = 0;

  function updateBlink(dt) {
    blinkTimer += dt;
    if (!isBlinking && blinkTimer > 3000 + Math.random() * 2000) {
      isBlinking = true;
      blinkProgress = 0;
      blinkTimer = 0;
    }
    if (isBlinking) {
      blinkProgress += dt / 180;
      if (blinkProgress >= 1) {
        isBlinking = false;
        blinkProgress = 0;
        upperLid.setAttribute('d', 'M-90,0 Q-50,-55 0,-60 Q50,-55 90,0');
        lowerLid.setAttribute('d', 'M-90,0 Q-50,55 0,60 Q50,55 90,0');
      } else {
        const t = blinkProgress < 0.5 ? easeInOut(blinkProgress * 2) : easeInOut(2 - blinkProgress * 2);
        const lidY = t * 55;
        upperLid.setAttribute('d', 'M-90,' + (0 + lidY * 0.3) + ' Q-50,' + (-55 + lidY) + ' 0,' + (-60 + lidY) + ' Q50,' + (-55 + lidY) + ' 90,' + (0 + lidY * 0.3));
        lowerLid.setAttribute('d', 'M-90,' + (0 - lidY * 0.15) + ' Q-50,' + (55 - lidY * 0.3) + ' 0,' + (60 - lidY * 0.3) + ' Q50,' + (55 - lidY * 0.3) + ' 90,' + (0 - lidY * 0.15));
      }
    }
  }

  // ====== 光束粒子 ======
  const beamParticleEls = [];
  const BEAM_PARTICLE_COUNT = 8;

  function initBeamParticles() {
    const container = document.getElementById('beamParticles');
    for (let i = 0; i < BEAM_PARTICLE_COUNT; i++) {
      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      circle.setAttribute('r', '2.5');
      circle.setAttribute('fill', '#00ffc8');
      circle.setAttribute('opacity', '0');
      container.appendChild(circle);
      beamParticleEls.push({ el: circle, t: i / BEAM_PARTICLE_COUNT, speed: 0.0004 + Math.random() * 0.0002 });
    }
  }

  function updateBeamParticles(dt) {
    const x1 = 370, y1 = 320, x2 = 1010, y2 = 280;
    beamParticleEls.forEach(p => {
      p.t += p.speed * dt;
      if (p.t > 1) p.t -= 1;
      const x = lerp(x1, x2, p.t);
      const y = lerp(y1, y2, p.t);
      const alpha = Math.sin(p.t * Math.PI) * 0.7;
      p.el.setAttribute('cx', x);
      p.el.setAttribute('cy', y);
      p.el.setAttribute('opacity', alpha.toFixed(2));
    });
  }

  // ====== 数据流粒子(流水线) ======
  const dataParticleEls = [];
  const DATA_PARTICLE_COUNT = 6;

  function initDataParticles() {
    const container = document.getElementById('dataParticles');
    for (let i = 0; i < DATA_PARTICLE_COUNT; i++) {
      const circle = document.createElementNS('http://www.w3.org/2000/svg', 'circle');
      circle.setAttribute('r', '3');
      circle.setAttribute('fill', '#00ffc8');
      circle.setAttribute('opacity', '0');
      circle.setAttribute('filter', 'url(#glowPrimary)');
      container.appendChild(circle);
      dataParticleEls.push({ el: circle, active: false });
    }
  }

  // ====== 阶段控制 ======
  function getPhaseDuration(phase) {
    // 根据滑块值调整关键阶段时长
    switch(phase) {
      case 1: return PHASE_DURATIONS[1] * (dwellThreshold / 300); // GAZE_LOCK
      case 3: return PHASE_DURATIONS[3] * (confidenceThreshold / 85); // COMPUTE
      default: return PHASE_DURATIONS[phase];
    }
  }

  function enterPhase(phase) {
    currentPhase = phase;
    phaseTime = 0;
    updatePhaseStrip(phase);

    switch(phase) {
      case 0: // IDLE
        irisTargetX = 0; irisTargetY = 0;
        setOpacity(beamGroup, 0);
        setOpacity(reticle, 0);
        setOpacity(dwellRing, 0);
        setOpacity(expressionPanel, 0);
        setOpacity(confGauge, 0);
        setOpacity(sysPrompt, 0);
        setOpacity(userResponse, 0);
        setOpacity(lampLight, 0);
        nodes.forEach((_, i) => activateNode(i, false));
        confidence = 0;
        dwellProgress = 0;
        bulb.setAttribute('fill', '#2a3040');
        break;

      case 1: // GAZE_LOCK
        irisTargetX = 18; irisTargetY = -3;
        setOpacity(beamGroup, 1);
        setOpacity(reticle, 1);
        setOpacity(dwellRing, 1);
        activateNode(0, true);
        break;

      case 2: // EXPRESSION
        setOpacity(expressionPanel, 1);
        activateNode(1, true);
        break;

      case 3: // COMPUTE
        setOpacity(confGauge, 1);
        activateNode(2, true);
        break;

      case 4: // CONFIRM
        setOpacity(sysPrompt, 1);
        activateNode(3, true);
        break;

      case 5: // RESPOND
        setOpacity(userResponse, 1);
        break;

      case 6: // EXECUTE
        break;

      case 7: // RESET
        break;
    }
  }

  function updatePhase(dt) {
    phaseTime += dt;
    const duration = getPhaseDuration(currentPhase);
    const progress = Math.min(1, phaseTime / duration);

    switch(currentPhase) {
      case 0: // IDLE - 待机,眼球缓慢移动
        irisTargetX = Math.sin(Date.now() * 0.001) * 5;
        irisTargetY = Math.cos(Date.now() * 0.0013) * 3;
        if (progress >= 1) enterPhase(1);
        break;

      case 1: // GAZE_LOCK - 视线锁定
        {
          // 眼球偏向目标
          const irisShift = easeOut(progress);
          irisTargetX = lerp(0, 22, irisShift);
          irisTargetY = lerp(0, -4, irisShift);

          // 瞳孔收缩(聚焦)
          const pupilR = lerp(14, 10, easeOut(Math.min(1, progress * 1.5)));
          pupil.setAttribute('r', pupilR);

          // 停留计时
          dwellProgress = progress;
          const circumference = 2 * Math.PI * 42;
          const dashLen = circumference * progress;
          dwellArc.setAttribute('stroke-dasharray', dashLen + ' ' + circumference);
          dwellText.textContent = Math.round(dwellThreshold * progress) + 'ms';

          // 光束渐显
          setOpacity(beamGroup, lerp(0.3, 1, easeOut(progress)));

          if (progress >= 1) enterPhase(2);
        }
        break;

      case 2: // EXPRESSION - 微表情捕捉
        {
          const ep = easeOut(progress);

          // 表情从中性变为微皱眉
          if (progress < 0.4) {
            exprMouth.setAttribute('d', 'M-7,6 Q0,4 7,6'); // 中性
            exprLabel.textContent = '中性';
          } else if (progress < 0.7) {
            exprMouth.setAttribute('d', 'M-7,5 Q0,3 7,5'); // 轻微不满
            exprLabel.textContent = '轻微关注';
          } else {
            exprMouth.setAttribute('d', 'M-7,7 Q0,4 7,7'); // 皱眉
            exprLabel.textContent = '关注倾向';
          }
          exprConf.textContent = '置信度: ' + Math.round(ep * 78) + '%';

          // 更新面板中的情绪倾向
          const emotionText = expressionPanel.querySelectorAll('text')[4];
          if (emotionText) {
            if (progress < 0.5) emotionText.textContent = '情绪倾向: 中性';
            else emotionText.textContent = '情绪倾向: 需求信号';
          }

          if (progress >= 1) enterPhase(3);
        }
        break;

      case 3: // COMPUTE - 边缘计算
        {
          const ep = easeInOut(progress);
          confidence = ep * 95; // 最高算到95%
          confBar.setAttribute('width', (confidence / 100 * 270).toFixed(1));
          confValue.textContent = Math.round(confidence) + '%';

          // 置信度颜色变化
          if (confidence < 50) confValue.setAttribute('fill', '#ff4757');
          else if (confidence < confidenceThreshold) confValue.setAttribute('fill', '#ffaa00');
          else confValue.setAttribute('fill', '#00ff88');

          // 停留环保持满
          const circumference = 2 * Math.PI * 42;
          dwellArc.setAttribute('stroke-dasharray', circumference + ' ' + circumference);
          dwellArc.setAttribute('stroke', '#00ff88');
          dwellText.textContent = dwellThreshold + 'ms';

          if (progress >= 1) enterPhase(4);
        }
        break;

      case 4: // CONFIRM - 系统主动确认
        {
          const ep = easeOut(Math.min(1, progress * 1.5));
          setOpacity(sysPrompt, ep);

          // 瞳孔略微扩大(听到系统提问)
          const pupilR = lerp(10, 12, ep);
          pupil.setAttribute('r', pupilR);

          if (progress >= 1) enterPhase(5);
        }
        break;

      case 5: // RESPOND - 极简回复
        {
          const ep = easeOut(progress);
          setOpacity(userResponse, ep);

          // 眼球微动(点头反馈)
          irisTargetY = -4 + Math.sin(progress * Math.PI * 2) * 2;

          if (progress >= 1) enterPhase(6);
        }
        break;

      case 6: // EXECUTE - 执行动作
        {
          const ep = easeOut(progress);

          // 灯泡亮起
          bulb.setAttribute('fill', lerpColor('#2a3040', '#ffdd44', ep));

          // 灯光渐显
          setOpacity(lampLight, ep * 0.9);

          // 瞄准框变绿
          reticle.querySelectorAll('line, circle').forEach(el => {
            el.setAttribute('stroke', ep > 0.5 ? '#00ff88' : '#00ffc8');
          });

          // 置信度条变绿
          if (ep > 0.3) {
            confBar.setAttribute('fill', '#00ff88');
          }

          if (progress >= 1) enterPhase(7);
        }
        break;

      case 7: // RESET - 重置
        {
          const ep = easeInOut(progress);

          // 所有元素淡出
          setOpacity(beamGroup, 1 - ep);
          setOpacity(reticle, 1 - ep);
          setOpacity(dwellRing, 1 - ep);
          setOpacity(expressionPanel, 1 - ep);
          setOpacity(confGauge, 1 - ep);
          setOpacity(sysPrompt, 1 - ep);
          setOpacity(userResponse, 1 - ep);
          setOpacity(lampLight, (1 - ep) * 0.9);

          // 眼球回中
          irisTargetX = lerp(22, 0, ep);
          irisTargetY = lerp(-4, 0, ep);

          // 瞳孔恢复
          pupil.setAttribute('r', lerp(12, 14, ep));

          // 停留环颜色重置
          dwellArc.setAttribute('stroke', '#00ffc8');

          // 灯泡重置
          bulb.setAttribute('fill', lerpColor('#ffdd44', '#2a3040', ep));

          // 流水线节点重置
          if (ep > 0.5) {
            nodes.forEach((_, i) => activateNode(i, false));
          }

          if (progress >= 1) enterPhase(0);
        }
        break;
    }
  }

  // 颜色插值辅助
  function lerpColor(c1, c2, t) {
    const r1 = parseInt(c1.slice(1,3), 16), g1 = parseInt(c1.slice(3,5), 16), b1 = parseInt(c1.slice(5,7), 16);
    const r2 = parseInt(c2.slice(1,3), 16), g2 = parseInt(c2.slice(3,5), 16), b2 = parseInt(c2.slice(5,7), 16);
    const r = Math.round(lerp(r1, r2, t)), g = Math.round(lerp(g1, g2, t)), b = Math.round(lerp(b1, b2, t));
    return '#' + ((1 << 24) + (r << 16) + (g << 8) + b).toString(16).slice(1);
  }

  // ====== 主动画循环 ======
  function animate(timestamp) {
    if (!lastTimestamp) lastTimestamp = timestamp;
    const dt = Math.min(50, timestamp - lastTimestamp); // 限制最大帧间隔
    lastTimestamp = timestamp;

    updateEye(dt);
    updateBlink(dt);
    updatePhase(dt);
    updateBeamParticles(dt);

    animationId = requestAnimationFrame(animate);
  }

  // ====== 初始化 ======
  function init() {
    initBeamParticles();
    initDataParticles();

    // 初始化阈值标记位置
    const threshX = 15 + (confidenceThreshold / 100) * 270;
    threshLine.setAttribute('x1', threshX);
    threshLine.setAttribute('x2', threshX);
    threshLabel.setAttribute('x', threshX);

    // 启动动画
    enterPhase(0);
    // 短暂延迟后开始第一阶段,给浏览器渲染时间
    setTimeout(() => {
      enterPhase(0);
      lastTimestamp = 0;
      animationId = requestAnimationFrame(animate);
    }, 100);
  }

  // 页面加载完成后自动启动
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', init);
  } else {
    init();
  }

  // 页面重新可见时重启动画(重开即播)
  document.addEventListener('visibilitychange', function() {
    if (!document.hidden) {
      lastTimestamp = 0;
      enterPhase(0);
      if (!animationId) {
        animationId = requestAnimationFrame(animate);
      }
    }
  });

})();
</script>
</body>
</html>

实现说明

本动画围绕 TRIZ「最终理想解」思想,直接展示"视觉-意图预测引擎"在理想状态下的完整运作原理,无需与传统语音交互做前后对比——因为 IFR 本身就是问题消失后的终极状态。

核心设计决策:

  1. 视觉引导:以明亮青色(#00ffc8)标注所有"系统主动读取人"的关键路径——注视光束、瞄准框、停留计时环,让视线自然跟随"机器读取人"的信息流;而琥珀色(#ffaa00)和珊瑚红(#ff4757)则标记置信度与阈值边界,形成明确的视觉层级。

  2. 资源利用的直观呈现:底部四节点流水线(GAZE → EXPR → EDGE → INTENT)逐步点亮,展示系统如何复用"人自然行为"(视线方向、微表情)作为输入资源,而非要求人额外付出语音指令的"成本"。

  3. 交互式参数控制:两个滑块分别控制「注视停留判定阈值」和「意图置信度阈值」,调整后动画时长和触发条件实时响应——用户可以亲手体验阈值变化如何影响系统判定速度和准确度的权衡。

  4. 动画自动循环:页面加载后自动从"待机 → 视线锁定 → 微表情捕捉 → 边缘计算 → 系统主动确认 → 极简回复 → 执行动作 → 重置"无缝循环,每次重开页面均从初始状态自动播放。

  5. 细节动效:眼球虹膜平滑追踪目标、周期性眨眼、光束粒子流、停留计时弧线填充、置信度仪表颜色渐变(红→黄→绿越过阈值)、灯泡点亮等,均用缓动函数精心调节,营造科技感与沉浸感。

积分规则:第一轮对话扣减8分,后续每轮扣6分