PROFILL

Play Now

PROFILL is a daily browser-based word-guessing game inspired by the Brazilian board game Perfil and the genre of daily puzzle games popularised by Wordle, Contexto, and Minute Cryptic. The premise: each day, a Wikipedia article is secretly chosen. You're given 9 numbered clue slots. Reveal one, make a guess. Reveal another, guess again. The fewer clues you need, the better your score.

The name is a deliberate triple pun: Perfil (the original game), Profil (German for "profile"), and PRO + FILL — you're filling in the answer like a pro.

The story behind it

I'm a Brazilian living in Munich. After three years in Germany, I still struggle to make language learning feel like something I actually want to do every day rather than something I should do. I play daily puzzle games obsessively — Wordle, Connections, Minute Cryptic, Contexto — and I kept thinking: why isn't there something like this that actually helps me engage with German?

PROFILL started as an answer to that question. The proof of concept is in English, but the architecture is designed for language learning from day one — with German and Portuguese already live, and translation helpers and vocabulary features on the roadmap. The real goal is a game where engaging with a foreign language feels like playing, not studying.

Inspiration

The original Perfil is a beloved Brazilian tabletop trivia game where one player reads progressively more obvious clues about a person, place, or thing, and the others try to guess it as early as possible. PROFILL brings that mechanic online as a daily shared puzzle — the same article for all players worldwide on a given day, with a shareable emoji result like Wordle.

The forced-guess mechanic — where you must attempt an answer after each clue before unlocking the next — is directly inspired by how Minute Cryptic raises the stakes of every hint. The gradual revelation of meaning through fragments owes something to Contexto's slow convergence toward an answer. And the daily ritual, the streak, the emoji share: all Wordle.

How the game works

Each day a Wikipedia article is selected from one of three rotating categories based on the day of the week:

  • Monday / Thursday — Places

  • Tuesday / Friday / Sunday — People (real, historical, or fictional)

  • Wednesday / Saturday — Things

Nine clues (sentences) are extracted from the article's body — never from the intro paragraph, which is too revealing. The clues are shuffled with a date-seeded algorithm so every player sees the same 9 clues, but in their own unique random order. Opening a clue slot feels like pulling a card from a booster pack — you don't know what you'll get.

The game loop:

  1. Tap any numbered slot to reveal a clue.

  2. The clue flies into a focus overlay with a typewriter animation.

  3. Make a guess — or skip (though skipping is discouraged).

  4. The score is simply the clue number you solved on. Lower is better. A score of 1 means you guessed correctly on the very first clue.

The golden clue: If you reveal all 9 regular clues without guessing correctly, a 10th special clue (the ✨ golden clue) is automatically revealed — extracted from the article's lead paragraph, which is more direct. Getting it on the golden clue scores a 10.

Sharing: After winning, you can copy an emoji summary:

PROFILL 📖 Jun 5, 2026 ⬜🟩⬜🟩⬜⬜⬜⬜⬜ Solved on clue 2 🏆

Tech stack

LayerTechnologyFrontendReact 18 + ViteRoutingReact Router v7BackendNode.js + ExpressDatabaseSQLite via better-sqlite3Article sourceWikipedia REST API + Wikimedia Pageviews APIHTML parsingnode-html-parserState persistencelocalStorage (no accounts)HostingRender (API + frontend)

Technical highlights

Clue extraction pipeline

For each article, two Wikipedia API calls run in parallel — one for the summary (title, thumbnail, URL) and one for the full article HTML. The HTML is parsed to:

  1. Skip the lead/intro section (before the first <h2>) — too identifying.

  2. Skip blocklisted sections: Discography, Filmography, See also, References, etc.

  3. Extract only <p> tags from the remaining sections.

  4. Strip citation markers ([1], [note 3]).

  5. Split into individual sentences; filter by length (8–500 chars).

Date-seeded shuffle

The sentence pool is shuffled using the mulberry32 PRNG seeded with the puzzle date ("2026-06-05" → integer 20260605). This guarantees that all players always get the same 9 sentences in the same pool, regardless of when the server re-extracts the article (e.g. after a cache wipe). Same date → same seed → same 9 clues.

