From 91d57b580700fb2e777510f5ceca7c05e58b0f28 Mon Sep 17 00:00:00 2001 From: "prompt.ac/@jeffrey" Date: Sun, 16 Aug 2026 18:07:16 -0400 Subject: [PATCH] =?UTF-8?q?The=20whistlegraph.org=20sort=20bar=20sheds=20i?= =?UTF-8?q?ts=20training=20wheels:=20no=20more=20'Sort:'=20prefix=20or=20F?= =?UTF-8?q?eatured=20chip=20(the=20featured=20order=20remains=20the=20word?= =?UTF-8?q?less=20default=20until=20a=20chip=20is=20pressed),=20'Most=20vi?= =?UTF-8?q?ewed'=20becomes=20Popularity=20in=20both=20sections,=20Reverse?= =?UTF-8?q?=20shrinks=20from=20a=20word=20to=20an=20up-down=20arrow=20icon?= =?UTF-8?q?=20that=20now=20applies=20to=20every=20sort,=20a=20Duration=20s?= =?UTF-8?q?ort=20appears=20=E2=80=94=20each=20work's=20runtime=20learned?= =?UTF-8?q?=20from=20its=20hero=20take=20the=20moment=20posts.json=20lands?= =?UTF-8?q?,=20unknowns=20sinking=20to=20the=20bottom=20=E2=80=94=20and=20?= =?UTF-8?q?on=20phones=20the=20header=20reserves=20a=20lane=20for=20the=20?= =?UTF-8?q?settings=20gear=20so=20it=20stops=20parking=20on=20the=20wordma?= =?UTF-8?q?rk?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Fable 5 --- system/public/whistlegraph.org/index.html | 32 ++++++++++++++++------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/system/public/whistlegraph.org/index.html b/system/public/whistlegraph.org/index.html index 03b7f81684..c444bc95a6 100644 --- a/system/public/whistlegraph.org/index.html +++ b/system/public/whistlegraph.org/index.html @@ -159,6 +159,7 @@ border:1px solid var(--line); border-radius:2px; padding:2px 8px; cursor:pointer; line-height:1.4; } .sortbar button:hover{background:var(--tint)} + .sortbar button svg{width:13px; height:13px; display:block; pointer-events:none} .sortbar button.on{background:var(--margin); color:var(--paper); border-color:var(--margin)} .sectionbar{display:flex; flex-wrap:wrap; gap:0; margin:7px 0 13px; border-bottom:2px solid var(--line); align-items:flex-end} .sectionbar .sl{font-size:11px; color:var(--dim); padding:7px 9px 6px 0; text-transform:uppercase; letter-spacing:.08em} @@ -298,6 +299,9 @@ gutter goes narrow and, above all, EQUAL: the column is centred glass. */ @media (max-width:560px){ .pad{padding-left:11px; padding-right:11px} + /* the gear parks in the header's top-right corner; give the wordmark a + lane of its own so the two never overlap on a phone */ + header{padding-right:46px} table.idx td{padding:4px 4px} table.idx tr{grid-template-columns:60px 70px minmax(0,1fr) 54px} td.cd .sw{display:none} @@ -363,14 +367,13 @@
- Sort: - + + - - +
@@ -706,6 +708,9 @@ function sortedWorks(key){ let sorted=null; if(key==="videos") sorted=g.sort((a,b)=>b.perf-a.perf||byViews(a,b)); if(key==="viewed") sorted=g.sort(byViews); + // Duration rides on the hero take's runtime, learned when posts.json lands — + // works still waiting on that data sink to the bottom instead of lying. + if(key==="duration") sorted=g.sort((a,b)=>(b.dur||0)-(a.dur||0)||byViews(a,b)); if(key==="year") sorted=g.sort((a,b)=>a.year-b.year||byViews(a,b)); // oldest first if(key==="title") sorted=g.sort((a,b)=>a.title.localeCompare(b.title)); if(key==="author") sorted=g.sort((a,b)=>a.by.localeCompare(b.by)||byViews(a,b)); @@ -717,10 +722,11 @@ function sortedWorks(key){ // (async; thumbnails stay lazy). Search + sort just rebuild VIEW. const PAGE=60; let curSort=null, sortReversed=false, curQuery="", VIEW=[], SHOWN=0; -const reversibleSorts=new Set(["year","title","author"]); +// Every sort reverses now that the control is one small icon — it appears as +// soon as any sort is chosen. function syncReverseSort(){ const button=document.getElementById("reverseSort"); - button.hidden=!reversibleSorts.has(curSort); + button.hidden=!curSort; button.classList.toggle("on",sortReversed); button.setAttribute("aria-pressed",String(sortReversed)); } @@ -833,6 +839,10 @@ function loadPosts(){ work.views=related.reduce((sum,p)=>sum+(p.views||0),0); const selected=related.find(post=>post.id===work.featuredPost); const featured=(selected&&postThumb(selected)?selected:null)||related.find(post=>postThumb(post)); + // The work's duration is its hero take's runtime — editorial pick first, + // then the most-seen post (related is already sorted by reach). + const heroTake=selected||related[0]; + if(heroTake?.duration!=null) work.dur=heroTake.duration; if(!work.slug&&postThumb(featured)){ work.thumb=postThumb(featured); work.noGlyph=false; @@ -896,9 +906,11 @@ Promise.all([ document.querySelectorAll("#workSortbar button[data-sort]").forEach(b=>b.addEventListener("click",()=>{ document.querySelectorAll("#workSortbar button[data-sort]").forEach(x=>x.classList.remove("on")); b.classList.add("on"); - curSort=b.dataset.sort==="featured"?null:b.dataset.sort; + curSort=b.dataset.sort; sortReversed=false; syncReverseSort(); - renderReset(true); + // Duration lives in posts.json; the first duration sort pulls it in. + if(curSort==="duration"&&!postsLoaded) loadPosts().then(()=>renderReset(true)); + else renderReset(true); })); document.getElementById("reverseSort").addEventListener("click",()=>{ sortReversed=!sortReversed; syncReverseSort(); renderReset(true); -- 2.51.2