From bbe5987a9bdfc4a7bb90526e445bf81c60e87fa6 Mon Sep 17 00:00:00 2001 From: tiennm99 Date: Tue, 1 Sep 2026 09:43:07 +0700 Subject: [PATCH] feat(credits): add Mapillary attribution overlay and credits page Adds an overlay to acknowledge Mapillary imagery copyright and a dedicated /credits page displaying all data sources and providers with proper attribution. --- docs/features.md | 10 ++- public/mapillary-logo.svg | 1 + src/app/components/PanoramaViewer.js | 31 ++++++- src/app/credits/page.js | 124 +++++++++++++++++++++++++++ src/app/page.js | 16 ++++ 5 files changed, 177 insertions(+), 5 deletions(-) create mode 100644 public/mapillary-logo.svg create mode 100644 src/app/credits/page.js diff --git a/docs/features.md b/docs/features.md index efb256b..1ccf077 100644 --- a/docs/features.md +++ b/docs/features.md @@ -22,9 +22,17 @@ - **Panoramas only**: non-panoramic images are filtered out when the index is built, so every indexed point is a panorama. The flag itself is not stored - **Thumbnail display**: `thumb_2048_url`, falling back to `thumb_original_url` +- **Attribution overlay**: every panorama shows the Mapillary logo (linking to + the Mapillary homepage, never the per-image page — the image id is the + round's answer) and a CC BY-SA 4.0 link, as the Mapillary Terms of Use + require. `/credits` lists all data sources and licenses; the home page + footer links to it ## Interactive Maps -- **Leaflet Integration**: OpenStreetMap-based interactive mapping +- **Leaflet Integration**: OpenStreetMap-based interactive mapping. Tiles come + from Geoapify when `NEXT_PUBLIC_GEOAPIFY_KEY` is set at build time (free + tier allows commercial use), falling back to the OSM public server + otherwise; the choice is centralized in `src/lib/map-tiles.js` - **Click-to-Place**: Intuitive guess marker placement - **Map Search**: search box on the guess map finds districts (offline, diacritic- and alias-aware: `quận 7`, `q7`, `hoàn kiếm`) and streets/places diff --git a/public/mapillary-logo.svg b/public/mapillary-logo.svg new file mode 100644 index 0000000..431ec8b --- /dev/null +++ b/public/mapillary-logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/app/components/PanoramaViewer.js b/src/app/components/PanoramaViewer.js index ba1e23e..6a6e0da 100644 --- a/src/app/components/PanoramaViewer.js +++ b/src/app/components/PanoramaViewer.js @@ -1,6 +1,7 @@ "use client"; import { useEffect, useRef, memo } from 'react'; +import Image from 'next/image'; import { Viewer } from '@photo-sphere-viewer/core'; import '@photo-sphere-viewer/core/index.css'; @@ -97,10 +98,32 @@ function PanoramaViewer({ imageUrl, onReady, onError }) { }, [imageUrl]); return ( -
+
+
+ {/* Imagery attribution required by the Mapillary Terms of Use: the logo, + visibly displayed, linking to the Mapillary homepage. Deliberately + NOT the per-image page — the image id resolves the round's answer, + and the server keeps it secret. Top-left: the navbar owns the bottom + edge and the floating guess map owns the bottom-right. */} +
+ + Mapillary + + + CC BY-SA 4.0 + +
+
); } diff --git a/src/app/credits/page.js b/src/app/credits/page.js new file mode 100644 index 0000000..e9b5ac1 --- /dev/null +++ b/src/app/credits/page.js @@ -0,0 +1,124 @@ +import Link from 'next/link'; +import Image from 'next/image'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; + +export const metadata = { + title: 'Credits — VNGeoGuessr', + description: 'Data sources, licenses, and open-source software behind VNGeoGuessr', +}; + +// Every external link opens in a new tab and drops the referrer. +function ExternalLink({ href, children }) { + return ( + + {children} + + ); +} + +const LIBRARIES = [ + { name: 'Next.js & React', license: 'MIT', href: 'https://nextjs.org/' }, + { name: 'Leaflet', license: 'BSD-2-Clause', href: 'https://leafletjs.com/' }, + { name: 'Photo Sphere Viewer', license: 'MIT', href: 'https://photo-sphere-viewer.js.org/' }, + { name: 'Turf.js', license: 'MIT', href: 'https://turfjs.org/' }, + { name: 'shadcn/ui & Radix UI', license: 'MIT', href: 'https://ui.shadcn.com/' }, + { name: 'Tailwind CSS', license: 'MIT', href: 'https://tailwindcss.com/' }, + { name: 'Lucide', license: 'ISC', href: 'https://lucide.dev/' }, +]; + +export default function CreditsPage() { + return ( +
+
+
+ + VNGeoGuessr + + + Back to game + +
+ +

Credits & Licenses

+ +
+ + + Street-level imagery + + + + Mapillary + +

+ All 360° panoramas are contributed by the{' '} + Mapillary{' '} + community and licensed under{' '} + CC BY-SA 4.0. + Thank you to everyone who mapped the streets of Vietnam. +

+
+
+ + + + Map data & tiles + + +

+ Map data ©{' '} + OpenStreetMap contributors, + available under the{' '} + Open Database License (ODbL). + {/* NEXT_PUBLIC_* is inlined at build time, so this static page + correctly reflects the deployed tile provider. Truthiness + only — the key itself must never render. */} + {process.env.NEXT_PUBLIC_GEOAPIFY_KEY ? ( + <> Map tiles are served by Geoapify. + ) : ( + <> Map tiles are served by OpenStreetMap. + )} +

+

+ Province and district boundaries are derived from OpenStreetMap + via{' '} + Nominatim. +

+
+
+ + + + Open-source software + + +
    + {LIBRARIES.map((lib) => ( +
  • + {lib.name} + {lib.license} +
  • + ))} +
+

+ VNGeoGuessr itself is open source under the{' '} + Apache License 2.0. +

+
+
+
+
+
+ ); +} diff --git a/src/app/page.js b/src/app/page.js index dca9eaf..6f57433 100644 --- a/src/app/page.js +++ b/src/app/page.js @@ -134,6 +134,22 @@ export default function Home() {
+ + {/* Data attribution. mb clears the fixed build-stamp footer. */} +

+ Imagery ©{' '} + + Mapillary + {' '} + (CC BY-SA 4.0) · Map data ©{' '} + + OpenStreetMap contributors + {' '} + ·{' '} + + Credits + +

setShowDonateModal(false)} />