)}
@@ -745,7 +748,7 @@ class SharedTopBar extends React.Component {
>
✕
-
+
Barra superior
@@ -784,6 +787,19 @@ class SharedTopBar extends React.Component {
}}>
Pellizca para zoom. Toca marcadores para escuchar. Cada grabacion incluye coordenadas, hora y metadatos.
+
+
Creditos
+
+ Leafletleafletjs.com
+ OpenStreetMapopenstreetmap.org
+ OpenTopoMapopentopomap.org
+ CARTOcarto.com
+ HOThotosm.org
+ Stadia Mapsstadiamaps.com
+ Esriarcgis.com
+ CyclOSMcyclosm.org
+
+
)}
@@ -810,9 +826,9 @@ class SharedTopBar extends React.Component {
bottom: '190px',
left: '50%',
transform: 'translateX(-50%)',
- backgroundColor: '#ffffffbf',
+ backgroundColor: 'rgba(220,225,235,0.78)',
borderRadius: '16px',
- boxShadow: 'rgb(157 58 58 / 30%) 0px 10px 30px',
+ boxShadow: 'rgba(78,78,134,0.25) 0px 10px 30px',
padding: '20px',
minWidth: '300px',
maxWidth: '400px',
@@ -852,7 +868,7 @@ class SharedTopBar extends React.Component {
alignItems: 'center',
gap: '6px',
padding: '8px 20px',
- backgroundColor: this.state.derivePaused ? '#F59E42' : '#3B82F6',
+ backgroundColor: this.state.derivePaused ? '#F59E42' : '#4e4e86',
color: 'white',
border: 'none',
borderRadius: '8px',
@@ -875,7 +891,7 @@ class SharedTopBar extends React.Component {
style={{
width: '100%',
padding: '10px 12px',
- border: '1px solid #D1D5DB',
+ border: '1px solid rgba(78,78,134,0.22)',
borderRadius: '8px',
fontSize: '14px',
outline: 'none',
@@ -894,7 +910,7 @@ class SharedTopBar extends React.Component {
style={{
flex: 1,
padding: '10px',
- backgroundColor: '#10B981',
+ backgroundColor: '#9dc04cd4',
color: 'white',
border: 'none',
borderRadius: '8px',
@@ -910,8 +926,8 @@ class SharedTopBar extends React.Component {
style={{
padding: '10px 16px',
backgroundColor: 'white',
- color: '#374151',
- border: '1px solid #D1D5DB',
+ color: 'rgb(1 9 2 / 84%)',
+ border: '1px solid rgba(78,78,134,0.22)',
borderRadius: '8px',
fontSize: '14px',
cursor: 'pointer'
diff --git a/src/components/TracklogImportModal.jsx b/src/components/TracklogImportModal.jsx
index c677807..9ba2f69 100644
--- a/src/components/TracklogImportModal.jsx
+++ b/src/components/TracklogImportModal.jsx
@@ -143,9 +143,9 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
bottom: '190px',
left: '50%',
transform: 'translateX(-50%)',
- backgroundColor: '#ffffffbf',
+ backgroundColor: 'rgba(220,225,235,0.78)',
borderRadius: '16px',
- boxShadow: 'rgb(157 58 58 / 30%) 0px 10px 30px',
+ boxShadow: 'rgba(78,78,134,0.25) 0px 10px 30px',
width: '90%',
maxWidth: '400px',
maxHeight: '60vh',
@@ -167,7 +167,7 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
margin: 0,
fontSize: '16px',
fontWeight: '600',
- color: '#1F2937'
+ color: '#000000c9'
}}>
Importar Deriva
@@ -194,7 +194,7 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
{
onDrop={handleDrop}
onClick={() => fileInputRef.current?.click()}
>
-
+
Selecciona un archivo .zip
@@ -244,14 +244,14 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
}}>
{validationResult.valid ? (
-
+
) : (
-
+
)}
{validationResult.valid ? 'Archivo válido' : 'Error de validación'}
@@ -277,7 +277,7 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
margin: '0 0 16px 0',
fontSize: '16px',
fontWeight: '600',
- color: '#1F2937'
+ color: '#000000c9'
}}>
⚙️ Opciones de Importación
@@ -290,7 +290,7 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
gap: '8px',
fontSize: '14px',
fontWeight: '500',
- color: '#374151',
+ color: 'rgb(1 9 2 / 84%)',
cursor: 'pointer'
}}>
{
padding: '16px',
backgroundColor: '#F9FAFB',
borderRadius: '8px',
- border: '1px solid #E5E7EB'
+ border: '1px solid rgba(78,78,134,0.15)'
}}>
-
+
Transformación de Ubicación
@@ -500,14 +500,14 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
}}>
{importResult.success ? (
-
+
) : (
-
+
)}
{importResult.success ? 'Importación exitosa' : 'Error de importación'}
@@ -543,7 +543,7 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
padding: '10px',
border: 'none',
borderRadius: '8px',
- backgroundColor: !validationResult?.valid || isImporting ? '#9CA3AF' : '#10B981',
+ backgroundColor: !validationResult?.valid || isImporting ? '#9CA3AF' : '#9dc04cd4',
color: 'white',
fontSize: '14px',
fontWeight: '500',
@@ -563,8 +563,8 @@ const TracklogImportModal = ({ isVisible, onClose, onImportComplete }) => {
style={{
padding: '10px 16px',
backgroundColor: 'white',
- color: '#374151',
- border: '1px solid #D1D5DB',
+ color: 'rgb(1 9 2 / 84%)',
+ border: '1px solid rgba(78,78,134,0.22)',
borderRadius: '8px',
fontSize: '14px',
cursor: isImporting ? 'not-allowed' : 'pointer',
diff --git a/src/components/UnifiedMap.jsx b/src/components/UnifiedMap.jsx
index 328266f..fa98ef0 100644
--- a/src/components/UnifiedMap.jsx
+++ b/src/components/UnifiedMap.jsx
@@ -13,6 +13,7 @@ import AudioRecorder from '../services/AudioRecorder.tsx';
// WalkSessionPanel removed — derive controls now integrated into SharedTopBar
import AliasPrompt from './AliasPrompt.jsx';
import SessionHistoryPanel from './SessionHistoryPanel.jsx';
+import useDraggable from '../hooks/useDraggable.js';
import DetailView from './DetailView.jsx';
import L from 'leaflet';
import { createDurationCircleIcon } from './SharedMarkerUtils';
@@ -55,21 +56,21 @@ const showAlert = (message) => {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0,0,0,0.7); z-index: 10000;
+ background: rgb(20 50 20 / 65%); z-index: 10000;
display: flex; align-items: center; justify-content: center;
`;
const modal = document.createElement('div');
modal.style.cssText = `
- background: white; border-radius: 8px; padding: 20px;
+ background: #f0f1ec; border-radius: 8px; padding: 20px;
max-width: 300px; margin: 20px; text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
`;
modal.innerHTML = `
-
${message}
+
${message}
`;
@@ -126,6 +127,9 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
// Debug state
const [showDebugInfo, setShowDebugInfo] = useState(false);
+ // Draggable reproductor
+ const { position: playerDragPos, handlePointerDown: onPlayerDragStart } = useDraggable();
+
// Walk session & recording state
const [showAliasPrompt, setShowAliasPrompt] = useState(false);
const [isAudioRecorderVisible, setIsAudioRecorderVisible] = useState(false);
@@ -151,7 +155,7 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
const prev = lastCenteredRef.current;
const curr = userLocation;
if (!prev) {
- mapInstance.setView([curr.lat, curr.lng], 16);
+ mapInstance.setView([curr.lat, curr.lng], 15);
lastCenteredRef.current = { lat: curr.lat, lng: curr.lng };
} else {
const R = 6371e3;
@@ -165,7 +169,7 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
const distance = R * c;
if (distance > 10) { // 10 meters threshold
- mapInstance.setView([curr.lat, curr.lng], 16);
+ mapInstance.setView([curr.lat, curr.lng], 15);
lastCenteredRef.current = { lat: curr.lat, lng: curr.lng };
}
}
@@ -846,7 +850,6 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
const audioUrl = src.type === 'blob' ? URL.createObjectURL(src.blob) : src.url;
const audio = new Audio(audioUrl);
audio.volume = isMuted ? 0 : volume;
- audio.loop = false; // NO LOOPING as requested
audioRefs.current.push(audio);
audioElements.push(audio);
@@ -888,15 +891,30 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
});
await Promise.all(playPromises);
-
- // Set up global end handler (when any audio ends, stop all)
- audioElements.forEach(audio => {
- audio.addEventListener('ended', () => {
- console.log('🏁 Audio ended in Jamm mode, stopping all');
- stopAllAudio();
- });
+
+ // Wait for all durations, then loop shorter files; longest is the leader
+ const durationsReady = audioElements.map(audio =>
+ new Promise(resolve => {
+ if (audio.duration && isFinite(audio.duration)) resolve(audio.duration);
+ else audio.addEventListener('loadedmetadata', () => resolve(audio.duration), { once: true });
+ })
+ );
+ const durations = await Promise.all(durationsReady);
+ const maxDuration = Math.max(...durations);
+ const leaderIndex = durations.indexOf(maxDuration);
+
+ audioElements.forEach((audio, i) => {
+ if (i === leaderIndex) {
+ audio.loop = false;
+ audio.addEventListener('ended', () => {
+ console.log('🏁 Longest audio ended in Jamm mode, stopping all');
+ stopAllAudio();
+ });
+ } else {
+ audio.loop = true;
+ }
});
-
+
console.log('✅ Jamm mode playback started successfully');
}
} catch (error) {
@@ -1088,14 +1106,19 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
const sessions = walkSessionService.getCompletedSessions();
const lines = sessions
.filter(s => s.breadcrumbs && s.breadcrumbs.length >= 2)
- .map(s => ({
- sessionId: s.sessionId,
- positions: s.breadcrumbs.map(b => [b.lat, b.lng]),
- color: userAliasService.aliasToHexColor(s.userAlias),
- alias: s.userAlias,
- title: s.title || 'Deriva sin título',
- recordingCount: s.recordingIds?.length || 0
- }));
+ .map((s, index) => {
+ // Golden-angle hue distribution: each session gets a visually distinct color
+ const hue = Math.round((index * 137.508) % 360);
+ const color = `hsl(${hue}, 70%, 50%)`;
+ return {
+ sessionId: s.sessionId,
+ positions: s.breadcrumbs.map(b => [b.lat, b.lng]),
+ color,
+ alias: s.userAlias,
+ title: s.title || 'Deriva sin título',
+ recordingCount: s.recordingIds?.length || 0
+ };
+ });
setSessionTracklines(lines);
// Initialize all sessions as visible
setVisibleSessionIds(new Set(sessions.map(s => s.sessionId)));
@@ -1306,9 +1329,10 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
{/* Map */}
{/* OpenStreetMap Layer */}
@@ -1350,13 +1374,25 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
opacity={currentLayer === 'StadiaSatellite' ? 1 : 0}
zIndex={currentLayer === 'StadiaSatellite' ? 1 : 0}
/>
+
+
{userLocation && (
<>
>
)}
@@ -1409,19 +1445,20 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
/>
)}
- {/* Saved session tracklines (per-user colored, filtered by visibility) */}
+ {/* Saved session tracklines (per-derive colored, filtered by visibility) */}
{sessionTracklines.filter(track => visibleSessionIds.has(track.sessionId)).map(track => (
+
{track.alias} — {track.title}
{track.recordingCount} grabacion(es)
@@ -1436,7 +1473,7 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
onBackToLanding={handleBackToMenu}
onLocationRefresh={() => {
if (mapInstance && userLocation) {
- mapInstance.setView([userLocation.lat, userLocation.lng], 16);
+ mapInstance.setView([userLocation.lat, userLocation.lng], 15);
lastCenteredRef.current = { lat: userLocation.lat, lng: userLocation.lng };
}
}}
@@ -1477,10 +1514,10 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
width: '56px',
height: '56px',
borderRadius: '50%',
- backgroundColor: '#EF4444',
+ backgroundColor: '#c24a6e',
color: 'white',
border: 'none',
- boxShadow: '0 4px 12px rgba(239,68,68,0.4)',
+ boxShadow: '0 4px 12px rgba(194,74,110,0.4)',
cursor: 'pointer',
display: 'flex',
alignItems: 'center',
@@ -1507,7 +1544,7 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
width: '56px',
height: '56px',
borderRadius: '50%',
- backgroundColor: '#10B981',
+ backgroundColor: '#9dc04cd4',
color: 'white',
border: 'none',
boxShadow: '0 4px 12px rgba(0,0,0,0.3)',
@@ -1530,10 +1567,10 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
position: 'fixed',
bottom: '190px',
left: '50%',
- transform: 'translateX(-50%)',
- backgroundColor: '#ffffffbf',
+ transform: `translate(calc(-50% + ${playerDragPos.x}px), ${playerDragPos.y}px)`,
+ backgroundColor: 'rgba(220,225,235,0.78)',
borderRadius: '16px',
- boxShadow: 'rgb(157 58 58 / 30%) 0px 10px 30px',
+ boxShadow: 'rgba(78,78,134,0.25) 0px 10px 30px',
padding: '20px',
minWidth: '300px',
maxWidth: '400px',
@@ -1552,13 +1589,17 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
color: '#6B7280',
fontSize: '18px',
padding: '4px',
- lineHeight: 1
+ lineHeight: 1,
+ zIndex: 1
}}
title="Cerrar reproductor"
>
✕
-
+
{sessionPlayback ? sessionPlayback.title : 'Reproductor'}
@@ -1579,9 +1620,9 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
Modo de Reproducción:
-
-
-
+
+
+
{currentAudio && (
@@ -1603,22 +1644,34 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
}}
style={{
display: 'flex', alignItems: 'center', gap: '8px',
- backgroundColor: (nearbySpots.length > 0 || selectedSpot) ? '#3B82F6' : '#6B7280',
+ backgroundColor: (nearbySpots.length > 0 || selectedSpot) ? '#4e4e86' : '#6B7280',
color: 'white', border: 'none', borderRadius: '8px', padding: '8px 16px', fontSize: '14px', cursor: (nearbySpots.length > 0 || selectedSpot) ? 'pointer' : 'not-allowed', transition: 'background-color 0.2s'
}}
>
Reproducir
-
-
+
handleVolumeChange(Number(e.target.value))} />
+ setProximityVolumeEnabled(!proximityVolumeEnabled)}
+ style={{
+ backgroundColor: proximityVolumeEnabled ? '#9dc04cd4' : 'rgba(78,78,134,0.15)',
+ color: proximityVolumeEnabled ? 'white' : 'rgb(1 9 2 / 84%)',
+ border: 'none', borderRadius: '6px',
+ padding: '6px 10px', fontSize: '11px', cursor: 'pointer'
+ }}
+ title="Volumen por proximidad"
+ >
+ 📍🔊
+
)}
@@ -1638,13 +1691,13 @@ const SoundWalkAndroid = ({ onBackToLanding, locationPermission: propLocationPer
zIndex: 2000
}}>
-
-
Cargando audio...
+
+
Cargando audio...
)}
diff --git a/src/hooks/useDraggable.js b/src/hooks/useDraggable.js
new file mode 100644
index 0000000..68320a2
--- /dev/null
+++ b/src/hooks/useDraggable.js
@@ -0,0 +1,44 @@
+import { useState, useCallback, useRef } from 'react';
+
+export default function useDraggable() {
+ const [position, setPosition] = useState({ x: 0, y: 0 });
+ const dragging = useRef(false);
+ const startPos = useRef({ x: 0, y: 0 });
+ const startOffset = useRef({ x: 0, y: 0 });
+
+ const handlePointerDown = useCallback((e) => {
+ // Only primary button
+ if (e.button !== 0) return;
+ dragging.current = true;
+ startPos.current = { x: e.clientX, y: e.clientY };
+ startOffset.current = { ...position };
+ e.currentTarget.setPointerCapture(e.pointerId);
+ e.currentTarget.style.cursor = 'grabbing';
+
+ const target = e.currentTarget;
+
+ const handlePointerMove = (ev) => {
+ if (!dragging.current) return;
+ const dx = ev.clientX - startPos.current.x;
+ const dy = ev.clientY - startPos.current.y;
+ setPosition({
+ x: startOffset.current.x + dx,
+ y: startOffset.current.y + dy
+ });
+ };
+
+ const handlePointerUp = () => {
+ dragging.current = false;
+ target.style.cursor = 'grab';
+ document.removeEventListener('pointermove', handlePointerMove);
+ document.removeEventListener('pointerup', handlePointerUp);
+ };
+
+ document.addEventListener('pointermove', handlePointerMove);
+ document.addEventListener('pointerup', handlePointerUp);
+ }, [position]);
+
+ const resetPosition = useCallback(() => setPosition({ x: 0, y: 0 }), []);
+
+ return { position, handlePointerDown, resetPosition };
+}
diff --git a/src/index.css b/src/index.css
index 68e9b32..4002f73 100644
--- a/src/index.css
+++ b/src/index.css
@@ -498,6 +498,33 @@ button,
cursor: pointer;
}
+/* ── Nature-Tech Palette ── */
+:root {
+ --nt-primary: #4e4e86;
+ --nt-primary-alpha: rgba(78,78,134,0.84);
+ --nt-primary-light: #6a6aad;
+ --nt-success: #9dc04cd4;
+ --nt-success-alpha: rgba(157,192,76,0.83);
+ --nt-success-bg: rgba(157,192,76,0.12);
+ --nt-danger: #c24a6e;
+ --nt-danger-hover: #a83d5a;
+ --nt-danger-alpha-low: rgba(194,74,110,0.4);
+ --nt-danger-alpha-high: rgba(194,74,110,0.8);
+ --nt-text-primary: #000000c9;
+ --nt-text-secondary: rgb(1 9 2 / 84%);
+ --nt-panel-bg: rgba(220,225,235,0.78);
+ --nt-bar-btn-bg: rgba(201,206,177,0.50);
+ --nt-bar-bg: rgba(220,225,235,0.92);
+ --nt-card-bg: #f0f1ec;
+ --nt-card-overlay: rgb(20 50 20 / 35%);
+ --nt-modal-overlay: rgb(20 50 20 / 65%);
+ --nt-detail-gradient: rgb(20 50 20 / 85%);
+ --nt-border-light: rgba(78,78,134,0.15);
+ --nt-border-medium: rgba(78,78,134,0.22);
+ --nt-shadow: rgba(78,78,134,0.18);
+ --nt-shadow-panel: rgba(78,78,134,0.25);
+}
+
table {
border-collapse: collapse;
}
@@ -28708,23 +28735,23 @@ table {
/* Custom marker styles */
.marker {
- background-color: #4f46e5;
+ background-color: var(--nt-primary);
border: 2px solid #fff;
border-radius: 50%;
cursor: pointer;
width: 20px;
height: 20px;
- box-shadow: 0 2px 4px rgba(0,0,0,0.3);
+ box-shadow: 0 2px 4px var(--nt-shadow);
}
.marker.selected {
- background-color: #dc2626;
+ background-color: var(--nt-danger);
border-color: #fef2f2;
transform: scale(1.2);
}
.marker.results {
- background-color: #059669;
+ background-color: var(--nt-success);
border-color: #ecfdf5;
transform: scale(1.1);
}
@@ -28768,13 +28795,13 @@ table {
@keyframes microphone-pulse {
0% {
- box-shadow: 0 0 20px rgba(239, 68, 68, 0.6), 0 4px 12px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 20px rgba(194, 74, 110, 0.6), 0 4px 12px var(--nt-shadow);
}
50% {
- box-shadow: 0 0 30px rgba(239, 68, 68, 0.8), 0 4px 12px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 30px rgba(194, 74, 110, 0.8), 0 4px 12px var(--nt-shadow);
}
100% {
- box-shadow: 0 0 20px rgba(239, 68, 68, 0.6), 0 4px 12px rgba(0, 0, 0, 0.15);
+ box-shadow: 0 0 20px rgba(194, 74, 110, 0.6), 0 4px 12px var(--nt-shadow);
}
}
@@ -28786,19 +28813,19 @@ table {
left: 0 !important;
right: 0 !important;
bottom: 0 !important;
- background-color: rgba(0, 0, 0, 0.5) !important;
+ background-color: var(--nt-modal-overlay) !important;
display: flex !important;
align-items: center !important;
justify-content: center !important;
}
.location-permission-content {
- background: white !important;
+ background: var(--nt-card-bg) !important;
border-radius: 8px !important;
padding: 24px !important;
max-width: 400px !important;
margin: 16px !important;
- box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04) !important;
+ box-shadow: 0 20px 25px -5px var(--nt-shadow), 0 10px 10px -5px var(--nt-shadow) !important;
position: relative !important;
z-index: 10000 !important;
}
@@ -28811,7 +28838,7 @@ table {
/* Ensure other components appear above map */
.gradient {
z-index: 1000;
- background: linear-gradient(to top, rgba(0,0,0,0.8) 0%, rgba(0,0,0,0.4) 50%, transparent 100%);
+ background: linear-gradient(to top, var(--nt-detail-gradient) 0%, rgb(20 50 20 / 40%) 50%, transparent 100%);
}
/* TopBar positioning */
@@ -28819,40 +28846,27 @@ table {
z-index: 1001;
}
-/* Leaflet attribution — compact dark */
-.leaflet-control-attribution {
- font-size: 7px !important;
- padding: 1px 4px !important;
- line-height: 1.2 !important;
- background: rgba(0, 0, 0, 0.45) !important;
- color: rgba(255, 255, 255, 0.7) !important;
- border-radius: 4px 0 0 0 !important;
-}
-.leaflet-control-attribution a {
- color: rgba(255, 255, 255, 0.8) !important;
-}
-
/* Leaflet Layer Control Styling */
.leaflet-control-layers {
- background: white !important;
- border: 2px solid rgba(0,0,0,0.2) !important;
+ background: var(--nt-card-bg) !important;
+ border: 2px solid var(--nt-border-light) !important;
border-radius: 4px !important;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
+ box-shadow: 0 2px 4px var(--nt-shadow) !important;
padding: 8px !important;
}
.leaflet-control-layers-toggle {
- background: white !important;
- border: 2px solid rgba(0,0,0,0.2) !important;
+ background: var(--nt-card-bg) !important;
+ border: 2px solid var(--nt-border-light) !important;
border-radius: 4px !important;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
+ box-shadow: 0 2px 4px var(--nt-shadow) !important;
}
.leaflet-control-layers-expanded {
- background: white !important;
- border: 2px solid rgba(0,0,0,0.2) !important;
+ background: var(--nt-card-bg) !important;
+ border: 2px solid var(--nt-border-light) !important;
border-radius: 4px !important;
- box-shadow: 0 2px 4px rgba(0,0,0,0.2) !important;
+ box-shadow: 0 2px 4px var(--nt-shadow) !important;
padding: 8px !important;
min-width: 200px !important;
}
@@ -28870,12 +28884,12 @@ table {
/* Microphone icon styling */
.microphone-button {
- background: #ef4444 !important;
+ background: var(--nt-danger) !important;
color: white !important;
border: 2px solid white !important;
border-radius: 50% !important;
padding: 8px !important;
- box-shadow: 0 4px 8px rgba(0,0,0,0.3) !important;
+ box-shadow: 0 4px 8px var(--nt-shadow) !important;
transition: all 0.3s ease !important;
}
@@ -28914,22 +28928,22 @@ table {
@keyframes microphone-pulse {
0% {
transform: translate(-50%, 50%) scale(1);
- box-shadow: 0 8px 25px rgba(239, 68, 68, 0.5), 0 4px 15px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 8px 25px rgba(194, 74, 110, 0.5), 0 4px 15px var(--nt-shadow);
}
50% {
transform: translate(-50%, 50%) scale(1.05);
- box-shadow: 0 12px 35px rgba(239, 68, 68, 0.7), 0 6px 20px rgba(0, 0, 0, 0.4);
+ box-shadow: 0 12px 35px rgba(194, 74, 110, 0.7), 0 6px 20px var(--nt-shadow);
}
100% {
transform: translate(-50%, 50%) scale(1);
- box-shadow: 0 8px 25px rgba(239, 68, 68, 0.5), 0 4px 15px rgba(0, 0, 0, 0.3);
+ box-shadow: 0 8px 25px rgba(194, 74, 110, 0.5), 0 4px 15px var(--nt-shadow);
}
}
.microphone-button:hover {
- background: #dc2626 !important;
+ background: var(--nt-danger-hover) !important;
transform: scale(1.05) !important;
- box-shadow: 0 6px 12px rgba(0,0,0,0.4) !important;
+ box-shadow: 0 6px 12px var(--nt-shadow) !important;
}
.microphone-button svg {
@@ -28940,13 +28954,13 @@ table {
/* Animation for microphone pulse */
@keyframes microphone-pulse {
0% {
- box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
+ box-shadow: 0 0 0 0 rgba(194, 74, 110, 0.7);
}
70% {
- box-shadow: 0 0 0 10px rgba(239, 68, 68, 0);
+ box-shadow: 0 0 0 10px rgba(194, 74, 110, 0);
}
100% {
- box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
+ box-shadow: 0 0 0 0 rgba(194, 74, 110, 0);
}
}
@@ -28999,7 +29013,7 @@ table {
margin: 0 0 12px 0;
font-size: 18px;
font-weight: 600;
- color: #1F2937;
+ color: #000000c9;
text-align: center;
}
@@ -29019,7 +29033,7 @@ table {
width: 100%;
padding: 8px 12px;
border-radius: 6px;
- border: 2px solid #D1D5DB;
+ border: 2px solid rgba(78,78,134,0.22);
font-size: 16px;
background-color: white;
cursor: pointer;
diff --git a/src/services/AudioRecorder.tsx b/src/services/AudioRecorder.tsx
index 77926be..ca765cc 100644
--- a/src/services/AudioRecorder.tsx
+++ b/src/services/AudioRecorder.tsx
@@ -3,6 +3,7 @@ import { Mic, Square, Play, Pause, Save, X } from 'lucide-react';
import audioService from './audioService.js';
import { VoiceRecorder } from 'capacitor-voice-recorder';
import breadcrumbService from './breadcrumbService.js';
+import useDraggable from '../hooks/useDraggable.js';
// Custom alert function for Android without localhost text
const showAlert = (message) => {
@@ -11,21 +12,21 @@ const showAlert = (message) => {
const overlay = document.createElement('div');
overlay.style.cssText = `
position: fixed; top: 0; left: 0; right: 0; bottom: 0;
- background: rgba(0,0,0,0.7); z-index: 10000;
+ background: rgb(20 50 20 / 65%); z-index: 10000;
display: flex; align-items: center; justify-content: center;
`;
const modal = document.createElement('div');
modal.style.cssText = `
- background: rgba(255, 255, 255, 0.85); border-radius: 8px; padding: 20px;
+ background: rgba(220,225,235,0.92); border-radius: 8px; padding: 20px;
max-width: 300px; margin: 20px; text-align: center;
box-shadow: 0 10px 30px rgba(0,0,0,0.3);
`;
modal.innerHTML = `
-
${message}
+
${message}
OK
`;
@@ -142,6 +143,7 @@ const AudioRecorder = ({
const [nativeRecordingPath, setNativeRecordingPath] = useState
(null);
const [audioBlob, setAudioBlob] = useState(null);
+ const { position: dragPos, handlePointerDown: onDragStart } = useDraggable();
const audioRef = useRef(null);
const timerRef = useRef(null);
@@ -581,14 +583,14 @@ const AudioRecorder = ({
position: 'fixed',
bottom: '190px',
left: '50%',
- transform: 'translateX(-50%)',
+ transform: `translate(calc(-50% + ${dragPos.x}px), ${dragPos.y}px)`,
zIndex: 999999,
pointerEvents: 'auto'
}}>
-
{isRecording ? 'Recording...' : nativeRecordingPath ? 'Review Recording' : 'Audio Recorder'}
@@ -624,7 +630,7 @@ const AudioRecorder = ({
style={{
color: '#6B7280',
backgroundColor: 'transparent',
- border: '1px solid #D1D5DB',
+ border: '1px solid rgba(78,78,134,0.22)',
borderRadius: '4px',
cursor: 'pointer',
padding: '4px 8px',
@@ -677,16 +683,16 @@ const AudioRecorder = ({
{showLogViewer && (
-
+