PageSourceSearch

https://alinda.dev/scripts/script.js

js alinda.dev collected 2026-10-05 03:09:01 UTC 11,932 bytes, 399 lines download raw bytes

1// Small Screen Variables
2const compactSize = 960;
3
4// Page Variables
5var activePage;
6var activePageId;
7var numOfPages = 0;
8var pages = {};
9var pageByIndex = [];
10var pageByPosition = {};
11var pageContainerEl;
12
13// Scrolling Variables
14var lastScrollPos = 0;
15var pageDelayMS = 750;
16var mouseWheelListener;
17var usingDefaultWheelBehavior = false;
18var isDoneScrolling = true;
19
20// Collections Variables
21var collapsibleObj = {};
22
23// Misc Variables
24var clickedOnHeaderObj = false;
25
26// Custom Events
27let pageChangeEvent = new CustomEvent("changedPage", {detail: {}});
28
29function initialize() {
30	pageContainerEl = document.getElementById("page-container");
31
32	populatePages();
33	setActivePage();
34	initializeEventListeners();
35	getProjectTabs();
36
37	// Some browsers stay in last position when refreshing the site. Scroll to Home if so.
38	//if (navigator.userAgent.search("Firefox") > 0 && pageContainerEl.scrollTop != 0)
39	//	scrollToPage("home");
40}
41
42// Returns true if visitor is using a device that is not compatible with site.
43// Ex. If using Mac, user is more likely to use Safari or is using a laptop.
44// https://www.learningjquery.com/2017/05/how-to-use-javascript-to-detect-browser
45function isJankyDevice() {
46	return navigator.platform == "MacIntel" || navigator.userAgent.
46search("Firefox") > 0;
47}
48
49function isCompact() {
50	return window.innerWidth <= compactSize;
51}
52
53function makeDeviceFriendly() {
54	if (isCompact() || isJankyDevice())
55		enableDefaultWheel();
56	else
57		disableDefaultWheel();
58}
59
60function populatePages() {
61	var pageElements = document.getElementsByClassName("page");
62	numOfPages = pageElements.length;
63	pageByPosition = {};		// Clear every time we resize to save space.
64
65	for (var i = 0; i < numOfPages; i++) {
66		// Populate pages array with data.
67		pages[pageElements[i].id] = {
68			id: pageElements[i].id,
69			index: i,
70			position: pageElements[i].offsetTop
71		};
72
73		// Array so we can look up a page id by referring to its index.
74		pageByIndex[i] = pageElements[i].id;
75
76		// Look up pages by its position.
77		pageByPosition[pageElements[i].offsetTop] = pageElements[i].id;
78	}
79}
80
81function initializeEventListeners() {
82	mouseWheelListener = function(e){mouseWheelToNextPage(e);};
83
84	// Prevent scrolling from mouse wheel. Replace with "wheel to next page" behavior.
85	disableDefaultWheel();
86	
87	// Navigate through pages using keyboard.
88	window.addEventListener("keydown", function(e){onKeyboardNav(e)});
89	
90	// User should be allowed to scroll as normal while mousing over content container.
91	setScrollableElements("scrolling-content-container");
92	setScrollableElements("collapsible-body");
93
94	// Recalculate page positions after resizing window.
95	window.addEventListener("resize", function(e){makeDeviceFriendly(); populatePages();});
96
97	// Listen for whenever the active page changes.
98	window.addEventListener("changedPage", function(e){updateActivePage(pageContainerEl);});
99}
100
101function setScrollableElements(className) {
102	scrollableDiv = document.getElementsByClassName(className);
103	
104	for (var i = 0; i < scrollableDiv.length; i++) {
105		scrollableDiv[i].addEventListener("mouseenter", function(e){enableDefaultWheel(this);});
106		scrollableDiv[i].addEventListener("mouseleave", function(e){disableDefaultWheel()});
107	}
108}
109
110function getProjectTabs() {
111	var tabObj = document.getElementsByClassName("collapsible-tab");
112	var bodyObj = document.getElementsByClassName("collapsible-body");
113
114	// Associate each tab with its body by the tab element as the key and its body as its value.
115	for (var i = 0; i < tabObj.length; i++)
116		collapsibleObj[tabObj[i].id] = bodyObj[i];
117}
118
119function onKeyboardNav(event) {
120	// Navigation keys that have an affect on the page when pressed.
121	// PgUp, PgDown, Left, Up, Right, Down
122	validKeys = [33, 34, 37, 38, 39, 40];
123
124	// Prevents strange bug that jumps page to top when pressing some keys.
125	if (!(validKeys.includes(event.keyCode)))
126		return;
127
128	nextPage = pages[activePageId].index;
129
130	// Determine if user is pressing (Left/Up/Page Up) or (Right/Down/Page Down)
131	if (event.keyCode === 37 || event.keyCode === 38 || event.keyCode === 33)
132		nextPage--;
133	else if (event.keyCode === 39 || event.keyCode === 40 || event.keyCode === 34)
134		nextPage++;
135
136	// If user attempts to scroll past all accessible pages, do nothing.
137	if (nextPage >= numOfPages || nextPage < 0)
138		return;
139
140	// Otherwise, scroll to next page.
141	scrollToPage(pageByIndex[nextPage]);
142}
143
144function enableDefaultWheel(el = null) {
145	/* Removed this feature due to glitchiness.
146	// If this div is small enough to fit on the screen without scrolling,
147	// then keep snapping to next page as normal when scrolling.
148	if (el && el.scrollHeight == el.offsetHeight)
149		return;
150	*/
151
152	// Otherwise, allow user to scroll through content without snapping to next page.
153	window.removeEventListener("wheel", mouseWheelListener);
154	usingDefaultWheelBehavior = true;
155}
156
157function disableDefaultWheel() {
158	if (isCompact() || isJankyDevice())
159		return;
160	
161	window.addEventListener("wheel", mouseWheelListener, {passive: false});
162	usingDefaultWheelBehavior = false;
163}
164
165/*============*\
166|| NAVIGATION ||
167\*============*/
168// What to do when user clicked on navigation.
169function clickedNav(element) {
170	scrollToPage(element.id.replace("nav-", ""));
171}
172
173// Scroll to indicated page id.
174function scrollToPage(pageId) {
175	page = document.getElementById(pageId);
176	page.scrollIntoView();
177	
178	if (!usingDefaultWheelBehavior)
179		switchPage(document.getElementById("nav-" + pageId));
180
181	// Reenable disabling of default wheel behavior after done scrolling.
182	setTimeout(function(){isDoneScrolling = true; window.dispatchEvent(pageChangeEvent);}, pageDelayMS);
183}
184
185function setActivePage() {
186	// Get new variables.
187	activePage = document.getElementsByClassName("active-tab")[0];
188	activePageId = activePage.id.replace("nav-", "");
189}
190
191function updateActivePage(el) {
192	// How many pixels away from element top before we trigger the tab switch.
193	var tolerance = window.screen.height * 0.3;
194
195	// If scrolling by mouse wheel is stalled and active page tab is set without scrolling,
196	// go ahead and scroll to appropriate page.
197	if (Math.abs(el.scrollTop - pages[activePageId].position) >= tolerance)
198		scrollToPage(activePageId);
199}
200
201// Deal with some instances where active page tab doesn't update due to manual scrolling.
202function updateActivePageEdgeCase() {
203	// If current page does not match up with active tab, update the tab to match the page.
204	if (isDoneScrolling && pages[activePageId].position != pageContainerEl.scrollTop) {
205		if (!(pageContainerEl.scrollTop in pageByPosition))
206			return;
207
208		actualPageId = pageByPosition[pageContainerEl.scrollTop];
209		switchPage(document.getElementById("nav-" + actualPageId));
210	}
211}
212
213function switchPage(element) {
214	// Remove active tag from previous active page and set to current page.
215	activePage.classList.remove("active-tab");
216	element.classList.add("active-tab");
217	setActivePage();
218}
219
220function mouseWheelToNextPage(e) {
221	// Prevent scrolling from mouse wheel.
222	e.preventDefault();
223
224	if (!isDoneScrolling)
225		return;
226
227	// Determine if user is scrolling up or down.
228	nextPage = e.deltaY > 0 ? pages[activePageId].index + 1 : pages[activePageId].index - 1;
229
230	// If user attempts to scroll past all accessible pages, do nothing.
231	if (nextPage >= numOfPages || nextPage < 0)
232		return;
233
234	// Otherwise, scroll to next page.
235	isDoneScrolling = false;
236	scrollToPage(pageByIndex[nextPage]);
237}
238
239/*==================*\
240|| COLLAPSIBLE TABS ||
241\*==================*/
242// Collapse all tabs besides selected category.
243// Scroll to container if not already in view.
244function onClickProjShortcut(el) {
245	var projId = el.id.replace("-shortcut", "");
246
247	for (var key in collapsibleObj) {
248		if (key == projId)
249			expandTab(collapsibleObj[projId], projId);
250		else
251			collapseTab(collapsibleObj[key], key);
252	}
253
254	document.getElementById("content-proj").scrollIntoView();
255}
256
257// Expand or collapse a tab depending on its display status.
258function expandOrCollapseTab(el) {
259	if (collapsibleObj[el.id].classList.contains("collapsed"))
260		expandTab(collapsibleObj[el.id], el.id);
261	else
262		collapseTab(collapsibleObj[el.id], el.id);
263}
264
265function collapseTab(el, id) {
266	el.classList.remove("expanded");
267	el.classList.add("collapsed");
268	flipIndicator(id, true);
269}
270
271function expandTab(el, id) {
272	el.classList.remove("collapsed");
273	el.classList.add("expanded");
274	flipIndicator(id, false);
275}
276
277function flipIndicator(id, isExpanded) {
278	var ind = document.getElementById(id).getElementsByClassName("tab-indicator")[0];
279
280	if (isExpanded) {
281		ind.classList.remove("indicator-collapse");
282		ind.classList.add("indicator-expand");
283	}
284	else {
285		ind.classList.remove("indicator-expand");
286		ind.classList.add("indicator-collapse");
287	}
288}
289
290/*==================*\
291|| PROJECT EXPLORER ||
292\*==================*/
293// Expand or collapse a project tab depending on its display status.
294function expandOrCollapseProject(el) {
295	// Expand tab but do not  collapse it if user pressed on header icon.
296	if (clickedOnHeaderObj) {
297		clickedOnHeaderObj = false;
298		return;
299	}
300
301	el.parentElement.classList.toggle("gc-hidden");
302}
303
304function preventTabAction() {
305	clickedOnHeaderObj = true;
306}
307
308/*===================*\
309|| PROJECT SPOTLIGHT ||
310\*===================*/
311function onClickThumbnail(el) {
312	var sector = el.parentElement.parentElement;
313	var thumbnail = el.getElementsByClassName("thumbnail-img")[0];
314	
315	// If image is already focused, do nothing.
316	if (thumbnail.classList.contains("img-focused"))
317		return;
318
319	// Otherwise, make it focused and make the currently focused image lose focus.
320	thumbnailLoseFocus(sector.getElementsByClassName("img-focused")[0]);
321	thumbnailGainFocus(thumbnail);
322
323	// Change the spotlight image to match the thumbnail.
324	var spotlight_active = sector.getElementsByClassName("spotlight-img-active")[0];
325	var spotlight_hidden = sector.getElementsByClassName("spotlight-img-hidden")[0];
326	var path = thumbnail.getAttribute("src");
327	
328	spotlight_hidden.setAttribute("src", path);
329
330	flipSpotlightVisibility(spotlight_hidden);
331	flipSpotlightVisibility(spotlight_active);
332	
333}
334
335function thumbnailGainFocus(el) {
336	el.classList.remove("img-unfocused")
337	el.classList.add("img-focused")
338}
339
340function thumbnailLoseFocus(el) {
341	el.classList.add("img-unfocused")
342	el.classList.remove("img-focused")
343}
344
345function flipSpotlightVisibility(el) {
346	el.classList.toggle("spotlight-img-hidden");
347	el.classList.toggle("spotlight-img-active");
348}
349
350/*=======*\
351|| AUDIO ||
352\*=======*/
353// Hide anything that is showing and display anything that was hidden.
354function flipAudioStatus(el) {
355	var hidden = el.getElementsByClassName("hidden")[0];
356	var showing = el.getElementsByClassName("showing")[0];
357
358	hidden.classList.remove("hidden");
359	hidden.classList.add("showing");
360	showing.classList.remove("showing");
361	showing.classList.add("hidden");
362}
363
364// Play audio and display the pause button.
365// Stop playing any audio that is currently playing.
366function playAudio(id) {
367	var allPlayers = document.getElementsByClassName("grid-audio-player");
368	var proj = document.getElementById(id.replace("audio", "proj-music"));
369	var audio = document.getElementById(id);
370
371	// Stop playing other audio if any.
372	for (var i = 0; i < allPlayers.length; i++) {
373		if (allPlayers[i] == id)
374			continue;
375
376		if (!allPlayers[i].paused)
377			stopAudio(allPlayers[i].id);
378	}
379
380	// Reload so it starts from beginning upon pressing on play again.	
381	audio.load();
382	audio.play();
383
384	flipAudioStatus(proj);
385}
386
387// Stop audio and display the play button.
388function stopAudio(id) {
389	var proj = document.getElementById(id.replace("audio", "proj-music"));
390	document.getElementById(id).pause();
391	flipAudioStatus(proj);
392}
393
394// Displays and updates audio track time.
395// https://stackoverflow.com/questions/4993097/html5-display-audio-currenttime
396function updateAudioTimeTracker(audioEl, id) {
397	var proj = document.getElementById(id);
398	proj.getElementsByClassName('audio-time-tracker')[0].innerHTML = Math.floor(audioEl.currentTime) + ' / ' + Math.floor(audioEl.duration);
399}

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.