https://github.com/johnsyweb/tampermonkey-parkrun
Messing with parkrun pages for my own enjoyment
https://github.com/johnsyweb/tampermonkey-parkrun
parkrun parkrun-alphabet-challenge parkrun-analysis parkrun-bingo parkrun-challenges parkrun-charts parkrun-compass parkrun-data-analysis parkrun-statistics parkrun-visualisations tampermonkey userscript
Last synced: 5 months ago
JSON representation
Messing with parkrun pages for my own enjoyment
- Host: GitHub
- URL: https://github.com/johnsyweb/tampermonkey-parkrun
- Owner: johnsyweb
- License: mit
- Created: 2024-12-22T10:42:54.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2026-01-12T02:00:27.000Z (5 months ago)
- Last Synced: 2026-01-12T06:06:49.033Z (5 months ago)
- Topics: parkrun, parkrun-alphabet-challenge, parkrun-analysis, parkrun-bingo, parkrun-challenges, parkrun-charts, parkrun-compass, parkrun-data-analysis, parkrun-statistics, parkrun-visualisations, tampermonkey, userscript
- Language: HTML
- Homepage: https://www.johnsy.com/tampermonkey-parkrun/
- Size: 11.7 MB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE.md
Awesome Lists containing this project
README
# Pete's parkrun userscripts
## A script or scripts for messing with parkrun results pages
You'll need something like [Tampermonkey](https://www.tampermonkey.net) if
you'd like to enjoy them. Or you can use the bookmarklet versions.
## Development Setup
This project uses [mise](https://mise.jdx.dev/) for development environment management.
### Prerequisites
- [mise](https://mise.jdx.dev/) - for managing development tools
### Setup
1. Install dependencies with mise (installs Ruby, Node, and pnpm):
```bash
mise install
```
2. Install npm dependencies:
```bash
pnpm install
```
The `mise.toml` file specifies the required versions:
- Ruby 3.4.7 (for Jekyll)
- Node LTS (for development tools)
- pnpm latest (for package management)
3. For microsite development, install Ruby/Jekyll dependencies:
```bash
cd docs
bundle install
```
### Previewing the Microsite Locally
The microsite documentation is built with Jekyll and served from the `docs/` directory.
1. Start the Jekyll server:
```bash
cd docs
bundle exec jekyll serve --port 4000 --host 0.0.0.0
```
2. Visit http://localhost:4000/tampermonkey-parkrun/ in your browser
### Generating Screenshots
To generate screenshots for the microsite:
```bash
pnpm screenshots
```
This will create screenshots of all userscripts in `docs/images/`.
### Building the Microsite
The microsite is automatically deployed to GitHub Pages on every push to `main`.
To build the site locally:
```bash
pnpm docs:build
```
To serve the site locally:
```bash
pnpm docs:serve
```
The site will be available at http://localhost:4000/tampermonkey-parkrun/
### Git Hooks
This project uses [husky](https://typicode.github.io/husky/) to manage git hooks. Hooks are automatically installed when you run `pnpm install`.
#### Pre-commit Hook
The pre-commit hook ensures code quality by running:
- `pnpm check-format` - Checks code formatting with Prettier
- `pnpm test` - Runs all Jest tests
- `pnpm lint` - Runs ESLint to check for linting errors
All checks must pass before a commit is allowed. This ensures all code is properly formatted, tested, and linted before being committed.
#### Pre-push Hook
The pre-push hook automatically generates screenshots when userscripts are modified:
- Checks if any `.user.js` files have been modified in the commits being pushed
- Automatically runs `pnpm screenshots` if userscripts were changed
- Prevents the push if screenshot generation fails
**Note:** Screenshots must be generated locally because parkrun websites block automated agents from accessing them in CI environments.
### GitHub Actions Workflow
The project includes a GitHub Actions workflow that:
1. Sets up Ruby (for Jekyll) and Node.js (for scripts)
2. Installs dependencies (via mise, pnpm, and bundler)
3. Updates the scripts data file from userscript metadata
4. Builds the Jekyll site
5. Deploys to GitHub Pages
This workflow runs on every push to `main` and automatically keeps the microsite up to date with the latest scripts.
## Why?
Why not?
## How to Install Bookmarklets
To use these scripts as bookmarklets, follow these steps:
1. Copy the JavaScript code for the desired bookmarklet from the sections below.
2. Open your browser's bookmarks manager.
3. Create a new bookmark.
4. Paste the JavaScript code into the URL field of the bookmark.
5. Save the bookmark.
Now, you can click the bookmark while on a parkrun results page to run the script.
## Updating Versions
To update the `@version` field in all userscripts, run the following Node.js script:
```bash
node scripts/update-version.js
```
This will automatically update the `@version` field in all `.user.js` files based on the current date and time.
## Bookmarklets
You can also use these scripts as bookmarklets by creating bookmarks with the following URLs:
### parkrun Alphabet Challenge
> Tracks progress on the unofficial parkrun alphabet challenge (A-Z, excluding X) with a 5x5 grid visualization and download feature.
[alphabet-challenge.user.js](https://raw.githubusercontent.com/johnsyweb/tampermonkey-parkrun/refs/heads/main/alphabet-challenge.user.js)
```javascript
javascript:(async()=>{var o,e,t,p;function c(e,t){var n,o,r,l,a="undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(a)return r=!(o=!0),{s:function(){a=a.call(e)},n:function(){var e=a.next();return o=e.done,e},e:function(e){r=!0,n=e},f:function(){try{o||null==a.return||a.return()}finally{if(r)throw n}}};if(Array.isArray(e)||(a=((e,t)=>{var n;if(e)return"string"==typeof e?i(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?i(e,t):void 0})(e))||t&&e&&"number"==typeof e.length)return a&&(e=a),l=0,{s:t=function(){},n:function(){return l>=e.length?{done:!0}:{done:!1,value:e[l++]}},e:function(e){throw e},f:t};throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}function i(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,o=Array(t);n')+""+l.completedCount+" of 25 letters completed"+'
After ')+l.totalEvents+" parkruns"+(l.dateOfCompletion?'🎉 Challenge completed on: ').concat(l.dateOfCompletion,""):""),o.appendChild(a),document.createElement("div"));return i.style.display="grid",i.style.gridTemplateColumns="repeat(5, 1fr)",i.style.gap=n.grid.gapSize,i.style.marginTop=n.grid.marginTop,p.forEach(function(r){var e=document.createElement("div"),t=(e.style.aspectRatio="1",e.style.position="relative",e.style.border="1px solid #666",e.style.borderRadius="4px",e.style.backgroundColor=l.completedLetters[r]?"#FFA300":"#008080",e.style.color="#fff",e.style.display="flex",e.style.flexDirection="column",e.style.alignItems="flex-start",e.style.justifyContent="flex-start",e.style.padding=n.grid.cellPadding,e.style.fontWeight="bold",e.style.fontSize="1em",e.style.cursor=l.completedLetters[r]?"pointer":"default",document.createElement("div"));t.textContent=r,t.style.fontSize=n.grid.letterFontSize,t.style.marginBottom=n.grid.letterMarginBottom,e.appendChild(t),l.completedLetters[r]&&((t=document.createElement("div")).innerHTML='')+l.completedLetters[r].eventName+"
"+'(')+l.completedLetters[r].date+")
",n.isMobile&&(t.style.display="none"),e.appendChild(t),e.addEventListener("click",function(){var e=d(),t=document.createElement("div"),n=(t.style.position="fixed",t.style.top="0",t.style.left="0",t.style.width="100%",t.style.height="100%",t.style.backgroundColor="rgba(0, 0, 0, 0.5)",t.style.zIndex="999",t.addEventListener("click",function(){document.body.removeChild(t)}),document.createElement("div")),o=(n.style.position="fixed",n.style.top="50%",n.style.left="50%",n.style.transform="translate(-50%, -50%)",n.style.backgroundColor="#2b223d",n.style.color="#fff",e.isMobile?"15px":"20px"),o=(n.style.padding=o,n.style.borderRadius="8px",n.style.boxShadow="0 2px 4px rgba(0,0,0,0.5)",n.style.zIndex="1000",n.style.maxWidth=e.isMobile?"95%":"90%",n.style.maxHeight=e.isMobile?"85%":"80%",n.style.overflowY="auto",n.style.fontSize=e.isMobile?"0.9em":"1em",document.createElement("h4")),e=(o.textContent="Letter ".concat(r),o.style.marginBottom="10px",o.style.color="#FFA300",n.appendChild(o),document.createElement("div"));e.style.marginBottom="10px",e.innerHTML=""+l.completedLetters[r].eventName+'
'+l.completedLetters[r].date+"",n.appendChild(e),t.appendChild(n),document.body.appendChild(t)})),i.appendChild(e)}),o.appendChild(i),e=o,a=d(),(t=document.createElement("div")).style.marginTop=a.button.marginTop,t.id="alphabet-download-btn-container",(r=document.createElement("button")).textContent="💾 Save as Image",r.style.padding=a.button.padding,r.style.backgroundColor="#FFA300",r.style.color="#2b223d",r.style.border="none",r.style.borderRadius="4px",r.style.cursor="pointer",r.style.fontWeight="bold",r.style.fontSize=a.button.fontSize,r.addEventListener("mouseover",function(){this.style.backgroundColor="#e59200"}),r.addEventListener("mouseout",function(){this.style.backgroundColor="#FFA300"}),r.addEventListener("click",function(){r.style.display="none",html2canvas(e,{backgroundColor:"#2b223d",scale:2,logging:!1,allowTaint:!0,useCORS:!0}).then(function(e){r.style.display="block";var t=document.createElement("a"),n=(new Date).toISOString().split("T")[0],o=window.location.pathname.split("/")[2]||"parkrunner";t.download="alphabet-challenge-".concat(o,"-").concat(n,".png"),t.href=e.toDataURL("image/png"),t.click()})}),t.appendChild(r),e.appendChild(t),o}o="https://html2canvas.hertzen.com/dist/html2canvas.min.js",await new Promise((e,t)=>{var n=document.createElement("script");n.src=o,n.onload=e,n.onerror=t,document.head.appendChild(n)}),p="ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("").filter(function(e){return"X"!==e}),(t=(t=document.querySelectorAll("#results"))[t.length-1])?(t=t=n((e=>{var t,n={},o=0,r=null,l=c(Array.from(e.querySelectorAll("tr")).reverse());try{for(l.s();!(t=l.n()).done;){var a=t.value.querySelectorAll("td");if(!(a.length<1)){var i=a[0].textContent.trim(),d=a[1].textContent.trim(),s=i.charAt(0).toUpperCase();if(o++,p.includes(s)&&!n[s]&&(n[s]={eventName:i,date:d},25===Object.keys(n).length)){r=d;break}}}}catch(e){l.e(e)}finally{l.f()}return e=Object.keys(n).length,{completedLetters:n,remainingLetters:p.filter(function(e){return!n[e]}),completedCount:e,dateOfCompletion:r,totalEvents:o}})(t)),(e=document.querySelector("h2"))&&e.parentNode&&(e.nextSibling?e.parentNode.insertBefore(t,e.nextSibling):e.parentNode.appendChild(t))):console.log("Results table not found")})();
```
### parkrun Compass Challenge
> Visualizes your progress on the compass challenge (North, South, East, West parkruns)
[compass-challenge.user.js](https://raw.githubusercontent.com/johnsyweb/tampermonkey-parkrun/refs/heads/main/compass-challenge.user.js)
```javascript
javascript:(async()=>{var o,t,e,b,w;function y(t,e){return(t=>{if(Array.isArray(t))return t})(t)||((t,e)=>{var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=n){var o,a,r,i,c=[],s=!0,l=!1;try{if(r=(n=n.call(t)).next,0===e){if(Object(n)!==n)return;s=!1}else for(;!(s=(o=r.call(n)).done)&&(c.push(o.value),c.length!==e);s=!0);}catch(t){l=!0,a=t}finally{try{if(!s&&null!=n.return&&(i=n.return(),Object(i)!==i))return}finally{if(l)throw a}}return c}})(t,e)||((t,e)=>{var n;if(t)return"string"==typeof t?a(t,e):"Map"===(n="Object"===(n={}.toString.call(t).slice(8,-1))&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?a(t,e):void 0})(t,e)||(()=>{throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,o=Array(e);n').concat(t.completedCount," of 4 compass directions completed")),r=(a+='
After ').concat(t.totalEvents," parkruns"),t.dateOfCompletion&&(a+='🧭 Challenge completed on ').concat(t.dateOfCompletion,"")),o.innerHTML=a,n.appendChild(o),e.isMobile?Math.min(e.compass.baseSize,window.innerWidth-40):e.compass.baseSize),a=document.createElement("div"),p=(a.style.position="relative",a.style.width="".concat(r,"px"),a.style.height="".concat(r,"px"),a.style.margin="0 auto",a.style.boxSizing="content-box",document.createElementNS("http://www.w3.org/2000/svg","svg")),m=(p.setAttribute("width","100%"),p.setAttribute("height","100%"),p.setAttribute("viewBox","0 0 ".concat(700," ").concat(700)),p.style.position="absolute",p.style.top="0",p.style.left="0",350),h=350,o=document.createElementNS("http://www.w3.org/2000/svg","circle");o.setAttribute("cx",m),o.setAttribute("cy",h),o.setAttribute("r",230),o.setAttribute("fill","#334"),o.setAttribute("stroke",b.accentColor),o.setAttribute("stroke-width","5"),p.appendChild(o);[{name:"north",angle:270,label:"N",data:t.directions.north},{name:"east",angle:0,label:"E",data:t.directions.east},{name:"south",angle:90,label:"S",data:t.directions.south},{name:"west",angle:180,label:"W",data:t.directions.west}].forEach(function(t){var e=t.angle*(Math.PI/180),n=m+230*Math.cos(e),o=h+230*Math.sin(e),a=230*.12,r=m+115*Math.cos(e),i=h+115*Math.sin(e),c=e+Math.PI/2,s=r+a*Math.cos(c),l=i+a*Math.sin(c),r=r-a*Math.cos(c),i=i-a*Math.sin(c),a=document.createElementNS("http://www.w3.org/2000/svg","path"),c="M ".concat(m," ").concat(h," L ").concat(s," ").concat(l," L ").concat(n," ").concat(o," L ").concat(r," ").concat(i," Z"),s=(a.setAttribute("d",c),a.setAttribute("fill",t.data?b.completedColor:b.pendingColor),a.setAttribute("stroke","#000"),a.setAttribute("stroke-width","1"),a.setAttribute("stroke-opacity","0.3"),p.appendChild(a),m+115*Math.cos(e)),l=h+115*Math.sin(e),n=document.createElementNS("http://www.w3.org/2000/svg","text");if(n.setAttribute("x",s),n.setAttribute("y",l),n.setAttribute("text-anchor","middle"),n.setAttribute("dominant-baseline","middle"),n.setAttribute("fill","#fff"),n.setAttribute("font-size","28px"),n.setAttribute("font-weight","bold"),n.setAttribute("stroke","#000"),n.setAttribute("stroke-width","1"),n.setAttribute("paint-order","stroke"),n.textContent=t.label,p.appendChild(n),t.data){var o=t.data.eventName.match(/(.+?)(?:\s+parkrun)?$/i),r=o?o[1]:t.data.eventName,i="".concat(r," - ").concat(t.data.date),c="text-path-".concat(t.name),a=document.createElementNS("http://www.w3.org/2000/svg","path"),d="",u=245;switch(t.name){case"north":d="M ".concat(180," ").concat(105," A ").concat(u," ").concat(u," 0 0 1 ").concat(520," ").concat(105);break;case"east":d="M ".concat(595," ").concat(180," A ").concat(u," ").concat(u," 0 0 1 ").concat(595," ").concat(520);break;case"south":d="M ".concat(180," ").concat(595," A ").concat(u," ").concat(u," 0 0 0 ").concat(520," ").concat(595);break;case"west":d="M ".concat(105," ").concat(520," A ").concat(u," ").concat(u," 0 0 1 ").concat(105," ").concat(180);break;default:console.warn("Unknown direction: ".concat(t.name))}a.setAttribute("id",c),a.setAttribute("d",d),a.setAttribute("fill","none"),p.appendChild(a);e=document.createElementNS("http://www.w3.org/2000/svg","text"),s=(e.setAttribute("fill","#FFFFFF"),e.setAttribute("font-size","14px"),e.setAttribute("font-weight","bold"),document.createElementNS("http://www.w3.org/2000/svg","textPath"));s.setAttribute("href","#".concat(c)),s.setAttribute("startOffset","50%"),s.setAttribute("text-anchor","middle"),"south"===t.name&&s.setAttribute("side","left"),s.textContent=i,e.appendChild(s),p.appendChild(e)}});var i,c,e=document.createElementNS("http://www.w3.org/2000/svg","defs"),r=document.createElementNS("http://www.w3.org/2000/svg","filter"),o=(r.setAttribute("id","text-shadow"),r.setAttribute("x","-20%"),r.setAttribute("y","-20%"),r.setAttribute("width","140%"),r.setAttribute("height","140%"),document.createElementNS("http://www.w3.org/2000/svg","feDropShadow")),o=(o.setAttribute("dx","0"),o.setAttribute("dy","0"),o.setAttribute("stdDeviation","2"),o.setAttribute("flood-color","#000000"),o.setAttribute("flood-opacity","0.7"),r.appendChild(o),e.appendChild(r),p.appendChild(e),document.createElementNS("http://www.w3.org/2000/svg","circle")),r=(o.setAttribute("cx",m),o.setAttribute("cy",h),o.setAttribute("r",30),o.setAttribute("fill",b.accentColor),p.appendChild(o),document.createElementNS("http://www.w3.org/2000/svg","text"));return r.setAttribute("x",m),r.setAttribute("y",h),r.setAttribute("text-anchor","middle"),r.setAttribute("dominant-baseline","middle"),r.setAttribute("fill",b.backgroundColor),r.setAttribute("font-size","24px"),r.setAttribute("font-weight","bold"),r.textContent="".concat(Math.round(t.completedCount/4*100),"%"),p.appendChild(r),a.appendChild(p),n.appendChild(a),i=n,e=s(),(o=document.createElement("div")).style.marginTop=e.button.marginTop,o.id="compass-download-btn-container",(c=document.createElement("button")).textContent="💾 Save as Image",c.style.padding=e.button.padding,c.style.backgroundColor=b.accentColor,c.style.color=b.backgroundColor,c.style.border="none",c.style.borderRadius="4px",c.style.cursor="pointer",c.style.fontWeight="bold",c.style.fontSize=e.button.fontSize,c.addEventListener("mouseover",function(){this.style.backgroundColor="#e59200"}),c.addEventListener("mouseout",function(){this.style.backgroundColor=b.accentColor}),c.addEventListener("click",function(){c.style.display="none",html2canvas(i,{backgroundColor:b.backgroundColor,scale:2,logging:!1,allowTaint:!0,useCORS:!0}).then(function(t){c.style.display="block";var e=document.createElement("a"),n=(new Date).toISOString().split("T")[0],o=window.location.pathname.split("/")[2]||"parkrunner";e.download="compass-challenge-".concat(o,"-").concat(n,".png"),e.href=t.toDataURL("image/png"),e.click()})}),o.appendChild(c),i.appendChild(o),n}o="https://html2canvas.hertzen.com/dist/html2canvas.min.js",await new Promise((t,e)=>{var n=document.createElement("script");n.src=o,n.onload=t,n.onerror=e,document.head.appendChild(n)}),b={backgroundColor:"#2b223d",accentColor:"#FFA300",completedColor:"#53BA9D",pendingColor:"#666",textColor:"#e0e0e0",subtleTextColor:"#cccccc"},w={north:/north/i,south:/south/i,east:/east/i,west:/west/i},document.querySelector("#results")?(t=t=n((t=>{for(var e={north:null,south:null,east:null,west:null},n=0,o=null,a=0,t=t.querySelectorAll("tr"),r=[],i=(t.forEach(function(t){var e,n,o,t=t.querySelectorAll("td");t.length<5||(e=t[0].textContent.trim(),n=t[1].textContent.trim(),o=t[2].textContent.trim(),(t=t[4].textContent.trim())&&o&&n&&e&&r.push({eventName:e,date:n,eventNumber:o,time:t}))}),r.reverse()),c=0;!o&&c Identifies and displays participants who attended both the launch event and the latest event
[launch-returnees.user.js](https://raw.githubusercontent.com/johnsyweb/tampermonkey-parkrun/refs/heads/main/launch-returnees.user.js)
```javascript
javascript:(async()=>{function m(t){return(t=>{if(Array.isArray(t))return r(t)})(t)||(t=>{if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)})(t)||((t,e)=>{var n;if(t)return"string"==typeof t?r(t,e):"Map"===(n="Object"===(n={}.toString.call(t).slice(8,-1))&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?r(t,e):void 0})(t)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function r(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);ne||a The parkrun p-index is an unofficial statistic that measures the number of different parkrun events a person has completed a specific number of times. To achieve a p-index of 10, you must have completed at least 10 different parkrun events 10 times each. This script calculate the p-index for a parkrunner and displays it on their results page.
[p-index.user.js](https://raw.githubusercontent.com/johnsyweb/tampermonkey-parkrun/refs/heads/main/p-index.user.js)
```javascript
javascript:(async()=>{var r,t,e,n,o,i,a,l,d;function s(t,e){return(t=>{if(Array.isArray(t))return t})(t)||((t,e)=>{var n=null==t?null:"undefined"!=typeof Symbol&&t[Symbol.iterator]||t["@@iterator"];if(null!=n){var r,o,i,a,l=[],d=!0,s=!1;try{if(i=(n=n.call(t)).next,0===e){if(Object(n)!==n)return;d=!1}else for(;!(d=(r=i.call(n)).done)&&(l.push(r.value),l.length!==e);d=!0);}catch(t){s=!0,o=t}finally{try{if(!d&&null!=n.return&&(a=n.return(),Object(a)!==a))return}finally{if(s)throw o}}return l}})(t,e)||((t,e)=>{var n;if(t)return"string"==typeof t?u(t,e):"Map"===(n="Object"===(n={}.toString.call(t).slice(8,-1))&&t.constructor?t.constructor.name:n)||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?u(t,e):void 0})(t,e)||(()=>{throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function u(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=Array(e);n tr").forEach(function(t){var e=t.querySelector("td:nth-child(1) > a").textContent.trim(),n=t.querySelector("td:nth-child(2)").textContent.trim(),t=t.querySelector("td:nth-child(3)").textContent.trim();r.unshift({eventName:e,date:n,eventNumber:t})}),r.reduce(function(t,e){var n=e.eventName,r=e.date,e=e.eventNumber;return t[n]||(t[n]=[]),t[n].push({date:r,eventNumber:e}),t},{}));return Object.entries(t).sort(function(t,e){return e[1].length-t[1].length})}function m(){var t=document.querySelectorAll("#results");return t[t.length-1]}function y(t){var t=t.filter(function(t,e){return s(t,2)[1].length>e}),o=t.length;function i(t){return new Date(t.split("/").reverse().join("-"))}t=t.map(function(t){var t=s(t,2),e=t[0],t=t[1],n=t[0],r=t[o-1];return{eventName:e,eventCount:t.length,firstDate:n.date,firstEventNumber:n.eventNumber,pIndexDate:r.date,pIndexEventNumber:r.eventNumber,firstDateForSorting:i(n.date),pIndexDateForSorting:i(r.date)}}).sort(function(t,e){return t.pIndexDateForSorting-e.pIndexDateForSorting}).slice(0,o).map(function(t){return t.eventName+" ("+t.eventCount+"): "+t.firstDate+" (#"+t.firstEventNumber+") - "+t.pIndexDate+" (#"+t.pIndexEventNumber+")"});return{pIndex:o,contributingEvents:t}}r="https://html2canvas.hertzen.com/dist/html2canvas.min.js",await new Promise((t,e)=>{var n=document.createElement("script");n.src=r,n.onload=t,n.onerror=e,document.head.appendChild(n)}),"undefined"!=typeof module&&module.exports?module.exports={calculatePIndex:y,extractEventDetails:p,findResultsTable:m}:(d=m())?(t=(d=y(p(d))).pIndex,d=d.contributingEvents,a=c(),(l=document.querySelector("h2"))&&((e=document.createElement("div")).textContent="p-index: "+t,e.style.fontSize=a.typography.pIndex,e.style.fontWeight="bold",e.style.marginTop=a.container.marginTop,e.style.backgroundColor="#2b223d",e.style.color="#ffa300",e.style.padding=a.container.padding,e.style.borderRadius="5px",e.style.display="flex",e.style.flexDirection="column",e.style.alignItems="center",e.style.justifyContent="center",e.style.width=a.container.width,e.style.maxWidth=a.container.maxWidth,e.style.marginLeft="auto",e.style.marginRight="auto",e.style.aspectRatio=a.container.aspectRatio,e.setAttribute("id","p-index-display"),(n=document.createElement("ul")).style.listStyleType="none",n.style.padding="0",n.style.marginTop=a.spacing.small,n.style.width="100%",d.forEach(function(t){var e=document.createElement("li");e.textContent=t,e.style.fontWeight="normal",e.style.fontSize=a.typography.listItem,e.style.marginBottom=a.listItem.marginBottom,e.style.textAlign=a.listItem.textAlign,e.style.wordBreak="break-word",n.appendChild(e)}),e.appendChild(n),o=e,t=c(),(d=document.createElement("div")).style.marginTop=t.button.marginTop,d.id="p-index-download-btn-container",(i=document.createElement("button")).textContent="💾 Save as Image",i.style.padding=t.button.padding,i.style.backgroundColor="#ffa300",i.style.color="#2b223d",i.style.border="none",i.style.borderRadius="4px",i.style.cursor="pointer",i.style.fontWeight="bold",i.style.fontSize=t.button.fontSize,i.addEventListener("mouseover",function(){this.style.backgroundColor="#e59200"}),i.addEventListener("mouseout",function(){this.style.backgroundColor="#ffa300"}),i.addEventListener("click",function(){i.style.display="none",html2canvas(o,{backgroundColor:"#2b223d",scale:2,logging:!1,allowTaint:!0,useCORS:!0}).then(function(t){i.style.display="block";var e=document.createElement("a"),n=(new Date).toISOString().split("T")[0],r=window.location.pathname.split("/")[2]||"parkrunner";e.download="p-index-".concat(r,"-").concat(n,".png"),e.href=t.toDataURL("image/png"),e.click()})}),d.appendChild(i),o.appendChild(d),l.parentNode.insertBefore(e,l.nextSibling),a.isMobile||setTimeout(function(){var t=e.getBoundingClientRect(),t=Math.max(t.width,t.height);e.style.width=t+"px",e.style.height=t+"px"},0))):console.error("Results table not found")})();
```
### parkrun Annual Summary
> Adds an annual participation summary (totals, averages, min/max) to parkrun event history pages
[parkrun-annual-summary.user.js](https://raw.githubusercontent.com/johnsyweb/tampermonkey-parkrun/refs/heads/main/parkrun-annual-summary.user.js)
```javascript
javascript:(async()=>{var r,w,E,S;function p(e){return(p="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function A(e){return(e=>{if(Array.isArray(e))return o(e)})(e)||(e=>{if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)})(e)||n(e)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function F(){var h,e="function"==typeof Symbol?Symbol:{},t=e.iterator||"@@iterator",n=e.toStringTag||"@@toStringTag";function r(e,t,n,r){var o,a,l,i,s,c,u,d,p,t=t&&t.prototype instanceof f?t:f,t=Object.create(t.prototype);return b(t,"_invoke",(o=e,a=n,u=r||[],d=!1,p={p:c=0,n:0,v:h,a:m,f:m.bind(h,4),d:function(e,t){return l=e,i=0,s=h,p.n=t,y}},function(e,t,n){if(1t||a(e=((e,t)=>{if("object"!=p(e)||!e)return e;var n=e[Symbol.toPrimitive];if(void 0===n)return("string"===t?String:Number)(e);if("object"!=p(n=n.call(e,t||"default")))return n;throw new TypeError("@@toPrimitive must return a primitive value.")})(e,"string"),"symbol"==p(e)?e:e+""))(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(o)):t(Object(o)).forEach(function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(o,e))})}return r}function N(e,t){return(e=>{if(Array.isArray(e))return e})(e)||((e,t)=>{var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,a,l,i=[],s=!0,c=!1;try{if(a=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;s=!1}else for(;!(s=(r=a.call(n)).done)&&(i.push(r.value),i.length!==t);s=!0);}catch(e){c=!0,o=e}finally{try{if(!s&&null!=n.return&&(l=n.return(),Object(l)!==l))return}finally{if(c)throw o}}return i}})(e,t)||n(e,t)||(()=>{throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function n(e,t){var n;if(e)return"string"==typeof e?o(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?o(e,t):void 0}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n\n
parkrun Annual Summary
\n \n \n "),e.a(2,'parkrun Annual Summary - '.concat(n.eventShortName,"").concat(a,"").concat(l).concat(r.outerHTML,""))},e)}))).apply(this,arguments)}function P(){return e.apply(this,arguments)}function e(){return(e=O(F().m(function e(t,n){var r,o;return F().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,function(){return l.apply(this,arguments)}(t,n);case 1:return r=e.v,o="parkrun-annual-summary-".concat(n.eventShortName,"-").concat(n.generatedAtISO,".html"),e.a(2,{blob:new Blob([r],{type:"text/html"}),filename:o})}},e)}))).apply(this,arguments)}function i(){return(i=O(F().m(function e(t,n){var r,o,a,l,i,s,c;return F().w(function(e){for(;;)switch(e.p=e.n){case 0:return e.p=0,r="".concat(n,"/").concat(t,"/results/eventhistory/"),e.n=1,fetch(r);case 1:return r=e.v,e.n=2,r.text();case 2:return o=e.v,c=new DOMParser,c=c.parseFromString(o,"text/html"),o=null!=(o=null==(o=c.querySelector("h1"))?void 0:o.textContent.trim())?o:"".concat(t," Event History"),a=[],l=[],i=[],s=[],c=c.querySelectorAll("tr.Results-table-row"),Array.from(c).reverse().forEach(function(e){var t=e.getAttribute("data-parkrun"),t=(t&&a.push(t),e.getAttribute("data-date")),t=(t&&(t=new Date(t).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"}),l.push(t)),e.getAttribute("data-finishers")),t=(t&&i.push(parseInt(t,10)),e.getAttribute("data-volunteers"));t&&s.push(parseInt(t,10))}),e.a(2,{eventName:t,title:o,eventNumbers:a,dates:l,finishers:i,volunteers:s});case 3:return e.p=3,c=e.v,console.error("Failed to fetch event history for ".concat(t,":"),c),e.a(2,null)}},e,null,[[0,3]])}))).apply(this,arguments)}function R(a){var l={};return a.dates.forEach(function(e,t){var e=new Date(e).getFullYear(),n=null!=(n=a.finishers[t])?n:0,r=null!=(r=a.volunteers[t])?r:0,o=a.eventNumbers[t],e=(l[e]||(l[e]={year:e,eventCount:0,totalFinishers:0,totalVolunteers:0,minFinishers:null,maxFinishers:null,minVolunteers:null,maxVolunteers:null}),l[e]);e.eventCount++,e.totalFinishers+=n,e.totalVolunteers+=r,(null===e.minFinishers||ne.maxFinishers.value)&&(e.maxFinishers={value:n,date:a.dates[t],eventNumber:o}),(null===e.minVolunteers||re.maxVolunteers.value)&&(e.maxVolunteers={value:r,date:a.dates[t],eventNumber:o})}),Object.keys(l).map(Number).sort(function(e,t){return e-t}).map(function(e){var t=l[e];return{year:e,eventCount:t.eventCount,totalFinishers:t.totalFinishers,totalVolunteers:t.totalVolunteers,avgFinishers:Math.round(t.totalFinishers/t.eventCount),avgVolunteers:Math.round(t.totalVolunteers/t.eventCount),minFinishers:t.minFinishers,maxFinishers:t.maxFinishers,minVolunteers:t.minVolunteers,maxVolunteers:t.maxVolunteers,finishersGrowth:null,volunteersGrowth:null}}).map(function(e,t,n){return 0===t?e:(t=(n=n[t-1]).avgFinishers?(e.avgFinishers-n.avgFinishers)/n.avgFinishers*100:null,n=n.avgVolunteers?(e.avgVolunteers-n.avgVolunteers)/n.avgVolunteers*100:null,T(T({},e),{},{finishersGrowth:t,volunteersGrowth:n}))})}function m(e){return e?e.value.toLocaleString():"-"}function h(e){var t,n;return null===e||Number.isNaN(e)?"-":(t=0').concat(t).concat(e.toFixed(1),"%"))}function L(o){var e=document.createElement("div"),t=(e.style.marginBottom="15px",e.style.padding="10px",e.style.backgroundColor=w.backgroundColor,e.style.borderRadius="6px",e.style.display="flex",e.style.alignItems="center",e.style.gap="10px",e.style.flexWrap="wrap",document.createElement("span")),a=(t.textContent="Compare with:",t.style.color=w.textColor,t.style.fontWeight="bold",e.appendChild(t),document.createElement("select")),t=(a.style.padding="6px 12px",a.style.backgroundColor="#3a3250",a.style.color=w.textColor,a.style.border="1px solid ".concat(w.gridColor),a.style.borderRadius="4px",a.style.cursor="pointer",document.createElement("option")),l=(t.value="",t.textContent="-- Select parkrun --",a.appendChild(t),o.forEach(function(e){var t=document.createElement("option");t.value=e.properties.eventname,t.textContent="".concat(e.properties.EventShortName," (").concat(e.distance.toFixed(1),"km)"),a.appendChild(t)}),document.createElement("button"));return l.textContent="+ Add",l.style.padding="6px 12px",l.style.backgroundColor=w.lineColor,l.style.color="#2b223d",l.style.border="none",l.style.borderRadius="4px",l.style.cursor="pointer",l.style.fontWeight="bold",l.addEventListener("click",O(F().m(function e(){var t,n,r;return F().w(function(e){for(;;)switch(e.n){case 0:if(t=a.value){e.n=1;break}return e.a(2);case 1:if(S.comparisonEvents.some(function(e){return e.eventName===t}))return alert("This parkrun is already selected for comparison"),e.a(2);e.n=2;break;case 2:return l.disabled=!0,l.textContent="Loading...",n=j(),e.n=3,function(){return i.apply(this,arguments)}(t,n.url);case 3:(n=e.v)?(r=o.find(function(e){return e.properties.eventname===t}),S.comparisonEvents.push(T(T({},n),{},{distance:null==r?void 0:r.distance})),W()):alert("Failed to fetch event history"),l.disabled=!1,l.textContent="+ Add",a.value="";case 4:return e.a(2)}},e)}))),e.appendChild(a),e.appendChild(l),e}function M(e,t){var n=R(e);if(0===n.length)return null;var e=document.createElement("div"),r=(e.className="tab-content",e.style.display=0===t?"block":"none",e.style.backgroundColor=w.backgroundColor,e.style.padding="15px",e.style.borderRadius="6px",document.createElement("div")),o=(r.style.overflowX="auto",document.createElement("table")),a=(o.className="annualSummaryTable",o.style.width="100%",o.style.borderCollapse="collapse",o.style.fontSize="14px",o.style.color=w.textColor,o.style.backgroundColor=w.backgroundColor,{key:"year",dir:"asc"}),l=[{key:"year",label:"Year",align:"left"},{key:"eventCount",label:"Events",align:"center"},{key:"totalFinishers",label:"Finishers Total",align:"right",color:w.barColor},{key:"minFinishers",label:"Finishers Min",align:"right",color:w.barColor},{key:"maxFinishers",label:"Finishers Max",align:"right",color:w.barColor},{key:"avgFinishers",label:"Finishers Avg",align:"right",color:w.barColor},{key:"finishersGrowth",label:"Finishers YoY",align:"right"},{key:"totalVolunteers",label:"Volunteers Total",align:"right",color:w.lineColor},{key:"minVolunteers",label:"Volunteers Min",align:"right",color:w.lineColor},{key:"maxVolunteers",label:"Volunteers Max",align:"right",color:w.lineColor},{key:"avgVolunteers",label:"Volunteers Avg",align:"right",color:w.lineColor},{key:"volunteersGrowth",label:"Volunteers YoY",align:"right"}],i=document.createElement("thead"),s=document.createElement("tr"),c=(s.style.borderBottom="2px solid ".concat(w.gridColor),l.forEach(function(e){var t=document.createElement("th");t.textContent=e.label,t.style.padding="10px",t.style.textAlign=e.align,t.style.cursor="pointer",e.color&&(t.style.color=e.color),t.addEventListener("click",function(){a.key===e.key?a.dir="asc"===a.dir?"desc":"asc":(a.key=e.key,a.dir="desc"),u()}),s.appendChild(t)}),i.appendChild(s),o.appendChild(i),document.createElement("tbody"));function u(){c.innerHTML="",A(n).sort(function(e,t){function n(e){return null==(e=e[r])?-1/0:"object"===p(e)&&void 0!==e.value?e.value:e}var r=a.key,o="asc"===a.dir?1:-1,e=n(e),t=n(t);return e===t?0:t'.concat(e.year,'\n ').concat(e.eventCount,'\n ').concat(e.totalFinishers.toLocaleString(),'\n ').concat(m(e.minFinishers),'\n ').concat(m(e.maxFinishers),'\n ').concat(e.avgFinishers,'\n ').concat(h(e.finishersGrowth),'\n ').concat(e.totalVolunteers.toLocaleString(),'\n ').concat(m(e.minVolunteers),'\n ').concat(m(e.maxVolunteers),'\n ').concat(e.avgVolunteers,'\n ').concat(h(e.volunteersGrowth),"\n "),c.appendChild(t)})}o.appendChild(c),r.appendChild(o),e.appendChild(r),u();var l=document.createElement("div"),i=(l.style.display="grid",l.style.gridTemplateColumns="1fr 1fr",l.style.gap="20px",l.style.marginTop="20px",document.createElement("div")),o=(i.style.minWidth="0",document.createElement("canvas")),r=(o.className="annualTotalsChart-".concat(t),i.appendChild(o),document.createElement("div")),d=(r.style.minWidth="0",document.createElement("canvas"));return d.className="annualGrowthChart-".concat(t),r.appendChild(d),l.appendChild(i),l.appendChild(r),e.appendChild(l),"undefined"!=typeof Chart&&(t=o.getContext("2d"),i=d.getContext("2d"),r=n.map(function(e){return e.year.toString()}),new Chart(t,{type:"bar",data:{labels:r,datasets:[{label:"Total Finishers",data:n.map(function(e){return e.totalFinishers}),backgroundColor:w.barColor,borderColor:w.barColor,borderWidth:1},{label:"Total Volunteers",data:n.map(function(e){return e.totalVolunteers}),backgroundColor:w.lineColor,borderColor:w.lineColor,borderWidth:1}]},options:{animation:!1,responsive:!0,maintainAspectRatio:!0,aspectRatio:1.3,plugins:{legend:{labels:{color:w.textColor}},title:{display:!0,text:"Annual Totals",color:w.textColor}},scales:{x:{title:{display:!0,text:"Year",color:w.textColor},ticks:{color:w.subtleTextColor},grid:{color:w.gridColor}},y:{beginAtZero:!0,title:{display:!0,text:"Participants",color:w.textColor},ticks:{precision:0,color:w.subtleTextColor},grid:{color:w.gridColor}}}}}),l=n.filter(function(e){return null!==e.finishersGrowth}),new Chart(i,{type:"line",data:{labels:l.map(function(e){return e.year.toString()}),datasets:[{label:"Finishers Growth",data:l.map(function(e){return e.finishersGrowth}),borderColor:w.barColor,backgroundColor:w.barColor,borderWidth:2,pointRadius:4,pointBackgroundColor:w.barColor,fill:!1,tension:.2},{label:"Volunteers Growth",data:l.map(function(e){return e.volunteersGrowth}),borderColor:w.lineColor,backgroundColor:w.lineColor,borderWidth:2,pointRadius:4,pointBackgroundColor:w.lineColor,fill:!1,tension:.2}]},options:{animation:!1,responsive:!0,maintainAspectRatio:!0,aspectRatio:1.3,plugins:{legend:{labels:{color:w.textColor}},title:{display:!0,text:"Year-over-Year Growth (%)",color:w.textColor}},scales:{x:{title:{display:!0,text:"Year",color:w.textColor},ticks:{color:w.subtleTextColor},grid:{color:w.gridColor}},y:{title:{display:!0,text:"Growth (%)",color:w.textColor},ticks:{color:w.subtleTextColor,callback:function(e){return e+"%"}},grid:{color:w.gridColor}}}}})),e}function G(e){var t=document.createElement("div");return t.style.minWidth="0",t.appendChild(e),t}function I(e,t,o,n,a,r,l){l=6{var n=document.createElement("script");n.src=r,n.onload=e,n.onerror=t,document.head.appendChild(n)}),w={backgroundColor:"#1c1b2a",barColor:"#f59e0b",lineColor:"#22d3ee",textColor:"#f3f4f6",subtleTextColor:"#d1d5db",gridColor:"rgba(243, 244, 246, 0.18)"},E=["#f59e0b","#22d3ee","#f97316","#10b981","#a855f7","#ef4444","#3b82f6","#84cc16"],S={currentEvent:null,comparisonEvents:[],allParkruns:null},function(){c.apply(this,arguments)}()})();
```
### parkrun Cancellation Impact
> Analyzes the impact of cancelled parkrun events on nearby alternatives
[parkrun-cancellation-impact.user.js](https://raw.githubusercontent.com/johnsyweb/tampermonkey-parkrun/refs/heads/main/parkrun-cancellation-impact.user.js)
```javascript
javascript:(async()=>{var r,k,v,C,g,w;function a(e){return(a="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function E(){var g,e="function"==typeof Symbol?Symbol:{},t=e.iterator||"@@iterator",n=e.toStringTag||"@@toStringTag";function r(e,t,n,r){var o,a,l,s,i,c,d,u,p,t=t&&t.prototype instanceof m?t:m,t=Object.create(t.prototype);return b(t,"_invoke",(o=e,a=n,d=r||[],u=!1,p={p:c=0,n:0,v:g,a:h,f:h.bind(g,4),d:function(e,t){return l=e,s=0,i=g,p.n=t,y}},function(e,t,n){if(1t||a{if(Array.isArray(e))return o(e)})(e)||(e=>{if("undefined"!=typeof Symbol&&null!=e[Symbol.iterator]||null!=e["@@iterator"])return Array.from(e)})(e)||n(e)||(()=>{throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function t(t,e){var n,r=Object.keys(t);return Object.getOwnPropertySymbols&&(n=Object.getOwnPropertySymbols(t),e&&(n=n.filter(function(e){return Object.getOwnPropertyDescriptor(t,e).enumerable})),r.push.apply(r,n)),r}function d(r){for(var e=1;e(e=((e,t)=>{if("object"!=a(e)||!e)return e;var n=e[Symbol.toPrimitive];if(void 0===n)return("string"===t?String:Number)(e);if("object"!=a(n=n.call(e,t||"default")))return n;throw new TypeError("@@toPrimitive must return a primitive value.")})(e,"string"),"symbol"==a(e)?e:e+""))(e))in t?Object.defineProperty(t,e,{value:n,enumerable:!0,configurable:!0,writable:!0}):t[e]=n}):Object.getOwnPropertyDescriptors?Object.defineProperties(r,Object.getOwnPropertyDescriptors(o)):t(Object(o)).forEach(function(e){Object.defineProperty(r,e,Object.getOwnPropertyDescriptor(o,e))})}return r}function u(e,t){return(e=>{if(Array.isArray(e))return e})(e)||((e,t)=>{var n=null==e?null:"undefined"!=typeof Symbol&&e[Symbol.iterator]||e["@@iterator"];if(null!=n){var r,o,a,l,s=[],i=!0,c=!1;try{if(a=(n=n.call(e)).next,0===t){if(Object(n)!==n)return;i=!1}else for(;!(i=(r=a.call(n)).done)&&(s.push(r.value),s.length!==t);i=!0);}catch(e){c=!0,o=e}finally{try{if(!i&&null!=n.return&&(l=n.return(),Object(l)!==l))return}finally{if(c)throw o}}return s}})(e,t)||n(e,t)||(()=>{throw new TypeError("Invalid attempt to destructure non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")})()}function n(e,t){var n;if(e)return"string"==typeof e?o(e,t):"Map"===(n="Object"===(n={}.toString.call(e).slice(8,-1))&&e.constructor?e.constructor.name:n)||"Set"===n?Array.from(e):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?o(e,t):void 0}function o(e,t){(null==t||t>e.length)&&(t=e.length);for(var n=0,r=Array(t);n{var e=null!=(e=null==(e=document.querySelector("h1"))?void 0:e.textContent.trim())?e:"Event History",n=[],r=[],o=[],a=[],l=[],t=document.querySelectorAll("tr.Results-table-row");return Array.from(t).reverse().forEach(function(e){var t=e.getAttribute("data-parkrun"),t=(t&&n.push(t),e.getAttribute("data-date")),t=(t&&(o.push(t),t=new Date(t).toLocaleDateString(void 0,{year:"numeric",month:"short",day:"numeric"}),r.push(t)),e.getAttribute("data-finishers")),t=(t&&a.push(parseInt(t,10)),e.getAttribute("data-volunteers"));t&&l.push(parseInt(t,10))}),{title:e,eventNumbers:n,dates:r,rawDates:o,finishers:a,volunteers:l}})()).eventNumbers.length)return console.log("No event history data found"),e.a(2);e.n=1;break;case 1:if(n=(e=>{var t=e.rawDates.map(x);if(t.length<2)return null;for(var n=[],r=1;r{var t=document.createElement("div"),n=(t.style.padding="20px",t.style.backgroundColor="#2b223d",t.style.borderRadius="8px",t.style.marginBottom="20px",t.style.border="1px solid ".concat(k.gridColor),document.createElement("h3")),r=(n.textContent="Cancellation Impact Analysis",n.style.color=k.barColor,n.style.margin="0 0 15px 0",n.style.fontSize="20px",t.appendChild(n),(n=document.createElement("div")).style.fontSize="16px",n.style.color=k.textColor,n.style.marginBottom="15px",n.innerHTML='').concat(e,""),t.appendChild(n),(e=document.createElement("div")).style.fontSize="14px",e.style.lineHeight="1.8",e.style.color=k.subtleTextColor,e.style.marginBottom="18px",e.innerHTML="📍 Analyzing impact on nearby parkruns within 50km",t.appendChild(e),document.createElement("button"));return r.textContent="▶ Start Analysis",r.className="start-analysis-btn",r.style.padding="12px 24px",r.style.backgroundColor=k.lineColor,r.style.color="#1c1b2a",r.style.border="none",r.style.borderRadius="6px",r.style.cursor="pointer",r.style.fontWeight="bold",r.style.fontSize="14px",r.style.transition="all 0.2s",r.addEventListener("mouseenter",function(){r.style.backgroundColor="#0ea5e9",r.style.transform="translateY(-1px)"}),r.addEventListener("mouseleave",function(){r.style.backgroundColor=k.lineColor,r.style.transform="translateY(0)"}),t.appendChild(r),{section:t,startButton:r}})(l),s=l.section,i=l.startButton,o.appendChild(s),c("h1",o),i.addEventListener("click",function(){i.disabled=!0,i.textContent="Starting...",i.style.opacity="0.6",i.style.cursor="not-allowed";var e=(()=>{var e=document.createElement("div"),t=(e.className="parkrun-cancellation-progress",e.style.padding="15px",e.style.backgroundColor=k.backgroundColor,e.style.borderRadius="6px",e.style.marginBottom="15px",e.style.border="1px solid ".concat(k.gridColor),document.createElement("h4")),r=(t.textContent="Analyzing Nearby parkrun Impact",t.style.margin="0 0 12px 0",t.style.color=k.barColor,e.appendChild(t),(t=document.createElement("div")).style.width="100%",t.style.height="20px",t.style.backgroundColor="#3a3250",t.style.borderRadius="4px",t.style.marginBottom="10px",t.style.overflow="hidden",document.createElement("div")),o=(r.style.width="0%",r.style.height="100%",r.style.backgroundColor=k.lineColor,r.style.transition="width 0.3s ease",t.appendChild(r),e.appendChild(t),document.createElement("div")),n=(o.style.fontSize="13px",o.style.color=k.subtleTextColor,o.style.marginBottom="12px",e.appendChild(o),document.createElement("div"));return n.style.fontSize="12px",n.style.color=k.lineColor,n.style.fontWeight="bold",n.style.marginBottom="10px",e.appendChild(n),(t=document.createElement("button")).textContent="Stop Analysis",t.style.padding="6px 12px",t.style.backgroundColor=k.alertColor,t.style.color=k.textColor,t.style.border="none",t.style.borderRadius="4px",t.style.cursor="pointer",t.style.fontWeight="bold",t.style.fontSize="12px",e.appendChild(t),{progressSection:e,updateProgress:function(e,t){var n=Math.round(e/t*100);r.style.width=n+"%",o.textContent="".concat(e,"/").concat(t," parkruns analyzed")},updateStatus:function(e){n.textContent=e},stop:t,hide:function(){e.style.display="none"}}})();s.insertAdjacentElement("afterend",e.progressSection),w.fetchController=new AbortController,function(){h.apply(this,arguments)}(e,o,s)});case 5:return e.a(2)}},e)}))).apply(this,arguments)}function h(){return(h=D(E().m(function e(t,n,r){var o,a,l,c,s,i,d,u,p,h,g,y,m,f,b;return E().w(function(e){for(;;)switch(e.p=e.n){case 0:o=T(),a=w.nearbyParkruns,s=(e=>{var t=e.rawDates.map(x);if(t.length<2)return[];for(var n=[],r=1;r{var n=[],r=(6-(e=x(e.toISOString().split("T")[0])).getUTCDay())%7,o=(0===r&&(r=7),new Date(e));for(o.setUTCDate(o.getUTCDate()+r);o{for(var n=t.toISOString().split("T")[0],r=0;r⚠ No Valid Analysis Dates\n
All detected cancellation dates appear to be part of global cancellation periods (e.g., COVID-19).
\n \n No nearby parkruns held events on these dates, indicating system-wide cancellations rather than single-event cancellations.\n
\n '),n.appendChild(m),t.stop.addEventListener("click",function(){t.hide()}),e.a(2);e.n=9;break;case 9:t.updateStatus("Analysis complete! Found ".concat(g.length," valid cancellation date(s)")),(m=document.querySelector(".start-analysis-btn"))&&(m.style.display="none"),t.stop.textContent="Close",t.stop.style.backgroundColor=k.successColor,w.resultsByDate=h,w.cancellationDates=y,w.analysisComplete=!0,-1===w.currentCancellationIndex&&0{var e=document.createElement("div"),t=(e.className="parkrun-cancellation-nav",e.style.padding="15px",e.style.backgroundColor="#2b223d",e.style.borderRadius="8px",e.style.marginBottom="20px",e.style.border="1px solid ".concat(k.gridColor),document.createElement("div"));t.style.color=k.textColor,t.style.fontSize="14px",t.style.marginBottom="12px",t.innerHTML="\n ".concat(a.length," Cancellation Date").concat(1!==a.length?"s":"",' Available\n \n Use dropdown or buttons to navigate • Keyboard: ← →\n \n '),e.appendChild(t);(t=document.createElement("div")).style.display="flex",t.style.alignItems="center",t.style.gap="8px",t.style.flexWrap="wrap";var n=document.createElement("button"),s=(n.textContent="←",n.style.padding="6px 10px",l\n parkrun Cancellation Impact
\n \n \n "),e.a(2,'parkrun Cancellation Impact - '.concat(n.eventShortName," - ").concat(n.cancellationDateStr,"").concat(a,"").concat(l).concat(r.outerHTML,""))},e)}))).apply(this,arguments)}function P(){return e.apply(this,arguments)}function e(){return(e=D(E().m(function e(t,n){var r,o;return E().w(function(e){for(;;)switch(e.n){case 0:return e.n=1,function(){return y.apply(this,arguments)}(t,n);case 1:return r=e.v,o="parkrun-cancellation-impact-".concat(n.eventShortName,"-").concat(n.cancellationDateStr,".html"),e.a(2,{blob:new Blob([r],{type:"text/html"}),filename:o})}},e)}))).apply(this,arguments)}function I(e,t){var n=A(t,C),t=(n.end=new Date(t),n.end.setUTCDate(n.end.getUTCDate()-1),O(e,n.start,n.end));return{window:n,filtered:t,baseline:L(t)}}function F(e,t,n,r){function o(e){var t=p.querySelector("tbody"),c=(t&&t.remove(),document.createElement("tbody"));e.forEach(function(e){var t,n,r=document.createElement("tr"),o=(r.style.borderBottom="1px solid ".concat(k.gridColor),r.style.transition="background-color 0.15s ease",null!==e.eventOnDate),a=(o||(r.style.opacity="0.6"),r.addEventListener("mouseenter",function(){r.style.backgroundColor=o?"rgba(34, 211, 238, 0.08)":"rgba(243, 244, 246, 0.03)"}),r.addEventListener("mouseleave",function(){r.style.backgroundColor="transparent"}),document.createElement("td")),l=(a.style.padding="10px",a.style.textAlign="left",a.style.fontWeight="bold",document.createElement("a")),a=(l.href="".concat(T().url,"/").concat(e.eventName,"/results/eventhistory/"),l.textContent=e.displayName||e.eventName,l.style.color=k.lineColor,l.style.textDecoration="none",l.target="_blank",l.addEventListener("mouseenter",function(){l.style.textDecoration="underline"}),l.addEventListener("mouseleave",function(){l.style.textDecoration="none"}),a.appendChild(l),r.appendChild(a),document.createElement("td")),a=(a.style.padding="10px",a.style.textAlign="right",a.style.color=k.subtleTextColor,a.textContent="".concat(e.distance,"km"),r.appendChild(a),document.createElement("td")),a=(a.style.padding="10px",a.style.textAlign="right",e.eventOnDate&&e.eventOnDate.eventNumber?(a.textContent=e.eventOnDate.eventNumber,a.style.color=k.textColor):(a.textContent="—",a.style.color=k.subtleTextColor),r.appendChild(a),document.createElement("td")),a=(a.style.padding="10px",a.style.textAlign="right",a.innerHTML="".concat(e.baseline.avgFinishers," / ").concat(e.baseline.avgVolunteers),r.appendChild(a),document.createElement("td")),a=(a.style.padding="10px",a.style.textAlign="right",e.eventOnDate?a.innerHTML="".concat(e.eventOnDate.finishers," / ").concat(e.eventOnDate.volunteers):(a.textContent="—",a.style.color=k.subtleTextColor),r.appendChild(a),document.createElement("td")),s=(a.style.padding="10px",a.style.textAlign="right",e.change?(t=0').concat(t).concat(e.change.finishersChange,' /\n ').concat(i).concat(e.change.volunteersChange,"\n ")):(a.textContent="—",a.style.color=k.subtleTextColor),r.appendChild(a),document.createElement("td")),i=(s.style.padding="10px",s.style.textAlign="right",e.change?(t=0').concat(n).concat(e.change.finishersPct.toFixed(1),"%")):(s.textContent="—",s.style.color=k.subtleTextColor),r.appendChild(s),document.createElement("td"));i.style.padding="10px",i.style.textAlign="right",e.eventOnDate?e.change.finishersChange<-5?(i.textContent="↓ Loss",i.style.color=k.alertColor):5