The teaser workflow now takes its Postiz routing and API key from a config node instead of hard-coded placeholders inside the Code node, so activating it is a matter of filling two fields rather than editing JavaScript. scripts/setup-metabase-dashboard.mjs registers the hidden11 database in Metabase and builds the funnel dashboard from the SQL views. It is idempotent, so it can be re-run after adding a view. Refs #4 #9 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. Fill in the "Postiz config" node:routingmaps a locale to the integration ids that should post it, e.g.{"en":["<x-id>"],"es":["<bluesky-id>"]}(list ids withGET {POSTIZ_URL}/api/public/v1/integrations), andpostizApiKeycomes frompass show homelab/postiz-api-key. - Funnel dashboard:
node scripts/setup-metabase-dashboard.mjsregisters the hidden11 Postgres database in Metabase and builds the funnel dashboard (funnel by day, completion and share rate, D1 retention, share channels, reminder opt-in). Re-running is safe. - 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