feat: Add a scroll-to-top button with accompanying styles and JavaScript functionality.

This commit is contained in:
2026-03-11 21:26:43 +03:00
parent 5181ef476d
commit dcb1e505fb
7 changed files with 104 additions and 8 deletions
+1
View File
@@ -4,6 +4,7 @@ node_modules/
# Ignore Zola HTML templates (Tera syntax breaks standard HTML parsing)
templates/**/*.html
templates/**/*.json
# Ignore minified static files
static/**/*.min.js
+1 -7
View File
@@ -33,7 +33,7 @@ Feature backlog for the blog. Check off items as they're implemented.
## UI / UX Polish
- [ ] **Scroll-to-top button** — floating button that appears after scrolling down, smooth-scrolls back to top
- [x] **Scroll-to-top button** — floating button that appears after scrolling down, smooth-scrolls back to top
- [ ] **Table of contents active-section highlight** — as you scroll, the current heading is highlighted in the TOC
- [ ] **Terminal-style command palette** — `Ctrl+K` opens a fuzzy-search palette over posts and nav links
- [ ] **Animated hero terminal prompt** — typewriter effect on the homepage subtitle / profile section
@@ -43,12 +43,6 @@ Feature backlog for the blog. Check off items as they're implemented.
---
## Distribution & Reach
- [ ] **Newsletter / email subscription** — integrate Buttondown or self-hosted Listmonk with a subscribe form
+1 -1
View File
@@ -36,7 +36,7 @@
letter-spacing: 0.04em;
&::after {
content: "→";
content: '→';
transition: transform 0.2s ease;
}
+60
View File
@@ -0,0 +1,60 @@
// Scroll to Top Floating Button
.scroll-to-top {
position: fixed;
bottom: 2rem;
right: 2rem;
display: flex;
align-items: center;
justify-content: center;
width: 3rem;
height: 3rem;
border-radius: 50%;
background: var(--bg-color);
color: var(--accent-color);
border: 1px solid var(--accent-color);
cursor: pointer;
z-index: 100;
// Hidden state by default
opacity: 0;
visibility: hidden;
transform: translateY(1rem);
transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);
// Visible state triggered by JS
&.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
@media (hover: hover) {
&:hover {
background: var(--accent-color);
color: var(--bg-color);
transform: translateY(-0.25rem);
box-shadow: 0 4px 12px rgba(16, 185, 129, 0.2); // Emerald glow matching accent
}
&:hover svg {
transform: translateY(-2px);
}
}
&:focus-visible {
outline: 2px solid var(--accent-color);
outline-offset: 4px;
}
svg {
transition: transform 0.2s ease;
}
// Responsive padding for mobile devices
@media (max-width: 768px) {
bottom: 1.5rem;
right: 1.5rem;
width: 2.75rem;
height: 2.75rem;
}
}
+1
View File
@@ -13,3 +13,4 @@
@import 'footer';
@import 'search';
@import 'utilities';
@import 'scroll-top';
+34
View File
@@ -0,0 +1,34 @@
document.addEventListener('DOMContentLoaded', () => {
const scrollToTopBtn = document.getElementById('scroll-to-top');
if (!scrollToTopBtn) return;
// Show button when scrolled down 400px
const toggleVisibility = () => {
if (window.scrollY > 400) {
scrollToTopBtn.classList.add('visible');
} else {
scrollToTopBtn.classList.remove('visible');
}
};
// Smooth scroll to top when clicked
scrollToTopBtn.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth',
});
});
// Throttle the scroll event listener a tiny bit for performance
let isTicking = false;
window.addEventListener('scroll', () => {
if (!isTicking) {
window.requestAnimationFrame(() => {
toggleVisibility();
isTicking = false;
});
isTicking = true;
}
});
});
+6
View File
@@ -245,9 +245,15 @@
{% include "partials/footer.html" %}
</div>
<button id="scroll-to-top" class="scroll-to-top" aria-label="Scroll to top">
<svg viewBox="0 0 24 24" width="24" height="24" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<polyline points="18 15 12 9 6 15"></polyline>
</svg>
</button>
<script src="{{ get_url(path="js/theme.js") }}"></script>
<script src="{{ get_url(path="js/copy-code.js") }}"></script>
<script defer src="{{ get_url(path="js/scroll-top.js") }}"></script>
{% block scripts %}{% endblock scripts %}
</body>
</html>