diff options
Diffstat (limited to 'docs/prototypes')
| -rw-r--r-- | docs/prototypes/panel-widget-gallery.html | 76 |
1 files changed, 74 insertions, 2 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html index 9740c46..2a2910b 100644 --- a/docs/prototypes/panel-widget-gallery.html +++ b/docs/prototypes/panel-widget-gallery.html @@ -126,6 +126,10 @@ h2::after{content:"";height:1px;background:var(--wash);flex:1} background:#221f1b;border:1px solid #33302b} .vtally .vn{margin-left:auto;color:var(--cream);font-variant-numeric:tabular-nums} .vtally .vtot{border-top:1px solid var(--wash);margin-top:3px;padding-top:4.5px;color:var(--steel)} +.vtally .vcopy{margin-top:5px;width:100%;font:inherit;font-size:.7rem;letter-spacing:.04em; + background:transparent;color:var(--steel);border:1px solid var(--wash);border-radius:3px; + padding:2.5px 0;cursor:pointer} +.vtally .vcopy:hover{color:var(--gold-hi);border-color:var(--gold)} .stagew{background:var(--well);border:1px solid #201d17;border-radius:9px;padding:14px 12px; min-height:78px;display:flex;align-items:center;justify-content:center;gap:12px;flex-wrap:wrap} .wrd{color:var(--gold-hi);font-size:.8rem;letter-spacing:.06em;font-variant-numeric:tabular-nums; @@ -1039,6 +1043,72 @@ const INFO={ const INFO_FIELDS=[['input','input'],['solves','solves'],['use','use'],['limits','limits'], ['origin','origin'],['difficulty','difficulty'],['prefer','prefer when'],['period','period']]; const VLAMP_TITLES={off:'validation: not done',amber:'validation: in progress',green:'validation: done'}; +/* VSTATUS — the baked validation record: the durable half of the walk. + Lamp clicks write localStorage, which is per-browser-profile and dies with a + cache clear, so the walk's progress is baked back into source periodically + (the "copy for source" control under the tally emits this block; paste it + here). Only amber/green are listed — off is the default and stays implicit. + Precedence is localStorage first, VSTATUS second: the live walk wins on the + machine doing it, and the baked record fills in on a fresh profile, after a + clear, or on the other daily driver. 'off' is a stored value like any other, + so deliberately un-checking a card beats a baked green rather than reverting + to it on reload. */ +const VSTATUS={ + "01": "green", + "06": "green", + "25": "green", + "R05": "green", + "12": "green", + "R07": "amber", + "18": "green", + "R10": "green", + "R11": "green", + "R52": "amber" +}; +/* GVexport — the live lamp state in VSTATUS shape, ready to paste back above. + Reads the lamps rather than localStorage so what you copy is what you see. + Builds the text by hand rather than via JSON.stringify: stringify orders + canonical integer keys ('12') numerically ahead of everything else ('01' has + a leading zero, 'R05' isn't numeric), which would reshuffle the baked block + on every bake and bury each walk's real change in diff noise. Card order is + DOM order, so the block grows in place. */ +function GVexport(){ + const rows=[]; + document.querySelectorAll('.vlamp').forEach(l=>{ + const v=l.dataset.v; + if(v!=='off') rows.push(' "'+l.closest('.card').id.slice(5)+'": "'+v+'"');}); + return '{\n'+rows.join(',\n')+'\n}'; +} +/* Surface the export as a selected textarea. This deliberately does NOT try to + reach the clipboard, because on this setup (Chrome 150, native Wayland, no + clipboard manager) neither clipboard path works from a file:// page: + - navigator.clipboard.writeText rejects NotAllowedError, "Write permission + denied" — a file origin can't hold the clipboard-write permission. + - document.execCommand('copy') returns TRUE and writes an empty string. The + clipboard ends up owned, advertising text/plain, serving nothing. Chrome + sets the PRIMARY selection but not CLIPBOARD; a hand-typed Ctrl+C in the + page behaves the same way, so it is not specific to execCommand. + JS can't detect any of this — readText is denied too — so claiming "copied" + would be a lie the code cannot check. Selecting the text is the one thing + that reliably works: a selection IS the X/Wayland PRIMARY selection, readable + with `wl-paste --primary`. Verified twice against the live browser. + The blur teardown is safe: Chrome keeps serving PRIMARY after the textarea is + gone (observed — the export read back fine both times, each after switching + away from the browser, which is what fires the blur). So the selection does + NOT have to stay on screen to survive; the handler only avoids leaving a + textarea sitting on the page. What does clobber PRIMARY is selecting text + anywhere else, which no handler here can prevent. */ +function gvCopy(btn){ + const flash=m=>{btn.textContent=m;setTimeout(()=>btn.textContent='copy for source',2200);}; + const ta=document.createElement('textarea'); + ta.value=GVexport(); + ta.style.cssText='position:fixed;left:12px;bottom:12px;z-index:70;width:320px;height:170px;'+ + 'font:12px ui-monospace,monospace;background:#14120f;color:#e8dcc0;border:1px solid #7d5c16;'+ + 'border-radius:4px;padding:6px'; + document.body.appendChild(ta); ta.select(); + flash('selected ▸'); + ta.addEventListener('blur',()=>ta.remove()); +} /* index tally: recounts every card lamp; setV calls it on every state change. Clicking a row jumps to the next card in that state (cycles), so a count that disagrees with a visual scan can be audited card by card. */ @@ -1052,10 +1122,12 @@ function updateVTally(){ `<div class="vrow" data-v="green"><span class="vdot" data-v="green"></span>done<span class="vn">${n.green}</span></div>`+ `<div class="vrow" data-v="amber"><span class="vdot" data-v="amber"></span>in progress<span class="vn">${n.amber}</span></div>`+ `<div class="vrow" data-v="off"><span class="vdot"></span>not done<span class="vn">${n.off}</span></div>`+ - `<div class="vrow vtot">total<span class="vn">${lamps.length}</span></div>`; + `<div class="vrow vtot">total<span class="vn">${lamps.length}</span></div>`+ + `<button class="vcopy" type="button">copy for source</button>`; el.querySelectorAll('.vrow[data-v]').forEach(row=>row.addEventListener('click',()=>{ VJUMP[row.dataset.v]=0; auditNext(row.dataset.v); })); + el.querySelector('.vcopy').addEventListener('click',e=>gvCopy(e.target)); } /* audit stepper: a row click jumps to the first card in that state and docks a floating pill; clicking the pill advances through the rest without scrolling @@ -1090,7 +1162,7 @@ function card(host, no, name, html, note){ `<div class="opts"></div><div class="wnote">${note}</div>`; const lamp=c.querySelector('.vlamp'), VKEY='gv-'+no, VSTATES=['off','amber','green']; const setV=v=>{lamp.dataset.v=v;lamp.title=VLAMP_TITLES[v];updateVTally();}; - setV(localStorage.getItem(VKEY)||'off'); + setV(localStorage.getItem(VKEY)||VSTATUS[no]||'off'); lamp.addEventListener('click',e=>{e.stopPropagation(); const v=VSTATES[(VSTATES.indexOf(lamp.dataset.v)+1)%3]; setV(v);localStorage.setItem(VKEY,v);}); |
