Files
maps/frontend/index.html
Dongho Kim f3f1a568e2 update
2025-12-29 03:44:27 +09:00

1544 lines
45 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" type="image/svg+xml" href="favicon.svg">
<title>Maps App</title>
<style>
/* ========================================
Apple-Style Glassmorphism Design System
======================================== */
:root {
/* Glass backgrounds */
--glass-bg-light: rgba(255, 255, 255, 0.72);
--glass-bg-medium: rgba(255, 255, 255, 0.55);
--glass-bg-dark: rgba(28, 28, 30, 0.75);
/* Borders */
--glass-border: rgba(255, 255, 255, 0.18);
--glass-border-strong: rgba(255, 255, 255, 0.25);
/* Shadows */
--shadow-sm: 0 2px 8px rgba(0, 0, 0, 0.08);
--shadow-md: 0 4px 16px rgba(0, 0, 0, 0.12);
--shadow-lg: 0 8px 32px rgba(0, 0, 0, 0.16);
--shadow-glow: 0 0 20px rgba(255, 255, 255, 0.1);
/* Accent colors */
--accent-blue: #007AFF;
--accent-blue-hover: #0A84FF;
--accent-red: #FF3B30;
--accent-green: #34C759;
/* Text */
--text-primary: rgba(0, 0, 0, 0.85);
--text-secondary: rgba(0, 0, 0, 0.55);
--text-light: rgba(255, 255, 255, 0.95);
--text-light-secondary: rgba(255, 255, 255, 0.7);
/* Spacing */
--radius-sm: 8px;
--radius-md: 12px;
--radius-lg: 16px;
--radius-xl: 20px;
--radius-full: 9999px;
/* Animation */
--transition-fast: 0.15s cubic-bezier(0.4, 0, 0.2, 1);
--transition-smooth: 0.25s cubic-bezier(0.4, 0, 0.2, 1);
--transition-bounce: 0.3s cubic-bezier(0.34, 1.56, 0.64, 1);
}
/* Dark Theme Overrides */
[data-theme="dark"] {
--glass-bg-light: rgba(40, 40, 42, 0.85);
--glass-bg-medium: rgba(50, 50, 52, 0.75);
--glass-bg-dark: rgba(28, 28, 30, 0.9);
--glass-border: rgba(255, 255, 255, 0.1);
--glass-border-strong: rgba(255, 255, 255, 0.18);
--text-primary: rgba(255, 255, 255, 0.95);
--text-secondary: rgba(255, 255, 255, 0.6);
}
[data-theme="dark"] body {
background-color: #0a0a0a;
}
/* Legacy label overrides removed to use new system */
[data-theme="dark"] .beta-tag {
background: rgba(255, 255, 255, 0.15);
color: var(--text-secondary);
}
[data-theme="dark"] .search-box {
background: rgba(255, 255, 255, 0.08);
border-color: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .search-box:focus-within {
background: rgba(255, 255, 255, 0.12);
}
[data-theme="dark"] .search-box input {
color: var(--text-primary);
}
[data-theme="dark"] .search-box input::placeholder {
color: var(--text-secondary);
}
[data-theme="dark"] .hamburger-line {
background: var(--text-primary);
}
[data-theme="dark"] .menu-divider {
background: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .recent-tile {
background: rgba(60, 60, 62, 0.8);
border-color: rgba(255, 255, 255, 0.08);
}
[data-theme="dark"] .icon-btn:hover {
background: rgba(255, 255, 255, 0.1);
}
[data-theme="dark"] .icon-btn:active {
background: rgba(255, 255, 255, 0.15);
}
[data-theme="dark"] .zoom-stack .zoom-divider {
background: rgba(255, 255, 255, 0.1);
}
body {
margin: 0;
overflow: hidden;
background-color: #1a1a1a;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
canvas {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: block;
/* Prevent browser default drag behavior */
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
-webkit-touch-callout: none;
-webkit-tap-highlight-color: transparent;
touch-action: none;
cursor: grab;
}
canvas:active {
cursor: grabbing;
}
/* ========================================
UI Container - Main Control Panel
======================================== */
#ui-container {
position: absolute;
top: 20px;
right: 20px;
display: flex;
flex-direction: column;
gap: 8px;
background: var(--glass-bg-light);
padding: 12px;
border-radius: var(--radius-lg);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid var(--glass-border);
box-shadow:
var(--shadow-lg),
inset 0 0.5px 0 rgba(255, 255, 255, 0.5),
var(--shadow-glow);
color: var(--text-primary);
min-width: 48px;
transition: transform var(--transition-smooth), box-shadow var(--transition-smooth);
}
#ui-container:hover {
box-shadow:
0 12px 40px rgba(0, 0, 0, 0.2),
inset 0 0.5px 0 rgba(255, 255, 255, 0.6),
0 0 30px rgba(255, 255, 255, 0.15);
}
.control-group {
display: flex;
flex-direction: column;
gap: 6px;
}
/* ========================================
Buttons - Glassmorphism Style
======================================== */
button {
background: var(--glass-bg-medium);
color: var(--text-primary);
border: 1px solid var(--glass-border);
padding: 10px 14px;
border-radius: var(--radius-md);
cursor: pointer;
font-size: 15px;
font-weight: 500;
font-family: inherit;
backdrop-filter: blur(10px);
-webkit-backdrop-filter: blur(10px);
box-shadow:
var(--shadow-sm),
inset 0 0.5px 0 rgba(255, 255, 255, 0.4);
transition:
transform var(--transition-fast),
background var(--transition-fast),
box-shadow var(--transition-fast),
border-color var(--transition-fast);
position: relative;
overflow: hidden;
}
button::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: linear-gradient(135deg,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0) 50%,
rgba(0, 0, 0, 0.05) 100%);
pointer-events: none;
}
button:hover {
background: var(--glass-bg-light);
border-color: var(--glass-border-strong);
box-shadow:
var(--shadow-md),
inset 0 0.5px 0 rgba(255, 255, 255, 0.6);
transform: translateY(-1px);
}
button:active {
background: rgba(200, 200, 200, 0.5);
transform: scale(0.97);
box-shadow:
var(--shadow-sm),
inset 0 1px 2px rgba(0, 0, 0, 0.1);
}
/* Zoom Buttons - Compact pill style */
#btn-zoom-in,
#btn-zoom-out {
width: 32px;
height: 32px;
padding: 0;
font-size: 16px;
font-weight: 400;
display: flex;
align-items: center;
justify-content: center;
border-radius: 0;
}
/* Location Button - Neutral by default, blue on hover */
#btn-location {
background: transparent;
color: var(--text-primary);
border: none;
font-weight: 600;
box-shadow: none;
}
#btn-location:hover {
background: linear-gradient(135deg, var(--accent-blue) 0%, #0A84FF 100%);
color: white;
box-shadow:
0 4px 12px rgba(0, 122, 255, 0.35),
inset 0 0.5px 0 rgba(255, 255, 255, 0.25);
transform: translateY(-1px);
}
#btn-location:active {
background: linear-gradient(135deg, #006AE0 0%, var(--accent-blue) 100%);
color: white;
transform: scale(0.97);
}
/* Theme Toggle Button */
#btn-theme {
background: transparent;
color: var(--text-primary);
border: none;
font-weight: 600;
box-shadow: none;
}
#btn-theme:hover {
background: rgba(0, 0, 0, 0.05);
transform: none;
box-shadow: none;
}
#btn-theme:active {
background: rgba(0, 0, 0, 0.1);
transform: none;
}
/* Slider styling */
input[type="range"] {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 6px;
background: rgba(0, 0, 0, 0.1);
border-radius: var(--radius-full);
outline: none;
margin: 8px 0;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
background: white;
border-radius: 50%;
cursor: pointer;
box-shadow:
0 2px 6px rgba(0, 0, 0, 0.2),
0 0 0 1px rgba(0, 0, 0, 0.05);
transition: transform var(--transition-fast), box-shadow var(--transition-fast);
}
input[type="range"]::-webkit-slider-thumb:hover {
transform: scale(1.1);
box-shadow:
0 3px 10px rgba(0, 0, 0, 0.25),
0 0 0 1px rgba(0, 0, 0, 0.08);
}
input[type="range"]::-moz-range-thumb {
width: 20px;
height: 20px;
background: white;
border: none;
border-radius: 50%;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.2);
}
/* ========================================
Debug Info - Minimal Pill Design
======================================== */
#debug-info {
position: absolute;
bottom: 24px;
left: 24px;
color: var(--text-light-secondary);
font-size: 11px;
font-weight: 500;
letter-spacing: 0.2px;
pointer-events: none;
background: var(--glass-bg-dark);
padding: 8px 14px;
border-radius: var(--radius-full);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border: 1px solid rgba(255, 255, 255, 0.1);
box-shadow:
var(--shadow-md),
inset 0 0.5px 0 rgba(255, 255, 255, 0.1);
}
/* ========================================
Compass - Premium Glass Design
======================================== */
#compass {
position: absolute;
top: 20px;
left: 20px;
width: 52px;
height: 52px;
background: var(--glass-bg-light);
border-radius: 50%;
border: 1px solid var(--glass-border);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
display: flex;
align-items: center;
justify-content: center;
color: var(--text-primary);
font-weight: 600;
pointer-events: none;
box-shadow:
var(--shadow-lg),
inset 0 0.5px 0 rgba(255, 255, 255, 0.5),
0 0 0 4px rgba(255, 255, 255, 0.08);
z-index: 100;
transition: transform var(--transition-smooth);
}
.direction {
position: absolute;
font-size: 10px;
font-weight: 700;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", sans-serif;
letter-spacing: 0.5px;
}
.n {
top: 6px;
color: var(--accent-red);
}
.s {
bottom: 6px;
color: var(--text-secondary);
font-size: 9px;
}
.e {
right: 8px;
color: var(--text-secondary);
font-size: 9px;
}
.w {
left: 8px;
color: var(--text-secondary);
font-size: 9px;
}
.compass-center {
width: 6px;
height: 6px;
background: linear-gradient(135deg, #666 0%, #333 100%);
border-radius: 50%;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.3);
z-index: 2;
}
.compass-arrow {
position: absolute;
top: 50%;
left: 50%;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-bottom: 16px solid var(--accent-red);
transform: translate(-50%, -100%);
filter: drop-shadow(0 1px 2px rgba(0, 0, 0, 0.2));
}
/* South arrow (optional visual balance) */
.compass-arrow::after {
content: '';
position: absolute;
top: 16px;
left: -4px;
width: 0;
height: 0;
border-left: 4px solid transparent;
border-right: 4px solid transparent;
border-top: 14px solid rgba(0, 0, 0, 0.2);
}
</style>
<style>
/* Pulse Animation */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(66, 133, 244, 0.6);
}
70% {
box-shadow: 0 0 0 15px rgba(66, 133, 244, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(66, 133, 244, 0);
}
}
#user-location {
position: absolute;
width: 16px;
height: 16px;
background: #4285F4;
border: 2px solid white;
border-radius: 50%;
display: none;
/* Hidden by default */
z-index: 1000;
/* High z-index to stay on top */
pointer-events: none;
box-shadow: 0 0 10px rgba(66, 133, 244, 0.6);
animation: pulse 2s infinite;
top: 0;
left: 0;
backdrop-filter: blur(2px);
}
/* Dark Theme adjustment for location marker */
[data-theme="dark"] #user-location {
background: #5c97f5;
box-shadow: 0 0 12px rgba(92, 151, 245, 0.7);
}
</style>
</head>
<body>
<div id="loading-screen">
<div class="spinner"></div>
<div class="loading-text">Loading Maps...</div>
</div>
<style>
#loading-screen {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: #1a1a1a;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
z-index: 9999;
transition: opacity 0.5s ease-out;
}
#loading-screen.fade-out {
opacity: 0;
pointer-events: none;
}
.spinner {
width: 40px;
height: 40px;
border: 3px solid rgba(255, 255, 255, 0.1);
border-radius: 50%;
border-top-color: #007AFF;
animation: spin 1s ease-in-out infinite;
margin-bottom: 16px;
}
.loading-text {
color: rgba(255, 255, 255, 0.8);
font-family: -apple-system, BlinkMacSystemFont, sans-serif;
font-size: 14px;
font-weight: 500;
letter-spacing: 0.5px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
[data-theme="light"] #loading-screen {
background-color: #f5f5f7;
}
[data-theme="light"] .loading-text {
color: rgba(0, 0, 0, 0.6);
}
[data-theme="light"] .spinner {
border-color: rgba(0, 0, 0, 0.1);
border-top-color: #007AFF;
}
</style>
<div id="user-location"></div>
<div id="compass">
<div class="direction n">N</div>
<div class="direction e">E</div>
<div class="direction s">S</div>
<div class="direction w">W</div>
<div class="compass-arrow"></div>
<div class="compass-center"></div>
</div>
<!-- Canvas for GPU-based label rendering (Apple Maps approach) -->
<canvas id="label-canvas" style="
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
z-index: 10;
background: transparent;
"></canvas>
<div id="labels"></div>
<style>
#labels {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: hidden;
}
.label {
position: absolute;
transform: translate(-50%, -50%);
color: #333;
/* Lighter, cleaner shadow */
text-shadow: 0 0 2px white, 0 0 4px white;
font-family: -apple-system, BlinkMacSystemFont, "SF Pro Text", "Helvetica Neue", sans-serif;
white-space: nowrap;
pointer-events: none;
font-weight: 500;
z-index: 10;
}
.label-country {
font-size: 14px;
font-weight: 600;
text-transform: uppercase;
letter-spacing: 1px;
color: #555;
opacity: 0.8;
}
.label-city {
font-size: 13px;
font-weight: 600;
color: #333;
}
/* Apple Maps-style street labels - Very Clean */
.label-street {
font-size: 10px;
font-weight: 500;
color: #6e6e73;
/* Apple Gray */
letter-spacing: 0.2px;
text-shadow:
0 0 2px rgba(255, 255, 255, 0.95),
0 0 4px rgba(255, 255, 255, 0.9);
}
/* Road type based sizing - larger roads get larger fonts */
.label-street-motorway {
font-size: 13px;
font-weight: 600;
color: #444;
}
.label-street-primary {
font-size: 12px;
font-weight: 600;
color: #555;
}
.label-street-secondary {
font-size: 11px;
font-weight: 500;
color: #5e5e63;
}
.label-street-tertiary {
font-size: 10px;
font-weight: 500;
}
.label-street-residential {
font-size: 9px;
font-weight: 400;
color: #7e7e83;
}
/* One-way direction arrows */
.oneway-arrow {
position: absolute;
font-size: 18px;
font-weight: bold;
color: rgba(100, 100, 100, 0.5);
pointer-events: none;
text-shadow: none;
z-index: 5;
}
[data-theme="dark"] .oneway-arrow {
color: rgba(180, 180, 180, 0.5);
}
/* Apple Maps-style POI labels */
.label-poi {
font-size: 11px;
font-weight: 500;
color: #666;
letter-spacing: 0.1px;
display: flex;
flex-direction: column;
align-items: center;
/* Icon above text */
gap: 2px;
text-shadow: 0 1px 2px rgba(255, 255, 255, 0.9);
}
/* The Icon Circle */
.label-poi::before {
content: '';
width: 8px;
height: 8px;
border-radius: 50%;
background-color: #999;
/* Default Gray */
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
border: 1px solid white;
display: block;
margin-bottom: 2px;
}
/* Specific POI Categories */
/* Parks - Green */
.label-poi-park {
color: #357a38;
}
.label-poi-park::before {
background-color: #4caf50;
}
.label-poi-garden::before {
background-color: #4caf50;
}
/* Restaurants/Food - Orange */
.label-poi-restaurant {
color: #d86800;
}
.label-poi-restaurant::before {
background-color: #ff9800;
}
.label-poi-cafe::before {
background-color: #ff9800;
}
/* Health - Red */
.label-poi-hospital {
color: #c62828;
}
.label-poi-hospital::before {
background-color: #f44336;
}
.label-poi-pharmacy::before {
background-color: #f44336;
}
/* Education - Gray/Purple */
.label-poi-university {
color: #555;
}
.label-poi-university::before {
background-color: #9e9e9e;
}
.label-poi-school::before {
background-color: #9e9e9e;
}
/* Stores - Purple */
.label-poi-shop {
color: #6a1b9a;
}
.label-poi-shop::before {
background-color: #9c27b0;
}
/* Transport - Blue */
.label-poi-fuel {
color: #0277bd;
}
.label-poi-fuel::before {
background-color: #03a9f4;
}
.label-poi-parking::before {
background-color: #03a9f4;
}
/* Tourism - Pink/Brown */
.label-poi-museum {
color: #ad1457;
}
.label-poi-museum::before {
background-color: #e91e63;
}
.label-poi-hotel {
color: #8d6e63;
}
.label-poi-hotel::before {
background-color: #795548;
}
/* Dark Theme Adjustments for Labels */
[data-theme="dark"] .label {
color: #ddd;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.8);
}
[data-theme="dark"] .label-street {
color: #a1a1a6;
text-shadow: 0 0 3px rgba(0, 0, 0, 0.9);
}
[data-theme="dark"] .label-poi {
color: #ccc;
}
[data-theme="dark"] .label-poi::before {
border-color: rgba(255, 255, 255, 0.2);
box-shadow: 0 1px 4px rgba(0, 0, 0, 0.5);
}
/* ========================================
TRANSIT LINE LABELS (S-Bahn, U-Bahn)
Munich-style badges
======================================== */
.label-transit {
font-size: 11px;
font-weight: 700;
letter-spacing: 0.3px;
padding: 4px 8px;
border-radius: 4px;
background: white;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.25);
border: 2px solid currentColor;
text-shadow: none;
}
/* S-Bahn - Green circle badge style */
.label-transit-sbahn {
background: #408335;
color: white;
border: none;
border-radius: 50%;
width: 24px;
height: 24px;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 10px;
font-weight: 800;
}
/* U-Bahn - Blue square badge style */
.label-transit-ubahn {
background: #0065AE;
color: white;
border: none;
border-radius: 4px;
padding: 3px 6px;
font-size: 10px;
font-weight: 800;
}
/* Generic rail - Gray */
.label-transit-rail {
background: #666;
color: white;
border: none;
}
/* Dark theme adjustments */
[data-theme="dark"] .label-transit {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.5);
}
[data-theme="dark"] .label-transit-sbahn {
background: #4a9c3d;
}
[data-theme="dark"] .label-transit-ubahn {
background: #1a7bcb;
}
</style>
<!-- ========================================
LEFT SIDEBAR - Google Style Hamburger Menu
======================================== -->
<div id="left-sidebar">
<!-- Google-style Hamburger/Saved Menu -->
<div id="hamburger-menu">
<button class="hamburger-btn" id="hamburger-toggle" title="Menu">
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
<span class="hamburger-line"></span>
</button>
<div class="menu-divider"></div>
<a href="#" class="menu-item">
<span class="menu-icon">🔖</span>
<span class="menu-label">Saved</span>
</a>
<a href="#" class="menu-item">
<span class="menu-icon">🕐</span>
<span class="menu-label">Recents</span>
</a>
<div class="menu-divider"></div>
<!-- Transportation Toggle -->
<button type="button" class="menu-item" id="btn-transport" title="Toggle Public Transport"
style="background:none; border:none; width:100%; font-family:inherit;">
<span class="menu-icon">🚇</span>
<span class="menu-label">Transit</span>
</button>
<div class="menu-divider"></div>
<!-- Recent locations (placeholder tiles) -->
<div class="recent-tiles">
<div class="recent-tile" title="Recent Location">
<div class="tile-placeholder">📍</div>
</div>
<div class="recent-tile" title="Recent Location">
<div class="tile-placeholder">🏛️</div>
</div>
<div class="recent-tile" title="Recent Location">
<div class="tile-placeholder">⛰️</div>
</div>
</div>
<!-- Get App at bottom -->
</div>
<!-- Slide-out Search Panel (hidden by default) -->
<div id="search-panel" class="hidden">
<div class="panel-header">
<div class="app-logo">
<span class="logo-icon">🗺️</span>
<span class="logo-text">Maps</span>
<span class="beta-tag">BETA</span>
</div>
</div>
<div class="search-box">
<span class="search-icon">🔍</span>
<input type="text" id="search-input" placeholder="Search maps">
</div>
<nav class="nav-menu">
<a href="#" class="nav-item">
<span class="nav-icon">📍</span>
<span>Guides</span>
</a>
<a href="#" class="nav-item">
<span class="nav-icon">↗️</span>
<span>Routes</span>
</a>
</nav>
</div>
</div>
<!-- ========================================
RIGHT CONTROLS - Apple Maps Style
======================================== -->
<div id="right-controls">
<!-- Theme toggle button -->
<div class="control-stack">
<button class="icon-btn" id="btn-theme" title="Toggle Theme">
<svg id="theme-icon-light" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2">
<circle cx="12" cy="12" r="5"></circle>
<line x1="12" y1="1" x2="12" y2="3"></line>
<line x1="12" y1="21" x2="12" y2="23"></line>
<line x1="4.22" y1="4.22" x2="5.64" y2="5.64"></line>
<line x1="18.36" y1="18.36" x2="19.78" y2="19.78"></line>
<line x1="1" y1="12" x2="3" y2="12"></line>
<line x1="21" y1="12" x2="23" y2="12"></line>
<line x1="4.22" y1="19.78" x2="5.64" y2="18.36"></line>
<line x1="18.36" y1="5.64" x2="19.78" y2="4.22"></line>
</svg>
<svg id="theme-icon-dark" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" style="display: none;">
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</button>
</div>
<!-- Location button -->
<div class="control-stack">
<button class="icon-btn" id="btn-location" title="My Location">
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
<polygon points="3,11 22,2 13,21 11,13 3,11"></polygon>
</svg>
</button>
</div>
<!-- Compass -->
<div id="compass-new">
<div class="compass-inner">
<div class="compass-needle"></div>
<span class="compass-n">N</span>
</div>
</div>
<!-- Zoom controls -->
<div class="control-stack zoom-stack">
<button class="icon-btn" id="btn-zoom-in" title="Zoom In">+</button>
<div class="zoom-divider"></div>
<button class="icon-btn" id="btn-zoom-out" title="Zoom Out"></button>
</div>
</div>
<!-- Hidden slider for compatibility -->
<input type="range" id="zoom-slider" min="0" max="100" value="50" style="display:none;">
<!-- Debug info removed for cleaner UI -->
<style>
/* ========================================
LEFT SIDEBAR STYLES
======================================== */
#left-sidebar {
position: absolute;
top: 0;
left: 0;
height: 100%;
display: flex;
z-index: 100;
}
/* Apple Maps-style Search Panel */
#search-panel {
width: 280px;
background: var(--glass-bg-light);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-right: 1px solid var(--glass-border);
padding: 16px;
display: flex;
flex-direction: column;
gap: 16px;
box-shadow: var(--shadow-lg);
/* Liquid animation properties */
transform: translateX(0);
opacity: 1;
visibility: visible;
transition:
transform 0.4s cubic-bezier(0.16, 1, 0.3, 1),
opacity 0.35s cubic-bezier(0.16, 1, 0.3, 1),
visibility 0.4s,
box-shadow 0.3s ease;
will-change: transform, opacity;
}
#search-panel.hidden {
transform: translateX(-100%);
opacity: 0;
visibility: hidden;
box-shadow: none;
}
/* Staggered content animation inside panel */
#search-panel>* {
opacity: 1;
transform: translateX(0);
transition:
opacity 0.3s cubic-bezier(0.16, 1, 0.3, 1),
transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
#search-panel.hidden>* {
opacity: 0;
transform: translateX(-20px);
}
/* Staggered delays for children */
#search-panel>*:nth-child(1) {
transition-delay: 0.02s;
}
#search-panel>*:nth-child(2) {
transition-delay: 0.04s;
}
#search-panel>*:nth-child(3) {
transition-delay: 0.06s;
}
#search-panel>*:nth-child(4) {
transition-delay: 0.08s;
}
#search-panel>*:nth-child(5) {
transition-delay: 0.10s;
}
#search-panel.hidden>*:nth-child(1) {
transition-delay: 0s;
}
#search-panel.hidden>*:nth-child(2) {
transition-delay: 0s;
}
#search-panel.hidden>*:nth-child(3) {
transition-delay: 0s;
}
#search-panel.hidden>*:nth-child(4) {
transition-delay: 0s;
}
#search-panel.hidden>*:nth-child(5) {
transition-delay: 0s;
}
.menu-bottom {
margin-top: auto;
}
.hamburger-btn.active .hamburger-line:nth-child(1) {
transform: translateY(7px) rotate(45deg);
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.hamburger-btn.active .hamburger-line:nth-child(2) {
opacity: 0;
transform: scaleX(0);
transition:
opacity 0.2s ease,
transform 0.25s ease;
}
.hamburger-btn.active .hamburger-line:nth-child(3) {
transform: translateY(-7px) rotate(-45deg);
transition: transform 0.35s cubic-bezier(0.16, 1, 0.3, 1);
}
.app-logo {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.logo-icon {
font-size: 20px;
}
.logo-text {
font-size: 17px;
font-weight: 600;
color: var(--text-primary);
}
.beta-tag {
font-size: 9px;
font-weight: 700;
background: rgba(0, 0, 0, 0.08);
color: var(--text-secondary);
padding: 2px 6px;
border-radius: 4px;
letter-spacing: 0.5px;
}
.search-box {
display: flex;
align-items: center;
gap: 10px;
background: rgba(0, 0, 0, 0.04);
border: 1px solid rgba(0, 0, 0, 0.06);
border-radius: var(--radius-md);
padding: 10px 12px;
transition: all var(--transition-fast);
}
.search-box:focus-within {
background: white;
border-color: var(--accent-blue);
box-shadow: 0 0 0 3px rgba(0, 122, 255, 0.15);
}
.search-icon {
font-size: 14px;
opacity: 0.5;
}
.search-box input {
flex: 1;
border: none;
background: transparent;
font-size: 15px;
font-family: inherit;
color: var(--text-primary);
outline: none;
}
.search-box input::placeholder {
color: var(--text-secondary);
}
.nav-menu {
display: flex;
flex-direction: column;
gap: 4px;
}
.nav-item {
display: flex;
align-items: center;
gap: 12px;
padding: 10px 12px;
border-radius: var(--radius-md);
color: var(--text-primary);
text-decoration: none;
font-size: 15px;
font-weight: 500;
transition: background var(--transition-fast);
}
.nav-item:hover {
background: rgba(0, 0, 0, 0.05);
}
.nav-icon {
font-size: 16px;
}
/* Google-style Hamburger Menu */
#hamburger-menu {
width: 64px;
background: var(--glass-bg-light);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-right: 1px solid var(--glass-border);
display: flex;
flex-direction: column;
align-items: center;
padding: 16px 0;
gap: 8px;
box-shadow: var(--shadow-md);
}
.hamburger-btn {
width: 44px;
height: 44px;
border-radius: var(--radius-md);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 5px;
cursor: pointer;
background: transparent;
border: none;
padding: 0;
box-shadow: none;
}
.hamburger-btn:hover {
background: rgba(0, 0, 0, 0.05);
transform: none;
box-shadow: none;
}
.hamburger-line {
width: 20px;
height: 2px;
background: var(--text-primary);
border-radius: 2px;
transform-origin: center;
transition:
transform 0.35s cubic-bezier(0.16, 1, 0.3, 1),
opacity 0.25s ease,
background 0.2s ease;
}
.menu-divider {
width: 32px;
height: 1px;
background: rgba(0, 0, 0, 0.1);
margin: 8px 0;
}
.menu-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
padding: 10px 8px;
border-radius: var(--radius-md);
color: var(--text-secondary);
text-decoration: none;
transition: all var(--transition-fast);
width: 48px;
}
.menu-item:hover {
background: rgba(0, 0, 0, 0.05);
color: var(--text-primary);
}
.menu-icon {
font-size: 18px;
}
.menu-item.active {
background: rgba(0, 122, 255, 0.1);
color: var(--accent-blue);
}
.menu-item.active .menu-icon {
transform: scale(1.1);
}
.menu-label {
font-size: 10px;
font-weight: 500;
text-align: center;
}
.recent-tiles {
display: flex;
flex-direction: column;
gap: 8px;
margin-top: auto;
padding-bottom: 16px;
}
.recent-tile {
width: 48px;
height: 48px;
background: white;
border-radius: var(--radius-md);
border: 1px solid rgba(0, 0, 0, 0.08);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all var(--transition-fast);
box-shadow: var(--shadow-sm);
}
.recent-tile:hover {
transform: scale(1.05);
box-shadow: var(--shadow-md);
}
.tile-placeholder {
font-size: 20px;
}
/* ========================================
RIGHT CONTROLS STYLES
======================================== */
#right-controls {
position: absolute;
top: 16px;
right: 16px;
display: flex;
flex-direction: column;
gap: 8px;
z-index: 100;
}
.control-stack {
background: var(--glass-bg-light);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-radius: var(--radius-sm);
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-md);
overflow: hidden;
}
.icon-btn {
width: 32px;
height: 32px;
fill: #333;
display: flex;
align-items: center;
justify-content: center;
background: transparent;
border: none;
color: var(--text-primary);
cursor: pointer;
padding: 6px;
font-size: 14px;
font-weight: 300;
transition: background var(--transition-fast);
box-shadow: none;
border-radius: 0;
}
.icon-btn::before {
display: none;
}
.icon-btn:hover {
background: rgba(0, 0, 0, 0.05);
transform: none;
box-shadow: none;
}
.icon-btn:active {
background: rgba(0, 0, 0, 0.1);
transform: none;
}
.icon-btn svg {
width: 16px;
height: 16px;
}
.zoom-stack .zoom-divider {
height: 1px;
background: rgba(0, 0, 0, 0.1);
}
/* New Compass Style */
#compass-new {
width: 32px;
height: 32px;
background: var(--glass-bg-light);
backdrop-filter: blur(20px) saturate(180%);
-webkit-backdrop-filter: blur(20px) saturate(180%);
border-radius: 50%;
border: 1px solid var(--glass-border);
box-shadow: var(--shadow-md);
display: flex;
align-items: center;
justify-content: center;
}
.compass-inner {
position: relative;
width: 20px;
height: 20px;
}
.compass-needle {
position: absolute;
top: 50%;
left: 50%;
width: 2px;
height: 14px;
transform: translate(-50%, -50%);
background: linear-gradient(to bottom,
var(--accent-red) 0%,
var(--accent-red) 50%,
#666 50%,
#666 100%);
border-radius: 1px;
}
.compass-n {
position: absolute;
top: -1px;
left: 50%;
transform: translateX(-50%);
font-size: 7px;
font-weight: 700;
color: var(--accent-red);
}
/* Hide old compass */
#compass {
display: none;
}
</style>
<script type="module">
import init, { run } from './wasm.js?v=transit_hover_v1';
async function main() {
try {
await init();
// run() is auto-called via #[wasm_bindgen(start)] in lib.rs
} catch (e) {
console.error("Wasm failed:", e);
}
}
main();
// Hamburger menu toggle
document.addEventListener('DOMContentLoaded', () => {
const hamburgerBtn = document.getElementById('hamburger-toggle');
const searchPanel = document.getElementById('search-panel');
if (hamburgerBtn && searchPanel) {
hamburgerBtn.addEventListener('click', () => {
searchPanel.classList.toggle('hidden');
hamburgerBtn.classList.toggle('active');
});
}
// Theme toggle functionality
const themeBtn = document.getElementById('btn-theme');
const lightIcon = document.getElementById('theme-icon-light');
const darkIcon = document.getElementById('theme-icon-dark');
// Check for saved theme preference or default to light
const savedTheme = localStorage.getItem('theme') || 'light';
document.documentElement.setAttribute('data-theme', savedTheme);
updateThemeIcon(savedTheme);
if (themeBtn) {
themeBtn.addEventListener('click', () => {
const currentTheme = document.documentElement.getAttribute('data-theme');
const newTheme = currentTheme === 'dark' ? 'light' : 'dark';
document.documentElement.setAttribute('data-theme', newTheme);
localStorage.setItem('theme', newTheme);
updateThemeIcon(newTheme);
// Trigger a resize event to force WASM to redraw with new theme
window.dispatchEvent(new Event('resize'));
});
}
function updateThemeIcon(theme) {
if (lightIcon && darkIcon) {
if (theme === 'dark') {
lightIcon.style.display = 'none';
darkIcon.style.display = 'block';
} else {
lightIcon.style.display = 'block';
darkIcon.style.display = 'none';
}
}
}
});
</script>
</body>
</html>