Last active
June 20, 2026 17:18
-
-
Save watershed/4049ffca75339cad170164af737b9a72 to your computer and use it in GitHub Desktop.
SVG webmap for WMUCC
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <style> | |
| /* map context */ | |
| .hrmap { | |
| margin-block: 1.5rem; | |
| overflow-x: hidden; | |
| --hmarMobile: 6vw; | |
| --hmarWide : 11vw; | |
| } | |
| .hrmap, | |
| .hrmap * { | |
| box-sizing: border-box; | |
| } | |
| .hrmap [hidden] { | |
| display: none !important; | |
| } | |
| .hrmap > aside { | |
| font-size: 0.875rem; | |
| padding: 0.33em 0.75em; | |
| background: hsl(201deg 30% 90%); | |
| opacity: 0.6; | |
| text-align: right; | |
| } | |
| .hrmap > aside a { | |
| color: currentColor; | |
| } | |
| .hrmap > aside:hover { | |
| opacity: 1; | |
| } | |
| /* map controls */ | |
| .hrmap__zoom, | |
| .hrmap__scales { | |
| display: flex; | |
| flex-wrap: wrap; | |
| gap: 0.25rem; | |
| align-items: baseline; | |
| line-height: 1; | |
| } | |
| .hrmap__zoom { | |
| background: #00263b; | |
| color: rgb(255 255 255 / 0.75); | |
| font-weight: 500; | |
| padding: 0.5em; | |
| justify-content: space-between; | |
| } | |
| .hrmap__zoom.stuck { | |
| position: fixed; | |
| top: 0; | |
| width: 100%; | |
| z-index: 2; | |
| } | |
| .hrmap__zoom button, | |
| .hrmap__zoom span { | |
| display: block; | |
| padding: 0.33em 0.4em 0.25em; | |
| } | |
| /* zoom buttons */ | |
| .hrmap button { | |
| background: transparent; | |
| font: inherit; | |
| border: 0; | |
| color: white; | |
| border-radius: 3px; | |
| transition: 0.3s; | |
| } | |
| .hrmap button:not([aria-selected="true"]):focus, | |
| .hrmap button:not([aria-selected="true"]):hover { | |
| background: #e13f2f; | |
| } | |
| .hrmap [aria-selected="true"] { | |
| background: white; | |
| color: #00263b; | |
| cursor: default; | |
| } | |
| /* map wrapper */ | |
| .pos-rel { | |
| position: relative; | |
| } | |
| .hrmap__wrap.zoomed { | |
| overflow-x: auto; | |
| } | |
| /* map */ | |
| .hrmap__img, | |
| .hrmap__wbs img { | |
| display: block; | |
| } | |
| .hrmap__img { | |
| width: 100%; | |
| max-width: unset !important; | |
| } | |
| /* wbs (walk,bike,scoot) */ | |
| .hrmap__wbs { | |
| position: absolute; | |
| border: 8px solid white; | |
| top: 8rem; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| box-shadow: 0 0 3rem rgb(0 0 0 / 0.65); | |
| z-index: 1; | |
| } | |
| [data-wbs="outside"][data-active="true"] { | |
| position: absolute; | |
| top: 0; | |
| right: 0; | |
| bottom: 0; | |
| left: 0; | |
| background: rgb(0 0 0 / 0.5); | |
| backdrop-filter: blur(4px); | |
| } | |
| .hrmap__wbs img { | |
| width: clamp(300px, 80vw, 430px); | |
| height: auto; | |
| } | |
| .hrmap__wbs [data-hide] { | |
| position: absolute; | |
| background: #00263b; | |
| font-size: 24px; | |
| display: block; | |
| line-height: 1; | |
| width: 40px; | |
| height: 40px; | |
| border-radius: 50%; | |
| top: 50%; | |
| right: -24px; | |
| z-index; 1; | |
| outline: 1.5px solid white; | |
| } | |
| .hrmap__zoom [data-wbs]::before {content: 'Show ';} | |
| .hrmap__zoom [data-wbs][aria-selected="true"]::before {content: 'Hide ';} | |
| /* inclusively hide */ | |
| .sr-only { | |
| clip: rect(0 0 0 0); | |
| clip-path: inset(50%); | |
| height: 1px; | |
| overflow: hidden; | |
| position: absolute; | |
| white-space: nowrap; | |
| width: 1px; | |
| } | |
| /* viewport width sensitive layout */ | |
| @media (max-width: 768px) { | |
| .hrmap { | |
| margin-inline: calc(var(--hmarMobile) * -1); | |
| padding-inline: calc((var(--hmarMobile) * -1) - 0.4em); | |
| } | |
| } | |
| @media (min-width: 769px) and (max-width: 1399px) { | |
| .hrmap {margin-inline: calc(var(--hmarWide) * -1);} | |
| } | |
| </style> | |
| <!-- map controls and map --> | |
| <div class="hrmap"> | |
| <div class="hrmap__controls" hidden> | |
| <div class="hrmap__zoom"> | |
| <div class="hrmap__scales"> | |
| <span>Scale</span> | |
| <button data-scale="100" aria-selected="true">1x</button> | |
| <button data-scale="150">1.5x</button> | |
| <button data-scale="200">2x</button> | |
| <button data-scale="250">2.5x</button> | |
| <button data-scale="300">3x</button> | |
| <button data-scale="400">4x</button> | |
| <span data-mapcta hidden>Swipe/scroll map area</span> | |
| </div> | |
| <button data-wbs>walk/bike/scoot detail</button> | |
| </div> | |
| </div> | |
| <div class="pos-rel"> | |
| <div class="hrmap__wbs" hidden> | |
| <img src="https://watershedcreative.com/wmucc-venue/wbs.jpg" alt="Satellite view of the route between sites for walking, biking or scooting."> | |
| <button data-wbs="inside" data-hide="true"><span class="sr-only">Hide walk/bike/scoot detail</span><span aria-hidden="true">×</span></button> | |
| </div> | |
| <div class="hrmap__wrap"> | |
| <img class="hrmap__img" | |
| src="https://watershedcreative.com/wmucc-venue/wmucc-web-map.svg?v=20260618" | |
| alt="Maps of the tournament sites at Highfields and Riverside with an inset of the routes between them."> | |
| <div tabindex="0" data-wbs="outside" data-hide="true" data-active="false" aria-hidden="true"></div> | |
| </div> | |
| </div> | |
| <aside>Mapping by <a href="https://mastodon.social/@urlyman/116629094659805747">Jonathan Schofield</a></aside> | |
| </div> | |
| <script> | |
| /* select elements to work with */ | |
| const hrmap = document.querySelector('.hrmap'); | |
| const contr = document.querySelector('.hrmap__controls'); | |
| const zoom = document.querySelector('.hrmap__zoom'); | |
| const scales = document.querySelectorAll('.hrmap__zoom [data-scale]'); | |
| const mapcta = document.querySelector('.hrmap__zoom [data-mapcta]'); | |
| const wrap = document.querySelector('.hrmap__wrap'); | |
| const mapimg = document.querySelector('.hrmap__img'); | |
| const wbs = document.querySelector('.hrmap__wbs'); | |
| const togglers = document.querySelectorAll('[data-wbs]'); | |
| const outside = document.querySelector('[data-wbs="outside"]') | |
| // modify map scale | |
| const applyScale = (btn) => { | |
| const scale = btn.dataset.scale * 1; | |
| const is100 = scale === 100; | |
| const method = is100 ? 'remove' : 'add'; | |
| mapimg.style.width = `${btn.dataset.scale}%`; | |
| wrap.classList[method]('zoomed'); | |
| if ( !is100 ) { | |
| } | |
| mapcta.hidden = is100; | |
| scales.forEach((elem) => { | |
| elem.setAttribute('aria-selected', elem === btn); | |
| }); | |
| } | |
| // toggle wbs overlay | |
| const toggleWbs = (btn) => { | |
| const hider = btn.dataset.hide; | |
| const state = hider ? true : btn.getAttribute('aria-selected') === 'true'; | |
| wbs.hidden = state; | |
| outside.dataset.active = !state; | |
| if ( hider ) { | |
| togglers[0].setAttribute('aria-selected', false); | |
| } | |
| else { | |
| btn.setAttribute('aria-selected', !state); | |
| wrap.style.minHeight = `${wbs.offsetHeight + 160}px`; | |
| if ( !state ) { | |
| console.log('hi'); | |
| wrap.scrollTo(0,0); | |
| wbs.scrollIntoView({block: 'center'}); | |
| } | |
| } | |
| } | |
| // create thresholds for IntersectionObserver | |
| const threshArray = (n) => { | |
| const arr = []; | |
| for (let i = 1.0; i <= n; i++) { | |
| const ratio = i / n; | |
| arr.push(ratio); | |
| } | |
| arr.push(0); | |
| return arr; | |
| } | |
| /* make zoom controls fixed on scroll | |
| to work around SquareSpace complexity making `position:sticky` impractical to use :( | |
| */ | |
| let prevRatio = 0.0; | |
| const controlStick = new IntersectionObserver((entries) => { | |
| const isMap = entries[0].target.localName === 'img'; | |
| entries.forEach((entry) => { | |
| const rect = entry.boundingClientRect; | |
| let method = 'remove'; | |
| if (entry.intersectionRatio > prevRatio) { | |
| method = isMap ? 'add' : 'remove'; | |
| } | |
| else { | |
| method = isMap ? 'remove' : rect.y < 44 ? 'add' : 'remove'; | |
| } | |
| prevRatio = entry.intersectionRatio; | |
| zoom.classList[method]('stuck'); | |
| if ( method === 'add' ) { | |
| updateZoomMaxWidth(); | |
| } | |
| }); | |
| }); | |
| // adjust max-width of zoom controls when fixed | |
| const updateZoomMaxWidth = () => { | |
| const wrapX = wrap.offsetWidth; | |
| zoom.style.maxWidth = `${wrapX}px`; | |
| } | |
| // instantiate observers | |
| const arr = threshArray(5); | |
| controlStick.observe(contr, {scrollMargin: '44px 0px 44px 0px', threshold: arr}); | |
| controlStick.observe(mapimg, {threshold: arr}); | |
| // adjust max-width of zoom controls on resize | |
| const obs = new ResizeObserver((entries) => { | |
| updateZoomMaxWidth(); | |
| }); | |
| obs.observe(hrmap); | |
| // show the map controls | |
| contr.hidden = false; | |
| // activate the map controls | |
| scales.forEach((btn) => { | |
| btn.addEventListener('click', () => { | |
| applyScale(btn); | |
| }); | |
| }); | |
| // enable wbs | |
| togglers.forEach((btn) => { | |
| btn.addEventListener('click', () => { | |
| toggleWbs(btn); | |
| }); | |
| }); | |
| // hide wbs on 'Escape' | |
| document.addEventListener('keyup', (e) => { | |
| if ( e.code === 'Escape' && outside.dataset.active === 'true' ) { | |
| toggleWbs(outside); | |
| } | |
| }); | |
| </script> |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There are 3 discrete areas of code:
Included in the scope
Demo page
watershedcreative.com/wmucc-venue.
Next steps
If the code is published in the above order it should be fine:
There are potential changes needed to the CSS and HTML needed, but maybe not… (fingers crossed)
HTML
On line 204 and 209 the two images invoked are referenced from my website. If SquareSpace is OK with calling files from a third party domain, then no changes to those lines are needed.
But if there are issues, you will need to upload both images to your SquareSpace account and change the respective
srcvalues to where SquareSpace stores the files.For example, it looks like the base URL for the folder in which your site’s file assets are stored is:
So if there are issues, you might need to change line 209 to:
…or something like it.
CSS
Lines 6 and 7 set two variables which attempt to make allowance for the way in which the SquareSpace template sets the left and right margins around page content – i.e. the horizontal margins, shortened in the variable names to
hmar.--hmarMobile--hmarWideThe values set for each are in viewport width units (
vw). For example,6vwis 6% of the active viewport (browser window) width.I think the threshold at which the template makes the switch between these two paradigms is 768 CSS pixels, so:
--hmarMobilewill apply.--hmarWidewill apply.Adjust the values given to each of these variables if needed. They are currently
6vwand11vwrespectively, as that seems to be what nets out from SquareSpace’s viper’s nest of code.Images used
SVG: maps of the tournament sites at Highfields and Riverside with an inset of the routes between them:

JPEG: satellite view of the route between sites for walking, biking or scooting:
