feat: add SEO meta, OG image, favicons and sitemap

- Full meta suite in index.html: description, canonical, robots,
  Open Graph + Twitter cards, theme-color and JSON-LD structured data
- 1200x630 OG share card matching the homepage Plan-B wordmark
- Favicon set generated from the intro storm icon (svg + png + apple
  touch + 192/512 PWA icons) and a web manifest
- robots.txt + sitemap.xml with hreflang alternates
- setLang() now syncs title/description/og tags on EN/DE toggle

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Dorian
2026-06-16 14:52:58 +01:00
parent ff8331c54d
commit b0b4d41571
13 changed files with 188 additions and 3 deletions

View File

@@ -3,10 +3,84 @@
<head>
<meta charset="UTF-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0"/>
<title>Plan-B — Survival Preparedness Advisor</title>
<!-- ── Primary meta ─────────────────────────────────────────────── -->
<title>Plan-B — AI-Assisted Crisis Preparedness Advisor</title>
<meta name="description" content="Free AI-assisted crisis-preparedness advisor for urban households. Assess your readiness across 9 collapse scenarios and get a personalised survival plan in minutes."/>
<meta name="author" content="Plan-B"/>
<meta name="theme-color" content="#5A9A78"/>
<meta name="color-scheme" content="light"/>
<meta name="robots" content="index, follow, max-image-preview:large, max-snippet:-1"/>
<link rel="canonical" href="https://plan-b.now/"/>
<!-- ── Open Graph (Facebook, LinkedIn, WhatsApp, Signal, Telegram…) ─ -->
<meta property="og:type" content="website"/>
<meta property="og:site_name" content="Plan-B"/>
<meta property="og:title" content="Plan-B — AI-Assisted Crisis Preparedness Advisor"/>
<meta property="og:description" content="Assess your household's readiness across 9 collapse scenarios and get a personalised survival plan — water, food, energy, medical and budget. Built for city apartments."/>
<meta property="og:url" content="https://plan-b.now/"/>
<meta property="og:image" content="https://plan-b.now/images/og-image.png"/>
<meta property="og:image:secure_url" content="https://plan-b.now/images/og-image.png"/>
<meta property="og:image:type" content="image/png"/>
<meta property="og:image:width" content="1200"/>
<meta property="og:image:height" content="630"/>
<meta property="og:image:alt" content="Plan-B — AI-Assisted Preparedness for Urban Households"/>
<meta property="og:locale" content="en_US"/>
<meta property="og:locale:alternate" content="de_DE"/>
<!-- ── Twitter / X ─────────────────────────────────────────────── -->
<meta name="twitter:card" content="summary_large_image"/>
<meta name="twitter:title" content="Plan-B — AI-Assisted Crisis Preparedness Advisor"/>
<meta name="twitter:description" content="Assess your household's readiness across 9 collapse scenarios and get a personalised survival plan. Built for city apartments."/>
<meta name="twitter:image" content="https://plan-b.now/images/og-image.png"/>
<meta name="twitter:image:alt" content="Plan-B — AI-Assisted Preparedness for Urban Households"/>
<!-- ── Icons / PWA ─────────────────────────────────────────────── -->
<link rel="icon" href="/images/favicon.svg" type="image/svg+xml"/>
<link rel="icon" href="/images/favicon-32.png" sizes="32x32" type="image/png"/>
<link rel="icon" href="/images/favicon-16.png" sizes="16x16" type="image/png"/>
<link rel="apple-touch-icon" href="/images/apple-touch-icon.png"/>
<link rel="manifest" href="/site.webmanifest"/>
<!-- ── Fonts ───────────────────────────────────────────────────── -->
<link rel="preconnect" href="https://fonts.googleapis.com"/>
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin/>
<link href="https://fonts.googleapis.com/css2?family=Space+Mono:wght@400;700&family=DM+Serif+Display&family=Barlow:wght@300;400;500;600&display=swap" rel="stylesheet"/>
<!-- ── Structured data (Google rich results) ───────────────────── -->
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "WebSite",
"@id": "https://plan-b.now/#website",
"url": "https://plan-b.now/",
"name": "Plan-B",
"description": "AI-assisted crisis-preparedness advisor for urban households.",
"inLanguage": ["en", "de"],
"publisher": { "@id": "https://plan-b.now/#org" }
},
{
"@type": "Organization",
"@id": "https://plan-b.now/#org",
"name": "Plan-B",
"url": "https://plan-b.now/",
"logo": "https://plan-b.now/images/icon-512.png"
},
{
"@type": "WebApplication",
"name": "Plan-B Preparedness Advisor",
"url": "https://plan-b.now/",
"applicationCategory": "LifestyleApplication",
"operatingSystem": "Web",
"browserRequirements": "Requires JavaScript.",
"inLanguage": ["en", "de"],
"offers": { "@type": "Offer", "price": "0", "priceCurrency": "EUR" }
}
]
}
</script>
</head>
<body>
<div id="app"></div>

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 661 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -0,0 +1,7 @@
<svg width="96" height="96" viewBox="0 0 96 96" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Plan-B">
<circle cx="48" cy="48" r="44" fill="#2a3010"/>
<g transform="translate(19.75 21.15) scale(0.46)" fill="#f4ecd8">
<path d="M44.2,71.76 c2.03,0,3.68,1.65,3.68,3.68c0,2.03-1.65,3.68-3.68,3.68H23.38l-0.46-0.04c-2.67-0.34-5.09-0.97-7.29-1.88 c-2.25-0.93-4.26-2.14-6.04-3.63H9.58c-1.68-1.4-3.15-2.99-4.4-4.72C1.84,64.25,0.04,58.63,0,53.03 c-0.04-5.66,1.72-11.29,5.52-15.85c1.23-1.48,2.68-2.84,4.34-4.04c1.93-1.4,4.14-2.58,6.64-3.55c1.72-0.67,3.56-1.23,5.5-1.68 c2.2-8.74,6.89-15.47,12.92-20.14c5.64-4.37,12.43-6.92,19.42-7.59c6.96-0.67,14.12,0.51,20.55,3.6 c7.37,3.54,13.43,9.56,17.11,16.87c1.6-0.25,3.2-0.38,4.79-0.36c6.72,0.05,13.2,2.45,18.3,7.95c5.31,5.72,7.88,14.14,7.79,21.82 c-0.07,6.31-1.77,12.59-5.25,17.22c-2.27,3.02-5.18,5.47-8.67,7.42c-3.36,1.88-7.28,3.31-11.68,4.33c-1.98,0.45-3.95-0.78-4.4-2.76 c-0.45-1.98,0.78-3.95,2.76-4.4c3.71-0.86,6.97-2.04,9.72-3.58c2.63-1.47,4.78-3.26,6.39-5.41c2.5-3.33,3.73-8.04,3.78-12.87 c0.06-5.07-1.18-10.16-3.59-13.86c-0.69-1.07-1.44-2.03-2.25-2.89c-3.61-3.89-8.19-5.59-12.95-5.62 c-3.46-0.02-7.02,0.81-10.41,2.31c-0.75,0.37-1.51,0.78-2.25,1.21c-2.25,1.32-4.48,2.93-6.74,4.78l-4.84-5.54 c1.67-1.55,3.48-2.96,5.4-4.21c1.53-1,3.13-1.88,4.77-2.65c0.66-0.33,1.33-0.64,2-0.93c-3.19-5.65-7.78-9.7-12.98-12.2 c-5.2-2.49-11.02-3.45-16.69-2.9c-5.63,0.54-11.1,2.59-15.62,6.1c-5.23,4.06-9.2,10.11-10.73,18.14l-0.48,2.51l-2.5,0.44 c-2.45,0.43-4.64,1.02-6.56,1.77c-1.86,0.72-3.52,1.61-4.97,2.66c-1.16,0.84-2.16,1.78-3.01,2.8c-2.63,3.15-3.85,7.1-3.82,11.1 c0.03,4.06,1.35,8.16,3.79,11.53c0.91,1.25,1.96,2.4,3.16,3.4l-0.01,0.01c1.2,1,2.58,1.83,4.13,2.47c1.53,0.63,3.22,1.08,5.09,1.34 H44.2L44.2,71.76z"/>
</g>
<path d="M47.2 49.5 L57.6 49.5 L54 56.5 L60.2 56.6 L45.2 74.9 L48.1 62.9 L43.2 62.9 Z" fill="#fff8dc"/>
</svg>

