fix: restore orange brand icon and harden OSM tile cache
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run
Some checks are pending
Deploy to GitHub Pages / deploy (push) Waiting to run
This commit is contained in:
parent
f75bad3e8d
commit
579dcddc1d
18 changed files with 79 additions and 39 deletions
10
index.html
10
index.html
|
|
@ -44,11 +44,11 @@
|
||||||
<link rel="manifest" href="/manifest.webmanifest">
|
<link rel="manifest" href="/manifest.webmanifest">
|
||||||
|
|
||||||
<!-- App Icons -->
|
<!-- App Icons -->
|
||||||
<link rel="icon" type="image/png" href="/images/icon-1024.png">
|
<link rel="icon" type="image/png" href="/images/brand-192.png">
|
||||||
<link rel="apple-touch-icon" href="/images/icon-180.png">
|
<link rel="apple-touch-icon" href="/images/brand-180.png">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/images/icon-180.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/brand-180.png">
|
||||||
<link rel="apple-touch-icon" sizes="180x180" href="/images/icon-180.png">
|
<link rel="apple-touch-icon" sizes="180x180" href="/images/brand-180.png">
|
||||||
<link rel="apple-touch-startup-image" href="/images/icon-1024.png">
|
<link rel="apple-touch-startup-image" href="/images/brand-1024.png">
|
||||||
|
|
||||||
<!-- Theme Color -->
|
<!-- Theme Color -->
|
||||||
<meta name="theme-color" content="#ff0040">
|
<meta name="theme-color" content="#ff0040">
|
||||||
|
|
|
||||||
BIN
public/images/brand-1024.png
Normal file
BIN
public/images/brand-1024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 218 KiB |
BIN
public/images/brand-180.png
Normal file
BIN
public/images/brand-180.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/brand-192.png
Normal file
BIN
public/images/brand-192.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
BIN
public/images/brand-512.png
Normal file
BIN
public/images/brand-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 64 KiB |
BIN
public/images/brand-maskable-1024.png
Normal file
BIN
public/images/brand-maskable-1024.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 146 KiB |
BIN
public/images/brand-maskable-512.png
Normal file
BIN
public/images/brand-maskable-512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 43 KiB |
|
|
@ -1,8 +1,8 @@
|
||||||
{
|
{
|
||||||
"name": "SkateSpots - Decentralized Skate Map",
|
"name": "SkateSpots — Open-sourced Skate Community App",
|
||||||
"short_name": "SkateSpots",
|
"short_name": "SkateSpots",
|
||||||
"id": "/",
|
"id": "/",
|
||||||
"description": "Discover skate spots, find coaches, join events, and connect with the skateboarding community on Nostr. Free, decentralized, censorship-resistant.",
|
"description": "Map, crews, events, gear and more — an open-sourced skate community app.",
|
||||||
"start_url": "/",
|
"start_url": "/",
|
||||||
"scope": "/",
|
"scope": "/",
|
||||||
"display": "standalone",
|
"display": "standalone",
|
||||||
|
|
@ -18,25 +18,25 @@
|
||||||
"related_applications": [],
|
"related_applications": [],
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/images/icon-192.png",
|
"src": "/images/brand-192.png",
|
||||||
"sizes": "192x192",
|
"sizes": "192x192",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/images/icon-512.png",
|
"src": "/images/brand-512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/images/icon-maskable-512.png",
|
"src": "/images/brand-maskable-512.png",
|
||||||
"sizes": "512x512",
|
"sizes": "512x512",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "maskable"
|
"purpose": "maskable"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"src": "/images/icon-1024.png",
|
"src": "/images/brand-1024.png",
|
||||||
"sizes": "1024x1024",
|
"sizes": "1024x1024",
|
||||||
"type": "image/png",
|
"type": "image/png",
|
||||||
"purpose": "any"
|
"purpose": "any"
|
||||||
|
|
@ -66,7 +66,7 @@
|
||||||
"url": "/map",
|
"url": "/map",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/images/icon-1024.png",
|
"src": "/images/brand-180.png",
|
||||||
"sizes": "180x180",
|
"sizes": "180x180",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
|
|
@ -79,7 +79,7 @@
|
||||||
"url": "/events",
|
"url": "/events",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/images/icon-1024.png",
|
"src": "/images/brand-180.png",
|
||||||
"sizes": "180x180",
|
"sizes": "180x180",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
|
|
@ -92,7 +92,7 @@
|
||||||
"url": "/coaches",
|
"url": "/coaches",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/images/icon-1024.png",
|
"src": "/images/brand-180.png",
|
||||||
"sizes": "180x180",
|
"sizes": "180x180",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
|
|
@ -105,7 +105,7 @@
|
||||||
"url": "/wallet",
|
"url": "/wallet",
|
||||||
"icons": [
|
"icons": [
|
||||||
{
|
{
|
||||||
"src": "/images/icon-1024.png",
|
"src": "/images/brand-180.png",
|
||||||
"sizes": "180x180",
|
"sizes": "180x180",
|
||||||
"type": "image/png"
|
"type": "image/png"
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -630,7 +630,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
||||||
title={user ? t('chromeUi.viewMyProfile') : t('nav.map')}
|
title={user ? t('chromeUi.viewMyProfile') : t('nav.map')}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/images/icon-1024.png"
|
src="/images/logo.png"
|
||||||
alt="SkateSpots"
|
alt="SkateSpots"
|
||||||
className="w-10 h-10 shrink-0 rounded-xl shadow-lg shadow-red-500/30 group-hover:shadow-red-500/50 transition-shadow"
|
className="w-10 h-10 shrink-0 rounded-xl shadow-lg shadow-red-500/30 group-hover:shadow-red-500/50 transition-shadow"
|
||||||
/>
|
/>
|
||||||
|
|
@ -857,7 +857,7 @@ export function Layout({ children, noPadding }: LayoutProps) {
|
||||||
aria-label={user ? t('chromeUi.viewMyProfile') : t('nav.map')}
|
aria-label={user ? t('chromeUi.viewMyProfile') : t('nav.map')}
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/images/icon-1024.png"
|
src="/images/logo.png"
|
||||||
alt="SkateSpots"
|
alt="SkateSpots"
|
||||||
className="w-9 h-9 rounded-lg flex-shrink-0 shadow-lg shadow-red-500/30"
|
className="w-9 h-9 rounded-lg flex-shrink-0 shadow-lg shadow-red-500/30"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,12 @@ import {
|
||||||
import { getLastLocation } from '@/lib/lastLocation';
|
import { getLastLocation } from '@/lib/lastLocation';
|
||||||
import { saveRoute, loadRoute, formatAge } from '@/lib/routeCache';
|
import { saveRoute, loadRoute, formatAge } from '@/lib/routeCache';
|
||||||
import { createCachedTileLayer } from '@/lib/cachedTileLayer';
|
import { createCachedTileLayer } from '@/lib/cachedTileLayer';
|
||||||
|
import {
|
||||||
|
MAP_TILE_URL,
|
||||||
|
MAP_TILE_SUBDOMAINS,
|
||||||
|
MAP_TILE_CACHE_NAMESPACE,
|
||||||
|
MAP_TILE_ATTRIBUTION,
|
||||||
|
} from '@/lib/mapTiles';
|
||||||
import { calculateDistance } from '@/lib/sortOptions';
|
import { calculateDistance } from '@/lib/sortOptions';
|
||||||
import {
|
import {
|
||||||
fetchRoute,
|
fetchRoute,
|
||||||
|
|
@ -598,13 +604,13 @@ export function RouteDialog({
|
||||||
minZoom: 2,
|
minZoom: 2,
|
||||||
});
|
});
|
||||||
|
|
||||||
createCachedTileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
createCachedTileLayer(MAP_TILE_URL, {
|
||||||
attribution: '',
|
attribution: MAP_TILE_ATTRIBUTION,
|
||||||
subdomains: 'abc',
|
subdomains: MAP_TILE_SUBDOMAINS,
|
||||||
maxZoom: 19,
|
maxZoom: 19,
|
||||||
noWrap: true,
|
noWrap: true,
|
||||||
className: 'skatespots-dark-tiles',
|
className: 'skatespots-dark-tiles',
|
||||||
}).addTo(map);
|
}, MAP_TILE_CACHE_NAMESPACE).addTo(map);
|
||||||
|
|
||||||
// Metric scale bar — genuinely useful on a route map (how long is that line?).
|
// Metric scale bar — genuinely useful on a route map (how long is that line?).
|
||||||
L.control.scale({ imperial: false, position: 'topleft' }).addTo(map);
|
L.control.scale({ imperial: false, position: 'topleft' }).addTo(map);
|
||||||
|
|
|
||||||
|
|
@ -258,7 +258,7 @@ export function SplashScreen() {
|
||||||
className="relative"
|
className="relative"
|
||||||
>
|
>
|
||||||
<img
|
<img
|
||||||
src="/images/icon-1024.png"
|
src="/images/logo.png"
|
||||||
alt="SkateSpots"
|
alt="SkateSpots"
|
||||||
draggable={false}
|
draggable={false}
|
||||||
className="relative h-28 w-28 select-none drop-shadow-[0_0_34px_rgba(255,106,0,0.65)]"
|
className="relative h-28 w-28 select-none drop-shadow-[0_0_34px_rgba(255,106,0,0.65)]"
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,12 @@ import { useEffect, useRef } from 'react';
|
||||||
import L from 'leaflet';
|
import L from 'leaflet';
|
||||||
|
|
||||||
import { createCachedTileLayer } from '@/lib/cachedTileLayer';
|
import { createCachedTileLayer } from '@/lib/cachedTileLayer';
|
||||||
|
import {
|
||||||
|
MAP_TILE_URL,
|
||||||
|
MAP_TILE_SUBDOMAINS,
|
||||||
|
MAP_TILE_CACHE_NAMESPACE,
|
||||||
|
MAP_TILE_ATTRIBUTION,
|
||||||
|
} from '@/lib/mapTiles';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
export interface LatLng {
|
export interface LatLng {
|
||||||
|
|
@ -53,14 +59,14 @@ export function SpotLocationPicker({ value, onChange, className }: SpotLocationP
|
||||||
attributionControl: true,
|
attributionControl: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
createCachedTileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
createCachedTileLayer(MAP_TILE_URL, {
|
||||||
attribution: '',
|
attribution: MAP_TILE_ATTRIBUTION,
|
||||||
subdomains: 'abc',
|
subdomains: MAP_TILE_SUBDOMAINS,
|
||||||
minZoom: 2,
|
minZoom: 2,
|
||||||
maxZoom: 19,
|
maxZoom: 19,
|
||||||
noWrap: true,
|
noWrap: true,
|
||||||
className: 'skatespots-dark-tiles',
|
className: 'skatespots-dark-tiles',
|
||||||
}).addTo(map);
|
}, MAP_TILE_CACHE_NAMESPACE).addTo(map);
|
||||||
|
|
||||||
map.on('click', (e: L.LeafletMouseEvent) => {
|
map.on('click', (e: L.LeafletMouseEvent) => {
|
||||||
onChangeRef.current({ lat: e.latlng.lat, lng: e.latlng.lng });
|
onChangeRef.current({ lat: e.latlng.lat, lng: e.latlng.lng });
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,12 @@ import type { BtcMapPlace } from '@/hooks/useBtcMapPlaces';
|
||||||
import { isRecentlyVerified, isBoosted } from '@/hooks/useBtcMapPlaces';
|
import { isRecentlyVerified, isBoosted } from '@/hooks/useBtcMapPlaces';
|
||||||
import { sanitizeUrl } from '@/lib/sanitizeUrl';
|
import { sanitizeUrl } from '@/lib/sanitizeUrl';
|
||||||
import { createCachedTileLayer } from '@/lib/cachedTileLayer';
|
import { createCachedTileLayer } from '@/lib/cachedTileLayer';
|
||||||
|
import {
|
||||||
|
MAP_TILE_URL,
|
||||||
|
MAP_TILE_SUBDOMAINS,
|
||||||
|
MAP_TILE_CACHE_NAMESPACE,
|
||||||
|
MAP_TILE_ATTRIBUTION,
|
||||||
|
} from '@/lib/mapTiles';
|
||||||
import { FORECAST_DAYS } from '@/lib/weather';
|
import { FORECAST_DAYS } from '@/lib/weather';
|
||||||
import { useTranslation } from '@/contexts/language';
|
import { useTranslation } from '@/contexts/language';
|
||||||
|
|
||||||
|
|
@ -955,17 +961,18 @@ export function SpotMap({
|
||||||
|
|
||||||
applyWorldMinZoom(map);
|
applyWorldMinZoom(map);
|
||||||
|
|
||||||
// Free, no-API-key basemap: OpenStreetMap standard tiles, darkened via a CSS
|
// OpenStreetMap standard tiles, darkened via a CSS filter (see the
|
||||||
// filter (see the `.skatespots-dark-tiles` rule in index.css) so it matches
|
// `.skatespots-dark-tiles` rule in index.css) so they match the app's dark
|
||||||
// the app's dark theme without depending on a keyed provider like CARTO.
|
// theme. Wrapped in an on-device tile cache so the basemap still draws at
|
||||||
// Wrapped in an on-device tile cache so the basemap still draws at the park.
|
// the park, with a fresh cache namespace so old access-blocked tiles are
|
||||||
createCachedTileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
|
// never reused.
|
||||||
attribution: '',
|
createCachedTileLayer(MAP_TILE_URL, {
|
||||||
subdomains: 'abc',
|
attribution: MAP_TILE_ATTRIBUTION,
|
||||||
|
subdomains: MAP_TILE_SUBDOMAINS,
|
||||||
minZoom: 1,
|
minZoom: 1,
|
||||||
maxZoom: 19,
|
maxZoom: 19,
|
||||||
className: 'skatespots-dark-tiles',
|
className: 'skatespots-dark-tiles',
|
||||||
}).addTo(map);
|
}, MAP_TILE_CACHE_NAMESPACE).addTo(map);
|
||||||
|
|
||||||
// Add click handler if enabled
|
// Add click handler if enabled
|
||||||
if (enableClickToAdd && onMapClick) {
|
if (enableClickToAdd && onMapClick) {
|
||||||
|
|
|
||||||
|
|
@ -212,7 +212,7 @@ export const LoginDialog: React.FC<LoginDialogProps> = ({ isOpen, onClose, onLog
|
||||||
setConnectError('');
|
setConnectError('');
|
||||||
setConnectState('waiting');
|
setConnectState('waiting');
|
||||||
const session = startNostrConnect(nostr, {
|
const session = startNostrConnect(nostr, {
|
||||||
appImage: 'https://skate-spots.org/images/icon-1024.png',
|
appImage: 'https://skate-spots.org/images/brand-1024.png',
|
||||||
timeoutMs: 180_000,
|
timeoutMs: 180_000,
|
||||||
});
|
});
|
||||||
connectRef.current = session;
|
connectRef.current = session;
|
||||||
|
|
|
||||||
|
|
@ -14,12 +14,15 @@ import { getTile, putTile } from '@/lib/tileCache';
|
||||||
* Works on web and in the Capacitor WebView alike, unlike a service worker.
|
* Works on web and in the Capacitor WebView alike, unlike a service worker.
|
||||||
*/
|
*/
|
||||||
export class CachedTileLayer extends L.TileLayer {
|
export class CachedTileLayer extends L.TileLayer {
|
||||||
|
/** Change this whenever the basemap/provider/cache contract changes. */
|
||||||
|
cacheNamespace = 'default';
|
||||||
|
|
||||||
protected createTile(coords: L.Coords, done: L.DoneCallback): HTMLElement {
|
protected createTile(coords: L.Coords, done: L.DoneCallback): HTMLElement {
|
||||||
const tile = document.createElement('img');
|
const tile = document.createElement('img');
|
||||||
tile.alt = '';
|
tile.alt = '';
|
||||||
tile.setAttribute('role', 'presentation');
|
tile.setAttribute('role', 'presentation');
|
||||||
|
|
||||||
const key = `${coords.z}/${coords.x}/${coords.y}`;
|
const key = `${this.cacheNamespace}:${coords.z}/${coords.x}/${coords.y}`;
|
||||||
const url = this.getTileUrl(coords);
|
const url = this.getTileUrl(coords);
|
||||||
|
|
||||||
const useSrc = (src: string, revoke: boolean) => {
|
const useSrc = (src: string, revoke: boolean) => {
|
||||||
|
|
@ -57,6 +60,9 @@ export class CachedTileLayer extends L.TileLayer {
|
||||||
export function createCachedTileLayer(
|
export function createCachedTileLayer(
|
||||||
urlTemplate: string,
|
urlTemplate: string,
|
||||||
options?: L.TileLayerOptions,
|
options?: L.TileLayerOptions,
|
||||||
|
cacheNamespace = 'default',
|
||||||
): CachedTileLayer {
|
): CachedTileLayer {
|
||||||
return new CachedTileLayer(urlTemplate, options);
|
const layer = new CachedTileLayer(urlTemplate, options);
|
||||||
|
layer.cacheNamespace = cacheNamespace;
|
||||||
|
return layer;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
15
src/lib/mapTiles.ts
Normal file
15
src/lib/mapTiles.ts
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
/**
|
||||||
|
* OpenStreetMap basemap configuration.
|
||||||
|
*
|
||||||
|
* Keep the OSM tile provider. `osm-v2` intentionally changes the cache
|
||||||
|
* namespace so old cached "Access blocked" tiles are never reused; it does not
|
||||||
|
* change the tile source.
|
||||||
|
*/
|
||||||
|
export const MAP_TILE_URL =
|
||||||
|
'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
|
||||||
|
|
||||||
|
export const MAP_TILE_SUBDOMAINS = 'abc';
|
||||||
|
|
||||||
|
export const MAP_TILE_CACHE_NAMESPACE = 'osm-v2';
|
||||||
|
|
||||||
|
export const MAP_TILE_ATTRIBUTION = '© OpenStreetMap contributors';
|
||||||
|
|
@ -593,7 +593,7 @@ function Footer() {
|
||||||
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
<div className="flex flex-col md:flex-row items-center justify-between gap-6">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<img
|
<img
|
||||||
src="/images/icon-1024.png"
|
src="/images/logo.png"
|
||||||
alt="SkateSpots"
|
alt="SkateSpots"
|
||||||
className="w-10 h-10 rounded-lg shadow-[0_0_20px_rgba(255,0,64,0.5)]"
|
className="w-10 h-10 rounded-lg shadow-[0_0_20px_rgba(255,0,64,0.5)]"
|
||||||
/>
|
/>
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ website: https://skate-spots.org
|
||||||
license: AGPL-3.0-only
|
license: AGPL-3.0-only
|
||||||
|
|
||||||
# Launcher / listing icon.
|
# Launcher / listing icon.
|
||||||
icon: ./public/images/icon-1024.png
|
icon: ./public/images/brand-1024.png
|
||||||
|
|
||||||
# Listing screenshots (uploaded by zsp to the Zapstore CDN).
|
# Listing screenshots (uploaded by zsp to the Zapstore CDN).
|
||||||
images:
|
images:
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue