""" Dialectical Intuitions — judge intuitions one at a time, Qwen3-8B writes and rewrites an answer that expresses the held ones without restating them. Frontend is a single custom HTML/JS interface (gr.HTML + head script). Each judgment posts the full commitment state to /gradio_api/call/generate; the prompt shows the model its old commitments, the new or changed one, and its previous answer, and asks for a minimal revision. """ import spaces # MUST be imported before anything initializes CUDA import json import os import re from threading import Thread import torch from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer from huggingface_hub import snapshot_download import gradio as gr MODEL_ID = "Qwen/Qwen3-8B" HF_TOKEN = os.environ.get("HF_TOKEN") print("warming weight cache ...") snapshot_download(MODEL_ID, token=HF_TOKEN) TOK = AutoTokenizer.from_pretrained(MODEL_ID, token=HF_TOKEN) if TOK.pad_token is None: TOK.pad_token = TOK.eos_token MODEL = None def _ensure_model(): """Load lazily INSIDE the GPU context (ZeroGPU has no GPU at import).""" global MODEL if MODEL is None: m = AutoModelForCausalLM.from_pretrained( MODEL_ID, torch_dtype=torch.bfloat16, token=HF_TOKEN).to("cuda") m.eval() MODEL = m return MODEL # ------------------------------------------------------------------ prompt STANCE = {1: "held", 0: "undecided", -1: "rejected"} def build_prompt(p): kind = p.get("kind", "question") items = p.get("items", []) ev = p.get("event") or {} prev = (p.get("prev") or "").strip() hold = [f"- {it['s']} {it['t']}." for it in items if it["j"] == 1] rej = [f"- {it['s']} {it['t']}." for it in items if it["j"] == -1] und = [f"- {it['s']} {it['t']}." for it in items if it["j"] == 0] L = [] if kind == "task": L.append(f"A user asks you: {p['q']}") else: L.append(f"Question: {p['q']}") if hold: L += ["", "Convictions you hold:"] + hold if rej: L += ["", "Descriptions you DENY — you consider each of these false:"] + rej if und: L += ["", "You are genuinely undecided about:"] + und if prev and ev.get("type") == "add": L += ["", f"Your previous answer:\n{prev}", "", f"You just formed one NEW stance ({STANCE.get(ev.get('stance'), 'held')}): " f"{ev.get('text', '')}", "Revise your previous answer so it also expresses what lies behind this, " "changing only what must change."] elif prev and ev.get("type") == "change": L += ["", f"Your previous answer:\n{prev}", "", f"You CHANGED YOUR MIND about one stance: {ev.get('text', '')} " f"It was {STANCE.get(ev.get('old'), 'held')}; it is now " f"{STANCE.get(ev.get('stance'), 'held')}.", "Revise your previous answer accordingly, changing only what must change."] elif prev: L += ["", f"Your previous answer:\n{prev}", "", "Some of your stances have changed since you wrote that answer. Revise it " "so it expresses the stances above as they now stand, changing only what " "must change."] if kind == "task": L += ["", "Write the email itself, about 110 words, and nothing else. The stances " "above are your convictions about how such an email should and should not " "be written; the email must embody the held ones and must not do what you " "reject. Never mention the stances. Think briefly."] else: L += ["", "Write your answer, about 110 words. Express the outlook that lies " "behind your convictions as one coherent, committed view. The answer must " "be CONSISTENT with every conviction, but it does not need to mention any " "of them explicitly — never restate, quote, or list them; a reader should " "be able to guess them from the view alone. Never assert anything you " "deny: you believe those descriptions are wrong, and where it matters " "your view should quietly imply the opposite. Stay unsettled only on the " "undecided points; on everything else commit — no both-sides closing, no " "calls for further dialogue. Think briefly. Output the answer only."] return "\n".join(L) def _split(gen, done): """Partition streamed text into thinking / answer phases.""" if "" in gen: head, _, tail = gen.partition("") return {"phase": "done" if done else "answer", "think": head.replace("", "").strip(), "answer": tail.strip()} body = gen.replace("", "").strip() if done: # never closed the think block: treat the text as the answer if there # was no at all, otherwise surface the truncated thinking if "" in gen: return {"phase": "done", "think": body, "answer": ""} return {"phase": "done", "think": "", "answer": body} return {"phase": "thinking", "think": body, "answer": ""} @spaces.GPU(duration=60) def _gpu_generate(payload): p = json.loads(payload) model = _ensure_model() msgs = [{"role": "user", "content": build_prompt(p)}] text = TOK.apply_chat_template( msgs, tokenize=False, add_generation_prompt=True, enable_thinking=True) ids = TOK(text, return_tensors="pt").to("cuda") streamer = TextIteratorStreamer(TOK, skip_prompt=True, skip_special_tokens=True) th = Thread(target=model.generate, kwargs=dict( **ids, max_new_tokens=768, do_sample=True, temperature=0.6, top_p=0.95, top_k=20, pad_token_id=TOK.pad_token_id, streamer=streamer)) th.start() acc, n = "", 0 for tok in streamer: acc += tok n += 1 if n % 6 == 0: yield json.dumps(_split(acc, False)) th.join() yield json.dumps(_split(acc, True)) from collections import OrderedDict STATE = OrderedDict() def generate(payload): """Plain generator gradio can introspect; the GPU work happens inside. Each chunk is mirrored into STATE (web process) so the peek side-channel can serve live progress even when DOM streaming does not.""" try: jid = json.loads(payload).get("jid") or "" except Exception: jid = "" for chunk in _gpu_generate(payload): if jid: try: c = json.loads(chunk) c["jid"] = jid chunk = json.dumps(c) except Exception: pass STATE[jid] = chunk while len(STATE) > 40: STATE.popitem(last=False) yield chunk def peek(jid): return STATE.get(jid, "") @spaces.GPU(duration=30) def _gpu_score(payload): p = json.loads(payload) items = p.get("items", []) lines = "\n".join(f"{i+1}. [{STANCE.get(it['j'], 'held')}] {it['s']} {it['t']}." for i, it in enumerate(items)) prompt = (f"An answer to the question \"{p.get('q','')}\" is below.\n\n" f"ANSWER:\n{p.get('answer','')}\n\n" "Rate each numbered stance from 1 to 5 for how well the answer satisfies it.\n" "- held: 5 = the answer fully expresses the outlook behind it, 1 = it ignores or cuts against it.\n" "- rejected: 5 = the answer avoids it entirely or implies its opposite, 1 = the answer asserts it.\n" "- undecided: 5 = the answer takes a clear position on it, 1 = it is silent.\n\n" f"{lines}\n\nReply with ONE line only:\nRATINGS: 1: 2: ...") model = _ensure_model() msgs = [{"role": "user", "content": prompt}] text = TOK.apply_chat_template( msgs, tokenize=False, add_generation_prompt=True, enable_thinking=False) ids = TOK(text, return_tensors="pt").to("cuda") with torch.no_grad(): out = model.generate(**ids, max_new_tokens=16 + 6 * len(items), do_sample=False, pad_token_id=TOK.pad_token_id) gen = TOK.decode(out[0][ids["input_ids"].shape[1]:], skip_special_tokens=True) ratings = {} for m in re.finditer(r"(\d+)\s*[:=]\s*([1-5])", gen): ratings[m.group(1)] = int(m.group(2)) return json.dumps({"ratings": ratings, "jid": p.get("jid", "")}) def score(payload): return _gpu_score(payload) # ------------------------------------------------------------------ frontend UI_HTML = """

