fix: styles and add zooming into location
This commit is contained in:
161
src/app.css
161
src/app.css
@@ -1,121 +1,54 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@import 'tw-animate-css';
|
||||
|
||||
@custom-variant dark (&:is(.dark *));
|
||||
|
||||
:root {
|
||||
--radius: 0.625rem;
|
||||
--background: oklch(1 0 0);
|
||||
--foreground: oklch(0.141 0.005 285.823);
|
||||
--card: oklch(1 0 0);
|
||||
--card-foreground: oklch(0.141 0.005 285.823);
|
||||
--popover: oklch(1 0 0);
|
||||
--popover-foreground: oklch(0.141 0.005 285.823);
|
||||
--primary: oklch(0.21 0.006 285.885);
|
||||
--primary-foreground: oklch(0.985 0 0);
|
||||
--secondary: oklch(0.967 0.001 286.375);
|
||||
--secondary-foreground: oklch(0.21 0.006 285.885);
|
||||
--muted: oklch(0.967 0.001 286.375);
|
||||
--muted-foreground: oklch(0.552 0.016 285.938);
|
||||
--accent: oklch(0.967 0.001 286.375);
|
||||
--accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--destructive: oklch(0.577 0.245 27.325);
|
||||
--border: oklch(0.92 0.004 286.32);
|
||||
--input: oklch(0.92 0.004 286.32);
|
||||
--ring: oklch(0.705 0.015 286.067);
|
||||
--chart-1: oklch(0.646 0.222 41.116);
|
||||
--chart-2: oklch(0.6 0.118 184.704);
|
||||
--chart-3: oklch(0.398 0.07 227.392);
|
||||
--chart-4: oklch(0.828 0.189 84.429);
|
||||
--chart-5: oklch(0.769 0.188 70.08);
|
||||
--sidebar: oklch(0.985 0 0);
|
||||
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
||||
--sidebar-primary: oklch(0.21 0.006 285.885);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.967 0.001 286.375);
|
||||
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
||||
--sidebar-border: oklch(0.92 0.004 286.32);
|
||||
--sidebar-ring: oklch(0.705 0.015 286.067);
|
||||
@font-face {
|
||||
font-family: 'Washington';
|
||||
src: url('/fonts/Washington.ttf') format('truetype');
|
||||
font-weight: normal;
|
||||
font-style: normal;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: oklch(0.141 0.005 285.823);
|
||||
--foreground: oklch(0.985 0 0);
|
||||
--card: oklch(0.21 0.006 285.885);
|
||||
--card-foreground: oklch(0.985 0 0);
|
||||
--popover: oklch(0.21 0.006 285.885);
|
||||
--popover-foreground: oklch(0.985 0 0);
|
||||
--primary: oklch(0.92 0.004 286.32);
|
||||
--primary-foreground: oklch(0.21 0.006 285.885);
|
||||
--secondary: oklch(0.274 0.006 286.033);
|
||||
--secondary-foreground: oklch(0.985 0 0);
|
||||
--muted: oklch(0.274 0.006 286.033);
|
||||
--muted-foreground: oklch(0.705 0.015 286.067);
|
||||
--accent: oklch(0.274 0.006 286.033);
|
||||
--accent-foreground: oklch(0.985 0 0);
|
||||
--destructive: oklch(0.704 0.191 22.216);
|
||||
--border: oklch(1 0 0 / 10%);
|
||||
--input: oklch(1 0 0 / 15%);
|
||||
--ring: oklch(0.552 0.016 285.938);
|
||||
--chart-1: oklch(0.488 0.243 264.376);
|
||||
--chart-2: oklch(0.696 0.17 162.48);
|
||||
--chart-3: oklch(0.769 0.188 70.08);
|
||||
--chart-4: oklch(0.627 0.265 303.9);
|
||||
--chart-5: oklch(0.645 0.246 16.439);
|
||||
--sidebar: oklch(0.21 0.006 285.885);
|
||||
--sidebar-foreground: oklch(0.985 0 0);
|
||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||
--sidebar-accent: oklch(0.274 0.006 286.033);
|
||||
--sidebar-accent-foreground: oklch(0.985 0 0);
|
||||
--sidebar-border: oklch(1 0 0 / 10%);
|
||||
--sidebar-ring: oklch(0.552 0.016 285.938);
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
--radius-sm: calc(var(--radius) - 4px);
|
||||
--radius-md: calc(var(--radius) - 2px);
|
||||
--radius-lg: var(--radius);
|
||||
--radius-xl: calc(var(--radius) + 4px);
|
||||
--color-background: var(--background);
|
||||
--color-foreground: var(--foreground);
|
||||
--color-card: var(--card);
|
||||
--color-card-foreground: var(--card-foreground);
|
||||
--color-popover: var(--popover);
|
||||
--color-popover-foreground: var(--popover-foreground);
|
||||
--color-primary: var(--primary);
|
||||
--color-primary-foreground: var(--primary-foreground);
|
||||
--color-secondary: var(--secondary);
|
||||
--color-secondary-foreground: var(--secondary-foreground);
|
||||
--color-muted: var(--muted);
|
||||
--color-muted-foreground: var(--muted-foreground);
|
||||
--color-accent: var(--accent);
|
||||
--color-accent-foreground: var(--accent-foreground);
|
||||
--color-destructive: var(--destructive);
|
||||
--color-border: var(--border);
|
||||
--color-input: var(--input);
|
||||
--color-ring: var(--ring);
|
||||
--color-chart-1: var(--chart-1);
|
||||
--color-chart-2: var(--chart-2);
|
||||
--color-chart-3: var(--chart-3);
|
||||
--color-chart-4: var(--chart-4);
|
||||
--color-chart-5: var(--chart-5);
|
||||
--color-sidebar: var(--sidebar);
|
||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||
--color-sidebar-primary: var(--sidebar-primary);
|
||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||
--color-sidebar-accent: var(--sidebar-accent);
|
||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||
--color-sidebar-border: var(--sidebar-border);
|
||||
--color-sidebar-ring: var(--sidebar-ring);
|
||||
body {
|
||||
font-family:
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
sans-serif;
|
||||
line-height: 1.5;
|
||||
background-color: #f8f8f8;
|
||||
color: #333;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
* {
|
||||
@apply border-border outline-ring/50;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
h1 {
|
||||
font-family: 'Washington', serif;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: 'Times New Roman', Times, serif;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
button {
|
||||
cursor: pointer;
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
input {
|
||||
font-family: inherit;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { MapLibre } from 'svelte-maplibre';
|
||||
import { MapLibre, Marker } from 'svelte-maplibre';
|
||||
import type { StyleSpecification } from 'svelte-maplibre';
|
||||
import { coordinates, getMapCenter, getMapZoom } from '$lib/stores/location';
|
||||
import {
|
||||
coordinates,
|
||||
getMapCenter,
|
||||
getMapZoom,
|
||||
shouldZoomToLocation,
|
||||
locationActions
|
||||
} from '$lib/stores/location';
|
||||
import LocationButton from './LocationButton.svelte';
|
||||
|
||||
interface Props {
|
||||
@@ -41,16 +47,46 @@
|
||||
|
||||
// Reactive center and zoom based on location or props
|
||||
const mapCenter = $derived(
|
||||
$coordinates && autoCenter
|
||||
$coordinates && (autoCenter || $shouldZoomToLocation)
|
||||
? ([$coordinates.longitude, $coordinates.latitude] as [number, number])
|
||||
: center || $getMapCenter
|
||||
);
|
||||
|
||||
const mapZoom = $derived($coordinates && autoCenter ? zoom || $getMapZoom : zoom || 13);
|
||||
const mapZoom = $derived(() => {
|
||||
if ($shouldZoomToLocation && $coordinates) {
|
||||
// Force zoom to calculated level when location button is clicked
|
||||
return $getMapZoom;
|
||||
}
|
||||
if ($coordinates && autoCenter) {
|
||||
return $getMapZoom;
|
||||
}
|
||||
return zoom || 13;
|
||||
});
|
||||
|
||||
// Effect to clear zoom trigger after it's been used
|
||||
$effect(() => {
|
||||
if ($shouldZoomToLocation) {
|
||||
// Use a timeout to ensure the map has updated before clearing the trigger
|
||||
setTimeout(() => {
|
||||
locationActions.clearZoomTrigger();
|
||||
}, 100);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<div class="map-container {className}">
|
||||
<MapLibre {style} center={mapCenter} zoom={mapZoom} />
|
||||
<MapLibre {style} center={mapCenter} zoom={mapZoom()}>
|
||||
{#if $coordinates}
|
||||
<Marker lngLat={[$coordinates.longitude, $coordinates.latitude]}>
|
||||
<div class="location-marker">
|
||||
<div class="marker-pulse"></div>
|
||||
<div class="marker-outer">
|
||||
<div class="marker-inner"></div>
|
||||
</div>
|
||||
</div>
|
||||
</Marker>
|
||||
{/if}
|
||||
</MapLibre>
|
||||
|
||||
{#if showLocationButton}
|
||||
<div class="location-controls">
|
||||
|
||||
@@ -12,6 +12,7 @@ interface LocationState {
|
||||
error: LocationError | null;
|
||||
isWatching: boolean;
|
||||
lastUpdated: Date | null;
|
||||
shouldZoomToLocation: boolean;
|
||||
}
|
||||
|
||||
const initialState: LocationState = {
|
||||
@@ -19,7 +20,8 @@ const initialState: LocationState = {
|
||||
status: 'idle',
|
||||
error: null,
|
||||
isWatching: false,
|
||||
lastUpdated: null
|
||||
lastUpdated: null,
|
||||
shouldZoomToLocation: false
|
||||
};
|
||||
|
||||
// Main location store
|
||||
@@ -37,6 +39,10 @@ export const hasLocationAccess = derived(
|
||||
locationStore,
|
||||
($location) => $location.coordinates !== null
|
||||
);
|
||||
export const shouldZoomToLocation = derived(
|
||||
locationStore,
|
||||
($location) => $location.shouldZoomToLocation
|
||||
);
|
||||
|
||||
// Location actions
|
||||
export const locationActions = {
|
||||
@@ -58,7 +64,8 @@ export const locationActions = {
|
||||
coordinates,
|
||||
status: 'success',
|
||||
error: null,
|
||||
lastUpdated: new Date()
|
||||
lastUpdated: new Date(),
|
||||
shouldZoomToLocation: true
|
||||
}));
|
||||
|
||||
return coordinates;
|
||||
@@ -169,6 +176,16 @@ export const locationActions = {
|
||||
|
||||
const ageInMinutes = (Date.now() - currentState.lastUpdated.getTime()) / (1000 * 60);
|
||||
return ageInMinutes > maxAgeMinutes;
|
||||
},
|
||||
|
||||
/**
|
||||
* Clear the zoom trigger flag
|
||||
*/
|
||||
clearZoomTrigger(): void {
|
||||
locationStore.update((state) => ({
|
||||
...state,
|
||||
shouldZoomToLocation: false
|
||||
}));
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,14 +199,17 @@ export const getMapCenter = derived(coordinates, ($coordinates) => {
|
||||
});
|
||||
|
||||
// Utility function to get appropriate zoom level based on accuracy
|
||||
export const getMapZoom = derived(coordinates, ($coordinates) => {
|
||||
export const getMapZoom = derived([coordinates, shouldZoomToLocation], ([$coordinates, $shouldZoom]) => {
|
||||
if ($coordinates?.accuracy) {
|
||||
// More aggressive zoom levels when location button is clicked
|
||||
const baseZoom = $shouldZoom ? 2 : 0; // Add 2 zoom levels when triggered by button
|
||||
|
||||
// Adjust zoom based on accuracy (lower accuracy = lower zoom)
|
||||
if ($coordinates.accuracy < 10) return 18; // Very accurate
|
||||
if ($coordinates.accuracy < 50) return 16; // Good accuracy
|
||||
if ($coordinates.accuracy < 100) return 14; // Moderate accuracy
|
||||
if ($coordinates.accuracy < 500) return 12; // Low accuracy
|
||||
return 10; // Very low accuracy
|
||||
if ($coordinates.accuracy < 10) return Math.min(20, 18 + baseZoom); // Very accurate
|
||||
if ($coordinates.accuracy < 50) return Math.min(19, 16 + baseZoom); // Good accuracy
|
||||
if ($coordinates.accuracy < 100) return Math.min(18, 14 + baseZoom); // Moderate accuracy
|
||||
if ($coordinates.accuracy < 500) return Math.min(16, 12 + baseZoom); // Low accuracy
|
||||
return Math.min(15, 10 + baseZoom); // Very low accuracy
|
||||
}
|
||||
return 13; // Default zoom level
|
||||
return $shouldZoom ? 16 : 13; // More aggressive default when triggered by button
|
||||
});
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
<style>
|
||||
.home-container {
|
||||
background-color: #f8f8f8;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
@@ -29,7 +28,7 @@
|
||||
background: white;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.map-section :global(.map-container) {
|
||||
|
||||
Reference in New Issue
Block a user