{"version":3,"file":"helpers-8a01db58.js","sources":["../../../src/js/helpers.js"],"sourcesContent":["import FetchWrapper from './fetch-wrapper.js';\nconst thisWebsiteAPI = new FetchWrapper(`${window.location.protocol}//${window.location.host}`);\n\n/*\nLinear Interpolation Functions\nhttps://www.trysmudford.com/blog/linear-interpolation-functions/\n*/\n\n/**\n * Returns the value between two provided numbers at a specified decimal midpoint\n * @param {number} lowBound The low bound\n * @param {number} highBound The high bound\n * @param {number} target The decimal location between X and Y who's value we want to find\n * @returns {number} The value at the target\n * @example\n * lerp(20, 80, 0.5); // returns 50\n */\nexport function lerp(lowBound, highBound, target) {\n\treturn lowBound * (1 - target) + highBound * target;\n}\n\n/**\n * If your number falls within the bounds of the min & max, it’ll return it. If not, it’ll return either the minimum it’s smaller, or the maximum if it’s bigger\n * @param {number} test The number to be tested\n * @param {number} min The minimum allowed\n * @param {number} max The maximum allowed\n * @returns {number} TEST if its value lies between MIN and MAX, MIN if TEST is lower than MIN, MAX if TEST is higher than MAX\n * @example\n * clamp(20, 80, 0.5); // returns 20\n */\nexport function clamp(test, min = 0, max = 1) {\n\treturn Math.min(max, Math.max(min, test));\n}\n\n/**\n * Returns the decimal position of a given value between two provided endpoints (clamped for safety)\n * @param {number} low The low bound\n * @param {number} high The high bound\n * @param {number} value The value between LOW and HIGH\n * @returns {number} The decimal position of VALUE, between LOW and HIGH\n * @example\n * invlerp(20, 80, 50); // returns 0.5\n */\nexport function invlerp(low, high, value) {\n\treturn clamp(\n\t\t(value - low) / (high - low)\n\t);\n}\n\n/**\n * Converts a value from one data range to another\n * @param {number} low1 The low bound of range 1\n * @param {number} high1 The high bound of range 1\n * @param {number} low2 The low bound of range 2\n * @param {number} high2 The high bound of range 2\n * @param {number} target1 The target value between low1 and high1\n * @returns {number} The value between low2 and high2 at the equivalent position\n * @example\n * range(10, 100, 2000, 20000, 50); // returns 10000\n */\nexport function range(low1, high1, low2, high2, target1) {\n\treturn lerp(\n\t\tlow2,\n\t\thigh2,\n\t\tinvlerp(\n\t\t\tlow1,\n\t\t\thigh1,\n\t\t\ttarget1\n\t\t)\n\t);\n}\n\n/**\n * Returns the type of interactions the device supports (click / mouseover)\n * @returns {string}\n */\nexport function interactionType() {\n\tlet interactionType = 'mouseover';\n\n\tif( window.matchMedia('(hover: hover)') ) { // desktop\n\t\tinteractionType = 'mouseover'; }\n\tif( window.matchMedia('(hover: none) and (pointer: coarse)') ) { // touchscreen\n\t\tinteractionType = 'click'; }\n\tif( window.matchMedia('(hover: none) and (pointer: fine)') ) { // stylus\n\t\tinteractionType = 'click'; }\n\tif( window.matchMedia('(hover: hover) and (pointer: coarse)') ) { // Wii/Kinect/etc\n\t\tinteractionType = 'mouseover'; }\n\tif( window.matchMedia('(hover: hover) and (pointer: fine)') ) { // mouse\n\t\tinteractionType = 'mouseover'; }\n\n\treturn interactionType;\n}\n\n/**\n * Add and update a data attribute on the HTML element to indicate the state of the page's scroll\n */\nfunction windowHasScrolled() {\n\tconst html = document.querySelector('html');\n\n\tif (window.scrollY > 0) {\n\t\thtml.dataset.pageHasScrolled = 'true';\n\t} else {\n\t\thtml.dataset.pageHasScrolled = 'false';\n\t}\n}\n\n/**\n * Adds an event listener to the window which will indicate if the page has scrolled by updating `data-page-has-scrolled` to true or false.\n */\nexport function initWindowHasScrolled() {\n\twindow.addEventListener('scroll', function() {\n\t\twindowHasScrolled();\n\t});\n}\n\nexport function sidebarController() {\n\n\tconst html = document.querySelector('html');\n\tconst sidebarTrigger = document.getElementById(\"sidebarTrigger\");\n\tsidebarTrigger.addEventListener('click', function() {\n\t\thtml.classList.toggle(\"sidebar-open\")\n\t});\n}\n\n/**\n * Finds HTML elements with a `data-scroll-reveal` attribute, and uses an IntersectionObserver to add a `data-in-viewport` attribute to that element when it's scrolled inside the viewport.\n * @param rootMargin - defaults to `0% 0% -20% 0%`\n */\nexport function scrollAnimatedBlocks(rootMargin = \"0% 0% -20% 0%\") {\n\tif (!!window.IntersectionObserver) {\n\t\tdocument.querySelector('html').dataset.supportsIntersectionObserver = 'true';\n\n\t\tlet observer = new IntersectionObserver((watchList, observer) => {\n\t\t\twatchList.forEach(watchedElement => {\n\t\t\t\tif (watchedElement.isIntersecting) {\n\t\t\t\t\twatchedElement.target.dataset.inViewport = 'true';\n\t\t\t\t\tobserver.unobserve(watchedElement.target);\n\t\t\t\t}\n\t\t\t});\n\t\t}, {\n\t\t\trootMargin: rootMargin\n\t\t});\n\n\t\tdocument.querySelectorAll('[data-reveal-on-scroll]').forEach(watchTarget => {\n\t\t\tobserver.observe(watchTarget);\n\t\t});\n\t}\n\telse {\n\t\tconsole.log(`Browser doesn't support IntersectionObserver`)\n\t}\n}\n\n/**\n * For images on our website.\n * @param {Node} popupLink\n */\nfunction handlePopupImageLink(popupLink) {\n\tpopupLink.addEventListener('click', (e) => {\n\t\te.preventDefault();\n\t\tlet parser = new DOMParser();\n\n\t\tlet clickedLink = e.currentTarget;\n\t\tclickedLink.dataset.fetchStatus = \"loading\";\n\n\t\tthisWebsiteAPI\n\t\t\t.getHtml(clickedLink.getAttribute('href'))\n\t\t\t.then(response => {\n\t\t\t\tlet responseAsDom = parser.parseFromString(response, \"text/html\");\n\t\t\t\tlet imageWeWant = responseAsDom.querySelector('#ajaxcontent').outerHTML;\n\n\t\t\t\tdocument.querySelector('body').insertAdjacentHTML('afterbegin', `\n\t\t\t\t\t\n\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t${imageWeWant}\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t\t\t\n\t\t\t\t\t\t\t
\n\t\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t`);\n\n\t\t\t\tlet lightbox = document.querySelector('#lightbox');\n\t\t\t\tlightbox.showModal();\n\t\t\t\tclickedLink.dataset.fetchStatus = 'loaded';\n\n\t\t\t\t// remove the entire thing from the DOM when closed, getting us back to where we were before clicked\n\t\t\t\tdocument.querySelector('#lightbox').addEventListener(\"close\", e => {\n\t\t\t\t\te.target.remove();\n\t\t\t\t});\n\t\t\t})\n\t\t\t.catch(error => {\n\t\t\t\tconsole.error(error);\n\t\t\t});\n\t})\n}\n\n/**\n * For links to youtube\n * @param {Node} popupLink\n */\nfunction handlePopupYoutubeLink(popupLink) {\n\tpopupLink.addEventListener('click', (e) => {\n\t\te.preventDefault();\n\t\tlet clickedLink = e.currentTarget;\n\t\tlet clickedUrl = new URL(clickedLink.href);\n\t\tlet videoId = clickedUrl.searchParams.get('v');\n\t\tlet videoWeWant = `\n\t\t\t\n\t\t`;\n\n\t\tclickedLink.dataset.fetchStatus = \"loading\";\n\n\t\tdocument.querySelector('body').insertAdjacentHTML('afterbegin', `\n\t\t\t\n\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t${videoWeWant}\n\t\t\t\t\t
\n\t\t\t\t\t
\n\t\t\t\t\t\t\n\t\t\t\t\t
\n\t\t\t\t
\n\t\t\t
\n\t\t`);\n\n\t\tlet lightbox = document.querySelector('#lightbox') ?? null;\n\t\tlet theVideo = document.querySelector('#lightbox iframe');\n\n\t\tlightbox.showModal();\n\t\tclickedLink.dataset.fetchStatus = \"loaded\";\n\n\t\t// remove the entire thing from the DOM when closed, to stop the video from continuing to play\n\t\tdocument.querySelector('#lightbox').addEventListener(\"close\", e => {\n\t\t\te.target.remove();\n\t\t});\n\t});\n}\n\n/**\n * Activate all anchor links that have a `data-popup=\"image\"` attribute.\n */\nexport function initPopupImages() {\n\tdocument.querySelectorAll('a[data-popup=\"image\"]').forEach( popupLink => {\n\t\thandlePopupImageLink( popupLink );\n\t});\n}\n\n/**\n * Activate all anchor links that have a `data-popup=\"youtube\"` attribute.\n */\nexport function initPopupYoutube() {\n\tdocument.querySelectorAll('a[data-popup=\"youtube\"]').forEach( popupLink => {\n\t\thandlePopupYoutubeLink( popupLink );\n\t});\n}\n\nexport function numberCounter() {\n\n}\n\nexport function tabs() {\n\tconst tabBlocks = document.querySelectorAll(\".tabBlock\");\n\n\ttabBlocks.forEach(tabBlock => {\n\t\tconst tabButtons = tabBlock.querySelectorAll(\".tabButton\");\n\t\tconst tabPanes = tabBlock.querySelectorAll('.tab');\n\n\t\ttabButtons.forEach( tabButton => {\n\t\t\ttabButton.addEventListener('click', e => {\n\t\t\t\te.preventDefault(); /* Kill the built-in behaviour of clicking a link (these should be a link, not buttons, but I'm not refactoring the markup in case other things use that) */\n\n\t\t\t\t/* make sure all other panes are inactive */\n\t\t\t\ttabPanes.forEach(pane => pane.classList.remove('active'));\n\n\t\t\t\t/* remove the active class on all other tab controls too */\n\t\t\t\ttabButtons.forEach(button => button.classList.remove('active'));\n\n\t\t\t\t/* make the pane associated with the clicked control active */\n\t\t\t\tconst targetId = e.currentTarget.dataset.id;\n\t\t\t\tdocument.querySelector( `#${targetId}` ).classList.add('active');\n\n\t\t\t\t/* Make the clicked tab control active too */\n\t\t\t\te.currentTarget.classList.add('active');\n\t\t\t});\n\t\t});\n\t});\n}\n\n/**\n * Adds a relative age in days to DOM elements that are annotated with some sort of date\n *\n * @param {string} targetElement - The selector of the element we're matching against\n * @param {string} outputTarget - The selector of the sub-element we append a relative time to\n *\n * @example data-date-utc=\"2023-06-23T09:43:58+01:00\"\n */\nexport function relativeAges(targetElement, outputTarget) {\n\tconst utcDate = new Date();\n\tconst isoDate = utcDate.toISOString();\n\tconst rtf1 = new Intl.RelativeTimeFormat(\n\t\t'en-GB',\n\t\t{ numeric: 'auto' }\n\t);\n\n\tdocument\n\t\t.querySelectorAll( targetElement )\n\t\t.forEach(item => {\n\t\t\tconst prodDate = new Date(item.dataset.dateUtc);\n\t\t\tconst dateInUTC = new Date(isoDate);\n\n\t\t\tlet differenceInDays = Math.abs(dateInUTC.getDate() - prodDate.getDate());\n\t\t\tlet output = rtf1.format(0 - differenceInDays, 'day')\n\n\t\t\tif (differenceInDays > 6) {\n\t\t\t\titem\n\t\t\t\t\t.querySelector(':scope ' + outputTarget)\n\t\t\t\t\t.insertAdjacentHTML(\n\t\t\t\t\t\t'beforeend',\n\t\t\t\t\t\t`(${output})`\n\t\t\t\t\t);\n\t\t\t}\n\t\t});\n}\n\n/* Fire functions on load */\nsidebarController();\ninitPopupImages();\ninitPopupYoutube();\ntabs();\nnumberCounter();\ninitWindowHasScrolled();\nscrollAnimatedBlocks();\nrelativeAges('[data-date-posted]','.meta');\n"],"names":["thisWebsiteAPI","FetchWrapper","windowHasScrolled","html","initWindowHasScrolled","sidebarController","scrollAnimatedBlocks","rootMargin","observer","watchList","watchedElement","watchTarget","handlePopupImageLink","popupLink","e","parser","clickedLink","response","imageWeWant","error","handlePopupYoutubeLink","videoWeWant","lightbox","initPopupImages","initPopupYoutube","tabs","tabBlock","tabButtons","tabPanes","tabButton","pane","button","targetId","relativeAges","targetElement","outputTarget","isoDate","rtf1","item","prodDate","dateInUTC","differenceInDays","output"],"mappings":"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"}