PageSourceSearch

https://www.webbyawards.com/wp-content/mu-plugins/rm-categories/as…/rm-categories-route.js?ver=2026.09.13.03

js webbyawards.com collected 2026-09-24 06:45:30 UTC 24,927 bytes, 849 lines download raw bytes

1/**
2 * Keep the URL, the heading and the open section saying the same thing.
3 *
4 * Two directions. Arriving on a category URL opens the section it names, by triggering the same
5 * control a visitor would click — the accordion and the nav are the theme's, and driving them any
6 * other way would reproduce their behaviour rather than use it, which is how the two drift apart.
7 * Opening a section by hand writes that section's URL into the address bar, and its name into the
8 * heading where the route asks for that, so the page can be shared from wherever the visitor got
9 * to.
10 *
11 * Two modes, one per kind of layout:
12 *
13 *   accordion  the section's opener is clicked until the theme's accordion shows it open, then the
14 *              opener of the media type inside it.
15 *   anchor     the nav link to the section is clicked, so the page scrolls, marks or switches
16 *              exactly as it does for a visitor. A section no link leads to is scrolled into view.
17 *              A `#fragment` link keeps doing what it does; only the address it leaves changes.
18 *
19 * Where each part is in the markup comes from the route's selectors. DEFAULTS is the markup of
20 * Telly-2023/rm-categories/telly.php; rm_categories_route_defaults() in
21 * inc/class-rm-categories-router.php holds the same values and sends only what a site changes,
22 * and tests/route-js-test.js holds the two copies together.
23 *
24 * replaceState rather than pushState: the list is one page, and pushing an entry per click
25 * makes Back walk the list instead of leaving the page.
26 *
27 * Build: 2026.09.13.03
28 */
29(function (window, document) {
30	'use strict';
31
32	var config = window.rmCategoriesRoute || {};
33
34	if (!config.base) {
35		return;
36	}
37
38	/* The route's defaults, as rm_categories_route_defaults() encodes them. */
39	var DEFAULTS = {
40		"level": "group",
41		"mode": "accordion",
42		"heading": "replace",
43		"selectors": {
44			"group": "li[data-rm-group]",
45			"group_opener": "a.opener",
46			"group_label": "a.opener span",
47			"media": "li[data-rm-media]",
48			"media_opener": "a.inner-opener",
49			"media_label": "a.inner-opener",
50			"active": "active",
51			"media_active": "active",
52			"heading": ".hero-area h1",
53			"list": [".category-holder", "ul.category"],
54			"nav": ""
55		}
56	};
57
58	var level = config.level || DEFAULTS.level;
59	var mode = config.mode || DEFAULTS.mode;
60	var headingMode = config.heading || DEFAULTS.heading;
61	var sel = withDefaults(DEFAULTS.selectors, config.selectors);
62
63	/** The theme animates its accordion over 300ms; read the result once it has settled. */
64	var SETTLE_MS = 360;
65
66	/** How often to retry the opening click, and how many times, while the theme binds its accordion. */
67	var READY_POLL_MS = 150;
68	var READY_ATTEMPTS = 40;
69
70	/**
71	 * How many times a nav link is clicked before it is taken to have no handler at all.
72	 *
73	 * A page binds its nav when the document is ready, moments after this script starts. A link
74	 * nothing binds is a plain `#fragment` link, and waiting on it as long as on an accordion would
75	 * leave the page standing still for six seconds before it scrolled.
76	 */
77	var LINK_ATTEMPTS = 10;
78
79	/**
80	 * @param {Object} defaults
81	 * @param {Object|undefined} overrides
82	 * @returns {Object} Every key of defaults, each taken from overrides where that names it.
83	 */
84	function withDefaults(defaults, overrides) {
85		var out = {};
86
87		Object.keys(defaults).forEach(function (key) {
88			out[key] = overrides && Object.prototype.hasOwnProperty.call(overrides, key) ? overrides[key] : defaults[key];
89		});
90
91		return out;
92	}
93
94	/**
95	 * The first element any of the selectors finds, trying them in order.
96	 *
97	 * @param {string|string[]} selectors
98	 * @param {Document|Element} [scope]
99	 * @returns {Element|null}
100	 */
101	function first(selectors, scope) {
102		var list = Array.isArray(selectors) ? selectors : [selectors];
103		var root = scope || document;
104
105		for (var i = 0; i < list.length; i++) {
106			var found = list[i] ? root.querySelector(list[i]) : null;
107
108			if (found) {
109				return found;
110			}
111		}
112
113		return null;
114	}
115
116	/**
117	 * @param {Element|null} element
118	 * @param {string} which 'group' or 'media'.
119	 * @returns {string} The slug the element carries, or ''.
120	 */
121	function slugOf(element, which) {
122		return element ? element.getAttribute('data-rm-' + which) || '' : '';
123	}
124
125	/**
126	 * The section the accordion is currently showing.
127	 *
128	 * Read from the DOM rather than tracked, so a click the page handles some other way cannot
129	 * leave this out of step with what is on screen. At media level a media type is looked for
130	 * across the page when no grouping is open, since a list with no grouping has none to open.
131	 *
132	 * @returns {{group: Element|null, media: Element|null}}
133	 */
134	function openSection() {
135		var group = document.querySelector(sel.group + '.' + sel.active);
136		var scope = group || ('media' === level ? document : null);
137		var media = scope ? scope.querySelector(sel.media + '.' + sel.media_active) : null;
138
139		return { group: group, media: media };
140	}
141
142	/**
143	 * @param {string} groupSlug
144	 * @param {string} mediaSlug
145	 * @returns {string} Path for that section, or the bare category page when it names none.
146	 */
147	function pathFor(groupSlug, mediaSlug) {
148		var path = '/' + config.base.replace(/^\/+|\/+$/g, '') + '/';
149		var lead = 'media' === level ? mediaSlug : groupSlug;
150
151		if (!lead) {
152			return path;
153		}
154
155		path += encodeURIComponent(lead) + '/';
156
157		if ('group' === level && mediaSlug) {
158			path += encodeURIComponent(mediaSlug) + '/';
159		}
160
161		return path;
162	}
163
164	/**
165	 * The name to show as the heading for a section.
166	 *
167	 * Taken from the opener the visitor sees, so the heading and the control they clicked always
168	 * read the same. Falls back to the page's own title when nothing is open.
169	 *
170	 * @param {Element|null} group
171	 * @param {Element|null} media
172	 * @returns {string}
173	 */
174	function headingFor(group, media) {
175		var opener = null;
176
177		if (media) {
178			opener = first(sel.media_label, media);
179		} else if (group) {
180			opener = first([sel.group_label, sel.group_opener], group);
181		}
182
183		return textOf(opener);
184	}
185
186	/**
187	 * @param {Element|null} element
188	 * @returns {string} Its text on one line, or the page's own title when there is none.
189	 */
190	function textOf(element) {
191		if (!element) {
192			return config.defaultHeading || '';
193		}
194
195		return (element.textContent || '').replace(/\s+/g, ' ').trim() || config.defaultHeading || '';
196	}
197
198	/**
199	 * Put a section's name in the heading, where the route asks for that.
200	 *
201	 * @param {string} text
202	 * @returns {void}
203	 */
204	function setHeading(text) {
205		var node = 'replace' === headingMode ? first(sel.heading) : null;
206
207		if (node) {
208			node.textContent = text;
209		}
210	}
211
212	/**
213	 * True while a section is being opened by this script rather than by a visitor.
214	 *
215	 * Opening one fires the page's own click handling, which would otherwise be read back as a
216	 * choice the visitor made and rewrite the URL they arrived on.
217	 */
218	var opening = false;
219
220	/**
221	 * Write the current section into the address bar and the heading.
222	 *
223	 * Opening a grouping opens its first media type as well — the theme's accordion does that on
224	 * its own — so a grouping click records the grouping, not whichever media type came with it.
225	 * Only choosing a media type puts one in the URL. Without that, arriving at a grouping whose
226	 * first media type shares its name rewrites the address to `.../branded-content/branded-content/`.
227	 *
228	 * @param {string} chosen 'group' or 'media'.
229	 * @returns {void}
230	 */
231	function sync(chosen) {
232		var open = openSection();
233		var media = ('media' === chosen) ? open.media : null;
234		var path = pathFor(slugOf(open.group, 'group'), slugOf(media, 'media'));
235
236		if (window.history && window.history.replaceState && window.location.pathname !== path) {
237			window.history.replaceState(null, '', path + window.location.search + window.location.hash);
238		}
239
240		setHeading(headingFor(open.group, media));
241		clearBareHash();
242	}
243
244	/**
245	 * Open the section the URL names.
246	 *
247	 * By clicking the opener, not by setting classes: the accordion holds its own state and
248	 * setting the classes underneath it would leave the next click toggling the wrong way.
249	 *
250	 * @returns {void}
251	 */
252	function openFromUrl() {
253		if (!('media' === level ? config.media : config.group)) {
254			return;
255		}
256
257		opening = true;
258		restoreTo = window.pageYOffset || document.documentElement.scrollTop || 0;
259		openWhenReady(0);
260	}
261
262	/**
263	 * Where the page was before anything was opened for the visitor.
264	 *
265	 * Opening a section means clicking an opener, and those openers are anchors with `href="#"`.
266	 * A click on one moves the page and leaves a bare `#` on the address bar, neither of which the
267	 * visitor asked for — they asked for a URL, and arriving on it should look like arriving on the
268	 * page with that section already open.
269	 */
270	var restoreTo = 0;
271
272	/**
273	 * Open the section, retrying until the click has an effect.
274	 *
275	 * An opener is only an opener once the theme has bound its accordion to it, and that happens in
276	 * a script of the theme's own further down the page. A click before then does nothing at all,
277	 * and the accordion afterwards opens its first item — which reads as success whenever the URL
278	 * names the first grouping, and as nothing happening for every other one.
279	 *
280	 * The readiness test is whether clicking worked, rather than whether some element the theme
281	 * happens to use today is present: this library serves ten sites and the accordion is each
282	 * theme's own, so any check written against one theme's internals is a check that silently
283	 * stops applying to the rest.
284	 *
285	 * @param {number} attempt
286	 * @returns {void}
287	 */
288	function openWhenReady(attempt) {
289		var group = groupToOpen();
290
291		if (!group) {
292			// Nothing to open. Released, or every later click reads as programmatic.
293			opening = false;
294
295			return;
296		}
297
298		if (group === document || group.classList.contains(sel.active)) {
299			openMediaThenSettle(group);
300
301			return;
302		}
303
304		if (attempt >= READY_ATTEMPTS) {
305			opening = false;
306
307			return;
308		}
309
310		var opener = group.querySelector(sel.group_opener);
311
312		if (opener) {
313			opener.click();
314		}
315
316		window.setTimeout(function () {
317			openWhenReady(attempt + 1);
318		}, READY_POLL_MS);
319	}
320
321	/**
322	 * The grouping holding the section the URL names.
323	 *
324	 * Named by the URL at group level. At media level it is the grouping around the media type, or
325	 * the whole page where the list has no grouping to open.
326	 *
327	 * @returns {Element|Document|null}
328	 */
329	function groupToOpen() {
330		if ('media' !== level) {
331			return document.querySelector(sel.group + '[data-rm-group="' + selector(config.group) + '"]');
332		}
333
334		var media = document.querySelector(sel.media + '[data-rm-media="' + selector(config.media) + '"]');
335
336		if (!media) {
337			return null;
338		}
339
340		return (media.parentNode && media.parentNode.closest ? media.parentNode.closest(sel.group) : null) || document;
341	}
342
343	/**
344	 * @param {string} value
345	 * @returns {string} Safe to interpolate into an attribute selector.
346	 */
347	function selector(value) {
348		return String(value).replace(/["\\]/g, '');
349	}
350
351	/**
352	 * The grouping is open. Choose the media type inside it, then stand down.
353	 *
354	 * @param {Element|Document} group
355	 * @returns {void}
356	 */
357	function openMediaThenSettle(group) {
358		if (config.media) {
359			var media = group.querySelector(sel.media + '[data-rm-media="' + selector(config.media) + '"]');
360			var inner = media ? media.querySelector(sel.media_opener) : null;
361
362			/*
363			 * Opening a grouping opens its first media type on its own, so clicking the one that
364			 * is already open would close it again.
365			 */
366			if (inner && !media.classList.contains(sel.media_active)) {
367				inner.click();
368			}
369		}
370
371		/*
372		 * Once the accordion has finished animating, bring the top of the list into view. Scrolling
373		 * to the opened grouping instead lands partway down it — a grouping near the foot of the
374		 * list puts its own heading above the viewport — and the list only reads from its top.
375		 */
376		window.setTimeout(function () {
377			scrollListIntoView();
378			clearBareHash();
379			opening = false;
380		}, SETTLE_MS);
381	}
382
383	/**
384	 * Put the top of the category list just below anything fixed to the top of the window.
385	 *
386	 * Measured rather than configured. These themes hang a navigation bar at the top once the page
387	 * scrolls, so nothing is stuck there to measure until after the scroll has happened — which is
388	 * why this scrolls, looks, and then corrects. A fixed offset per site would be a number that is
389	 * right until someone changes the header.
390	 *
391	 * @returns {void}
392	 */
393	function scrollListIntoView() {
394		var list = first(sel.list);
395
396		if (!list) {
397			window.scrollTo(0, restoreTo);
398
399			return;
400		}
401
402		scrollIntoPlace(list);
403	}
404
405	/**
406	 * Put the top of an element just below anything fixed to the top of the window.
407	 *
408	 * @param {Element} element
409	 * @returns {void}
410	 */
411	function scrollIntoPlace(element) {
412		var allowance = parseInt((window.rmCategories || {}).scrollOffset, 10) || 0;
413		var top = element.getBoundingClientRect().top + (window.pageYOffset || 0) - allowance;
414
415		window.scrollTo(0, top < 0 ? 0 : top);
416
417		/*
418		 * The site's own allowance covers furniture the theme pins only once the page has moved,
419		 * which is not there to be measured beforehand. Anything that does turn out to be stuck is
420		 * taken as a floor rather than added to it, or the two would be subtracted twice.
421		 */
422		window.setTimeout(function () {
423			var gap = element.getBoundingClientRect().top;
424			var stuck = stuckToTop();
425
426			if (stuck > gap) {
427				window.scrollBy(0, -(stuck - gap));
428			}
429		}, 60);
430	}
431
432	/**
433	 * How far down the window the topmost fixed or sticky furniture reaches.
434	 *
435	 * @returns {number} Pixels, 0 when nothing is stuck to the top.
436	 */
437	function stuckToTop() {
438		var lowest = 0;
439
440		[].forEach.call(document.body.getElementsByTagName('*'), function (element) {
441			var style = window.getComputedStyle(element);
442
443			if ('fixed' !== style.position && 'sticky' !== style.position) {
444				return;
445			}
446
447			var box = element.getBoundingClientRect();
448
449			// Only what actually covers the top of the window, and only full-width furniture.
450			if (box.top > 4 || box.bottom <= 0 || box.width < window.innerWidth * 0.5) {
451				return;
452			}
453
454			if (box.bottom > lowest) {
455				lowest = box.bottom;
456			}
457		});
458
459		return Math.round(lowest);
460	}
461
462	/**
463	 * Take the `#` an opener click leaves behind off the address bar.
464	 *
465	 * Only a bare one. A real fragment is something the visitor or another script put there and is
466	 * none of this code's business.
467	 *
468	 * @returns {void}
469	 */
470	function clearBareHash() {
471		if ('#' !== window.location.hash || !window.history || !window.history.replaceState) {
472			return;
473		}
474
475		window.history.replaceState(null, '', window.location.pathname + window.location.search);
476	}
477
478	/**
479	 * @returns {void}
480	 */
481	function startAccordion() {
482		document.addEventListener('click', function (event) {
483			if (opening || !event.target || !event.target.closest) {
484				return;
485			}
486
487			/*
488			 * The markup nests the media-type openers inside the grouping's own anchor, so an
489			 * inner click matches both selectors. The inner one is the specific answer.
490			 */
491			var inner = event.target.closest(sel.media_opener);
492			var outer = inner ? null : event.target.closest(sel.group_opener);
493
494			if (!inner && !outer) {
495				return;
496			}
497
498			window.setTimeout(function () {
499				sync(inner ? 'media' : 'group');
500			}, SETTLE_MS);
501		});
502
503		openFromUrl();
504	}
505
506	/*
507	 * ---------------------------------------------------------------------------------------------
508	 * Anchor mode: the page's nav is the opener.
509	 * ---------------------------------------------------------------------------------------------
510	 */
511
512	/** The nav link this script is clicking, and whether the page's own handler took the click. */
513	var following = null;
514	var handled = false;
515
516	/**
517	 * @returns {string} Selector for the links a visitor chooses sections with.
518	 */
519	function navSelector() {
520		return sel.nav || (window.rmCategories || {}).navLink || '';
521	}
522
523	/**
524	 * @param {Element} link
525	 * @returns {string} The id a same-page link points at, or ''.
526	 */
527	function fragmentOf(link) {
528		var href = link.getAttribute('href') || '';
529
530		return '#' === href.charAt(0) ? href.slice(1) : '';
531	}
532
533	/**
534	 * The section a URL names.
535	 *
536	 * A media type is looked for inside its grouping when the URL names both, since two groupings
537	 * can each hold a media type of the same name.
538	 *
539	 * @param {string} groupSlug
540	 * @param {string} mediaSlug
541	 * @returns {Element|null}
542	 */
543	function sectionFor(groupSlug, mediaSlug) {
544		if (!mediaSlug) {
545			return groupSlug ? document.querySelector(sel.group + '[data-rm-group="' + selector(groupSlug) + '"]') : null;
546		}
547
548		var found = document.querySelectorAll(sel.media + '[data-rm-media="' + selector(mediaSlug) + '"]');
549
550		for (var i = 0; i < found.length; i++) {
551			if (!groupSlug || found[i].closest('[data-rm-group="' + selector(groupSlug) + '"]')) {
552				return found[i];
553			}
554		}
555
556		return null;
557	}
558
559	/**
560	 * The nav link for a section: one the theme marked with its slug, else one pointing at the
561	 * section's id, else one whose fragment is the slug itself, the form the page's own `#fragment`
562	 * addresses take.
563	 *
564	 * @param {string} which 'group' or 'media'.
565	 * @param {string} slug
566	 * @param {Element|null} section
567	 * @returns {Element|null}
568	 */
569	function linkFor(which, slug, section) {
570		var nav = navSelector();
571		var links = nav ? document.querySelectorAll(nav) : [];
572		var i;
573
574		for (i = 0; i < links.length; i++) {
575			if (slugOf(links[i], which) === slug) {
576				return links[i];
577			}
578		}
579
580		for (i = 0; i < links.length; i++) {
581			var fragment = fragmentOf(links[i]);
582
583			if (fragment && ((section && section.id === fragment) || fragment === slug)) {
584				return links[i];
585			}
586		}
587
588		return null;
589	}
590
591	/**
592	 * Whether following a link lands on the section.
593	 *
594	 * A link marked for the slug, or one pointing at the section itself, is the visitor's own way
595	 * there. One pointing at another element — a grouping's first section, when the URL names a
596	 * later media type inside the grouping — is marked rather than followed.
597	 *
598	 * @param {Element} link
599	 * @param {string} which
600	 * @param {string} slug
601	 * @param {Element|null} section
602	 * @returns {boolean}
603	 */
604	function leadsTo(link, which, slug, section) {
605		if (!section || slugOf(link, which) === slug) {
606			return true;
607		}
608
609		return document.getElementById(fragmentOf(link)) === section;
610	}
611
612	/**
613	 * Click a nav link until something on the page handles the click.
614	 *
615	 * Handled means default-prevented by the time the click reaches the window, which the shared
616	 * anchor nav and every theme nav do. Until then this prevents it instead, so the browser does
617	 * not jump to the fragment while the page is still binding its nav.
618	 *
619	 * @param {Element} link
620	 * @param {number} attempt
621	 * @param {function(boolean)} done Told whether the page took the click.
622	 * @returns {void}
623	 */
624	function follow(link, attempt, done) {
625		following = link;
626		handled = false;
627		link.click();
628		following = null;
629
630		if (handled || attempt + 1 >= LINK_ATTEMPTS) {
631			done(handled);
632
633			return;
634		}
635
636		window.setTimeout(function () {
637			follow(link, attempt + 1, done);
638		}, READY_POLL_MS);
639	}
640
641	/**
642	 * Bring a section into view and mark the nav item leading to it, the way the shared anchor nav
643	 * does for a link a visitor follows.
644	 *
645	 * @param {Element|null} section
646	 * @param {Element|null} link
647	 * @returns {void}
648	 */
649	function reveal(section, link) {
650		var nav = window.rmCategoriesNav;
651
652		// A page that draws its own list loads no shared nav, and the section is scrolled to directly.
653		if (!nav) {
654			if (section) {
655				scrollIntoPlace(section);
656			}
657
658			return;
659		}
660
661		if (link && nav.setActive) {
662			nav.setActive(link);
663		}
664
665		if (section && nav.scrollTo) {
666			nav.scrollTo(section);
667		}
668	}
669
670	/**
671	 * Open the section the URL names.
672	 *
673	 * @returns {void}
674	 */
675	function openAnchor() {
676		var slug = 'media' === level ? config.media : (config.media || config.group);
677		var which = config.media ? 'media' : 'group';
678
679		if (!slug) {
680			return;
681		}
682
683		var section = sectionFor('media' === level ? '' : config.group, config.media);
684		var link = linkFor(which, slug, section);
685
686		if (!link && !section) {
687			return;
688		}
689
690		opening = true;
691
692		if (link && leadsTo(link, which, slug, section)) {
693			follow(link, 0, function (taken) {
694				if (!taken) {
695					reveal(section || document.getElementById(fragmentOf(link)), link);
696				}
697
698				settleAnchor(link);
699			});
700
701			return;
702		}
703
704		reveal(section, link);
705		settleAnchor(link);
706	}
707
708	/**
709	 * Once the page has moved, take the fragment the link left on the address bar off it again:
710	 * the path names the section already.
711	 *
712	 * @param {Element|null} link
713	 * @returns {void}
714	 */
715	function settleAnchor(link) {
716		window.setTimeout(function () {
717			clearFragment(link ? link.getAttribute('href') || '' : '');
718			clearBareHash();
719			opening = false;
720		}, SETTLE_MS);
721	}
722
723	/**
724	 * Remove the fragment from the address bar when it is exactly the one a nav link wrote there.
725	 *
726	 * @param {string} href The link's own href.
727	 * @returns {void}
728	 */
729	function clearFragment(href) {
730		if ('#' !== href.charAt(0) || window.location.hash !== href || !window.history || !window.history.replaceState) {
731			return;
732		}
733
734		window.history.replaceState(null, '', window.location.pathname + window.location.search);
735	}
736
737	/**
738	 * Which section a nav link chose.
739	 *
740	 * Read from the link where the theme marked it, and otherwise from the element its fragment
741	 * points at. At group level a media type goes into the URL only when the link names one itself:
742	 * a grouping's link points at the grouping's first section, which carries that section's media
743	 * type as well, and the visitor chose the grouping.
744	 *
745	 * @param {Element} link
746	 * @returns {{group: string, media: string}}
747	 */
748	function chosenBy(link) {
749		var fragment = fragmentOf(link);
750		var target = fragment ? document.getElementById(fragment) : null;
751		var media = slugOf(link, 'media');
752		var group = slugOf(link, 'group');
753
754		if ('media' === level) {
755			return { group: '', media: media || slugOf(target && target.closest('[data-rm-media]'), 'media') };
756		}
757
758		if (!group) {
759			var holder = target || (media ? sectionFor('', media) : null);
760
761			group = slugOf(holder && holder.closest('[data-rm-group]'), 'group');
762		}
763
764		return { group: group, media: group ? media : '' };
765	}
766
767	/**
768	 * Write the section a nav link chose into the address bar, and its name into the heading.
769	 *
770	 * A link that names no section — one that shows every media type at once, say — writes the
771	 * bare category page. The fragment the link itself put on the address bar comes off, since the
772	 * path names the section; any other fragment, and the query, stay.
773	 *
774	 * @param {Element} link
775	 * @returns {void}
776	 */
777	function syncAnchor(link) {
778		var chosen = chosenBy(link);
779		var path = pathFor(chosen.group, chosen.media);
780		var href = link.getAttribute('href') || '';
781		var hash = window.location.hash === href ? '' : window.location.hash;
782
783		if (window.history && window.history.replaceState && window.location.pathname + window.location.hash !== path + hash) {
784			window.history.replaceState(null, '', path + window.location.search + hash);
785		}
786
787		setHeading(chosen.group || chosen.media ? textOf(link) : config.defaultHeading || '');
788		clearBareHash();
789	}
790
791	/**
792	 * @returns {void}
793	 */
794	function startAnchor() {
795		/*
796		 * Whether the page handled a click this script made. On the window, so it runs after every
797		 * listener on the document, whenever the page bound it.
798		 */
799		window.addEventListener('click', function (event) {
800			if (!following || event.target !== following) {
801				return;
802			}
803
804			handled = event.defaultPrevented;
805
806			if (!handled) {
807				event.preventDefault();
808			}
809		});
810
811		document.addEventListener('click', function (event) {
812			// A click can land on a text node, which has no closest().
813			var from = event.target && 1 === event.target.nodeType ? event.target : event.target && event.target.parentElement;
814			var nav = navSelector();
815			var link = !opening && from && nav ? from.closest(nav) : null;
816
817			// Only a link that addresses a section on this page says which section was chosen.
818			if (!link || (!fragmentOf(link) && !slugOf(link, 'media') && !slugOf(link, 'group'))) {
819				return;
820			}
821
822			window.setTimeout(function () {
823				syncAnchor(link);
824			}, SETTLE_MS);
825		});
826
827		// After the document's own ready handlers have run, which is where a page binds its nav.
828		window.setTimeout(openAnchor, 0);
829	}
830
831	/**
832	 * @returns {void}
833	 */
834	function start() {
835		if ('anchor' === mode) {
836			startAnchor();
837
838			return;
839		}
840
841		startAccordion();
842	}
843
844	if (document.readyState === 'loading') {
845		document.addEventListener('DOMContentLoaded', start);
846	} else {
847		start();
848	}
849})(window, document);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.