<link href="https://cdn.jsdelivr.net/gh/rastikerdar/vazir-font@v30.1.0/dist/font-face.css" rel="stylesheet" type="text/css" />
<!-- هدر تزئینی -->
<div style="max-width:1100px; margin:0 auto; padding:20px;">
<div style="
background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
border-radius: 24px;
padding: 40px 30px;
text-align: center;
box-shadow: 0 20px 60px rgba(102, 126, 234, 0.3);
position: relative;
overflow: hidden;
margin-bottom: 25px;
">
<div style="
position: absolute;
top: -50%;
right: -10%;
width: 300px;
height: 300px;
background: rgba(255,255,255,0.1);
border-radius: 50%;
filter: blur(40px);
"></div>
<div style="
position: absolute;
bottom: -50%;
left: -10%;
width: 250px;
height: 250px;
background: rgba(255,255,255,0.08);
border-radius: 50%;
filter: blur(40px);
"></div>
<h1 style="
position: relative;
font-family: Vazir, Tahoma;
color: #fff;
font-weight: 700;
font-size: 36px;
margin: 0;
text-shadow: 0 2px 20px rgba(0,0,0,0.2);
letter-spacing: -0.5px;
">
📚 کتابخانه
</h1>
<p style="
position: relative;
color: rgba(255,255,255,0.9);
font-family: Vazir, Tahoma;
font-size: 14px;
margin: 10px 0 0;
font-weight: 300;
">
مجموعهای از بهترین منابع مطالعاتی
</p>
</div>
<!-- نوار جستجو -->
<div id="book-toolbar" style="
max-width: 1100px;
margin: 0 auto 20px;
padding: 0 10px;
display: flex;
justify-content: center;
align-items: center;
direction: rtl;
">
<div style="position: relative; width: 100%; max-width: 600px;">
<span style="
position: absolute;
right: 18px;
top: 50%;
transform: translateY(-50%);
font-size: 18px;
color: #999;
pointer-events: none;
">🔍</span>
<input id="book-search" type="text"
placeholder="جستجو در عنوان، حیطه، نویسنده یا سال..."
style="
width: 100%;
padding: 14px 50px 14px 20px;
border-radius: 999px;
border: 2px solid transparent;
background: #fff;
font-family: Vazir, Tahoma;
font-size: 14px;
outline: none;
box-shadow: 0 4px 20px rgba(0,0,0,0.08);
transition: all 0.3s ease;
box-sizing: border-box;
"
onfocus="this.style.borderColor='#667eea'; this.style.boxShadow='0 4px 25px rgba(102, 126, 234, 0.25)';"
onblur="this.style.borderColor='transparent'; this.style.boxShadow='0 4px 20px rgba(0,0,0,0.08)';">
</div>
</div>
</div>
<div id="book-grid" style="
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 20px;
padding: 10px 40px 60px;
max-width: 1100px;
margin: 0 auto;
direction: rtl;
"></div>
<style>
.card {
background: #fff;
border-radius: 20px;
box-shadow: 0 4px 15px rgba(0,0,0,0.06);
padding: 20px;
display: flex;
align-items: flex-start;
justify-content: space-between;
transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
flex-direction: row-reverse;
font-family: Vazir, Tahoma;
border: 1px solid rgba(0,0,0,0.04);
position: relative;
overflow: hidden;
cursor: pointer;
}
.card::before {
content: '';
position: absolute;
top: 0;
right: 0;
width: 4px;
height: 100%;
background: linear-gradient(180deg, #667eea, #764ba2);
opacity: 0;
transition: opacity 0.3s ease;
}
.card:hover {
transform: translateY(-8px);
box-shadow: 0 20px 40px rgba(102, 126, 234, 0.2);
border-color: rgba(102, 126, 234, 0.2);
}
.card:hover::before {
opacity: 1;
}
.card:hover .card-icon img {
transform: scale(1.1) rotate(-3deg);
}
.card-content {
flex: 1;
text-align: right;
padding-right: 15px;
}
.card-content h3 {
margin: 0 0 12px;
font-weight: 700;
font-size: 15px;
color: #2d3748;
line-height: 1.6;
}
.card-content .dot-line {
font-size: 12.5px;
color: #718096;
margin: 5px 0;
line-height: 1.7;
}
.download-btn {
display: inline-flex;
align-items: center;
gap: 6px;
background: linear-gradient(135deg, #667eea, #764ba2);
color: #fff;
border: none;
border-radius: 25px;
padding: 7px 18px;
font-size: 12px;
font-weight: 500;
font-family: Vazir, Tahoma;
text-decoration: none;
margin-top: 12px;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.3);
}
.download-btn:hover {
transform: translateY(-2px);
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.45);
background: linear-gradient(135deg, #5568d3, #653a91);
}
.download-btn:active {
transform: translateY(0);
}
.card-icon {
background: linear-gradient(135deg, #f7f7f9, #eef0f5);
border-radius: 16px;
padding: 12px;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.card-icon img {
width: 2cm;
height: 2cm;
transition: transform 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.275);
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.1));
}
/* استایل حالتهای خالی و خطا */
.empty-state {
grid-column: 1/-1;
text-align: center;
font-family: Vazir, Tahoma;
color: #718096;
background: linear-gradient(135deg, #fafbfc, #f5f7fa);
border: 2px dashed #e2e8f0;
padding: 50px 20px;
border-radius: 20px;
font-size: 14px;
}
.empty-state-icon {
font-size: 48px;
margin-bottom: 15px;
opacity: 0.5;
}
.error-state {
grid-column: 1/-1;
text-align: center;
font-family: Vazir, Tahoma;
color: #c53030;
background: linear-gradient(135deg, #fff5f5, #fed7d7);
border: 2px dashed #fc8181;
padding: 30px 20px;
border-radius: 20px;
font-size: 14px;
}
/* انیمیشن ورود کارتها */
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeInUp 0.5s ease backwards;
}
/* لودینگ */
.loading {
grid-column: 1/-1;
text-align: center;
padding: 60px 20px;
}
.loading-spinner {
display: inline-block;
width: 50px;
height: 50px;
border: 4px solid #f3f3f3;
border-top: 4px solid #667eea;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
.loading-text {
font-family: Vazir, Tahoma;
color: #718096;
margin-top: 15px;
font-size: 14px;
}
/* ریسپانسیو */
@media (max-width:900px){
#book-grid{grid-template-columns:1fr; padding: 10px 20px 40px;}
.card{padding: 16px;}
}
@media (max-width:600px){
#book-grid{padding: 10px 15px 30px;}
.card-content h3{font-size: 14px;}
.card-content .dot-line{font-size: 12px;}
h1{font-size: 28px !important;}
}
</style>
<script>
(() => {
/* ===== تنظیمات ===== */
const LIB_ID = "nikan-west-library";
const FORCED_DATA_PAGE_ID = 74285297;
const JSON_FILE_NAME = `library-${LIB_ID}.json`;
/* =================== */
function detectBase(){
try{ if (window.AJS && typeof AJS.contextPath==="function") return AJS.contextPath(); }catch(e){}
const meta = document.querySelector('meta[name="ajs-context-path"]')?.content;
if (meta) return meta;
const m = location.pathname.match(/^(.*)\/(pages|display|download)\//);
if (m) return m;
return '';
}
const BASE = detectBase();
const DATA_PAGE_ID = FORCED_DATA_PAGE_ID;
const ENDPOINT_ATTACH_LIST =
`${BASE}/rest/api/content/${DATA_PAGE_ID}/child/attachment`;
const grid = document.getElementById("book-grid");
const searchInput = document.getElementById("book-search");
let allBooks = [];
// نمایش لودینگ
function showLoading(){
grid.innerHTML = `
<div class="loading">
<div class="loading-spinner"></div>
<div class="loading-text">در حال بارگذاری کتابها...</div>
</div>`;
}
function createBookCard(book, index){
const authorsBlock = (book.authors && book.authors.length)
? book.authors.map(a=>`<div class="dot-line">✍️ ${a}</div>`).join("")
: `<div class="dot-line">✍️ بدون نویسنده</div>`;
const card = document.createElement("div");
card.className = "card";
card.style.animationDelay = `${index * 0.05}s`;
card.innerHTML = `
<div class="card-content">
<h3>${book.title || "-"}</h3>
<div class="dot-line">📂 حیطه: ${book.category || "-"}</div>
${authorsBlock}
<div class="dot-line">📅 تاریخ انتشار: ${book.year || "-"}</div>
${book.fileUrl ? `<a href="${book.fileUrl}" target="_blank" class="download-btn">⬇ دانلود کتاب</a>` : ""}
</div>
<div class="card-icon">
<img src="/download/thumbnails/79076840/12%20-%20Copy.png?api=v2" alt="کتاب">
</div>
`;
return card;
}
function renderBooks(list){
grid.innerHTML = "";
if (!list.length){
grid.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📭</div>
<div>کتابی با این فیلتر پیدا نشد.</div>
<div style="font-size: 12px; color: #a0aec0; margin-top: 8px;">لطفاً عبارت دیگری جستجو کنید.</div>
</div>`;
return;
}
list.forEach((b, i) => grid.appendChild(createBookCard(b, i)));
}
async function getExistingAttachmentMeta() {
const url = `${ENDPOINT_ATTACH_LIST}?filename=${encodeURIComponent(JSON_FILE_NAME)}&expand=results`;
const res = await fetch(url, { credentials: "include" });
if (!res.ok) return null;
const data = await res.json();
if (!data.results || !data.results.length) return null;
return data.results;
}
async function loadJsonFromAttachment() {
const meta = await getExistingAttachmentMeta();
if (!meta) return { libId: LIB_ID, books: [] };
const downloadLink = meta._links && meta._links.download;
if (!downloadLink) return { libId: LIB_ID, books: [] };
const res = await fetch(BASE + downloadLink, { credentials: "include" });
if (!res.ok) throw new Error("خطا در دانلود JSON: " + res.status);
const text = await res.text();
if (!text.trim()) return { libId: LIB_ID, books: [] };
try {
const parsed = JSON.parse(text.replace(/^\uFEFF/, '').trim());
if (!parsed.books) parsed.books = [];
if (!parsed.libId) parsed.libId = LIB_ID;
return parsed;
} catch(e) {
console.error("JSON parse error:", e);
return { libId: LIB_ID, books: [] };
}
}
async function loadBooks() {
showLoading();
try {
const data = await loadJsonFromAttachment();
allBooks = data.books || [];
if (!allBooks.length) {
grid.innerHTML = `
<div class="empty-state">
<div class="empty-state-icon">📚</div>
<div>هنوز کتابی ثبت نشده است.</div>
<div style="font-size: 12px; color: #a0aec0; margin-top: 8px;">از طریق فرم ثبت، اولین کتاب را اضافه کنید.</div>
</div>`;
return;
}
// کمی تأخیر برای نمایش لودینگ
setTimeout(() => renderBooks(allBooks), 300);
} catch (e) {
console.error(e);
grid.innerHTML = `
<div class="error-state">
<div style="font-size: 32px; margin-bottom: 10px;">⚠️</div>
<div>خطا در خواندن کتابها</div>
<div style="font-size: 12px; margin-top: 8px; opacity: 0.8;">${e.message}</div>
</div>`;
}
}
function handleSearch(){
const q = (searchInput.value || "").trim().toLowerCase();
if (!q){
renderBooks(allBooks);
return;
}
const filtered = allBooks.filter(b => {
const title = (b.title || "").toLowerCase();
const category = (b.category || "").toLowerCase();
const year = (b.year || "").toLowerCase();
const authors = Array.isArray(b.authors) ? b.authors.join(" ") : (b.authors || "");
const authorsL = authors.toLowerCase();
const haystack = `${title} ${category} ${year} ${authorsL}`;
return haystack.includes(q);
});
renderBooks(filtered);
}
// دیبانس برای سرچ بهتر
let searchTimeout;
searchInput.addEventListener("input", () => {
clearTimeout(searchTimeout);
searchTimeout = setTimeout(handleSearch, 200);
});
loadBooks();
})();
</script> |