Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>NASA Worldview</title> | |
| <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css"> | |
| <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" /> | |
| <style> | |
| /* CSS Reset & Base */ | |
| *, *::before, *::after { | |
| box-sizing: border-box; | |
| margin: 0; | |
| padding: 0; | |
| } | |
| :root { | |
| --color-primary: #4eccc4; | |
| --color-primary-dark: #3db8b0; | |
| --color-bg-dark: #1a1d21; | |
| --color-bg-panel: #262b31; | |
| --color-bg-panel-hover: #2f353d; | |
| --color-border: #444; | |
| --color-text: #eee; | |
| --color-text-muted: #888; | |
| --color-text-secondary: #aaa; | |
| --color-accent-red: #b71c1c; | |
| --color-accent-red-hover: #d32f2f; | |
| --sidebar-width: 320px; | |
| --toolbar-height: 56px; | |
| --timeline-height: 64px; | |
| --radius-sm: 4px; | |
| --radius-md: 8px; | |
| --shadow-sm: 0 2px 4px rgba(0,0,0,0.3); | |
| --shadow-md: 0 4px 12px rgba(0,0,0,0.4); | |
| --transition-fast: 0.15s ease; | |
| --transition-normal: 0.3s ease; | |
| } | |
| body, html { | |
| width: 100%; | |
| height: 100%; | |
| overflow: hidden; | |
| font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; | |
| background: var(--color-bg-dark); | |
| color: var(--color-text); | |
| font-size: 14px; | |
| } | |
| /* App Container */ | |
| #app { | |
| position: relative; | |
| width: 100vw; | |
| height: 100vh; | |
| display: flex; | |
| } | |
| /* Built with anycoder link */ | |
| .anycoder-link { | |
| position: fixed; | |
| top: 8px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 10000; | |
| background: rgba(78, 204, 196, 0.15); | |
| border: 1px solid var(--color-primary); | |
| padding: 6px 16px; | |
| border-radius: 20px; | |
| font-size: 12px; | |
| color: var(--color-primary); | |
| text-decoration: none; | |
| backdrop-filter: blur(8px); | |
| transition: all var(--transition-fast); | |
| } | |
| .anycoder-link:hover { | |
| background: var(--color-primary); | |
| color: var(--color-bg-dark); | |
| } | |
| /* Sidebar */ | |
| #wvSidebarEl { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| bottom: var(--timeline-height); | |
| width: var(--sidebar-width); | |
| background: var(--color-bg-panel); | |
| z-index: 1000; | |
| display: flex; | |
| flex-direction: column; | |
| transition: transform var(--transition-normal); | |
| box-shadow: var(--shadow-md); | |
| } | |
| #wvSidebarEl.collapsed { | |
| transform: translateX(-100%); | |
| } | |
| /* Sidebar Header */ | |
| .sidebar-header { | |
| padding: 12px 16px; | |
| border-bottom: 1px solid var(--color-border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| background: var(--color-bg-dark); | |
| } | |
| #wv-logo { | |
| width: 36px; | |
| height: 36px; | |
| background: var(--color-primary); | |
| border-radius: var(--radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-weight: 800; | |
| color: var(--color-bg-dark); | |
| font-size: 13px; | |
| letter-spacing: -0.5px; | |
| cursor: pointer; | |
| transition: transform var(--transition-fast); | |
| } | |
| #wv-logo:hover { | |
| transform: scale(1.05); | |
| } | |
| .sidebar-toggle-btn { | |
| background: none; | |
| border: none; | |
| color: var(--color-text); | |
| cursor: pointer; | |
| font-size: 16px; | |
| width: 32px; | |
| height: 32px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| border-radius: var(--radius-sm); | |
| transition: all var(--transition-fast); | |
| } | |
| .sidebar-toggle-btn:hover { | |
| background: var(--color-bg-panel-hover); | |
| color: var(--color-primary); | |
| } | |
| /* Navigation Tabs */ | |
| .nav-tabs { | |
| display: flex; | |
| border-bottom: 1px solid var(--color-border); | |
| background: var(--color-bg-dark); | |
| } | |
| .nav-item { | |
| flex: 1; | |
| text-align: center; | |
| padding: 14px 0; | |
| cursor: pointer; | |
| color: var(--color-text-muted); | |
| font-size: 13px; | |
| font-weight: 500; | |
| text-transform: uppercase; | |
| letter-spacing: 0.5px; | |
| position: relative; | |
| transition: all var(--transition-fast); | |
| border: none; | |
| background: none; | |
| } | |
| .nav-item:hover { | |
| color: var(--color-text); | |
| background: var(--color-bg-panel-hover); | |
| } | |
| .nav-item.active { | |
| color: var(--color-primary); | |
| } | |
| .nav-item.active::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 2px; | |
| background: var(--color-primary); | |
| } | |
| /* Tab Content */ | |
| .tab-content { | |
| flex: 1; | |
| overflow: hidden; | |
| position: relative; | |
| } | |
| .tab-pane { | |
| position: absolute; | |
| inset: 0; | |
| overflow-y: auto; | |
| opacity: 0; | |
| visibility: hidden; | |
| transition: opacity var(--transition-fast); | |
| } | |
| .tab-pane.active { | |
| opacity: 1; | |
| visibility: visible; | |
| } | |
| /* Scroll Container */ | |
| #layers-scroll-container, #events-scroll-container { | |
| padding: 12px; | |
| } | |
| /* Layer Groups */ | |
| .layer-group { | |
| margin-bottom: 16px; | |
| background: var(--color-bg-dark); | |
| border-radius: var(--radius-md); | |
| overflow: hidden; | |
| } | |
| .layer-group-header { | |
| padding: 12px 14px; | |
| font-weight: 600; | |
| color: var(--color-text); | |
| font-size: 12px; | |
| text-transform: uppercase; | |
| letter-spacing: 0.8px; | |
| cursor: pointer; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| background: var(--color-bg-panel-hover); | |
| transition: background var(--transition-fast); | |
| user-select: none; | |
| } | |
| .layer-group-header:hover { | |
| background: #3a424b; | |
| } | |
| .layer-group-header i { | |
| transition: transform var(--transition-fast); | |
| color: var(--color-text-muted); | |
| font-size: 12px; | |
| } | |
| .layer-group-header.collapsed i { | |
| transform: rotate(-90deg); | |
| } | |
| .group-content { | |
| padding: 8px 0; | |
| } | |
| .group-content.collapsed { | |
| display: none; | |
| } | |
| /* Layer Items */ | |
| .layer-item { | |
| display: flex; | |
| align-items: flex-start; | |
| padding: 10px 14px; | |
| cursor: pointer; | |
| user-select: none; | |
| transition: all var(--transition-fast); | |
| position: relative; | |
| } | |
| .layer-item:hover { | |
| background: rgba(78, 204, 196, 0.05); | |
| } | |
| .layer-item.active { | |
| background: rgba(78, 204, 196, 0.1); | |
| } | |
| .layer-item.active::before { | |
| content: ''; | |
| position: absolute; | |
| left: 0; | |
| top: 0; | |
| bottom: 0; | |
| width: 3px; | |
| background: var(--color-primary); | |
| } | |
| .layer-visibility { | |
| width: 32px; | |
| height: 32px; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| margin-right: 12px; | |
| color: var(--color-text-muted); | |
| transition: color var(--transition-fast); | |
| flex-shrink: 0; | |
| } | |
| .layer-visibility.visible { | |
| color: var(--color-primary); | |
| } | |
| .layer-visibility i { | |
| font-size: 16px; | |
| } | |
| .layer-info { | |
| flex: 1; | |
| min-width: 0; | |
| } | |
| .layer-title { | |
| font-size: 13px; | |
| color: var(--color-text); | |
| font-weight: 500; | |
| line-height: 1.4; | |
| margin-bottom: 2px; | |
| } | |
| .layer-subtitle { | |
| font-size: 11px; | |
| color: var(--color-text-muted); | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| flex-wrap: wrap; | |
| } | |
| .layer-badge { | |
| background: var(--color-bg-panel-hover); | |
| padding: 2px 8px; | |
| border-radius: 12px; | |
| font-size: 10px; | |
| color: var(--color-text-secondary); | |
| } | |
| .layer-notice { | |
| position: absolute; | |
| right: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| width: 18px; | |
| height: 18px; | |
| background: #ff9800; | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 11px; | |
| font-weight: bold; | |
| color: #000; | |
| } | |
| /* Base Layer Items (radio style) */ | |
| .base-layer-item { | |
| padding: 12px 14px; | |
| cursor: pointer; | |
| transition: all var(--transition-fast); | |
| border-left: 3px solid transparent; | |
| } | |
| .base-layer-item:hover { | |
| background: rgba(78, 204, 196, 0.05); | |
| } | |
| .base-layer-item.active { | |
| background: rgba(78, 204, 196, 0.1); | |
| border-left-color: var(--color-primary); | |
| } | |
| .base-layer-title { | |
| font-size: 13px; | |
| color: var(--color-text); | |
| font-weight: 500; | |
| margin-bottom: 4px; | |
| } | |
| .base-layer-title span { | |
| font-weight: 400; | |
| color: var(--color-text-muted); | |
| font-size: 11px; | |
| } | |
| .base-layer-source { | |
| font-size: 11px; | |
| color: var(--color-text-muted); | |
| padding-left: 16px; | |
| position: relative; | |
| } | |
| .base-layer-source::before { | |
| content: ''; | |
| position: absolute; | |
| left: 4px; | |
| top: 6px; | |
| width: 6px; | |
| height: 6px; | |
| background: var(--color-primary); | |
| border-radius: 50%; | |
| opacity: 0; | |
| transition: opacity var(--transition-fast); | |
| } | |
| .base-layer-item.active .base-layer-source::before { | |
| opacity: 1; | |
| } | |
| /* Add Layers Button */ | |
| .add-layers-container { | |
| padding: 16px; | |
| border-top: 1px solid var(--color-border); | |
| } | |
| .add-layers-btn { | |
| width: 100%; | |
| height: 40px; | |
| background: var(--color-accent-red); | |
| border: none; | |
| border-radius: var(--radius-sm); | |
| color: #fff; | |
| font-size: 13px; | |
| font-weight: 500; | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 8px; | |
| transition: all var(--transition-fast); | |
| } | |
| .add-layers-btn:hover { | |
| background: var(--color-accent-red-hover); | |
| } | |
| .add-layers-btn i { | |
| font-size: 12px; | |
| } | |
| /* Events Tab */ | |
| .event-item { | |
| padding: 14px; | |
| border-bottom: 1px solid var(--color-border); | |
| cursor: pointer; | |
| transition: background var(--transition-fast); | |
| } | |
| .event-item:hover { | |
| background: var(--color-bg-panel-hover); | |
| } | |
| .event-category { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 6px; | |
| font-size: 11px; | |
| text-transform: uppercase; | |
| font-weight: 600; | |
| margin-bottom: 8px; | |
| } | |
| .event-category.wildfire { color: #ff5722; } | |
| .event-category.storm { color: #2196f3; } | |
| .event-category.volcano { color: #795548; } | |
| .event-category.flood { color: #03a9f4; } | |
| .event-title { | |
| font-size: 13px; | |
| color: var(--color-text); | |
| font-weight: 500; | |
| margin-bottom: 6px; | |
| line-height: 1.4; | |
| } | |
| .event-meta { | |
| font-size: 11px; | |
| color: var(--color-text-muted); | |
| display: flex; | |
| gap: 16px; | |
| } | |
| /* Main Map Area */ | |
| #wv-map { | |
| flex: 1; | |
| position: relative; | |
| background: #0a0a0a; | |
| } | |
| #mapCanvas { | |
| width: 100%; | |
| height: 100%; | |
| } | |
| /* Toolbar */ | |
| #wvToolbarEl { | |
| position: absolute; | |
| top: 12px; | |
| left: calc(var(--sidebar-width) + 12px); | |
| right: 12px; | |
| height: var(--toolbar-height); | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: center; | |
| pointer-events: none; | |
| z-index: 999; | |
| transition: left var(--transition-normal); | |
| } | |
| #wvToolbarEl.full-width { | |
| left: 12px; | |
| } | |
| .toolbar-left, .toolbar-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| pointer-events: auto; | |
| } | |
| .toolbar-brand { | |
| background: var(--color-bg-panel); | |
| padding: 10px 16px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--color-border); | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .toolbar-brand-title { | |
| font-size: 15px; | |
| font-weight: 600; | |
| color: #fff; | |
| } | |
| .toolbar-brand-subtitle { | |
| font-size: 11px; | |
| color: var(--color-text-muted); | |
| } | |
| .wv-toolbar-button { | |
| width: 44px; | |
| height: 44px; | |
| background: var(--color-bg-panel); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-md); | |
| color: var(--color-text); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all var(--transition-fast); | |
| position: relative; | |
| } | |
| .wv-toolbar-button:hover { | |
| background: var(--color-bg-panel-hover); | |
| border-color: #666; | |
| color: var(--color-primary); | |
| } | |
| .wv-toolbar-button:active { | |
| background: #1e2226; | |
| } | |
| .wv-toolbar-button i { | |
| font-size: 18px; | |
| } | |
| .wv-toolbar-button .badge { | |
| position: absolute; | |
| top: -4px; | |
| right: -4px; | |
| width: 18px; | |
| height: 18px; | |
| background: var(--color-accent-red); | |
| border-radius: 50%; | |
| font-size: 10px; | |
| font-weight: 600; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| color: #fff; | |
| } | |
| /* Search Overlay */ | |
| .search-overlay { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| padding: 16px; | |
| background: var(--color-bg-panel); | |
| border-bottom: 1px solid var(--color-border); | |
| transform: translateY(-100%); | |
| transition: transform var(--transition-normal); | |
| z-index: 1001; | |
| } | |
| .search-overlay.active { | |
| transform: translateY(0); | |
| } | |
| .search-input-container { | |
| display: flex; | |
| gap: 12px; | |
| align-items: center; | |
| } | |
| .search-input { | |
| flex: 1; | |
| height: 44px; | |
| background: var(--color-bg-dark); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-md); | |
| padding: 0 16px; | |
| color: var(--color-text); | |
| font-size: 14px; | |
| } | |
| .search-input:focus { | |
| outline: none; | |
| border-color: var(--color-primary); | |
| } | |
| .search-close { | |
| width: 44px; | |
| height: 44px; | |
| background: none; | |
| border: none; | |
| color: var(--color-text); | |
| cursor: pointer; | |
| font-size: 18px; | |
| } | |
| /* Timeline */ | |
| #timeline-container { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: var(--timeline-height); | |
| background: var(--color-bg-panel); | |
| border-top: 1px solid var(--color-border); | |
| display: flex; | |
| align-items: center; | |
| justify-content: space-between; | |
| padding: 0 20px; | |
| z-index: 1000; | |
| gap: 20px; | |
| } | |
| .timeline-left, .timeline-right { | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .timeline-btn { | |
| width: 40px; | |
| height: 40px; | |
| background: none; | |
| border: none; | |
| color: var(--color-text-muted); | |
| cursor: pointer; | |
| border-radius: var(--radius-sm); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| transition: all var(--transition-fast); | |
| } | |
| .timeline-btn:hover { | |
| color: var(--color-text); | |
| background: var(--color-bg-panel-hover); | |
| } | |
| .timeline-btn i { | |
| font-size: 16px; | |
| } | |
| .timeline-btn.now-btn { | |
| width: auto; | |
| padding: 0 16px; | |
| font-size: 12px; | |
| font-weight: 600; | |
| letter-spacing: 0.5px; | |
| color: var(--color-text); | |
| } | |
| .timeline-date-container { | |
| flex: 1; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .timeline-date { | |
| font-size: 18px; | |
| font-weight: 700; | |
| color: #fff; | |
| letter-spacing: 1px; | |
| font-family: 'SF Mono', Monaco, monospace; | |
| cursor: pointer; | |
| padding: 10px 20px; | |
| border-radius: var(--radius-sm); | |
| transition: all var(--transition-fast); | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .timeline-date:hover { | |
| background: var(--color-bg-panel-hover); | |
| } | |
| .timeline-date i { | |
| font-size: 14px; | |
| color: var(--color-primary); | |
| } | |
| .animate-btn { | |
| color: var(--color-primary) ; | |
| background: rgba(78, 204, 196, 0.1) ; | |
| } | |
| .animate-btn:hover { | |
| background: rgba(78, 204, 196, 0.2) ; | |
| } | |
| .animate-btn.playing { | |
| animation: pulse 1.5s ease-in-out infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 1; } | |
| 50% { opacity: 0.7; } | |
| } | |
| /* Date Picker Modal */ | |
| .datepicker-modal { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0,0,0,0.7); | |
| z-index: 10000; | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| backdrop-filter: blur(4px); | |
| } | |
| .datepicker-modal.active { | |
| display: flex; | |
| } | |
| .datepicker { | |
| background: var(--color-bg-panel); | |
| border-radius: var(--radius-md); | |
| padding: 24px; | |
| min-width: 320px; | |
| box-shadow: var(--shadow-md); | |
| } | |
| .datepicker-header { | |
| text-align: center; | |
| font-size: 18px; | |
| font-weight: 600; | |
| margin-bottom: 20px; | |
| color: var(--color-primary); | |
| } | |
| .datepicker-inputs { | |
| display: flex; | |
| gap: 12px; | |
| margin-bottom: 20px; | |
| } | |
| .datepicker-inputs select, .datepicker-inputs input { | |
| flex: 1; | |
| height: 44px; | |
| background: var(--color-bg-dark); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-sm); | |
| padding: 0 12px; | |
| color: var(--color-text); | |
| font-size: 14px; | |
| } | |
| .datepicker-actions { | |
| display: flex; | |
| gap: 12px; | |
| } | |
| .datepicker-actions button { | |
| flex: 1; | |
| height: 44px; | |
| border-radius: var(--radius-sm); | |
| border: none; | |
| cursor: pointer; | |
| font-size: 14px; | |
| font-weight: 500; | |
| transition: all var(--transition-fast); | |
| } | |
| .datepicker-ok { | |
| background: var(--color-primary); | |
| color: var(--color-bg-dark); | |
| } | |
| .datepicker-ok:hover { | |
| background: var(--color-primary-dark); | |
| } | |
| .datepicker-cancel { | |
| background: var(--color-bg-dark); | |
| color: var(--color-text); | |
| } | |
| .datepicker-cancel:hover { | |
| background: var(--color-bg-panel-hover); | |
| } | |
| /* Mobile Zoom Buttons */ | |
| .mobile-zoom { | |
| position: absolute; | |
| bottom: 80px; | |
| right: 16px; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| z-index: 998; | |
| } | |
| .mobile-zoom button { | |
| width: 48px; | |
| height: 48px; | |
| background: var(--color-bg-panel); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-md); | |
| color: var(--color-text); | |
| cursor: pointer; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 20px; | |
| transition: all var(--transition-fast); | |
| } | |
| .mobile-zoom button:hover { | |
| background: var(--color-bg-panel-hover); | |
| color: var(--color-primary); | |
| } | |
| /* Coordinates Display */ | |
| .coords-display { | |
| position: absolute; | |
| bottom: 80px; | |
| left: 16px; | |
| background: var(--color-bg-panel); | |
| padding: 10px 16px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--color-border); | |
| font-size: 12px; | |
| color: var(--color-text-muted); | |
| font-family: 'SF Mono', Monaco, monospace; | |
| z-index: 998; | |
| display: none; | |
| } | |
| .coords-display.active { | |
| display: block; | |
| } | |
| /* Alert Dropdown */ | |
| .alert-dropdown { | |
| position: absolute; | |
| top: 72px; | |
| right: 16px; | |
| background: var(--color-bg-panel); | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--color-border); | |
| box-shadow: var(--shadow-md); | |
| z-index: 999; | |
| min-width: 280px; | |
| display: none; | |
| } | |
| .alert-dropdown.active { | |
| display: block; | |
| } | |
| .alert-header { | |
| padding: 14px 16px; | |
| border-bottom: 1px solid var(--color-border); | |
| display: flex; | |
| align-items: center; | |
| gap: 10px; | |
| font-size: 13px; | |
| font-weight: 500; | |
| } | |
| .alert-header i { | |
| color: #ff9800; | |
| } | |
| .alert-item { | |
| padding: 12px 16px; | |
| border-bottom: 1px solid var(--color-border); | |
| font-size: 12px; | |
| color: var(--color-text-secondary); | |
| cursor: pointer; | |
| transition: background var(--transition-fast); | |
| } | |
| .alert-item:hover { | |
| background: var(--color-bg-panel-hover); | |
| } | |
| .alert-item:last-child { | |
| border-bottom: none; | |
| } | |
| /* Comparison Mode Bar */ | |
| .comparison-bar { | |
| position: absolute; | |
| top: 72px; | |
| left: 50%; | |
| transform: translateX(-50%) translateY(-100%); | |
| background: var(--color-bg-panel); | |
| padding: 12px 20px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--color-border); | |
| display: flex; | |
| align-items: center; | |
| gap: 20px; | |
| z-index: 998; | |
| transition: transform var(--transition-normal); | |
| } | |
| .comparison-bar.active { | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| .comparison-title { | |
| font-size: 13px; | |
| font-weight: 600; | |
| } | |
| .comparison-modes { | |
| display: flex; | |
| gap: 8px; | |
| } | |
| .comparison-mode { | |
| padding: 8px 16px; | |
| background: var(--color-bg-dark); | |
| border: 1px solid var(--color-border); | |
| border-radius: var(--radius-sm); | |
| color: var(--color-text-muted); | |
| font-size: 12px; | |
| cursor: pointer; | |
| transition: all var(--transition-fast); | |
| } | |
| .comparison-mode:hover, .comparison-mode.active { | |
| color: var(--color-primary); | |
| border-color: var(--color-primary); | |
| } | |
| /* Loading Spinner */ | |
| .map-loading { | |
| position: absolute; | |
| inset: 0; | |
| background: var(--color-bg-dark); | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 997; | |
| transition: opacity var(--transition-normal); | |
| } | |
| .map-loading.hidden { | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| .spinner { | |
| width: 48px; | |
| height: 48px; | |
| border: 3px solid var(--color-border); | |
| border-top-color: var(--color-primary); | |
| border-radius: 50%; | |
| animation: spin 1s linear infinite; | |
| } | |
| @keyframes spin { | |
| to { transform: rotate(360deg); } | |
| } | |
| /* Toast Notification */ | |
| .toast { | |
| position: fixed; | |
| bottom: 100px; | |
| left: 50%; | |
| transform: translateX(-50%) translateY(100px); | |
| background: var(--color-bg-panel); | |
| padding: 14px 24px; | |
| border-radius: var(--radius-md); | |
| border: 1px solid var(--color-primary); | |
| color: var(--color-text); | |
| font-size: 13px; | |
| z-index: 10001; | |
| transition: transform var(--transition-normal); | |
| display: flex; | |
| align-items: center; | |
| gap: 12px; | |
| } | |
| .toast.active { | |
| transform: translateX(-50%) translateY(0); | |
| } | |
| .toast i { | |
| color: var(--color-primary); | |
| } | |
| /* Responsive */ | |
| @media (max-width: 768px) { | |
| :root { | |
| --sidebar-width: 100%; | |
| } | |
| #wvSidebarEl { | |
| bottom: 0; | |
| transform: translateX(-100%); | |
| } | |
| #wvSidebarEl.mobile-open { | |
| transform: translateX(0); | |
| } | |
| #wvSidebarEl.collapsed { | |
| transform: translateX(-100%); | |
| } | |
| #wvToolbarEl { | |
| left: 12px ; | |
| } | |
| .toolbar-brand { | |
| display: none; | |
| } | |
| .mobile-only { | |
| display: flex ; | |
| } | |
| #timeline-container { | |
| padding: 0 12px; | |
| } | |
| .timeline-date { | |
| font-size: 14px; | |
| padding: 8px 12px; | |
| } | |
| } | |
| @media (min-width: 769px) { | |
| .mobile-only { | |
| display: none ; | |
| } | |
| } | |
| /* Utility */ | |
| .sr-only { | |
| position: absolute; | |
| width: 1px; | |
| height: 1px; | |
| padding: 0; | |
| margin: -1px; | |
| overflow: hidden; | |
| clip: rect(0, 0, 0, 0); | |
| white-space: nowrap; | |
| border: 0; | |
| } | |
| /* Custom Scrollbar */ | |
| ::-webkit-scrollbar { | |
| width: 8px; | |
| height: 8px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: var(--color-bg-dark); | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: var(--color-border); | |
| border-radius: 4px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: #555; | |
| } | |
| /* Leaflet Customization */ | |
| .leaflet-control-attribution { | |
| background: var(--color-bg-panel) ; | |
| color: var(--color-text-muted) ; | |
| font-size: 10px ; | |
| padding: 4px 8px ; | |
| border-radius: var(--radius-sm) 0 0 0 ; | |
| } | |
| .leaflet-control-attribution a { | |
| color: var(--color-primary) ; | |
| } | |
| /* Projection Toggle Modal */ | |
| .projection-modal { | |
| position: fixed; | |
| inset: 0; | |
| background: rgba(0,0,0,0.7); | |
| z-index: 10000; | |
| display: none; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .projection-modal.active { | |
| display: flex; | |
| } | |
| .projection-grid { | |
| display: grid; | |
| grid-template-columns: repeat(3, 1fr); | |
| gap: 16px; | |
| background: var(--color-bg-panel); | |
| padding: 24px; | |
| border-radius: var(--radius-md); | |
| } | |
| .projection-option { | |
| width: 120px; | |
| height: 120px; | |
| background: var(--color-bg-dark); | |
| border: 2px solid var(--color-border); | |
| border-radius: var(--radius-md); | |
| cursor: pointer; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 12px; | |
| transition: all var(--transition-fast); | |
| } | |
| .projection-option:hover, .projection-option.active { | |
| border-color: var(--color-primary); | |
| } | |
| .projection-option i { | |
| font-size: 40px; | |
| color: var(--color-text-muted); | |
| } | |
| .projection-option.active i { | |
| color: var(--color-primary); | |
| } | |
| .projection-option span { | |
| font-size: 12px; | |
| color: var(--color-text); | |
| } | |
| /* Measure Tool */ | |
| .measure-tooltip { | |
| background: var(--color-bg-panel); | |
| border: 1px solid var(--color-primary); | |
| color: var(--color-text); | |
| padding: 8px 12px; | |
| border-radius: var(--radius-sm); | |
| font-size: 12px; | |
| white-space: nowrap; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <a href="https://huggingface.co/spaces/akhaliq/anycoder" target="_blank" class="anycoder-link">Built with anycoder</a> | |
| <div id="app"> | |
| <!-- Sidebar --> | |
| <section id="wvSidebarEl"> | |
| <div class="sidebar-header"> | |
| <div id="wv-logo" title="Reset to Defaults">WV</div> | |
| <button class="sidebar-toggle-btn" id="collapseSidebarBtn" aria-label="Toggle Sidebar"> | |
| <i class="fa-solid fa-chevron-left"></i> | |
| </button> | |
| </div> | |
| <div class="nav-tabs"> | |
| <button class="nav-item active" data-tab="layers">Layers</button> | |
| <button class="nav-item" data-tab="events">Events</button> | |
| </div> | |
| <div class="tab-content"> | |
| <!-- Layers Tab --> | |
| <div class="tab-pane active" id="layers-tab"> | |
| <div id="layers-scroll-container"> | |
| <!-- Reference Layers --> | |
| <div class="layer-group"> | |
| <div class="layer-group-header" onclick="toggleGroup(this)"> | |
| <span>Reference</span> | |
| <i class="fa-solid fa-chevron-down"></i> | |
| </div> | |
| <div class="group-content"> | |
| <div class="layer-item" data-layer="labels" onclick="toggleOverlay('labels', this)"> | |
| <div class="layer-visibility" id="labels-visibility"> | |
| <i class="fa-solid fa-eye-slash"></i> | |
| </div> | |
| <div class="layer-info"> | |
| <div class="layer-title">Place Labels</div> | |
| <div class="layer-subtitle">Esri, TomTom, OpenStreetMap</div> | |
| </div> | |
| </div> | |
| <div class="layer-item" data-layer="features" onclick="toggleOverlay('features', this)"> | |
| <div class="layer-visibility" id="features-visibility"> | |
| <i class="fa-solid fa-eye-slash"></i> | |
| </div> | |
| <div class="layer-info"> | |
| <div class="layer-title">Coastlines / Borders / Roads</div> | |
| <div class="layer-subtitle">© OpenStreetMap contributors</div> | |
| </div> | |
| </div> | |
| <div class="layer-item active" data-layer="coastlines" onclick="toggleOverlay('coastlines', this)"> | |
| <div class="layer-visibility visible" id="coastlines-visibility"> | |
| <i class="fa-solid fa-eye"></i> | |
| </div> | |
| <div class="layer-info"> | |
| <div class="layer-title">Coastlines</div> | |
| <div class="layer-subtitle">© OpenStreetMap contributors</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Base Layers --> | |
| <div class="layer-group"> | |
| <div class="layer-group-header" onclick="toggleGroup(this)"> | |
| <span>Base Layers</span> | |
| <i class="fa-solid fa-chevron-down"></i> | |
| </div> | |
| <div class="group-content"> | |
| <div class="base-layer-item" data-base="pace" onclick="setBaseLayer('pace', this)"> | |
| <div class="base-layer-title">Corrected Reflectance (True Color) <span>PACE / OCI</span></div> | |
| <div class="base-layer-source">Ocean Color Instrument</div> | |
| </div> | |
| <div class="base-layer-item" data-base="noaa21" onclick="setBaseLayer('noaa21', this)"> | |
| <div class="base-layer-title">Corrected Reflectance (True Color) <span>NOAA-21 / VIIRS</span></div> | |
| <div class="base-layer-source">Visible Infrared Imaging Radiometer Suite</div> | |
| <div class="layer-notice">!</div> | |
| </div> | |
| <div class="base-layer-item" data-base="noaa20" onclick="setBaseLayer('noaa20', this)"> | |
| <div class="base-layer-title">Corrected Reflectance (True Color) <span>NOAA-20 / VIIRS</span></div> | |
| <div class="base-layer-source">Visible Infrared Imaging Radiometer Suite</div> | |
| </div> | |
| <div class="base-layer-item" data-base="snpp" onclick="setBaseLayer('snpp', this)"> | |
| <div class="base-layer-title">Corrected Reflectance (True Color) <span>Suomi NPP / VIIRS</span></div> | |
| <div class="base-layer-source">Visible Infrared Imaging Radiometer Suite</div> | |
| </div> | |
| <div class="base-layer-item" data-base="aqua" onclick="setBaseLayer('aqua', this)"> | |
| <div class="base-layer-title">Corrected Reflectance (True Color) <span>Aqua / MODIS</span></div> | |
| <div class="base-layer-source">Moderate Resolution Imaging Spectroradiometer</div> | |
| </div> | |
| <div class="base-layer-item active" data-base="terra" onclick="setBaseLayer('terra', this)"> | |
| <div class="base-layer-title">Corrected Reflectance (True Color) <span>Terra / MODIS</span></div> | |
| <div class="base-layer-source">Moderate Resolution Imaging Spectroradiometer <span class="layer-badge">v6.1 NRT</span></div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="add-layers-container"> | |
| <button class="add-layers-btn" onclick="showAddLayers()"> | |
| <i class="fa-solid fa-plus"></i> | |
| Add Layers | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Events Tab --> | |
| <div class="tab-pane" id="events-tab"> | |
| <div id="events-scroll-container"> | |
| <div class="event-item" onclick="flyToEvent(-34.6037, -58.3816, 'Wildfire in Argentina')"> | |
| <div class="event-category wildfire"> | |
| <i class="fa-solid fa-fire"></i> | |
| Wildfire | |
| </div> | |
| <div class="event-title">Wildfire in Buenos Aires Province</div> | |
| <div class="event-meta"> | |
| <span><i class="fa-regular fa-clock"></i> 2 hours ago</span> | |
| <span><i class="fa-solid fa-location-dot"></i> Argentina</span> | |
| </div> | |
| </div> | |
| <div class="event-item" onclick="flyToEvent(35.6762, 139.6503, 'Typhoon approaching Japan')"> | |
| <div class="event-category storm"> | |
| <i class="fa-solid fa-hurricane"></i> | |
| Severe Storm | |
| </div> | |
| <div class="event-title">Typhoon Khanun approaching Honshu</div> | |
| <div class="event-meta"> | |
| <span><i class="fa-regular fa-clock"></i> 5 hours ago</span> | |
| <span><i class="fa-solid fa-location-dot"></i> Japan</span> | |
| </div> | |
| </div> | |
| <div class="event-item" onclick="flyToEvent(64.9631, -19.0208, 'Volcanic eruption in Iceland')"> | |
| <div class="event-category volcano"> | |
| <i class="fa-solid fa-volcano"></i> | |
| Volcano | |
| </div> | |
| <div class="event-title">Fagradalsfjall eruption continues</div> | |
| <div class="event-meta"> | |
| <span><i class="fa-regular fa-clock"></i> 1 day ago</span> | |
| <span><i class="fa-solid fa-location-dot"></i> Iceland</span> | |
| </div> | |
| </div> | |
| <div class="event-item" onclick="flyToEvent(23.8103, 90.4125, 'Flooding in Bangladesh')"> | |
| <div class="event-category flood"> | |
| <i class="fa-solid fa-water"></i> | |
| Flood | |
| </div> | |
| <div class="event-title">Monsoon flooding in Dhaka region</div> | |
| <div class="event-meta"> | |
| <span><i class="fa-regular fa-clock"></i> 2 days ago</span> | |
| <span><i class="fa-solid fa-location-dot"></i> Bangladesh</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </section> | |
| <!-- Mobile Sidebar Toggle --> | |
| <button class="mobile-only wv-toolbar-button" style="position: fixed; top: 12px; left: 12px; z-index: 1001;" onclick="toggleMobileSidebar()"> | |
| <i class="fa-solid fa-layer-group"></i> | |
| <span class="badge">9</span> | |
| </button> | |
| <!-- Toolbar --> | |
| <div id="wvToolbarEl"> | |
| <div class="toolbar-left"> | |
| <div class="toolbar-brand"> | |
| <div> | |
| <div class="toolbar-brand-title">NASA Worldview</div> | |
| <div class="toolbar-brand-subtitle">Earth Science Data System</div> | |
| </div> | |
| </div> | |
| </div> | |
| <div class="toolbar-right"> | |
| <button class="wv-toolbar-button" title="Location Search" onclick="toggleSearch()"> | |
| <i class="fa-solid fa-magnifying-glass-location"></i> | |
| </button> | |
| <button class="wv-toolbar-button" title="Share" onclick="shareMap()"> | |
| <i class="fa-solid fa-share-from-square"></i> | |
| </button> | |
| <button class="wv-toolbar-button" title="Switch Projection" onclick="toggleProjectionModal()"> | |
| <i class="fa-solid fa-earth-asia"></i> | |
| </button> | |
| <button class="wv-toolbar-button" title="Take Snapshot" onclick="takeSnapshot()"> | |
| <i class="fa-solid fa-camera"></i> | |
| </button> | |
| <button class="wv-toolbar-button" title="Information" onclick="showInfo()"> | |
| <i class="fa-solid fa-circle-info"></i> | |
| <span class="badge">2</span> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Search Overlay --> | |
| <div class="search-overlay" id="searchOverlay"> | |
| <div class="search-input-container"> | |
| <input type="text" class="search-input" id="searchInput" placeholder="Search for places, coordinates, or features..." onkeypress="handleSearchKey(event)"> | |
| <button class="search-close" onclick="toggleSearch()"> | |
| <i class="fa-solid fa-xmark"></i> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Map --> | |
| <div id="wv-map"> | |
| <div id="mapCanvas"></div> | |
| <div class="map-loading" id="mapLoading"> | |
| <div class="spinner"></div> | |
| </div> | |
| </div> | |
| <!-- Mobile Zoom Controls --> | |
| <div class="mobile-zoom"> | |
| <button onclick="map.zoomIn()" title="Zoom In"> | |
| <i class="fa-solid fa-plus"></i> | |
| </button> | |
| <button onclick="map.zoomOut()" title="Zoom Out"> | |
| <i class="fa-solid fa-minus"></i> | |
| </button> | |
| </div> | |
| <!-- Coordinates Display --> | |
| <div class="coords-display" id="coordsDisplay"> | |
| <span id="coordsText">0.0000°, 0.0000°</span> | |
| </div> | |
| <!-- Alert Dropdown --> | |
| <div class="alert-dropdown" id="alertDropdown"> | |
| <div class="alert-header"> | |
| <i class="fa-solid fa-triangle-exclamation"></i> | |
| <span>Layer Notifications</span> | |
| </div> | |
| <div class="alert-item" onclick="showAlertDetail('noaa21')"> | |
| <strong>NOAA-21 VIIRS</strong> - Data availability delayed by 2 hours | |
| </div> | |
| <div class="alert-item" onclick="showAlertDetail('terra')"> | |
| <strong>Terra MODIS</strong> - Orbital maintenance scheduled | |
| </div> | |
| </div> | |
| <!-- Comparison Mode Bar --> | |
| <div class="comparison-bar" id="comparisonBar"> | |
| <span class="comparison-title">Comparison Mode</span> | |
| <div class="comparison-modes"> | |
| <button class="comparison-mode active" onclick="setComparisonMode('swipe')">Swipe</button> | |
| <button class="comparison-mode" onclick="setComparisonMode('opacity')">Opacity</button> | |
| <button class="comparison-mode" onclick="setComparisonMode('spy')">Spy</button> | |
| </div> | |
| <button class="wv-toolbar-button" style="width: 32px; height: 32px;" onclick="toggleComparison()"> | |
| <i class="fa-solid fa-xmark"></i> | |
| </button> | |
| </div> | |
| <!-- Timeline --> | |
| <div id="timeline-container"> | |
| <div class="timeline-left"> | |
| <button class="timeline-btn" title="Previous Day" onclick="changeDate(-1)"> | |
| <i class="fa-solid fa-arrow-left"></i> | |
| </button> | |
| <button class="timeline-btn now-btn" title="Go to Today" onclick="goToToday()"> | |
| NOW | |
| </button> | |
| <button class="timeline-btn" title="Next Day" onclick="changeDate(1)"> | |
| <i class="fa-solid fa-arrow-right"></i> | |
| </button> | |
| </div> | |
| <div class="timeline-date-container"> | |
| <div class="timeline-date" onclick="openDatePicker()"> | |
| <span id="dateDisplay">2026 JAN 29</span> | |
| <i class="fa-solid fa-calendar"></i> | |
| </div> | |
| </div> | |
| <div class="timeline-right"> | |
| <button class |