Skip to content

Commit 98a620b

Browse files
committed
update slider + clickable to subslides
1 parent 34ced9c commit 98a620b

3 files changed

Lines changed: 114 additions & 57 deletions

File tree

website/js/delegations.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -769,7 +769,7 @@
769769
.attr("font-family", "'Elms Sans', sans-serif")
770770
.attr("font-size", 24)
771771
.attr("font-weight", 700)
772-
.text(`Distribution of medals in ${graph.season} Olympics, ${graph.year}`);
772+
.text(`Distribution of medals in ${graph.season} Olympics`);
773773

774774
svg.append("text")
775775
.attr("x", width / 2)

website/js/race-navigation.js

Lines changed: 74 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@
1212
conclusion: "Finish"
1313
};
1414

15+
const config = {
16+
mainRunnerX: "-25px", // runner position next to the vertical line
17+
firstSideRunnerX: 55, // runner position on first side slide
18+
sideStep: 28 // distance between side slide dots
19+
};
20+
1521
const state = {
1622
sections: [],
1723
currentSectionIndex: 0,
@@ -27,12 +33,14 @@
2733
.map((section, index, array) => {
2834
const anchor = section.dataset.anchor || `section-${index}`;
2935
const slides = Array.from(section.querySelectorAll(".slide"));
36+
const slideCount = Math.max(1, slides.length);
3037

3138
return {
3239
anchor,
3340
label: sectionLabels[anchor] || anchor,
34-
hasSlides: slides.length > 1,
35-
slideCount: slides.length,
41+
slideCount,
42+
extraSlides: Math.max(0, slideCount - 1),
43+
hasSlides: slideCount > 1,
3644
isStart: index === 0,
3745
isFinish: index === array.length - 1
3846
};
@@ -56,7 +64,7 @@
5664
</div>
5765
5866
<div class="race-finish-wrap" aria-hidden="true">
59-
<div class="race-finish-label">FINISH</div>
67+
<div class="race-finish-label">FINISH</div>
6068
</div>
6169
</div>
6270
`;
@@ -75,22 +83,48 @@
7583

7684
state.sections.forEach((section, index) => {
7785
const checkpoint = document.createElement("button");
78-
7986
checkpoint.className = "race-checkpoint";
8087
checkpoint.type = "button";
81-
checkpoint.dataset.index = index;
88+
checkpoint.dataset.index = String(index);
8289
checkpoint.style.top = `${getProgressPercent(index)}%`;
8390

91+
const sideDots = Array.from({ length: section.extraSlides }, (_, i) => {
92+
const slideNumber = i + 1;
93+
const left = 66 + i * config.sideStep;
94+
95+
return `
96+
<span
97+
class="race-side-dot"
98+
data-slide="${slideNumber}"
99+
style="left: ${left}px;"
100+
></span>
101+
`;
102+
}).join("");
103+
104+
const sidePathWidth =
105+
section.extraSlides > 0
106+
? 18 + (section.extraSlides - 1) * config.sideStep
107+
: 0;
108+
109+
checkpoint.style.setProperty("--side-path-width", `${sidePathWidth}px`);
110+
84111
checkpoint.innerHTML = `
85112
<span class="race-dot"></span>
86-
${section.hasSlides ? `<span class="race-side-dot"></span>` : ""}
113+
${sideDots}
87114
<span class="race-label">${section.label}</span>
88115
`;
89116

90-
checkpoint.addEventListener("click", () => {
91-
if (window.fullpage_api) {
92-
window.fullpage_api.moveTo(index + 1);
93-
}
117+
checkpoint.addEventListener("click", (event) => {
118+
if (!window.fullpage_api) return;
119+
120+
const sideDot = event.target.closest(".race-side-dot");
121+
122+
if (sideDot) {
123+
const slideNumber = Number(sideDot.dataset.slide);
124+
window.fullpage_api.moveTo(index + 1, slideNumber);
125+
return;
126+
}
127+
window.fullpage_api.moveTo(index + 1, 0);
94128
});
95129

96130
container.appendChild(checkpoint);
@@ -104,30 +138,41 @@
104138
const safeSectionIndex = clamp(sectionIndex, 0, state.sections.length - 1);
105139
const currentSection = state.sections[safeSectionIndex];
106140

141+
const safeSlideIndex = clamp(
142+
Number(slideIndex) || 0,
143+
0,
144+
currentSection.slideCount - 1
145+
);
146+
107147
state.currentSectionIndex = safeSectionIndex;
108-
state.currentSlideIndex = slideIndex;
148+
state.currentSlideIndex = safeSlideIndex;
109149

110150
const progress = getProgressPercent(safeSectionIndex);
111151

112152
nav.style.setProperty("--race-progress", `${progress}%`);
113153

154+
updateRunner(nav, currentSection, progress, safeSlideIndex);
155+
updateNavClasses(nav, currentSection, safeSectionIndex, direction);
156+
updateCheckpoints(safeSectionIndex, safeSlideIndex);
157+
}
158+
159+
function updateRunner(nav, currentSection, progress, slideIndex) {
114160
const runner = nav.querySelector(".race-runner");
115-
if (runner) {
161+
if (!runner) return;
162+
116163
runner.style.top = `${progress}%`;
117164

118-
const shouldMoveRight =
119-
currentSection &&
120-
currentSection.hasSlides &&
121-
slideIndex > 0;
165+
let runnerX = config.mainRunnerX;
122166

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-
);
167+
if (currentSection.hasSlides && slideIndex > 0) {
168+
runnerX = `${config.firstSideRunnerX + (slideIndex - 1) * config.sideStep}px`;
128169
}
129170

130-
nav.classList.toggle("race-is-start", safeSectionIndex === 0);
171+
runner.style.setProperty("--runner-x", runnerX);
172+
}
173+
174+
function updateNavClasses(nav, currentSection, sectionIndex, direction) {
175+
nav.classList.toggle("race-is-start", sectionIndex === 0);
131176
nav.classList.toggle("race-is-finish", Boolean(currentSection?.isFinish));
132177

133178
nav.classList.toggle("race-going-up", direction === "up");
@@ -141,30 +186,28 @@
141186
nav._raceTimer = window.setTimeout(() => {
142187
nav.classList.remove("race-is-moving");
143188
}, 700);
144-
145-
updateCheckpoints(safeSectionIndex, slideIndex);
146189
}
147190

148191
function updateCheckpoints(activeSectionIndex, activeSlideIndex) {
149192
document.querySelectorAll(".race-checkpoint").forEach((checkpoint, index) => {
150-
const section = state.sections[index];
151-
152193
checkpoint.classList.toggle("is-active", index === activeSectionIndex);
153194
checkpoint.classList.toggle("is-passed", index < activeSectionIndex);
154195

155-
const sideDot = checkpoint.querySelector(".race-side-dot");
196+
const sideDots = checkpoint.querySelectorAll(".race-side-dot");
197+
198+
sideDots.forEach((sideDot) => {
199+
const slideNumber = Number(sideDot.dataset.slide);
156200

157-
if (sideDot && section) {
158201
const sidePassed =
159202
index < activeSectionIndex ||
160-
(index === activeSectionIndex && activeSlideIndex > 0);
203+
(index === activeSectionIndex && activeSlideIndex > slideNumber);
161204

162205
const sideActive =
163-
index === activeSectionIndex && activeSlideIndex > 0;
206+
index === activeSectionIndex && activeSlideIndex === slideNumber;
164207

165208
sideDot.classList.toggle("is-passed-side", sidePassed);
166209
sideDot.classList.toggle("is-active-side", sideActive);
167-
}
210+
});
168211
});
169212
}
170213

website/styles.css

Lines changed: 39 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,14 @@ body {
5858
background: white;
5959
}
6060

61+
.fp-slidesNav ul li a span {
62+
background: rgba(255, 255, 255, 0.8) !important;
63+
}
64+
65+
.fp-slidesNav ul li a.active span {
66+
background: white !important;
67+
}
68+
6169
/* =============================================
6270
Paragraph in two columns
6371
=========================================== */
@@ -1060,21 +1068,26 @@ p {
10601068
position: absolute;
10611069
left: 46px;
10621070
top: 50%;
1063-
width: 18px;
1071+
width: var(--side-path-width, 0px);
10641072
height: 3px;
10651073
transform: translateY(-50%);
10661074
border-radius: 999px;
10671075
background: rgba(255, 255, 255, 0.42);
10681076
opacity: 0;
1077+
z-index: 0;
10691078
}
10701079

10711080
.race-checkpoint:has(.race-side-dot)::after {
10721081
opacity: 1;
10731082
}
10741083

1084+
.race-dot,
1085+
.race-side-dot {
1086+
z-index: 2;
1087+
}
1088+
10751089
.race-side-dot {
10761090
position: absolute;
1077-
left: 66px;
10781091
top: 50%;
10791092
width: 11px;
10801093
height: 11px;
@@ -1085,33 +1098,34 @@ p {
10851098
box-shadow:
10861099
0 0 0 2px rgba(0, 0, 0, 0.25),
10871100
0 0 8px rgba(255, 255, 255, 0.24);
1088-
transition:
1089-
transform 0.25s ease,
1090-
background 0.25s ease,
1091-
border-color 0.25s ease,
1092-
box-shadow 0.25s ease;
10931101
}
10941102

1095-
.race-checkpoint.is-passed .race-side-dot {
1096-
background: #f4b400;
1097-
border-color: #f4b400;
1098-
box-shadow:
1099-
0 0 0 2px rgba(0, 0, 0, 0.25),
1100-
0 0 10px rgba(244, 180, 0, 0.65);
1101-
}
1103+
/* Hover labels */
1104+
.race-label {
1105+
position: absolute;
1106+
left: 36px;
1107+
top: 50%;
1108+
transform: translateY(-50%);
1109+
opacity: 0;
1110+
pointer-events: none;
11021111

1103-
.race-checkpoint.is-active .race-side-dot {
1104-
background: white;
1105-
border-color: white;
1106-
transform: translateY(-50%) scale(1.18);
1107-
box-shadow:
1108-
0 0 0 4px rgba(244, 180, 0, 0.18),
1109-
0 0 14px rgba(255, 255, 255, 0.72);
1112+
color: white;
1113+
background: rgba(0, 0, 0, 0.85);
1114+
border: 1px solid rgba(255, 255, 255, 0.35);
1115+
border-radius: 8px;
1116+
padding: 4px 8px;
1117+
1118+
font-family: "Elms Sans", sans-serif;
1119+
font-size: 13px;
1120+
white-space: nowrap;
1121+
z-index: 10;
1122+
1123+
transition: opacity 0.18s ease, transform 0.18s ease;
11101124
}
11111125

1112-
/* Hide text labels for clean look */
1113-
.race-label {
1114-
display: none;
1126+
.race-checkpoint:hover .race-label {
1127+
opacity: 1;
1128+
transform: translateY(-50%) translateX(4px);
11151129
}
11161130

11171131
/* =============================================
@@ -1126,7 +1140,7 @@ p {
11261140
position: absolute;
11271141

11281142
/* main position = to the LEFT of the vertical track */
1129-
left: var(--runner-x, -20px);
1143+
left: var(--runner-x, -25px);
11301144

11311145
top: 0%;
11321146
width: 42px;

0 commit comments

Comments
 (0)