claude-code 0af6a01b69
Build and push image / build (push) Successful in 7m50s
Add Metabase dashboard setup and rework the teaser workflow
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
2026-08-26 13:07:07 +00:00
2026-08-26 13:04:49 +00:00
2026-08-26 13:04:49 +00:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00
2026-05-02 20:50:36 +02:00

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: /archive and /<locale>/archive list 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 in public/ (regenerate with node 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/events alongside GA4, so the funnel survives ad blockers. Schema and the funnel/retention/share-channel views are in deploy/sql/hidden11-events.sql.
  • Daily teaser: GET /api/teaser returns a spoiler-free localized teaser for today's puzzle (first Easy clue). deploy/n8n-daily-teaser.json is an importable n8n workflow that posts it daily through the Postiz public API. Fill in the "Postiz config" node: routing maps a locale to the integration ids that should post it, e.g. {"en":["<x-id>"],"es":["<bluesky-id>"]} (list ids with GET {POSTIZ_URL}/api/public/v1/integrations), and postizApiKey comes from pass show homelab/postiz-api-key.
  • Funnel dashboard: node scripts/setup-metabase-dashboard.mjs registers 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 in public/<key>.txt; the key is in pass 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-XXXXXXXXXX
  • NEXT_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 backing POST /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 by GET /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 is deploy/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 the google-site-verification meta tag. hidden11.app is already verified by DNS TXT, so this is only needed for a second property.
  • BING_SITE_VERIFICATION: token for the msvalidate.01 meta 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 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

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: alexandrev
  • REGISTRY_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_ID
  • NEXT_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_KEY
  • N8N_PUSH_WEBHOOK_URL

For search-engine verification meta tags, create hidden11-seo with:

  • GOOGLE_SITE_VERIFICATION
  • BING_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_ENABLED
  • NEXT_PUBLIC_SPONSOR_NAME
  • NEXT_PUBLIC_SPONSOR_TAGLINE
  • NEXT_PUBLIC_SPONSOR_CTA
  • NEXT_PUBLIC_SPONSOR_URL
  • NEXT_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
S
Description
No description provided
Readme 1 MiB
Languages
TypeScript 87.9%
JavaScript 11.8%
Dockerfile 0.2%
CSS 0.1%