Archive and answer pages now exist in all six languages and are prerendered instead of rendered per request, and each answer page carries a sentence of real context per player (club, league, nationality, former clubs, honours) plus Article and BreadcrumbList JSON-LD. The sitemap grows from 121 to 691 URLs, every one with hreflang alternates. Adds an optional display name to shared results, so a challenge link reads "<name> challenges you" in the page, the OG image and the head-to-head verdict; the name is sanitised both when written and when parsed back. Adds an opt-in daily reminder (Web Push without payload, so no content encryption) and a PWA install prompt shown once the day is finished, plus first-party event ingestion at /api/events that mirrors GA4 so the funnel survives ad blockers. Wires the missing N8N_PLAYS_WEBHOOK_URL into the deployment along with the new events, push and search-verification secrets, and ships the n8n workflows, the Postgres schema with funnel/retention views, and an IndexNow submitter. Refs #2 #3 #5 #6 #7 #8 #9 #10 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QvQ1ErZNRUcWgCEkJ9uyrn
hidden11
Daily football lineup puzzle built with Next.js, React, TypeScript and Tailwind CSS.
Player data provenance and clue rules are documented in docs/data-sources.md.
Growth features
- Localized home pages:
/(en) plus/es,/fr,/de,/it,/pt— server-rendered intro, how-to-play and FAQ (FAQPage JSON-LD) with hreflang alternates on every home. - Archive, in six languages:
/archiveand/<locale>/archivelist past puzzles;/puzzle/<n>and/<locale>/puzzle/<n>publish answers for past days only (today and future 404). Answer pages carry a sentence of real context per player — club, league, nationality, former clubs and honours — plus Article and BreadcrumbList JSON-LD, and are prerendered (generateStaticParams+ hourly ISR) rather than rendered per request./play/<n>replays a past puzzle without touching stats or streak. - Challenge loop: shared result links (
/r/<code>) offer "Accept the challenge" →/?challenge=<code>. If the challenge matches today's puzzle, the game shows the rival's score and a head-to-head verdict on the result screen. - Compact share text: one line per difficulty with the final attempt grid — built in
lib/share.ts. - PWA:
app/manifest.ts+ icons inpublic/(regenerate withnode scripts/generate-icons.mjs). - Named challenges: the share sheet takes an optional display name that travels inside the
share code, so
/r/<code>reads " challenges you" in the page, the OG image and the head-to-head verdict. The name is sanitised on the way out and on the way in. - Daily reminder: an opt-in Web Push subscription plus a PWA install prompt, offered once the
day is finished. The push is sent without a payload (VAPID header only, no content encryption);
the notification copy lives in the service worker's Cache Storage (
public/sw.js). - First-party funnel: every game event is mirrored to
POST /api/eventsalongside GA4, so the funnel survives ad blockers. Schema and the funnel/retention/share-channel views are indeploy/sql/hidden11-events.sql. - Daily teaser:
GET /api/teaserreturns a spoiler-free localized teaser for today's puzzle (first Easy clue).deploy/n8n-daily-teaser.jsonis an importable n8n workflow that posts it daily through the Postiz public API (setPOSTIZ_API_KEYin n8n and fill in the integration ids inside the Code node). - IndexNow:
node scripts/submit-indexnow.mjs [--today]submits the sitemap URLs to Bing, Yandex and Seznam. The key file lives inpublic/<key>.txt; the key is inpass show hidden11/indexnow-key. Google has no ping endpoint any more — its sitemap is submitted once from Search Console.
Local development
npm install
npm run dev
Open http://localhost:3000.
Optional analytics env vars:
NEXT_PUBLIC_GA_MEASUREMENT_ID: GA4 measurement ID, e.g.G-XXXXXXXXXXNEXT_PUBLIC_CLARITY_PROJECT_ID: Microsoft Clarity project ID
Optional feedback env vars:
N8N_FEEDBACK_WEBHOOK_URL: n8n webhook URL used to receive feedback submissions
Optional funnel-analytics env vars:
N8N_EVENTS_WEBHOOK_URL: n8n webhook backingPOST /api/events(workflow:deploy/n8n-events.json, schema:deploy/sql/hidden11-events.sql). When unset the endpoint accepts and discards, so the game is unaffected.
Optional daily-reminder env vars:
VAPID_PUBLIC_KEY: application server key served byGET /api/push(pass show hidden11/vapid-public-key)N8N_PUSH_WEBHOOK_URL: n8n webhook that stores push subscriptions (workflow:deploy/n8n-push-subscriptions.json). Sending isdeploy/n8n-daily-push.json; the VAPID private key never leaves n8n (pass show hidden11/vapid-private-key).
Optional search-verification env vars:
GOOGLE_SITE_VERIFICATION: token for thegoogle-site-verificationmeta tag. hidden11.app is already verified by DNS TXT, so this is only needed for a second property.BING_SITE_VERIFICATION: token for themsvalidate.01meta tag
Optional social-proof counter env vars:
N8N_PLAYS_WEBHOOK_URL: n8n webhook URL backing the daily play counter shown on the result screen ("N people played today"). The webhook receives{ action: "get" | "increment", date: "YYYY-MM-DD" }and must respond with{ count: number }. When unset, the counter line is simply hidden — no number is ever fabricated.
Optional sponsor env vars:
NEXT_PUBLIC_SPONSOR_ENABLED: set tofalseto hide the sponsor slotNEXT_PUBLIC_SPONSOR_NAME: sponsor name shown in the slotNEXT_PUBLIC_SPONSOR_TAGLINE: short sponsor copyNEXT_PUBLIC_SPONSOR_CTA: sponsor button textNEXT_PUBLIC_SPONSOR_URL: outbound sponsor URLNEXT_PUBLIC_SPONSOR_LABEL: small label above the sponsor name
Production build
npm run build
npm start
Docker
docker build -t hidden11:latest .
docker run --rm -p 3000:3000 hidden11:latest
CI/CD
Gitea Actions workflow:
.gitea/workflows/build-and-push.yaml- Builds on
main - Pushes:
gitea.alexandre-vazquez.cloud/alexandrev/hidden11:<git-sha>gitea.alexandre-vazquez.cloud/alexandrev/hidden11:latest
Required Gitea repository secrets:
REGISTRY_USERNAME:alexandrevREGISTRY_TOKEN: token with package/container registry permissions
Create/read tokens from pass; never commit credentials.
Kubernetes
kubectl apply -f deploy/k8s
The app is configured for:
- Namespace:
hidden11 - Ingress host:
hidden11.app - Ingress class:
public - TLS secret:
hidden11-tls
To enable analytics in Kubernetes, create a secret named hidden11-analytics in the hidden11 namespace with:
NEXT_PUBLIC_GA_MEASUREMENT_IDNEXT_PUBLIC_CLARITY_PROJECT_ID
To enable feedback submission, create a secret named hidden11-feedback in the hidden11 namespace with:
N8N_FEEDBACK_WEBHOOK_URL
For the social-proof play counter, create hidden11-plays with:
N8N_PLAYS_WEBHOOK_URL
For the funnel pipeline, create hidden11-events with:
N8N_EVENTS_WEBHOOK_URL
For the daily reminder, create hidden11-push with:
VAPID_PUBLIC_KEYN8N_PUSH_WEBHOOK_URL
For search-engine verification meta tags, create hidden11-seo with:
GOOGLE_SITE_VERIFICATIONBING_SITE_VERIFICATION
To configure the sponsor slot in Kubernetes, create a secret named hidden11-sponsor in the hidden11 namespace with any of:
NEXT_PUBLIC_SPONSOR_ENABLEDNEXT_PUBLIC_SPONSOR_NAMENEXT_PUBLIC_SPONSOR_TAGLINENEXT_PUBLIC_SPONSOR_CTANEXT_PUBLIC_SPONSOR_URLNEXT_PUBLIC_SPONSOR_LABEL
ArgoCD
kubectl apply -f deploy/argocd/application.yaml
The ArgoCD Application includes ArgoCD Image Updater annotations for:
- image:
gitea.alexandre-vazquez.cloud/alexandrev/hidden11 - strategy:
digest - write-back:
argocd