Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>SignApp - Speech to Sign Language</title> | |
| <meta name="description" content="Real-time speech to sign language avatar powered by AI"> | |
| <link rel="preconnect" href="https://fonts.googleapis.com"> | |
| <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet"> | |
| <script type="importmap"> | |
| { | |
| "imports": { | |
| "three": "https://unpkg.com/three@0.158.0/build/three.module.js", | |
| "three/addons/": "https://unpkg.com/three@0.158.0/examples/jsm/" | |
| } | |
| } | |
| </script> | |
| <style> | |
| :root { | |
| --bg-color-top: #0c0a1a; | |
| --bg-color-mid: #1a1333; | |
| --bg-color-bot: #0d1b2a; | |
| --text-main: #e2e8f0; | |
| --text-main-muted: #cbd5e1; | |
| --text-muted: #64748b; | |
| --topbar-bg: rgba(12,10,26,0.9); | |
| --bottom-panel-bg1: rgba(12,10,26,0.85); | |
| --bottom-panel-bg2: rgba(12,10,26,0.4); | |
| --chip-bg: rgba(255,255,255,0.06); | |
| --chip-border: rgba(255,255,255,0.08); | |
| --input-bg: rgba(255,255,255,0.05); | |
| --input-border: rgba(255,255,255,0.1); | |
| --input-focus: rgba(167,139,250,0.4); | |
| --accent-color: #a78bfa; | |
| --accent-hover: rgba(167,139,250,0.3); | |
| --btn-bg: rgba(167,139,250,0.2); | |
| --btn-border: rgba(167,139,250,0.3); | |
| --btn-text: #c4b5fd; | |
| --btn-hover-bg: rgba(167,139,250,0.3); | |
| --btn-hover-text: #fff; | |
| --btn-sec-bg: rgba(255,255,255,0.05); | |
| --btn-sec-border: rgba(255,255,255,0.1); | |
| --btn-sec-hover-bg: rgba(255,255,255,0.1); | |
| --btn-sec-hover-border: rgba(255,255,255,0.2); | |
| --btn-sec-text: #94a3b8; | |
| --btn-sec-hover-text: #fff; | |
| --status-dot-processing: #f59e0b; | |
| --gloss-token-bg: rgba(167,139,250,0.15); | |
| --gloss-token-border: rgba(167,139,250,0.25); | |
| --gloss-token-text: #c4b5fd; | |
| --gloss-token-active-bg: rgba(167,139,250,0.35); | |
| --gloss-token-active-border: #a78bfa; | |
| --gloss-token-active-text: #fff; | |
| } | |
| .light-mode { | |
| --bg-color-top: #f8fafc; | |
| --bg-color-mid: #e2e8f0; | |
| --bg-color-bot: #cbd5e1; | |
| --text-main: #1e293b; | |
| --text-main-muted: #334155; | |
| --text-muted: #475569; | |
| --topbar-bg: rgba(248, 250, 252, 0.9); | |
| --bottom-panel-bg1: rgba(248, 250, 252, 0.95); | |
| --bottom-panel-bg2: rgba(248, 250, 252, 0.6); | |
| --chip-bg: rgba(0,0,0,0.05); | |
| --chip-border: rgba(0,0,0,0.1); | |
| --input-bg: rgba(0,0,0,0.05); | |
| --input-border: rgba(0,0,0,0.1); | |
| --input-focus: rgba(124,58,237,0.4); | |
| --accent-color: #7c3aed; | |
| --accent-hover: rgba(124, 58, 237, 0.15); | |
| --btn-bg: rgba(124, 58, 237, 0.1); | |
| --btn-border: rgba(124, 58, 237, 0.2); | |
| --btn-text: #6d28d9; | |
| --btn-hover-bg: rgba(124, 58, 237, 0.2); | |
| --btn-hover-text: #1e293b; | |
| --btn-sec-bg: rgba(0,0,0,0.05); | |
| --btn-sec-border: rgba(0,0,0,0.1); | |
| --btn-sec-hover-bg: rgba(0,0,0,0.1); | |
| --btn-sec-hover-border: rgba(0,0,0,0.2); | |
| --btn-sec-text: #475569; | |
| --btn-sec-hover-text: #1e293b; | |
| --status-dot-processing: #d97706; | |
| --gloss-token-bg: rgba(124, 58, 237, 0.1); | |
| --gloss-token-border: rgba(124, 58, 237, 0.2); | |
| --gloss-token-text: #6d28d9; | |
| --gloss-token-active-bg: rgba(124, 58, 237, 0.25); | |
| --gloss-token-active-border: #7c3aed; | |
| --gloss-token-active-text: #1e293b; | |
| } | |
| *, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| font-family: 'Inter', sans-serif; | |
| background: linear-gradient(135deg, var(--bg-color-top) 0%, var(--bg-color-mid) 40%, var(--bg-color-bot) 100%); | |
| color: var(--text-main); | |
| overflow: hidden; | |
| height: 100vh; | |
| width: 100vw; | |
| } | |
| /* ββ 3D Canvas βββββββββββββββββββββββββββββββ */ | |
| #avatar-canvas { | |
| position: fixed; | |
| top: 0; left: 0; | |
| width: 100%; height: 100%; | |
| z-index: 0; | |
| } | |
| /* ββ Top Bar βββββββββββββββββββββββββββββββββ */ | |
| #top-bar { | |
| position: fixed; | |
| top: 0; left: 0; right: 0; | |
| z-index: 20; | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 16px 28px; | |
| background: linear-gradient(180deg, var(--topbar-bg) 0%, transparent 100%); | |
| } | |
| .logo { | |
| font-weight: 700; | |
| font-size: 20px; | |
| letter-spacing: -0.5px; | |
| background: linear-gradient(135deg, #a78bfa, #60a5fa); | |
| -webkit-background-clip: text; | |
| background-clip: text; | |
| -webkit-text-fill-color: transparent; | |
| } | |
| .status-chip { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| padding: 6px 14px; | |
| border-radius: 20px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| background: var(--chip-bg); | |
| border: 1px solid var(--chip-border); | |
| backdrop-filter: blur(10px); | |
| transition: all 0.3s ease; | |
| } | |
| .status-dot { | |
| width: 8px; height: 8px; | |
| border-radius: 50%; | |
| background: #4ade80; | |
| transition: background 0.3s; | |
| } | |
| .status-dot.recording { | |
| background: #ef4444; | |
| animation: pulse-dot 1s ease-in-out infinite; | |
| } | |
| .status-dot.processing { | |
| background: var(--status-dot-processing); | |
| animation: pulse-dot 0.6s ease-in-out infinite; | |
| } | |
| .status-dot.signing { | |
| background: var(--accent-color); | |
| animation: pulse-dot 1.2s ease-in-out infinite; | |
| } | |
| @keyframes pulse-dot { | |
| 0%, 100% { opacity: 1; transform: scale(1); } | |
| 50% { opacity: 0.5; transform: scale(1.3); } | |
| } | |
| /* ββ Bottom Panel ββββββββββββββββββββββββββββ */ | |
| #bottom-panel { | |
| position: fixed; | |
| bottom: 0; left: 0; right: 0; | |
| z-index: 20; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 16px; | |
| padding: 20px 28px 32px; | |
| background: linear-gradient(0deg, var(--bottom-panel-bg1) 0%, var(--bottom-panel-bg2) 70%, transparent 100%); | |
| pointer-events: none; | |
| } | |
| #bottom-panel > * { | |
| pointer-events: auto; | |
| } | |
| /* ββ Transcript Area βββββββββββββββββββββββββ */ | |
| #transcript-area { | |
| width: 100%; | |
| max-width: 640px; | |
| text-align: center; | |
| min-height: 50px; | |
| } | |
| .transcript-label { | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| letter-spacing: 1.5px; | |
| color: var(--text-muted); | |
| margin-bottom: 6px; | |
| } | |
| .transcript-text { | |
| font-size: 15px; | |
| color: var(--text-main-muted); | |
| line-height: 1.5; | |
| opacity: 0; | |
| transform: translateY(8px); | |
| transition: all 0.4s ease; | |
| } | |
| .transcript-text.visible { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| .gloss-tokens { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 6px; | |
| justify-content: center; | |
| margin-top: 8px; | |
| } | |
| .gloss-token { | |
| padding: 4px 10px; | |
| border-radius: 6px; | |
| font-size: 13px; | |
| font-weight: 600; | |
| font-family: 'Inter', monospace; | |
| background: var(--gloss-token-bg); | |
| border: 1px solid var(--gloss-token-border); | |
| color: var(--gloss-token-text); | |
| transition: all 0.3s ease; | |
| } | |
| .gloss-token.active { | |
| background: var(--gloss-token-active-bg); | |
| border-color: var(--gloss-token-active-border); | |
| color: #fff; | |
| transform: scale(1.08); | |
| box-shadow: 0 0 12px rgba(167,139,250,0.3); | |
| } | |
| .gloss-token.done { | |
| opacity: 0.4; | |
| } | |
| /* ββ Mic Button ββββββββββββββββββββββββββββββ */ | |
| #mic-btn { | |
| width: 72px; height: 72px; | |
| border-radius: 50%; | |
| border: none; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: linear-gradient(135deg, #7c3aed, #3b82f6); | |
| box-shadow: 0 4px 24px rgba(124,58,237,0.4), 0 0 0 0 rgba(124,58,237,0); | |
| transition: all 0.3s ease; | |
| position: relative; | |
| } | |
| #mic-btn:hover { | |
| transform: scale(1.06); | |
| box-shadow: 0 6px 32px rgba(124,58,237,0.5); | |
| } | |
| #mic-btn:active { transform: scale(0.97); } | |
| #mic-btn.recording { | |
| background: linear-gradient(135deg, #ef4444, #dc2626); | |
| box-shadow: 0 4px 24px rgba(239,68,68,0.4); | |
| animation: pulse-ring 1.5s ease-out infinite; | |
| } | |
| #mic-btn.processing { | |
| background: linear-gradient(135deg, #f59e0b, #d97706); | |
| pointer-events: none; | |
| animation: spin-slow 2s linear infinite; | |
| } | |
| @keyframes pulse-ring { | |
| 0% { box-shadow: 0 4px 24px rgba(239,68,68,0.4), 0 0 0 0 rgba(239,68,68,0.3); } | |
| 100% { box-shadow: 0 4px 24px rgba(239,68,68,0.4), 0 0 0 20px rgba(239,68,68,0); } | |
| } | |
| @keyframes spin-slow { | |
| from { transform: rotate(0deg); } | |
| to { transform: rotate(360deg); } | |
| } | |
| .mic-icon { | |
| width: 28px; height: 28px; | |
| fill: white; | |
| } | |
| /* ββ Audio Waveform Visualizer ββββββββββββββββ */ | |
| #waveform { | |
| width: 200px; | |
| height: 40px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 3px; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| #waveform.active { opacity: 1; } | |
| .wave-bar { | |
| width: 3px; | |
| height: 8px; | |
| border-radius: 2px; | |
| background: var(--accent-color); | |
| transition: height 0.1s ease; | |
| } | |
| /* ββ Signing Progress ββββββββββββββββββββββββ */ | |
| #sign-label { | |
| font-size: 13px; | |
| color: var(--text-muted); | |
| font-weight: 500; | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| } | |
| #sign-label.visible { opacity: 1; } | |
| /* ββ Text Input Area ββββββββββββββββββββββββββ */ | |
| #text-input-container { | |
| width: 100%; | |
| max-width: 500px; | |
| display: flex; | |
| gap: 10px; | |
| padding: 4px; | |
| background: var(--input-bg); | |
| border: 1px solid var(--input-border); | |
| border-radius: 12px; | |
| margin-bottom: 8px; | |
| transition: all 0.3s ease; | |
| } | |
| #text-input-container:focus-within { | |
| background: var(--input-bg); | |
| border-color: var(--input-focus); | |
| box-shadow: 0 0 15px var(--accent-hover); | |
| } | |
| #text-input { | |
| color: var(--text-main); | |
| flex: 1; | |
| background: transparent; | |
| border: none; | |
| outline: none; | |
| color: #fff; | |
| padding: 10px 16px; | |
| font-family: inherit; | |
| font-size: 14px; | |
| } | |
| #text-input::placeholder { color: var(--text-muted); } | |
| #send-btn { | |
| background: var(--btn-bg); | |
| border: 1px solid var(--btn-border); | |
| color: var(--gloss-token-text); | |
| padding: 0 16px; | |
| border-radius: 8px; | |
| cursor: pointer; | |
| font-size: 13px; | |
| font-weight: 600; | |
| transition: all 0.2s ease; | |
| } | |
| #send-btn:hover { | |
| background: var(--btn-hover-bg); | |
| color: #fff; | |
| } | |
| /* ββ Controls Layout ββββββββββββββββββββββββββ */ | |
| .controls-row { | |
| display: flex; | |
| align-items: center; | |
| gap: 20px; | |
| } | |
| /* ββ Secondary Button (Replay) ββββββββββββββββ */ | |
| .secondary-btn { | |
| width: 48px; height: 48px; | |
| border-radius: 50%; | |
| border: 1px solid var(--input-border); | |
| background: var(--input-bg); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all 0.3s ease; | |
| color: var(--text-muted); | |
| } | |
| .secondary-btn:hover { | |
| background: rgba(255,255,255,0.1); | |
| border-color: rgba(255,255,255,0.2); | |
| color: #fff; | |
| transform: translateY(-2px); | |
| } | |
| .secondary-btn:active { transform: translateY(0); } | |
| .secondary-btn:disabled { | |
| opacity: 0.3; | |
| cursor: not-allowed; | |
| transform: none; | |
| } | |
| .btn-icon { width: 20px; height: 20px; fill: currentColor; } | |
| /* ββ Responsive adjustments βββββββββββββββββββ */ | |
| @media (max-width: 600px), (max-height: 700px) { | |
| .transcript-text { font-size: 13px; line-height: 1.3; } | |
| .gloss-token { font-size: 11px; padding: 3px 8px; } | |
| #bottom-panel { padding: 12px 16px 20px; gap: 8px; } | |
| #text-input-container { max-width: 100%; margin-bottom: 4px; } | |
| #mic-btn { width: 60px; height: 60px; } | |
| #waveform { height: 30px; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- Top Bar --> | |
| <div id="top-bar"> | |
| <div style="display: flex; gap: 16px; align-items: center;"> | |
| <div class="logo">SignApp</div> | |
| <button id="theme-btn" class="secondary-btn" style="width: 36px; height: 36px;" title="Toggle Theme"> | |
| <svg class="btn-icon" viewBox="0 0 24 24" id="theme-icon" style="width: 18px; height: 18px;"> | |
| <path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41.39.39 1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41.39.39 1.03.39 1.41 0l1.06-1.06z"/> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="status-chip"> | |
| <span class="status-dot" id="status-dot"></span> | |
| <span id="status-text">Ready</span> | |
| </div> | |
| </div> | |
| <!-- Bottom Panel --> | |
| <div id="bottom-panel"> | |
| <!-- Transcript --> | |
| <div id="transcript-area"> | |
| <div class="transcript-label" id="transcript-label"></div> | |
| <div class="transcript-text" id="transcript-text"></div> | |
| <div class="gloss-tokens" id="gloss-tokens"></div> | |
| </div> | |
| <!-- Text Input --> | |
| <div id="text-input-container"> | |
| <input type="text" id="text-input" placeholder="Or type something here..." autocomplete="off"> | |
| <button id="send-btn">Send</button> | |
| </div> | |
| <!-- Waveform --> | |
| <div id="waveform"></div> | |
| <!-- Sign Progress --> | |
| <div id="sign-label"></div> | |
| <div style="font-size: 10px; color: var(--text-muted); opacity: 0.8; text-align: center; max-width: 80%; margin-top: -8px; margin-bottom: 4px;">Note: Generated signs are AI approximations and may not be 100% accurate.</div> | |
| <!-- Controls --> | |
| <div class="controls-row"> | |
| <!-- Replay Button --> | |
| <button id="replay-btn" class="secondary-btn" title="Replay last signs" disabled> | |
| <svg class="btn-icon" viewBox="0 0 24 24"> | |
| <path d="M12 5V1L7 6l5 5V7c3.31 0 6 2.69 6 6s-2.69 6-6 6-6-2.69-6-6H4c0 4.42 3.58 8 8 8s8-3.58 8-8-3.58-8-8-8z"/> | |
| </svg> | |
| </button> | |
| <!-- Microphone Button --> | |
| <button id="mic-btn" title="Click to speak"> | |
| <svg class="mic-icon" viewBox="0 0 24 24" id="mic-svg"> | |
| <path d="M12 14c1.66 0 3-1.34 3-3V5c0-1.66-1.34-3-3-3S9 3.34 9 5v6c0 1.66 1.34 3 3 3zm-1-9c0-.55.45-1 1-1s1 .45 1 1v6c0 .55-.45 1-1 1s-1-.45-1-1V5z"/> | |
| <path d="M17 11c0 2.76-2.24 5-5 5s-5-2.24-5-5H5c0 3.53 2.61 6.43 6 6.92V21h2v-3.08c3.39-.49 6-3.39 6-6.92h-2z"/> | |
| </svg> | |
| </button> | |
| <!-- Placeholder to balance the row if needed, or other future buttons --> | |
| <div style="width: 48px;"></div> | |
| </div> | |
| </div> | |
| <script type="module" src="./main.js"></script> | |
| </body> | |
| </html> |