From 3048be5c2981c12ced0c4c5ff4998e12f63d3c22 Mon Sep 17 00:00:00 2001
From: alexandrev-tibco
Date: Wed, 6 May 2026 09:25:40 +0200
Subject: [PATCH] Add sponsor slots and translated clues
---
README.md | 18 +++
app/page.tsx | 3 +-
app/sponsor/page.tsx | 90 +++++++++++
components/HomeClient.tsx | 13 +-
components/ShareResult.tsx | 8 +-
components/SponsorSlot.tsx | 44 +++++
deploy/k8s/deployment.yaml | 36 +++++
lib/i18n.ts | 319 ++++++++++++++++++++++++++++++++++++-
lib/sponsor.ts | 28 ++++
9 files changed, 554 insertions(+), 5 deletions(-)
create mode 100644 app/sponsor/page.tsx
create mode 100644 components/SponsorSlot.tsx
create mode 100644 lib/sponsor.ts
diff --git a/README.md b/README.md
index 1056543..424c031 100644
--- a/README.md
+++ b/README.md
@@ -20,6 +20,15 @@ Optional feedback env vars:
- `N8N_FEEDBACK_WEBHOOK_URL`: n8n webhook URL used to receive feedback submissions
+Optional sponsor env vars:
+
+- `NEXT_PUBLIC_SPONSOR_ENABLED`: set to `false` to hide the sponsor slot
+- `NEXT_PUBLIC_SPONSOR_NAME`: sponsor name shown in the slot
+- `NEXT_PUBLIC_SPONSOR_TAGLINE`: short sponsor copy
+- `NEXT_PUBLIC_SPONSOR_CTA`: sponsor button text
+- `NEXT_PUBLIC_SPONSOR_URL`: outbound sponsor URL
+- `NEXT_PUBLIC_SPONSOR_LABEL`: small label above the sponsor name
+
## Production build
```bash
@@ -73,6 +82,15 @@ To enable feedback submission, create a secret named `hidden11-feedback` in the
- `N8N_FEEDBACK_WEBHOOK_URL`
+To configure the sponsor slot in Kubernetes, create a secret named `hidden11-sponsor` in the `hidden11` namespace with any of:
+
+- `NEXT_PUBLIC_SPONSOR_ENABLED`
+- `NEXT_PUBLIC_SPONSOR_NAME`
+- `NEXT_PUBLIC_SPONSOR_TAGLINE`
+- `NEXT_PUBLIC_SPONSOR_CTA`
+- `NEXT_PUBLIC_SPONSOR_URL`
+- `NEXT_PUBLIC_SPONSOR_LABEL`
+
## ArgoCD
```bash
diff --git a/app/page.tsx b/app/page.tsx
index 185c624..23ad03a 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,7 +1,8 @@
import HomeClient from "@/components/HomeClient";
+import { getSponsorConfig } from "@/lib/sponsor";
export const dynamic = "force-dynamic";
export default function Page() {
- return ;
+ return ;
}
diff --git a/app/sponsor/page.tsx b/app/sponsor/page.tsx
new file mode 100644
index 0000000..9c23875
--- /dev/null
+++ b/app/sponsor/page.tsx
@@ -0,0 +1,90 @@
+import type { Metadata } from "next";
+import Link from "next/link";
+
+export const metadata: Metadata = {
+ title: "Sponsor hidden11",
+ description: "Sponsor the daily football puzzle played by sharp football fans.",
+ alternates: {
+ canonical: "https://hidden11.app/sponsor",
+ },
+ openGraph: {
+ title: "Sponsor hidden11",
+ description: "Reach football fans inside a daily football puzzle with shareable results.",
+ url: "https://hidden11.app/sponsor",
+ siteName: "hidden11",
+ type: "website",
+ },
+ twitter: {
+ card: "summary_large_image",
+ title: "Sponsor hidden11",
+ description: "Reach football fans inside a daily football puzzle with shareable results.",
+ },
+};
+
+const sponsorEmail = "sponsors@hidden11.app";
+
+export default function SponsorPage() {
+ const subject = encodeURIComponent("Sponsor hidden11");
+ const body = encodeURIComponent(
+ "Hi hidden11,\n\nI am interested in sponsoring hidden11.\n\nBrand:\nWebsite:\nCampaign dates:\nGoal:\n",
+ );
+
+ return (
+
+
+
+
+
+ Sponsorship
+
+ Reach football fans in a daily game they come back to.
+
+
+ hidden11 is a daily football lineup puzzle with shareable results, streaks and football-history clues.
+ Sponsorship appears directly in the playing flow and result moment.
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+function Metric({ label, value }: { label: string; value: string }) {
+ return (
+
+ );
+}
diff --git a/components/HomeClient.tsx b/components/HomeClient.tsx
index 437e598..1524c04 100644
--- a/components/HomeClient.tsx
+++ b/components/HomeClient.tsx
@@ -8,9 +8,11 @@ import Pitch from "@/components/Pitch";
import PlayerSearch from "@/components/PlayerSearch";
import FeedbackModal from "@/components/FeedbackModal";
import ShareResult from "@/components/ShareResult";
+import SponsorSlot from "@/components/SponsorSlot";
import StatsPanel from "@/components/StatsPanel";
import { trackEvent } from "@/lib/analytics-events";
import { buildGlobalStatsShareText } from "@/lib/share";
+import type { SponsorConfig } from "@/lib/sponsor";
import {
DIFFICULTIES,
MAX_ATTEMPTS,
@@ -37,7 +39,11 @@ import {
} from "@/lib/storage";
import type { DailyProgress, Difficulty, GlobalStats, Position } from "@/lib/types";
-export default function Home() {
+type HomeProps = {
+ sponsor: SponsorConfig;
+};
+
+export default function Home({ sponsor }: HomeProps) {
const dateKey = useMemo(() => getTodayKey(), []);
const dailyPuzzles = useMemo(() => getDailyPuzzles(dateKey), [dateKey]);
const [locale, setLocale] = useState("en");
@@ -316,6 +322,10 @@ export default function Home() {
+
“{quote.text[locale]}”
diff --git a/components/SponsorSlot.tsx b/components/SponsorSlot.tsx
new file mode 100644
index 0000000..fecc8fe
--- /dev/null
+++ b/components/SponsorSlot.tsx
@@ -0,0 +1,44 @@
+"use client";
+
+import { trackEvent } from "@/lib/analytics-events";
+import type { SponsorConfig } from "@/lib/sponsor";
+
+type SponsorSlotProps = {
+ placement: "home" | "result";
+ sponsor: SponsorConfig;
+};
+
+export default function SponsorSlot({ placement, sponsor }: SponsorSlotProps) {
+ if (!sponsor.enabled) {
+ return null;
+ }
+
+ function trackClick() {
+ trackEvent("sponsor_click", {
+ placement,
+ sponsor_name: sponsor.name,
+ sponsor_url: sponsor.url,
+ });
+ }
+
+ return (
+
+ );
+}
diff --git a/deploy/k8s/deployment.yaml b/deploy/k8s/deployment.yaml
index e6d05b5..53b7939 100644
--- a/deploy/k8s/deployment.yaml
+++ b/deploy/k8s/deployment.yaml
@@ -40,6 +40,42 @@ spec:
name: hidden11-feedback
key: N8N_FEEDBACK_WEBHOOK_URL
optional: true
+ - name: NEXT_PUBLIC_SPONSOR_ENABLED
+ valueFrom:
+ secretKeyRef:
+ name: hidden11-sponsor
+ key: NEXT_PUBLIC_SPONSOR_ENABLED
+ optional: true
+ - name: NEXT_PUBLIC_SPONSOR_NAME
+ valueFrom:
+ secretKeyRef:
+ name: hidden11-sponsor
+ key: NEXT_PUBLIC_SPONSOR_NAME
+ optional: true
+ - name: NEXT_PUBLIC_SPONSOR_TAGLINE
+ valueFrom:
+ secretKeyRef:
+ name: hidden11-sponsor
+ key: NEXT_PUBLIC_SPONSOR_TAGLINE
+ optional: true
+ - name: NEXT_PUBLIC_SPONSOR_CTA
+ valueFrom:
+ secretKeyRef:
+ name: hidden11-sponsor
+ key: NEXT_PUBLIC_SPONSOR_CTA
+ optional: true
+ - name: NEXT_PUBLIC_SPONSOR_URL
+ valueFrom:
+ secretKeyRef:
+ name: hidden11-sponsor
+ key: NEXT_PUBLIC_SPONSOR_URL
+ optional: true
+ - name: NEXT_PUBLIC_SPONSOR_LABEL
+ valueFrom:
+ secretKeyRef:
+ name: hidden11-sponsor
+ key: NEXT_PUBLIC_SPONSOR_LABEL
+ optional: true
ports:
- name: http
containerPort: 3000
diff --git a/lib/i18n.ts b/lib/i18n.ts
index 7886efc..03d9db0 100644
--- a/lib/i18n.ts
+++ b/lib/i18n.ts
@@ -636,9 +636,324 @@ export function getDictionary(locale: Locale): Dictionary {
return {
...fallback,
...dictionary,
- clue: {
+ clue: createClueDictionary(locale, {
...fallback.clue,
...dictionary.clue,
- },
+ }),
};
}
+
+function createClueDictionary(locale: Locale, overrides: Record): Record {
+ return new Proxy(overrides, {
+ get(target, property) {
+ if (typeof property !== "string") {
+ return undefined;
+ }
+
+ return target[property] ?? translateGeneratedClue(property, locale);
+ },
+ });
+}
+
+function translateGeneratedClue(clue: string, locale: Locale): string {
+ if (locale === "en") {
+ return clue;
+ }
+
+ const sharedClub = clue.match(/^(\d+|Two) players share the same club\.$/);
+ if (sharedClub) {
+ const count = sharedClub[1] === "Two" ? 2 : Number(sharedClub[1]);
+ return tSharedClub(locale, count);
+ }
+
+ const leagueCount = clue.match(/^(\d+) players? play in (.+)\.$/);
+ if (leagueCount) {
+ return tLeagueCount(locale, Number(leagueCount[1]), leagueCount[2]);
+ }
+
+ const differentClubs = clue.match(/^All five players come from different clubs\.$/);
+ if (differentClubs) {
+ return tAllDifferentClubs(locale);
+ }
+
+ const moreChampions = clue.match(/^The (.+) has more Champions League titles than the (.+)\.$/);
+ if (moreChampions) {
+ return tMoreChampions(locale, moreChampions[1], moreChampions[2]);
+ }
+
+ const sameChampions = clue.match(/^The (.+) and (.+) have won the same number of Champions League titles\.$/);
+ if (sameChampions) {
+ return tSameChampions(locale, sameChampions[1], sameChampions[2]);
+ }
+
+ const wonLeagueTitles = clue.match(/^The (.+) has won (\d+) league titles\.$/);
+ if (wonLeagueTitles) {
+ return tWonLeagueTitles(locale, wonLeagueTitles[1], Number(wonLeagueTitles[2]));
+ }
+
+ const wonCountries = clue.match(/^The (.+) has won league titles in (.+)\.$/);
+ if (wonCountries) {
+ return tWonCountries(locale, wonCountries[1], wonCountries[2]);
+ }
+
+ const bothTopFlight = clue.match(/^The (.+) and (.+) both have played in top-flight football\.$/);
+ if (bothTopFlight) {
+ return tBothTopFlight(locale, bothTopFlight[1], bothTopFlight[2]);
+ }
+
+ const teammates = clue.match(/^The (.+) and (.+) were once teammates at (.+)\.$/);
+ if (teammates) {
+ return tTeammates(locale, teammates[1], teammates[2], teammates[3]);
+ }
+
+ const sameLeagueTitles = clue.match(/^The (.+) and (.+) have won the same number of league titles\.$/);
+ if (sameLeagueTitles) {
+ return tSameLeagueTitles(locale, sameLeagueTitles[1], sameLeagueTitles[2]);
+ }
+
+ const moreLeagueTitles = clue.match(/^The (.+) has more league titles than the (.+)\.$/);
+ if (moreLeagueTitles) {
+ return tMoreLeagueTitles(locale, moreLeagueTitles[1], moreLeagueTitles[2]);
+ }
+
+ const nationality = clue.match(/^The (.+) is from (.+)\.$/);
+ if (nationality) {
+ return tNationality(locale, nationality[1], nationality[2]);
+ }
+
+ return clue;
+}
+
+const positionTranslations: Record> = {
+ en: { defender: "defender", goalkeeper: "goalkeeper", midfielder: "midfielder", striker: "striker", winger: "winger" },
+ es: { defender: "defensa", goalkeeper: "portero", midfielder: "centrocampista", striker: "delantero", winger: "extremo" },
+ fr: { defender: "défenseur", goalkeeper: "gardien", midfielder: "milieu", striker: "attaquant", winger: "ailier" },
+ de: { defender: "Verteidiger", goalkeeper: "Torwart", midfielder: "Mittelfeldspieler", striker: "Stürmer", winger: "Flügelspieler" },
+ it: { defender: "difensore", goalkeeper: "portiere", midfielder: "centrocampista", striker: "attaccante", winger: "ala" },
+ pt: { defender: "defesa", goalkeeper: "guarda-redes", midfielder: "médio", striker: "avançado", winger: "extremo" },
+};
+
+const countryTranslations: Record> = {
+ en: {},
+ es: {
+ Argentina: "Argentina",
+ Austria: "Austria",
+ Belgium: "Bélgica",
+ Brazil: "Brasil",
+ Canada: "Canadá",
+ Croatia: "Croacia",
+ England: "Inglaterra",
+ France: "Francia",
+ Georgia: "Georgia",
+ Germany: "Alemania",
+ Italy: "Italia",
+ Netherlands: "Países Bajos",
+ Norway: "Noruega",
+ Poland: "Polonia",
+ Portugal: "Portugal",
+ Scotland: "Escocia",
+ Spain: "España",
+ Sweden: "Suecia",
+ Switzerland: "Suiza",
+ Ukraine: "Ucrania",
+ },
+ fr: {
+ Argentina: "Argentine",
+ Austria: "Autriche",
+ Belgium: "Belgique",
+ Brazil: "Brésil",
+ Canada: "Canada",
+ Croatia: "Croatie",
+ England: "Angleterre",
+ France: "France",
+ Georgia: "Géorgie",
+ Germany: "Allemagne",
+ Italy: "Italie",
+ Netherlands: "Pays-Bas",
+ Norway: "Norvège",
+ Poland: "Pologne",
+ Portugal: "Portugal",
+ Scotland: "Écosse",
+ Spain: "Espagne",
+ Sweden: "Suède",
+ Switzerland: "Suisse",
+ Ukraine: "Ukraine",
+ },
+ de: {
+ Argentina: "Argentinien",
+ Austria: "Österreich",
+ Belgium: "Belgien",
+ Brazil: "Brasilien",
+ Canada: "Kanada",
+ Croatia: "Kroatien",
+ England: "England",
+ France: "Frankreich",
+ Georgia: "Georgien",
+ Germany: "Deutschland",
+ Italy: "Italien",
+ Netherlands: "Niederlande",
+ Norway: "Norwegen",
+ Poland: "Polen",
+ Portugal: "Portugal",
+ Scotland: "Schottland",
+ Spain: "Spanien",
+ Sweden: "Schweden",
+ Switzerland: "Schweiz",
+ Ukraine: "Ukraine",
+ },
+ it: {
+ Argentina: "Argentina",
+ Austria: "Austria",
+ Belgium: "Belgio",
+ Brazil: "Brasile",
+ Canada: "Canada",
+ Croatia: "Croazia",
+ England: "Inghilterra",
+ France: "Francia",
+ Georgia: "Georgia",
+ Germany: "Germania",
+ Italy: "Italia",
+ Netherlands: "Paesi Bassi",
+ Norway: "Norvegia",
+ Poland: "Polonia",
+ Portugal: "Portogallo",
+ Scotland: "Scozia",
+ Spain: "Spagna",
+ Sweden: "Svezia",
+ Switzerland: "Svizzera",
+ Ukraine: "Ucraina",
+ },
+ pt: {
+ Argentina: "Argentina",
+ Austria: "Áustria",
+ Belgium: "Bélgica",
+ Brazil: "Brasil",
+ Canada: "Canadá",
+ Croatia: "Croácia",
+ England: "Inglaterra",
+ France: "França",
+ Georgia: "Geórgia",
+ Germany: "Alemanha",
+ Italy: "Itália",
+ Netherlands: "Países Baixos",
+ Norway: "Noruega",
+ Poland: "Polónia",
+ Portugal: "Portugal",
+ Scotland: "Escócia",
+ Spain: "Espanha",
+ Sweden: "Suécia",
+ Switzerland: "Suíça",
+ Ukraine: "Ucrânia",
+ },
+};
+
+function pos(locale: Locale, position: string): string {
+ return positionTranslations[locale][position] ?? position;
+}
+
+function countries(locale: Locale, value: string): string {
+ return value
+ .split(" and ")
+ .map((country) => countryTranslations[locale][country] ?? country)
+ .join(tAnd(locale));
+}
+
+function tAnd(locale: Locale): string {
+ return locale === "de" ? " und " : locale === "it" ? " e " : locale === "pt" ? " e " : locale === "fr" ? " et " : " y ";
+}
+
+function tSharedClub(locale: Locale, count: number): string {
+ if (locale === "es") return count === 2 ? "Dos jugadores comparten club." : `${count} jugadores comparten club.`;
+ if (locale === "fr") return count === 2 ? "Deux joueurs partagent le même club." : `${count} joueurs partagent le même club.`;
+ if (locale === "de") return count === 2 ? "Zwei Spieler haben denselben Klub." : `${count} Spieler haben denselben Klub.`;
+ if (locale === "it") return count === 2 ? "Due giocatori condividono lo stesso club." : `${count} giocatori condividono lo stesso club.`;
+ return count === 2 ? "Dois jogadores partilham o mesmo clube." : `${count} jogadores partilham o mesmo clube.`;
+}
+
+function tLeagueCount(locale: Locale, count: number, league: string): string {
+ if (locale === "es") return `${count} ${count === 1 ? "jugador juega" : "jugadores juegan"} en ${league}.`;
+ if (locale === "fr") return `${count} ${count === 1 ? "joueur évolue" : "joueurs évoluent"} en ${league}.`;
+ if (locale === "de") return `${count} ${count === 1 ? "Spieler spielt" : "Spieler spielen"} in der ${league}.`;
+ if (locale === "it") return `${count} ${count === 1 ? "giocatore gioca" : "giocatori giocano"} in ${league}.`;
+ return `${count} ${count === 1 ? "jogador joga" : "jogadores jogam"} na ${league}.`;
+}
+
+function tAllDifferentClubs(locale: Locale): string {
+ if (locale === "es") return "Los cinco jugadores son de clubes diferentes.";
+ if (locale === "fr") return "Les cinq joueurs viennent de clubs différents.";
+ if (locale === "de") return "Alle fünf Spieler kommen aus unterschiedlichen Klubs.";
+ if (locale === "it") return "Tutti e cinque i giocatori vengono da club diversi.";
+ return "Os cinco jogadores vêm de clubes diferentes.";
+}
+
+function tMoreChampions(locale: Locale, left: string, right: string): string {
+ if (locale === "es") return `El ${pos(locale, left)} tiene más Champions League que el ${pos(locale, right)}.`;
+ if (locale === "fr") return `Le ${pos(locale, left)} a plus de Ligues des champions que le ${pos(locale, right)}.`;
+ if (locale === "de") return `Der ${pos(locale, left)} hat mehr Champions-League-Titel als der ${pos(locale, right)}.`;
+ if (locale === "it") return `Il ${pos(locale, left)} ha più Champions League del ${pos(locale, right)}.`;
+ return `O ${pos(locale, left)} tem mais Champions League do que o ${pos(locale, right)}.`;
+}
+
+function tSameChampions(locale: Locale, left: string, right: string): string {
+ if (locale === "es") return `El ${pos(locale, left)} y el ${pos(locale, right)} han ganado el mismo número de Champions League.`;
+ if (locale === "fr") return `Le ${pos(locale, left)} et le ${pos(locale, right)} ont gagné le même nombre de Ligues des champions.`;
+ if (locale === "de") return `Der ${pos(locale, left)} und der ${pos(locale, right)} haben gleich viele Champions-League-Titel gewonnen.`;
+ if (locale === "it") return `Il ${pos(locale, left)} e il ${pos(locale, right)} hanno vinto lo stesso numero di Champions League.`;
+ return `O ${pos(locale, left)} e o ${pos(locale, right)} ganharam o mesmo número de Champions League.`;
+}
+
+function tWonLeagueTitles(locale: Locale, position: string, count: number): string {
+ if (locale === "es") return `El ${pos(locale, position)} ha ganado ${count} ${count === 1 ? "liga" : "ligas"}.`;
+ if (locale === "fr") return `Le ${pos(locale, position)} a gagné ${count} ${count === 1 ? "championnat" : "championnats"}.`;
+ if (locale === "de") return `Der ${pos(locale, position)} hat ${count} Ligatitel gewonnen.`;
+ if (locale === "it") return `Il ${pos(locale, position)} ha vinto ${count} ${count === 1 ? "campionato" : "campionati"}.`;
+ return `O ${pos(locale, position)} ganhou ${count} ${count === 1 ? "campeonato" : "campeonatos"}.`;
+}
+
+function tWonCountries(locale: Locale, position: string, value: string): string {
+ if (locale === "es") return `El ${pos(locale, position)} ha ganado ligas en ${countries(locale, value)}.`;
+ if (locale === "fr") return `Le ${pos(locale, position)} a gagné des championnats en ${countries(locale, value)}.`;
+ if (locale === "de") return `Der ${pos(locale, position)} hat Ligatitel in ${countries(locale, value)} gewonnen.`;
+ if (locale === "it") return `Il ${pos(locale, position)} ha vinto campionati in ${countries(locale, value)}.`;
+ return `O ${pos(locale, position)} ganhou campeonatos em ${countries(locale, value)}.`;
+}
+
+function tBothTopFlight(locale: Locale, left: string, right: string): string {
+ if (locale === "es") return `El ${pos(locale, left)} y el ${pos(locale, right)} han jugado en primera división.`;
+ if (locale === "fr") return `Le ${pos(locale, left)} et le ${pos(locale, right)} ont joué en première division.`;
+ if (locale === "de") return `Der ${pos(locale, left)} und der ${pos(locale, right)} haben erstklassig gespielt.`;
+ if (locale === "it") return `Il ${pos(locale, left)} e il ${pos(locale, right)} hanno giocato in massima serie.`;
+ return `O ${pos(locale, left)} e o ${pos(locale, right)} jogaram na primeira divisão.`;
+}
+
+function tTeammates(locale: Locale, left: string, right: string, club: string): string {
+ if (locale === "es") return `El ${pos(locale, left)} y el ${pos(locale, right)} fueron compañeros en ${club}.`;
+ if (locale === "fr") return `Le ${pos(locale, left)} et le ${pos(locale, right)} ont été coéquipiers à ${club}.`;
+ if (locale === "de") return `Der ${pos(locale, left)} und der ${pos(locale, right)} waren einst Teamkollegen bei ${club}.`;
+ if (locale === "it") return `Il ${pos(locale, left)} e il ${pos(locale, right)} sono stati compagni al ${club}.`;
+ return `O ${pos(locale, left)} e o ${pos(locale, right)} foram colegas no ${club}.`;
+}
+
+function tSameLeagueTitles(locale: Locale, left: string, right: string): string {
+ if (locale === "es") return `El ${pos(locale, left)} y el ${pos(locale, right)} han ganado el mismo número de ligas.`;
+ if (locale === "fr") return `Le ${pos(locale, left)} et le ${pos(locale, right)} ont gagné le même nombre de championnats.`;
+ if (locale === "de") return `Der ${pos(locale, left)} und der ${pos(locale, right)} haben gleich viele Ligatitel gewonnen.`;
+ if (locale === "it") return `Il ${pos(locale, left)} e il ${pos(locale, right)} hanno vinto lo stesso numero di campionati.`;
+ return `O ${pos(locale, left)} e o ${pos(locale, right)} ganharam o mesmo número de campeonatos.`;
+}
+
+function tMoreLeagueTitles(locale: Locale, left: string, right: string): string {
+ if (locale === "es") return `El ${pos(locale, left)} tiene más ligas que el ${pos(locale, right)}.`;
+ if (locale === "fr") return `Le ${pos(locale, left)} a plus de championnats que le ${pos(locale, right)}.`;
+ if (locale === "de") return `Der ${pos(locale, left)} hat mehr Ligatitel als der ${pos(locale, right)}.`;
+ if (locale === "it") return `Il ${pos(locale, left)} ha più campionati del ${pos(locale, right)}.`;
+ return `O ${pos(locale, left)} tem mais campeonatos do que o ${pos(locale, right)}.`;
+}
+
+function tNationality(locale: Locale, position: string, country: string): string {
+ if (locale === "es") return `El ${pos(locale, position)} es de ${countries(locale, country)}.`;
+ if (locale === "fr") return `Le ${pos(locale, position)} vient de ${countries(locale, country)}.`;
+ if (locale === "de") return `Der ${pos(locale, position)} kommt aus ${countries(locale, country)}.`;
+ if (locale === "it") return `Il ${pos(locale, position)} viene da ${countries(locale, country)}.`;
+ return `O ${pos(locale, position)} é de ${countries(locale, country)}.`;
+}
diff --git a/lib/sponsor.ts b/lib/sponsor.ts
new file mode 100644
index 0000000..1741aae
--- /dev/null
+++ b/lib/sponsor.ts
@@ -0,0 +1,28 @@
+export type SponsorConfig = {
+ cta: string;
+ enabled: boolean;
+ label: string;
+ name: string;
+ tagline: string;
+ url: string;
+};
+
+export function getSponsorConfig(): SponsorConfig {
+ const name = process.env.NEXT_PUBLIC_SPONSOR_NAME?.trim() || "Sponsor hidden11";
+ const url = process.env.NEXT_PUBLIC_SPONSOR_URL?.trim() || "https://hidden11.app/sponsor";
+ const tagline =
+ process.env.NEXT_PUBLIC_SPONSOR_TAGLINE?.trim() ||
+ "Reach football fans inside a daily puzzle they actually finish.";
+ const cta = process.env.NEXT_PUBLIC_SPONSOR_CTA?.trim() || "Become a sponsor";
+ const label = process.env.NEXT_PUBLIC_SPONSOR_LABEL?.trim() || "Today's sponsor";
+ const enabled = process.env.NEXT_PUBLIC_SPONSOR_ENABLED !== "false";
+
+ return {
+ cta,
+ enabled,
+ label,
+ name,
+ tagline,
+ url,
+ };
+}