My personal website
1/*!
2 * Personal website of Sefa Eyeoglu
3 * Copyright (C) 2018-2022 Sefa Eyeoglu <contact@scrumplex.net>
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as published by
7 * the Free Software Foundation, either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19import ready from "./_utils";
20
21const mainElem = document.getElementById("main");
22
23document.querySelectorAll(".scrumplex-logo").forEach((elem) => {
24 elem.addEventListener("dblclick", () => {
25 const randomRotation = Math.floor(Math.random() * 360);
26 elem.style.transform = `rotate(${randomRotation}deg)`;
27 });
28});
29
30document.querySelectorAll("*[data-scroll]").forEach((elem) => {
31 elem.addEventListener("click", (e) => {
32 e.preventDefault();
33 const targetSelector = elem.getAttribute("data-scroll");
34 applyScrollSpecial(targetSelector);
35 });
36});
37
38mainElem.addEventListener("animationend", applyScrollInitially);
39
40function applyScrollInitially() {
41 mainElem.removeEventListener("animationend", applyScrollInitially);
42
43 window.addEventListener("scroll", applyScrollConditionally);
44
45 if (location.hash.startsWith("#")) {
46 const result = applyScrollSpecial(location.hash);
47 if (result) return;
48 }
49 applyScrollConditionally();
50}
51
52function applyScrollConditionally() {
53 if (
54 mainElem.getBoundingClientRect().top <= 0 || // offset to top window border
55 mainElem.getBoundingClientRect().height >= window.innerHeight
56 )
57 applyScroll();
58}
59
60function applyScrollSpecial(targetSelector) {
61 if (!targetSelector) return false;
62 const targetElem = document.querySelector(targetSelector);
63 if (!targetElem) return false;
64 const offset = applyScroll(targetElem);
65 window.scrollTo({ top: offset, behavior: "smooth" });
66 history.pushState({ hash: targetSelector }, "", targetSelector);
67 return true;
68}
69
70function applyScroll(scrollTarget = null) {
71 // Black magic ahead!
72 // We sometimes need this method to show all sheets and then scroll to one of them.
73 // To achieve this we need to have a way to get the target position (like pixels) of
74 // the sheet we want to scroll to (in this case it's scrollTarget).
75 // As the bounding box always includes current transforms (so our translateY in the
76 // fade-in animation) we need to somehow get the boundingBox while our sheet is not
77 // being transformed. A hacky way to do this is to modify the behavior of .sheet[hidden]
78 // depending on the state of this method. In this case we are adding .scrolled to body
79 // which will cause all hidden sheets to instead be transparent.
80 // Additionally we will stop any animations that would be playing then.
81 // Now the element should be at it's target position so we just grab the position before
82 // un-hiding the elements and voila!
83 // One caveat: if the content changes after this method returned, the scroll might be a
84 // bit offset.
85
86 let offset = getElementScrollOffset(scrollTarget);
87 if (document.body.classList.contains("scrolled")) return offset;
88
89 const hiddenElems = document.querySelectorAll(".sheet[hidden]"),
90 posMain = getElementScrollOffset(mainElem); // offset to top rel. to document
91
92 document.getElementById("wrapper").style.paddingTop = `${posMain}px`;
93 document.body.classList.add("scrolled");
94
95 mainElem.classList.add("sheet-splashed");
96 mainElem.classList.remove("sheet-splash");
97 if (scrollTarget)
98 // recalculate, as it probably changed because of body.scrolled
99 offset = getElementScrollOffset(scrollTarget);
100
101 hiddenElems.forEach((elem) => {
102 elem.removeAttribute("hidden");
103 });
104
105 window.removeEventListener("scroll", applyScrollConditionally);
106 return offset;
107}
108
109function startSpinningChars(fps = 4) {
110 const spinningChars = ["|", "/", "-", "\\"],
111 spinnerElement = document.getElementById("text-spinner");
112
113 let currentIndex = 0;
114
115 setInterval(() => {
116 spinnerElement.innerText = spinningChars[currentIndex];
117 currentIndex++;
118 if (currentIndex >= spinningChars.length) currentIndex = 0;
119 }, 1000 / fps);
120
121 spinnerElement.removeAttribute("hidden");
122}
123
124function getElementScrollOffset(elem) {
125 if (elem) return window.pageYOffset + elem.getBoundingClientRect().top;
126 return -1;
127}
128
129ready().then(() => {
130 startSpinningChars();
131});