aboutsummaryrefslogtreecommitdiff
path: root/docs/prototypes/panel-widget-gallery.html
diff options
context:
space:
mode:
Diffstat (limited to 'docs/prototypes/panel-widget-gallery.html')
-rw-r--r--docs/prototypes/panel-widget-gallery.html76
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);});