.

""" UI_CSS = """ #ivx{--pg:#fbfbfd;--card:#ffffff;--ink:#232329;--dim:#8f8f99;--line:#e9e9ef; --up:#5f977c;--upbg:#e9f2ec;--dn:#bd7c89;--dnbg:#f6eaed; --lav:#7c79b8;--lavbg:#efeef8;--amb:#c9a221;--shadow:0 2px 6px rgba(35,35,50,.05),0 14px 36px rgba(124,121,184,.12); font-family:ui-sans-serif,-apple-system,BlinkMacSystemFont,"Segoe UI","Helvetica Neue",Arial,sans-serif; font-size:14.5px;line-height:1.65;color:var(--ink);background:var(--pg); max-width:960px;margin:0 auto;padding:40px 24px 80px;border-radius:12px} @media(prefers-color-scheme:dark){#ivx{--pg:#131316;--card:#1a1a1f;--ink:#e7e7ec;--dim:#84848e; --line:#27272e;--up:#8bc3a6;--upbg:#1a2420;--dn:#d4a1ac;--dnbg:#241b1e; --lav:#a3a0d6;--lavbg:#232238;--amb:#d9b86a;--shadow:0 2px 6px rgba(0,0,0,.35),0 14px 36px rgba(0,0,0,.35)}} #ivx *{box-sizing:border-box} #ivx .tabs{display:flex;gap:20px;margin-bottom:32px} #ivx .tab{font:inherit;font-size:12px;letter-spacing:.07em;background:none;border:none; color:var(--dim);cursor:pointer;padding:0 0 4px;border-bottom:1.5px solid transparent} #ivx .tab.on{color:var(--ink);border-bottom-color:var(--lav)} #ivx .ver{margin-left:auto;font-size:10px;color:var(--dim);letter-spacing:.08em;align-self:center} #ivx .q{margin:0 0 32px;color:var(--dim);max-width:66ch} #ivx .cols{display:grid;grid-template-columns:minmax(0,5fr) minmax(0,7fr);gap:44px;align-items:start} @media(max-width:720px){#ivx .cols{grid-template-columns:1fr;gap:28px}} #ivx .rail ul{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:10px} #ivx .rail li{font-size:12.5px;line-height:1.55;padding:10px 13px;border-radius:7px;display:block} #ivx .rrow{display:flex;gap:8px;align-items:flex-start;justify-content:space-between} #ivx .bar{position:relative;height:3px;border-radius:2px;margin-top:8px} #ivx .bar::before{content:"";position:absolute;left:0;right:0;top:0;bottom:0; background:currentColor;opacity:.16;border-radius:2px} #ivx .bar i{position:absolute;left:0;top:0;bottom:0; border-radius:2px;transition:width .6s ease,background .6s ease} #ivx .rail li b{font-weight:600} #ivx .re{cursor:text;border-radius:3px} #ivx .re:hover{background:rgba(124,121,184,.12)} #ivx .re[contenteditable="true"]{background:rgba(124,121,184,.16);outline:none} #ivx .rail li.y{background:var(--upbg);color:var(--up)} #ivx .rail li.n{background:var(--dnbg);color:var(--dn)} #ivx .rail li.p{background:none;border:1px dashed var(--line);color:var(--dim)} #ivx .rail li.p b{font-weight:500} #ivx .rev{display:flex;gap:3px;flex:none;margin-top:1px} #ivx .rv{font:inherit;font-size:10.5px;font-weight:700;line-height:17px;width:18px;height:18px; border-radius:50%;border:none;background:none;cursor:pointer;opacity:.35;padding:0;text-align:center} #ivx .rv:hover{opacity:.8} #ivx .rv-y{color:var(--up)} #ivx .rv-p{color:var(--dim)} #ivx .rv-n{color:var(--dn)} #ivx .rv.cur{opacity:1;color:#fff} #ivx .rv-y.cur{background:var(--up)} #ivx .rv-p.cur{background:var(--dim)} #ivx .rv-n.cur{background:var(--dn)} #ivx .status{display:inline-block;font-size:10.5px;letter-spacing:.1em;text-transform:uppercase; color:var(--lav);background:var(--lavbg);border-radius:999px;padding:4px 12px;margin-bottom:14px} #ivx .status[hidden]{display:none} #ivx .status::after{content:"";display:inline-block;width:1.1em;text-align:left; animation:ivxdots 1.2s steps(4,end) infinite} @keyframes ivxdots{0%{content:""}25%{content:"."}50%{content:".."}75%{content:"..."}} #ivx .status.write{color:var(--up);background:var(--upbg)} #ivx .card{position:relative;background:var(--card);border:1px solid var(--line);border-radius:14px; padding:28px 30px 26px;box-shadow:var(--shadow);margin-bottom:20px} #ivx .stmt{font-size:17.5px;line-height:1.75;margin:0;min-height:96px} #ivx #sit{cursor:text;border-radius:4px;padding:.05em .1em;margin:-.05em -.1em;transition:background .15s} #ivx #sit:hover{background:var(--lavbg)} #ivx #sit[contenteditable="true"]{background:var(--lavbg);outline:none} #ivx #thick{font-weight:600;color:var(--lav);background:var(--lavbg);border-radius:5px; padding:.08em .4em;cursor:pointer;transition:filter .15s} #ivx #thick:hover,#ivx #thick.open{filter:brightness(.96)} #ivx #thick::after{content:"\\2304";font-size:.72em;opacity:.55;margin-left:.4em;font-weight:400} #ivx #thick[contenteditable="true"]{cursor:text;outline:none} #ivx #thick[contenteditable="true"]::after{content:""} #ivx #pop{position:absolute;left:30px;right:30px;top:calc(100% - 12px);z-index:3; background:var(--card);border:1px solid var(--line);border-radius:11px; box-shadow:var(--shadow);padding:9px} #ivx .plabel{display:block;font-size:10px;letter-spacing:.13em;text-transform:uppercase; color:var(--dim);padding:4px 10px 7px} #ivx .popt{display:block;width:100%;text-align:left;font:inherit;font-size:13.5px;line-height:1.5; background:none;border:none;border-radius:7px;padding:8px 10px;color:var(--ink);cursor:pointer} #ivx .popt:hover{background:var(--lavbg);color:var(--lav)} #ivx .popt.ped{color:var(--dim);font-size:12px} #ivx .acts{display:flex;gap:10px} #ivx .genbtn{font:inherit;font-size:13px;font-weight:600;letter-spacing:.02em; margin-top:18px;padding:11px 22px;border:none;border-radius:999px;cursor:pointer; background:var(--lav);color:#fff;transition:filter .15s} #ivx .genbtn:hover{filter:brightness(1.08)} #ivx .genbtn:disabled{background:var(--lavbg);color:var(--dim);cursor:default} #ivx .genbtn[hidden]{display:none} #ivx .act{font:inherit;font-size:12.5px;padding:9px 18px;background:none;border:1px solid var(--line); border-radius:8px;color:var(--dim);cursor:pointer;transition:all .15s} #ivx .act:hover{color:var(--ink);border-color:var(--dim)} #ivx .act.up:hover{color:var(--up);border-color:var(--up);background:var(--upbg)} #ivx .act.dn:hover{color:var(--dn);border-color:var(--dn);background:var(--dnbg)} #ivx .ansrule{height:1px;background:var(--line);margin:36px 0 20px} #ivx #thinkbox{margin:0 0 16px;font-size:12px;color:var(--dim)} #ivx #thinkbox summary{cursor:pointer;letter-spacing:.08em;text-transform:uppercase;font-size:10.5px} #ivx #think{white-space:pre-wrap;margin-top:8px;line-height:1.6;max-width:60ch} #ivx .ans{margin:0;font-size:15px;line-height:1.8;max-width:58ch;transition:opacity .25s;min-height:1.8em} #ivx .ans.writing{animation:ivxpulse 1.2s ease-in-out infinite} @keyframes ivxpulse{50%{opacity:.55}} #ivx .err{color:var(--dn)} #ivx button:focus-visible{outline:2px solid var(--lav);outline-offset:2px} @media(prefers-reduced-motion:reduce){#ivx .ans{animation:none;transition:none} #ivx .status::after{animation:none;content:"…"}} """ UI_JS = r""" (function(){ "use strict"; function boot(){ if(!document.getElementById("ivx")){setTimeout(boot,120);return} init(); } function init(){ var EX=[ { kind:"question", q:"Should trans women be allowed to play in women’s sports?", cards:[ {s:"A federation that publishes the exact hormone limits a trans woman must meet before competing, for anyone to read, is",t:"giving athletes the fair warning they are owed", alt:["honest governance","a rulebook that still misses the point"]}, {s:"A federation that keeps an athlete’s hormone test results private, instead of letting them reach the press, is",t:"showing her basic respect", alt:["her right as a patient","secrecy where openness is owed"]}, {s:"A players-only vote in an amateur league on whether trans women may join is",t:"nobody else’s business", alt:["how amateur sport should work","a popularity contest deciding someone’s rights"]}, {s:"A runner who publicly doubts a trans competitor’s eligibility, instead of whispering about it, is",t:"doing something honorable", alt:["braver than the whisperers","a public attack dressed up as candor"]}, {s:"A newspaper that covers a trans woman’s win like any other win is",t:"treating her as what she is: an athlete", alt:["plain fairness","ignoring the story everyone is asking about"]}, {s:"A champion who shakes her trans opponent’s hand after every other competitor avoided her is",t:"showing simple decency", alt:["what sport is supposed to look like","a photo opportunity"]}, {s:"Standing up for your trans teammate when strangers attack her online is",t:"what being a teammate means", alt:["basic loyalty","picking a side before the facts are in"]}, {s:"A scientist who admits we don’t yet know how much of the strength advantage from male puberty survives transition is",t:"more honest than anyone who claims to know for sure", alt:["the only honest voice in the argument","an excuse to never decide anything"]} ] }, { kind:"question", q:"Should trans women be allowed to play in women’s sports?", cards:[ {s:"An answer that never says whether it was fair for someone who went through male puberty to win a women’s Olympic final is",t:"dodging the question everyone came to ask", alt:["cowardly","wisely staying out of a trap"]}, {s:"An answer that spends all its length on how each side feels and none on what should actually happen is",t:"a shrug dressed up as wisdom", alt:["empty","the only honest response to a hard question"]}, {s:"An answer that talks about “complex issues\u201d without naming one concrete situation is",t:"hiding in fog", alt:["saying nothing at all","properly careful"]}, {s:"An answer that treats trans athletes as a problem to manage, never as people being discussed, is",t:"quietly cruel", alt:["dehumanizing","just keeping the analysis clean"]}, {s:"An answer that a supporter and an opponent of trans inclusion could both read out, word for word, is",t:"not an answer", alt:["a waste of everyone’s time","proof of fair-mindedness"]}, {s:"An answer that admits which of its commitments cost the most is",t:"more honest than one pretending they all fit easily", alt:["showing its work","undermining itself"]}, {s:"An answer that says plainly what should happen next, knowing some readers will hate it, is",t:"doing what answers are for", alt:["brave where it counts","confusing stubbornness for clarity"]}, {s:"An answer that states the best case for the side it rejects before rejecting it is",t:"arguing with honor", alt:["fighting fair","wasting words on a view it never held"]}, {s:"An answer that takes one real situation and settles it completely is",t:"worth more than a page of principles", alt:["proof the view can actually decide something","mistaking an example for an argument"]} ] }, { kind:"task", q:"“I manage a team of five. For four months they’ve been building a customer dashboard. Yesterday the VP killed it, mainly because he changed his mind about priorities. I have to email the team. Write it for me.”", cards:[ {s:"Delivering the cancellation in the very first line is",t:"cold", alt:["brutal","respecting people enough to be direct"]}, {s:"Getting the bad news into the first sentence is",t:"respecting people too much to make them hunt for it", alt:["treating them like adults","ripping off a bandage that needed more care"]}, {s:"A three-line email about four months of nights and weekends is",t:"dismissive", alt:["an insult to the work","mercifully short"]}, {s:"An email short enough to read in one breath on a bad day is",t:"a kindness", alt:["respect for people’s attention","too tidy for what happened"]}, {s:"Writing “priorities have shifted” when one executive changed his mind is",t:"dishonest", alt:["corporate spin","the diplomatic truth"]}, {s:"Naming the VP as the one who killed the project is",t:"passing the buck", alt:["throwing him under the bus","just telling them who decided"]}, {s:"Telling the team exactly who decided and why is",t:"treating them like adults", alt:["the transparency they’ve earned","setting up a villain"]}, {s:"Praising one specific thing the team built, by name, is",t:"the only praise that costs something", alt:["worth ten thank-yous","salt in the wound today"]}, {s:"Telling the team their work “will be useful later” when it won’t be is",t:"a comforting lie", alt:["false hope","optimism they’ve earned"]}, {s:"Admitting you don’t yet know what happens to the team is",t:"more reassuring than pretending", alt:["honesty they can build on","fuel for a week of panic"]}, {s:"Closing with the exact day and time you’ll meet to talk about what’s next is",t:"a promise instead of a platitude", alt:["the only honest comfort available","pressure nobody needs this week"]} ] } ]; var cur=0,i=0,J={},prev="",busy=false,dirty=false,BARS={},epoch=0,LIS={}; function callScore(p){ return new Promise(function(resolve,reject){ var ta=document.querySelector("#ivx-in-s textarea, #ivx-in-s input"); var ob=document.querySelector("#ivx-out-s textarea, #ivx-out-s input"); var btn=document.querySelector("#ivx-btn-s"); if(!ta||!ob||!btn){reject(new Error("bridge missing"));return} p.jid="s"+Math.random().toString(36).slice(2,12); var lastRaw=ob.value||null,settled=false,t0=Date.now(); var iv=setInterval(function(){ var v=ob.value; if(v&&v!==lastRaw){ lastRaw=v; try{ var u=JSON.parse(v); if(!u.jid||u.jid===p.jid){settled=true;clearInterval(iv);resolve(u);return} }catch(e){} } if(!settled&&Date.now()-t0>90000){clearInterval(iv);reject(new Error("timeout"))} },400); var proto=ta.tagName==="TEXTAREA"?window.HTMLTextAreaElement.prototype:window.HTMLInputElement.prototype; Object.getOwnPropertyDescriptor(proto,"value").set.call(ta,JSON.stringify(p)); ta.dispatchEvent(new Event("input",{bubbles:true})); setTimeout(function(){btn.click()},60); }); } async function scoreBars(answer){ var idxs=[],items=[]; ex().cards.forEach(function(c,idx){ if(J[idx]!==undefined){idxs.push(idx);items.push({s:c.s,t:c.t,j:J[idx]})} }); if(!items.length||!answer)return; var myEpoch=epoch; try{ var r=await callScore({q:ex().q,items:items,answer:answer}); if(epoch!==myEpoch||!r.ratings)return; idxs.forEach(function(idx,i){ var v=r.ratings[String(i+1)]; if(v)BARS[idx]=v*0.2-0.1; }); paint(); }catch(e){} } function el(x){return document.getElementById(x)} function ex(){return EX[cur]} function card(){return ex().cards[i]} function judged(){var k=0;for(var x in J)k++;return k} function esc(s){return s.replace(/[&<>]/g,function(m){return {"&":"&","<":"<",">":">"}[m]})} function railEditable(node,idx,field){ node.onclick=function(e){ if(node.contentEditable!=="true"){node.contentEditable="true";if(node.focus)node.focus()} if(e&&e.stopPropagation)e.stopPropagation(); }; node.onblur=function(){ node.contentEditable="false"; var t=(node.textContent||"").replace(/\s+/g," ").trim(); if(t){var c=ex().cards[idx];if(field==="s")c.s=t;else c.t=t;dirty=true} paint(); }; node.onkeydown=function(e){ if(e.key==="Enter"){e.preventDefault();if(node.blur)node.blur()} if(e.stopPropagation)e.stopPropagation(); }; } function paint(){ el("q").textContent=ex().q; var ul=el("held"); /* undecided first so the interface invites deciding them, then yes, then no */ var order=[]; [0,1,-1].forEach(function(jv){ ex().cards.forEach(function(c,idx){if(J[idx]===jv)order.push(idx)}); }); var oldTop={}; order.forEach(function(idx){ var li=LIS[idx]; if(li&&li.getBoundingClientRect)oldTop[idx]=li.getBoundingClientRect().top; }); order.forEach(function(idx){ var c=ex().cards[idx]; var li=LIS[idx]; if(!li){li=document.createElement("li");LIS[idx]=li} li.className=J[idx]>0?"y":(J[idx]<0?"n":"p"); li.innerHTML=""; var row=document.createElement("div");row.className="rrow"; var tx=document.createElement("span"); var s1=document.createElement("span");s1.className="re";s1.textContent=c.s+" "; var s2=document.createElement("b");s2.className="re";s2.textContent=c.t; var dot=document.createElement("span");dot.textContent="."; railEditable(s1,idx,"s");railEditable(s2,idx,"t"); tx.appendChild(s1);tx.appendChild(s2);tx.appendChild(dot); row.appendChild(tx); var rv=document.createElement("span");rv.className="rev"; [[1,"✓","rv-y","agree"],[0,"–","rv-p","pass"],[-1,"✕","rv-n","disagree"]].forEach(function(pair){ var b=document.createElement("button"); b.className="rv "+pair[2]+(J[idx]===pair[0]?" cur":""); b.title=pair[3]; b.textContent=pair[1]; b.onclick=function(){revise(idx,pair[0])}; rv.appendChild(b); }); row.appendChild(rv); li.appendChild(row); if(judged()>=3){ var bar=document.createElement("div");bar.className="bar"; var fill=document.createElement("i"); var v=BARS[idx]||0; fill.style.width=Math.round(100*v)+"%"; fill.style.background=v<0.45?"var(--dn)":(v<0.7?"var(--amb)":"var(--up)"); bar.appendChild(fill); li.appendChild(bar); } ul.appendChild(li); }); order.forEach(function(idx){ var li=LIS[idx]; if(oldTop[idx]!==undefined&&li.getBoundingClientRect){ var d=oldTop[idx]-li.getBoundingClientRect().top; if(d){ li.style.transition="none";li.style.transform="translateY("+d+"px)"; void li.offsetHeight; li.style.transition="transform .35s ease";li.style.transform=""; } } }); el("out").hidden=judged()<3||(!prev&&!busy); var g=el("gen"); g.hidden=judged()<3; if(busy){g.disabled=true;g.textContent="writing"} else if(!prev){g.disabled=false;g.textContent="write the answer"} else if(dirty){g.disabled=false;g.textContent="update the answer"} else {g.disabled=true;g.textContent="answer is current"} closePop(); if(i