Expand How It Works into FAQ section with collapsible questions
This commit is contained in:
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user