about summary refs log tree commit diff stats
diff options
context:
space:
mode:
authorelioat <elioat@tilde.institute>2025-03-10 19:14:34 -0400
committerelioat <elioat@tilde.institute>2025-03-10 19:14:34 -0400
commitd5652a7836eb7a9796d04d0885a7eebee3760a87 (patch)
tree71dd948cd515014f43a3c2d5aaaf2ee1de774d80
parent56d1996e7c6e8b3a927274e892da86dde67725b7 (diff)
downloadtour-d5652a7836eb7a9796d04d0885a7eebee3760a87.tar.gz
*
-rw-r--r--html/voice-memos/app.js79
-rw-r--r--html/voice-memos/index.html169
2 files changed, 215 insertions, 33 deletions
diff --git a/html/voice-memos/app.js b/html/voice-memos/app.js
index 0436494..f13e28f 100644
--- a/html/voice-memos/app.js
+++ b/html/voice-memos/app.js
@@ -96,6 +96,12 @@ const updateUI = () => {
     elements.playBtn.disabled = audioChunks.length === 0 || isRecording || isPlaying;
     elements.saveBtn.disabled = audioChunks.length === 0 || isRecording;
     
+    // Update recording indicator
+    const recordingIndicator = document.getElementById('recordingIndicator');
+    if (recordingIndicator) {
+        recordingIndicator.style.display = isRecording ? 'block' : 'none';
+    }
+    
     // Update status message
     let statusMessage = '';
     
@@ -113,7 +119,7 @@ const updateUI = () => {
     } else if (!elements.inputSource.value) {
         statusMessage = 'Please allow microphone access and select an input source';
     } else {
-        statusMessage = 'Ready to record. Click "Start Recording" to begin.';
+        statusMessage = 'Ready to record. Click "Record" to begin.';
     }
     
     elements.status.textContent = statusMessage;
@@ -202,12 +208,19 @@ const setupWaveformVisualization = (analyser) => {
     const dataArray = new Uint8Array(bufferLength);
     const canvas = document.createElement('canvas');
     const canvasCtx = canvas.getContext('2d');
+    const recordingIndicator = document.getElementById('recordingIndicator');
+    
+    // Show recording indicator
+    recordingIndicator.style.display = 'block';
     
     elements.waveform.innerHTML = '';
     elements.waveform.appendChild(canvas);
     
     const draw = () => {
-        if (!stateManager.getState().isRecording) return;
+        if (!stateManager.getState().isRecording) {
+            recordingIndicator.style.display = 'none';
+            return;
+        }
         
         requestAnimationFrame(draw);
         analyser.getByteTimeDomainData(dataArray);
@@ -215,19 +228,70 @@ const setupWaveformVisualization = (analyser) => {
         canvas.width = elements.waveform.clientWidth;
         canvas.height = elements.waveform.clientHeight;
         
-        canvasCtx.fillStyle = '#f8f8f8';
+        // Clear the canvas with a gradient background
+        const gradient = canvasCtx.createLinearGradient(0, 0, 0, canvas.height);
+        gradient.addColorStop(0, '#f8f8f8');
+        gradient.addColorStop(1, '#f2f2f7');
+        canvasCtx.fillStyle = gradient;
         canvasCtx.fillRect(0, 0, canvas.width, canvas.height);
         
-        canvasCtx.lineWidth = 2;
-        canvasCtx.strokeStyle = '#007AFF';
+        // Draw center line
+        canvasCtx.beginPath();
+        canvasCtx.strokeStyle = '#e5e5ea';
+        canvasCtx.lineWidth = 1;
+        canvasCtx.moveTo(0, canvas.height / 2);
+        canvasCtx.lineTo(canvas.width, canvas.height / 2);
+        canvasCtx.stroke();
+        
+        // Draw waveform
         canvasCtx.beginPath();
         
+        // Modern style with thicker lines and smoother curves
+        canvasCtx.lineWidth = 2;
+        canvasCtx.strokeStyle = '#ff3b30';
+        
         const sliceWidth = canvas.width / bufferLength;
         let x = 0;
         
+        // First pass to smooth the data
+        const smoothedData = [];
+        const smoothingFactor = 0.2;
+        
         for (let i = 0; i < bufferLength; i++) {
-            const v = dataArray[i] / 128.0;
-            const y = v * canvas.height / 2;
+            const raw = dataArray[i] / 128.0 - 1.0;
+            
+            // Apply smoothing
+            if (i > 0) {
+                smoothedData.push(smoothedData[i-1] * smoothingFactor + raw * (1 - smoothingFactor));
+            } else {
+                smoothedData.push(raw);
+            }
+        }
+        
+        // Draw the smoothed waveform
+        for (let i = 0; i < bufferLength; i++) {
+            const v = smoothedData[i];
+            const y = (v * canvas.height / 4) + canvas.height / 2;
+            
+            if (i === 0) {
+                canvasCtx.moveTo(x, y);
+            } else {
+                canvasCtx.lineTo(x, y);
+            }
+            
+            x += sliceWidth;
+        }
+        
+        canvasCtx.stroke();
+        
+        // Draw a reflection of the waveform (mirrored and more subtle)
+        canvasCtx.beginPath();
+        canvasCtx.strokeStyle = 'rgba(255, 59, 48, 0.3)';
+        x = 0;
+        
+        for (let i = 0; i < bufferLength; i++) {
+            const v = -smoothedData[i]; // Mirror the waveform
+            const y = (v * canvas.height / 4) + canvas.height / 2;
             
             if (i === 0) {
                 canvasCtx.moveTo(x, y);
@@ -238,7 +302,6 @@ const setupWaveformVisualization = (analyser) => {
             x += sliceWidth;
         }
         
-        canvasCtx.lineTo(canvas.width, canvas.height / 2);
         canvasCtx.stroke();
     };
     
diff --git a/html/voice-memos/index.html b/html/voice-memos/index.html
index e328ee5..7c9213d 100644
--- a/html/voice-memos/index.html
+++ b/html/voice-memos/index.html
@@ -5,69 +5,188 @@
     <meta name="viewport" content="width=device-width, initial-scale=1.0">
     <title>Voice Memos</title>
     <style>
+        :root {
+            --primary-color: #ff3b30;
+            --secondary-color: #34c759;
+            --text-color: #333;
+            --light-gray: #f2f2f7;
+            --medium-gray: #e5e5ea;
+            --dark-gray: #8e8e93;
+            --border-radius: 12px;
+        }
+        
         body {
-            font-family: sans-serif;
+            font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif;
             max-width: 800px;
             margin: 0 auto;
             padding: 20px;
-            background-color:beige;
+            background-color: #f9f9f9;
+            color: var(--text-color);
         }
+        
         .container {
             background-color: white;
-            padding: 20px;
-            border-radius: 8px;
-            box-shadow: 0 2px 4px rgba(0,0,0,0.1);
+            padding: 24px;
+            border-radius: var(--border-radius);
+            box-shadow: 0 4px 20px rgba(0,0,0,0.08);
+        }
+        
+        h1 {
+            font-size: 24px;
+            font-weight: 600;
+            margin-top: 0;
+            margin-bottom: 24px;
+            text-align: center;
         }
+        
+        .input-container {
+            margin-bottom: 24px;
+        }
+        
         .controls {
             display: flex;
-            gap: 10px;
-            margin: 20px 0;
+            gap: 12px;
+            margin: 24px 0;
+            justify-content: center;
         }
+        
         button {
-            padding: 8px 16px;
+            padding: 10px 20px;
             border: none;
-            border-radius: 4px;
+            border-radius: 24px;
+            font-weight: 500;
+            font-size: 14px;
+            cursor: pointer;
+            transition: all 0.2s ease;
+            display: flex;
+            align-items: center;
+            justify-content: center;
+            min-width: 100px;
+        }
+        
+        #startBtn {
+            background-color: var(--primary-color);
+            color: white;
+        }
+        
+        #stopBtn {
+            background-color: var(--dark-gray);
+            color: white;
+        }
+        
+        #playBtn {
+            background-color: var(--secondary-color);
+            color: white;
+        }
+        
+        #saveBtn {
             background-color: #007AFF;
             color: white;
-            cursor: pointer;
-            transition: background-color 0.2s;
         }
+        
         button:hover {
-            background-color: #0056b3;
+            transform: translateY(-2px);
+            box-shadow: 0 4px 8px rgba(0,0,0,0.1);
         }
+        
         button:disabled {
-            background-color: #ccc;
+            background-color: var(--medium-gray);
+            color: var(--dark-gray);
             cursor: not-allowed;
+            transform: none;
+            box-shadow: none;
         }
+        
         select {
-            padding: 8px;
-            border-radius: 4px;
-            border: 1px solid #ddd;
-            margin-bottom: 20px;
+            width: 100%;
+            padding: 12px;
+            border-radius: var(--border-radius);
+            border: 1px solid var(--medium-gray);
+            background-color: var(--light-gray);
+            font-size: 14px;
+            appearance: none;
+            background-image: url("data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23007CB2%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-5%200-9.3%201.8-12.9%205.4A17.6%2017.6%200%200%200%200%2082.2c0%205%201.8%209.3%205.4%2012.9l128%20127.9c3.6%203.6%207.8%205.4%2012.8%205.4s9.2-1.8%2012.8-5.4L287%2095c3.5-3.5%205.4-7.8%205.4-12.8%200-5-1.9-9.2-5.5-12.8z%22%2F%3E%3C%2Fsvg%3E");
+            background-repeat: no-repeat;
+            background-position: right 12px top 50%;
+            background-size: 12px auto;
+        }
+        
+        .waveform-container {
+            position: relative;
+            width: 100%;
+            height: 120px;
+            background-color: var(--light-gray);
+            border-radius: var(--border-radius);
+            margin: 24px 0;
+            overflow: hidden;
         }
+        
         #waveform {
             width: 100%;
-            height: 100px;
-            background-color: #f8f8f8;
-            border-radius: 4px;
-            margin: 20px 0;
+            height: 100%;
+            display: flex;
+            align-items: center;
+            justify-content: center;
         }
