Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/app/(marketing)/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const webAppJsonLd = {
};

export const metadata: Metadata = {
title: "PackedPlaces.com See How Crowded Travel Destinations Really Get",
title: "PackedPlaces.com: See How Crowded Travel Destinations Really Get",
alternates: { canonical: "https://packedplaces.com" },
};

Expand Down
135 changes: 135 additions & 0 deletions src/app/api/og/route.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { ImageResponse } from "next/og";
import { crowdColor } from "@/lib/crowd-palette";

/**
* Default Open Graph card for the site (1200x630), used by the home page and
* any page that does not supply its own image. Lives under /api so it stays
* dynamic while the marketing pages remain static; the root layout references
* it via openGraph.images. Destination pages set their own card at
* /api/og/destination/[slug].
*/

export const runtime = "nodejs";

const SIZE = { width: 1200, height: 630 };

const FRAUNCES_URL =
"https://fonts.gstatic.com/s/fraunces/v38/6NVf8FyLNQOQZAnv9ZwNjucMHVn85Ni7emAe9lKqZTnbB-gzTK0K1ChJdt9vIVYX9G37lod_sPEKsxx664UJf1iVSv7W.ttf";

// A left-to-right crowd ramp (quiet to packed) built from the shared palette,
// so the card reads in the same visual language as the map and the charts.
const RAMP = Array.from({ length: 24 }, (_, i) => i / 23);

let frauncesPromise: Promise<ArrayBuffer | null> | null = null;

function loadFraunces(): Promise<ArrayBuffer | null> {
frauncesPromise ??= fetch(FRAUNCES_URL)
.then((res) => (res.ok ? res.arrayBuffer() : null))
.catch(() => null);
return frauncesPromise;
}

