feat(landing): self-host Inter font and promote Android app on Google Play
- Replace next/font/google with next/font/local + vendored Inter variable woff2 (latin, @fontsource-variable/inter@5.3.0), so neither visitors nor the Docker build contact Google's servers. - Add PlayStoreBadge (official Google Play artwork, DE/EN by locale, served locally from /public/badges) to the hero, a new "PawFeed in your pocket" section, and the shared landing footer (also covers /ueber-uns). - Centralize the Play Store URL in src/lib/app-links.ts (derived from the android.package id org.pawfeed.app). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
779f18966d
commit
71ac36b08a
10 files changed
+76
-6
No files matched your search
+5
-1
@@ -742,7 +742,11 @@
|
||||
"detailPoint2": "Gesundheitsdaten bleiben privat, außer du teilst sie bewusst per Link",
|
||||
"footerTagline": "Wo Haustiere die Stars sind.",
|
||||
"footerDesignedBy": "Designed & Powered by PawFeed",
|
||||
"footerSecurityBadge": "Sicherheits-Check: Note A+"
|
||||
"footerSecurityBadge": "Sicherheits-Check: Note A+",
|
||||
"heroAndroidHint": "Auch als App für Android",
|
||||
"playStoreAlt": "Jetzt bei Google Play",
|
||||
"appHeading": "PawFeed für die Hosentasche",
|
||||
"appBody": "Die Android-App bringt Feed, Storys und Nachrichten direkt aufs Handy — mit Push-Benachrichtigungen, wenn dein Tier ein neues Like oder einen neuen Follower bekommt."
|
||||
},
|
||||
"AboutPage": {
|
||||
"metaTitle": "Über uns — PawFeed",
|
||||
|
||||
+5
-1
@@ -742,7 +742,11 @@
|
||||
"detailPoint2": "Health data stays private unless you deliberately share it via link",
|
||||
"footerTagline": "Where pets are the stars.",
|
||||
"footerDesignedBy": "Designed & Powered by PawFeed",
|
||||
"footerSecurityBadge": "Security check: Grade A+"
|
||||
"footerSecurityBadge": "Security check: Grade A+",
|
||||
"heroAndroidHint": "Also available as an Android app",
|
||||
"playStoreAlt": "Get it on Google Play",
|
||||
"appHeading": "PawFeed in your pocket",
|
||||
"appBody": "The Android app puts the feed, stories, and messages right on your phone — with push notifications when your pet gets a new like or follower."
|
||||
},
|
||||
"AboutPage": {
|
||||
"metaTitle": "About us — PawFeed",
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.8 KiB |
Binary file not shown.
+8
-3
@@ -1,6 +1,6 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { headers } from "next/headers";
|
||||
import { Inter } from "next/font/google";
|
||||
import localFont from "next/font/local";
|
||||
import { ThemeProvider } from "next-themes";
|
||||
import { NextIntlClientProvider } from "next-intl";
|
||||
import { getLocale, getMessages } from "next-intl/server";
|
||||
@@ -9,9 +9,14 @@ import { ActivePetProvider } from "@/context/ActivePetContext";
|
||||
import { Toaster } from "@/components/ui/sonner";
|
||||
import "./globals.css";
|
||||
|
||||
const inter = Inter({
|
||||
// Inter (variable weight, latin subset) is vendored in ./fonts rather than
|
||||
// pulled via next/font/google, so neither visitors nor the Docker build ever
|
||||
// contact Google's servers. Source: @fontsource-variable/inter@5.3.0.
|
||||
const inter = localFont({
|
||||
src: "./fonts/inter-latin-wght-normal.woff2",
|
||||
variable: "--font-sans",
|
||||
subsets: ["latin"],
|
||||
weight: "100 900",
|
||||
display: "swap",
|
||||
});
|
||||
|
||||
export const metadata: Metadata = {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { PawPrint, HeartPulse, MessageCircle, Images, Heart, Sparkles, CheckCirc
|
||||
import { ScrollReveal } from "@/components/landing/ScrollReveal";
|
||||
import { LandingHeader } from "@/components/landing/LandingHeader";
|
||||
import { LandingFooter } from "@/components/landing/LandingFooter";
|
||||
import { PlayStoreBadge } from "@/components/landing/PlayStoreBadge";
|
||||
|
||||
/**
|
||||
* Root route (/) — public landing page for logged-out visitors, redirects
|
||||
@@ -74,6 +75,11 @@ export default async function RootPage() {
|
||||
{t("logIn")}
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div className="mt-8 flex flex-col items-center gap-2 lg:items-start">
|
||||
<span className="text-sm text-muted-foreground">{t("heroAndroidHint")}</span>
|
||||
<PlayStoreBadge className="h-11" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Phone mockup — sits inside one soft marigold glow, the hero's
|
||||
@@ -211,6 +217,20 @@ export default async function RootPage() {
|
||||
</div>
|
||||
</ScrollReveal>
|
||||
</section>
|
||||
|
||||
{/* Android app — a closing, left-aligned invitation rather than a
|
||||
second hero; the official Play badge is the only CTA. */}
|
||||
<section className="px-6 pb-24 sm:px-10">
|
||||
<ScrollReveal className="mx-auto flex max-w-5xl flex-col items-center gap-8 text-center sm:flex-row sm:items-center sm:justify-between sm:text-left">
|
||||
<div className="max-w-xl">
|
||||
<h2 className="text-2xl font-bold tracking-tight text-foreground sm:text-3xl">
|
||||
{t("appHeading")}
|
||||
</h2>
|
||||
<p className="mt-3 text-base leading-relaxed text-muted-foreground">{t("appBody")}</p>
|
||||
</div>
|
||||
<PlayStoreBadge className="h-14 shrink-0" />
|
||||
</ScrollReveal>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
{/* Footer — repeats the navbar */}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { getTranslations } from "next-intl/server";
|
||||
import Link from "next/link";
|
||||
import { PawPrint, ShieldCheck } from "lucide-react";
|
||||
import { PlayStoreBadge } from "./PlayStoreBadge";
|
||||
|
||||
/**
|
||||
* Shared footer for the public landing page and "Über uns" (Gitea #23) —
|
||||
@@ -19,6 +20,9 @@ export async function LandingFooter() {
|
||||
<span className="text-base font-bold text-foreground">PawFeed</span>
|
||||
</div>
|
||||
<p className="mt-2 text-sm text-muted-foreground">{t("footerTagline")}</p>
|
||||
<div className="mt-4">
|
||||
<PlayStoreBadge className="h-10" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav className="flex flex-wrap items-center justify-center gap-x-6 gap-y-2 text-sm text-muted-foreground sm:justify-end">
|
||||
@@ -35,7 +39,7 @@ export async function LandingFooter() {
|
||||
<a href="https://www.cloudflare.com" target="_blank" rel="noopener noreferrer" className="flex items-center">
|
||||
{/* Official Cloudflare badge asset — brand guidelines require
|
||||
using their own artwork, not a custom "Protected by
|
||||
Cloudflare" text label. Plain <img>, matching the existing
|
||||
Cloudflare" text label. Plain img element, matching the existing
|
||||
AiDisclosureBadge pattern for small fixed-size assets. */}
|
||||
<img
|
||||
src="/badges/cloudflare-protected.png"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import Image from "next/image";
|
||||
import { getLocale, getTranslations } from "next-intl/server";
|
||||
import { PLAY_STORE_URL } from "@/lib/app-links";
|
||||
|
||||
/**
|
||||
* Official "Get it on Google Play" badge — Google's brand guidelines require
|
||||
* their own artwork (no recolouring, no custom text), vendored locally in
|
||||
* /public/badges so the page makes no request to Google.
|
||||
*/
|
||||
export async function PlayStoreBadge({ className = "h-12" }: { className?: string }) {
|
||||
const locale = await getLocale();
|
||||
const t = await getTranslations("Landing");
|
||||
const src = locale.startsWith("de") ? "/badges/google-play-de.png" : "/badges/google-play-en.png";
|
||||
|
||||
return (
|
||||
<a
|
||||
href={PLAY_STORE_URL}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="inline-flex rounded-lg transition-opacity hover:opacity-85 focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-orange-500"
|
||||
>
|
||||
<Image src={src} alt={t("playStoreAlt")} width={646} height={250} sizes="180px" className={`${className} w-auto`} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Store listing URLs for the native app — the single place to change them.
|
||||
* The Play URL is derived from the Android package name in mobile/app.json
|
||||
* (`android.package`), so it stays valid as long as that id doesn't change.
|
||||
*/
|
||||
export const ANDROID_PACKAGE_ID = "org.pawfeed.app";
|
||||
|
||||
export const PLAY_STORE_URL = `https://play.google.com/store/apps/details?id=${ANDROID_PACKAGE_ID}`;
|
||||
Reference in new issue
Block a user