Expand How It Works into FAQ section with collapsible questions

This commit is contained in:
Laan Tungir
2026-06-28 20:23:01 -04:00
parent 27f4cd6857
commit d6b167a50b
2 changed files with 197 additions and 39 deletions

View File

@@ -1,5 +1,5 @@
{
"VERSION": "v0.7.58",
"VERSION_NUMBER": "0.7.58",
"BUILD_DATE": "2026-06-29T00:18:18.870Z"
"VERSION": "v0.7.59",
"VERSION_NUMBER": "0.7.59",
"BUILD_DATE": "2026-06-29T00:23:01.531Z"
}

View File

@@ -302,6 +302,37 @@
font-size: 13px;
}
/* ---------- FAQ items ---------- */
.stegoFaqItem {
border: 1px solid var(--border-color);
border-radius: 6px;
margin-bottom: 10px;
overflow: hidden;
}
.stegoFaqItem:last-child { margin-bottom: 0; }
.stegoFaqToggle {
padding: 12px 14px;
font-size: 15px;
font-weight: 600;
background: var(--background-color);
width: 100%;
}
.stegoFaqToggle:hover {
background: var(--secondary-color);
}
.stegoFaqItem .stegoCollapsibleContent {
padding: 0 14px 14px 14px;
margin-top: 0;
}
.stegoCollapsibleContent ul {
padding-left: 20px;
margin: 8px 0;
}
.stegoCollapsibleContent li {
margin-bottom: 4px;
}
.stegoFooter {
text-align: center;
color: var(--muted-color);
@@ -403,37 +434,157 @@
<p id="stegoMatchIndicator" class="stegoInfo"></p>
</section>
<!-- How It Works -->
<!-- ================================================================
FAQ SECTION
================================================================
Each question is its own collapsible card using the same
stegoCollapsibleToggle / stegoCollapsibleContent pattern.
================================================================ -->
<section class="stegoCard">
<h2>
<button id="stegoHowToggle" class="stegoCollapsibleToggle" aria-expanded="false">
How It Works <span class="chevron"></span>
<h2 style="text-align: center; margin-bottom: 16px;">FAQ</h2>
<!-- Q1: What is the point of this? -->
<div class="stegoFaqItem">
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
What is the point of this? <span class="chevron"></span>
</button>
</h2>
<div id="stegoHowContent" class="stegoCollapsibleContent stegoHidden">
<p>
This demo uses a <strong>half-splitting entropy coding</strong> scheme
built on top of GPT-2's next-token probability distribution.
</p>
<ol>
<li>The secret message is converted to a bit string (UTF-8 → bits).</li>
<li>For each secret bit, GPT-2 produces a probability distribution over
the entire vocabulary for the next token.</li>
<li>Tokens are sorted by probability (descending) and split into two
halves at the 50% cumulative probability mark.</li>
<li>Bit <code>0</code> → the next token is sampled from the
<em>first</em> (higher-probability) half; bit <code>1</code>
from the <em>second</em> half.</li>
<li>A shared-key PRNG (mulberry32) selects the exact token within the
chosen half, so the decoder can reproduce the same random draws.</li>
<li>The decoder re-runs GPT-2 on the same context, observes which half
each cover token fell into, and recovers the bits → original message.</li>
</ol>
<p>
Because both sides share the same model, context, and PRNG seed, the
decoder can perfectly reconstruct the hidden bits. The resulting cover
text reads like normal GPT-2 output, hiding the secret in plain sight.
</p>
<div class="stegoCollapsibleContent stegoHidden">
<p>
This demo lets you <strong>hide a secret message inside ordinary-looking
AI-generated text</strong>. The output reads like a normal sentence a
language model might produce, but it secretly encodes your message bit
by bit.
</p>
<p>
The practical point is <em>covert communication</em>: two people who
share a key can exchange messages that, to anyone watching, look like
innocuous GPT-2 text. There's no obvious ciphertext, no encrypted file,
and no metadata screaming "this is encrypted." The secret is hidden in
plain sight.
</p>
<p>
It's also a neat demonstration of how much information is packed into
every token a language model emits — each token can carry a full secret
bit while still looking natural.
</p>
</div>
</div>
<!-- Q2: How does it work? -->
<div class="stegoFaqItem">
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
How does it work? <span class="chevron"></span>
</button>
<div class="stegoCollapsibleContent stegoHidden">
<p>
This demo uses a <strong>half-splitting entropy coding</strong> scheme
built on top of GPT-2's next-token probability distribution.
</p>
<ol>
<li>The secret message is converted to a bit string (UTF-8 → bits).</li>
<li>For each secret bit, GPT-2 produces a probability distribution over
the entire vocabulary for the next token.</li>
<li>Tokens are sorted by probability (descending) and split into two
halves at the 50% cumulative probability mark.</li>
<li>Bit <code>0</code> → the next token is sampled from the
<em>first</em> (higher-probability) half; bit <code>1</code>
from the <em>second</em> half.</li>
<li>A shared-key PRNG (mulberry32) selects the exact token within the
chosen half, so the decoder can reproduce the same random draws.</li>
<li>The decoder re-runs GPT-2 on the same context, observes which half
each cover token fell into, and recovers the bits → original message.</li>
</ol>
<p>
Because both sides share the same model, context, and PRNG seed, the
decoder can perfectly reconstruct the hidden bits. The resulting cover
text reads like normal GPT-2 output, hiding the secret in plain sight.
</p>
</div>
</div>
<!-- Q3: Can you give me a simple example? -->
<div class="stegoFaqItem">
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
Can you give me a simple example of how it works? <span class="chevron"></span>
</button>
<div class="stegoCollapsibleContent stegoHidden">
<p>
Suppose the context is <code>"I like to eat"</code> and your secret
message is <code>"HI"</code>. The letter <code>H</code> is byte
<code>0x48</code> = bits <code>01001000</code>, and <code>I</code> is
<code>0x49</code> = bits <code>01001001</code>. So the encoder needs to
hide 16 bits total.
</p>
<p>
For the <strong>first bit (0)</strong>, GPT-2 looks at
<code>"I like to eat"</code> and ranks every possible next token by
probability. It splits that ranked list at the 50% cumulative-probability
mark into a "high-prob" half and a "low-prob" half. Since the bit is
<code>0</code>, the encoder samples a token from the <em>high-prob</em>
half — say <code>" pizza"</code>. The cover text is now
<code>"I like to eat pizza"</code>.
</p>
<p>
For the <strong>second bit (1)</strong>, GPT-2 now looks at
<code>"I like to eat pizza"</code> and produces a fresh distribution.
The bit is <code>1</code>, so the encoder samples from the
<em>low-prob</em> half — maybe <code>" and"</code>. Cover text:
<code>"I like to eat pizza and"</code>.
</p>
<p>
This continues for all 16 bits, then a few padding tokens are added so
the text ends naturally. The final cover text might read something like:
</p>
<p style="font-family: var(--font-mono, monospace); background: var(--background-color); padding: 8px 12px; border-radius: 6px; border: 1px solid var(--border-color);">
"I like to eat pizza and pasta with my friends on weekends."
</p>
<p>
To anyone else, that's just a normal sentence. But the decoder — who
knows the same context, key, and model — re-runs GPT-2 at each step,
checks which half each token fell into, and recovers the bits
<code>01001000 01001001</code><code>"HI"</code>.
</p>
</div>
</div>
<!-- Q4: What is your inspiration for this? -->
<div class="stegoFaqItem">
<button class="stegoCollapsibleToggle stegoFaqToggle" aria-expanded="false">
What is your inspiration for this? <span class="chevron"></span>
</button>
<div class="stegoCollapsibleContent stegoHidden">
<p>
This project is inspired by research into <strong>linguistic
steganography</strong> — the art of hiding information within natural
language. Traditional steganography hides data in images, audio, or
file metadata, but text-based steganography is harder because text is
compact and every character is visible.
</p>
<p>
The breakthrough idea is to use a <strong>language model's own
probability distribution</strong> as the carrier channel. Instead of
tweaking pixels or LSBs, you steer which token the model emits next,
encoding one secret bit per token. Key inspirations include:
</p>
<ul>
<li><strong>Entropy coding approaches</strong> (e.g. Cachin's
information-theoretic steganography) that treat the cover as a
probability distribution and map secret bits onto it.</li>
<li><strong>"Dissociated Press" / Markov-chain steganography</strong>
— early experiments that used statistical text generation to hide
messages, limited by the poor quality of pre-LLM language models.</li>
<li><strong>Modern LLM steganography research</strong> — papers that
use GPT-style models to achieve near-perfect cover text quality
while encoding multiple bits per token via arithmetic or
half-splitting codes.</li>
</ul>
<p>
The half-splitting scheme used here is one of the simplest robust
approaches: it guarantees exactly one bit per token, is symmetric
(encoder and decoder run the same logic), and produces text that's
indistinguishable from normal model output to a casual reader.
</p>
</div>
</div>
</section>
@@ -1237,8 +1388,8 @@
const decodeProgressBar = document.getElementById("stegoDecodeProgressBar");
const decodeRecoveredChars = document.getElementById("stegoDecodeRecoveredChars");
const howToggle = document.getElementById("stegoHowToggle");
const howContent = document.getElementById("stegoHowContent");
// Legacy single-toggle references removed — FAQ section now uses
// multiple .stegoFaqToggle buttons handled generically below.
// State
let model = null;
@@ -1248,12 +1399,19 @@
let lastSecret = null;
// ---------------------------------------------------------------------------
// Collapsible "How It Works"
// Collapsible FAQ toggles
// ---------------------------------------------------------------------------
howToggle.addEventListener("click", () => {
const expanded = howToggle.getAttribute("aria-expanded") === "true";
howToggle.setAttribute("aria-expanded", String(!expanded));
howContent.classList.toggle("stegoHidden", expanded);
// Each .stegoFaqToggle button toggles the visibility of the next
// .stegoCollapsibleContent sibling inside its .stegoFaqItem container.
document.querySelectorAll(".stegoFaqToggle").forEach((toggle) => {
toggle.addEventListener("click", () => {
const expanded = toggle.getAttribute("aria-expanded") === "true";
toggle.setAttribute("aria-expanded", String(!expanded));
const content = toggle.nextElementSibling;
if (content) {
content.classList.toggle("stegoHidden", expanded);
}
});
});
// ---------------------------------------------------------------------------