After

Width:  |  Height:  |  Size: 1.8 KiB

BIN
public/images/icon-192.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

BIN
public/images/icon-512.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

42
public/images/og-card.svg Normal file
View File

@@ -0,0 +1,42 @@
<svg width="1200" height="630" viewBox="0 0 1200 630" xmlns="http://www.w3.org/2000/svg">
<defs>
<!-- Glossy paint sheen on the wordmark, matching the hero's
#2a2a26 → #1A1A18 → #060604 vertical gradient. -->
<linearGradient id="paint" x1="0" y1="0" x2="0" y2="1">
<stop offset="0" stop-color="#2a2a26"/>
<stop offset="0.45" stop-color="#1A1A18"/>
<stop offset="1" stop-color="#060604"/>
</linearGradient>
<filter id="ds" x="-20%" y="-20%" width="140%" height="160%">
<feGaussianBlur in="SourceAlpha" stdDeviation="6"/>
<feOffset dx="0" dy="7" result="o"/>
<feComponentTransfer in="o" result="s"><feFuncA type="linear" slope="0.28"/></feComponentTransfer>
<feMerge><feMergeNode in="s"/><feMergeNode in="SourceGraphic"/></feMerge>
</filter>
</defs>
<!-- Flat hero background -->
<rect width="1200" height="630" fill="#F0F0F0"/>
<!-- Embossed multilingual "Plan B" pattern (the homepage page-bg),
tilted ~-5deg and held very faint behind the wordmark. -->
<g transform="rotate(-5 600 315)" fill="#1A1A18" opacity="0.04"
font-family="Georgia, 'Times New Roman', serif" font-style="italic" font-weight="700" font-size="64" letter-spacing="6">
<text x="-60" y="70">Plan B&#8194;Piano B&#8194;Plano B&#8194;План Б&#8194;プランB&#8194;Plan B</text>
<text x="-160" y="190">Phương án B&#8194;备用计划&#8194;Plan B&#8194;플랜 B&#8194;Piano B&#8194;योजना बी</text>
<text x="-90" y="310">Piano B&#8194;Plan B&#8194;Σχέδιο Β&#8194;Plano B&#8194;خطة ب&#8194;プランB</text>
<text x="-200" y="430">Plan B&#8194;תוכנית ב&#8194;プランB&#8194;План Б&#8194;Plan B&#8194;Piano B</text>
<text x="-60" y="550">备用计划&#8194;Plan B&#8194;योजना बी&#8194;Plano B&#8194;Plan B&#8194;Σχέδιο Β</text>
<text x="-150" y="670">Plan B&#8194;Piano B&#8194;План Б&#8194;Plan B&#8194;プランB&#8194;Plano B</text>
</g>
<!-- Hero wordmark — centred, glossy dark paint, like the homepage. -->
<text x="600" y="372" text-anchor="middle" filter="url(#ds)"
font-family="Georgia, 'Times New Roman', serif" font-weight="700"
font-size="190" letter-spacing="9" fill="url(#paint)">Plan-B</text>
<!-- Tagline -->
<text x="600" y="460" text-anchor="middle"
font-family="'Helvetica Neue', Arial, sans-serif" font-weight="500"
font-size="23" letter-spacing="7" fill="#0a0a0a">AI-ASSISTED PREPAREDNESS FOR URBAN HOUSEHOLDS</text>
</svg>

