rafmacalaba commited on
Commit
05c6007
·
verified ·
1 Parent(s): 676999c

index.html: landing + deep links

Browse files
Files changed (1) hide show
  1. index.html +33 -322
index.html CHANGED
@@ -1,326 +1,37 @@
1
- <!doctype html>
2
- <html lang="en"><head><meta charset="utf-8">
 
 
3
  <meta name="viewport" content="width=device-width, initial-scale=1">
4
- <title>datause-ner labeling · passage queue</title>
5
  <style>
6
- :root{
7
- --bg:#F6F4EF; --card:#FFFFFF; --line:#E7E3DD; --ink:#211D1A; --mut:#6F6A63;
8
- --primary:#4F46E5; --primary-ink:#FFFFFF;
9
- --keep:#0F7A4D; --keep-bg:#E2F5E8; --keep-line:#7BC7A2;
10
- --drop:#C2412B; --drop-bg:#FCE8E3; --drop-line:#E8A394;
11
- --conf:#B45309; --conf-bg:#FBF0DC; --conf-line:#E0B37A;
12
- --unsc:#6B7280; --unsc-bg:#F1F3F5;
13
- --mark-u:#FDF0DA; --mark-u-line:#D97706; --mark-k:#DCFCE7; --mark-k-line:#22A55B;
14
- --mark-d:#FEE2E2; --mark-d-line:#DC2626;
15
- }
16
- *{box-sizing:border-box}
17
- body{margin:0;background:var(--bg);color:var(--ink);font-family:"Fira Sans","Segoe UI",system-ui,sans-serif;font-size:15px;line-height:1.6}
18
- header{position:sticky;top:0;z-index:30;background:rgba(255,255,255,.94);backdrop-filter:blur(6px);border-bottom:1px solid var(--line)}
19
- .hrow{display:flex;gap:10px;align-items:center;flex-wrap:wrap;padding:10px 20px}
20
- header h1{font-size:14px;margin:0;letter-spacing:.2px;font-weight:800}
21
- .sub{font-size:12.5px;color:var(--mut)}
22
- #bar{height:5px;background:var(--line);border-radius:3px;margin:2px 0}
23
- #bar>i{display:block;height:100%;width:0;background:linear-gradient(90deg,var(--primary),#22A55B);border-radius:3px;transition:width .25s}
24
- main{max-width:1120px;margin:0 auto;padding:18px 20px 110px;display:grid;grid-template-columns:1fr 356px;gap:22px;align-items:start}
25
- @media(max-width:920px){main{grid-template-columns:1fr}}
26
- .card{background:var(--card);border:1px solid var(--line);border-radius:16px;box-shadow:0 1px 2px rgba(33,29,26,.05),0 10px 28px rgba(33,29,26,.05)}
27
- .passage-card{padding:16px 20px 14px}
28
- .grid-2{display:grid;grid-template-columns:auto 1fr;gap:18px}
29
- .badges{display:flex;gap:6px;align-items:center;flex-wrap:wrap;margin-bottom:10px}
30
- .badge{font-size:11px;font-weight:700;padding:3px 10px;border-radius:999px;background:var(--unsc-bg);color:var(--unsc)}
31
- .b-keep{background:var(--keep-bg);color:var(--keep)}.b-drop{background:var(--drop-bg);color:var(--drop)}
32
- .b-confusion{background:var(--conf-bg);color:var(--conf)}.b-mixed{background:#E9E6FF;color:#4338CA}.b-unscored{background:var(--unsc-bg);color:var(--unsc)}
33
- .passage{max-height:min(54vh,560px);overflow-y:auto;font-size:14.5px;line-height:1.8;padding:14px 6px;border:1px solid var(--line);border-radius:12px;background:#FDFCFA}
34
- .passage mark{cursor:pointer;border-radius:3px;padding:0 2px;font-weight:600}
35
- mark.m-u{background:var(--mark-u);border-bottom:2px solid var(--mark-u-line)}
36
- mark.m-k{background:var(--mark-k);border-bottom:2px solid var(--mark-k-line)}
37
- mark.m-d{background:var(--mark-d);border-bottom:2px solid var(--mark-d-line)}
38
- mark.on{outline:2px solid var(--primary);outline-offset:1px}
39
- mark sup{font-size:9.5px;font-weight:800;color:var(--primary);margin-left:1px}
40
- .legend{display:flex;gap:8px;align-items:center;margin-top:8px;font-size:11.5px;color:var(--mut);flex-wrap:wrap}
41
- .legend i{display:inline-block;width:20px;height:4px;border-radius:2px}
42
- .legend .u i{background:var(--mark-u);border-bottom:2px solid var(--mark-u-line)}
43
- .legend .k i{background:var(--mark-k);border-bottom:2px solid var(--mark-k-line)}
44
- .legend .d i{background:var(--mark-d);border-bottom:2px solid var(--mark-d-line)}
45
- .legend .a i{outline:2px solid var(--primary);outline-offset:1px}
46
- .chips{display:flex;gap:7px;align-items:center;overflow-x:auto;margin-top:10px;padding:7px 4px;scrollbar-width:thin}
47
- .chips::-webkit-scrollbar{height:5px}.chips::-webkit-scrollbar-thumb{background:var(--line);border-radius:3px}
48
- .chip{font:inherit;font-size:11.5px;cursor:pointer;border:1px solid var(--line);background:var(--card);color:var(--ink);border-radius:999px;padding:5px 11px;white-space:nowrap;display:inline-flex;gap:5px;align-items:center;transition:all .15s}
49
- .chip b{width:18px;height:18px;border-radius:50%;font-size:10px;display:inline-grid;place-items:center;color:#fff}
50
- .chip .dot{width:7px;height:7px;border-radius:50%;background:var(--conf-line)}
51
- .chip.r-k{background:var(--keep-bg);border-color:var(--keep-line);color:var(--keep)}
52
- .chip.r-d{background:var(--drop-bg);border-color:var(--drop-line);color:var(--drop)}
53
- .chip.on{outline:2px solid var(--primary);outline-offset:1px}
54
- .chip .sc{font-variant-numeric:tabular-nums}
55
- .panel-card{padding:16px 18px;position:sticky;top:96px;display:flex;flex-direction:column;gap:12px}
56
- .panel-label{font-size:10.5px;text-transform:uppercase;letter-spacing:.8px;color:var(--mut);font-weight:800}
57
- .surface{font-size:20px;font-weight:800;line-height:1.35;padding:10px 12px;background:#FBF8F1;border:1px solid #EADFC4;border-radius:12px}
58
- .pstate{font-size:13px;font-weight:700}
59
- .pstate.un{color:var(--conf)}.pstate.k{color:var(--keep)}.pstate.d{color:var(--drop)}
60
- .scoreline{display:flex;gap:7px;align-items:center;flex-wrap:wrap;font-size:12.5px;color:var(--mut)}
61
- .tag{font-size:11px;font-weight:800;padding:2px 9px;border-radius:999px;letter-spacing:.3px}
62
- .tag.k{background:var(--keep-bg);color:var(--keep)}.tag.d{background:var(--drop-bg);color:var(--drop)}
63
- .tag.c{background:var(--conf-bg);color:var(--conf)}.tag.u{background:var(--unsc-bg);color:var(--unsc)}
64
- .note{font:inherit;font-size:13.5px;padding:9px 12px;border:1px solid var(--line);border-radius:10px;background:#FDFCFA;width:100%}
65
- .note:focus{outline:2px solid var(--primary);outline-offset:0;border-color:var(--primary)}
66
- .hint{font-size:11.5px;color:var(--mut);line-height:1.55}
67
- kbd{font-family:inherit;font-size:10.5px;background:var(--unsc-bg);border:1px solid var(--line);border-radius:5px;padding:1px 5px}
68
- #dock{position:fixed;left:0;right:0;bottom:0;z-index:30;background:rgba(255,255,255,.97);backdrop-filter:blur(6px);border-top:1px solid var(--line);padding:11px 20px;display:flex;gap:8px;align-items:center;justify-content:center;flex-wrap:wrap}
69
- button{font:inherit;cursor:pointer;border:1px solid var(--line);background:var(--card);color:var(--ink);border-radius:12px;padding:10px 18px;font-size:13.5px;transition:all .15s}
70
- button.nav:hover{border-color:var(--primary);color:var(--primary)}
71
- button:disabled{opacity:.4;cursor:default}
72
- button.keep{background:var(--keep);border-color:var(--keep);color:#fff;font-weight:800;min-width:132px;box-shadow:0 1px 3px rgba(15,122,77,.35)}
73
- button.keep:hover{filter:brightness(1.05)}
74
- button.drop{background:var(--drop);border-color:var(--drop);color:#fff;font-weight:800;min-width:132px;box-shadow:0 1px 3px rgba(194,65,43,.35)}
75
- button.drop:hover{filter:brightness(1.05)}
76
- button.primary{background:var(--primary);border-color:var(--primary);color:#fff}
77
- select,input[type=text]{font:inherit;font-size:12.5px;padding:6px 10px;border:1px solid var(--line);border-radius:9px;background:var(--card);color:var(--ink)}
78
- .state{font-size:12.5px;color:var(--mut);min-width:180px;text-align:center;font-variant-numeric:tabular-nums}
79
- #who{width:76px}
80
- .btnrow{display:flex;gap:8px;align-items:center}
81
- .foot{text-align:center;color:var(--mut);font-size:12px;padding-top:14px}
82
- .empty{text-align:center;color:var(--mut);padding:60px 0;font-size:16px}
83
- .warn{background:#FBF0DC;border:1px solid var(--conf-line);border-radius:10px;padding:8px 12px;font-size:12.5px}
84
- </style></head><body>
85
- <header>
86
- <div class="hrow">
87
- <h1>datause-ner labeling</h1>
88
- <span class="sub" id="qname">queue</span>
89
- <span class="sub" id="prog" style="font-weight:800;color:var(--ink)"></span><span style="flex:1"></span>
90
- <label class="sub">annotator <input type="text" id="who" placeholder="initials" style="width:76px"></label>
91
- <label class="sub"><input type="checkbox" id="rev"> scores</label>
92
- <select id="fQueue" title="queue"></select>
93
- <select id="fOrigin" title="origin filter"></select>
94
- <select id="fBand" title="band filter"></select>
95
- <select id="fLuna" title="Luna verdict vs model">
96
- <option value="">luna×model: all</option><option value="agree">agree</option>
97
- <option value="conflict">conflict</option><option value="unsure">model unsure</option></select>
98
- <button class="primary" onclick="exportRulings()">⬇ Export</button>
99
- <span class="sub" id="sink"></span>
100
- </div>
101
- <div id="bar"><i></i></div>
102
- </header>
103
  <main>
104
- <div class="card passage-card">
105
- <div class="badges"><span class="badge" id="bOrigin"></span><span class="badge" id="bQueue"></span><span class="badge" id="bBand"></span><span class="badge" id="bKey"></span></div>
106
- <div id="noctx" class="warn" style="display:none">surface-only item — no passage available.</div>
107
- <div class="passage" id="passage"></div>
108
- <div class="legend">
109
- <span class="u"><i></i> unruled</span><span class="k"><i></i> kept</span><span class="d"><i></i> dropped</span><span class="a"><i></i> active</span>
110
- </div>
111
- <div class="chips" id="chips"></div>
112
- <div style="margin-top:4px"></div>
113
- </div>
114
- <div class="card panel-card">
115
- <div class="panel-label">Ruling target</div>
116
- <div class="surface" id="surface"></div>
117
- <div class="scoreline"><span id="pos"></span><span class="sc" id="sc"></span></div>
118
- <div class="pstate" id="pstate">unruled</div>
119
- <div class="btnrow">
120
- <button class="keep" id="bKeep" onclick="rule('DATA_MENTION')">✓ Keep</button>
121
- <button class="drop" id="bDrop" onclick="rule('NON_MENTION')">✕ Drop</button>
122
- </div>
123
- <input class="note" id="note" placeholder="note (optional) — saved with the ruling">
124
- <div class="hint"><kbd>k</kbd> keep · <kbd>d</kbd> drop · <kbd>←</kbd><kbd>→</kbd> mention · <kbd>↑</kbd><kbd>↓</kbd> passage · <kbd>s</kbd> skip · <kbd>z</kbd> undo</div>
125
- </div>
126
- <div class="empty" id="empty" style="display:none">Nothing matches the active filters.</div>
127
  </main>
128
- <div id="dock">
129
- <button class="nav" id="bPPrev" onclick="moveP(-1)">⏮ P</button>
130
- <button class="nav" id="bMPrev" onclick="moveM(-1)">‹ M <span id="mPos"></span></button>
131
- <button class="keep" onclick="rule('DATA_MENTION')">✓ Keep</button>
132
- <button class="drop" onclick="rule('NON_MENTION')">✕ Drop</button>
133
- <button class="nav" id="bMNext" onclick="moveM(1)"><span id="mPos2"></span> M ›</button>
134
- <button class="nav" id="bPNext" onclick="moveP(1)">P ⏭</button>
135
- <button class="nav" onclick="moveP(1)">Skip <small>(s)</small></button>
136
- <button class="nav" id="bUndo" onclick="undo()">⟲ Undo</button>
137
- <span class="state" id="state"></span>
138
- </div>
139
- <script>
140
- let Q=[], R={}, pidx=0, mi=0, lastRuled=null;
141
- const $=id=>document.getElementById(id);
142
- const LS='datause_ner_rulings_v1';
143
- try{R=JSON.parse(localStorage.getItem(LS)||'{}')}catch(e){R={}}
144
- $('who').value=localStorage.getItem('datause_ner_who')||'';
145
- $('who').oninput=()=>localStorage.setItem('datause_ner_who',$('who').value);
146
- const save=()=>localStorage.setItem(LS,JSON.stringify(R));
147
- const esc=s=>String(s??'').replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
148
- const mentionsOf=q=>Array.isArray(q.mentions)&&q.mentions.length?q.mentions:[{
149
- key:q.key,surface:q.surface,start:q.start,end:q.end,
150
- head_score:q.head_score,band:q.band}];
151
- const isFlat=q=>!Array.isArray(q.mentions);
152
- async function post(ruling){
153
- try{const r=await fetch('/api/rulings',{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify(ruling)});
154
- if(r.ok){$('sink').textContent='● server ('+(await r.json()).n+')';return}}catch(e){}
155
- $('sink').textContent='● local export';
156
- }
157
- async function load(){
158
- try{const r=await fetch('/api/queue');if(r.ok)Q=(await r.json()).items||[]}catch(e){}
159
- if(!Q.length){
160
- for(const f of ['./queue_gliner.json']){
161
- try{const r=await fetch(f);if(r.ok)Q.push(...(await r.text()).split('\n').filter(Boolean).map(JSON.parse))}catch(e){}
162
- }
163
- }
164
- fillFilters();render();
165
- }
166
- const lunaState=m=>{
167
- if(m.luna==null||m.head_score==null)return null;
168
- if(m.luna_split)return 'conflict'; // Luna itself split on the same span
169
- if(m.band==='keep')return m.luna===1?'agree':'conflict';
170
- if(m.band==='drop')return m.luna===0?'agree':'conflict';
171
- return 'unsure';
172
- };
173
- const filtered=()=>{const qu=$('fQueue').value,o=$('fOrigin').value,b=$('fBand').value,lu=$('fLuna').value;
174
- return Q.map((q,i)=>({q,i})).filter(({q})=>(!qu||q.queue===qu)&&(!o||q.origin===o)&&(!b||q.band===b)
175
- &&(!lu||mentionsOf(q).some(m=>lunaState(m)===lu)))};
176
- const totalMentions=()=>Q.reduce((n,q)=>n+(q.mentions?.length||1),0);
177
- function fillFilters(){
178
- const qs=[...new Set(Q.map(q=>q.queue))].sort();
179
- $('fQueue').innerHTML=qs.map(q=>`<option value="${esc(q)}">${esc(q)} · ${Q.filter(x=>x.queue===q).length}</option>`).join('');
180
- if(qs.includes('gliner'))$('fQueue').value='gliner';
181
- else if(qs.includes('probe_candidates/pool'))$('fQueue').value='probe_candidates/pool';
182
- else if(qs.includes('human473'))$('fQueue').value='human473';
183
- const os=[...new Set(Q.map(q=>q.origin))].sort(),bs=[...new Set(Q.map(q=>q.band))].sort();
184
- $('fOrigin').innerHTML='<option value="">all origins</option>'+os.map(o=>`<option>${esc(o)}</option>`).join('');
185
- $('fBand').innerHTML='<option value="">all bands</option>'+bs.map(b=>`<option>${esc(b)}</option>`).join('');
186
- for(const id of ['fQueue','fOrigin','fBand','fLuna']) $(id).onchange=()=>{pidx=0;mi=0;render()};
187
- $('rev').onchange=()=>render();
188
- }
189
- function bandsHTML(m){
190
- const t=m.band==='keep'?'k':m.band==='drop'?'d':m.band==='unscored'?'u':'c';
191
- return `<span class="tag ${t}">${esc(m.band||'?')}</span>`;
192
- }
193
- function contextHTML(q){
194
- const p=q.ctx||'';const ms=mentionsOf(q);
195
- const marks=[];
196
- for(let i=0;i<ms.length;i++){
197
- const m=ms[i];let s=m.start,e=m.end;
198
- if(!(Number.isInteger(s)&&Number.isInteger(e)&&e>s&&p.slice(s,e)===m.surface)){
199
- s=p.indexOf(m.surface||'');e=s<0?0:s+(m.surface||'').length;
200
- }
201
- if(s<0)continue;
202
- marks.push({s,e,i});
203
- }
204
- // char-once walk: overlapping spans never duplicate passage text; a mark
205
- // starting inside the previous one is suppressed in-text (chip remains)
206
- marks.sort((a,b)=>a.s-b.s||b.e-a.e);
207
- let out='',cur=0,suppressed=0;
208
- for(const mk of marks){
209
- if(mk.s<cur){suppressed++;continue}
210
- out+=esc(p.slice(cur,mk.s));
211
- const r=R[ms[mk.i].key];
212
- const cls=r? (r.ruling==='DATA_MENTION'?'m-k':'m-d') : 'm-u';
213
- out+=`<mark class="${cls}" data-mi="${mk.i}" onclick="clickM(${mk.i})">${esc(p.slice(mk.s,mk.e))}${ms.length>1?`<sup>${mk.i+1}</sup>`:''}</mark>`;
214
- cur=mk.e;
215
- }
216
- out+=esc(p.slice(cur));
217
- return out;
218
- }
219
- function render(){
220
- const F=filtered();
221
- const ruled=Object.keys(R).length, tm=totalMentions();
222
- $('prog').textContent=`${ruled}/${tm} ruled`;
223
- $('qname').textContent=`${$('fQueue').value||'queue'} · ${F.length}`;
224
- $('empty').style.display=F.length?'none':'block';
225
- const bar=document.querySelector('#bar>i');bar.style.width=(tm?100*ruled/tm:0)+'%';
226
- if(!F.length){$('passage').innerHTML='';$('chips').innerHTML='';$('state').textContent='adjust filters';return}
227
- pidx=Math.max(0,Math.min(pidx,F.length-1));
228
- const q=F[pidx].q, ms=mentionsOf(q);
229
- mi=Math.max(0,Math.min(mi,ms.length-1));
230
- const m=ms[mi],r=R[m.key];
231
- $('bOrigin').textContent=[q.origin,q.subset].filter(Boolean).join(' · ');
232
- const nk=ms.filter(x=>x.luna===1).length,nd=ms.filter(x=>x.luna===0).length;
233
- $('bQueue').textContent=(isFlat(q)?'single mention':`${q.mentions.length} mentions`)
234
- +(nk+nd?` · luna: ${nk}✓ / ${nd}✕`:'');
235
- const bb=$('bBand');bb.textContent=`tag: ${q.band}`;bb.className='badge b-'+(q.band||'unscored');
236
- $('bKey').textContent=q.mentions?m.key:q.key;
237
- $('noctx').style.display=q.ctx?'none':'block';
238
- $('passage').innerHTML=q.ctx?contextHTML(q):`<div class="empty">${esc(m.surface||'(no context)')}</div>`;
239
- for(const el of $('passage').querySelectorAll('mark[data-mi]')){
240
- if(el.dataset.mi===String(mi))el.classList.add('on');
241
- else el.classList.remove('on');
242
- }
243
- $('passage').style.display='';
244
- const act=$('passage').querySelector('mark.on');
245
- if(act)act.scrollIntoView({block:'center',behavior:'smooth'});
246
- $('chips').innerHTML=ms.map((mm,i)=>{
247
- const rr=R[mm.key];
248
- const cls=rr?(rr.ruling==='DATA_MENTION'?'r-k':'r-d'):'';
249
- const on=i===mi?'on':'';
250
- const dot=mm.luna_split?'var(--conf)':mm.luna===1?'var(--keep)':mm.luna===0?'var(--drop)':'var(--conf-line)';
251
- return `<button class="chip ${cls} ${on}" data-ma="${i}" onclick="clickM(${i})"><b>${i+1}</b><span class="dot" style="background:${dot}"></span>${esc(mm.surface)}</button>`;
252
- }).join('');
253
- const actChip=$('chips').querySelector('button.on');
254
- if(actChip)actChip.scrollIntoView({inline:'center',behavior:'smooth',block:'nearest'});
255
- const sc=$('rev').checked?(m.head_score==null?'n/a':(+m.head_score).toFixed(3)):'•••';
256
- $('surface').textContent=m.surface||'(no surface)';
257
- $('pos').textContent=`M ${mi+1}/${ms.length}`;
258
- $('sc').innerHTML=bandsHTML(m)
259
- +(m.luna_split?`<span class="tag c">Luna split ✓/✕</span>`
260
- :m.luna!=null?`<span class="tag ${m.luna===1?'k':'d'}">Luna ${m.luna===1?'✓ kept':'✕ dropped'}</span>`:'')
261
- +`<span class="sc">probe ${sc}</span>`
262
- +(m.extractor_score!=null?`<span class="sc">ext ${(+m.extractor_score).toFixed(2)}</span>`:'');
263
- const st=$('pstate');
264
- if(r){st.textContent=(r.ruling==='DATA_MENTION'?'✓ kept':'✕ dropped')+' — your ruling';st.className='pstate '+(r.ruling==='DATA_MENTION'?'k':'d')}
265
- else{st.textContent='unruled — model tag above is a hint, you decide';st.className='pstate un'}
266
- $('note').value=r?.note||'';
267
- $('state').textContent=`P ${pidx+1}/${F.length} · M ${mi+1}/${ms.length}${r?' · ruled':''}`;
268
- $('mPos').textContent=`${mi+1}/${ms.length}`;$('mPos2').textContent=`${mi+1}/${ms.length}`;
269
- $('bPPrev').disabled=pidx===0;$('bPNext').disabled=pidx>=F.length-1;
270
- $('bMPrev').disabled=mi===0&&pidx===0;$('bMNext').disabled=mi>=ms.length-1&&pidx>=F.length-1;
271
- $('bUndo').disabled=!lastRuled;
272
- }
273
- function clickM(i){mi=i;render()}
274
- function moveM(d){
275
- const F=filtered();if(!F.length)return;
276
- const ms=mentionsOf(F[pidx].q);
277
- if(d>0){if(mi<ms.length-1)mi++;else if(pidx<F.length-1){pidx++;mi=0} }
278
- else{if(mi>0)mi--;else if(pidx>0){pidx--;mi=mentionsOf(F[pidx].q).length-1} }
279
- render();
280
- }
281
- function moveP(d){
282
- const F=filtered();if(!F.length)return;
283
- pidx=Math.max(0,Math.min(pidx+d,F.length-1));mi=0;render();
284
- }
285
- async function rule(v){
286
- const F=filtered();if(!F.length)return;
287
- const q=F[pidx].q,ms=mentionsOf(q),m=ms[mi];
288
- R[m.key]={key:m.key,ruling:v,annotator:$('who').value||'anon',ts:new Date().toISOString(),
289
- note:$('note').value||'',head_score:m.head_score,origin:q.origin,surface:m.surface,queue:q.queue};
290
- lastRuled=m.key;save();post(R[m.key]);
291
- if(mi<ms.length-1)mi++;
292
- else if(pidx<F.length-1){pidx++;mi=0}
293
- render();
294
- }
295
- function undo(){
296
- if(!lastRuled)return;
297
- const F=filtered();
298
- let found=false;
299
- for(let fi=0;fi<F.length;fi++){
300
- const ms=mentionsOf(F[fi].q);
301
- for(let j=0;j<ms.length;j++){
302
- if(ms[j].key===lastRuled){pidx=fi;mi=j;found=true;break}
303
- }
304
- if(found)break;
305
- }
306
- delete R[lastRuled];lastRuled=null;save();
307
- if(!found){$('state').textContent='ruled item outside current filters';return}
308
- render();
309
- }
310
- function exportRulings(){
311
- const blob=new Blob([JSON.stringify({items:Object.values(R)},null,1)],{type:'application/json'});
312
- const a=document.createElement('a');a.href=URL.createObjectURL(blob);
313
- a.download=`rulings-${$('who').value||'anon'}-export.json`;a.click();
314
- }
315
- document.addEventListener('keydown',e=>{
316
- if(/INPUT|SELECT|TEXTAREA/.test(e.target.tagName))return;
317
- if(e.key==='k')rule('DATA_MENTION');
318
- else if(e.key==='d')rule('NON_MENTION');
319
- else if(e.key==='ArrowRight')moveM(1);
320
- else if(e.key==='ArrowLeft')moveM(-1);
321
- else if(e.key==='ArrowDown'||e.key==='s')moveP(1);
322
- else if(e.key==='ArrowUp')moveP(-1);
323
- else if(e.key==='z')undo();
324
- });
325
- load();
326
- </script></body></html>
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>data-use annotation</title>
7
  <style>
8
+ :root { --bg: #f6f8fa; --card: #fff; --fg: #1f2328; --mut: #59636e; --line: #d0d7de; --hl: #0969da; }
9
+ body { background: var(--bg); color: var(--fg); font: 16px/1.6 -apple-system, system-ui, sans-serif; margin: 0; }
10
+ main { max-width: 640px; margin: 0 auto; padding: 60px 20px; text-align: center; }
11
+ h1 { font-size: 24px; margin-bottom: 6px; }
12
+ p { color: var(--mut); }
13
+ .grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin: 28px 0; }
14
+ a.user { background: var(--card); border: 2px solid var(--line); border-radius: 12px; padding: 22px 10px; font-size: 19px; font-weight: 700; color: var(--fg); text-decoration: none; }
15
+ a.user:hover { border-color: var(--hl); }
16
+ a.user span { display: block; font-size: 13px; font-weight: 400; color: var(--mut); margin-top: 4px; }
17
+ .links a { color: var(--mut); font-size: 14px; margin: 0 10px; }
18
+ </style>
19
+ </head>
20
+ <body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
21
  <main>
22
+ <h1>data-use annotation</h1>
23
+ <p>keep / drop data mentions · pick your name to load your 240 passages</p>
24
+ <div class="grid">
25
+ <a class="user" href="./review.html?user=paddy">paddy<span>240 passages</span></a>
26
+ <a class="user" href="./review.html?user=aj">aj<span>240 passages</span></a>
27
+ <a class="user" href="./review.html?user=aivin">aivin<span>240 passages</span></a>
28
+ <a class="user" href="./review.html?user=rafael">rafael<span>240 passages</span></a>
29
+ </div>
30
+ <div class="links">
31
+ <a href="./review.html?ds=sample">sample · 60</a>
32
+ <a href="./review.html?ds=gold">gold · 473</a>
33
+ <a href="./legacy-index.html">legacy UI</a>
34
+ </div>
 
 
 
 
 
 
 
 
 
 
35
  </main>
36
+ </body>
37
+ </html>