<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: João Vitor B.S</title>
    <description>The latest articles on DEV Community by João Vitor B.S (@raaywasdead).</description>
    <link>https://dev.to/raaywasdead</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3817551%2F2e4c0344-a7b4-4831-95b5-ca0d61f81025.jpeg</url>
      <title>DEV Community: João Vitor B.S</title>
      <link>https://dev.to/raaywasdead</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/raaywasdead"/>
    <language>en</language>
    <item>
      <title>How I built a hidden ARG inside a Deltarune fan site</title>
      <dc:creator>João Vitor B.S</dc:creator>
      <pubDate>Tue, 10 Mar 2026 22:27:18 +0000</pubDate>
      <link>https://dev.to/raaywasdead/how-i-built-a-hidden-arg-inside-a-deltarune-fan-site-28dn</link>
      <guid>https://dev.to/raaywasdead/how-i-built-a-hidden-arg-inside-a-deltarune-fan-site-28dn</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24mav8ow95n35hfxuexp.webp" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F24mav8ow95n35hfxuexp.webp" alt=" " width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I recently finished a personal project: a Deltarune archive built with React and TypeScript.&lt;/p&gt;

&lt;p&gt;The site has character pages, lore, music, stats and sprites — all styled after the game's battle screen UI. But it also has a secret layer that most people won't find unless they're patient.&lt;/p&gt;

&lt;p&gt;I built a hidden ARG into it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The trigger&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you stay on the Knight's page for 30 seconds without touching anything, something happens.&lt;/p&gt;

&lt;p&gt;The implementation is simple — a setTimeout that resets on any user activity:&lt;/p&gt;

&lt;p&gt;jslet idleTimer: ReturnType | null = null;&lt;/p&gt;

&lt;p&gt;const resetTimer = () =&amp;gt; {&lt;br&gt;
  if (idleTimer) clearTimeout(idleTimer);&lt;br&gt;
  idleTimer = setTimeout(triggerARG, 30000);&lt;br&gt;
};&lt;/p&gt;

&lt;p&gt;window.addEventListener("mousemove", resetTimer);&lt;br&gt;
window.addEventListener("keydown", resetTimer);&lt;br&gt;
resetTimer();&lt;/p&gt;

&lt;p&gt;When the timer fires, it starts a chain of 6 phases:&lt;/p&gt;

&lt;p&gt;flash → blackout → glitch dialogue → secret reveal → terminal breach → corrupted save file&lt;/p&gt;

&lt;p&gt;Each phase is its own React state, with transitions between them handled by setTimeout chains and CSS animations.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The audio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I wanted the experience to feel unsettling. No libraries — everything is Web Audio API from scratch.&lt;br&gt;
Static noise:&lt;/p&gt;

&lt;p&gt;jsfunction createStaticNode(ctx: AudioContext) {&lt;br&gt;
  const len = ctx.sampleRate * 2;&lt;br&gt;
  const buf = ctx.createBuffer(1, len, ctx.sampleRate);&lt;br&gt;
  const data = buf.getChannelData(0);&lt;br&gt;
  for (let i = 0; i &amp;lt; len; i++) data[i] = (Math.random() * 2 - 1) * 0.18;&lt;br&gt;
  // loop + gain ramp&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;Heartbeat is two oscillators with exponential gain envelopes fired with a 220ms offset — simulating the lub-dub. It plays during the follower testimonies and the corrupted save file screen.&lt;/p&gt;

&lt;p&gt;Voice blips for Gaster's dialogue are short .mp3 clips played as BufferSource nodes, one per character reveal, cut hard at 90ms for that crisp Undertale text sound.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The glitch typewriter&lt;/strong&gt;&lt;br&gt;
Gaster's lines reveal character by character. Each letter briefly flashes as a random Wingdings symbol before settling:&lt;/p&gt;

&lt;p&gt;jsconst WDING_POOL = "◆○●■□★☆✓✗✞♥♠♣♦①②③④⑤";&lt;/p&gt;

&lt;p&gt;// Each letter: set a random symbol, clear after 80–160ms&lt;br&gt;
if (/[a-zà-ÿ]/i.test(ch)) {&lt;br&gt;
  const glitchCh = WDING_POOL[Math.floor(Math.random() * WDING_POOL.length)];&lt;br&gt;
  setSettling(s =&amp;gt; ({ ...s, [idx]: glitchCh }));&lt;br&gt;
  setTimeout(() =&amp;gt; {&lt;br&gt;
    setSettling(s =&amp;gt; { const n = { ...s }; delete n[idx]; return n; });&lt;br&gt;
  }, 80 + Math.random() * 80);&lt;br&gt;
}&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The silent response&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;There's one topic in the Gaster encounter — "You jumped in on purpose" — where he just responds with ..., one dot at a time, 1 second apart. No voice. No skip. No interaction. You just wait.&lt;/p&gt;

&lt;p&gt;It's three setTimeout calls and blocking the advance handler. But it felt like the most honest thing to write for that question.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Other easter eggs&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Typing gaster anywhere on the page triggers a subtle screen effect&lt;br&gt;
The Spamton, Queen and Noelle pages have alternate forms with different sprites, music and stats&lt;br&gt;
The Knight's nameplate shows [NULL] after you've completed the ARG&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stack&lt;/strong&gt;&lt;br&gt;
React + TypeScript + Vite, pure CSS for all animations, Web Audio API for sound, deployed on Vercel.&lt;/p&gt;

&lt;p&gt;Try it: deltarune-carchive.vercel.app&lt;br&gt;
Code: github.com/raaywasdead/deltarune_c.archive&lt;/p&gt;

&lt;p&gt;Go to the Knight's page and wait. 👀&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>webdev</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
