Books

A personal reading archive

The Bookshelf.

Some stories you finish. Some stories quietly become a part of you.

27 books and counting

`; } function render() { const term = searchEl.value.toLowerCase().trim(); const filtered = books.filter(book => { const matchesCategory = activeCategory === "All" || book.group === activeCategory; const matchesSearch = !term || (book.title + " " + book.author + " " + book.group).toLowerCase().includes(term); return matchesCategory && matchesSearch; }); root.querySelector('#rr-count').textContent = `${filtered.length} of ${books.length} books · and counting`; booksEl.innerHTML = ""; if (!filtered.length) { booksEl.innerHTML = '
No books found. Try another search.
'; return; } filtered.forEach((book, index) => { const btn = document.createElement('button'); btn.className = 'rr-book'; btn.type = 'button'; btn.setAttribute('aria-label', book.title + ' by ' + book.author); btn.innerHTML = `
${book.title}
${coverMarkup(book)}
`; btn.addEventListener('click', () => openBook(book)); booksEl.appendChild(btn); }); } function openBook(book) { activeBook = book; root.querySelector('#rr-review-category').textContent = book.group; root.querySelector('#rr-review-title').textContent = book.title; root.querySelector('#rr-review-author').textContent = 'by ' + book.author; const cover = root.querySelector('#rr-review-cover'); cover.innerHTML = book.image ? `` : `${book.title}`; root.querySelector('#rr-punchline').textContent = book.punchline || "A book, a few thoughts, and a place to remember them."; root.querySelector('#rr-review-text').textContent = book.review || "My notes on this book will find their way here. Until then, this space is being kept warm."; root.querySelector('#rr-status').textContent = book.status || "READING ARCHIVE"; const stars = root.querySelector('#rr-stars'); stars.innerHTML = ""; for (let i = 1; i <= 5; i++) { const star = document.createElement('button'); star.className = 'rr-star' + ((ratings[book.title] || 0) >= i ? ' selected' : ''); star.type = 'button'; star.textContent = '★'; star.setAttribute('aria-label', 'Rate ' + i + ' stars'); star.addEventListener('click', () => { ratings[book.title] = i; openBook(book); }); stars.appendChild(star); } overlay.classList.add('open'); document.body.style.overflow = 'hidden'; lookupCover(book); } function closeBook() { overlay.classList.remove('open'); document.body.style.overflow = ''; } async function lookupCover(book) { if (book.image || book.coverAttempted) return; book.coverAttempted = true; try { const query = encodeURIComponent('intitle:' + book.title + ' inauthor:' + book.author); const response = await fetch('https://www.googleapis.com/books/v1/volumes?q=' + query + '&maxResults=1'); const data = await response.json(); const image = data.items?.[0]?.volumeInfo?.imageLinks?.thumbnail; if (image) { book.image = image.replace('http://', 'https://'); if (activeBook === book) { root.querySelector('#rr-review-cover').innerHTML = ``; } render(); } } catch (error) { // The typographic cover remains as a fallback. } } root.querySelectorAll('.rr-category').forEach(button => { button.addEventListener('click', () => { root.querySelectorAll('.rr-category').forEach(item => item.classList.remove('active')); button.classList.add('active'); activeCategory = button.dataset.category; render(); }); }); searchEl.addEventListener('input', render); root.querySelector('#rr-close').addEventListener('click', closeBook); overlay.addEventListener('click', event => { if (event.target === overlay) closeBook(); }); document.addEventListener('keydown', event => { if (event.key === 'Escape') closeBook(); }); render(); })();