diff --git a/index.html b/index.html index dca73ca..5c09f3d 100644 --- a/index.html +++ b/index.html @@ -249,6 +249,16 @@ .gflegend span{display:inline-flex;align-items:center;gap:7px;font-size:12.5px;font-weight:700;color:var(--ink);white-space:nowrap} .gflegend i{width:12px;height:12px;border-radius:4px;flex:none} .gzoom button{padding:7px 13px;font-size:12.5px;min-height:34px;display:flex;align-items:center;justify-content:center} + /* timeline week navigation — fixed over the chart edges, does not scroll with the grid */ + .gnav{position:absolute;left:0;right:0;bottom:10px;height:44px;z-index:22;pointer-events:none} + .gnavbtn{position:absolute;top:0;transform:none;pointer-events:auto; + width:30px;height:44px;border-radius:10px;background:rgba(255,255,255,.94); + border:1px solid var(--line);box-shadow:0 2px 10px rgba(20,24,35,.1); + font-size:22px;font-weight:400;line-height:1;color:var(--ink-2); + display:flex;align-items:center;justify-content:center;transition:background .15s,color .15s} + .gnavbtn:hover{background:var(--surface);color:var(--ink)} + .gnavbtn:active{transform:scale(.96)} + .gnav-l{left:6px}.gnav-r{right:6px} /* owners shown as a compact segmented row of avatars */ .gpeople{padding:3px;gap:2px} .gpeople button{padding:2px;min-height:0;width:38px;height:34px;border-radius:8px;display:flex; @@ -605,6 +615,9 @@ .gpeople button{width:30px;height:28px} .gpeople .av{width:23px;height:23px;font-size:9px} .gzoom button{padding:6px 9px;font-size:11.5px;min-height:30px} + .gnav{bottom:6px;height:38px} + .gnavbtn{width:26px;height:38px;font-size:20px} + .gnav-l{left:4px}.gnav-r{right:4px} .gficon{width:31px;height:31px} .gficon svg{width:17px;height:17px} /* capture + team sheets use the full width on a phone */ @@ -739,6 +752,10 @@ +
diff --git a/src/app/main.js b/src/app/main.js index 3efad79..5446fc0 100644 --- a/src/app/main.js +++ b/src/app/main.js @@ -1,7 +1,7 @@ import { PEOPLE, TODAY, HARDWARE_VOCAB, CLIENTS, SIZE_PTS, SIZE_NAMES, LEAD, ZOOMS, GBAR_H, - R0G, R1G, SPAN_G, TODAY_PX, + R0G, R1G, SPAN_G, GANTT_PAST, TODAY_PX, C_LATE, C_TODAY, C_RADAR, C_LATER, C_DONE, } from "../data/constants.js"; import { inferOwnerByDomain, canonHardware, findClient, buildRespMapText, buildVocabText, norm as _norm } from "../lib/domain.js"; @@ -231,7 +231,7 @@ function sizeScale(){ bars. Bar height tracks t-shirt size; each bar has a done-dot (left) and owner bubble (right). The window scrolls horizontally; zoom buttons set how many days fit on screen. */ /* chart starts at today - no dead space on the left */ -let ZOOM = 2, showDone = false; +let ZOOM = 1, showDone = false; // default: 3-week view const { dayN, dayIso, barSpan, workDays, barColor, barGeom, rollupSpan, spanFor, leafWeight, progWD, isUrgent, fmtD, @@ -290,7 +290,31 @@ function toggleFocus(){ focusToday=!focusToday; const b=$id("gfocusbtn"); if(b)b let GVIEW="proj"; // "proj" | "tasks" | "subs" function setGView(v){ GVIEW=v; defer(renderGantt); } function setZoom(i){ ZOOM=i; setTimeout(renderAll,0); } // drives the scale window and the gantt zoom +/* scroll the timeline by one week without changing the visible day count (1W / 3W / 6W) */ +function navTimeline(dir){ + const sc=document.querySelector(".gscroll"), inner=sc?.querySelector(".ginner"); + if(!sc||!inner) return; + const anchor=scrollAnchorDay(sc,inner); + const target=Math.max(R0G,Math.min(R1G-ZOOMS[ZOOM].v,anchor+dir*7)); + sc.scrollLeft=dayScrollPx(target,inner); + pinFlags(); placeFloat(); +} +/* pixel offset of a day index on the inner track (inverse of gx) */ +function dayScrollPx(d,inner){ + return gx(d)/100*inner.clientWidth; +} +/* day index nearest the left edge of the scrolled viewport */ +function scrollAnchorDay(sc,inner){ + const frac=sc.scrollLeft/Math.max(inner.clientWidth,1)*100; + let best=R0G, dist=Infinity; + for(let d=R0G;d<=R1G;d++){ + const g=gx(d); if(Math.abs(g-frac)