After

Width:  |  Height:  |  Size: 2.5 KiB

BIN
public/images/og-image.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

4
public/robots.txt Normal file
View File

@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://plan-b.now/sitemap.xml

16
public/site.webmanifest Normal file
View File

@@ -0,0 +1,16 @@
{
"name": "Plan-B — Crisis Preparedness Advisor",
"short_name": "Plan-B",
"description": "AI-assisted crisis-preparedness advisor for urban households.",
"start_url": "/",
"scope": "/",
"display": "standalone",
"background_color": "#FAFAFA",
"theme_color": "#5A9A78",
"lang": "en",
"icons": [
{ "src": "/images/icon-192.png", "sizes": "192x192", "type": "image/png" },
{ "src": "/images/icon-512.png", "sizes": "512x512", "type": "image/png" },
{ "src": "/images/favicon.svg", "sizes": "any", "type": "image/svg+xml", "purpose": "any" }
]
}

12
public/sitemap.xml Normal file
View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml">
<url>
<loc>https://plan-b.now/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
<xhtml:link rel="alternate" hreflang="en" href="https://plan-b.now/"/>
<xhtml:link rel="alternate" hreflang="de" href="https://plan-b.now/"/>
<xhtml:link rel="alternate" hreflang="x-default" href="https://plan-b.now/"/>
</url>
</urlset>

