diff --git a/frontend/css/style.css b/frontend/css/style.css
index 72625834a..25057d477 100644
--- a/frontend/css/style.css
+++ b/frontend/css/style.css
@@ -7501,3 +7501,26 @@ dialog.book-preview-modal::backdrop {
font-size: 0.95rem;
}
}
+/* ── Reading Time Badge — Issue #606 ── */
+.reading-time-badge,
+.reading-time-remaining {
+ display: inline-flex;
+ align-items: center;
+ gap: 5px;
+ font-size: 0.7rem;
+ padding: 3px 8px;
+ border-radius: 10px;
+ margin-bottom: 6px;
+}
+
+.reading-time-badge {
+ background: rgba(212, 175, 55, 0.15);
+ color: var(--accent-gold);
+ border: 1px solid rgba(212, 175, 55, 0.3);
+}
+
+.reading-time-remaining {
+ background: rgba(29, 158, 117, 0.12);
+ color: #1D9E75;
+ border: 1px solid rgba(29, 158, 117, 0.25);
+}
\ No newline at end of file
diff --git a/frontend/js/app.js b/frontend/js/app.js
index bc22b829b..d45334e17 100644
--- a/frontend/js/app.js
+++ b/frontend/js/app.js
@@ -79,6 +79,8 @@
// API_BASE and MOOD_API_BASE are declared globally in config.js (loaded first).
// Do NOT re-declare them here — use the globals from config.js directly.
+import { calcReadingTime, calcRemainingTime } from './readingTime.js';
+
if (typeof window.IS_DEV === 'undefined') {
window.IS_DEV = typeof window !== 'undefined' && ['localhost', '127.0.0.1', '::1'].includes(window.location.hostname);
}
@@ -710,6 +712,17 @@ class BookRenderer {
Read Details
+ ${(() => {
+ const pages = bookData?.volumeInfo?.pageCount;
+ const { label: totalLabel } = calcReadingTime(pages);
+ const { label: remainLabel } = calcRemainingTime(pages, progress);
+ if (!totalLabel) return '';
+ return `
+
+
+ ${shelf === 'current' && remainLabel ? remainLabel + ' left' : totalLabel}
+
`;
+})()}
${shelf === 'current' ? `
diff --git a/frontend/js/library-3d.js b/frontend/js/library-3d.js
index fcf93481d..25cb27623 100644
--- a/frontend/js/library-3d.js
+++ b/frontend/js/library-3d.js
@@ -4,6 +4,7 @@
*/
// Sample books data for demonstration
+import { calcReadingTime, calcRemainingTime } from './readingTime.js';
const SAMPLE_BOOKS = {
current: [
{
@@ -1869,6 +1870,16 @@ class BookshelfRenderer3D {
authorSpan.className = 'spine-author';
authorSpan.textContent = book.author ? book.author.split(' ').pop() : '';
face.appendChild(authorSpan);
+ // Issue #606 — Reading Time Badge on spine
+const pages = book.pageCount || book.page_count || book.volumeInfo?.pageCount;
+const { label: timeLabel } = calcReadingTime(pages);
+if (timeLabel) {
+ const timeBadge = document.createElement('div');
+ timeBadge.className = 'reading-time-badge';
+ timeBadge.style.cssText = 'writing-mode: vertical-rl; transform: rotate(180deg); font-size: 0.55rem; margin-top: 6px; padding: 2px 4px;';
+ timeBadge.innerHTML = ` ${timeLabel}`;
+ face.appendChild(timeBadge);
+}
if (traits.pattern.includes('ornament')) {
const ornament = document.createElement('div');
@@ -2262,6 +2273,22 @@ class BookshelfRenderer3D {
if (titleEl) titleEl.textContent = book.title;
if (authorEl) authorEl.textContent = book.author; // Removed "by" prefix to match design
+ // Issue #606 — Populate reading time in modal
+const readingTimeStat = document.getElementById('modal-reading-time-stat');
+const readingTimeLabel = document.getElementById('modal-reading-time-label');
+const pages = book.pageCount || book.page_count;
+const progress = typeof book.progress === 'number' ? book.progress : 0;
+const { label: totalTimeLabel } = calcReadingTime(pages);
+const { label: remainLabel } = calcRemainingTime(pages, progress);
+
+if (readingTimeStat && totalTimeLabel) {
+ readingTimeStat.style.display = 'block';
+ if (readingTimeLabel) {
+ readingTimeLabel.textContent = book.shelfType === 'current' && remainLabel
+ ? `${remainLabel} left`
+ : totalTimeLabel;
+ }
+}
if (starsEl) starsEl.textContent = this.getStarRating(book.rating);
if (scoreEl) scoreEl.textContent = (book.rating != null ? book.rating.toFixed(1) : 'N/A');
if (countEl) countEl.textContent = `(${book.ratingCount} ratings)`;
diff --git a/frontend/pages/library.html b/frontend/pages/library.html
index 7a67ad7e2..952627600 100644
--- a/frontend/pages/library.html
+++ b/frontend/pages/library.html
@@ -435,6 +435,14 @@
1,234 ratings
+
+
+
+
+
+
+
Est. Reading Time
+