diff --git a/public/logo.png b/public/logo.png index d2ccc01..021a88f 100644 Binary files a/public/logo.png and b/public/logo.png differ diff --git a/src/assets/airplane-animation.png b/src/assets/airplane-animation.png new file mode 100644 index 0000000..7fa84fe Binary files /dev/null and b/src/assets/airplane-animation.png differ diff --git a/src/lib/timeline/detail/PhotoEditor.svelte b/src/lib/timeline/detail/PhotoEditor.svelte index 24a4d6d..a6a58b5 100644 --- a/src/lib/timeline/detail/PhotoEditor.svelte +++ b/src/lib/timeline/detail/PhotoEditor.svelte @@ -7,18 +7,23 @@ let fileInput; let uploading = $state(false); + let uploadError = $state(''); function remove(index) { onchange(photos.filter((_, i) => i !== index)); + uploadError = ''; } async function addFiles(e) { const files = Array.from(e.currentTarget.files ?? []); if (!files.length) return; uploading = true; + uploadError = ''; try { const urls = await Promise.all(files.map(uploadPhoto)); onchange([...photos, ...urls]); + } catch (err) { + uploadError = err?.message ?? 'Upload failed. Check Firebase Storage rules.'; } finally { uploading = false; e.currentTarget.value = ''; @@ -68,6 +73,10 @@ {/if} + + {#if uploadError} +
{uploadError}
+ {/if} diff --git a/src/lib/world-map/JourneyView.svelte b/src/lib/world-map/JourneyView.svelte index 56ba5ef..3e9222e 100644 --- a/src/lib/world-map/JourneyView.svelte +++ b/src/lib/world-map/JourneyView.svelte @@ -5,7 +5,7 @@ import worldData from 'world-atlas/countries-50m.json'; import { get } from 'svelte/store'; import { journals } from '../stores/journalStore.js'; - import airplaneImg from '../../assets/airplane.png'; + import airplaneImg from '../../assets/airplane-animation.png'; let { onclose, onprogress, mode = 'map', onmodechange } = $props(); diff --git a/src/lib/world-map/WorldMap.svelte b/src/lib/world-map/WorldMap.svelte index 855954b..a38664a 100644 --- a/src/lib/world-map/WorldMap.svelte +++ b/src/lib/world-map/WorldMap.svelte @@ -6,6 +6,7 @@ import { getSelected, setTotalCount, getFlashing } from '../layout/selection.svelte.js'; import { getUserProfile } from '../auth/userStore.svelte.js'; import { nameToId } from '../shared/countries.js'; + import homeIconUrl from '../../assets/home.png'; import crayonCursorUrl from '../../assets/logo-cursor.png'; let { onCountryClick = (_name) => {} } = $props(); @@ -103,6 +104,28 @@ $effect(updateAllFills); + function placeHomeMarker() { + if (!_g || !_pathFn || !_countries) return; + _g.selectAll('.home-marker').remove(); + const name = getUserProfile()?.homeCountry; + if (!name) return; + const found = _countries.find(f => f.properties.name === name); + if (!found) return; + const [cx, cy] = _pathFn.centroid(found); + if (isNaN(cx) || isNaN(cy)) return; + const SIZE = 14; + _g.append('image') + .attr('class', 'home-marker') + .attr('href', homeIconUrl) + .attr('x', cx - SIZE / 2) + .attr('y', cy - SIZE / 2) + .attr('width', SIZE) + .attr('height', SIZE) + .style('pointer-events', 'none'); + } + + $effect(placeHomeMarker); + $effect(() => { const flashSet = getFlashing(); const paths = _paths; // reactive read so effect re-runs when _paths is set @@ -166,7 +189,7 @@ }) .on('mousemove', (event) => { const [x, y] = d3.pointer(event, frameEl); - tooltip.style('left', (x + 10) + 'px').style('top', (y - 28) + 'px'); + tooltip.style('left', (x + 22) + 'px').style('top', (y - 28) + 'px'); }) .on('mouseleave', (event, d) => { const s = getSelected(); @@ -207,6 +230,7 @@ } renderMicrostates(); + placeHomeMarker(); const zoom = d3.zoom() .scaleExtent([1, 32]) @@ -230,8 +254,9 @@ fitProjection(projection, width, height); const countryPaths = _g.selectAll('path'); countryPaths.attr('d', path); - updateFill(countryPaths); + updateAllFills(); renderMicrostates(); + placeHomeMarker(); } });