diff --git a/AGENTS.md b/AGENTS.md index b6de3fc..6c64732 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1119,8 +1119,11 @@ session with **no other memory of this project**. So: tags/address sit under a **More details** collapsible (access defaults public, shelter defaults outdoor); field errors render inline (no toast per field); and the form autosaves via **`useFormDraft`** (generalized from `useLabDraft`, same - resume-banner pattern). ⚠️ Leaflet's base CSS is **CDN-loaded in `index.html`** - (`src/main.tsx` just has a comment) — a new Leaflet map needs no CSS import. + resume-banner pattern). ⚠️ Leaflet's base CSS **and marker icons are self-hosted** + now (2026-10-12) — the CSS is imported in `src/main.tsx` (`leaflet/dist/leaflet.css`, + `MarkerCluster*.css`) and `SpotMap.tsx` imports the marker PNGs from + `leaflet/dist/images/`, so there's no unpkg/CDN request and the CSP dropped + `unpkg.com` (in all three copies). A new Leaflet map needs no CSS import. Follow-ups in the same pass: the 60 m duplicate check now runs **at pin-time** (debounced, warning under the map) using the **same `useSkateSpots(2000)` cache key as the map** (was a second `useSkateSpots(500)` query); and publish **caps diff --git a/index.html b/index.html index a2241ed..412b727 100644 --- a/index.html +++ b/index.html @@ -27,7 +27,7 @@ - - - - + + + + + +
diff --git a/netlify.toml b/netlify.toml index 257f49f..9966b83 100644 --- a/netlify.toml +++ b/netlify.toml @@ -11,7 +11,7 @@ [headers.values] # Keep this Content-Security-Policy in sync with public/_headers. # Web-only hardening; the native Capacitor build is unaffected (it has no CSP). - Content-Security-Policy = "default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: blob: https:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https: wss:; media-src 'self' blob: https:; frame-src 'self' https:; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" + Content-Security-Policy = "default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https: wss:; media-src 'self' blob: https:; frame-src 'self' https:; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests" X-Frame-Options = "DENY" X-Content-Type-Options = "nosniff" Referrer-Policy = "strict-origin-when-cross-origin" diff --git a/public/_headers b/public/_headers index bdf7196..c170fcf 100644 --- a/public/_headers +++ b/public/_headers @@ -1,5 +1,5 @@ /* - Content-Security-Policy: default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline' https://unpkg.com; img-src 'self' data: blob: https:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https: wss:; media-src 'self' blob: https:; frame-src 'self' https:; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests + Content-Security-Policy: default-src 'none'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob: https:; font-src 'self' https://fonts.gstatic.com data:; connect-src 'self' https: wss:; media-src 'self' blob: https:; frame-src 'self' https:; worker-src 'self' blob:; manifest-src 'self'; object-src 'none'; base-uri 'none'; form-action 'self'; frame-ancestors 'none'; upgrade-insecure-requests X-Content-Type-Options: nosniff X-Frame-Options: DENY Referrer-Policy: strict-origin-when-cross-origin diff --git a/src/components/SpotMap.tsx b/src/components/SpotMap.tsx index 77d0527..c853f7c 100644 --- a/src/components/SpotMap.tsx +++ b/src/components/SpotMap.tsx @@ -2,6 +2,9 @@ import { dateLocaleCode } from '@/lib/dateFnsI18n'; import { useEffect, useRef, useMemo, useCallback } from 'react'; import L from 'leaflet'; import 'leaflet.markercluster'; +import markerIcon from 'leaflet/dist/images/marker-icon.png'; +import markerIcon2x from 'leaflet/dist/images/marker-icon-2x.png'; +import markerShadow from 'leaflet/dist/images/marker-shadow.png'; import ngeohash from 'ngeohash'; import { useNavigate } from 'react-router-dom'; import type { SkateSpot, MarketplaceItem, SkateTour, SkateEvent, SkateCrew } from '@/lib/types'; @@ -57,9 +60,9 @@ function popupRow(prefix: string, text: string): HTMLElement { // Fix default marker icon issues with webpack/vite const defaultIcon = L.icon({ - iconUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon.png', - iconRetinaUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-icon-2x.png', - shadowUrl: 'https://unpkg.com/leaflet@1.9.4/dist/images/marker-shadow.png', + iconUrl: markerIcon, + iconRetinaUrl: markerIcon2x, + shadowUrl: markerShadow, iconSize: [25, 41], iconAnchor: [12, 41], popupAnchor: [1, -34], diff --git a/src/main.tsx b/src/main.tsx index bc1877e..f044c90 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -40,7 +40,11 @@ import '@fontsource/barlow-condensed/700.css'; // Cyrillic (Russian) — Inter has Cyrillic; Outfit does not import '@fontsource-variable/inter'; -// Leaflet CSS is loaded via CDN in index.html +// Leaflet base CSS — self-hosted (bundled) instead of a CDN, so there's no +// third-party request at boot and the CSP no longer needs to allow unpkg.com. +import 'leaflet/dist/leaflet.css'; +import 'leaflet.markercluster/dist/MarkerCluster.css'; +import 'leaflet.markercluster/dist/MarkerCluster.Default.css'; /** * AMBER CALLBACK PRE-CHECK + REDIRECT