private-synthid / index.html
jfrery-zama's picture
bigger logo
9df3e86 unverified
raw
history blame
14.3 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Private AI Text Detector – Check if your encrypted text is AI-generated</title>
<link rel="icon" type="image/x-icon" href="favicon.ico">
<style>
@import url('https://fonts.googleapis.com/css2?family=Telegraf:wght@400;500;600;700&display=swap');
:root {
--yellow: #ffd200;
--black: #000;
--white: #fff;
--grey-050: #f9f9f9;
--grey-200: #e0e0e0;
--container-max: 1200px;
--shadow-sm: 0 2px 4px rgba(0,0,0,0.06);
--spacing-unit: 8px;
}
/* reset */
*, *::before, *::after { margin:0; padding:0; box-sizing:border-box; }
body {
font-family: 'Telegraf', sans-serif;
background: var(--yellow);
color: var(--black);
line-height: 1.5;
font-weight: 400;
}
.explanation-text {
font-size: 1.3rem;
line-height: 1.6;
margin-bottom: calc(var(--spacing-unit) * 4);
}
.explanation-text a {
color: var(--black);
text-decoration: underline;
font-weight: 500;
}
.explanation-text a:hover {
opacity: 0.8;
}
/* navbar */
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
padding: calc(var(--spacing-unit) * 3) calc(var(--spacing-unit) * 4);
max-width: var(--container-max);
margin-inline: auto;
}
.logo {
height: 60px;
}
.contact-btn {
border: 2px solid var(--black);
background: transparent;
color: var(--black);
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3);
border-radius: 8px;
font-size: 1rem;
font-weight: 600;
font-family: 'Telegraf', sans-serif;
cursor: pointer;
transition: all .2s ease;
text-decoration: none;
display: inline-block;
}
.contact-btn:hover {
background: var(--black);
color: var(--yellow);
transform: translateY(-1px);
}
/* hero */
.hero {
display: grid;
gap: calc(var(--spacing-unit) * 6);
align-items: center;
max-width: var(--container-max);
margin: calc(var(--spacing-unit) * 4) auto calc(var(--spacing-unit) * 10);
padding-inline: calc(var(--spacing-unit) * 4);
grid-template-columns: 1fr;
}
.hero h1 {
font-size: clamp(3rem, 6vw, 4.5rem);
font-weight: 700;
margin-bottom: calc(var(--spacing-unit) * 3);
letter-spacing: -0.03em;
line-height: 1.1;
}
.hero p {
font-size: 1.4rem;
max-width: 45ch;
font-weight: 400;
line-height: 1.5;
}
.hero-diagram {
background: var(--white);
border: 2px solid var(--black);
border-radius: 0;
padding: calc(var(--spacing-unit) * 4);
box-shadow: 8px 8px 0 var(--black);
width: 100%;
min-height: 400px;
display: flex;
align-items: center;
justify-content: center;
}
.hero-diagram .mermaid {
max-width: 100%;
font-family: 'Telegraf', sans-serif;
font-size: 1.2rem;
}
/* wizard */
main {
max-width: var(--container-max);
margin: 0 auto calc(var(--spacing-unit) * 12);
padding: 0 calc(var(--spacing-unit) * 4);
}
.section-title {
font-size: 3rem;
font-weight: 700;
margin-bottom: calc(var(--spacing-unit) * 6);
text-align: center;
letter-spacing: -0.02em;
}
.cards-wrapper {
display: grid;
gap: calc(var(--spacing-unit) * 3);
grid-template-columns: repeat(auto-fit, minmax(min(380px, 100%), 1fr));
}
.card {
background: var(--white);
border: 2px solid var(--black);
border-radius: 0;
box-shadow: 6px 6px 0 var(--black);
padding: calc(var(--spacing-unit) * 4);
min-height: 280px;
display: flex;
flex-direction: column;
transition: transform 0.2s ease;
}
.card:hover {
transform: translate(-2px, -2px);
box-shadow: 8px 8px 0 var(--black);
}
.card h2 {
margin-bottom: calc(var(--spacing-unit) * 3);
font-size: 1.8rem;
font-weight: 700;
letter-spacing: -0.01em;
height: 30px;
}
.card-content {
flex: 1;
display: flex;
flex-direction: column;
gap: calc(var(--spacing-unit) * 2);
}
.controls {
display: flex;
align-items: center;
gap: calc(var(--spacing-unit) * 1.5);
min-height: 40px;
}
textarea {
width: 100%;
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 2);
border: 2px solid var(--black);
border-radius: 0;
font-family: 'Telegraf', sans-serif;
font-size: 1.2rem;
resize: vertical;
background: var(--white);
min-height: 60px;
transition: all 0.2s ease;
font-weight: 500;
}
textarea:focus {
outline: none;
transform: translate(-2px, -2px);
box-shadow: 4px 4px 0 var(--black);
}
.btn {
border: 2px solid var(--black);
background: var(--yellow);
color: var(--black);
font-weight: 600;
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3);
min-width: 160px;
font-size: 1.2rem;
font-family: 'Telegraf', sans-serif;
border-radius: 0;
cursor: pointer;
transition: all .2s ease;
display: inline-flex;
align-items: center;
justify-content: center;
gap: calc(var(--spacing-unit) * 1);
white-space: nowrap;
letter-spacing: -0.01em;
}
.btn:hover:not(:disabled) {
background: var(--black);
color: var(--yellow);
transform: translateY(-2px);
}
.btn:disabled {
opacity: .5;
cursor: not-allowed;
}
.status {
font-size: 1.2rem;
color: var(--black);
flex: 1;
font-weight: 500;
}
.loader {
width: 20px;
height: 20px;
border: 3px solid transparent;
border-top: 3px solid var(--black);
border-right: 3px solid var(--black);
border-radius: 50%;
animation: spin 1s linear infinite;
flex-shrink: 0;
}
@keyframes spin { to { transform: rotate(360deg);} }
#encIcon {
font-size: 1.25rem;
flex-shrink: 0;
}
footer {
font-size: 1.2rem;
text-align: center;
padding: calc(var(--spacing-unit) * 6) calc(var(--spacing-unit) * 2);
color: var(--black);
font-weight: 500;
}
.visually-hidden { position: absolute !important; height: 1px; width: 1px; overflow: hidden; clip: rect(1px,1px,1px,1px); white-space: nowrap; }
/* Result styling */
#decResult {
font-family: 'Telegraf', sans-serif;
padding: calc(var(--spacing-unit) * 3);
background: var(--grey-050);
border: 2px solid var(--black);
border-radius: 8px;
min-height: 80px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
gap: calc(var(--spacing-unit) * 1);
font-weight: 600;
font-size: 1.4rem;
text-align: center;
}
#decResult:not(:empty) {
color: var(--black);
}
.watermark-flag {
font-size: 2rem;
font-weight: 700;
margin-bottom: calc(var(--spacing-unit) * 1);
}
.watermark-score {
font-size: 1.2rem;
font-weight: 500;
opacity: 0.8;
}
/* Result type styling */
.watermark-flag.inconclusive {
color: #f57c00;
}
.watermark-flag.watermarked {
color: #2e7d32;
}
.watermark-flag.ai-generated {
color: #1976d2;
}
/* Responsive adjustments */
@media (max-width: 968px) {
.hero {
grid-template-columns: 1fr;
gap: calc(var(--spacing-unit) * 4);
}
.hero-diagram {
min-height: 300px;
}
}
@media (max-width: 768px) {
.cards-wrapper {
gap: calc(var(--spacing-unit) * 2);
}
.card {
padding: calc(var(--spacing-unit) * 3);
box-shadow: 4px 4px 0 var(--black);
}
.btn {
min-width: 140px;
padding: calc(var(--spacing-unit) * 1.25) calc(var(--spacing-unit) * 2.5);
}
.hero h1 {
font-size: 2.5rem;
}
.section-title {
font-size: 2rem;
}
}
</style>
<!-- external scripts -->
<script type="module" src="https://belladoreai.github.io/llama3-tokenizer-js/bundle/llama3-tokenizer-with-baked-data.js"></script>
</head>
<body>
<!-- Navbar -->
<nav class="navbar">
<img src="zama_logo.webp" alt="Zama logo" class="logo" />
<a href="https://www.zama.ai/contact" class="contact-btn" target="_blank" rel="noopener noreferrer">Contact us</a>
</nav>
<!-- Hero -->
<header class="hero" role="banner">
<div class="hero-copy">
<h1>Private AI Text Detection</h1>
<div class="explanation-text">
Private SynthID uses Google's SynthID technology to identify AI-generated text while preserving privacy through Fully Homomorphic Encryption (FHE). This means you can learn whether your text was produced by an AI—without anyone ever seeing the content. Learn more about <a href="https://deepmind.google/science/synthid/" target="_blank">SynthID</a> and <a href="https://www.zama.ai/" target="_blank">FHE applications</a>.
</div>
</div>
</header>
<!-- Wizard Steps -->
<main>
<div class="cards-wrapper">
<!-- 1 Keys -->
<section class="card" aria-labelledby="step1">
<h2 id="step1">1. Keys</h2>
<div class="card-content">
<div class="controls" style="margin-top: 0;">
<p id="keygenStatus" class="status" aria-live="polite">Generate new keys (a few minutes on first run)</p>
<span id="keygenSpin" class="loader" hidden aria-label="Generating keys"></span>
</div>
<div style="display:flex; gap:var(--spacing-unit); margin-top:auto">
<button id="btnKeygen" class="btn" aria-describedby="keygenStatus">🔑 Generate new keys</button>
<button id="btnLoadSaved" class="btn" aria-describedby="keygenStatus">🗝️ Load saved keys</button>
</div>
</div>
</section>
<!-- 2 Encrypt -->
<section class="card" aria-labelledby="step2">
<h2 id="step2">2. Encrypt text</h2>
<div class="card-content">
<div class="controls">
<p id="tokenizerStatus" class="status" aria-live="polite" style="display: none;">Loading tokenizer...</p>
<span id="tokenizerSpin" class="loader" hidden aria-label="Loading tokenizer"></span>
</div>
<label for="tokenInput" class="visually-hidden">Text to encrypt</label>
<textarea id="tokenInput" rows="2" placeholder="Enter text to encrypt or use the example below" autocomplete="off"></textarea>
<div style="display: flex; gap: calc(var(--spacing-unit) * 1); margin-bottom: calc(var(--spacing-unit) * 2); flex-wrap: wrap; align-items: center;">
<button id="btnWatermarked" class="btn" style="min-width: auto; padding: calc(var(--spacing-unit) * 1) calc(var(--spacing-unit) * 1.5); font-size: 1rem;">✨ Load sample AI text</button>
</div>
<div class="controls" style="margin-top: auto;">
<button id="btnEncrypt" class="btn" disabled>🛡️ Encrypt</button>
<span id="encryptSpin" class="loader" hidden aria-label="Encrypting"></span>
<span id="encIcon" hidden aria-label="Encrypted">🔒</span>
<span id="encStatus" class="status"></span>
</div>
</div>
</section>
<!-- 3 Send -->
<section class="card" aria-labelledby="step3">
<h2 id="step3">3. Send to server</h2>
<div class="card-content">
<div>
<p id="srvStatus" class="status" aria-live="polite">Waiting for encrypted data… (server processing can take several minutes)</p>
<p id="srvComputing" class="status" aria-live="polite" hidden>Server computing…</p>
</div>
<div class="controls" style="margin-top: auto;">
<button id="btnSend" class="btn" disabled>📡 Send</button>
<span id="spin" class="loader" hidden aria-label="Working"></span>
</div>
</div>
</section>
<!-- 4 Result -->
<section class="card" aria-labelledby="step4">
<h2 id="step4">4. Result</h2>
<div class="card-content">
<label for="encResult" class="visually-hidden">Encrypted result bytes</label>
<textarea id="encResult" rows="1" readonly placeholder="Encrypted result will appear here"></textarea>
<p id="decResult" aria-live="polite" style="flex: 1;"></p>
<div class="controls" style="margin-top: auto;">
<button id="btnDecrypt" class="btn" disabled>🔓 Decrypt</button>
</div>
<div class="disclaimer" style="margin-top: calc(var(--spacing-unit) * 3); font-size: 0.9rem; color: var(--black); opacity: 0.7; line-height: 1.5;">
<p><strong>Note about reliability:</strong> AI text detection works best with longer passages (100+ tokens). In this demo, input is limited to 16 tokens for performance, which may affect accuracy. Reliability improves significantly with 10+ tokens but is less reliable on very short snippets.</p>
</div>
</div>
</section>
</div>
</main>
<footer>© 2025 Zama — Demo only, not for production use.</footer>
<script type="module" src="wasm-demo.js"></script>
<script>
// Since the tokenizer loads automatically and there's no explicit loading status in the JS,
// we'll hide the tokenizer status message on page load
window.addEventListener('DOMContentLoaded', () => {
const tokenizerStatus = document.getElementById('tokenizerStatus');
if (tokenizerStatus) {
tokenizerStatus.style.display = 'none';
}
});
</script>
</body>
</html>