+        
         .status {
-            margin: 10px 0;
+            text-align: center;
+            margin: 16px 0;
+            font-size: 14px;
+            color: var(--dark-gray);
             font-weight: 500;
         }
+        
+        .recording-indicator {
+            display: none;
+            position: absolute;
+            top: 10px;
+            right: 10px;
+            width: 12px;
+            height: 12px;
+            border-radius: 50%;
+            background-color: var(--primary-color);
+            animation: pulse 1.5s infinite;
+        }
+        
+        @keyframes pulse {
+            0% {
+                transform: scale(0.95);
+                box-shadow: 0 0 0 0 rgba(255, 59, 48, 0.7);
+            }
+            
+            70% {
+                transform: scale(1);
+                box-shadow: 0 0 0 10px rgba(255, 59, 48, 0);
+            }
+            
+            100% {
+                transform: scale(0.95);
+                box-shadow: 0 0 0 0 rgba(255, 59, 48, 0);
+            }
+        }
     </style>
 </head>
 <body>
     <div class="container">
-        <select id="inputSource"></select>
+        <h1>Voice Memos</h1>
+        
+        <div class="input-container">
+            <select id="inputSource"></select>
+        </div>
+        
+        <div class="waveform-container">
+            <div id="waveform"></div>
+            <div class="recording-indicator" id="recordingIndicator"></div>
+        </div>
+        
         <div class="controls">
-            <button id="startBtn">Start Recording</button>
+            <button id="startBtn">Record</button>
             <button id="stopBtn" disabled>Stop</button>
             <button id="playBtn" disabled>Play</button>
             <button id="saveBtn" disabled>Save</button>
         </div>
-        <div id="waveform"></div>
+        
         <div id="status" class="status">Select an input source to begin</div>
     </div>
     <script src="app.js"></script>