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