Spaces:
Runtime error
Runtime error
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate"> | |
| <meta http-equiv="Pragma" content="no-cache"> | |
| <meta http-equiv="Expires" content="0"> | |
| <title>TreeTrack Map - Interactive Field View</title> | |
| <link rel="icon" type="image/png" href="/static/image/icons8-tree-96.png"> | |
| <link rel="apple-touch-icon" href="/static/image/icons8-tree-96.png"> | |
| <!-- Leaflet CSS --> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| <!-- Leaflet MarkerCluster CSS for better grouping --> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.css" /> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet.markercluster@1.4.1/dist/MarkerCluster.Default.css" /> | |
| <link rel="stylesheet" href="/static/css/design-system.css"> | |
| <style> | |
| :root { | |
| /* Remove color overrides - use design system's green nature colors */ | |
| --gray-50: #f8fafc; | |
| --gray-100: #f1f5f9; | |
| --gray-200: #e2e8f0; | |
| --gray-300: #cbd5e1; | |
| --gray-400: #94a3b8; | |
| --gray-500: #64748b; | |
| --gray-600: #475569; | |
| --gray-700: #334155; | |
| --gray-800: #1e293b; | |
| --gray-900: #0f172a; | |
| --green-50: #f0fdf4; | |
| --green-500: #22c55e; | |
| --green-600: #16a34a; | |
| --red-50: #fef2f2; | |
| --red-500: #ef4444; | |
| --red-600: #dc2626; | |
| --orange-500: #f97316; | |
| --orange-600: #ea580c; | |
| /* Spacing */ | |
| --space-1: 0.25rem; | |
| --space-2: 0.5rem; | |
| --space-3: 0.75rem; | |
| --space-4: 1rem; | |
| --space-5: 1.25rem; | |
| --space-6: 1.5rem; | |
| --space-8: 2rem; | |
| /* Radius */ | |
| --radius-sm: 0.375rem; | |
| --radius-md: 0.5rem; | |
| --radius-lg: 0.75rem; | |
| --radius-xl: 1rem; | |
| --radius-2xl: 1.5rem; | |
| /* Shadows */ | |
| --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05); | |
| --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1); | |
| --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1); | |
| --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1); | |
| } | |
| * { | |
| margin: 0; | |
| padding: 0; | |
| box-sizing: border-box; | |
| } | |
| body { | |
| font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; | |
| font-feature-settings: 'cv11' 1, 'ss01' 1; | |
| line-height: 1.6; | |
| color: var(--gray-800); | |
| background: linear-gradient(135deg, #fefefe 0%, #f6f8f4 25%, #edf2e8 100%); | |
| height: 100vh; | |
| overflow: hidden; | |
| -webkit-font-smoothing: antialiased; | |
| -moz-osx-font-smoothing: grayscale; | |
| } | |
| .app-container { | |
| height: 100vh; | |
| display: flex; | |
| flex-direction: column; | |
| } | |
| /* Map-specific header styling with Granim */ | |
| .map-header { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| /* Ensure header content is above background */ | |
| .tt-header-content { | |
| position: relative; | |
| z-index: 2; | |
| } | |
| .header-stats { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-4); | |
| } | |
| .stat-item { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-2); | |
| padding: var(--space-3) var(--space-4); | |
| background: var(--glass-bg-dark); | |
| border-radius: var(--radius-xl); | |
| border: var(--glass-border); | |
| backdrop-filter: var(--glass-blur); | |
| transition: all var(--transition-normal); | |
| } | |
| .stat-item:hover { | |
| background: rgba(255, 255, 255, 0.2); | |
| transform: translateY(-1px); | |
| } | |
| .stat-icon { | |
| font-size: var(--text-lg); | |
| width: 24px; | |
| height: 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| background: rgba(255, 255, 255, 0.2); | |
| border-radius: var(--radius-md); | |
| } | |
| .stat-text { | |
| font-size: var(--text-sm); | |
| font-weight: var(--font-medium); | |
| opacity: 0.9; | |
| } | |
| .stat-number { | |
| font-size: var(--text-lg); | |
| font-weight: var(--font-bold); | |
| color: var(--primary-100); | |
| } | |
| .header-actions { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-3); | |
| } | |
| /* Map-specific adjustments for user info */ | |
| .map-user-info { | |
| padding: var(--space-3) var(--space-5); | |
| } | |
| /* Button styles handled by design system */ | |
| /* Map Container */ | |
| .map-container { | |
| flex: 1; | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| #map { | |
| width: 100%; | |
| height: 100%; | |
| z-index: 1; | |
| } | |
| /* Controls Panel */ | |
| .controls-panel { | |
| position: absolute; | |
| top: var(--space-4); | |
| left: var(--space-4); | |
| z-index: 1000; | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(12px); | |
| border-radius: var(--radius-xl); | |
| border: 1px solid var(--gray-200); | |
| box-shadow: var(--shadow-lg); | |
| overflow: hidden; | |
| } | |
| .controls-header { | |
| padding: var(--space-4); | |
| border-bottom: 1px solid var(--gray-100); | |
| } | |
| .controls-title { | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| color: var(--gray-900); | |
| margin: 0; | |
| } | |
| .controls-content { | |
| padding: var(--space-3); | |
| } | |
| .control-group { | |
| display: flex; | |
| flex-direction: column; | |
| gap: var(--space-2); | |
| } | |
| .control-button { | |
| background: var(--gray-50); | |
| color: var(--gray-700); | |
| border: 1px solid var(--gray-200); | |
| padding: var(--space-3) var(--space-4); | |
| border-radius: var(--radius-md); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.15s ease; | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-2); | |
| } | |
| .control-button:hover { | |
| background: var(--gray-100); | |
| border-color: var(--gray-300); | |
| transform: translateY(-1px); | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .control-button.active { | |
| background: var(--primary-50); | |
| color: var(--primary-700); | |
| border-color: var(--primary-500); | |
| } | |
| /* Location Panel */ | |
| .location-panel { | |
| position: absolute; | |
| bottom: var(--space-4); | |
| left: var(--space-4); | |
| right: var(--space-4); | |
| z-index: 1000; | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(12px); | |
| border-radius: var(--radius-xl); | |
| border: 1px solid var(--gray-200); | |
| box-shadow: var(--shadow-lg); | |
| transform: translateY(100%); | |
| transition: transform 0.3s ease; | |
| } | |
| .location-panel.active { | |
| transform: translateY(0); | |
| } | |
| .location-header { | |
| padding: var(--space-4); | |
| border-bottom: 1px solid var(--gray-100); | |
| } | |
| .location-title { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| color: var(--gray-900); | |
| margin: 0; | |
| } | |
| .location-content { | |
| padding: var(--space-4); | |
| } | |
| .coordinates-grid { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: var(--space-4); | |
| margin-bottom: var(--space-4); | |
| } | |
| .coord-item { | |
| text-align: center; | |
| } | |
| .coord-label { | |
| font-size: 0.75rem; | |
| font-weight: 500; | |
| color: var(--gray-600); | |
| margin-bottom: var(--space-1); | |
| text-transform: uppercase; | |
| letter-spacing: 0.05em; | |
| } | |
| .coord-value { | |
| font-size: 1.125rem; | |
| font-weight: 700; | |
| color: var(--primary-600); | |
| font-variant-numeric: tabular-nums; | |
| } | |
| .location-actions { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr; | |
| gap: var(--space-3); | |
| } | |
| /* Ensure buttons are visible in location panel */ | |
| .location-actions .tt-btn { | |
| width: 100%; | |
| padding: var(--space-3) var(--space-4); | |
| border-radius: var(--radius-md); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| cursor: pointer; | |
| transition: all 0.15s ease; | |
| border: 1px solid transparent; | |
| text-align: center; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| min-height: 44px; | |
| } | |
| .location-actions .tt-btn-primary { | |
| background: var(--primary-600); | |
| color: white; | |
| border-color: var(--primary-600); | |
| } | |
| .location-actions .tt-btn-primary:hover { | |
| background: var(--primary-700); | |
| border-color: var(--primary-700); | |
| transform: translateY(-1px); | |
| } | |
| .location-actions .tt-btn-secondary { | |
| background: var(--gray-100); | |
| color: var(--gray-700); | |
| border-color: var(--gray-300); | |
| } | |
| .location-actions .tt-btn-secondary:hover { | |
| background: var(--gray-200); | |
| border-color: var(--gray-400); | |
| transform: translateY(-1px); | |
| } | |
| /* Messages */ | |
| .message { | |
| position: absolute; | |
| top: var(--space-4); | |
| left: 50%; | |
| transform: translateX(-50%); | |
| padding: var(--space-3) var(--space-6); | |
| border-radius: var(--radius-lg); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| z-index: 1500; | |
| opacity: 0; | |
| transition: opacity 0.3s ease; | |
| pointer-events: none; | |
| } | |
| .message.show { | |
| opacity: 1; | |
| } | |
| .message.success { | |
| background: var(--green-50); | |
| color: var(--green-600); | |
| border: 1px solid var(--green-500); | |
| } | |
| .message.error { | |
| background: var(--red-50); | |
| color: var(--red-600); | |
| border: 1px solid var(--red-500); | |
| } | |
| .message.info { | |
| background: var(--primary-50); | |
| color: var(--primary-600); | |
| border: 1px solid var(--primary-500); | |
| } | |
| /* Loading */ | |
| .loading { | |
| position: absolute; | |
| top: 50%; | |
| left: 50%; | |
| transform: translate(-50%, -50%); | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(8px); | |
| padding: var(--space-6) var(--space-8); | |
| border-radius: var(--radius-xl); | |
| z-index: 2000; | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-3); | |
| box-shadow: var(--shadow-lg); | |
| border: 1px solid var(--gray-200); | |
| } | |
| .spinner { | |
| border: 2px solid var(--gray-200); | |
| border-top: 2px solid var(--primary-500); | |
| border-radius: 50%; | |
| width: 1.5rem; | |
| height: 1.5rem; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| 0% { transform: rotate(0deg); } | |
| 100% { transform: rotate(360deg); } | |
| } | |
| .loading-text { | |
| color: var(--gray-700); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| } | |
| /* Custom Leaflet Overrides */ | |
| .leaflet-control-container { | |
| display: none; | |
| } | |
| .leaflet-popup-content-wrapper { | |
| background: white; | |
| border-radius: var(--radius-lg); | |
| box-shadow: var(--shadow-xl); | |
| border: 1px solid var(--gray-200); | |
| position: relative; | |
| } | |
| .leaflet-popup-tip { | |
| background: white; | |
| border: 1px solid var(--gray-200); | |
| } | |
| .leaflet-popup-content { | |
| margin: 0; | |
| line-height: 1.5; | |
| overflow: hidden; | |
| } | |
| /* Fix popup close button positioning */ | |
| .leaflet-popup-close-button { | |
| position: absolute; | |
| top: 8px; | |
| right: 8px; | |
| z-index: 10; | |
| background: rgba(255, 255, 255, 0.9); | |
| color: var(--gray-600); | |
| border: 1px solid var(--gray-300); | |
| border-radius: 50%; | |
| width: 24px; | |
| height: 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 14px; | |
| line-height: 1; | |
| cursor: pointer; | |
| transition: all 0.15s ease; | |
| text-decoration: none; | |
| font-weight: 400; | |
| box-shadow: var(--shadow-sm); | |
| } | |
| .leaflet-popup-close-button:hover { | |
| background: white; | |
| color: var(--gray-800); | |
| border-color: var(--gray-400); | |
| box-shadow: var(--shadow-md); | |
| transform: scale(1.05); | |
| } | |
| .leaflet-popup-close-button:focus { | |
| outline: 2px solid var(--primary-500); | |
| outline-offset: 2px; | |
| } | |
| /* Tree Popup Content */ | |
| .tree-popup { | |
| padding: var(--space-4); | |
| min-width: 280px; | |
| } | |
| .tree-popup-header { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-3); | |
| margin-bottom: var(--space-3); | |
| padding-bottom: var(--space-3); | |
| border-bottom: 1px solid var(--gray-100); | |
| } | |
| .tree-icon { | |
| font-size: 2rem; | |
| color: var(--green-500); | |
| } | |
| .tree-popup-title { | |
| flex: 1; | |
| } | |
| .tree-name { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| color: var(--gray-900); | |
| margin: 0; | |
| } | |
| .tree-scientific { | |
| font-size: 0.75rem; | |
| color: var(--gray-600); | |
| font-style: italic; | |
| margin-top: var(--space-1); | |
| } | |
| .tree-popup-details { | |
| display: grid; | |
| gap: var(--space-2); | |
| margin-bottom: var(--space-3); | |
| } | |
| .detail-row { | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| font-size: 0.8125rem; | |
| } | |
| .detail-label { | |
| color: var(--gray-600); | |
| font-weight: 500; | |
| } | |
| .detail-value { | |
| color: var(--gray-900); | |
| font-weight: 600; | |
| } | |
| .tree-popup-actions { | |
| display: grid; | |
| grid-template-columns: 1fr 1fr auto; | |
| gap: var(--space-2); | |
| } | |
| .btn-icon { | |
| background: transparent; | |
| border: 1px solid var(--gray-300); | |
| color: var(--gray-600); | |
| padding: var(--space-2); | |
| border-radius: var(--radius-md); | |
| cursor: pointer; | |
| transition: all 0.15s ease; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .btn-icon:hover { | |
| background: var(--gray-50); | |
| border-color: var(--gray-400); | |
| } | |
| .btn-icon.edit:hover { | |
| background: var(--primary-50); | |
| border-color: var(--primary-500); | |
| color: var(--primary-600); | |
| } | |
| .btn-icon.delete:hover { | |
| background: var(--red-50); | |
| border-color: var(--red-500); | |
| color: var(--red-600); | |
| } | |
| /* Mobile Optimizations */ | |
| @media (max-width: 768px) { | |
| .header-content { | |
| padding: var(--space-3) var(--space-4); | |
| } | |
| .header-stats { | |
| gap: var(--space-3); | |
| } | |
| .stat-item { | |
| padding: var(--space-1) var(--space-3); | |
| } | |
| .stat-text { | |
| display: none; | |
| } | |
| .logo { | |
| font-size: 1.25rem; | |
| } | |
| .controls-panel { | |
| top: var(--space-3); | |
| left: var(--space-3); | |
| } | |
| .instructions-panel { | |
| top: var(--space-3); | |
| right: var(--space-3); | |
| width: 280px; | |
| max-width: calc(100vw - 6rem); | |
| } | |
| .location-panel { | |
| bottom: var(--space-3); | |
| left: var(--space-3); | |
| right: var(--space-3); | |
| } | |
| .coordinates-grid { | |
| grid-template-columns: 1fr; | |
| gap: var(--space-3); | |
| } | |
| .location-actions { | |
| grid-template-columns: 1fr; | |
| } | |
| .user-details { | |
| display: none; | |
| } | |
| .tree-popup { | |
| min-width: 260px; | |
| padding: var(--space-3); | |
| } | |
| .tree-popup-actions { | |
| grid-template-columns: 1fr; | |
| gap: var(--space-2); | |
| } | |
| } | |
| /* Gesture Hint */ | |
| .gesture-hint { | |
| position: absolute; | |
| bottom: var(--space-16); | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background: rgba(0, 0, 0, 0.8); | |
| color: white; | |
| padding: var(--space-3) var(--space-6); | |
| border-radius: var(--radius-xl); | |
| font-size: 0.875rem; | |
| font-weight: 500; | |
| z-index: 1000; | |
| animation: fadeInOut 4s ease-in-out; | |
| pointer-events: none; | |
| } | |
| @keyframes fadeInOut { | |
| 0%, 100% { opacity: 0; } | |
| 25%, 75% { opacity: 1; } | |
| } | |
| /* Custom Map Markers */ | |
| .map-marker { | |
| position: relative; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: transform 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55), filter 0.2s ease; | |
| cursor: pointer; | |
| } | |
| /* Realistic Tree Markers */ | |
| .realistic-tree:hover { | |
| transform: scale(1.1) translateY(-2px); | |
| filter: drop-shadow(3px 6px 12px rgba(93, 127, 73, 0.4)) brightness(1.1); | |
| } | |
| .realistic-tree svg { | |
| transition: all 0.3s ease; | |
| } | |
| .realistic-tree:hover svg { | |
| transform: rotate(-1deg); | |
| } | |
| /* Minimal Tree Marker */ | |
| .minimal-tree-marker { | |
| transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| } | |
| .minimal-tree-marker:hover { | |
| transform: scale(1.15) translateY(-2px); | |
| filter: drop-shadow(0 4px 8px rgba(0,0,0,0.3)) brightness(1.1); | |
| } | |
| /* Temp Marker Animation */ | |
| .realistic-tree-temp { | |
| animation: gentle-pulse 2.5s ease-in-out infinite; | |
| } | |
| .realistic-tree-temp:hover { | |
| transform: scale(1.15) translateY(-3px); | |
| filter: drop-shadow(3px 6px 12px rgba(185, 28, 28, 0.5)) brightness(1.1); | |
| animation: none; | |
| } | |
| @keyframes gentle-pulse { | |
| 0%, 100% { | |
| opacity: 1; | |
| transform: scale(1) translateY(0px); | |
| } | |
| 50% { | |
| opacity: 0.85; | |
| transform: scale(1.03) translateY(-1px); | |
| } | |
| } | |
| /* User Location Marker */ | |
| .user-marker { | |
| filter: drop-shadow(0 3px 8px rgba(59, 130, 246, 0.4)); | |
| } | |
| .user-marker:hover { | |
| transform: scale(1.1); | |
| filter: drop-shadow(0 5px 15px rgba(59, 130, 246, 0.6)); | |
| } | |
| /* Enhanced marker clustering effect */ | |
| .leaflet-marker-icon.realistic-tree { | |
| z-index: 1000; | |
| } | |
| .leaflet-marker-icon.realistic-tree-temp { | |
| z-index: 1100; | |
| } | |
| /* Smooth marker entrance animation */ | |
| .leaflet-marker-icon { | |
| animation: marker-appear 0.6s cubic-bezier(0.68, -0.55, 0.265, 1.55); | |
| } | |
| @keyframes marker-appear { | |
| 0% { | |
| opacity: 0; | |
| transform: scale(0) translateY(20px); | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: scale(1) translateY(0px); | |
| } | |
| } | |
| /* Tree Tooltip Improvements */ | |
| .tree-tooltip { | |
| background: rgba(255, 255, 255, 0.95) ; | |
| border: 1px solid var(--gray-200) ; | |
| border-radius: var(--radius-md) ; | |
| box-shadow: var(--shadow-lg) ; | |
| backdrop-filter: blur(8px); | |
| font-size: 0.8125rem; | |
| padding: var(--space-2) var(--space-3) ; | |
| max-width: 200px; | |
| } | |
| .tree-tooltip-content { | |
| text-align: center; | |
| } | |
| .tree-name { | |
| font-weight: 600; | |
| color: var(--gray-900); | |
| margin-bottom: var(--space-1); | |
| } | |
| .tree-details { | |
| font-size: 0.75rem; | |
| color: var(--gray-600); | |
| } | |
| .tree-creator { | |
| font-size: 0.7rem; | |
| color: var(--gray-500); | |
| font-style: italic; | |
| } | |
| /* Performance optimizations */ | |
| .leaflet-tile-pane { | |
| filter: none; | |
| } | |
| .leaflet-overlay-pane { | |
| transform: translate3d(0, 0, 0); | |
| } | |
| /* Instructions Panel */ | |
| .instructions-panel { | |
| position: absolute; | |
| top: var(--space-4); | |
| right: var(--space-4); | |
| z-index: 1000; | |
| background: rgba(255, 255, 255, 0.95); | |
| backdrop-filter: blur(12px); | |
| border-radius: var(--radius-xl); | |
| border: 1px solid var(--gray-200); | |
| box-shadow: var(--shadow-lg); | |
| width: 300px; | |
| max-width: calc(100vw - 2rem); | |
| transition: all 0.3s ease; | |
| } | |
| .instructions-panel.hidden { | |
| transform: translateX(100%); | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| .instructions-header { | |
| padding: var(--space-4); | |
| border-bottom: 1px solid var(--gray-100); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| } | |
| .instructions-title { | |
| font-size: 1rem; | |
| font-weight: 600; | |
| color: var(--gray-900); | |
| margin: 0; | |
| } | |
| .close-instructions { | |
| background: none; | |
| border: none; | |
| font-size: 1.5rem; | |
| color: var(--gray-500); | |
| cursor: pointer; | |
| padding: 0; | |
| width: 24px; | |
| height: 24px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: 50%; | |
| transition: all 0.15s ease; | |
| } | |
| .close-instructions:hover { | |
| background: var(--gray-100); | |
| color: var(--gray-700); | |
| } | |
| .instructions-content { | |
| padding: var(--space-4); | |
| } | |
| .instructions-content p { | |
| margin: 0 0 var(--space-3) 0; | |
| font-size: 0.875rem; | |
| color: var(--gray-700); | |
| line-height: 1.5; | |
| } | |
| .instructions-content p:last-of-type { | |
| margin-bottom: var(--space-4); | |
| } | |
| .tree-legend h4 { | |
| font-size: 0.875rem; | |
| font-weight: 600; | |
| color: var(--gray-900); | |
| margin: 0 0 var(--space-2) 0; | |
| } | |
| .legend-items { | |
| display: grid; | |
| gap: var(--space-1); | |
| } | |
| .legend-item { | |
| display: flex; | |
| align-items: center; | |
| gap: var(--space-2); | |
| font-size: 0.75rem; | |
| color: var(--gray-600); | |
| } | |
| .legend-color { | |
| width: 12px; | |
| height: 12px; | |
| border-radius: 50%; | |
| flex-shrink: 0; | |
| } | |
| /* Instructions Toggle Button */ | |
| .instructions-toggle { | |
| position: absolute; | |
| top: var(--space-4); | |
| right: var(--space-4); | |
| z-index: 1001; | |
| background: var(--primary-600); | |
| color: white; | |
| border: none; | |
| border-radius: 50%; | |
| width: 48px; | |
| height: 48px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| cursor: pointer; | |
| box-shadow: var(--shadow-lg); | |
| transition: all 0.2s ease; | |
| font-size: 1.25rem; | |
| font-weight: 600; | |
| } | |
| .instructions-toggle:hover { | |
| background: var(--primary-700); | |
| transform: scale(1.05); | |
| } | |
| .instructions-toggle.hidden { | |
| opacity: 0; | |
| pointer-events: none; | |
| transform: translateX(100%); | |
| } | |
| /* Focus States */ | |
| .btn:focus-visible, | |
| .control-button:focus-visible, | |
| .btn-icon:focus-visible { | |
| outline: 2px solid var(--primary-500); | |
| outline-offset: 2px; | |
| } | |
| /* HIDE HIDEOUS BLUE CLUSTER COVERAGE POLYGONS */ | |
| .leaflet-cluster-anim .leaflet-marker-icon, | |
| .marker-cluster-small .leaflet-polygon, | |
| .marker-cluster-medium .leaflet-polygon, | |
| .marker-cluster-large .leaflet-polygon, | |
| .leaflet-div-icon .leaflet-polygon, | |
| .leaflet-marker-pane .leaflet-polygon, | |
| path.leaflet-interactive[stroke="#03f"], | |
| path.leaflet-interactive[fill="#03f"] { | |
| display: none ; | |
| opacity: 0 ; | |
| visibility: hidden ; | |
| } | |
| /* Hide cluster coverage areas completely */ | |
| .leaflet-overlay-pane svg path[fill="#0033ff"], | |
| .leaflet-overlay-pane svg path[stroke="#0033ff"], | |
| .leaflet-overlay-pane svg path[fill="blue"], | |
| .leaflet-overlay-pane svg path[stroke="blue"] { | |
| display: none ; | |
| } | |
| </style> | |
| <script> | |
| // Force refresh if we detect cached version | |
| (function() { | |
| const currentVersion = '5.1.1'; | |
| const timestamp = '1762284025'; // Current timestamp for cache busting | |
| const lastVersion = sessionStorage.getItem('treetrack_version'); | |
| const lastTimestamp = sessionStorage.getItem('treetrack_timestamp'); | |
| if (!lastVersion || lastVersion !== currentVersion || !lastTimestamp || lastTimestamp !== timestamp) { | |
| // Clear all storage to force fresh start | |
| sessionStorage.clear(); | |
| localStorage.removeItem('treetrack_cache'); | |
| sessionStorage.setItem('treetrack_version', currentVersion); | |
| sessionStorage.setItem('treetrack_timestamp', timestamp); | |
| if (lastVersion && lastVersion !== currentVersion) { | |
| // Force hard refresh | |
| location.reload(true); | |
| return; | |
| } | |
| } | |
| })(); | |
| </script> | |
| </head> | |
| <body> | |
| <div class="app-container"> | |
| <!-- Header --> | |
| <div class="tt-header map-header" style="background-image: url('https://images.unsplash.com/photo-1441974231531-c6227db76b6e?q=80&w=1920&auto=format&fit=crop&ixlib=rb-4.0.3'); background-size: cover; background-position: center;"> | |
| <div class="tt-header-content"> | |
| <div class="tt-header-brand"> | |
| <div class="tt-header-logo">TreeTrack Map</div> | |
| <div class="tt-header-subtitle">Interactive Field Research View</div> | |
| </div> | |
| <div class="header-stats"> | |
| <div class="stat-item"> | |
| <span class="stat-icon">T</span> | |
| <span class="stat-text">Trees:</span> | |
| <span class="stat-number" id="treeCount">0</span> | |
| </div> | |
| </div> | |
| <div class="tt-header-actions"> | |
| <div class="tt-user-info map-user-info" id="userInfo"> | |
| <div class="tt-user-avatar" id="userAvatar">U</div> | |
| <div class="tt-user-details"> | |
| <div class="tt-user-name" id="userName">Loading...</div> | |
| <div class="tt-user-role" id="userRole">User</div> | |
| </div> | |
| </div> | |
| <a href="/welcome" class="tt-btn tt-btn-secondary" id="welcomeBtn" style="display: none;">Back to Homepage</a> | |
| <a href="/form" class="tt-btn tt-btn-primary">Back to Form</a> | |
| <button id="logoutBtn" class="tt-btn tt-btn-secondary">Logout</button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Map Container --> | |
| <div class="map-container"> | |
| <div id="map"></div> | |
| <!-- Instructions Toggle Button (shows when panel is hidden) --> | |
| <button class="instructions-toggle hidden" id="instructionsToggle" title="Show Map Guide">?</button> | |
| <!-- Instructions Panel --> | |
| <div class="instructions-panel" id="instructionsPanel"> | |
| <div class="instructions-header"> | |
| <h3 class="instructions-title">Tree Map Guide</h3> | |
| <button class="close-instructions" id="closeInstructions" title="Hide/Show Guide">×</button> | |
| </div> | |
| <div class="instructions-content"> | |
| <p>This is the Tree Map of Tezpur.</p> | |
| <p>Zoom in and out to explore data collected from various locations.</p> | |
| <p>Click on individual tree icons to view their details and information.</p> | |
| <div style="margin: 15px 0; padding: 12px; background: rgba(255, 255, 255, 0.1); border-radius: 8px; border-left: 3px solid #22c55e;"> | |
| <h4 style="margin: 0 0 8px 0; font-size: 0.9rem; font-weight: 600;">Tree Clusters</h4> | |
| <p style="margin: 0 0 8px 0; font-size: 0.8rem; line-height: 1.4;">Circles with numbers represent groups of nearby trees:</p> | |
| <ul style="margin: 0; padding-left: 16px; font-size: 0.8rem; color: #6b7280;"> | |
| <li><strong>Small clusters (2-10 trees):</strong> Yellow/orange circles</li> | |
| <li><strong>Medium clusters (10-100 trees):</strong> Orange circles</li> | |
| <li><strong>Large clusters (100+ trees):</strong> Red circles</li> | |
| </ul> | |
| <p style="margin: 8px 0 0 0; font-size: 0.8rem; font-weight: 600;">How to explore clusters:</p> | |
| <ol style="margin: 4px 0 0 0; padding-left: 16px; font-size: 0.8rem; color: #6b7280;"> | |
| <li><strong>Zoom in</strong> to get closer to cluster areas</li> | |
| <li><strong>Click/touch clusters</strong> to expand into spiral view of individual trees</li> | |
| <li><strong>Click individual trees</strong> to view their detailed information</li> | |
| </ol> | |
| </div> | |
| <p>Use the map controls on the left to:</p> | |
| <ul style="margin: 10px 0; padding-left: 20px; font-size: 0.875rem; color: #6b7280;"> | |
| <li>Find your current location</li> | |
| <li>Clear location pins</li> | |
| <li>Center the map view</li> | |
| </ul> | |
| <p style="font-size: 0.8rem; color: #6b7280; margin-top: 12px;">All individual trees are displayed with consistent green markers with brown trunks.</p> | |
| </div> | |
| </div> | |
| <!-- Controls Panel --> | |
| <div class="controls-panel"> | |
| <div class="controls-header"> | |
| <h3 class="controls-title">Map Controls</h3> | |
| </div> | |
| <div class="controls-content"> | |
| <div class="control-group"> | |
| <button id="myLocationBtn" class="control-button"> | |
| <span>Loc</span> | |
| <span>My Location</span> | |
| </button> | |
| <button id="clearPinsBtn" class="control-button"> | |
| <span>Clr</span> | |
| <span>Clear Pins</span> | |
| </button> | |
| <button id="centerMapBtn" class="control-button"> | |
| <span>Ctr</span> | |
| <span>Center View</span> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Location Panel --> | |
| <div class="location-panel" id="locationPanel"> | |
| <div class="location-header"> | |
| <h3 class="location-title">Location Selected</h3> | |
| </div> | |
| <div class="location-content"> | |
| <div class="coordinates-grid"> | |
| <div class="coord-item"> | |
| <div class="coord-label">Latitude</div> | |
| <div class="coord-value" id="latValue">--</div> | |
| </div> | |
| <div class="coord-item"> | |
| <div class="coord-label">Longitude</div> | |
| <div class="coord-value" id="lngValue">--</div> | |
| </div> | |
| </div> | |
| <div class="location-actions"> | |
| <button id="useLocationBtn" class="tt-btn tt-btn-primary"> | |
| Use This Location | |
| </button> | |
| <button id="cancelBtn" class="tt-btn tt-btn-secondary"> | |
| Cancel | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Loading --> | |
| <div id="loading" class="loading" style="display: none;"> | |
| <div class="spinner"></div> | |
| <div class="loading-text">Loading map...</div> | |
| </div> | |
| <!-- Messages --> | |
| <div id="message" class="message"></div> | |
| <!-- Gesture Hint --> | |
| <div class="gesture-hint"> | |
| Tap anywhere on the map to drop a location pin | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Leaflet JS --> | |
| <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script> | |
| <!-- Leaflet MarkerCluster JS for performance and grouping --> | |
| <script src="https://unpkg.com/leaflet.markercluster@1.4.1/dist/leaflet.markercluster.js"></script> | |
| <script src="/static/map.js?v=5.1.1&t=1762284025"></script> | |
| <script> | |
| console.log('🗺️ Map script loaded successfully'); | |
| </script> | |
| </body> | |
| </html> | |