Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="utf-8" /> | |
<meta name="viewport" content="width=device-width, initial-scale=1" /> | |
<title>Private SynthID - Check if your 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 { | |
font-size: 2rem; | |
font-weight: 700; | |
letter-spacing: -0.02em; | |
} | |
.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 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 ; 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) * 2); | |
background: var(--grey-050); | |
border: 2px solid var(--black); | |
border-radius: 8px; | |
min-height: 48px; | |
display: flex; | |
align-items: center; | |
font-weight: 600; | |
font-size: 1.3rem; | |
} | |
#decResult:not(:empty) { | |
color: var(--black); | |
} | |
/* 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> | |
<script type="module"> | |
import mermaid from 'https://cdn.jsdelivr.net/npm/[email protected]/+esm'; | |
mermaid.initialize({ | |
startOnLoad: true, | |
theme: 'base', | |
themeVariables: { | |
primaryColor: '#ffd200', | |
primaryTextColor: '#000', | |
lineColor: '#000', | |
tertiaryColor: '#fff', | |
fontFamily: 'Telegraf', | |
fontSize: '1.4rem' | |
}, | |
flowchart: { | |
nodeSpacing: 50, | |
rankSpacing: 50, | |
curve: 'basis', | |
fontSize: '1.4rem' | |
}, | |
sequence: { | |
actorFontSize: '1.4rem', | |
noteFontSize: '1.4rem', | |
messageFontSize: '1.4rem' | |
} | |
}); | |
</script> | |
</head> | |
<body> | |
<!-- Navbar --> | |
<nav class="navbar"> | |
<div class="logo">ZAMA</div> | |
<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 Watermark Detection - Check if your text contains watermarks</h1> | |
<div class="explanation-text"> | |
Private SynthID uses Google's SynthID technology to detect watermarks in AI-generated text while preserving privacy through Fully Homomorphic Encryption (FHE). This means you can check if text contains AI watermarks without revealing the content to anyone. Note that this only detects watermarked text - not all AI-generated text will be detected. 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> | |
<div class="hero-diagram"> | |
<pre class="mermaid"> | |
sequenceDiagram | |
participant User🧑💻 | |
participant Browser🔐 | |
participant RustServer🦀 | |
User🧑💻->>Browser🔐: 1️⃣ Generate keys | |
Browser🔐->>Browser🔐: TFHE keygen (WASM) | |
Browser🔐->>RustServer🦀: 2️⃣ /handshake (server_key_b64) | |
RustServer🦀->>Browser🔐: UID (uuid-v4) | |
User🧑💻->>Browser🔐: 3️⃣ Enter text | |
Browser🔐->>Browser🔐: Tokenise ◁ HF tokenizer | |
Browser🔐->>Browser🔐: Encrypt tokens (WASM) | |
Browser🔐->>RustServer🦀: 4️⃣ /detect (uid, ciphertext_b64) | |
RustServer🦀->>RustServer🦀: FHE SynthID detect | |
RustServer🦀->>Browser🔐: Encrypted result_b64 | |
Browser🔐->>Browser🔐: Decrypt result (WASM) | |
Browser🔐-->>User🧑💻: flag / score / g | |
</pre> | |
</div> | |
</header> | |
<!-- Wizard Steps --> | |
<main> | |
<h2 class="section-title">How watermark detection works</h2> | |
<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">Ready to generate keys</p> | |
<span id="keygenSpin" class="loader" hidden aria-label="Generating keys"></span> | |
</div> | |
<div style="margin-top: auto;"> | |
<button id="btnKeygen" class="btn" aria-describedby="keygenStatus">🔑 Generate 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" autocomplete="off"></textarea> | |
<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...</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> | |
</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> |