Skip to content

Commit 34ced9c

Browse files
committed
animated slider
1 parent 9ecb2a6 commit 34ced9c

3 files changed

Lines changed: 608 additions & 1 deletion

File tree

website/index.html

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -322,19 +322,47 @@ <h2>Project Gamma</h2>
322322
<script type="module" src="js/body-types.js"></script>
323323
<script src="js/geopolitics-conflicts.js"></script>
324324
<script src="js/fair-distribution.js"></script>
325+
<script src="js/race-navigation.js"></script>
325326

326327

327328
<script>
328329
new fullpage('#website', {
329330
autoScrolling: true,
330-
navigation: true,
331+
332+
// We replace the default fullPage dots with our race track.
333+
navigation: false,
334+
331335
navigationPosition: 'right',
332336
scrollingSpeed: 700,
333337
slidesNavigation: true,
334338
controlArrows: false,
335339
continuousHorizontal: false,
336340
loopHorizontal: false,
337341
scrollHorizontally: true,
342+
343+
onLeave: function(origin, destination, direction) {
344+
if (window.RaceNavigation) {
345+
window.RaceNavigation.update(destination.index, 0, direction);
346+
}
347+
},
348+
349+
afterLoad: function(origin, destination, direction) {
350+
if (window.RaceNavigation) {
351+
window.RaceNavigation.update(destination.index, 0, direction);
352+
}
353+
},
354+
355+
onSlideLeave: function(section, origin, destination, direction) {
356+
if (window.RaceNavigation) {
357+
window.RaceNavigation.update(section.index, destination.index, direction);
358+
}
359+
},
360+
361+
afterSlideLoad: function(section, origin, destination, direction) {
362+
if (window.RaceNavigation) {
363+
window.RaceNavigation.update(section.index, destination.index, direction);
364+
}
365+
}
338366
});
339367
</script>
340368

