mirror of
https://github.com/tiennm99/vngeoguessr.git
synced 2026-10-11 12:29:01 +00:00
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.
This commit is contained in:
1 parent
649247b3a1
commit
bbe5987a9b
5 files changed
+177
-5
No files matched your search
+9
-1
@@ -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
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="61" height="14" viewBox="0 0 61 14"><g fill="none" fill-rule="evenodd"> <path fill="#FFF" d="M23.9948303 2.81452285L23.9948303 11.313853 22.3708637 11.313853 22.3708637 6.89223614 20.5439012 11.313853 19.6602724 11.313853 17.8333099 6.89223614 17.8333099 11.313853 16.2093433 11.313853 16.2093433 2.81452285 17.8810737 2.81452285 20.1020868 8.16959211 22.3231 2.81452285 23.9948303 2.81452285 23.9948303 2.81452285zM31.0819584 4.66914546L32.5029292 4.66914546 32.5029292 5.22184756C32.5984572 5.02533028 32.7895101 4.85747428 33.0760939 4.71827453 33.3626777 4.57907479 33.6771191 4.50947596 34.0194275 4.50947596 34.3458145 4.50947596 34.6522954 4.58521586 34.9388791 4.73669794 35.2254629 4.88818001 35.4742298 5.11130567 35.6851873 5.4060816 35.8961448 5.70085752 36.0633162 6.06522786 36.1867064 6.49920353 36.3100966 6.9331792 36.3717908 7.4285591 36.3717908 7.98535807 36.3717908 8.55853349 36.3120868 9.06210148 36.1926769 9.49607715 36.073267 9.93005282 35.9100759 10.2944232 35.7030987 10.5891991 35.4961215 10.883975 35.2513349 11.1050536 34.9687315 11.2524416 34.686128 11.3998296 34.3776571 11.4735225 34.0433093 11.4735225 33.6850796 11.4735225 33.39651 11.4162058 33.1775918 11.3015707 32.9586737 11.1869356 32.801453 11.0436439 32.7059251 10.8716913L32.7059251 13.5205423 31.0819584 13.8787876 31.0819584 4.66914546 31.0819584 4.66914546zM33.6373177 9.88910976C34.425423 9.88910976 34.8194698 9.25862626 34.8194698 7.99764034 34.8194698 7.38352382 34.7219532 6.91680227 34.526917 6.59746168 34.3318808 6.27812109 34.0512718 6.11845319 33.6850814 6.11845319 33.4144189 6.11845319 33.1895336 6.21466334 33.0104188 6.40708651 32.8313039 6.59950969 32.6939846 6.82672939 32.5984567 7.08875244L32.5984567 8.98022186C32.7099059 9.283186 32.8591661 9.51040571 33.0462416 9.66188778 33.2333171 9.81336986 33.4303405 9.88910976 33.6373177 9.88910976L33.6373177 9.88910976 33.6373177 9.88910976zM38.7122133 4.66914546L38.7122133 11.313853 37.0763058 11.313853 37.0763058 4.66914546 38.7122133 4.66914546 38.7122133 4.66914546zM37.0763058 3.19527319C37.0763058 2.9578148 37.1519309 2.75311236 37.3031835 2.58115974 37.454436 2.40920711 37.6494693 2.32323209 37.8882891 2.32323209 38.1111876 2.32323209 38.3042307 2.40920711 38.4674242 2.58115974 38.6306178 2.75311236 38.7122133 2.9578148 38.7122133 3.19527319 38.7122133 3.42454335 38.6306178 3.61901067 38.4674242 3.77868096 38.3042307 3.93835126 38.1111876 4.01818521 37.8882891 4.01818521 37.6494693 4.01818521 37.454436 3.93835126 37.3031835 3.77868096 37.1519309 3.61901067 37.0763058 3.42454335 37.0763058 3.19527319L37.0763058 3.19527319 37.0763058 3.19527319zM39.5600194 2.92506327L41.183986 2.3600789 41.183986 11.313853 39.5600194 11.313853 39.5600194 2.92506327 39.5600194 2.92506327zM42.0317921 2.92506327L43.6557587 2.3600789 43.6557587 11.313853 42.0317921 11.313853 42.0317921 2.92506327 42.0317921 2.92506327zM52.4678491 4.66914546L52.4678491 5.12358941C52.5792983 4.93526034 52.7623908 4.78582757 53.0171319 4.67528659 53.2718731 4.56474562 53.5385512 4.50947596 53.8171743 4.50947596 54.0002695 4.50947596 54.1773915 4.5381343 54.3485457 4.59545185 54.5196999 4.65276939 54.653039 4.74693251 54.7485669 4.87794403L54.1634613 6.36409857C53.9644448 6.18395773 53.7455299 6.09388866 53.5067101 6.09388866 53.3076936 6.09388866 53.1265913 6.16962856 52.9633977 6.32111063 52.8002042 6.47259271 52.6668651 6.70800051 52.5633765 7.0273411L52.5633765 11.313853 50.9394099 11.313853 50.9394099 4.66914546 52.4678491 4.66914546 52.4678491 4.66914546zM48.4616088 10.8293192C48.0288882 11.2158724 47.5247405 11.484111 46.9866674 11.484111 45.1230941 11.484111 44.3419473 9.9280521 44.3419473 8.00855616 44.3419473 6.08906023 45.1230941 4.53300133 46.9866674 4.53300133 47.5659007 4.53300133 48.0581835 4.78305929 48.4616088 5.15851782L48.4616088 4.62308717 50.1165139 4.62308717 50.1165139 11.3940252 48.4616088 11.3940252 48.4616088 10.8293192 48.4616088 10.8293192zM48.4616088 9.15122021C48.2543924 9.61274643 47.8880601 9.90097852 47.3585983 9.91039814 46.3573695 9.92821093 45.9732516 9.08058547 45.9552061 8.02687778 45.9371607 6.97317009 46.2920212 6.11239773 47.29325 6.09458506 47.8094156 6.08540205 48.2279222 6.42162094 48.4616088 6.9625236L48.4616088 9.15122021 48.4616088 9.15122021zM28.7863689 10.8293192C28.3536484 11.2158724 27.8495007 11.484111 27.3114276 11.484111 25.4478542 11.484111 24.6667075 9.9280521 24.6667075 8.00855616 24.6667075 6.08906023 25.4478542 4.53300133 27.3114276 4.53300133 27.8906609 4.53300133 28.3829436 4.78305929 28.7863689 5.15851782L28.7863689 4.62308717 30.441274 4.62308717 30.441274 11.3940252 28.7863689 11.3940252 28.7863689 10.8293192 28.7863689 10.8293192 28.7863689 10.8293192zM28.7863689 9.15122021C28.5791526 9.61274643 28.2128202 9.90097852 27.6833584 9.91039814 26.6821296 9.92821093 26.2980117 9.08058547 26.2799663 8.02687778 26.2619208 6.97317009 26.6167813 6.11239773 27.6180101 6.09458506 28.1341757 6.08540205 28.5526824 6.421Line truncated
|
||||
|
After Width: | Height: | Size: 7.3 KiB |
@@ -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 (
|
||||
<div
|
||||
ref={containerRef}
|
||||
className="w-full h-full bg-neutral-900 overflow-hidden touch-none"
|
||||
/>
|
||||
<div className="relative w-full h-full bg-neutral-900 overflow-hidden touch-none">
|
||||
<div ref={containerRef} className="w-full h-full" />
|
||||
{/* 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. */}
|
||||
<div className="absolute top-2 left-2 z-10 flex items-center gap-2 rounded-md bg-black/50 px-2 py-1">
|
||||
<a
|
||||
href="https://www.mapillary.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Imagery from Mapillary"
|
||||
>
|
||||
<Image src="/mapillary-logo.svg" alt="Mapillary" width={61} height={14} className="h-3 w-auto" />
|
||||
</a>
|
||||
<a
|
||||
href="https://creativecommons.org/licenses/by-sa/4.0/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-[10px] leading-none text-white/80 hover:text-white"
|
||||
>
|
||||
CC BY-SA 4.0
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -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 (
|
||||
<a
|
||||
href={href}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="text-brand underline underline-offset-2 hover:no-underline"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<div className="min-h-dvh vn-gradient-bg">
|
||||
<div className="container mx-auto px-4 py-6 max-w-3xl">
|
||||
<header className="flex flex-wrap justify-between items-center gap-3 mb-10">
|
||||
<Link href="/" className="text-2xl font-bold text-foreground tracking-wider">
|
||||
VNGeoGuessr
|
||||
</Link>
|
||||
<Link href="/" className="text-sm text-muted-foreground hover:text-foreground underline underline-offset-2">
|
||||
Back to game
|
||||
</Link>
|
||||
</header>
|
||||
|
||||
<h1 className="text-3xl font-extrabold text-foreground mb-8 tracking-tight">Credits & Licenses</h1>
|
||||
|
||||
<div className="space-y-6">
|
||||
<Card className="bg-card border-border shadow-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-xl font-bold text-card-foreground">Street-level imagery</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm text-muted-foreground leading-relaxed">
|
||||
<a
|
||||
href="https://www.mapillary.com/"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="Mapillary"
|
||||
className="inline-block rounded-md bg-neutral-900 px-3 py-2"
|
||||
>
|
||||
<Image src="/mapillary-logo.svg" alt="Mapillary" width={122} height={28} className="h-5 w-auto" />
|
||||
</a>
|
||||
<p>
|
||||
All 360° panoramas are contributed by the{' '}
|
||||
<ExternalLink href="https://www.mapillary.com/">Mapillary</ExternalLink>{' '}
|
||||
community and licensed under{' '}
|
||||
<ExternalLink href="https://creativecommons.org/licenses/by-sa/4.0/">CC BY-SA 4.0</ExternalLink>.
|
||||
Thank you to everyone who mapped the streets of Vietnam.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border shadow-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-xl font-bold text-card-foreground">Map data & tiles</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3 text-sm text-muted-foreground leading-relaxed">
|
||||
<p>
|
||||
Map data ©{' '}
|
||||
<ExternalLink href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</ExternalLink>,
|
||||
available under the{' '}
|
||||
<ExternalLink href="https://opendatacommons.org/licenses/odbl/">Open Database License (ODbL)</ExternalLink>.
|
||||
{/* 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 <ExternalLink href="https://www.geoapify.com/">Geoapify</ExternalLink>.</>
|
||||
) : (
|
||||
<> Map tiles are served by OpenStreetMap.</>
|
||||
)}
|
||||
</p>
|
||||
<p>
|
||||
Province and district boundaries are derived from OpenStreetMap
|
||||
via{' '}
|
||||
<ExternalLink href="https://nominatim.org/">Nominatim</ExternalLink>.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
<Card className="bg-card border-border shadow-sm">
|
||||
<CardHeader className="pb-3">
|
||||
<CardTitle className="text-xl font-bold text-card-foreground">Open-source software</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="text-sm text-muted-foreground leading-relaxed">
|
||||
<ul className="space-y-1.5">
|
||||
{LIBRARIES.map((lib) => (
|
||||
<li key={lib.name} className="flex justify-between gap-4">
|
||||
<ExternalLink href={lib.href}>{lib.name}</ExternalLink>
|
||||
<span className="text-muted-foreground/70">{lib.license}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
<p className="mt-4 pt-3 border-t border-border">
|
||||
VNGeoGuessr itself is open source under the{' '}
|
||||
<ExternalLink href="https://www.apache.org/licenses/LICENSE-2.0">Apache License 2.0</ExternalLink>.
|
||||
</p>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -134,6 +134,22 @@ export default function Home() {
|
||||
</CardContent>
|
||||
</Card>
|
||||
</div>
|
||||
|
||||
{/* Data attribution. mb clears the fixed build-stamp footer. */}
|
||||
<p className="mt-10 mb-8 text-center text-xs text-muted-foreground/80">
|
||||
Imagery ©{' '}
|
||||
<a href="https://www.mapillary.com/" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2 hover:text-foreground">
|
||||
Mapillary
|
||||
</a>{' '}
|
||||
(CC BY-SA 4.0) · Map data ©{' '}
|
||||
<a href="https://www.openstreetmap.org/copyright" target="_blank" rel="noopener noreferrer" className="underline underline-offset-2 hover:text-foreground">
|
||||
OpenStreetMap contributors
|
||||
</a>{' '}
|
||||
·{' '}
|
||||
<Link href="/credits" className="underline underline-offset-2 hover:text-foreground">
|
||||
Credits
|
||||
</Link>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<DonateQRModal isOpen={showDonateModal} onClose={() => setShowDonateModal(false)} />
|
||||
|
||||
Reference in new issue
Block a user