From 3b83905603d44b1a08f8b2b36a605808ce686ad6 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 02 Jun 2026 00:46:48 +0000
Subject: [PATCH] =double checking schema outputs for legacytattooremoval

---
 src/glossary/view.js |  184 ++++++++++++++++++++++++++++++++++++++++++++++
 1 files changed, 184 insertions(+), 0 deletions(-)

diff --git a/src/glossary/view.js b/src/glossary/view.js
new file mode 100644
index 0000000..f68d59a
--- /dev/null
+++ b/src/glossary/view.js
@@ -0,0 +1,184 @@
+/**
+ * Glossary Navigation Active State Manager
+ * Handles highlighting active terms as they scroll into view
+ * and syncing navigation with scroll position
+ */
+class GlossaryNavigator {
+	constructor(glossarySelector = 'dl.glossary', navSelector = 'nav.glossary-index') {
+		this.glossary = document.querySelector(glossarySelector);
+		this.nav = document.querySelector(navSelector);
+
+		if (!this.glossary || !this.nav) return;
+
+		this.terms = this.glossary.querySelectorAll('dt[id]');
+		this.navList = this.nav.querySelector('ul');
+		this.activeClass = 'active';
+		this.currentActive = null;
+
+		this.init();
+		this.setupResizeHandler();
+	}
+
+	init() {
+		// Set up Intersection Observer with screen-size appropriate margins
+		const observerOptions = {
+			root: null, // viewport
+			rootMargin: this.getRootMargin(),
+			threshold: 0
+		};
+
+		this.observer = new IntersectionObserver(
+			(entries) => this.handleIntersection(entries),
+			observerOptions
+		);
+
+		// Observe all terms
+		this.terms.forEach(term => this.observer.observe(term));
+
+		// Also handle manual scroll for edge cases
+		this.handleScroll = this.debounce(() => this.checkActiveTerm(), 100);
+		window.addEventListener('scroll', this.handleScroll, { passive: true });
+	}
+
+	getRootMargin() {
+		// On larger screens: centered (50% from top and bottom)
+		return '-50% 0px -50% 0px';
+	}
+
+	setupResizeHandler() {
+		let resizeTimer;
+		window.addEventListener('resize', () => {
+			clearTimeout(resizeTimer);
+			resizeTimer = setTimeout(() => {
+				// Reinitialize observer with new margins on resize
+				this.reinitialize();
+			}, 250);
+		});
+	}
+
+	reinitialize() {
+		// Disconnect old observer
+		if (this.observer) {
+			this.observer.disconnect();
+		}
+
+		// Create new observer with updated margins
+		this.init();
+	}
+
+	handleIntersection(entries) {
+		// Find the entry that's intersecting
+		const intersecting = entries.find(entry => entry.isIntersecting);
+
+		if (intersecting) {
+			this.setActive(intersecting.target);
+		}
+	}
+
+	checkActiveTerm() {
+		// Fallback method to find which term is in the trigger zone
+		const remInPixels = parseFloat(getComputedStyle(document.documentElement).fontSize);
+		const margin = remInPixels * 4;
+
+		let closestTerm = null;
+		let closestDistance = Infinity;
+
+		this.terms.forEach(term => {
+			const rect = term.getBoundingClientRect();
+
+			const isInZone = rect.top + rect.height / 2 >= 0 && rect.top + rect.height / 2 <= window.innerHeight;
+
+			if (isInZone) {
+				// Find closest to the trigger point
+				const triggerPoint = window.innerHeight / 2;
+
+				const distance = Math.abs(rect.top - triggerPoint);
+
+				if (distance < closestDistance) {
+					closestDistance = distance;
+					closestTerm = term;
+				}
+			}
+		});
+
+		if (closestTerm) {
+			this.setActive(closestTerm);
+		}
+	}
+
+	setActive(term) {
+		if (this.currentActive === term) return;
+
+		// Remove active class from previous term
+		if (this.currentActive) {
+			this.currentActive.classList.remove(this.activeClass);
+		}
+
+		// Add active class to current term
+		term.classList.add(this.activeClass);
+		this.currentActive = term;
+
+		// Update navigation
+		this.updateNavigation(term.id);
+	}
+
+	updateNavigation(termId) {
+		// Remove active from all nav links
+		const navLinks = this.nav.querySelectorAll('a');
+		navLinks.forEach(link => link.classList.remove(this.activeClass));
+
+		// Find and activate corresponding nav link
+		const activeLink = this.nav.querySelector(`a[href="#${termId}"]`);
+
+		if (activeLink) {
+			activeLink.classList.add(this.activeClass);
+
+			// Scroll the nav list to center the active link
+			this.centerNavItem(activeLink);
+		}
+	}
+
+	centerNavItem(link) {
+		const listRect = this.navList.getBoundingClientRect();
+		const linkRect = link.getBoundingClientRect();
+
+		// Calculate position to center the link in the nav container
+		const scrollTop = this.navList.scrollTop;
+		const linkOffset = linkRect.top - listRect.top;
+		const centerOffset = (listRect.height / 2) - (linkRect.height / 2);
+
+		this.navList.scrollTo({
+			top: scrollTop + linkOffset - centerOffset,
+			behavior: 'smooth'
+		});
+	}
+
+	debounce(func, wait) {
+		let timeout;
+		return function executedFunction(...args) {
+			const later = () => {
+				clearTimeout(timeout);
+				func(...args);
+			};
+			clearTimeout(timeout);
+			timeout = setTimeout(later, wait);
+		};
+	}
+
+	destroy() {
+		if (this.observer) {
+			this.observer.disconnect();
+		}
+		window.removeEventListener('scroll', this.handleScroll);
+	}
+}
+
+
+// Initialize when DOM is ready
+if (document.readyState === 'loading') {
+	document.addEventListener('DOMContentLoaded', () => {
+		new GlossaryNavigator();
+	});
+} else {
+	new GlossaryNavigator();
+}

--
Gitblit v1.10.0