The overview earns its corner while a reader is working out where they are, and stops earning it the moment they want to look at what is underneath it. It now slides off to the right on a handle, which stays at the edge to bring it back. The handle is part of the panel rather than a control beside it: it shares the panel's edge, only the outer corners are rounded, and the chevron turns to point the way the panel will go. Put away is put away -- the panel stops answering the pointer, so what is left of it past the edge of the window does not steer the map, and a sweep already in progress ends where it is rather than following the panel out. It keeps drawing, so bringing it back costs nothing. Where the corner of the screen is wanted is a preference about a game rather than about one of its maps, so it is remembered with the rest of what a game is left in, and carries across that game's maps.
- commit
- 4ed0741165c25001c32b8fec297707c7888c0326
- parent
- 95678833f793c3da692c1904583d943fd286d3c7
- author
- Dami <dami.etoile@gmail.com>
| M | assets/ | 12 | +++++-- |
| M | frontend/ | 60 | ++++++++++++++++++++++++++++++ |
| M | frontend/ | 35 | +++++++++++++++++- |
@@ -135,9 +135,15 @@
135135 </div>136136 137137 <div class="map-corner">138- <div id="overview" class="overview" hidden aria-label="Map overview">139- <canvas id="overview-canvas"></canvas>140- <div id="overview-viewport" class="overview-viewport"></div>138+ <div id="overview-shelf" class="overview-shelf" hidden>139+ <button id="overview-dock" class="overview-dock" type="button" aria-expanded="true"140+ aria-label="Put the overview away" title="Put the overview away">141+ <svg viewBox="0 0 16 16" aria-hidden="true"><path d="m6 3 5 5-5 5"/></svg>142+ </button>143+ <div id="overview" class="overview" aria-label="Map overview">144+ <canvas id="overview-canvas"></canvas>145+ <div id="overview-viewport" class="overview-viewport"></div>146+ </div>141147 </div>142148 143149 <div class="map-hint" id="map-hint">@@ -995,6 +995,66 @@ input {
995995 pointer-events: auto;996996 }997997 998+/* The overview earns its corner while the reader is looking for where they are,999+ and stops earning it the moment they are looking at what is underneath it. It1000+ slides off to the right when put away, leaving the handle at the edge to1001+ bring it back. */1002+.overview-shelf {1003+ display: flex;1004+ align-items: center;1005+ transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);1006+}1007+1008+.overview-shelf[hidden] {1009+ display: none;1010+}1011+1012+.overview-shelf.is-docked {1013+ transform: translateX(calc(100% - 23px));1014+}1015+1016+/* Put away is put away: what is left of the panel past the edge of the window1017+ does not steer the map. */1018+.overview-shelf.is-docked .overview {1019+ pointer-events: none;1020+}1021+1022+/* The handle is part of the panel it moves, not a control beside it: it shares1023+ the panel's edge, and only the outer corners are rounded. */1024+.overview-dock {1025+ display: grid;1026+ width: 24px;1027+ height: 52px;1028+ padding: 0;1029+ border: 1px solid var(--line-strong);1030+ border-right: 0;1031+ border-radius: 8px 0 0 8px;1032+ margin-right: -1px;1033+ place-items: center;1034+ background: rgba(22, 25, 21, 0.9);1035+ box-shadow: -5px 6px 20px rgba(0, 0, 0, 0.24);1036+ color: var(--muted);1037+ cursor: pointer;1038+}1039+1040+.overview-dock:hover {1041+ color: var(--accent);1042+}1043+1044+.overview-dock svg {1045+ width: 13px;1046+ fill: none;1047+ stroke: currentColor;1048+ stroke-linecap: round;1049+ stroke-linejoin: round;1050+ stroke-width: 1.8;1051+ transition: transform 220ms cubic-bezier(0.22, 0.61, 0.36, 1);1052+}1053+1054+.overview-shelf.is-docked .overview-dock svg {1055+ transform: rotate(180deg);1056+}1057+9981058 /* Only worth the space once the whole map no longer fits on screen, which is9991059 why it is hidden until then rather than always present. */10001060 .overview {@@ -48,6 +48,8 @@ const elements = {
4848 gridHint: $("#grid-hint"),4949 soloChip: $("#solo-chip"),5050 overview: $("#overview"),51+ overviewShelf: $("#overview-shelf"),52+ overviewDock: $("#overview-dock"),5153 overviewCanvas: $("#overview-canvas"),5254 overviewViewport: $("#overview-viewport"),5355 gridNavigator: $("#grid-navigator"),@@ -111,6 +113,7 @@ const state = {
111113 overviewRun: 0,112114 overviewKey: "",113115 overviewPointer: null,116+ overviewDocked: false,114117 renderedShard: 0,115118 mapRun: 0,116119 textByMap: new Map(),@@ -587,6 +590,7 @@ function bindUIEvents() {
587590 // Capturing the pointer keeps the sweep alive past the edge of the overview,588591 // where the destination is simply the nearest point still on the map.589592 elements.overview.addEventListener("pointerdown", (event) => {593+ if (state.overviewDocked) return;590594 if (event.pointerType === "mouse" && event.button !== 0) return;591595 event.preventDefault();592596 elements.overview.setPointerCapture(event.pointerId);@@ -605,6 +609,10 @@ function bindUIEvents() {
605609 };606610 elements.overview.addEventListener("pointerup", releaseOverview);607611 elements.overview.addEventListener("pointercancel", releaseOverview);612+ elements.overviewDock.addEventListener("click", () => {613+ setOverviewDocked(!state.overviewDocked);614+ elements.viewport.focus({ preventScroll: true });615+ });608616 elements.mobileLegend.addEventListener("click", () => {609617 const open = elements.sidebar.classList.toggle("is-open");610618 elements.mobileLegend.setAttribute("aria-expanded", String(open));@@ -725,6 +733,7 @@ function saveSession() {
725733 zoom: view?.getZoom(),726734 hidden: [...state.hiddenCategories],727735 collapsed: [...state.collapsedSections],736+ overviewDocked: state.overviewDocked,728737 };729738 localStorage.setItem(sessionKey, JSON.stringify(stored));730739 } catch {@@ -796,6 +805,9 @@ async function selectMap(id) {
796805 state.hiddenCategories = new Set(restore.hidden);797806 state.collapsedSections = new Set(restore.collapsed);798807 }808+ // Where the corner of the screen is wanted is a preference about the game809+ // rather than about one of its maps, so it carries across them.810+ setOverviewDocked(Boolean(state.restore?.overviewDocked), false);799811 state.search = "";800812 elements.search.value = "";801813 elements.searchResults.hidden = true;@@ -1037,7 +1049,9 @@ function updateOverviewViewport() {
10371049 const key = covered ? "hidden" : visible.map(Math.round).join(",");10381050 if (key === state.overviewKey) return;10391051 state.overviewKey = key;1040- elements.overview.hidden = covered;1052+ // The handle goes with it: there is nothing to send for while the whole map1053+ // is already on screen.1054+ elements.overviewShelf.hidden = covered;10411055 if (covered) return;10421056 10431057 const canvas = elements.overviewCanvas;@@ -1054,6 +1068,25 @@ function updateOverviewViewport() {
10541068 box.height = `${Math.max(8, (lower - upper) * canvas.height)}px`;10551069 }10561070 1071+// Put away rather than switched off: the overview keeps drawing, and the handle1072+// it leaves behind is the whole of the way back. A reader who wants the corner1073+// of their map usually wants it for that map's whole session, so the choice is1074+// remembered with the rest of what a game is left in.1075+function setOverviewDocked(docked, remember = true) {1076+ state.overviewDocked = docked;1077+ // A sweep in progress ends where it is rather than following the panel out.1078+ if (docked && state.overviewPointer !== null) {1079+ state.overviewPointer = null;1080+ settleView();1081+ }1082+ elements.overviewShelf.classList.toggle("is-docked", docked);1083+ elements.overviewDock.setAttribute("aria-expanded", String(!docked));1084+ const label = docked ? "Bring the overview back" : "Put the overview away";1085+ elements.overviewDock.setAttribute("aria-label", label);1086+ elements.overviewDock.title = label;1087+ if (remember) saveSession();1088+}1089+10571090 // The press that opens a gesture eases across, so a plain click reads as a10581091 // move rather than a cut. Everything after it lands at once: a view that eased10591092 // its way to each new position would trail behind the hand steering it.