Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no"> | |
| <title>Fitbit x Claude Voice Bridge</title> | |
| <!-- FontAwesome for Icons --> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <style> | |
| :root { | |
| --ios-bg: #F2F2F7; | |
| --ios-card: #FFFFFF; | |
| --ios-text: #1C1C1E; | |
| --ios-gray: #8E8E93; | |
| --fitbit-green: #00B0B9; | |
| --claude-purple: #D97757; | |
| --claude-bg: #F9F7F3; | |
| --accent-blue: #007AFF; | |
| --shadow: 0 4px 20px rgba(0, 0, 0, 0.08); | |
| --font-stack: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| } | |
| * { | |
| box-sizing: box-sizing; | |
| margin: 0; | |
| padding: 0; | |
| -webkit-tap-highlight-color: transparent; | |
| } | |
| body { | |
| background-color: #e0e0e0; | |
| font-family: var(--font-stack); | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| min-height: 100vh; | |
| overflow: hidden; | |
| } | |
| /* --- Phone Container --- */ | |
| .iphone-frame { | |
| width: 375px; | |
| height: 812px; | |
| background: var(--ios-bg); | |
| border-radius: 40px; | |
| box-shadow: 0 20px 50px rgba(0, 0, 0, 0.2); | |
| position: relative; | |
| overflow: hidden; | |
| display: flex; | |
| flex-direction: column; | |
| border: 8px solid #333; | |
| } | |
| /* --- Header / Status Bar --- */ | |
| .status-bar { | |
| height: 44px; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| padding: 0 20px; | |
| font-size: 14px; | |
| font-weight: 600; | |
| color: var(--ios-text); | |
| background: rgba(255, 255, 255, 0.8); | |
| backdrop-filter: blur(10px); | |
| z-index: 10; | |
| } | |
| .app-header { | |
| padding: 10px 20px; | |
| background: rgba(255, 255, 255, 0.9); | |
| backdrop-filter: blur(20px); | |
| border-bottom: 1px solid rgba(0, 0, 0, 0.05); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| z-index: 9; | |
| } | |
| .app-title { | |
| font-weight: 700; | |
| font-size: 18px; | |
| color: var(--ios-text); | |
| } | |
| .connection-status { | |
| display: flex; | |
| gap: 8px; | |
| font-size: 12px; | |
| color: var(--ios-gray); | |
| } | |
| .status-dot { | |
| width: 8px; | |
| height: 8px; | |
| border-radius: 50%; | |
| background-color: var(--fitbit-green); | |
| box-shadow: 0 0 4px var(--fitbit-green); | |
| animation: pulse 2s infinite; | |
| } | |
| .help-btn { | |
| cursor: pointer; | |
| color: var(--accent-blue); | |
| font-size: 16px; | |
| transition: transform 0.2s; | |
| } | |
| .help-btn:active { | |
| transform: scale(0.9); | |
| } | |
| /* --- Main Content Area --- */ | |
| .content-area { | |
| flex: 1; | |
| padding: 20px; | |
| overflow-y: auto; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 15px; | |
| } | |
| /* Fitbit Data Card */ | |
| .fitbit-context { | |
| background: linear-gradient(135deg, #00B0B9, #008a91); | |
| color: white; | |
| padding: 15px; | |
| border-radius: 16px; | |
| box-shadow: var(--shadow); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .fitbit-label { | |
| font-size: 12px; | |
| opacity: 0.8; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| } | |
| .fitbit-data { | |
| font-size: 20px; | |
| font-weight: 700; | |
| } | |
| .heart-icon { | |
| font-size: 16px; | |
| animation: heartbeat 1.2s infinite; | |
| } | |
| /* Claude Response Card */ | |
| .claude-card { | |
| background: var(--claude-bg); | |
| border: 1px solid rgba(0, 0, 0, 0.05); | |
| padding: 15px; | |
| border-radius: 16px; | |
| font-size: 14px; | |
| line-height: 1.5; | |
| color: #333; | |
| box-shadow: var(--shadow); | |
| position: relative; | |
| display: none; | |
| /* Hidden by default */ | |
| } | |
| .claude-card.active { | |
| display: block; | |
| animation: slideUp 0.4s ease-out; | |
| } | |
| .claude-header { | |
| font-size: 10px; | |
| color: var(--claude-purple); | |
| font-weight: 700; | |
| text-transform: uppercase; | |
| margin-bottom: 5px; | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| /* --- Voice Interaction Zone --- */ | |
| .voice-zone { | |
| margin-top: auto; | |
| margin-bottom: 20px; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| height: 180px; | |
| } | |
| .mic-button { | |
| width: 80px; | |
| height: 80px; | |
| background: white; | |
| border-radius: 50%; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| box-shadow: 0 10px 30px rgba(0, 0, 0, 0.1); | |
| cursor: pointer; | |
| transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1); | |
| position: relative; | |
| z-index: 2; | |
| } | |
| .mic-button i { | |
| font-size: 24px; | |
| color: var(--ios-text); | |
| transition: color 0.3s; | |
| } | |
| .mic-button.listening { | |
| background: var(--accent-blue); | |
| transform: scale(1.1); | |
| } | |
| .mic-button.listening i { | |
| color: white; | |
| } | |
| /* Ripple Effect */ | |
| .ripple { | |
| position: absolute; | |
| width: 80px; | |
| height: 80px; | |
| border-radius: 50%; | |
| border: 2px solid var(--accent-blue); | |
| opacity: 0; | |
| z-index: 1; | |
| } | |
| .mic-button.listening .ripple { | |
| animation: rippleAnim 1.5s infinite; | |
| } | |
| .voice-status-text { | |
| margin-top: 15px; | |
| font-size: 14px; | |
| color: var(--ios-gray); | |
| font-weight: 500; | |
| height: 20px; | |
| } | |
| /* --- Footer --- */ | |
| .footer-link { | |
| position: absolute; | |
| bottom: 10px; | |
| width: 100%; | |
| text-align: center; | |
| font-size: 10px; | |
| color: var(--ios-gray); | |
| opacity: 0.6; | |
| } | |
| .footer-link a { | |
| color: var(--ios-text); | |
| text-decoration: none; | |
| font-weight: 600; | |
| } | |
| /* --- Animations --- */ | |
| @keyframes pulse { | |
| 0% { opacity: 0.5; } | |
| 50% { opacity: 1; } | |
| 100% { opacity: 0.5; } | |
| } | |
| @keyframes heartbeat { | |
| 0% { transform: scale(1); } | |
| 14% { transform: scale(1.3); } | |
| 28% { transform: scale(1); } | |
| 42% { transform: scale(1.3); } | |
| 70% { transform: scale(1); } | |
| } | |
| @keyframes rippleAnim { | |
| 0% { width: 80px; height: 80px; opacity: 1; } | |
| 100% { width: 200px; height: 200px; opacity: 0; } | |
| } | |
| @keyframes slideUp { | |
| from { transform: translateY(20px); opacity: 0; } | |
| to { transform: translateY(0); opacity: 1; } | |
| } | |
| /* --- Loading Bar --- */ | |
| .loading-bar { | |
| height: 2px; | |
| width: 100%; | |
| background: #eee; | |
| position: relative; | |
| overflow: hidden; | |
| display: none; | |
| } | |
| .loading-bar.active { | |
| display: block; | |
| } | |
| .loading-progress { | |
| height: 100%; | |
| width: 50%; | |
| background: var(--fitbit-green); | |
| position: absolute; | |
| animation: load 1.5s infinite ease-in-out; | |
| } | |
| @keyframes load { | |
| 0% { left: -50%; } | |
| 100% { left: 100%; } | |
| } | |
| /* --- Installation Guide Modal --- */ | |
| .modal-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background: rgba(0, 0, 0, 0.4); | |
| backdrop-filter: blur(5px); | |
| z-index: 100; | |
| display: none; /* Hidden by default */ | |
| justify-content: center; | |
| align-items: center; | |
| padding: 20px; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| } | |
| .modal-overlay.open { | |
| display: flex; | |
| opacity: 1; | |
| } | |
| .modal-content { | |
| background: rgba(255, 255, 255, 0.95); | |
| width: 100%; | |
| max-width: 300px; | |
| border-radius: 20px; | |
| padding: 25px; | |
| box-shadow: 0 10px 40px rgba(0,0,0,0.2); | |
| text-align: center; | |
| transform: scale(0.9); | |
| transition: transform 0.3s cubic-bezier(0.2, 0.8, 0.2, 1); | |
| } | |
| .modal-overlay.open .modal-content { | |
| transform: scale(1); | |
| } | |
| .modal-title { | |
| font-size: 18px; | |
| font-weight: 700; | |
| margin-bottom: 15px; | |
| color: var(--ios-text); | |
| } | |
| .modal-step { | |
| display: flex; | |
| align-items: center; | |
| gap: 15px; | |
| margin-bottom: 15px; | |
| text-align: left; | |
| } | |
| .step-icon { | |
| width: 40px; | |
| height: 40px; | |
| background: #eee; | |
| border-radius: 10px; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| font-size: 18px; | |
| color: var(--ios-text); | |
| } | |
| .step-text { | |
| font-size: 13px; | |
| line-height: 1.4; | |
| color: #555; | |
| } | |
| .modal-close { | |
| margin-top: 10px; | |
| background: var(--ios-text); | |
| color: white; | |
| border: none; | |
| padding: 12px 20px; | |
| border-radius: 12px; | |
| font-weight: 600; | |
| font-size: 14px; | |
| cursor: pointer; | |
| width: 100%; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="iphone-frame"> | |
| <!-- iOS Status Bar Simulation --> | |
| <div class="status-bar"> | |
| <span>9:41</span> | |
| <div style="display: flex; gap: 5px;"> | |
| <i class="fas fa-signal"></i> | |
| <i class="fas fa-wifi"></i> | |
| <i class="fas fa-battery-three-quarters"></i> | |
| </div> | |
| </div> | |
| <!-- App Header --> | |
| <div class="app-header"> | |
| <div class="app-title">Fitbit<span style="color:var(--fitbit-green)">Voice</span></div> | |
| <div class="connection-status"> | |
| <span>Fitbit</span> | |
| <div class="status-dot"></div> | |
| <span>Claude</span> | |
| <i class="fas fa-check-circle" style="color: var(--claude-purple)"></i> | |
| </div> | |
| <!-- Added Help Button --> | |
| <div class="help-btn" onclick="toggleModal()"> | |
| <i class="fas fa-info-circle"></i> | |
| </div> | |
| </div> | |
| <!-- Main Scrollable Content --> | |
| <div class="content-area" id="chat-container"> | |
| <!-- Context Card: Simulates Fitbit Data being sent --> | |
| <div class="fitbit-context"> | |
| <div> | |
| <div class="fitbit-label">Current Activity</div> | |
| <div class="fitbit-data">Walking <span style="font-size: 14px; opacity: 0.8">• 102 bpm</span></div> | |
| </div> | |
| <i class="fas fa-heartbeat heart-icon"></i> | |
| </div> | |
| <!-- Claude Response Placeholder --> | |
| <div class="claude-card" id="claude-response"> | |
| <div class="claude-header"> | |
| <i class="fas fa-sparkles"></i> Claude AI | |
| </div> | |
| <div id="claude-text"> | |
| Waiting for command... | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading Bar --> | |
| <div class="loading-bar" id="loader"> | |
| <div class="loading-progress"></div> | |
| </div> | |
| <!-- Voice Control Area --> | |
| <div class="voice-zone"> | |
| <div class="voice-status-text" id="status-text">Tap to speak</div> | |
| <div class="mic-button" id="mic-btn" onclick="toggleRecording()"> | |
| <i class="fas fa-microphone"></i> | |
| <div class="ripple"></div> | |
| </div> | |
| </div> | |
| <!-- Installation Guide Modal --> | |
| <div class="modal-overlay" id="install-modal"> | |
| <div class="modal-content"> | |
| <div class="modal-title">Install on iPhone</div> | |
| <div class="modal-step"> | |
| <div class="step-icon"><i class="fas fa-share"></i></div> | |
| <div class="step-text">Tap the <b>Share</b> button in Safari.</div> | |
| </div> | |
| <div class="modal-step"> | |
| <div class="step-icon"><i class="fas fa-plus-square"></i></div> | |
| <div class="step-text">Scroll down and tap <b>"Add to Home Screen"</b>.</div> | |
| </div> | |
| <div class="modal-step"> | |
| <div class="step-icon"><i class="fas fa-check"></i></div> | |
| <div class="step-text">Tap <b>"Add"</b> to finish.</div> | |
| </div> | |
| <button class="modal-close" onclick="toggleModal()">Got it</button> | |
| </div> | |
| </div> | |
| <!-- Footer Link --> | |
| <div class="footer-link"> | |
| Built with <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank">anycoder</a> | |
| </div> | |
| </div> | |
| <script> | |
| const micBtn = document.getElementById('mic-btn'); | |
| const statusText = document.getElementById('status-text'); | |
| const claudeCard = document.getElementById('claude-response'); | |
| const claudeText = document.getElementById('claude-text'); | |
| const loader = document.getElementById('loader'); | |
| const chatContainer = document.getElementById('chat-container'); | |
| const modal = document.getElementById('install-modal'); | |
| let isRecording = false; | |
| // Mock responses for demonstration | |
| const mockResponses = [ | |
| "I've logged your 'Walking' activity. Your heart rate is slightly elevated at 102 bpm. Is this a new workout session?", | |
| "Based on your Fitbit data, you've taken 4,200 steps today. You are 60% towards your daily goal. Keep moving!", | |
| "I noticed a spike in your heart rate at 9:30 AM. Did you just finish a sprint? I can add that to your exercise log." | |
| ]; | |
| function toggleRecording() { | |
| if (isRecording) { | |
| stopRecording(); | |
| } else { | |
| startRecording(); | |
| } | |
| } | |
| function startRecording() { | |
| isRecording = true; | |
| micBtn.classList.add('listening'); | |
| statusText.innerText = "Listening..."; | |
| statusText.style.color = "#007AFF"; | |
| // Simulate listening duration (3 seconds) | |
| setTimeout(() => { | |
| processCommand(); | |
| }, 3000); | |
| } | |
| function stopRecording() { | |
| isRecording = false; | |
| micBtn.classList.remove('listening'); | |
| statusText.innerText = "Tap to speak"; | |
| statusText.style.color = "#8E8E93"; | |
| } | |
| function processCommand() { | |
| stopRecording(); | |
| // 1. Show Loading State (Sending to Claude) | |
| loader.classList.add('active'); | |
| statusText.innerText = "Processing with Claude..."; | |
| statusText.style.color = "#D97757"; // Claude color | |
| // 2. Simulate Network Delay | |
| setTimeout(() => { | |
| loader.classList.remove('active'); | |
| // 3. Generate Response | |
| const randomResponse = mockResponses[Math.floor(Math.random() * mockResponses.length)]; | |
| // 4. Update UI with Response | |
| claudeText.innerText = randomResponse; | |
| claudeCard.classList.add('active'); | |
| statusText.innerText = "Command Sent"; | |
| // Scroll to bottom | |
| chatContainer.scrollTop = chatContainer.scrollHeight; | |
| }, 2000); | |
| } | |
| function toggleModal() { | |
| if (modal.classList.contains('open')) { | |
| modal.classList.remove('open'); | |
| } else { | |
| modal.classList.add('open'); | |
| } | |
| } | |
| // Initialize | |
| console.log("Fitbit-Claude Bridge Initialized"); | |
| </script> | |
| </body> | |
| </html> |