Spaces:
Running
Running
| <html lang="zh-TW"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta http-equiv="Content-Security-Policy" content="default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval' https://accounts.google.com https://www.gstatic.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' ws: wss: https://accounts.google.com; img-src 'self' data: https: blob:; media-src 'self' blob: data:; frame-src https://accounts.google.com; base-uri 'self';"> | |
| <title>Bloom Ware 語音沉浸式 - 光暈花瓣版</title> | |
| <link rel="icon" href="/login/icon.svg" type="image/svg+xml"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> | |
| <style> | |
| * { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| background: #F5F1ED; | |
| color: #1A1A1A; | |
| overflow: hidden; | |
| -webkit-font-smoothing: antialiased; | |
| /* 2025 最佳實踐:多層 fallback 確保相容性 */ | |
| height: 100vh; /* fallback 1: 傳統瀏覽器 */ | |
| height: 100svh; /* fallback 2: 小視窗高度 */ | |
| height: 100dvh; /* 主要方案: 動態視窗高度(iframe 最佳) */ | |
| /* 針對 WebKit 瀏覽器的額外修正 */ | |
| height: -webkit-fill-available; | |
| } | |
| /* === 控制面板 === */ | |
| .control-panel { | |
| position: fixed; | |
| top: 20px; | |
| left: 20px; | |
| z-index: 200; | |
| background: rgba(255, 255, 255, 0.95); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 16px; | |
| padding: 24px; | |
| backdrop-filter: blur(20px) saturate(180%); | |
| max-width: 320px; | |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); | |
| display: none; /* 隱藏模擬面板 */ | |
| } | |
| .control-panel h3 { | |
| font-size: 13px; | |
| font-weight: 600; | |
| letter-spacing: 0.5px; | |
| color: #1A1A1A; | |
| margin-bottom: 20px; | |
| text-transform: uppercase; | |
| } | |
| .control-group { | |
| margin-bottom: 20px; | |
| } | |
| .control-group label { | |
| display: block; | |
| font-size: 11px; | |
| font-weight: 500; | |
| color: rgba(0, 0, 0, 0.5); | |
| margin-bottom: 8px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| } | |
| .btn { | |
| width: 100%; | |
| padding: 12px 16px; | |
| margin-bottom: 8px; | |
| background: #FFFFFF; | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 10px; | |
| color: #1A1A1A; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); | |
| } | |
| .btn:hover { | |
| background: #F5F5F5; | |
| border-color: rgba(0, 0, 0, 0.12); | |
| transform: translateY(-1px); | |
| box-shadow: 0 2px 4px rgba(0, 0, 0, 0.08); | |
| } | |
| .btn:active { | |
| transform: translateY(0); | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); | |
| } | |
| select { | |
| width: 100%; | |
| padding: 12px; | |
| background: #FFFFFF; | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 10px; | |
| color: #1A1A1A; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| box-shadow: 0 1px 2px rgba(0, 0, 0, 0.04); | |
| } | |
| select:hover { | |
| background: #F5F5F5; | |
| border-color: rgba(0, 0, 0, 0.12); | |
| } | |
| /* === 沉浸式覆蓋層 === */ | |
| .voice-immersive-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| /* 2025 最佳實踐:漸進式增強 */ | |
| width: 100vw; | |
| width: 100dvw; /* 動態寬度(iframe 最佳) */ | |
| height: 100vh; /* fallback 1 */ | |
| height: 100svh; /* fallback 2 */ | |
| height: 100dvh; /* 主要方案 */ | |
| height: -webkit-fill-available; /* WebKit 修正 */ | |
| z-index: 100; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| background: #F5F1ED; | |
| } | |
| /* === 動態漸變背景(移除光暈,改用極淺色情緒底色)=== */ | |
| .voice-immersive-background { | |
| position: fixed; /* 改為 fixed 確保覆蓋整個視窗 */ | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0; | |
| transition: opacity 1.2s cubic-bezier(0.4, 0, 0.2, 1); | |
| z-index: -1; /* 改為 -1 確保在最底層 */ | |
| pointer-events: none; /* 不阻擋其他元素的點擊 */ | |
| } | |
| .voice-immersive-background::before { | |
| content: ''; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: var(--emotion-bg); | |
| opacity: 0.5; /* 提高透明度,從 0.15 增加到 0.5 */ | |
| } | |
| .voice-immersive-background.active { | |
| opacity: 1; | |
| } | |
| /* 情緒淺色底色變體 */ | |
| .emotion-neutral { | |
| --emotion-bg: linear-gradient(180deg, #E0F2FE 0%, #F5F1ED 100%); | |
| --petal-color: #FFFFFF; | |
| } | |
| .emotion-happy { | |
| --emotion-bg: linear-gradient(180deg, #FEF3C7 0%, #F5F1ED 100%); | |
| --petal-color: #FFFFFF; | |
| } | |
| .emotion-sad { | |
| --emotion-bg: linear-gradient(180deg, #E0E7FF 0%, #F5F1ED 100%); | |
| --petal-color: #FFFFFF; | |
| } | |
| .emotion-angry { | |
| --emotion-bg: linear-gradient(180deg, #FEE2E2 0%, #F5F1ED 100%); | |
| --petal-color: #FFFFFF; | |
| } | |
| .emotion-fear { | |
| --emotion-bg: linear-gradient(180deg, #EDE9FE 0%, #F5F1ED 100%); | |
| --petal-color: #FFFFFF; | |
| } | |
| .emotion-surprise { | |
| --emotion-bg: linear-gradient(180deg, #FFEDD5 0%, #F5F1ED 100%); | |
| --petal-color: #FFFFFF; | |
| } | |
| /* === 中央容器 === */ | |
| .voice-center-container { | |
| position: relative; | |
| z-index: 10; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 60px; | |
| } | |
| /* === 波形與麥克風容器 === */ | |
| .voice-waveform-container { | |
| position: relative; | |
| width: 400px; | |
| height: 400px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| /* Canvas 波形 */ | |
| #waveform-canvas { | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| opacity: 0.3; | |
| } | |
| /* ========== 多層蓮花設計(Bloom Ware 品牌特色)========== */ | |
| .voice-mic-container { | |
| position: relative; | |
| width: 240px; | |
| height: 240px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| z-index: 20; | |
| } | |
| /* 花蕊中心(精緻珍珠球體設計,位於花瓣下方)*/ | |
| .bloom-core { | |
| --core-scale: 1; /* 預設縮放比例,可由 media query 覆蓋 */ | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| width: 35px; | |
| height: 35px; | |
| border-radius: 50%; | |
| background: | |
| radial-gradient(circle at 30% 30%, | |
| #FFFFFF 0%, | |
| #FEF9E7 15%, | |
| #FDEAA8 35%, | |
| #FCD34D 60%, | |
| #F59E0B 85%, | |
| #D97706 100% | |
| ); | |
| box-shadow: | |
| 0 4px 16px rgba(245, 158, 11, 0.4), | |
| 0 2px 8px rgba(251, 191, 36, 0.3), | |
| inset 0 3px 10px rgba(255, 255, 255, 0.8), | |
| inset 0 -3px 10px rgba(217, 119, 6, 0.35); | |
| animation: coreBreath 4s ease-in-out infinite; | |
| z-index: 3; | |
| } | |
| /* 花蕊高光(更強烈的玻璃光澤)*/ | |
| .bloom-core::before { | |
| content: ''; | |
| position: absolute; | |
| top: 8%; | |
| left: 10%; | |
| width: 55%; | |
| height: 55%; | |
| border-radius: 50%; | |
| background: | |
| radial-gradient(circle at 35% 35%, | |
| rgba(255, 255, 255, 0.95) 0%, | |
| rgba(255, 255, 255, 0.6) 25%, | |
| rgba(255, 255, 255, 0.2) 50%, | |
| transparent 70% | |
| ); | |
| filter: blur(1px); | |
| } | |
| /* 花蕊深層陰影(移除綠莖,改為立體陰影)*/ | |
| .bloom-core::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 5%; | |
| right: 8%; | |
| width: 40%; | |
| height: 40%; | |
| border-radius: 50%; | |
| background: | |
| radial-gradient(circle at 50% 50%, | |
| rgba(217, 119, 6, 0.4) 0%, | |
| rgba(217, 119, 6, 0.2) 50%, | |
| transparent 100% | |
| ); | |
| filter: blur(2px); | |
| } | |
| @keyframes coreBreath { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) scale(calc(var(--core-scale) * 1)) rotate(0deg); | |
| box-shadow: | |
| 0 3px 12px rgba(251, 191, 36, 0.3), | |
| 0 1px 4px rgba(251, 191, 36, 0.2), | |
| inset 0 2px 6px rgba(255, 255, 255, 0.6), | |
| inset 0 -2px 6px rgba(245, 158, 11, 0.3); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(calc(var(--core-scale) * 1.05)) rotate(2deg); | |
| box-shadow: | |
| 0 4px 16px rgba(251, 191, 36, 0.4), | |
| 0 2px 6px rgba(251, 191, 36, 0.3), | |
| inset 0 3px 8px rgba(255, 255, 255, 0.7), | |
| inset 0 -3px 8px rgba(245, 158, 11, 0.4); | |
| } | |
| } | |
| /* 花瓣容器(多層結構,啟用 3D 透視)*/ | |
| .bloom-petals { | |
| position: absolute; | |
| top: -15%; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| transform-style: preserve-3d; | |
| perspective: 1000px; | |
| } | |
| /* 單一花瓣(蓮花造型,3D 透視)*/ | |
| .bloom-petal { | |
| position: absolute; | |
| width: 65px; | |
| height: 105px; | |
| top: 50%; | |
| left: 50%; | |
| transform-origin: 50% 90%; | |
| transform-style: preserve-3d; | |
| transition: all 1.8s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| opacity: 0.8; | |
| z-index: 5; | |
| } | |
| /* 外層花瓣(較大,1-8 是外層)*/ | |
| .bloom-petal:nth-child(-n+8) { | |
| width: 65px; | |
| height: 105px; | |
| z-index: 5; | |
| } | |
| /* 內層花瓣(較小,9-16 是內層,錯開 22.5 度)*/ | |
| .bloom-petal:nth-child(n+9) { | |
| width: 50px; | |
| height: 85px; | |
| z-index: 10; | |
| } | |
| /* 內層花瓣顏色微調(略淡,增加層次感)*/ | |
| .bloom-petal:nth-child(n+9)::before { | |
| background: | |
| radial-gradient(ellipse 80% 60% at 50% 30%, | |
| rgba(255, 255, 255, 0.96) 0%, | |
| rgba(255, 255, 255, 0.94) 40%, | |
| rgba(249, 250, 251, 0.88) 70%, | |
| rgba(243, 244, 246, 0.82) 100% | |
| ), | |
| linear-gradient(180deg, | |
| rgba(255, 255, 255, 0.95) 0%, | |
| rgba(254, 254, 254, 0.92) 30%, | |
| rgba(249, 250, 251, 0.88) 70%, | |
| rgba(243, 244, 246, 0.85) 100% | |
| ); | |
| } | |
| /* 花瓣形狀(真實蓮花瓣:頂端尖、中間寬、底部收窄)*/ | |
| .bloom-petal::before { | |
| content: ''; | |
| position: absolute; | |
| width: 100%; | |
| height: 100%; | |
| background: | |
| radial-gradient(ellipse 80% 60% at 50% 30%, | |
| rgba(255, 255, 255, 1) 0%, | |
| rgba(255, 255, 255, 0.98) 40%, | |
| rgba(249, 250, 251, 0.95) 70%, | |
| rgba(243, 244, 246, 0.9) 100% | |
| ), | |
| linear-gradient(180deg, | |
| #FFFFFF 0%, | |
| #FEFEFE 30%, | |
| #F9FAFB 70%, | |
| #F3F4F6 100% | |
| ); | |
| clip-path: path('M 30 5 Q 35 0, 40 5 Q 50 20, 55 50 Q 58 75, 50 92 Q 45 98, 30 98 Q 22 98, 17 92 Q 9 75, 12 50 Q 17 20, 27 5'); | |
| box-shadow: | |
| 0 6px 20px rgba(0, 0, 0, 0.15), | |
| inset -3px 4px 10px rgba(0, 0, 0, 0.08), | |
| inset 3px -4px 12px rgba(255, 255, 255, 0.95), | |
| inset 0 -2px 6px rgba(0, 0, 0, 0.04); | |
| transition: all 0.8s cubic-bezier(0.34, 1.56, 0.64, 1); | |
| border: 1px solid rgba(0, 0, 0, 0.06); | |
| filter: drop-shadow(0 2px 4px rgba(0, 0, 0, 0.08)); | |
| } | |
| /* 花瓣中心脈絡(細緻葉脈,非綠莖)*/ | |
| .bloom-petal::after { | |
| content: ''; | |
| position: absolute; | |
| top: 5%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 1.5px; | |
| height: 75%; | |
| background: linear-gradient(180deg, | |
| rgba(0, 0, 0, 0.08) 0%, | |
| rgba(0, 0, 0, 0.04) 50%, | |
| transparent 100% | |
| ); | |
| border-radius: 1px; | |
| opacity: 0.6; | |
| filter: blur(0.3px); | |
| } | |
| /* 待機狀態:3D 螺旋緊閉效果(含苞待放)*/ | |
| .bloom-petal { | |
| opacity: 0.9; | |
| } | |
| /* 外層花瓣待機:3D 球形包覆(統一視角 70deg)*/ | |
| .bloom-petal:nth-child(1) { | |
| transform: translate(-50%, -50%) rotateZ(0deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(2) { | |
| transform: translate(-50%, -50%) rotateZ(45deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(3) { | |
| transform: translate(-50%, -50%) rotateZ(90deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(4) { | |
| transform: translate(-50%, -50%) rotateZ(135deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(5) { | |
| transform: translate(-50%, -50%) rotateZ(180deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(6) { | |
| transform: translate(-50%, -50%) rotateZ(225deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(7) { | |
| transform: translate(-50%, -50%) rotateZ(270deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| .bloom-petal:nth-child(8) { | |
| transform: translate(-50%, -50%) rotateZ(315deg) rotateX(70deg) translateY(-5px) scale(0.9); | |
| } | |
| /* 內層花瓣待機:改為 60deg(比外層更平,符合「包覆」邏輯)且 translateY 更小(更靠近中心)*/ | |
| .bloom-petal:nth-child(9) { | |
| transform: translate(-50%, -50%) rotateZ(22.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(10) { | |
| transform: translate(-50%, -50%) rotateZ(67.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(11) { | |
| transform: translate(-50%, -50%) rotateZ(112.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(12) { | |
| transform: translate(-50%, -50%) rotateZ(157.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(13) { | |
| transform: translate(-50%, -50%) rotateZ(202.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(14) { | |
| transform: translate(-50%, -50%) rotateZ(247.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(15) { | |
| transform: translate(-50%, -50%) rotateZ(292.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| .bloom-petal:nth-child(16) { | |
| transform: translate(-50%, -50%) rotateZ(337.5deg) rotateX(60deg) translateY(-8px) scale(0.85); | |
| } | |
| /* === Agent 思考中:3D 立體綻放(16片蓮花瓣依序張開)=== */ | |
| .voice-mic-container.thinking .bloom-petal { | |
| opacity: 1; | |
| } | |
| /* 外層 8 片大花瓣(rotateX 回到 0deg = 完全平展,繼續緊縮)*/ | |
| .voice-mic-container.thinking .bloom-petal:nth-child(1) { | |
| --rotate: 0deg; | |
| transform: translate(-50%, -50%) rotateZ(0deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 0s; | |
| z-index: 10; /* 0deg = 正前方 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(2) { | |
| --rotate: 45deg; | |
| transform: translate(-50%, -50%) rotateZ(45deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 0.2s; | |
| z-index: 8; /* 45deg = 略偏右 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(3) { | |
| --rotate: 90deg; | |
| transform: translate(-50%, -50%) rotateZ(90deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 0.4s; | |
| z-index: 6; /* 90deg = 右側面 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(4) { | |
| --rotate: 135deg; | |
| transform: translate(-50%, -50%) rotateZ(135deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 0.6s; | |
| z-index: 4; /* 135deg = 右後方 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(5) { | |
| --rotate: 180deg; | |
| transform: translate(-50%, -50%) rotateZ(180deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 0.8s; | |
| z-index: 2; /* 180deg = 正後方(最深層)*/ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(6) { | |
| --rotate: 225deg; | |
| transform: translate(-50%, -50%) rotateZ(225deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 1.0s; | |
| z-index: 4; /* 225deg = 左後方 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(7) { | |
| --rotate: 270deg; | |
| transform: translate(-50%, -50%) rotateZ(270deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 1.2s; | |
| z-index: 6; /* 270deg = 左側面 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(8) { | |
| --rotate: 315deg; | |
| transform: translate(-50%, -50%) rotateZ(315deg) rotateX(0deg) translateY(-15px) scale(0.6); | |
| animation: petalBloom 1.6s ease-in-out infinite 1.4s; | |
| z-index: 8; /* 315deg = 略偏左 */ | |
| } | |
| /* 內層 8 片小花瓣(覆蓋花蕊上方,z-index 略高於對應外層)*/ | |
| .voice-mic-container.thinking .bloom-petal:nth-child(9) { | |
| --rotate-inner: 22.5deg; | |
| transform: translate(-50%, -50%) rotateZ(22.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 0.1s; | |
| z-index: 11; /* 22.5deg = 前方偏右 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(10) { | |
| --rotate-inner: 67.5deg; | |
| transform: translate(-50%, -50%) rotateZ(67.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 0.3s; | |
| z-index: 9; /* 67.5deg = 右前方 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(11) { | |
| --rotate-inner: 112.5deg; | |
| transform: translate(-50%, -50%) rotateZ(112.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 0.5s; | |
| z-index: 7; /* 112.5deg = 右側偏後 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(12) { | |
| --rotate-inner: 157.5deg; | |
| transform: translate(-50%, -50%) rotateZ(157.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 0.7s; | |
| z-index: 5; /* 157.5deg = 後方偏右 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(13) { | |
| --rotate-inner: 202.5deg; | |
| transform: translate(-50%, -50%) rotateZ(202.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 0.9s; | |
| z-index: 3; /* 202.5deg = 後方偏左 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(14) { | |
| --rotate-inner: 247.5deg; | |
| transform: translate(-50%, -50%) rotateZ(247.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 1.1s; | |
| z-index: 5; /* 247.5deg = 左側偏後 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(15) { | |
| --rotate-inner: 292.5deg; | |
| transform: translate(-50%, -50%) rotateZ(292.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 1.3s; | |
| z-index: 7; /* 292.5deg = 左前方 */ | |
| } | |
| .voice-mic-container.thinking .bloom-petal:nth-child(16) { | |
| --rotate-inner: 337.5deg; | |
| transform: translate(-50%, -50%) rotateZ(337.5deg) rotateX(15deg) translateY(-5px) scale(0.5); | |
| animation: petalBloomInner 1.6s ease-in-out infinite 1.5s; | |
| z-index: 9; /* 337.5deg = 前方偏左 */ | |
| } | |
| /* 外層花瓣動畫(加入微妙旋轉)*/ | |
| @keyframes petalBloom { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate) + 0deg)) translateY(-30px) scale(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate) + 1.5deg)) translateY(-33px) scale(1.04); | |
| } | |
| } | |
| /* 內層花瓣動畫(較短的移動距離 + 反向微轉)*/ | |
| @keyframes petalBloomInner { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate-inner) + 0deg)) translateY(-15px) scale(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate-inner) - 1deg)) translateY(-18px) scale(1.03); | |
| } | |
| } | |
| .voice-mic-container.thinking .bloom-petal::before { | |
| box-shadow: | |
| 0 6px 24px rgba(0, 0, 0, 0.16), | |
| inset -2px 3px 8px rgba(0, 0, 0, 0.08), | |
| inset 2px -3px 10px rgba(255, 255, 255, 0.95); | |
| } | |
| /* === 斷線/重連:逆時針綻放紅色警示(16片花瓣)=== */ | |
| .voice-mic-container.disconnected .bloom-petal { | |
| opacity: 1; | |
| } | |
| /* 外層 8 片大花瓣(逆時針) */ | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(1) { | |
| --rotate-disc: 0deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 0s; | |
| z-index: 10; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(2) { | |
| --rotate-disc: 45deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 0.2s; | |
| z-index: 8; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(3) { | |
| --rotate-disc: 90deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 0.4s; | |
| z-index: 6; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(4) { | |
| --rotate-disc: 135deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 0.6s; | |
| z-index: 4; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(5) { | |
| --rotate-disc: 180deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 0.8s; | |
| z-index: 2; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(6) { | |
| --rotate-disc: 225deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 1.0s; | |
| z-index: 4; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(7) { | |
| --rotate-disc: 270deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 1.2s; | |
| z-index: 6; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(8) { | |
| --rotate-disc: 315deg; | |
| animation: petalDisconnect 1.6s ease-in-out infinite 1.4s; | |
| z-index: 8; | |
| } | |
| /* 內層 8 片小花瓣(逆時針,錯開 22.5°) */ | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(9) { | |
| --rotate-disc-inner: 22.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 0.1s; | |
| z-index: 11; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(10) { | |
| --rotate-disc-inner: 67.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 0.3s; | |
| z-index: 9; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(11) { | |
| --rotate-disc-inner: 112.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 0.5s; | |
| z-index: 7; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(12) { | |
| --rotate-disc-inner: 157.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 0.7s; | |
| z-index: 5; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(13) { | |
| --rotate-disc-inner: 202.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 0.9s; | |
| z-index: 3; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(14) { | |
| --rotate-disc-inner: 247.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 1.1s; | |
| z-index: 5; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(15) { | |
| --rotate-disc-inner: 292.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 1.3s; | |
| z-index: 7; | |
| } | |
| .voice-mic-container.disconnected .bloom-petal:nth-child(16) { | |
| --rotate-disc-inner: 337.5deg; | |
| animation: petalDisconnectInner 1.6s ease-in-out infinite 1.5s; | |
| z-index: 9; | |
| } | |
| /* 外層花瓣斷線動畫(加入逆時針旋轉效果)*/ | |
| @keyframes petalDisconnect { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate-disc) + 0deg)) translateY(-30px) scale(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate-disc) - 3deg)) translateY(-33px) scale(1.04); | |
| } | |
| } | |
| /* 內層花瓣斷線動畫(加入逆時針旋轉效果)*/ | |
| @keyframes petalDisconnectInner { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate-disc-inner) + 0deg)) translateY(-15px) scale(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) rotateZ(calc(var(--rotate-disc-inner) - 2deg)) translateY(-18px) scale(1.03); | |
| } | |
| } | |
| .voice-mic-container.disconnected .bloom-petal::before { | |
| background: linear-gradient(180deg, | |
| #FEE2E2 0%, | |
| #FCA5A5 30%, | |
| #F87171 70%, | |
| #EF4444 100% | |
| ); | |
| box-shadow: | |
| 0 6px 24px rgba(239, 68, 68, 0.5), | |
| inset -2px 2px 6px rgba(239, 68, 68, 0.35), | |
| inset 2px -2px 6px rgba(255, 255, 255, 0.5); | |
| } | |
| .voice-mic-container.disconnected .bloom-core { | |
| background: linear-gradient(180deg, #FEE2E2 0%, #FCA5A5 50%, #EF4444 100%); | |
| border-color: #EF4444; | |
| box-shadow: | |
| 0 4px 20px rgba(239, 68, 68, 0.4), | |
| inset 0 1px 2px rgba(255, 255, 255, 0.3); | |
| } | |
| /* 滑鼠懸停:輕微放大 */ | |
| .voice-mic-container:hover .bloom-core { | |
| transform: scale(1.08); | |
| box-shadow: | |
| 0 4px 20px rgba(0, 0, 0, 0.15), | |
| inset 0 1px 3px rgba(255, 255, 255, 0.6); | |
| } | |
| /* 錄音中狀態:核心脈衝(花蕊變紅,花瓣保持閉合但有微動效果)*/ | |
| .voice-mic-container.recording .bloom-core { | |
| background: radial-gradient(circle at 35% 35%, | |
| #FEE2E2 0%, | |
| #FCA5A5 25%, | |
| #F87171 55%, | |
| #EF4444 85%, | |
| #DC2626 100% | |
| ); | |
| border-color: #EF4444; | |
| animation: recordingCoreFlash 1.5s ease-in-out infinite; | |
| } | |
| /* 錄音時花瓣保持球形閉合(與待機狀態相同),但加入微光呼吸效果 */ | |
| .voice-mic-container.recording .bloom-petal { | |
| animation: recordingPetalGlow 1.8s ease-in-out infinite; | |
| } | |
| @keyframes recordingCoreFlash { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) scale(1); | |
| box-shadow: | |
| 0 4px 20px rgba(239, 68, 68, 0.4), | |
| inset 0 1px 2px rgba(255, 255, 255, 0.3); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(1.15); | |
| box-shadow: | |
| 0 6px 30px rgba(239, 68, 68, 0.6), | |
| inset 0 1px 3px rgba(255, 255, 255, 0.4); | |
| } | |
| } | |
| /* 花瓣微光呼吸(僅改變透明度和亮度,不改變形狀)*/ | |
| @keyframes recordingPetalGlow { | |
| 0%, 100% { | |
| opacity: 0.85; | |
| filter: brightness(1); | |
| } | |
| 50% { | |
| opacity: 1; | |
| filter: brightness(1.08); | |
| } | |
| } | |
| /* === Agent 回覆中:花瓣完全綻放(蓮花盛開狀態)+ 柔和呼吸波浪 === */ | |
| .voice-mic-container.speaking .bloom-petal { | |
| opacity: 1; | |
| } | |
| /* 外層 8 片大花瓣:完全平展向外(rotateX 0deg = 平放)*/ | |
| .voice-mic-container.speaking .bloom-petal:nth-child(-n+8) { | |
| animation: petalSpeakingOuter 3s ease-in-out infinite; | |
| } | |
| /* 內層 8 片小花瓣:稍微抬起(rotateX 10deg),形成層次感 */ | |
| .voice-mic-container.speaking .bloom-petal:nth-child(n+9) { | |
| animation: petalSpeakingInner 3s ease-in-out infinite 0.3s; | |
| } | |
| /* 外層花瓣動畫:完全綻放 + 輕微呼吸(內縮版本)*/ | |
| @keyframes petalSpeakingOuter { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) rotateZ(var(--speaking-angle, 0deg)) rotateX(0deg) translateY(-30px) scale(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) rotateZ(var(--speaking-angle, 0deg)) rotateX(0deg) translateY(-33px) scale(1.03); | |
| } | |
| } | |
| /* 內層花瓣動畫:略微抬起 + 反向呼吸 */ | |
| @keyframes petalSpeakingInner { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) rotateZ(var(--speaking-angle-inner, 0deg)) rotateX(10deg) translateY(-28px) scale(1.05); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) rotateZ(var(--speaking-angle-inner, 0deg)) rotateX(10deg) translateY(-25px) scale(1.02); | |
| } | |
| } | |
| /* 定義每片花瓣的角度(外層)*/ | |
| .voice-mic-container.speaking .bloom-petal:nth-child(1) { --speaking-angle: 0deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(2) { --speaking-angle: 45deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(3) { --speaking-angle: 90deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(4) { --speaking-angle: 135deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(5) { --speaking-angle: 180deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(6) { --speaking-angle: 225deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(7) { --speaking-angle: 270deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(8) { --speaking-angle: 315deg; } | |
| /* 定義每片花瓣的角度(內層)*/ | |
| .voice-mic-container.speaking .bloom-petal:nth-child(9) { --speaking-angle-inner: 22.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(10) { --speaking-angle-inner: 67.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(11) { --speaking-angle-inner: 112.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(12) { --speaking-angle-inner: 157.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(13) { --speaking-angle-inner: 202.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(14) { --speaking-angle-inner: 247.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(15) { --speaking-angle-inner: 292.5deg; } | |
| .voice-mic-container.speaking .bloom-petal:nth-child(16) { --speaking-angle-inner: 337.5deg; } | |
| .voice-mic-container.speaking .bloom-petal::before { | |
| box-shadow: | |
| 0 8px 32px rgba(0, 0, 0, 0.18), | |
| inset -2px 3px 8px rgba(0, 0, 0, 0.1), | |
| inset 2px -3px 12px rgba(255, 255, 255, 0.95); | |
| } | |
| /* 花蕊在回覆時保持金黃色,但有呼吸效果 */ | |
| .voice-mic-container.speaking .bloom-core { | |
| animation: speakingCoreBreath 2.4s ease-in-out infinite; | |
| } | |
| @keyframes speakingCoreBreath { | |
| 0%, 100% { | |
| transform: translate(-50%, -50%) scale(1); | |
| } | |
| 50% { | |
| transform: translate(-50%, -50%) scale(1.08); | |
| } | |
| } | |
| /* === Agent 文字輸出區域(打字機效果)=== */ | |
| .voice-agent-output { | |
| max-width: 600px; | |
| padding: 20px 28px; | |
| background: rgba(255, 255, 255, 0.98); | |
| border: 1px solid rgba(0, 0, 0, 0.1); | |
| border-radius: 14px; | |
| backdrop-filter: blur(20px) saturate(180%); | |
| font-size: 16px; | |
| font-weight: 400; | |
| color: #1A1A1A; | |
| text-align: left; | |
| line-height: 1.7; | |
| box-shadow: 0 6px 28px rgba(0, 0, 0, 0.1); | |
| font-family: 'Inter', -apple-system, sans-serif; | |
| display: none; /* 預設隱藏 */ | |
| opacity: 0; | |
| transform: translateY(10px); | |
| transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| /* 限制兩行高度 + 滾動 */ | |
| max-height: calc(1.7em * 2 + 40px); /* 兩行文字 + padding */ | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| .voice-agent-output.active { | |
| display: block; | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| /* 自訂滾動條樣式 */ | |
| .voice-agent-output::-webkit-scrollbar { | |
| width: 4px; | |
| } | |
| .voice-agent-output::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .voice-agent-output::-webkit-scrollbar-thumb { | |
| background: rgba(0, 0, 0, 0.15); | |
| border-radius: 2px; | |
| } | |
| /* 打字游標效果 */ | |
| .voice-agent-output::after { | |
| content: '▋'; | |
| animation: typingCursor 0.8s steps(2) infinite; | |
| margin-left: 2px; | |
| color: rgba(0, 0, 0, 0.6); | |
| } | |
| .voice-agent-output.typing-done::after { | |
| display: none; /* 打字完成後隱藏游標 */ | |
| } | |
| @keyframes typingCursor { | |
| 0%, 50% { opacity: 1; } | |
| 51%, 100% { opacity: 0; } | |
| } | |
| /* === 實時字幕 / 文字輸入框 === */ | |
| .voice-transcript { | |
| max-width: 700px; | |
| padding: 24px 32px; | |
| background: rgba(255, 255, 255, 0.95); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 16px; | |
| backdrop-filter: blur(20px) saturate(180%); | |
| font-size: 20px; | |
| font-weight: 400; | |
| color: #1A1A1A; | |
| text-align: center; | |
| line-height: 1.6; | |
| min-height: 60px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s; | |
| box-shadow: 0 4px 24px rgba(0, 0, 0, 0.08); | |
| } | |
| .voice-transcript.provisional { | |
| color: rgba(0, 0, 0, 0.4); | |
| font-style: italic; | |
| /* 打字狀態:限制一行 */ | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| max-height: calc(1.6em + 48px); /* 一行文字 + padding */ | |
| } | |
| .voice-transcript.final { | |
| color: #1A1A1A; | |
| border-color: rgba(0, 0, 0, 0.12); | |
| box-shadow: 0 6px 32px rgba(0, 0, 0, 0.12); | |
| } | |
| /* 文字輸入模式 */ | |
| .voice-transcript.text-input-mode { | |
| padding: 0; | |
| min-height: 100px; | |
| max-height: 200px; | |
| overflow: hidden; | |
| } | |
| .voice-transcript.text-input-mode textarea { | |
| width: 100%; | |
| height: 100%; | |
| min-height: 100px; | |
| padding: 24px 32px; | |
| background: transparent; | |
| border: none; | |
| outline: none; | |
| font-size: 18px; | |
| font-weight: 400; | |
| color: #1A1A1A; | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif; | |
| resize: vertical; | |
| line-height: 1.6; | |
| } | |
| .voice-transcript.text-input-mode textarea::placeholder { | |
| color: rgba(0, 0, 0, 0.4); | |
| font-style: italic; | |
| } | |
| /* === 工具卡片 === */ | |
| .voice-tool-card { | |
| position: absolute; | |
| padding: 20px 24px; | |
| background: rgba(255, 255, 255, 0.98); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 16px; | |
| backdrop-filter: blur(30px) saturate(180%); | |
| min-width: 280px; | |
| max-width: 360px; | |
| z-index: 40; | |
| box-shadow: 0 8px 32px rgba(0, 0, 0, 0.12); | |
| animation: cardEnter 0.5s cubic-bezier(0.34, 1.56, 0.64, 1) forwards; | |
| } | |
| @keyframes cardEnter { | |
| from { | |
| opacity: 0; | |
| transform: translateY(20px) scale(0.95); | |
| } | |
| to { | |
| opacity: 1; | |
| transform: translateY(0) scale(1); | |
| } | |
| } | |
| .voice-tool-card.exiting { | |
| animation: cardExit 0.3s ease-out forwards; | |
| } | |
| @keyframes cardExit { | |
| from { | |
| opacity: 1; | |
| transform: scale(1); | |
| } | |
| to { | |
| opacity: 0; | |
| transform: scale(0.95); | |
| } | |
| } | |
| /* 定位變體 */ | |
| .voice-tool-card.pos-top-right { | |
| top: 100px; | |
| right: 100px; | |
| } | |
| .voice-tool-card.pos-top-left { | |
| top: 100px; | |
| left: 100px; | |
| } | |
| .voice-tool-card.pos-bottom-right { | |
| bottom: 100px; | |
| right: 100px; | |
| } | |
| .voice-tool-card.pos-bottom-left { | |
| bottom: 100px; | |
| left: 100px; | |
| } | |
| .voice-tool-card .card-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| margin-bottom: 16px; | |
| padding-bottom: 16px; | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.06); | |
| } | |
| .voice-tool-card .card-icon { | |
| font-size: 28px; | |
| width: 48px; | |
| height: 48px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: #F5F5F5; | |
| border-radius: 12px; | |
| } | |
| .voice-tool-card h3 { | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: #1A1A1A; | |
| letter-spacing: -0.3px; | |
| } | |
| .voice-tool-card .card-content { | |
| color: rgba(0, 0, 0, 0.7); | |
| } | |
| .voice-tool-card .data-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 10px 0; | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.04); | |
| } | |
| .voice-tool-card .data-row:last-child { | |
| border-bottom: none; | |
| } | |
| .voice-tool-card .data-label { | |
| color: rgba(0, 0, 0, 0.5); | |
| font-size: 13px; | |
| font-weight: 500; | |
| } | |
| .voice-tool-card .data-value { | |
| color: #1A1A1A; | |
| font-size: 15px; | |
| font-weight: 600; | |
| font-family: 'JetBrains Mono', monospace; | |
| } | |
| /* ChatWindow 折疊圖標 */ | |
| .chat-icon { | |
| position: fixed; | |
| /* iframe 環境適配:使用固定值避免 viewport 計算問題 */ | |
| bottom: 40px; | |
| left: 40px; | |
| z-index: 101; | |
| width: 56px; | |
| height: 56px; | |
| background: rgba(255, 255, 255, 0.95); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| backdrop-filter: blur(20px); | |
| box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| font-size: 20px; | |
| } | |
| .chat-icon:hover { | |
| transform: scale(1.1); | |
| background: #FFFFFF; | |
| box-shadow: 0 6px 24px rgba(0, 0, 0, 0.15); | |
| } | |
| /* 退出按鈕 */ | |
| .exit-button { | |
| position: absolute; | |
| /* iframe 環境適配:使用固定值 */ | |
| top: 40px; | |
| right: 40px; | |
| z-index: 50; | |
| padding: 12px 20px; | |
| background: rgba(255, 255, 255, 0.95); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 10px; | |
| color: rgba(0, 0, 0, 0.6); | |
| font-family: 'Inter', sans-serif; | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.2s; | |
| backdrop-filter: blur(10px); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |
| } | |
| .exit-button:hover { | |
| background: #FFFFFF; | |
| color: #1A1A1A; | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 16px rgba(0, 0, 0, 0.1); | |
| } | |
| /* 情緒標籤 */ | |
| .emotion-indicator { | |
| position: absolute; | |
| /* iframe 環境適配:使用固定值 */ | |
| top: 40px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 50; | |
| padding: 10px 20px; | |
| background: rgba(255, 255, 255, 0.95); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-radius: 20px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| color: rgba(0, 0, 0, 0.7); | |
| backdrop-filter: blur(20px); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.06); | |
| } | |
| /* 狀態指示器 */ | |
| .status-badge { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| padding: 6px 12px; | |
| background: rgba(34, 197, 94, 0.1); | |
| border: 1px solid rgba(34, 197, 94, 0.2); | |
| border-radius: 8px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: #16A34A; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| margin-top: 12px; | |
| } | |
| .status-badge::before { | |
| content: ''; | |
| width: 6px; | |
| height: 6px; | |
| background: #16A34A; | |
| border-radius: 50%; | |
| animation: blink 2s ease-in-out infinite; | |
| } | |
| @keyframes blink { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.3; } | |
| } | |
| /* === 登入覆蓋層(蓮花風格)=== */ | |
| .login-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| width: 100vw; | |
| height: 100vh; | |
| background: linear-gradient(135deg, #E6F7F0 0%, #F5F1ED 100%); | |
| z-index: 9999; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: opacity 0.6s ease, visibility 0.6s ease; | |
| } | |
| .login-overlay.hidden { | |
| opacity: 0; | |
| visibility: hidden; | |
| pointer-events: none; | |
| } | |
| .login-container { | |
| text-align: center; | |
| max-width: 400px; | |
| padding: 48px; | |
| background: rgba(255, 255, 255, 0.95); | |
| border-radius: 32px; | |
| backdrop-filter: blur(20px) saturate(180%); | |
| box-shadow: 0 8px 48px rgba(0, 0, 0, 0.12); | |
| } | |
| .login-lotus { | |
| width: 120px; | |
| height: 120px; | |
| margin: 0 auto 32px; | |
| position: relative; | |
| } | |
| .login-lotus::before { | |
| content: '🪷'; | |
| font-size: 120px; | |
| line-height: 1; | |
| animation: float 3s ease-in-out infinite; | |
| } | |
| @keyframes float { | |
| 0%, 100% { transform: translateY(0px); } | |
| 50% { transform: translateY(-10px); } | |
| } | |
| .login-title { | |
| font-size: 28px; | |
| font-weight: 700; | |
| color: #1A1A1A; | |
| margin-bottom: 12px; | |
| letter-spacing: -0.5px; | |
| } | |
| .login-subtitle { | |
| font-size: 15px; | |
| color: rgba(0, 0, 0, 0.5); | |
| margin-bottom: 40px; | |
| line-height: 1.6; | |
| } | |
| .login-button { | |
| display: inline-flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 12px; | |
| padding: 16px 32px; | |
| background: #FFFFFF; | |
| border: 1px solid rgba(0, 0, 0, 0.12); | |
| border-radius: 16px; | |
| color: #1A1A1A; | |
| font-family: 'Inter', sans-serif; | |
| font-size: 15px; | |
| font-weight: 600; | |
| cursor: pointer; | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| } | |
| .login-button:hover { | |
| background: #F5F5F5; | |
| border-color: rgba(0, 0, 0, 0.18); | |
| transform: translateY(-2px); | |
| box-shadow: 0 4px 16px rgba(0, 0, 0, 0.12); | |
| } | |
| .login-button:active { | |
| transform: translateY(0); | |
| box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08); | |
| } | |
| .login-button img { | |
| width: 20px; | |
| height: 20px; | |
| } | |
| /* === 工具抽屜(手機端右側拉出面板)=== */ | |
| .tool-drawer-toggle { | |
| position: fixed; | |
| right: 0; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| z-index: 200; | |
| width: 24px; | |
| height: 60px; | |
| background: rgba(255, 255, 255, 0.6); | |
| border: 1px solid rgba(0, 0, 0, 0.08); | |
| border-right: none; | |
| border-radius: 12px 0 0 12px; | |
| display: none; /* 預設隱藏,有工具結果時才顯示 */ | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| backdrop-filter: blur(10px); | |
| box-shadow: -2px 0 8px rgba(0, 0, 0, 0.06); | |
| transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); | |
| } | |
| .tool-drawer-toggle.visible { | |
| display: flex; | |
| } | |
| .tool-drawer-toggle:hover { | |
| background: rgba(255, 255, 255, 0.9); | |
| width: 28px; | |
| } | |
| .tool-drawer-toggle::before { | |
| content: '‹'; | |
| font-size: 18px; | |
| color: rgba(0, 0, 0, 0.5); | |
| transition: transform 0.3s; | |
| } | |
| .tool-drawer-toggle.open::before { | |
| transform: rotate(180deg); | |
| } | |
| /* 工具抽屜面板 */ | |
| .tool-drawer { | |
| position: fixed; | |
| right: -320px; | |
| top: 0; | |
| width: 320px; | |
| height: 100%; | |
| height: 100dvh; | |
| background: rgba(255, 255, 255, 0.98); | |
| border-left: 1px solid rgba(0, 0, 0, 0.08); | |
| z-index: 199; | |
| backdrop-filter: blur(20px) saturate(180%); | |
| box-shadow: -4px 0 24px rgba(0, 0, 0, 0.1); | |
| transition: right 0.4s cubic-bezier(0.4, 0, 0.2, 1); | |
| display: flex; | |
| flex-direction: column; | |
| overflow: hidden; | |
| } | |
| .tool-drawer.open { | |
| right: 0; | |
| } | |
| .tool-drawer-header { | |
| padding: 20px; | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.06); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| flex-shrink: 0; | |
| } | |
| .tool-drawer-header h3 { | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: #1A1A1A; | |
| margin: 0; | |
| } | |
| .tool-drawer-close { | |
| width: 32px; | |
| height: 32px; | |
| border: none; | |
| background: rgba(0, 0, 0, 0.05); | |
| border-radius: 8px; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 16px; | |
| color: rgba(0, 0, 0, 0.5); | |
| transition: all 0.2s; | |
| } | |
| .tool-drawer-close:hover { | |
| background: rgba(0, 0, 0, 0.1); | |
| color: rgba(0, 0, 0, 0.8); | |
| } | |
| .tool-drawer-content { | |
| flex: 1; | |
| overflow-y: auto; | |
| overflow-x: hidden; | |
| padding: 16px; | |
| -webkit-overflow-scrolling: touch; | |
| } | |
| /* 抽屜內的工具卡片樣式覆寫 */ | |
| .tool-drawer-content .voice-tool-card { | |
| position: relative; | |
| top: auto; | |
| left: auto; | |
| right: auto; | |
| bottom: auto; | |
| width: 100%; | |
| max-width: none; | |
| min-width: auto; | |
| margin-bottom: 16px; | |
| animation: none; | |
| } | |
| /* 抽屜內滾動條樣式 */ | |
| .tool-drawer-content::-webkit-scrollbar { | |
| width: 4px; | |
| } | |
| .tool-drawer-content::-webkit-scrollbar-track { | |
| background: transparent; | |
| } | |
| .tool-drawer-content::-webkit-scrollbar-thumb { | |
| background: rgba(0, 0, 0, 0.15); | |
| border-radius: 2px; | |
| } | |
| /* 抽屜遮罩層 */ | |
| .tool-drawer-overlay { | |
| position: fixed; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| bottom: 0; | |
| background: rgba(0, 0, 0, 0.3); | |
| z-index: 198; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: all 0.3s; | |
| } | |
| .tool-drawer-overlay.visible { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| /* === 響應式設計:根據裝置類型切換工具卡片顯示方式 === */ | |
| /* ========== 響應式設計:設備檢測與適配 ========== */ | |
| /* === 1. 手機模式(寬度 <= 480px)=== */ | |
| @media (max-width: 480px) { | |
| #tool-cards-container { display: none ; } | |
| .tool-drawer-toggle.visible { display: flex; } | |
| .tool-drawer { display: flex; } | |
| /* 花朵縮小 */ | |
| .voice-waveform-container { width: 280px; height: 280px; } | |
| .voice-mic-container { width: 180px; height: 180px; } | |
| .bloom-core { | |
| width: 28px; | |
| height: 28px; | |
| --core-scale: 0.8; /* 28/35 = 0.8 */ | |
| } | |
| .bloom-petal:nth-child(-n+8) { width: 50px; height: 80px; } | |
| .bloom-petal:nth-child(n+9) { width: 38px; height: 65px; } | |
| /* 文字輸出區域 */ | |
| .voice-agent-output { max-width: 90vw; font-size: 14px; padding: 16px 20px; } | |
| } | |
| /* === 2. 平板模式(481px - 1024px)=== */ | |
| @media (min-width: 481px) and (max-width: 1024px) { | |
| #tool-cards-container { display: none ; } | |
| .tool-drawer-toggle.visible { display: flex; } | |
| .tool-drawer { display: flex; } | |
| /* 花朵適中 */ | |
| .voice-waveform-container { width: 350px; height: 350px; } | |
| .voice-mic-container { width: 210px; height: 210px; } | |
| } | |
| /* === 3. 電腦模式(寬度 > 1024px 且非直立螢幕)=== */ | |
| @media (min-width: 1025px) and (orientation: landscape) { | |
| #tool-cards-container { display: block; } | |
| .tool-drawer-toggle { display: none ; } | |
| .tool-drawer { display: none ; } | |
| .tool-drawer-overlay { display: none ; } | |
| } | |
| /* === 4. 90度橫屏/直立螢幕模式(寬度 < 高度,無論實際寬度)=== */ | |
| /* 這適用於外接顯示器 90 度旋轉的情況 */ | |
| @media (orientation: portrait) and (min-width: 600px) { | |
| /* 使用抽屜模式 */ | |
| #tool-cards-container { display: none ; } | |
| .tool-drawer-toggle.visible { display: flex; } | |
| .tool-drawer { display: flex; } | |
| /* 花朵響應式放大(根據視窗高度)*/ | |
| .voice-waveform-container { | |
| width: min(50vh, 500px); | |
| height: min(50vh, 500px); | |
| } | |
| .voice-mic-container { | |
| width: min(30vh, 300px); | |
| height: min(30vh, 300px); | |
| } | |
| .bloom-core { | |
| width: min(5vh, 45px); | |
| height: min(5vh, 45px); | |
| --core-scale: 1.29; /* 45/35 = 1.29 (max scale) */ | |
| } | |
| .bloom-petal:nth-child(-n+8) { | |
| width: min(8vh, 80px); | |
| height: min(13vh, 130px); | |
| } | |
| .bloom-petal:nth-child(n+9) { | |
| width: min(6vh, 60px); | |
| height: min(10vh, 100px); | |
| } | |
| /* 文字輸出區域適配 */ | |
| .voice-agent-output { | |
| max-width: 80vw; | |
| font-size: clamp(14px, 2vh, 18px); | |
| } | |
| } | |
| /* === 5. 超寬螢幕(寬度 > 1920px)=== */ | |
| @media (min-width: 1921px) { | |
| .voice-waveform-container { width: 500px; height: 500px; } | |
| .voice-mic-container { width: 300px; height: 300px; } | |
| .bloom-core { | |
| width: 45px; | |
| height: 45px; | |
| --core-scale: 1.29; /* 45/35 = 1.29 */ | |
| } | |
| .bloom-petal:nth-child(-n+8) { width: 80px; height: 130px; } | |
| .bloom-petal:nth-child(n+9) { width: 60px; height: 100px; } | |
| } | |
| /* === 6. 強制直立模式(JS 動態添加 class)=== */ | |
| body.portrait-mode #tool-cards-container { display: none ; } | |
| body.portrait-mode .tool-drawer-toggle.visible { display: flex; } | |
| body.portrait-mode .tool-drawer { display: flex; } | |
| body.portrait-mode .voice-waveform-container { | |
| width: min(50vh, 500px); | |
| height: min(50vh, 500px); | |
| } | |
| body.portrait-mode .voice-mic-container { | |
| width: min(30vh, 300px); | |
| height: min(30vh, 300px); | |
| } | |
| body.portrait-mode .bloom-core { | |
| --core-scale: 1.29; /* 與直立螢幕模式一致 */ | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="control-panel"> | |
| <h3>🎛️ 控制面板</h3> | |
| <div class="control-group"> | |
| <label>情緒切換</label> | |
| <select id="emotion-select"> | |
| <option value="neutral">😐 中性 (預設)</option> | |
| <option value="happy">😊 開心</option> | |
| <option value="sad">😢 悲傷</option> | |
| <option value="angry">😡 生氣</option> | |
| <option value="fear">😨 恐懼</option> | |
| <option value="surprise">😲 驚訝</option> | |
| </select> | |
| </div> | |
| <div class="control-group"> | |
| <label>字幕測試</label> | |
| <button class="btn" id="transcript-provisional">顯示臨時字幕</button> | |
| <button class="btn" id="transcript-final">顯示最終字幕</button> | |
| </div> | |
| <div class="control-group"> | |
| <label>模擬工具調用</label> | |
| <button class="btn" id="simulate-weather">🌤️ 查詢天氣</button> | |
| <button class="btn" id="simulate-news">📰 查詢新聞</button> | |
| <button class="btn" id="simulate-health">❤️ 查詢健康</button> | |
| </div> | |
| <div class="control-group"> | |
| <label>模擬下次輸入</label> | |
| <button class="btn" id="simulate-next-input">🎤 開始新輸入(清除卡片)</button> | |
| </div> | |
| <div class="control-group"> | |
| <label>Agent 狀態</label> | |
| <button class="btn" id="toggle-recording">切換錄音狀態</button> | |
| <button class="btn" id="toggle-thinking">切換思考狀態(順時針)</button> | |
| <button class="btn" id="toggle-speaking">切換回覆狀態(打字機輸出)</button> | |
| <button class="btn" id="toggle-disconnected">切換斷線狀態(逆時針)</button> | |
| </div> | |
| <div class="control-group"> | |
| <label>💡 喚醒詞可行性評估</label> | |
| <div style="font-size: 11px; line-height: 1.6; color: rgba(0,0,0,0.6); padding: 12px; background: rgba(0,0,0,0.02); border-radius: 8px; border: 1px solid rgba(0,0,0,0.06);"> | |
| <strong style="color: rgba(0,0,0,0.8);">技術限制:</strong><br> | |
| • Web Speech API 不支援持續監聽<br> | |
| • 需要用戶點擊才能啟動麥克風<br> | |
| • 瀏覽器安全政策限制背景錄音<br><br> | |
| <strong style="color: rgba(0,0,0,0.8);">替代方案:</strong><br> | |
| • iOS App 可實現真正喚醒詞<br> | |
| • Web 端改用「按住說話」模式<br> | |
| • 或結合快捷鍵(Space/Ctrl+M) | |
| </div> | |
| </div> | |
| <div class="status-badge"> | |
| Interactive Preview | |
| </div> | |
| </div> | |
| <div class="voice-immersive-overlay"> | |
| <div class="voice-immersive-background emotion-neutral active" id="background"></div> | |
| <div class="emotion-indicator" id="emotion-indicator">當前情緒: 😐 中性</div> | |
| <button class="exit-button" id="logoutBtn">登出</button> | |
| <div class="chat-icon" id="chatIcon" title="切換對話視窗">💬</div> | |
| <div class="voice-center-container"> | |
| <div class="voice-waveform-container"> | |
| <canvas id="waveform-canvas" width="400" height="400"></canvas> | |
| <div class="voice-mic-container" id="mic-container"> | |
| <div class="bloom-core"></div> | |
| <div class="bloom-petals"> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| <div class="bloom-petal"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="voice-agent-output" id="agent-output"></div> | |
| <div class="voice-transcript provisional" id="transcript"> | |
| 請說話... | |
| </div> | |
| </div> | |
| <div id="tool-cards-container"></div> | |
| </div> | |
| <div class="tool-drawer-overlay" id="toolDrawerOverlay"></div> | |
| <div class="tool-drawer-toggle" id="toolDrawerToggle"></div> | |
| <div class="tool-drawer" id="toolDrawer"> | |
| <div class="tool-drawer-header"> | |
| <h3>📊 工具結果</h3> | |
| <button class="tool-drawer-close" id="toolDrawerClose">✕</button> | |
| </div> | |
| <div class="tool-drawer-content" id="toolDrawerContent"> | |
| </div> | |
| </div> | |
| <script src="js/config.js"></script> | |
| <script src="js/ui.js"></script> | |
| <script src="js/tts.js"></script> | |
| <script src="js/tools.js"></script> | |
| <script src="js/agent.js"></script> | |
| <script src="js/canvas.js"></script> | |
| <script src="js/location.js"></script> | |
| <script src="js/websocket.js"></script> | |
| <script src="js/app.js"></script> | |
| <script> | |
| (function() { | |
| function detectScreenOrientation() { | |
| const width = window.innerWidth; | |
| const height = window.innerHeight; | |
| const isPortrait = height > width; | |
| const isLargePortrait = isPortrait && width >= 600; | |
| document.body.classList.remove('mobile-mode', 'tablet-mode', 'desktop-mode', 'portrait-mode'); | |
| if (isLargePortrait) { | |
| document.body.classList.add('portrait-mode'); | |
| // console.log('📱 螢幕模式: 直立螢幕(90度橫屏)', width, 'x', height); | |
| } else if (width <= 480) { | |
| document.body.classList.add('mobile-mode'); | |
| // console.log('📱 螢幕模式: 手機', width, 'x', height); | |
| } else if (width <= 1024) { | |
| document.body.classList.add('tablet-mode'); | |
| // console.log('📱 螢幕模式: 平板', width, 'x', height); | |
| } else { | |
| document.body.classList.add('desktop-mode'); | |
| // console.log('🖥️ 螢幕模式: 桌機/筆電', width, 'x', height); | |
| } | |
| } | |
| detectScreenOrientation(); | |
| window.addEventListener('resize', detectScreenOrientation); | |
| if (screen.orientation) { | |
| screen.orientation.addEventListener('change', detectScreenOrientation); | |
| } | |
| })(); | |
| </script> | |
| </body> | |
| </html> | |