Clue redaction

Before display, clues are redacted: the article subject's name and all common variants are replaced with _____. Variants generated include the full slug, comma-split parts, individual tokens (minus stop words), adjacent token pairs, and parenthetical disambiguation stripped. Redaction operates on text values only — never on link href attributes, avoiding accidental URL corruption.

Runs format

Clues are stored as arrays of runs — typed text segments — rather than raw HTML strings:

json

[ { "value": "plain text" }, { "value": "italic text", "em": true }, { "value": "link text", "href": "https://en.wikipedia.org/wiki/..." } ]

This lets the frontend render clues with pure React elements — no dangerouslySetInnerHTML, no sanitiser library needed. Rich rendering (bold, italic, clickable Wikipedia links) is already architected; it just needs a UI pass.

Answer never reaches the client

Guess validation happens server-side. The answer (article title) is only sent to the frontend after the game is won — either via a correct guess response or the /api/giveup endpoint. The schedule file (schedule.json) is also never exposed directly; the API only ever returns today's article, never future dates.

ClueFocusOverlay with typewriter

When a clue is revealed, it "flies to the foreground" in a modal overlay. The text types out character-by-character at 20 ms/char using a setInterval driven by a useEffect that re-fires on each new clueN. Clicking the card area skips to the end immediately. Previously-revealed cards can be tapped to re-open the overlay for re-reading (no typewriter on re-open).

Multi-language support

The game runs in three languages — EN, DE, and PT — each with its own daily schedule and article pool. Each language uses native-language Wikipedia slugs (e.g. Schokolade, not Chocolate, for the German game). State is stored per-language in localStorage under separate keys (profill:state:en, etc.), so a player can keep independent streaks across all three.

Architecture decisions

Score formula in one place (utils/scoring.js) — currently just return revealedClueNs.length (clue count). Easy to tune without hunting the codebase.

No user accounts — state lives in localStorage. Streak is maintained via lastPlayedDate; any gap resets it. Simpler to build, ships faster, preserves privacy. Designed to be replaced by server-side profiles later without touching game logic.

lang is first-class everywhere — DB schema, API endpoints, and localStorage keys all carry it from day one. Adding a fourth language is a config change, not a refactor. This wasn't an afterthought — the multilingual angle is core to the project's identity.

Article scheduling is a file, not an algorithmschedule.json maps dates to Wikipedia slugs. An algorithm populates it once a month using the Wikimedia Pageviews API to favour well-known articles; a developer can override any day by editing the file directly. No runtime article selection on every request. Future dates are never exposed by the API, keeping upcoming puzzles spoiler-free for the developer too.

Data access is abstracted — all SQLite calls go through a thin data layer, so swapping to Postgres later wouldn't touch business logic.

Current status

Alpha 0.2 — fully playable, three languages live, content loaded through July 2026.

MilestoneWhat shippedAlpha 0.1Single-language proof of concept: clue extraction, redaction, scoring, emoji shareAlpha 0.1.xGerman 🇩🇪 and Portuguese 🇧🇷 added; language tabs; game state survives page reloadsAlpha 0.2Redesigned game mechanic: 9 clues in a 3×3 grid, forced reveal→guess alternation, golden clue, score by clue count

What's next

LLM-based clue ranking — rank the 9 clues vague → specific using an LLM, so early clues are never immediate giveaways. This is the one place where AI meaningfully improves the editorial quality of the game.

Rich clue rendering — render italic, bold, and clickable Wikipedia links using the existing runs format. The architecture is already there; it just needs the UI pass.

Community threads — automated daily discussion posts to a PROFILL subreddit (via Reddit API) and Discord server (via webhook) at midnight each day. Players get a link after solving to discuss the puzzle without requiring embedded comments or a managed social feature.

User accounts — move state from localStorage to server-side profiles for cross-device streaks and proper leaderboards.

Admin UI — password-protected schedule editor so the puzzle calendar can be curated without touching schedule.json directly.

Language learning helpers — optional translation hints and word definitions that cost score points, designed specifically for the PT and DE audiences. This is the long-term vision: a game where playing is studying.

Links