Download index.html from Deep-Fake/mozaic-sample-explorer: direct link, hf CLI and curl.
- Browser
- Download file 10.1 kB
-
https://huggingface.co/spaces/Deep-Fake/mozaic-sample-explorer/resolve/main/index.html
- Command line
-
hf download hf://spaces/Deep-Fake/mozaic-sample-explorer/index.html
-
curl -L -o index.html https://huggingface.co/spaces/Deep-Fake/mozaic-sample-explorer/resolve/main/index.html
10.1 kB
| <html lang="en"> | |
| <head> | |
| <meta charset="utf-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1"> | |
| <title>MOZAIC Sample Explorer</title> | |
| <style> | |
| :root { | |
| --bg: #f7f7f8; --card: #ffffff; --text: #1b1c1f; --muted: #62656e; --line: #e2e3e7; | |
| --accent: #4f46e5; --ok: #15803d; --bad: #b91c1c; --code: #f0f0f3; | |
| } | |
| @media (prefers-color-scheme: dark) { | |
| :root:not([data-theme="light"]) { | |
| --bg: #111214; --card: #1a1b1f; --text: #ececf0; --muted: #a0a3ad; --line: #2c2e34; | |
| --accent: #8b85ff; --ok: #4ade80; --bad: #f87171; --code: #24262c; | |
| } | |
| } | |
| :root[data-theme="dark"] { | |
| --bg: #111214; --card: #1a1b1f; --text: #ececf0; --muted: #a0a3ad; --line: #2c2e34; | |
| --accent: #8b85ff; --ok: #4ade80; --bad: #f87171; --code: #24262c; | |
| } | |
| * { box-sizing: border-box; } | |
| body { margin: 0; background: var(--bg); color: var(--text); | |
| font: 15px/1.55 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; } | |
| main { max-width: 1040px; margin: 0 auto; padding: 28px 16px 48px; } | |
| h1 { font-size: 1.7rem; margin: 0 0 6px; } | |
| .lede { color: var(--muted); margin: 0 0 4px; } | |
| .fine { color: var(--muted); font-size: .82rem; margin: 0 0 20px; } | |
| code { background: var(--code); padding: 1px 6px; border-radius: 5px; font-size: .88em; } | |
| .tabs { display: flex; gap: 4px; border-bottom: 1px solid var(--line); margin-bottom: 18px; overflow-x: auto; } | |
| .tabs button { background: none; border: 0; border-bottom: 2px solid transparent; color: var(--muted); | |
| padding: 9px 12px; font: inherit; cursor: pointer; white-space: nowrap; } | |
| .tabs button[aria-selected="true"] { color: var(--accent); border-bottom-color: var(--accent); font-weight: 600; } | |
| .task-q { font-weight: 600; margin: 0; } | |
| .task-in { color: var(--muted); margin: 2px 0 14px; } | |
| .note { color: var(--muted); font-size: .82rem; margin: 0 0 6px; } | |
| .stats { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 10px; margin-bottom: 18px; } | |
| .stat { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 10px 12px; } | |
| .stat b { display: block; font-size: 1.45rem; font-variant-numeric: tabular-nums; } | |
| .stat span { color: var(--muted); font-size: .82rem; } | |
| .nav { display: flex; gap: 8px; align-items: center; margin-bottom: 14px; } | |
| .nav select { flex: 1; min-width: 0; padding: 8px 10px; border-radius: 8px; border: 1px solid var(--line); | |
| background: var(--card); color: var(--text); font: inherit; } | |
| .nav button { padding: 8px 14px; border-radius: 8px; border: 1px solid var(--line); background: var(--card); | |
| color: var(--text); font: inherit; cursor: pointer; } | |
| .clips { display: grid; grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); gap: 12px; margin-bottom: 16px; } | |
| figure { margin: 0; background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 10px; } | |
| figure video { width: 100%; max-height: 320px; background: #000; border-radius: 6px; display: block; } | |
| figcaption { color: var(--muted); font-size: .84rem; margin-top: 6px; } | |
| .grid { display: grid; grid-template-columns: 3fr 2fr; gap: 16px; } | |
| .card { background: var(--card); border: 1px solid var(--line); border-radius: 10px; padding: 14px 16px; } | |
| .card h3 { font-size: .8rem; text-transform: uppercase; letter-spacing: .04em; color: var(--muted); margin: 0 0 6px; } | |
| blockquote { margin: 0 0 12px; padding-left: 12px; border-left: 3px solid var(--line); } | |
| table { border-collapse: collapse; width: 100%; margin-top: 10px; } | |
| th, td { text-align: left; padding: 7px 8px; border-bottom: 1px solid var(--line); } | |
| th { color: var(--muted); font-weight: 500; font-size: .85rem; } | |
| .ok { color: var(--ok); font-weight: 600; } .bad { color: var(--bad); font-weight: 600; } | |
| details { margin-top: 14px; } summary { cursor: pointer; color: var(--muted); } | |
| pre { background: var(--code); padding: 10px; border-radius: 8px; overflow-x: auto; font-size: .85rem; } | |
| ul { margin: 0; padding-left: 18px; } | |
| @media (max-width: 680px) { | |
| .stats { grid-template-columns: repeat(2, minmax(0, 1fr)); } | |
| .grid { grid-template-columns: 1fr; } | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <main> | |
| <h1>MOZAIC sample explorer</h1> | |
| <p class="lede">MOZAIC is an audio-visual deepfake benchmark built on VoxCeleb2. Every clip pairs a real or fake face | |
| with a real or fake voice. This page shows a small pilot: <code id="model">…</code> answering MOZAIC questions | |
| zero-shot, with example clips per task.</p> | |
| <p class="fine">Research demo. Fake clips are synthetic media made for detection research; they do not show anything | |
| the people in them said or did. A pilot this small is far too few for conclusions.</p> | |
| <div class="tabs" role="tablist" id="tabs"></div> | |
| <section id="panel"></section> | |
| </main> | |
| <script> | |
| const TASKS = { | |
| t1: ["T1 · Spot the fake", "Which part is fake: the video, the audio, both, or neither?", | |
| "The model saw 32 frames and heard the audio."], | |
| t2: ["T2 · Manipulation type", "How was the video made?", "The model saw 32 frames only, no sound."], | |
| t3: ["T3 · Same person", "Is the face (or voice) in clip 1 the same person as in clip 2?", | |
| "Face questions: frames of both clips, no sound. Voice questions: sound of both clips, no frames."], | |
| t3_5: ["T3.5 · Real same person", "Is clip 1 a real recording of clip 2's person?", | |
| "Face questions: frames of both clips, no sound. Voice questions: sound of both clips, no frames."], | |
| }; | |
| const LABELS = { RVRA: "real video · real audio", RVFA: "real video · fake audio", | |
| FVRA: "fake video · real audio", FVFA: "fake video · fake audio" }; | |
| const METHODS = { real: "camera (real)", facefusion: "FaceFusion face swap", s2v: "Wan2.2-S2V (audio-driven)", | |
| animate: "Wan2.2-Animate (video-driven)", seed_vc: "Seed-VC voice conversion" }; | |
| const esc = s => String(s ?? "").replace(/[&<>"']/g, c => ({ "&": "&", "<": "<", ">": ">", '"': """, "'": "'" }[c])); | |
| function clipDesc(clip) { | |
| const [label, method, frames, arm] = clip.split("__"); | |
| const parts = [LABELS[label] || label, METHODS[method] || method, frames.replace("f", " frames")]; | |
| if (arm !== "main") parts.push(`${arm} audio`); | |
| return parts.join(" · "); | |
| } | |
| let DATA, current = { task: null, i: 0 }; | |
| function renderTabs() { | |
| const tabs = document.getElementById("tabs"); | |
| tabs.innerHTML = ""; | |
| for (const [key, [title]] of Object.entries(TASKS)) { | |
| if (!DATA.tasks[key] || !DATA.tasks[key].items.length) continue; | |
| const b = document.createElement("button"); | |
| b.role = "tab"; b.textContent = title; | |
| b.setAttribute("aria-selected", key === current.task); | |
| b.onclick = () => { current = { task: key, i: 0 }; renderTabs(); renderPanel(); }; | |
| tabs.appendChild(b); | |
| } | |
| } | |
| function renderPanel() { | |
| const key = current.task, task = DATA.tasks[key], s = task.scores, it = task.items[current.i]; | |
| const [, question, inputs] = TASKS[key]; | |
| const right = Math.round(s.accuracy * s.n); | |
| const opts = task.items.map((x, i) => | |
| `<option value="${i}" ${i === current.i ? "selected" : ""}>${x.correct ? "✓" : "✗"} #${i + 1}${x.part ? ` [${x.part}]` : ""} ${esc(clipDesc(x.clips[0]))}</option>`).join(""); | |
| const clips = it.clips.map((c, n) => ` | |
| <figure><video controls preload="metadata" playsinline src="clips/${encodeURIComponent(c)}"></video> | |
| <figcaption>Clip ${n + 1}${it.clips.length > 1 && n === 1 ? " (real reference)" : ""}</figcaption></figure>`).join(""); | |
| document.getElementById("panel").innerHTML = ` | |
| <p class="task-q">${esc(question)}</p> | |
| <p class="task-in">${esc(inputs)}</p> | |
| <p class="note">Scores over all ${s.n} questions Gemini answered in this pilot run; ${task.items.length} shown below.</p> | |
| <div class="stats"> | |
| <div class="stat"><b>${right}/${s.n}</b><span>correct</span></div> | |
| <div class="stat"><b>${s.accuracy.toFixed(2)}</b><span>accuracy (95% CI ${s.ci95[0].toFixed(2)}–${s.ci95[1].toFixed(2)})</span></div> | |
| <div class="stat"><b>${s.balanced_accuracy.toFixed(2)}</b><span>balanced accuracy</span></div> | |
| <div class="stat"><b>${s.cohen_kappa.toFixed(2)}</b><span>Cohen's κ</span></div> | |
| </div> | |
| <div class="nav"> | |
| <button id="prev" aria-label="Previous sample">◀</button> | |
| <select id="pick" aria-label="Sample">${opts}</select> | |
| <button id="next" aria-label="Next sample">▶</button> | |
| </div> | |
| <div class="clips">${clips}</div> | |
| <div class="grid"> | |
| <div class="card"> | |
| <h3>Question</h3> | |
| <blockquote>${esc(it.question)}</blockquote> | |
| <div>Options, in the order shown: ${it.options.map(o => `<code>${esc(o)}</code>`).join(" · ")}</div> | |
| <table> | |
| <tr><th>Correct answer</th><th>Gemini answered</th><th></th></tr> | |
| <tr><td><b>${esc(it.gt_answer)}</b></td><td><b>${esc(it.pred_answer ?? "— unreadable —")}</b></td> | |
| <td class="${it.correct ? "ok" : "bad"}">${it.correct ? "✓ Correct" : "✗ Wrong"}</td></tr> | |
| </table> | |
| </div> | |
| <div class="card"> | |
| <h3>About the clip(s), hidden from the model</h3> | |
| <ul>${it.clips.map((c, n) => `<li><b>Clip ${n + 1}:</b> ${esc(clipDesc(c))}</li>`).join("")}</ul> | |
| <details><summary>Gemini's raw reply</summary><pre>${esc((it.raw || "").trim())}</pre></details> | |
| </div> | |
| </div>`; | |
| const go = d => { current.i = (current.i + d + task.items.length) % task.items.length; renderPanel(); }; | |
| document.getElementById("prev").onclick = () => go(-1); | |
| document.getElementById("next").onclick = () => go(1); | |
| document.getElementById("pick").onchange = e => { current.i = +e.target.value; renderPanel(); }; | |
| } | |
| fetch("samples.json").then(r => r.json()).then(d => { | |
| DATA = d; | |
| document.getElementById("model").textContent = d.model; | |
| current.task = Object.keys(TASKS).find(k => d.tasks[k] && d.tasks[k].items.length); | |
| renderTabs(); renderPanel(); | |
| }).catch(e => { document.getElementById("panel").textContent = "Could not load samples.json: " + e; }); | |
| </script> | |
| </body> | |
| </html> | |