export async function GET() {
const fraunces = await loadFraunces();

return new ImageResponse(
(
<div
style={{
width: "100%",
height: "100%",
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
backgroundColor: "#f6f1e7",
backgroundImage:
"linear-gradient(to right, rgba(34,48,58,0.06) 1px, transparent 1px), linear-gradient(to bottom, rgba(34,48,58,0.06) 1px, transparent 1px)",
backgroundSize: "80px 80px",
padding: "72px 80px",
}}
>
<div style={{ display: "flex", fontSize: 24, letterSpacing: 6, color: "#c2542f" }}>
127 REGIONS · 700+ DESTINATIONS · 100+ COUNTRIES
</div>

<div style={{ display: "flex", flexDirection: "column" }}>
<div
style={{
display: "flex",
fontSize: 96,
lineHeight: 1.02,
color: "#22303a",
fontFamily: fraunces ? "Fraunces" : undefined,
}}
>
PackedPlaces
</div>
<div
style={{
display: "flex",
marginTop: 20,
fontSize: 40,
lineHeight: 1.15,
maxWidth: 900,
color: "#5c6a72",
fontFamily: fraunces ? "Fraunces" : undefined,
fontStyle: fraunces ? "italic" : undefined,
}}
>
See how crowded travel destinations really get, week by week.
</div>
</div>

<div style={{ display: "flex", flexDirection: "column" }}>
<div style={{ display: "flex", gap: 4 }}>
{RAMP.map((score, i) => (
<div
key={i}
style={{
width: 42,
height: 48,
borderRadius: 3,
backgroundColor: crowdColor(score, "light"),
}}
/>
))}
</div>
<div
style={{
display: "flex",
justifyContent: "space-between",
marginTop: 16,
fontSize: 20,
letterSpacing: 4,
color: "#97998c",
}}
>
<div style={{ display: "flex" }}>QUIET</div>
<div style={{ display: "flex" }}>PACKED</div>
</div>
<div
style={{
display: "flex",
justifyContent: "flex-end",
marginTop: 20,
fontSize: 22,
letterSpacing: 4,
color: "#97998c",
}}
>
PACKEDPLACES.COM
</div>
</div>
</div>
),
{
...SIZE,
headers: {
"Cache-Control": "public, max-age=86400",
},
fonts: fraunces
? [{ name: "Fraunces", data: fraunces, weight: 600 as const }]
: undefined,
},
);
}
18 changes: 18 additions & 0 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,24 @@ export async function generateMetadata(): Promise<Metadata> {
url: "https://packedplaces.com",
siteName: "PackedPlaces.com",
type: "website",
images: [
{
// Generated 1200x630 card (next/og), resolved against metadataBase.
url: "/api/og",
width: 1200,
height: 630,
alt: t("ogTitle"),
},
],
},
twitter: {
// Large-image card so shared links render a full-width preview instead of
// the tiny bild-less "summary" thumbnail. twitter.images is omitted on
// purpose: with it absent, X falls back to og:image, so destination pages
// that set their own og:image get the correct card without touching twitter.
card: "summary_large_image",
title: t("ogTitle"),
description: t("ogDescription"),
},
// No hreflang alternates: the site serves every language from the same URL
// (locale is negotiated, not path-prefixed), so per-language alternates would
Expand Down
6 changes: 3 additions & 3 deletions src/messages/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
"contactTitle": "Kontakt",
"contactText": "Bei Fragen zu dieser Datenschutzerklärung oder zur Ausübung Ihrer DSGVO-Rechte nutzen Sie bitte unser Kontaktformular.",
"metaTitle": "Datenschutzerklärung — PackedPlaces.com",
"metaDescription": "Datenschutzerklärung von PackedPlaces.com wie wir mit Ihren Daten, Cookies und Analysen umgehen."
"metaDescription": "Datenschutzerklärung von PackedPlaces.com: wie wir mit Ihren Daten, Cookies und Analysen umgehen."
},
"contact": {
"title": "Kontakt",
Expand Down Expand Up @@ -465,9 +465,9 @@
"system": "System"
},
"meta": {
"title": "PackedPlaces.com Sehen Sie, wie voll Reiseziele wirklich werden",
"title": "PackedPlaces.com: Sehen Sie, wie voll Reiseziele wirklich werden",
"description": "PackedPlaces.com visualisiert die Auslastung von über 700 Reisezielen weltweit, Woche für Woche, basierend auf Schulferien-Daten aus über 100 Ländern.",
"ogTitle": "PackedPlaces.com Sehen Sie, wie voll Reiseziele wirklich werden",
"ogTitle": "PackedPlaces.com: Sehen Sie, wie voll Reiseziele wirklich werden",
"ogDescription": "PackedPlaces.com visualisiert die Auslastung von über 700 Reisezielen weltweit, Woche für Woche, basierend auf Schulferien-Daten aus über 100 Ländern."
},
"bestTime": {
Expand Down
6 changes: 3 additions & 3 deletions src/messages/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
"contactTitle": "Contact Us",
"contactText": "If you have questions about this privacy policy or wish to exercise your GDPR rights, please reach out via our contact form.",
"metaTitle": "Privacy Policy — PackedPlaces.com",
"metaDescription": "Privacy policy for PackedPlaces.com how we handle your data, cookies, and analytics."
"metaDescription": "Privacy policy for PackedPlaces.com: how we handle your data, cookies, and analytics."
},
"contact": {
"title": "Contact Us",
Expand Down Expand Up @@ -465,9 +465,9 @@
"system": "System"
},
"meta": {
"title": "PackedPlaces.com See How Crowded Travel Destinations Really Get",
"title": "PackedPlaces.com: See How Crowded Travel Destinations Really Get",
"description": "PackedPlaces.com visualizes tourist crowdedness for 700+ destinations worldwide, week by week, powered by school holiday data from 100+ countries.",
"ogTitle": "PackedPlaces.com See How Crowded Travel Destinations Really Get",
"ogTitle": "PackedPlaces.com: See How Crowded Travel Destinations Really Get",
"ogDescription": "PackedPlaces.com visualizes tourist crowdedness for 700+ destinations worldwide, week by week, powered by school holiday data from 100+ countries."
},
"bestTime": {
Expand Down
8 changes: 4 additions & 4 deletions src/messages/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
"contactTitle": "Contacto",
"contactText": "Si tiene preguntas sobre esta política de privacidad o desea ejercer sus derechos RGPD, contáctenos a través de nuestro formulario de contacto.",
"metaTitle": "Política de privacidad — PackedPlaces.com",
"metaDescription": "Política de privacidad de PackedPlaces.com cómo tratamos sus datos, cookies y analíticas."
"metaDescription": "Política de privacidad de PackedPlaces.com: cómo tratamos sus datos, cookies y analíticas."
},
"contact": {
"title": "Contacto",
Expand All @@ -223,7 +223,7 @@
"errorServer": "Algo salió mal. Por favor, inténtelo de nuevo más tarde.",
"errorTurnstile": "La verificación captcha falló. Por favor, inténtelo de nuevo.",
"metaTitle": "Contacto — PackedPlaces.com",
"metaDescription": "Póngase en contacto con el equipo de PackedPlaces.com preguntas, comentarios y sugerencias son bienvenidos."
"metaDescription": "Póngase en contacto con el equipo de PackedPlaces.com: preguntas, comentarios y sugerencias son bienvenidos."
},
"featurePages": {
"shell": {
Expand Down Expand Up @@ -465,9 +465,9 @@
"system": "Sistema"
},
"meta": {
"title": "PackedPlaces.com Descubra lo masificados que están realmente los destinos turísticos",
"title": "PackedPlaces.com: Descubra lo masificados que están realmente los destinos turísticos",
"description": "PackedPlaces.com visualiza la masificación turística de más de 700 destinos en todo el mundo, semana a semana, basándose en datos de vacaciones escolares de más de 100 países.",
"ogTitle": "PackedPlaces.com Descubra lo masificados que están realmente los destinos turísticos",
"ogTitle": "PackedPlaces.com: Descubra lo masificados que están realmente los destinos turísticos",
"ogDescription": "PackedPlaces.com visualiza la masificación turística de más de 700 destinos en todo el mundo, semana a semana, basándose en datos de vacaciones escolares de más de 100 países."
},
"bestTime": {
Expand Down
8 changes: 4 additions & 4 deletions src/messages/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -200,7 +200,7 @@
"contactTitle": "Nous contacter",
"contactText": "Si vous avez des questions sur cette politique de confidentialité ou souhaitez exercer vos droits RGPD, contactez-nous via notre formulaire de contact.",
"metaTitle": "Politique de confidentialité — PackedPlaces.com",
"metaDescription": "Politique de confidentialité de PackedPlaces.com comment nous traitons vos données, cookies et analyses."
"metaDescription": "Politique de confidentialité de PackedPlaces.com: comment nous traitons vos données, cookies et analyses."
},
"contact": {
"title": "Contact",
Expand All @@ -223,7 +223,7 @@
"errorServer": "Une erreur est survenue. Veuillez réessayer plus tard.",
"errorTurnstile": "La vérification captcha a échoué. Veuillez réessayer.",
"metaTitle": "Contact — PackedPlaces.com",
"metaDescription": "Contactez l'équipe de PackedPlaces.com questions, retours et suggestions bienvenus."
"metaDescription": "Contactez l'équipe de PackedPlaces.com: questions, retours et suggestions bienvenus."
},
"featurePages": {
"shell": {
Expand Down Expand Up @@ -465,9 +465,9 @@
"system": "Système"
},
"meta": {
"title": "PackedPlaces.com Découvrez l'affluence réelle des destinations touristiques",
"title": "PackedPlaces.com: Découvrez l'affluence réelle des destinations touristiques",
"description": "PackedPlaces.com visualise l'affluence touristique de plus de 700 destinations dans le monde, semaine par semaine, à partir des données de vacances scolaires de plus de 100 pays.",
"ogTitle": "PackedPlaces.com Découvrez l'affluence réelle des destinations touristiques",
"ogTitle": "PackedPlaces.com: Découvrez l'affluence réelle des destinations touristiques",
"ogDescription": "PackedPlaces.com visualise l'affluence touristique de plus de 700 destinations dans le monde, semaine par semaine, à partir des données de vacances scolaires de plus de 100 pays."
},
"bestTime": {
Expand Down
Loading