diff --git a/GanjooRazor/Pages/Index.cshtml b/GanjooRazor/Pages/Index.cshtml
index c7d4ed39..3a0b717a 100644
--- a/GanjooRazor/Pages/Index.cshtml
+++ b/GanjooRazor/Pages/Index.cshtml
@@ -76,7 +76,7 @@ else
- برای جستجوی کامل در شعرها کلید اینتر را بزنید یا روی «بیاب» کلیک کنید؛ فهرست زیر فقط جستجوی نام سخنوران است.
+ برای جستجو در متون «بیاب» را بزنید، فهرست زیر تنها حاصل جستجو در فهرست سخنوران و کتابهاست.
@@ -197,12 +197,12 @@ else
- مرور کتابها
+ قفسهٔ کتابها
diff --git a/GanjooRazor/wwwroot/css/p8.css b/GanjooRazor/wwwroot/css/p8.css
index 7a87f030..d08e1141 100644
--- a/GanjooRazor/wwwroot/css/p8.css
+++ b/GanjooRazor/wwwroot/css/p8.css
@@ -342,7 +342,94 @@ select {
color: var(--ink-black);
}
-/* Home page "مرور کتابها" book shelf - see initHomeBooksShelf()/buildBookSpine() in bk.js. */
+/* A book matched by the top search box's live filter (see onHomeSearchInput()/
+ buildHomeBookResultChip() in bk.js) - rendered alongside matching poets in #home-found-poets, so
+ it needs the same flex-row layout .spacer:has(.poet) gives poet results, but triggered on its own
+ class too since a search can match only books and no poets at all. */
+.spacer:has(.home-book-result) {
+ display: flex;
+ flex-wrap: wrap;
+ justify-content: center;
+ align-items: flex-start;
+ gap: 14px 28px;
+ margin: 15px auto 25px auto;
+ padding: 0;
+ clear: both;
+}
+
+/* Sized and laid out to match .spacer .poet exactly (110px column, 76x96 "portrait" area, same pill
+ caption below) so a book result sits proportionate to the poet results next to it instead of
+ looming over them as its own, differently-sized shape. */
+.home-book-result {
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ width: 110px;
+ margin: 0 0 10px 0;
+ text-decoration: none;
+ transition: transform 0.25s ease;
+}
+
+.home-book-result:hover {
+ transform: translateY(-5px);
+}
+
+.home-book-result-cover {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 76px;
+ height: 96px;
+ border-radius: 10px;
+ padding: 6px;
+ overflow: hidden;
+ text-align: center;
+ color: #fdf8ee;
+ font-family: 'IranNastaliq', serif;
+ font-size: 1rem;
+ line-height: 1.35;
+ box-shadow:
+ 0 0 0 2px var(--gold),
+ 0 0 0 5px var(--bg-page),
+ 0 0 0 7px var(--border-gold),
+ 0 4px 12px rgba(0, 0, 0, 0.15);
+ transition: box-shadow 0.25s ease;
+}
+
+.home-book-result:hover .home-book-result-cover {
+ box-shadow:
+ 0 0 0 2px var(--brand),
+ 0 0 0 5px var(--bg-page),
+ 0 0 0 7px var(--brand),
+ 0 6px 18px rgba(0, 0, 0, 0.22);
+}
+
+.home-book-result-caption {
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ width: 100%;
+ text-align: center;
+ margin: 5px 0 8px 0;
+ padding: 6px 12px;
+ border-radius: 12px;
+ background-color: var(--card-inner);
+ border: 1px solid var(--border-gold);
+ box-shadow: 0 2px 6px rgba(0, 0, 0, 0.08);
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--lapis);
+ transition: all 0.2s ease;
+}
+
+.home-book-result:hover .home-book-result-caption {
+ background-color: var(--gold-subtle);
+ border-color: var(--brand);
+ transform: translateY(-1px);
+ box-shadow: 0 4px 10px rgba(0, 0, 0, 0.12);
+}
+
+/* Home page "قفسهٔ کتابها" book shelf - see initHomeBooksShelf()/buildBookSpine() in bk.js. */
#books-shelf-outer {
position: relative;
}
diff --git a/GanjooRazor/wwwroot/js/bk.js b/GanjooRazor/wwwroot/js/bk.js
index ee1d77a7..93b8c731 100644
--- a/GanjooRazor/wwwroot/js/bk.js
+++ b/GanjooRazor/wwwroot/js/bk.js
@@ -1560,6 +1560,31 @@ function persianizeNumerals(value) {
// behavior into the top box: typing filters poets live (same underlying match as onInlineSearch,
// against every ".poet" element's data-value), while submitting the form (Enter / the "بیاب" button)
// still does the original full poem search unchanged - so nothing about submission itself changes.
+// homeBooksCache is filled once by initHomeBooksShelf() further below - the same catalog backing
+// the "قفسهٔ کتابها" shelf is reused here so a search also matches book names, not just poets.
+var homeBooksCache = [];
+
+function buildHomeBookResultChip(book) {
+ var a = document.createElement('a');
+ a.className = 'home-book-result';
+ a.href = book.fullUrl || '#';
+
+ // Same 76x96 "portrait" box a poet result uses, just filled with the book's own hashed color
+ // and its name instead of a photo - see .home-book-result-cover in p8.css for the sizing.
+ var cover = document.createElement('span');
+ cover.className = 'home-book-result-cover';
+ cover.style.background = 'hsl(' + hashBookColorHue(book.id) + ', 42%, 32%)';
+ cover.textContent = book.name;
+
+ var caption = document.createElement('span');
+ caption.className = 'home-book-result-caption';
+ caption.textContent = book.poetName || '';
+
+ a.appendChild(cover);
+ a.appendChild(caption);
+ return a;
+}
+
function onHomeSearchInput(value) {
var clearBtn = document.getElementById('home-search-clear');
var popularRow = document.getElementById('home-popular-poets-row');
@@ -1603,6 +1628,13 @@ function onHomeSearchInput(value) {
foundNode.appendChild(poets[i].cloneNode(true));
}
+ for (var k = 0; k < homeBooksCache.length; k++) {
+ var book = homeBooksCache[k];
+ if (book.name && book.name.indexOf(replaced) !== -1) {
+ foundNode.appendChild(buildHomeBookResultChip(book));
+ }
+ }
+
var hasMatches = foundNode.childElementCount > 0;
foundNode.style.display = hasMatches ? '' : 'none';
// No matching poet: there's nothing useful to show in place of the favorites row, so put the
@@ -1620,7 +1652,7 @@ function clearHomeSearch() {
input.focus();
}
-// Home page (Index.cshtml) only: "مرور کتابها" - a horizontally scrollable shelf of book "spines",
+// Home page (Index.cshtml) only: "قفسهٔ کتابها" - a horizontally scrollable shelf of book "spines",
// Grokipedia-homepage-style. Fetched once from this page's own "?Handler=BookCatalog" (which proxies
// RMuseum's api/ganjoor/book-catalog server-side) rather than calling that API directly from browser
// JS - same convention every other client-side data fetch on this site follows. There's no cover-art
@@ -1744,6 +1776,7 @@ function initHomeBooksShelf() {
url: '?Handler=BookCatalog',
success: function (books) {
if (!books || books.length === 0) return;
+ homeBooksCache = books;
var frag = document.createDocumentFragment();
for (var i = 0; i < books.length; i++) {
frag.appendChild(buildBookSpine(books[i]));