DEV Community

FlameAI Studio
FlameAI Studio

Posted on

How I Engineered a Noir-Themed Puzzle Platform with 365 Days of Unique Content

Building OnlinePuzzle.net taught me that lightweight web apps can still require heavyweight engineering — especially when content, world-building, and user experience all need to align.

When I started building OnlinePuzzle.net, my goal wasn’t just to make another puzzle game.
I wanted to create a fully engineered detective world — one that feels like stepping into a 1940s case file, but runs smoothly in the browser, loads instantly, and requires zero onboarding.

To achieve that, I had to design:

  • four independent puzzle engines,
  • a 365-day content system with zero repetition,
  • a verifiable data pipeline,
  • a consistent Noir user experience,
  • and a lightweight architecture that doesn’t depend on a backend.

This post breaks down the engineering behind it.


01 — Why the Architecture Matters More Than the Gameplay

Each puzzle type is simple on the surface:

  1. Daily 5 — Wordle-style deduction
  2. Scramble — an anagram solver
  3. Word Search — themed 8-word grids
  4. Memory Clues — matching pairs of story fragments

But the vision required:

  • 365 unique daily cases
  • 2700+ handcrafted clues and words
  • a world that feels internally consistent
  • no accidental reuse or cross-leaks between puzzle types

This meant content engineering was the real challenge — not JavaScript or UI.

So the architecture had to treat the content as code, not as loose text files.


02 — TypeScript as the Content Backbone

Instead of storing text in JSON, Google Sheets, or Markdown, I structured every piece of game content as typed objects:

export interface DailyClue {
  word: string
  hintPrimary: string
  hintSecondary: string
}

export interface WordSearchPack {
  id: number
  theme: string
  words: string[]
}

export interface MemoryPair {
  clueA: string
  clueB: string
  category: 'evidence' | 'person' | 'location' | 'action'
}


Enter fullscreen mode Exit fullscreen mode

Why?

  • Compile-time validation
  • Script-based consistency checks
  • Zero runtime surprises
  • Easy future expansion
    Most importantly, I could write automation scripts that verified:

  • no duplicates across all 2700 entries

  • no hidden collisions (e.g., similar stems)

  • all words conform to Noir style guidelines

  • themes and categories remain consistent

Content became first-class engineering, not decoration.


03 — Four Puzzle Engines, One Reusable Framework

The gameplay engines are fully decoupled from the UI and content.

Each engine shares:

  • a unified interface
  • a timing & streak module
  • animation hooks
  • error handling
  • accessibility helpers

Example: The Daily 5 checker is a pure function:

export function evaluateGuess(guess: string, answer: string) {
  return guess.split('').map((char, i) => {
    if (char === answer[i]) return 'correct'
    if (answer.includes(char)) return 'present'
    return 'absent'
  })
}
Enter fullscreen mode Exit fullscreen mode

The same purity principle applies to:

  • Scramble shuffling
  • Word Search grid generation
  • Memory Clues pairing logic The engines remain small, testable, and replaceable.

04 — The Noir UX Layer: Lightweight but Consistent

To create an aesthetic without heavy assets, I relied on:

  • Tailwind CSS for the typography and paper-like textures
  • Framer Motion for stamps, card flips, and scene transitions
  • Web Audio API for typewriter clicks and ambient noise
  • component-level variants for detective-style cards, folders, and case files

A typical Noir card component looks like:

<Card variant="case-file">
  <Stamp type="classified" />
  <p className="typewriter">{text}</p>
</Card>
Enter fullscreen mode Exit fullscreen mode

Everything is stylized through composition, not images.

This keeps the app:

  • fast
  • responsive
  • PWA-friendly
  • easy to theme

The Noir experience is produced through design systems, not heavy graphics.


05 — PWA + Offline = A Lightweight “Daily Habit” App

The entire platform is offline-capable via Service Workers:

  • assets cached
  • gameplay logic self-contained
  • user progress stored in localStorage

This enhances:

  • load speed
  • daily retention
  • global access (important for puzzle players worldwide)

A puzzle game shouldn’t require a backend to function, so it doesn’t.


06 — The Detective Profile System (Without a Backend)

User data — streaks, XP, achievements — is stored locally:

interface DetectiveProfile {
  streak: number
  bestStreak: number
  xp: number
  achievements: string[]
}
Enter fullscreen mode Exit fullscreen mode

Why not sync to a server?

  • privacy
  • instant writes
  • offline mode
  • reduced complexity
  • no login friction

This aligns with the philosophy:
maximum immersion, minimum friction.


07 — Lessons Learned
✔ 1. Content requires the same rigor as backend code.

Unstructured text becomes a liability at scale.

✔ 2. Aesthetic consistency is a system, not a skin.

Noir experience = typography + motion + sound + narrative tone.

✔ 3. Puzzle engines must be pure functions.

It guarantees testability and portability.

✔ 4. PWAs shine when the app is revisited daily.

Daily puzzles + offline capability is a perfect match.

✔ 5. Lightweight tools can still deliver deep experiences.

People don’t need AAA graphics —
they need cohesion, polish, and emotional texture.


08 — Try the Platform

(You can add the link yourself on dev.to — the platform encourages non-spam content.)

If you enjoy:

  • designing narrative-driven systems,
  • building lightweight Web apps,
  • or engineering large structured content sets,

then OnlinePuzzle.net might give you some inspiration.

Top comments (0)