diff --git a/.prettierignore b/.prettierignore
index 910a633..987dd0d 100644
--- a/.prettierignore
+++ b/.prettierignore
@@ -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
diff --git a/TODO.md b/TODO.md
index 600ee20..55a5505 100644
--- a/TODO.md
+++ b/TODO.md
@@ -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
-
-
-
-
diff --git a/sass/_feed.scss b/sass/_feed.scss
index 88b3dad..238ad6f 100644
--- a/sass/_feed.scss
+++ b/sass/_feed.scss
@@ -36,7 +36,7 @@
letter-spacing: 0.04em;
&::after {
- content: "→";
+ content: '→';
transition: transform 0.2s ease;
}
diff --git a/sass/_scroll-top.scss b/sass/_scroll-top.scss
new file mode 100644
index 0000000..2f48542
--- /dev/null
+++ b/sass/_scroll-top.scss
@@ -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;
+ }
+}
diff --git a/sass/main.scss b/sass/main.scss
index 55b45fa..7085875 100644
--- a/sass/main.scss
+++ b/sass/main.scss
@@ -13,3 +13,4 @@
@import 'footer';
@import 'search';
@import 'utilities';
+@import 'scroll-top';
diff --git a/static/js/scroll-top.js b/static/js/scroll-top.js
new file mode 100644
index 0000000..f7a8ad7
--- /dev/null
+++ b/static/js/scroll-top.js
@@ -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;
+ }
+ });
+});
diff --git a/templates/base.html b/templates/base.html
index bafde57..d77445f 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -245,9 +245,15 @@
{% include "partials/footer.html" %}
+
+
{% block scripts %}{% endblock scripts %}