Jordglob, spara plats och delade pins på stadsnivå
- Platsrutan: MapLibre GL 6.11 (självhostad, verifierad mot npm) med globprojektion, dag-/nattskugga i realtid med mjuk skymning, Natural Earth-konturer, röd nål, scroll- och knappzoom - "Spara som min plats" (bara i webbläsaren) i stället för automatisk sparning - "Dela min pin": öppet anonymt index i Cloudflare D1. Servern avrundar till 0,1°, slår själv upp stad/land (ingen fritext), en pin per ägare, borttagbar, spärr via saltad IP-hash som går ut - vendor/ markerad som -text i .gitattributes (filerna hålls byte-exakta) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
074d96566e
commit
aeb1c76428
+147
-32
@@ -6,10 +6,7 @@
|
||||
<title>publiccloud.se</title>
|
||||
<meta name="description" content="Himlen över Örebro – följer solen och det aktuella vädret.">
|
||||
<meta name="theme-color" content="#0b0d14">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.css"
|
||||
integrity="sha512-Zcn6bjR/8RZbLEpLIeOwNtzREBAJnUKESxces60Mpoj+2okopSAcSUIUOseddDm0cxnGQzxIR7vJgsLZbdLE3w==" crossorigin="anonymous">
|
||||
<script defer src="https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.9.4/leaflet.js"
|
||||
integrity="sha512-BwHfrr4c9kmRkLw6iXFdzcdWV/PGkVgiIyIWLLlTSXzWQzxuSg4DiQUCpauz/EWjgk5TYQqX/kvn9pG1NpYfqg==" crossorigin="anonymous"></script>
|
||||
<link rel="stylesheet" href="vendor/maplibre-6.11.2/maplibre-gl.css">
|
||||
<style>
|
||||
:root { --bg: #0b0d14; --fg: rgba(255,255,255,.85); }
|
||||
html, body { margin: 0; height: 100%; background: var(--bg); overflow: hidden; }
|
||||
@@ -75,18 +72,22 @@
|
||||
.panel .row { display: flex; justify-content: center; flex-wrap: wrap; gap: 4px 12px; align-items: baseline; }
|
||||
.grade { display: inline-block; padding: 1px 9px; border-radius: 999px; font-size: .82em; background: rgba(255,255,255,.16); white-space: nowrap; }
|
||||
.grade.good { background: rgba(80, 200, 140, .38); } .grade.ok { background: rgba(230, 190, 80, .38); } .grade.poor { background: rgba(225, 95, 85, .38); }
|
||||
#map { height: clamp(110px, 22vh, 270px); border-radius: 10px; overflow: hidden; margin-bottom: 10px; background: #1b2030; }
|
||||
#map .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(.85) contrast(.9) saturate(.5); }
|
||||
#map .leaflet-control-attribution { background: rgba(10,14,28,.6); color: rgba(255,255,255,.7); font-size: 10px; }
|
||||
/* zoom buttons in the same glass style as the panels */
|
||||
#map .leaflet-bar { border: 1px solid rgba(255,255,255,.28); border-radius: 8px; overflow: hidden; box-shadow: none; }
|
||||
#map .leaflet-bar a { width: 32px; height: 32px; line-height: 30px; font-size: 20px; font-weight: 400;
|
||||
background: rgba(10,14,28,.62); color: #fff; border-bottom: 1px solid rgba(255,255,255,.2);
|
||||
backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); }
|
||||
#map .leaflet-bar a:last-child { border-bottom: 0; }
|
||||
#map .leaflet-bar a:hover { background: rgba(40,48,70,.85); color: #fff; }
|
||||
#map .leaflet-bar a.leaflet-disabled { color: rgba(255,255,255,.35); background: rgba(10,14,28,.62); }
|
||||
#map .leaflet-control-attribution a { color: #9cc3ff; }
|
||||
/* the globe (MapLibre, globe projection): transparent around the sphere so the glass shows through */
|
||||
#map { height: clamp(150px, 26vh, 300px); border-radius: 10px; overflow: hidden; margin-bottom: 10px; background: transparent; position: relative; }
|
||||
#map .maplibregl-ctrl-attrib { background: rgba(10,14,28,.6); color: rgba(255,255,255,.7); font-size: 10px; }
|
||||
#map .maplibregl-ctrl-attrib a { color: #9cc3ff; }
|
||||
#map .maplibregl-ctrl-attrib-button { filter: invert(1); background-color: transparent; }
|
||||
/* own + / − buttons in the glass style */
|
||||
#map .gz { position: absolute; top: 8px; left: 8px; z-index: 2; display: flex; flex-direction: column;
|
||||
border: 1px solid rgba(255,255,255,.28); border-radius: 8px; overflow: hidden; }
|
||||
#map .gz button { width: 32px; height: 32px; padding: 0; border: 0; border-radius: 0; font: 400 20px/1 system-ui, sans-serif;
|
||||
color: #fff; background: rgba(10,14,28,.62); backdrop-filter: blur(6px); -webkit-backdrop-filter: blur(6px); cursor: pointer; }
|
||||
#map .gz button + button { border-top: 1px solid rgba(255,255,255,.2); }
|
||||
#map .gz button:hover { background: rgba(40,48,70,.85); }
|
||||
.me-pin { width: 24px; height: 34px; filter: drop-shadow(0 2px 3px rgba(0,0,0,.55)); cursor: default; }
|
||||
#pin-tip { position: absolute; z-index: 3; pointer-events: none; padding: 4px 9px; border-radius: 7px; font-size: .8em; white-space: nowrap;
|
||||
background: rgba(10,14,28,.8); color: #fff; transform: translate(-50%, -140%); display: none; }
|
||||
.panel .btns.small button { font-size: .82em; padding: 5px 10px; }
|
||||
.btns { display: flex; gap: 8px; flex-wrap: wrap; margin-top: 10px; justify-content: center; }
|
||||
.panel button { font: inherit; font-size: .9em; color: #fff; background: rgba(255,255,255,.14); border: 1px solid rgba(255,255,255,.28);
|
||||
border-radius: 8px; padding: 6px 12px; cursor: pointer; }
|
||||
@@ -167,13 +168,18 @@
|
||||
</section>
|
||||
<section class="panel tr" aria-label="Plats">
|
||||
<h2>Plats</h2>
|
||||
<div id="map" role="application" aria-label="Karta – klicka för att välja plats"></div>
|
||||
<div id="map" role="application" aria-label="Jordglob – klicka för att välja plats"><div id="pin-tip"></div></div>
|
||||
<div class="row"><strong id="p-place">Örebro slott</strong><span class="note" id="p-coords" style="margin:0"></span></div>
|
||||
<div class="btns">
|
||||
<button id="b-mine" type="button">Utgå från min plats</button>
|
||||
<button id="b-home" type="button">Örebro slott</button>
|
||||
</div>
|
||||
<div class="note" id="p-loc-note">Klicka i kartan för att välja en annan plats.</div>
|
||||
<div class="btns small">
|
||||
<button id="b-save" type="button">Spara som min plats</button>
|
||||
<button id="b-share" type="button">Dela min pin</button>
|
||||
</div>
|
||||
<div class="note" id="p-loc-note">Klicka på globen för att välja en annan plats.</div>
|
||||
<div class="note" id="p-pins-note"></div>
|
||||
</section>
|
||||
<section class="panel bl" aria-label="Natten">
|
||||
<h2>Kväll & natt</h2>
|
||||
@@ -193,7 +199,11 @@
|
||||
// (GPS) or click the map; everything (sun, moon, weather, aurora) is then computed for that place. Saved per browser.
|
||||
const HOME = { lat: 59.27389, lng: 15.21528, name: 'Örebro slott', home: true };
|
||||
let loc = HOME;
|
||||
try { const saved = JSON.parse(localStorage.getItem('pc-loc') || 'null'); if (saved && isFinite(saved.lat) && isFinite(saved.lng)) loc = saved; } catch (_) {}
|
||||
const store = { get(k) { try { return JSON.parse(localStorage.getItem(k) || 'null'); } catch (_) { return null; } },
|
||||
set(k, v) { try { v == null ? localStorage.removeItem(k) : localStorage.setItem(k, JSON.stringify(v)); } catch (_) {} } };
|
||||
if (!store.get('pc-home') && store.get('pc-loc')) store.set('pc-home', store.get('pc-loc')); // earlier versions auto-saved
|
||||
store.set('pc-loc', null);
|
||||
{ const saved = store.get('pc-home'); if (saved && isFinite(saved.lat) && isFinite(saved.lng)) loc = saved; }
|
||||
let LAT = loc.lat, LNG = loc.lng;
|
||||
const FPS = 24;
|
||||
const webm = document.createElement('video').canPlayType('video/webm; codecs="vp9"') !== '';
|
||||
@@ -973,17 +983,122 @@
|
||||
}
|
||||
|
||||
// ---------- location: map, own position, Örebro slott ----------
|
||||
let map = null, marker = null;
|
||||
function initMap() {
|
||||
if (!window.L || map) return;
|
||||
map = L.map('map', { zoomControl: false, scrollWheelZoom: false }).setView([LAT, LNG], 11);
|
||||
L.control.zoom({ position: 'topleft', zoomInTitle: 'Zooma in', zoomOutTitle: 'Zooma ut' }).addTo(map); // + / − buttons
|
||||
L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 18,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' }).addTo(map);
|
||||
marker = L.circleMarker([LAT, LNG], { radius: 7, color: '#fff', weight: 2, fillColor: '#ffcf5c', fillOpacity: .9 }).addTo(map);
|
||||
map.on('click', e => setLocation({ lat: e.latlng.lat, lng: e.latlng.lng, name: null, home: false }));
|
||||
// ---------- the globe: MapLibre GL (self-hosted), globe projection, day/night shading, shared city pins ----------
|
||||
let map = null, marker = null, ml = null, pinData = { cities: [], total: 0 };
|
||||
// the night side of the Earth right now: everything beyond the terminator (subsolar point from the same sun formulas)
|
||||
function nightPolygon(t) {
|
||||
// grid of 1.5° cells; sun altitude per cell centre; darkness grows through twilight (0 → −12°); equal cells in a row are merged.
|
||||
// Stable on every date (a closed-form terminator line breaks down around the equinoxes, when the sun's declination ≈ 0).
|
||||
const d = toDays(t), L = eclLong(meanAnomaly(d));
|
||||
const dec = Math.asin(Math.sin(e) * Math.sin(L)), ra = Math.atan2(Math.sin(L) * Math.cos(e), Math.cos(L));
|
||||
const lonS = (ra - rad * (280.16 + 360.9856235 * d)) / rad;
|
||||
const feats = [], S = 1.5;
|
||||
for (let la = -84; la < 84; la += S) {
|
||||
const phi = (la + S / 2) * rad; let run = null;
|
||||
const flush = () => { if (run && run.o > 0) feats.push({ type: 'Feature', properties: { o: run.o },
|
||||
geometry: { type: 'Polygon', coordinates: [[[run.a, la], [run.b, la], [run.b, la + S], [run.a, la + S], [run.a, la]]] } }); run = null; };
|
||||
for (let lo = -180; lo < 180; lo += S) {
|
||||
const H = (lo + S / 2 - lonS) * rad;
|
||||
const alt = Math.asin(Math.sin(phi) * Math.sin(dec) + Math.cos(phi) * Math.cos(dec) * Math.cos(H)) / rad;
|
||||
const o = Math.round(Math.min(1, Math.max(0, (.5 - alt) / 12.5)) * 16) / 16; // 0 in daylight … 1 in full night, 16 steps
|
||||
if (run && run.o === o) run.b = lo + S; else { flush(); run = { a: lo, b: lo + S, o }; }
|
||||
}
|
||||
flush();
|
||||
}
|
||||
return { type: 'FeatureCollection', features: feats };
|
||||
}
|
||||
function pinsGeo() {
|
||||
return { type: 'FeatureCollection', features: pinData.cities.map(c => ({ type: 'Feature',
|
||||
properties: { n: c.n, label: `${c.city}, ${c.country} · ${c.n} ${c.n === 1 ? 'delad pin' : 'delade pins'}` },
|
||||
geometry: { type: 'Point', coordinates: [c.lng, c.lat] } })) };
|
||||
}
|
||||
async function initMap() {
|
||||
if (map) return;
|
||||
try { ml = await import('./vendor/maplibre-6.11.2/maplibre-gl.mjs'); }
|
||||
catch (_) { $('map').innerHTML = '<div class="note" style="padding:40px 10px">Globen kunde inte laddas.</div>'; return; }
|
||||
try {
|
||||
// start zoom so the whole globe fits the panel (globe diameter ≈ 512·2^z/π px)
|
||||
const box = $('map').getBoundingClientRect(), fitZ = Math.log2(.62 * Math.min(box.width, box.height) * Math.PI / 512); // .62: comfortable margin (MapLibre draws the globe ~17 % larger than 512·2^z/π)
|
||||
map = new ml.Map({ container: 'map', center: [LNG, LAT], zoom: fitZ, minZoom: fitZ - .4, attributionControl: { compact: true }, dragRotate: false,
|
||||
style: { version: 8, projection: { type: 'globe' },
|
||||
sources: { osm: { type: 'raster', tiles: ['https://tile.openstreetmap.org/{z}/{x}/{y}.png'], tileSize: 256, maxzoom: 19,
|
||||
attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>' },
|
||||
night: { type: 'geojson', data: nightPolygon(now()) },
|
||||
coast: { type: 'geojson', data: location.origin + '/vendor/ne/coast-110m.json' },
|
||||
borders: { type: 'geojson', data: location.origin + '/vendor/ne/borders-110m.json' },
|
||||
pins: { type: 'geojson', data: pinsGeo() } },
|
||||
layers: [
|
||||
{ id: 'osm', type: 'raster', source: 'osm', paint: { 'raster-brightness-max': .8, 'raster-saturation': .05, 'raster-contrast': .18 } },
|
||||
{ id: 'night', type: 'fill', source: 'night', paint: { 'fill-color': '#040610', 'fill-opacity': ['*', .62, ['get', 'o']], 'fill-antialias': false } },
|
||||
{ id: 'borders', type: 'line', source: 'borders', paint: { 'line-color': 'rgba(70,64,60,.6)', 'line-dasharray': [2, 1.5],
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 0, .5, 5, 1] } },
|
||||
{ id: 'coast', type: 'line', source: 'coast', paint: { 'line-color': 'rgba(20,52,88,.9)',
|
||||
'line-width': ['interpolate', ['linear'], ['zoom'], 0, .9, 5, 1.6] } },
|
||||
{ id: 'pins-glow', type: 'circle', source: 'pins', paint: { 'circle-color': '#ffd76a', 'circle-opacity': .3, 'circle-blur': .9,
|
||||
'circle-radius': ['+', 7, ['*', 3, ['sqrt', ['get', 'n']]]] } },
|
||||
{ id: 'pins', type: 'circle', source: 'pins', paint: { 'circle-color': '#ffe7a3', 'circle-stroke-color': '#fff', 'circle-stroke-width': .8,
|
||||
'circle-radius': ['+', 2.2, ['*', 1.1, ['sqrt', ['get', 'n']]]] } } ] } });
|
||||
} catch (_) { $('map').insertAdjacentHTML('beforeend', '<div class="note" style="padding:40px 10px">Din webbläsare kan inte visa globen (WebGL).</div>'); return; }
|
||||
$('map').insertAdjacentHTML('beforeend', '<div class="gz"><button type="button" id="gz-in" aria-label="Zooma in" title="Zooma in">+</button>'
|
||||
+ '<button type="button" id="gz-out" aria-label="Zooma ut" title="Zooma ut">−</button></div>');
|
||||
$('gz-in').onclick = () => map.zoomIn(); $('gz-out').onclick = () => map.zoomOut();
|
||||
setInterval(() => map.getSource('night') && map.getSource('night').setData(nightPolygon(now())), 60 * 1000);
|
||||
map.on('load', () => { fetchPins(); $('map').dataset.proj = JSON.stringify(map.getProjection && map.getProjection()); }); // diagnostics
|
||||
map.on('error', ev => { $('map').dataset.err = String(ev && ev.error && ev.error.message || ev).slice(0, 200); });
|
||||
const el = document.createElement('div'); el.className = 'me-pin'; el.title = 'Din valda plats';
|
||||
el.innerHTML = '<svg viewBox="0 0 24 34" width="24" height="34" aria-hidden="true"><path d="M12 33c-.6 0-1-.4-1.3-.9C8.6 27.6 1 19.6 1 12a11 11 0 0 1 22 0c0 7.6-7.6 15.6-9.7 20.1-.3.5-.7.9-1.3.9z" fill="#e63946" stroke="#8d1a24" stroke-width="1"/><circle cx="12" cy="12" r="4.2" fill="#fff"/></svg>';
|
||||
marker = new ml.Marker({ element: el, anchor: 'bottom' }).setLngLat([LNG, LAT]).addTo(map);
|
||||
const tip = $('pin-tip');
|
||||
map.on('mousemove', 'pins', ev => { const f = ev.features[0]; tip.textContent = f.properties.label; tip.style.left = ev.point.x + 'px';
|
||||
tip.style.top = ev.point.y + 'px'; tip.style.display = 'block'; map.getCanvas().style.cursor = 'pointer'; });
|
||||
map.on('mouseleave', 'pins', () => { tip.style.display = 'none'; map.getCanvas().style.cursor = ''; });
|
||||
map.on('click', ev => {
|
||||
const hit = map.getLayer('pins') && map.queryRenderedFeatures(ev.point, { layers: ['pins', 'pins-glow'] })[0];
|
||||
if (hit) { tip.textContent = hit.properties.label; tip.style.left = ev.point.x + 'px'; tip.style.top = ev.point.y + 'px'; tip.style.display = 'block'; return; }
|
||||
setLocation({ lat: ev.lngLat.lat, lng: ((ev.lngLat.lng + 540) % 360) - 180, name: null, home: false });
|
||||
});
|
||||
}
|
||||
addEventListener('load', initMap);
|
||||
|
||||
// ---------- shared pins: an open, anonymous index at city level (server looks up the city; no free text) ----------
|
||||
async function fetchPins() {
|
||||
try { pinData = await (await fetch('/api/pins', { cache: 'no-store' })).json(); } catch (_) { return; }
|
||||
if (map && map.getSource('pins')) map.getSource('pins').setData(pinsGeo());
|
||||
const n = pinData.total || 0, c = (pinData.cities || []).length;
|
||||
$('p-pins-note').textContent = n ? `${n} ${n === 1 ? 'person' : 'personer'} i ${c} ${c === 1 ? 'stad' : 'städer'} följer himlen här.` : '';
|
||||
}
|
||||
setInterval(fetchPins, 5 * 60 * 1000);
|
||||
function showPinButtons() {
|
||||
const mine = store.get('pc-pin'), saved = store.get('pc-home');
|
||||
$('b-share').textContent = mine ? `Ta bort min pin (${mine.city})` : 'Dela min pin';
|
||||
$('b-share').title = mine ? '' : 'Delas anonymt: bara stad och land, ingen exakt position. Du kan ta bort den när du vill.';
|
||||
const same = saved && Math.abs(saved.lat - LAT) < 1e-6 && Math.abs(saved.lng - LNG) < 1e-6;
|
||||
$('b-save').textContent = same ? 'Glöm min plats' : 'Spara som min plats';
|
||||
}
|
||||
$('b-save').addEventListener('click', () => {
|
||||
const saved = store.get('pc-home'), same = saved && Math.abs(saved.lat - LAT) < 1e-6 && Math.abs(saved.lng - LNG) < 1e-6;
|
||||
if (same) { store.set('pc-home', null); $('p-loc-note').textContent = 'Glömt – nästa gång startar sidan på Örebro slott.'; }
|
||||
else { store.set('pc-home', { lat: LAT, lng: LNG, name: loc.name, home: !!loc.home });
|
||||
$('p-loc-note').textContent = `Sparad! Sidan startar på ${loc.name || 'den här platsen'} nästa gång (bara i din webbläsare).`; }
|
||||
showPinButtons();
|
||||
});
|
||||
$('b-share').addEventListener('click', async () => {
|
||||
const mine = store.get('pc-pin'), btn = $('b-share'); btn.disabled = true;
|
||||
try {
|
||||
if (mine) {
|
||||
await fetch('/api/pins', { method: 'DELETE', headers: { 'X-Pin-Token': mine.token } });
|
||||
store.set('pc-pin', null); $('p-loc-note').textContent = 'Din pin är borttagen.';
|
||||
} else {
|
||||
const r = await fetch('/api/pins', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ lat: LAT, lng: LNG }) });
|
||||
const d = await r.json();
|
||||
if (!r.ok) { $('p-loc-note').textContent = d.error || 'Kunde inte dela just nu.'; return; }
|
||||
store.set('pc-pin', { token: d.token, city: d.city, country: d.country });
|
||||
$('p-loc-note').textContent = `Delad som ${d.city}, ${d.country} – bara stad och land, anonymt.`;
|
||||
}
|
||||
fetchPins();
|
||||
} catch (_) { $('p-loc-note').textContent = 'Kunde inte nå servern just nu.'; }
|
||||
finally { btn.disabled = false; showPinButtons(); }
|
||||
});
|
||||
async function placeName(lat, lng) { // OpenStreetMap Nominatim, only when the place changes
|
||||
try {
|
||||
const r = await (await fetch(`https://nominatim.openstreetmap.org/reverse?format=jsonv2&zoom=14&accept-language=sv&lat=${lat.toFixed(5)}&lon=${lng.toFixed(5)}`)).json();
|
||||
@@ -997,22 +1112,22 @@
|
||||
$('p-place').textContent = loc.name || 'Vald plats';
|
||||
$('p-coords').textContent = `${LAT.toFixed(3)}°, ${LNG.toFixed(3)}°`;
|
||||
$('b-home').disabled = !!loc.home;
|
||||
if (map) { map.setView([LAT, LNG], map.getZoom()); marker.setLatLng([LAT, LNG]); }
|
||||
if (map) { map.easeTo({ center: [LNG, LAT], duration: 1200 }); marker.setLngLat([LNG, LAT]); }
|
||||
showPinButtons();
|
||||
}
|
||||
async function setLocation(l) {
|
||||
loc = l; LAT = l.lat; LNG = l.lng; sunDay = null; lastPhase = -1;
|
||||
setTZ(l.home ? 'Europe/Stockholm' : (Intl.DateTimeFormat().resolvedOptions().timeZone || 'Europe/Stockholm'));
|
||||
showLoc(); tick(); fetchWeather();
|
||||
if (!l.name) { l.name = await placeName(l.lat, l.lng); if (loc === l) showLoc(); }
|
||||
try { localStorage.setItem('pc-loc', JSON.stringify(l)); } catch (_) {}
|
||||
renderPanels();
|
||||
}
|
||||
$('b-home').addEventListener('click', () => { $('p-loc-note').textContent = 'Klicka i kartan för att välja en annan plats.'; setLocation({ ...HOME }); });
|
||||
$('b-home').addEventListener('click', () => { $('p-loc-note').textContent = 'Klicka på globen för att välja en annan plats.'; setLocation({ ...HOME }); });
|
||||
$('b-mine').addEventListener('click', () => {
|
||||
if (!navigator.geolocation) { $('p-loc-note').textContent = 'Din webbläsare kan inte dela position.'; return; }
|
||||
$('p-loc-note').textContent = 'Hämtar din position…';
|
||||
navigator.geolocation.getCurrentPosition(
|
||||
p => { $('p-loc-note').textContent = `Din position (±${Math.round(p.coords.accuracy)} m). Sparas bara i din webbläsare.`;
|
||||
p => { $('p-loc-note').textContent = `Din position (±${Math.round(p.coords.accuracy)} m). Tryck "Spara som min plats" för att starta här nästa gång.`;
|
||||
setLocation({ lat: p.coords.latitude, lng: p.coords.longitude, name: null, home: false }); },
|
||||
err => { $('p-loc-note').textContent = err.code === 1 ? 'Du nekade positionsdelning – vi utgår från Örebro slott.' : 'Kunde inte hämta din position.'; },
|
||||
{ enableHighAccuracy: false, timeout: 15000, maximumAge: 10 * 60 * 1000 });
|
||||
|
||||
Reference in New Issue
Block a user