Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions frontend/css/style.css
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
13 changes: 13 additions & 0 deletions frontend/js/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
Expand Down Expand Up @@ -710,6 +712,17 @@ class BookRenderer {
<i class="fa-solid fa-circle-info"></i> Read Details
</button>

${(() => {
const pages = bookData?.volumeInfo?.pageCount;
const { label: totalLabel } = calcReadingTime(pages);
const { label: remainLabel } = calcRemainingTime(pages, progress);
if (!totalLabel) return '';
return `
<div class="reading-time-badge" style="margin-bottom: 6px;">
<i class="fa-regular fa-clock"></i>
<span>${shelf === 'current' && remainLabel ? remainLabel + ' left' : totalLabel}</span>
</div>`;
})()}
${shelf === 'current' ? `
<div class="reading-progress">
<input type="range" min="0" max="100" value="${progress}" class="progress-slider" />
Expand Down
27 changes: 27 additions & 0 deletions frontend/js/library-3d.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
*/

// Sample books data for demonstration
import { calcReadingTime, calcRemainingTime } from './readingTime.js';
const SAMPLE_BOOKS = {
current: [
{
Expand Down Expand Up @@ -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 = `<i class="fa-regular fa-clock"></i> ${timeLabel}`;
face.appendChild(timeBadge);
}

if (traits.pattern.includes('ornament')) {
const ornament = document.createElement('div');
Expand Down Expand Up @@ -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)`;
Expand Down
8 changes: 8 additions & 0 deletions frontend/pages/library.html
Original file line number Diff line number Diff line change
Expand Up @@ -435,6 +435,14 @@ <h1 class="book-title-large" id="modal-title"></h1>
<span class="stat-label" id="modal-rating-count">1,234 ratings</span>
</div>
<div class="stat-item">
<!-- Issue #606: Estimated Reading Time -->
<div class="stat-item" id="modal-reading-time-stat" style="display:none;">
<div class="reading-time-badge" id="modal-reading-time">
<i class="fa-regular fa-clock"></i>
<span id="modal-reading-time-label"></span>
</div>
<span class="stat-label">Est. Reading Time</span>
</div>
<div class="book-categories" id="modal-categories"></div>
</div>
</div>
Expand Down
Loading