Spaces:
Running
Running
<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 { | |
/* palette tuned to match screenshot */ | |
--white: #ffffff; | |
--black: #000000; | |
--grey-025: #f5f5f5; | |
--grey-050: #f0f0f0; | |
--grey-100: #e8e8e8; | |
--grey-200: #e0e0e0; /* button & border base */ | |
--grey-300: #d4d4d4; /* button hover */ | |
--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(--white); /* removed yellow */ | |
color: var(--black); | |
line-height: 1.5; | |
font-weight: 400; | |
} | |
/* general copy */ | |
.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: 120px; | |
} | |
.contact-btn { | |
border: none; /* no thick outline */ | |
background: var(--grey-200); /* light grey like screenshot */ | |
color: var(--black); | |
padding: calc(var(--spacing-unit) * 1.5) calc(var(--spacing-unit) * 3); | |
border-radius: 6px; | |
font-size: 1rem; | |
font-weight: 600; | |
font-family: 'Telegraf', sans-serif; | |
cursor: pointer; | |
transition: background .2s ease; | |
text-decoration: none; | |
display: inline-block; | |
} | |
.contact-btn:hover { | |
background: var(--grey-300); /* subtle hover */ | |
} | |
/* 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: 1px solid var(--grey-200); /* lighter border */ | |
border-radius: 4px; | |
padding: calc(var(--spacing-unit) * 4); | |
box-shadow: none; /* removed strong shadow */ | |
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) * 6); | |
grid-template-columns: 1fr; | |
} | |
.card { | |
background: var(--white); | |
border: 1px solid var(--grey-200); /* lighter soft border */ | |
border-radius: 6px; | |
box-shadow: none; /* no drop shadow */ | |
padding: calc(var(--spacing-unit) * 4); | |
min-height: 280px; | |
display: flex; | |
flex-direction: column; | |
transition: background 0.2s ease; | |
} | |
.card:hover { | |
background: var(--grey-025); | |
} | |
.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: 1px solid var(--grey-200); | |
border-radius: 4px; | |
font-family: 'Telegraf', sans-serif; | |
font-size: 1.2rem; | |
resize: vertical; | |
background: var(--white); | |
min-height: 60px; | |
transition: border-color 0.2s ease; | |
font-weight: 500; | |
} | |
textarea:focus { | |
outline: none; | |
border-color: var(--grey-300); | |
} | |
.btn { | |
border: none; /* flat buttons */ | |
background: var(--grey-200); | |
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: 6px; | |
cursor: pointer; | |
transition: background 0.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(--grey-300); | |
} | |
.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 ; 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: 1px solid var(--grey-200); | |
border-radius: 6px; | |
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); | |
} | |
.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> | |
<!-- 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 and send them to the server (requires a 130MB upload). Keys can then be loaded (instantly).</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</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> | |
// Hide tokenizer status on load | |
window.addEventListener('DOMContentLoaded', () => { | |
const tokenizerStatus = document.getElementById('tokenizerStatus'); | |
if (tokenizerStatus) { | |
tokenizerStatus.style.display = 'none'; | |
} | |
}); | |
</script> | |
</body> | |
</html> | |