website/js/race-navigation.js

Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
(function () {
2+
"use strict";
3+
4+
const sectionLabels = {
5+
title: "Start",
6+
introduction: "Intro",
7+
gender: "Gender",
8+
delegations: "Medals",
9+
"body-types": "Bodies",
10+
geopolitics: "Politics",
11+
fair: "Fairness",
12+
conclusion: "Finish"
13+
};
14+
15+
const state = {
16+
sections: [],
17+
currentSectionIndex: 0,
18+
currentSlideIndex: 0
19+
};
20+
21+
function initRaceNavigation() {
22+
const oldNav = document.getElementById("race-nav");
23+
if (oldNav) oldNav.remove();
24+
25+
state.sections = Array.from(document.querySelectorAll("#website > .section"))
26+
.filter(section => !section.classList.contains("fp-auto-height"))
27+
.map((section, index, array) => {
28+
const anchor = section.dataset.anchor || `section-${index}`;
29+
const slides = Array.from(section.querySelectorAll(".slide"));
30+
31+
return {
32+
anchor,
33+
label: sectionLabels[anchor] || anchor,
34+
hasSlides: slides.length > 1,
35+
slideCount: slides.length,
36+
isStart: index === 0,
37+
isFinish: index === array.length - 1
38+
};
39+
});
40+
41+
const nav = document.createElement("div");
42+
nav.id = "race-nav";
43+
44+
nav.innerHTML = `
45+
<div class="race-track-shell">
46+
<div class="race-start">START</div>
47+
48+
<div class="race-track">
49+
<div class="race-track-line"></div>
50+
<div class="race-track-progress"></div>
51+
<div class="race-checkpoints"></div>
52+
53+
<div class="race-runner" aria-hidden="true">
54+
${runnerSvg()}
55+
</div>
56+
</div>
57+
58+
<div class="race-finish-wrap" aria-hidden="true">
59+
<div class="race-finish-label">FINISH</div>
60+
</div>
61+
</div>
62+
`;
63+
64+
document.body.appendChild(nav);
65+
66+
buildCheckpoints();
67+
updateRaceNavigation(0, 0, "down");
68+
}
69+
70+
function buildCheckpoints() {
71+
const container = document.querySelector(".race-checkpoints");
72+
if (!container) return;
73+
74+
container.innerHTML = "";
75+
76+
state.sections.forEach((section, index) => {
77+
const checkpoint = document.createElement("button");
78+
79+
checkpoint.className = "race-checkpoint";
80+
checkpoint.type = "button";
81+
checkpoint.dataset.index = index;
82+
checkpoint.style.top = `${getProgressPercent(index)}%`;
83+
84+
checkpoint.innerHTML = `
85+
<span class="race-dot"></span>
86+
${section.hasSlides ? `<span class="race-side-dot"></span>` : ""}
87+
<span class="race-label">${section.label}</span>
88+
`;
89+
90+
checkpoint.addEventListener("click", () => {
91+
if (window.fullpage_api) {
92+
window.fullpage_api.moveTo(index + 1);
93+
}
94+
});
95+
96+
container.appendChild(checkpoint);
97+
});
98+
}
99+
100+
function updateRaceNavigation(sectionIndex = 0, slideIndex = 0, direction = "down") {
101+
const nav = document.getElementById("race-nav");
102+
if (!nav || !state.sections.length) return;
103+
104+
const safeSectionIndex = clamp(sectionIndex, 0, state.sections.length - 1);
105+
const currentSection = state.sections[safeSectionIndex];
106+
107+
state.currentSectionIndex = safeSectionIndex;
108+
state.currentSlideIndex = slideIndex;
109+
110+
const progress = getProgressPercent(safeSectionIndex);
111+
112+
nav.style.setProperty("--race-progress", `${progress}%`);
113+
114+
const runner = nav.querySelector(".race-runner");
115+
if (runner) {
116+
runner.style.top = `${progress}%`;
117+
118+
const shouldMoveRight =
119+
currentSection &&
120+
currentSection.hasSlides &&
121+
slideIndex > 0;
122+
123+
// left of vertical line by default, move right when on horizontal slide
124+
runner.style.setProperty(
125+
"--runner-x",
126+
shouldMoveRight ? "60px" : "-20px"
127+
);
128+
}
129+
130+
nav.classList.toggle("race-is-start", safeSectionIndex === 0);
131+
nav.classList.toggle("race-is-finish", Boolean(currentSection?.isFinish));
132+
133+
nav.classList.toggle("race-going-up", direction === "up");
134+
nav.classList.toggle("race-going-down", direction === "down");
135+
nav.classList.toggle("race-going-right", direction === "right");
136+
nav.classList.toggle("race-going-left", direction === "left");
137+
138+
nav.classList.add("race-is-moving");
139+
140+
window.clearTimeout(nav._raceTimer);
141+
nav._raceTimer = window.setTimeout(() => {
142+
nav.classList.remove("race-is-moving");
143+
}, 700);
144+
145+
updateCheckpoints(safeSectionIndex, slideIndex);
146+
}
147+
148+
function updateCheckpoints(activeSectionIndex, activeSlideIndex) {
149+
document.querySelectorAll(".race-checkpoint").forEach((checkpoint, index) => {
150+
const section = state.sections[index];
151+
152+
checkpoint.classList.toggle("is-active", index === activeSectionIndex);
153+
checkpoint.classList.toggle("is-passed", index < activeSectionIndex);
154+
155+
const sideDot = checkpoint.querySelector(".race-side-dot");
156+
157+
if (sideDot && section) {
158+
const sidePassed =
159+
index < activeSectionIndex ||
160+
(index === activeSectionIndex && activeSlideIndex > 0);
161+
162+
const sideActive =
163+
index === activeSectionIndex && activeSlideIndex > 0;
164+
165+
sideDot.classList.toggle("is-passed-side", sidePassed);
166+
sideDot.classList.toggle("is-active-side", sideActive);
167+
}
168+
});
169+
}
170+
171+
function getProgressPercent(index) {
172+
if (state.sections.length <= 1) return 0;
173+
return (index / (state.sections.length - 1)) * 100;
174+
}
175+
176+
function clamp(value, min, max) {
177+
return Math.min(Math.max(value, min), max);
178+
}
179+
180+
function runnerSvg() {
181+
return `
182+
<svg viewBox="0 0 80 80" class="runner-svg">
183+
<circle class="runner-head" cx="42" cy="14" r="7"></circle>
184+
<line class="runner-body" x1="42" y1="22" x2="37" y2="42"></line>
185+
186+
<line class="runner-arm runner-arm-front" x1="39" y1="28" x2="20" y2="36"></line>
187+
<line class="runner-arm runner-arm-back" x1="39" y1="28" x2="58" y2="34"></line>
188+
189+
<line class="runner-leg runner-leg-front" x1="37" y1="42" x2="20" y2="63"></line>
190+
<line class="runner-leg runner-leg-back" x1="37" y1="42" x2="59" y2="61"></line>
191+
</svg>
192+
`;
193+
}
194+
195+
window.RaceNavigation = {
196+
init: initRaceNavigation,
197+
update: updateRaceNavigation
198+
};
199+
200+
if (document.readyState === "loading") {
201+
document.addEventListener("DOMContentLoaded", initRaceNavigation);
202+
} else {
203+
initRaceNavigation();
204+
}
205+
})();

0 commit comments

Comments
 (0)