File size: 7,643 Bytes
a9d7edb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
b74e85b
a9d7edb
 
 
 
b74e85b
 
 
a9d7edb
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
edb73aa
a9d7edb
edb73aa
a9d7edb
edb73aa
 
 
a9d7edb
 
 
 
 
 
b74e85b
a9d7edb
 
 
 
b74e85b
a9d7edb
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Image preference study</title>
<style>
  :root { --bg:#0f1115; --fg:#e8e8ea; --mut:#9aa0a6; --acc:#4f8cff; --card:#1a1d23; --line:#262a31; }
  * { box-sizing:border-box; }
  body { margin:0; background:var(--bg); color:var(--fg); font:16px/1.5 system-ui,-apple-system,Segoe UI,Roboto,sans-serif; }
  .wrap { max-width:1000px; margin:0 auto; padding:20px 14px 72px; }
  h1 { font-size:22px; margin:0 0 8px; } .mut { color:var(--mut); } .center { text-align:center; }
  .card { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:20px; margin-top:16px; }
  input[type=email] { width:100%; padding:12px; border-radius:8px; border:1px solid #333; background:#0c0e12; color:var(--fg); font-size:16px; }
  button.primary { cursor:pointer; border:0; border-radius:8px; padding:11px 16px; font-size:15px; font-weight:600; background:var(--acc); color:#fff; }
  button.primary:disabled { opacity:.4; cursor:default; }
  .bar { height:6px; background:var(--line); border-radius:6px; overflow:hidden; margin:8px 0; }
  .bar > div { height:100%; background:var(--acc); width:0; transition:width .2s; }
  .prompt { text-align:center; font-size:18px; margin:6px 0 6px; }
  .hint { text-align:center; color:var(--mut); margin:0 0 14px; font-size:14px; }
  .pair { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
  .opt { background:#0c0e12; border:2px solid var(--line); border-radius:12px; padding:8px; text-align:center; cursor:pointer; transition:border-color .1s, transform .05s; }
  .opt:hover { border-color:var(--acc); } .opt:active { transform:scale(.99); }
  .opt img { width:100%; border-radius:8px; display:block; }
  .opt .tag { margin-top:6px; font-weight:700; color:var(--mut); letter-spacing:.04em; font-size:13px; }
  .hide { display:none; } .err { color:#ff7a7a; margin-top:10px; }
  table { width:100%; border-collapse:collapse; margin-top:10px; font-size:15px; }
  th,td { text-align:left; padding:6px; border-bottom:1px solid var(--line); }
  @media (max-width:560px){ .pair{ grid-template-columns:1fr; } }
</style>
</head>
<body>
<div class="wrap">
  <h1>Image preference study</h1>

  <div id="intro" class="card">
    <p>Thank you for helping! You'll see <b>pairs of AI-generated images</b> made from the same text prompt.
       For each pair, just <b>click the image you prefer</b> (better overall — more appealing and faithful to the prompt).</p>
    <p class="mut">About <span id="npairs"></span> quick clicks, ~<span id="mins"></span> min. Your email is only used so each person answers once; it is not shared or published.</p>
    <p><input id="email" type="email" placeholder="you@example.com" autocomplete="email"></p>
    <p><label style="cursor:pointer"><input type="checkbox" id="optrec" checked style="vertical-align:middle"> Allow my anonymized responses to be recorded for research.</label></p>
    <p><button class="primary" id="start">Start</button></p>
    <div id="introErr" class="err"></div>
  </div>

  <div id="study" class="card hide">
    <div class="mut"><span id="pos">1</span> / <span id="total">0</span></div>
    <div class="bar"><div id="prog"></div></div>
    <div class="prompt"><span id="prompt"></span></div>
    <p class="hint">Click the image you prefer — or “Cannot decide” if they look equally good.</p>
    <div class="pair">
      <div class="opt" id="optL"><img id="imgL"><div class="tag">IMAGE 1</div></div>
      <div class="opt" id="optR"><img id="imgR"><div class="tag">IMAGE 2</div></div>
    </div>
    <div class="center" style="margin-top:14px">
      <button class="primary" id="optC" style="background:#3a3f48">Cannot decide</button>
    </div>
  </div>

  <div id="done" class="card hide">
    <h1 class="center">All done — thank you! 🎉</h1>
    <p id="recnote" class="mut center"></p>
    <div id="myresults"></div>
    <p class="mut center" style="margin-top:16px">You may close this tab.</p>
  </div>
</div>

<script>
let token=null, pairs=[], i=0, votes=[], busy=false;
const $ = id => document.getElementById(id);

async function start(){
  $("introErr").textContent=""; $("start").disabled=true;
  try{
    const r = await fetch("/api/register",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:$("email").value, record:$("optrec").checked})});
    const d = await r.json();
    if(!r.ok){ $("introErr").textContent=d.detail||"Could not start."; $("start").disabled=false; return; }
    token=d.token; pairs=d.pairs; i=0; votes=[];
    $("total").textContent=pairs.length;
    $("intro").classList.add("hide"); $("study").classList.remove("hide");
    render();
  }catch(e){ $("introErr").textContent="Network error."; $("start").disabled=false; }
}
function render(){
  const p=pairs[i];
  $("pos").textContent=i+1; $("prog").style.width=(100*i/pairs.length)+"%";
  $("prompt").textContent=p.prompt; $("imgL").src=p.left; $("imgR").src=p.right;
  window.scrollTo(0,0);
}
async function choose(side){
  if(busy) return;
  votes.push({pair_id:pairs[i].pair_id, choice:side});
  i++;
  if(i<pairs.length){ render(); return; }
  busy=true; $("prog").style.width="100%";
  let data;
  try{
    const r = await fetch("/api/submit",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({token,votes})});
    data = await r.json();
    if(!r.ok){ alert(data.detail||"Submit failed"); i--; votes.pop(); busy=false; return; }
  }catch(e){ alert("Network error on submit; please retry."); i--; votes.pop(); busy=false; return; }
  $("recnote").textContent = data.recorded
    ? "Your anonymized responses have been recorded — thank you for contributing!"
    : "Per your choice, your responses were NOT recorded. Here is your personal summary anyway:";
  renderResults(data.results);
  $("study").classList.add("hide"); $("done").classList.remove("hide");
}
const STYLE_LBL = {photo:"Photo", anime:"Anime", "concept-art":"Concept art", paintings:"Paintings"};
function renderResults(res){
  const p=res.pref, pct=x=> x==null?"—":Math.round(100*x)+"%";
  const ks=Object.keys(p.by_style);
  let rows="";
  for(const s of ks){
    const ps=p.by_style[s]||{};
    rows+=`<tr><td>${STYLE_LBL[s]||s}</td><td>${pct(ps.win_rate)}</td><td>${pct(ps.tie_rate)}</td><td>${pct(ps.lose_rate)}</td></tr>`;
  }
  const table = ks.length>1 ? `<table><tr class="mut"><th>Style</th><th>Ours</th><th>No pref.</th><th>Other</th></tr>${rows}</table>` : "";
  $("myresults").innerHTML = `
    <p class="center">Over your <b>${p.n}</b> comparisons: you preferred <b>ours ${pct(p.win_rate)}</b>,
      had <b>no preference ${pct(p.tie_rate)}</b>, and preferred <b>the other ${pct(p.lose_rate)}</b>
      <span class="mut">(the method identity was hidden).</span></p>${table}`;
}
fetch("/api/info").then(r=>r.json()).then(d=>{ $("npairs").textContent=d.n_pairs; $("mins").textContent=d.est_minutes; }).catch(()=>{ $("npairs").textContent="a few dozen"; });
$("start").onclick=start;
$("email").addEventListener("keydown",e=>{ if(e.key==="Enter") start(); });
$("optL").onclick=()=>choose("left");
$("optR").onclick=()=>choose("right");
$("optC").onclick=()=>choose("cannot");
document.addEventListener("keydown",e=>{
  if($("study").classList.contains("hide")) return;
  if(e.key==="ArrowLeft"||e.key==="a"||e.key==="A") choose("left");
  if(e.key==="ArrowRight"||e.key==="b"||e.key==="B") choose("right");
  if(e.key==="c"||e.key==="C"||e.key==="ArrowDown"||e.key===" ") { e.preventDefault(); choose("cannot"); }
});
</script>
</body>
</html>