diff --git a/README.md b/README.md
index 3848171..075ee8f 100644
--- a/README.md
+++ b/README.md
@@ -5,7 +5,10 @@ A Flask-based webcomic website with server-side rendering using Jinja2 templates
## Features
- Comic viewer with navigation (First, Previous, Next, Latest)
+- Client-side navigation using JSON API (no page reloads)
- Archive page with thumbnail grid
+- RSS feed support
+- JSON API for programmatic access
- Responsive design
- Server-side rendering with Jinja2
- Clean, comic-focused layout
@@ -33,6 +36,8 @@ sunday/
└── static/ # Static files
├── css/
│ └── style.css # Main stylesheet
+ ├── js/
+ │ └── comic-nav.js # Client-side navigation
├── images/ # Comic images
│ └── thumbs/ # Thumbnail images for archive
└── feed.rss # RSS feed (generated)
diff --git a/static/js/comic-nav.js b/static/js/comic-nav.js
new file mode 100644
index 0000000..7a89329
--- /dev/null
+++ b/static/js/comic-nav.js
@@ -0,0 +1,151 @@
+// Client-side comic navigation using the API
+(function() {
+ 'use strict';
+
+ let totalComics = 0;
+
+ // Fetch and display a comic
+ async function loadComic(comicId) {
+ try {
+ const response = await fetch(`/api/comics/${comicId}`);
+
+ if (!response.ok) {
+ if (response.status === 404) {
+ window.location.href = '/404';
+ return;
+ }
+ throw new Error('Failed to load comic');
+ }
+
+ const comic = response.json ? await response.json() : comic;
+ displayComic(comic);
+
+ } catch (error) {
+ console.error('Error loading comic:', error);
+ }
+ }
+
+ // Update the page with comic data
+ function displayComic(comic) {
+ // Update title
+ const title = comic.title || `#${comic.number}`;
+ document.querySelector('.comic-header h1').textContent = title;
+
+ // Update date
+ document.querySelector('.comic-date').textContent = comic.date;
+
+ // Update image
+ const img = document.querySelector('.comic-image img');
+ img.src = `/static/images/${comic.filename}`;
+ img.alt = title;
+ img.title = comic.alt_text;
+
+ // Update author note
+ const transcriptDiv = document.querySelector('.comic-transcript');
+ if (comic.author_note) {
+ if (!transcriptDiv) {
+ const container = document.querySelector('.comic-container');
+ const newDiv = document.createElement('div');
+ newDiv.className = 'comic-transcript';
+ newDiv.innerHTML = '
Author Note
';
+ container.appendChild(newDiv);
+ }
+ document.querySelector('.comic-transcript p').textContent = comic.author_note;
+ document.querySelector('.comic-transcript').style.display = 'block';
+ } else if (transcriptDiv) {
+ transcriptDiv.style.display = 'none';
+ }
+
+ // Update navigation buttons
+ updateNavButtons(comic.number);
+
+ // Update page title
+ document.title = `${title} - Sunday Comics`;
+
+ // Update URL without reload
+ history.pushState({ comicId: comic.number }, '', `/comic/${comic.number}`);
+ }
+
+ // Update navigation button states
+ function updateNavButtons(currentNumber) {
+ const navButtons = document.querySelector('.nav-buttons');
+
+ // First button
+ const firstBtn = navButtons.children[0];
+ if (currentNumber > 1) {
+ firstBtn.className = 'btn btn-nav';
+ firstBtn.onclick = (e) => { e.preventDefault(); loadComic(1); };
+ } else {
+ firstBtn.className = 'btn btn-nav btn-disabled';
+ firstBtn.onclick = null;
+ }
+
+ // Previous button
+ const prevBtn = navButtons.children[1];
+ if (currentNumber > 1) {
+ prevBtn.className = 'btn btn-nav';
+ prevBtn.onclick = (e) => { e.preventDefault(); loadComic(currentNumber - 1); };
+ } else {
+ prevBtn.className = 'btn btn-nav btn-disabled';
+ prevBtn.onclick = null;
+ }
+
+ // Comic number display
+ navButtons.children[2].textContent = `Comic #${currentNumber}`;
+
+ // Next button
+ const nextBtn = navButtons.children[3];
+ if (currentNumber < totalComics) {
+ nextBtn.className = 'btn btn-nav';
+ nextBtn.onclick = (e) => { e.preventDefault(); loadComic(currentNumber + 1); };
+ } else {
+ nextBtn.className = 'btn btn-nav btn-disabled';
+ nextBtn.onclick = null;
+ }
+
+ // Latest button
+ const latestBtn = navButtons.children[4];
+ if (currentNumber < totalComics) {
+ latestBtn.className = 'btn btn-nav';
+ latestBtn.onclick = (e) => { e.preventDefault(); loadComic(totalComics); };
+ } else {
+ latestBtn.className = 'btn btn-nav btn-disabled';
+ latestBtn.onclick = null;
+ }
+ }
+
+ // Initialize on page load
+ async function init() {
+ // Only run on comic pages
+ if (!document.querySelector('.comic-container')) {
+ return;
+ }
+
+ // Get total comics count from the page
+ totalComics = parseInt(document.querySelector('.comic-container').dataset.totalComics || 0);
+
+ // Get current comic number
+ const currentNumber = parseInt(document.querySelector('.comic-container').dataset.comicNumber || 0);
+
+ if (currentNumber && totalComics) {
+ updateNavButtons(currentNumber);
+ }
+
+ // Handle browser back/forward
+ window.addEventListener('popstate', (event) => {
+ if (event.state && event.state.comicId) {
+ loadComic(event.state.comicId);
+ }
+ });
+
+ // Set initial state
+ history.replaceState({ comicId: currentNumber }, '', window.location.pathname);
+ }
+
+ // Run when DOM is ready
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+})();
diff --git a/templates/base.html b/templates/base.html
index 6eba9bb..f4a061e 100644
--- a/templates/base.html
+++ b/templates/base.html
@@ -61,6 +61,7 @@
+
{% block extra_js %}{% endblock %}