View File

@@ -639,7 +639,9 @@ import { onMounted } from 'vue'
const T = {
en: {
brand: "Plan-B",
page_title: "Plan-B — Survival Preparedness Advisor",
page_title: "Plan-B — AI-Assisted Crisis Preparedness Advisor",
meta_description: "Free AI-assisted crisis-preparedness advisor for urban households. Assess your readiness across 9 collapse scenarios and get a personalised survival plan in minutes.",
og_locale: "en_US",
hero_eyebrow: "Crisis Preparedness Advisor",
hero_sub: "AI-Assisted Preparedness for Urban Households",
hero_cta: "Begin",
@@ -761,7 +763,9 @@ const T = {
},
de: {
brand: "Plan-B",
page_title: "Plan-B — Krisenvorsorge-Berater",
page_title: "Plan-B — KI-gestützter Krisenvorsorge-Berater",
meta_description: "Kostenloser KI-gestützter Krisenvorsorge-Berater für urbane Haushalte. Bewerte deine Vorsorge über 9 Krisenszenarien und erhalte in Minuten einen personalisierten Survival-Plan.",
og_locale: "de_DE",
hero_eyebrow: "⚡ Krisenvorsorge-Berater",
hero_sub: "KI-gestützte Vorsorge für urbane Haushalte",
hero_cta: "Beginnen",
@@ -899,6 +903,7 @@ function setLang(lang) {
if (T[lang][key]) el.textContent = T[lang][key]
})
if (T[lang].page_title) document.title = T[lang].page_title
syncMeta(lang)
document.querySelectorAll('[data-ph-' + lang + ']').forEach(el => {
el.placeholder = el.getAttribute('data-ph-' + lang)
})
@@ -913,6 +918,31 @@ function setLang(lang) {
function t(key) { return T[currentLang][key] || T['en'][key] || key }
// Keep SEO-relevant <head> tags in sync with the active language. The static
// tags in index.html are the English defaults (what non-JS social scrapers
// read); this updates them for the browser tab and JS-executing crawlers
// (e.g. Googlebot) when the user toggles EN/DE on the single-URL SPA.
function syncMeta(lang) {
const tr = T[lang] || T.en
const setAttr = (selector, attr, val) => {
const el = document.head.querySelector(selector)
if (el && val) el.setAttribute(attr, val)
}
if (tr.meta_description) {
setAttr('meta[name="description"]', 'content', tr.meta_description)
setAttr('meta[property="og:description"]', 'content', tr.meta_description)
setAttr('meta[name="twitter:description"]', 'content', tr.meta_description)
}
if (tr.page_title) {
setAttr('meta[property="og:title"]', 'content', tr.page_title)
setAttr('meta[name="twitter:title"]', 'content', tr.page_title)
}
if (tr.og_locale) {
setAttr('meta[property="og:locale"]', 'content', tr.og_locale)
setAttr('meta[property="og:locale:alternate"]', 'content', lang === 'de' ? 'en_US' : 'de_DE')
}
}
function syncPreferredLanguageField() {
const field = document.getElementById('f_pref_lang')
if (field) field.value = currentLang