Capstone Project

Story Safe

Project TypeGroup capstone project
RoleUI/UX, logo, parsing, spoiler logic, CSS
StackReact, Vite, Express, MongoDB, CSS
Animated preview of the Story Safe spoiler-control wiki

Story Safe was my capstone project: a wiki platform built around spoiler control. The idea was that someone should be able to browse a story wiki without accidentally seeing information from chapters, arcs, or episodes they had not reached yet.

This project had a lot going on. I took a large role in the early UI/UX direction and logo work, then moved into front-end implementation, parsing cleaned wiki data into something usable, and building the spoiler-blur behavior that made the main product idea work.

My Role

Where I Worked On The Project

My work sat between product direction, visual design, frontend implementation, and the spoiler-protection logic that made the wiki feel different from a normal fandom site.

  • I helped shape the early visual direction, including the logo and the wiki-style interface.
  • Shhwip worked on the backend side of getting raw wiki data from the wiki database.
  • After that raw HTML existed, I worked on cleaning, parsing, and displaying it in a way the React frontend could use.
  • I worked on CSS across the app, including the spoiler blur behavior and responsive/mobile styling.
  • As time got tight, I wrote a small JavaScript generator that produced deterministic CSS for the number of spoiler levels in a story.

System Pieces

What Had To Come Together

The project was bigger than a static UI. The wiki needed imported content, account-aware spoiler settings, search behavior, article rendering, and CSS that could protect users from future-story information.

Spoiler Level Model

Users set a spoiler level, and article content tagged above that level stays blurred so browsing a wiki does not reveal future story knowledge.

Wiki Import Pipeline

Raw wiki content had to be cleaned and transformed so the project could display imported articles instead of hand-writing every page.

UI And Identity

The project needed to feel like a real wiki product, so I worked on the brand, page structure, navigation, search, and visual system.

Deterministic CSS

The spoiler system relied on generated CSS selectors so the UI could reveal or hide content based on one predictable user setting.

Process

How I Thought Through It

I had to keep the product idea practical. The important thing was not making the most elegant possible spoiler system; it was making something deterministic that could ship for the capstone timeline.

  • I started by helping define how the product should feel: familiar enough to behave like a wiki, but clearly centered around spoiler safety.
  • The backend/import work gave us raw wiki content, but the raw output was not ready for the frontend. I worked on cleaning the HTML, extracting usable titles, handling imported images, and getting the rendered page closer to a wiki article.
  • For spoilers, the simple rule was more important than a clever implementation: content marked with a higher story level should remain hidden until the user's selected level allowed it.
  • The CSS generator was a pragmatic decision. Instead of manually writing a large number of repetitive selector combinations, I generated the CSS for the maximum chapter or arc count we needed.

Code

Snippets From The Work

These snippets show the practical parts I worked on: generating spoiler CSS, binding a user spoiler level to article content, cleaning imported wiki HTML, and filtering search results by story progress.

tools/spoilerCSS/Spoil.jsx

Generating Spoiler CSS

When time was tight, I generated the repetitive spoiler-level CSS instead of hand-writing selectors for every chapter or arc combination.

const maxSpoilLevel = 31;
let cssRules = "";
let cssRulesA = "";

for (let i = 1; i <= maxSpoilLevel; i++) {
  for (let j = 1; j <= i; j++) {
    cssRules += `[data-spoil-level='${i}'] .spoil_${j},`;
  }
}

for (let i = 1; i <= maxSpoilLevel; i++) {
  for (let j = 1; j <= i; j++) {
    cssRulesA += `[data-spoil-level='${i}'] .spoil_${j} a,`;
  }
}

front-end/src/pages/Wiki.jsx

Connecting User Level To Article Content

The article page read the user's saved spoiler level and applied it as a data attribute around the parsed article HTML.

const userSpoilLevel = parseInt(localStorage.getItem("noSpoilLevel"), 10);

return (
  <main className="page__main" lang="en">
    <div data-spoil-level={userSpoilLevel}>
      {parse(doc)}
    </div>
  </main>
);

back-end/routes/parse.mjs

Cleaning Imported Wiki HTML

After the raw wiki content came through, I worked on cleanup steps that removed unused document metadata and turned imported title data into a usable article title.

const $ = cheerio.load(record);

$("html").replaceWith(function () {
  return $('<div class="document"></div>').append($(this).contents());
});

$("meta, link[rel='dc:isVersionOf'], title, base").remove();

$("pre").each((index, element) => {
  const preContent = $(element).html();
  const titleMatch = preContent.match(/&lt;title&gt;(.*?)&lt;\/title&gt;/i);

  if (titleMatch) {
    $(element).replaceWith(`<div class="title">${titleMatch[1]}</div>`);
  }
});

front-end/src/pages/SearchPage.jsx

Filtering Search By Spoiler Level

Search results also needed spoiler awareness, so results above the user's current story level were removed before rendering.

const noSpoilLevel = localStorage.getItem("noSpoilLevel") || 0;

const filteredResults = response.data.filter((result) => {
  return result.spoiler_level <= noSpoilLevel;
});

setSearchResults(filteredResults);

Reflection

What I Took From It

Story Safe was one of the first projects where design, data shape, and implementation constraints all had to answer to the same product promise.

  • I learned how quickly UI/UX decisions become technical constraints once real data enters the system.
  • I got more comfortable taking messy imported HTML and shaping it into a usable React experience.
  • The spoiler system taught me to value deterministic behavior, especially when the product promise depends on protecting the user from seeing the wrong information.
  • The capstone made collaboration feel real: backend parsing, frontend rendering, styling, routing, and product behavior all had to line up for the idea to work.