400 lines
23 KiB
PHP
400 lines
23 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
require_once __DIR__ . '/includes/layout.php';
|
|
|
|
library_bootstrap();
|
|
|
|
$lang = library_get_language();
|
|
$query = trim((string) ($_GET['q'] ?? ''));
|
|
$language = trim((string) ($_GET['language'] ?? ''));
|
|
library_track_request('catalog_viewed', null, [
|
|
'query' => $query,
|
|
'language_filter' => $language,
|
|
]);
|
|
|
|
$pageCopy = [
|
|
'en' => [
|
|
'meta_title' => 'Cinematic Digital Library',
|
|
'meta_description' => 'Explore a bespoke digital library with an editorial catalog, immersive discovery panels, and bilingual browsing for public documents.',
|
|
'hero_eyebrow' => 'Cinematic editorial library',
|
|
'hero_title' => 'A digital archive staged like a premium reading room.',
|
|
'hero_copy' => 'Browse the public collection inside a more expressive library shell: dark graphite atmosphere, warm archival surfaces, and faster discovery across English and Arabic titles.',
|
|
'browse_catalog' => 'Enter the catalog',
|
|
'add_documents' => 'Open Admin Studio',
|
|
'hero_stat_public' => 'public works on display',
|
|
'hero_stat_private' => 'private works in reserve',
|
|
'hero_stat_summaries' => 'AI summaries prepared',
|
|
'curator_kicker' => 'Curator note',
|
|
'curator_title' => 'Designed for a modern archive, not a generic dashboard.',
|
|
'curator_copy' => 'This first theme pass turns the library into a more memorable public-facing experience while keeping the structure fast, readable, and ready for the document pages next.',
|
|
'curator_name' => 'Editorial Library Mode',
|
|
'curator_role' => 'Graphite base · ivory surfaces · cyan and amber glow',
|
|
'snapshot_kicker' => 'Shelf spotlight',
|
|
'snapshot_title' => 'What now feels different',
|
|
'snapshot_badge' => 'Bespoke theme',
|
|
'public_titles' => 'Public titles',
|
|
'private_titles' => 'Private titles',
|
|
'ai_summaries' => 'AI summaries',
|
|
'snapshot_item_1' => 'Shared public pages inherit the new cinematic shell automatically',
|
|
'snapshot_item_2' => 'Discovery controls now sit in a more premium editorial layout',
|
|
'snapshot_item_3' => 'The visual system is ready to extend into document and profile pages next',
|
|
'discovery_kicker' => 'Discovery desk',
|
|
'discovery_title' => 'Search the active collection',
|
|
'keyword' => 'Keyword',
|
|
'keyword_placeholder' => 'Title, author, tag, or excerpt',
|
|
'language_filter' => 'Content language',
|
|
'all_shelves' => 'All shelves',
|
|
'filter' => 'Refine',
|
|
'rules_kicker' => 'Reading room rules',
|
|
'rules_title' => 'Public and private shelves remain clearly separated.',
|
|
'rules_copy' => 'Only public works appear in this catalog. Private items stay reserved for the admin workspace, so the public stage remains intentional and curated.',
|
|
'rules_link' => 'Review publishing controls',
|
|
'palette_title' => 'Theme palette',
|
|
'palette_copy' => 'Graphite depth, archival ivory cards, luminous cyan highlights, and warm amber contrast create a premium “digital archive” mood.',
|
|
'catalog_kicker' => 'Public collection',
|
|
'catalog_title' => 'Available titles on stage',
|
|
'catalog_copy' => 'A cleaner one-language-per-view catalog with stronger visual hierarchy and a more collectible feel.',
|
|
'result_singular' => 'result',
|
|
'result_plural' => 'results',
|
|
'empty_title' => 'No public documents yet',
|
|
'empty_copy' => 'Upload your first Arabic or English document from the Admin Studio to turn this stage into a browsable library.',
|
|
'open_admin' => 'Open Admin Studio',
|
|
'no_cover' => 'No cover',
|
|
'untitled' => 'Untitled document',
|
|
'cover_alt' => 'Cover image for',
|
|
'author' => 'Author',
|
|
'views' => 'Views',
|
|
'tags' => 'Tags',
|
|
'author_fallback' => 'Not set',
|
|
'open_reader' => 'Open reader →',
|
|
'workflow_kicker' => 'Reader journey',
|
|
'workflow_title' => 'How the experience flows',
|
|
'workflow_item_1' => 'An admin uploads a document and decides whether it belongs on the public stage or in private reserve.',
|
|
'workflow_item_2' => 'Readers discover public titles from the catalog, then open the detail page and immersive reader.',
|
|
'workflow_item_3' => 'Language, summaries, and activity tracking stay aligned with the current browsing context.',
|
|
'recent_kicker' => 'Fresh arrivals',
|
|
'recent_title' => 'Latest public additions',
|
|
'manage_shelf' => 'Manage the shelf',
|
|
'unknown_author' => 'Unknown author',
|
|
],
|
|
'ar' => [
|
|
'meta_title' => 'مكتبة رقمية سينمائية',
|
|
'meta_description' => 'استكشف مكتبة رقمية بتصميم مخصص مع فهرس تحريري ولوحات اكتشاف غامرة وتصفح ثنائي اللغة للمستندات العامة.',
|
|
'hero_eyebrow' => 'مكتبة تحريرية سينمائية',
|
|
'hero_title' => 'أرشيف رقمي بواجهة تشبه قاعة قراءة فاخرة.',
|
|
'hero_copy' => 'تصفح المجموعة العامة داخل هوية بصرية أكثر تميزاً: أجواء جرافيت داكنة، وأسِطح أرشيفية دافئة، واكتشاف أسرع للعناوين العربية والإنجليزية.',
|
|
'browse_catalog' => 'ادخل إلى الفهرس',
|
|
'add_documents' => 'افتح مساحة الإدارة',
|
|
'hero_stat_public' => 'أعمال عامة معروضة',
|
|
'hero_stat_private' => 'أعمال خاصة محفوظة',
|
|
'hero_stat_summaries' => 'ملخصات ذكاء اصطناعي جاهزة',
|
|
'curator_kicker' => 'ملاحظة قيّمة',
|
|
'curator_title' => 'التصميم الآن أقرب إلى أرشيف حديث منه إلى لوحة تقليدية.',
|
|
'curator_copy' => 'هذا التحديث الأول يحول المكتبة إلى تجربة عامة أكثر تميزاً مع الحفاظ على السرعة والوضوح والاستعداد لتوسيع النمط إلى صفحات المستندات والملف الشخصي لاحقاً.',
|
|
'curator_name' => 'وضع المكتبة التحريرية',
|
|
'curator_role' => 'قاعدة جرافيتية · أسطح عاجية · توهج سماوي وعنّابي دافئ',
|
|
'snapshot_kicker' => 'واجهة الرف',
|
|
'snapshot_title' => 'ما الذي أصبح مختلفاً الآن',
|
|
'snapshot_badge' => 'هوية مخصصة',
|
|
'public_titles' => 'العناوين العامة',
|
|
'private_titles' => 'العناوين الخاصة',
|
|
'ai_summaries' => 'ملخصات الذكاء الاصطناعي',
|
|
'snapshot_item_1' => 'جميع الصفحات العامة ترث الآن الغلاف السينمائي الجديد تلقائياً',
|
|
'snapshot_item_2' => 'أدوات الاكتشاف أصبحت داخل تخطيط تحريري أكثر فخامة',
|
|
'snapshot_item_3' => 'النظام البصري أصبح جاهزاً لتمديده إلى صفحات المستند والملف الشخصي لاحقاً',
|
|
'discovery_kicker' => 'مكتب الاكتشاف',
|
|
'discovery_title' => 'ابحث في المجموعة النشطة',
|
|
'keyword' => 'الكلمة المفتاحية',
|
|
'keyword_placeholder' => 'العنوان أو المؤلف أو الوسوم أو المقتطف',
|
|
'language_filter' => 'لغة المحتوى',
|
|
'all_shelves' => 'كل الرفوف',
|
|
'filter' => 'تنقيح',
|
|
'rules_kicker' => 'قواعد قاعة القراءة',
|
|
'rules_title' => 'يبقى الفصل واضحاً بين الرف العام والرف الخاص.',
|
|
'rules_copy' => 'لا تظهر هنا إلا الأعمال العامة. أما العناصر الخاصة فتبقى محفوظة داخل مساحة الإدارة حتى يظل المشهد العام مقصوداً ومنسقاً.',
|
|
'rules_link' => 'راجع ضوابط النشر',
|
|
'palette_title' => 'لوحة الألوان',
|
|
'palette_copy' => 'عمق جرافيتي، وبطاقات عاجية أرشيفية، ولمسات سماوية مضيئة، وتباين كهرماني دافئ يمنح المكتبة مزاج أرشيف رقمي فاخر.',
|
|
'catalog_kicker' => 'المجموعة العامة',
|
|
'catalog_title' => 'العناوين المتاحة على الواجهة',
|
|
'catalog_copy' => 'فهرس أوضح بلغة واجهة واحدة في كل مرة مع هرمية بصرية أقوى وطابع أكثر قابلية للاقتناء.',
|
|
'result_singular' => 'نتيجة',
|
|
'result_plural' => 'نتائج',
|
|
'empty_title' => 'لا توجد مستندات عامة بعد',
|
|
'empty_copy' => 'ارفع أول مستند عربي أو إنجليزي من مساحة الإدارة لتحويل هذه الواجهة إلى مكتبة قابلة للتصفح.',
|
|
'open_admin' => 'افتح مساحة الإدارة',
|
|
'no_cover' => 'بدون غلاف',
|
|
'untitled' => 'مستند بدون عنوان',
|
|
'cover_alt' => 'صورة الغلاف لـ',
|
|
'author' => 'المؤلف',
|
|
'views' => 'المشاهدات',
|
|
'tags' => 'الوسوم',
|
|
'author_fallback' => 'غير محدد',
|
|
'open_reader' => 'فتح القارئ ←',
|
|
'workflow_kicker' => 'رحلة القارئ',
|
|
'workflow_title' => 'كيف تتدفق التجربة',
|
|
'workflow_item_1' => 'يرفع المشرف مستنداً ويقرر ما إذا كان سيظهر على الواجهة العامة أو يبقى محفوظاً بشكل خاص.',
|
|
'workflow_item_2' => 'يكتشف القراء العناوين العامة من الفهرس ثم يفتحون صفحة التفاصيل والقارئ الغامر.',
|
|
'workflow_item_3' => 'تظل اللغة والملخصات وتتبع النشاط متوافقة مع سياق التصفح الحالي.',
|
|
'recent_kicker' => 'وصل حديثاً',
|
|
'recent_title' => 'أحدث الإضافات العامة',
|
|
'manage_shelf' => 'إدارة الرف',
|
|
'unknown_author' => 'مؤلف غير معروف',
|
|
],
|
|
][$lang];
|
|
|
|
$page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1;
|
|
$limit = 12;
|
|
$offset = ($page - 1) * $limit;
|
|
|
|
$result = library_fetch_documents_paginated(true, ['q' => $query, 'language' => $language], $limit, $offset);
|
|
$documents = $result['data'];
|
|
$totalDocuments = $result['total'];
|
|
$totalPages = (int) ceil($totalDocuments / $limit);
|
|
|
|
$metrics = library_catalog_metrics();
|
|
$recentDocuments = library_recent_documents(3, true);
|
|
|
|
library_render_header(
|
|
$pageCopy['meta_title'],
|
|
$pageCopy['meta_description'],
|
|
'catalog'
|
|
);
|
|
?>
|
|
<section class="hero-surface hero-surface--immersive mb-4 mb-lg-5">
|
|
<div class="row g-4 g-xl-5 align-items-center">
|
|
<div class="col-xl-7">
|
|
<div class="hero-copy-wrap">
|
|
<span class="eyebrow"><?= h($pageCopy['hero_eyebrow']) ?></span>
|
|
<h1 class="display-5 mb-3"><?= h($pageCopy['hero_title']) ?></h1>
|
|
<p class="lead text-secondary mb-4"><?= h($pageCopy['hero_copy']) ?></p>
|
|
<div class="hero-actions">
|
|
<a class="btn btn-dark" href="#catalog-grid"><?= h($pageCopy['browse_catalog']) ?></a>
|
|
<a class="btn btn-outline-secondary" href="/admin.php"><?= h($pageCopy['add_documents']) ?></a>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="hero-stat-strip">
|
|
<article class="hero-stat">
|
|
<span class="hero-stat-value"><?= h((string) $metrics['public_count']) ?></span>
|
|
<span class="hero-stat-label"><?= h($pageCopy['hero_stat_public']) ?></span>
|
|
</article>
|
|
<article class="hero-stat">
|
|
<span class="hero-stat-value"><?= h((string) $metrics['private_count']) ?></span>
|
|
<span class="hero-stat-label"><?= h($pageCopy['hero_stat_private']) ?></span>
|
|
</article>
|
|
<article class="hero-stat">
|
|
<span class="hero-stat-value"><?= h((string) $metrics['summarized_count']) ?></span>
|
|
<span class="hero-stat-label"><?= h($pageCopy['hero_stat_summaries']) ?></span>
|
|
</article>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-xl-5">
|
|
<div class="hero-showcase">
|
|
<div class="curator-card">
|
|
<div class="section-kicker"><?= h($pageCopy['curator_kicker']) ?></div>
|
|
<h2 class="h4 mb-3"><?= h($pageCopy['curator_title']) ?></h2>
|
|
<p class="curator-note"><?= h($pageCopy['curator_copy']) ?></p>
|
|
<div class="curator-signature">
|
|
<span class="signature-mark">NL</span>
|
|
<div>
|
|
<span class="signature-name"><?= h($pageCopy['curator_name']) ?></span>
|
|
<span class="signature-role"><?= h($pageCopy['curator_role']) ?></span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="spotlight-card">
|
|
<div class="spotlight-head">
|
|
<div>
|
|
<div class="section-kicker"><?= h($pageCopy['snapshot_kicker']) ?></div>
|
|
<h2 class="h5 mb-1"><?= h($pageCopy['snapshot_title']) ?></h2>
|
|
</div>
|
|
<span class="badge spotlight-badge"><?= h($pageCopy['snapshot_badge']) ?></span>
|
|
</div>
|
|
<div class="metric-grid metric-grid-compact mb-3">
|
|
<article class="metric-card">
|
|
<span class="metric-value"><?= h((string) $metrics['public_count']) ?></span>
|
|
<span class="metric-label"><?= h($pageCopy['public_titles']) ?></span>
|
|
</article>
|
|
<article class="metric-card">
|
|
<span class="metric-value"><?= h((string) $metrics['private_count']) ?></span>
|
|
<span class="metric-label"><?= h($pageCopy['private_titles']) ?></span>
|
|
</article>
|
|
</div>
|
|
<ul class="curated-points mb-0">
|
|
<li><?= h($pageCopy['snapshot_item_1']) ?></li>
|
|
<li><?= h($pageCopy['snapshot_item_2']) ?></li>
|
|
<li><?= h($pageCopy['snapshot_item_3']) ?></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section class="row g-4 mb-4 mb-lg-5">
|
|
<div class="col-lg-8">
|
|
<div class="discovery-panel h-100">
|
|
<div class="section-kicker"><?= h($pageCopy['discovery_kicker']) ?></div>
|
|
<h2 class="h4 mb-3"><?= h($pageCopy['discovery_title']) ?></h2>
|
|
<form class="discovery-form-grid" method="get" action="/index.php">
|
|
<div>
|
|
<label class="form-label" for="q"><?= h($pageCopy['keyword']) ?></label>
|
|
<input class="form-control" id="q" name="q" type="search" value="<?= h($query) ?>" placeholder="<?= h($pageCopy['keyword_placeholder']) ?>">
|
|
</div>
|
|
<div>
|
|
<label class="form-label" for="language"><?= h($pageCopy['language_filter']) ?></label>
|
|
<select class="form-select" id="language" name="language">
|
|
<option value=""><?= h($pageCopy['all_shelves']) ?></option>
|
|
<option value="en" <?= $language === 'en' ? 'selected' : '' ?>><?= h(library_language_label('en')) ?></option>
|
|
<option value="ar" <?= $language === 'ar' ? 'selected' : '' ?>><?= h(library_language_label('ar')) ?></option>
|
|
<option value="bilingual" <?= $language === 'bilingual' ? 'selected' : '' ?>><?= h(library_language_label('bilingual')) ?></option>
|
|
</select>
|
|
</div>
|
|
<div class="d-grid align-self-end">
|
|
<button class="btn btn-dark" type="submit"><?= h($pageCopy['filter']) ?></button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-4">
|
|
<div class="tone-panel h-100">
|
|
<div class="section-kicker"><?= h($pageCopy['rules_kicker']) ?></div>
|
|
<h2 class="h5 mb-3"><?= h($pageCopy['rules_title']) ?></h2>
|
|
<p class="text-secondary mb-3"><?= h($pageCopy['rules_copy']) ?></p>
|
|
<div class="tone-swatch-row" aria-hidden="true">
|
|
<span class="tone-swatch cyan"></span>
|
|
<span class="tone-swatch ivory"></span>
|
|
<span class="tone-swatch amber"></span>
|
|
</div>
|
|
<h3 class="h6 mb-2"><?= h($pageCopy['palette_title']) ?></h3>
|
|
<p class="text-secondary mb-3"><?= h($pageCopy['palette_copy']) ?></p>
|
|
<a class="link-arrow" href="/admin.php"><?= h($pageCopy['rules_link']) ?></a>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<section id="catalog-grid" class="catalog-shell mb-4 mb-lg-5">
|
|
<div class="catalog-section-head">
|
|
<div>
|
|
<div class="section-kicker"><?= h($pageCopy['catalog_kicker']) ?></div>
|
|
<h2 class="h3 mb-2"><?= h($pageCopy['catalog_title']) ?></h2>
|
|
<p class="text-secondary mb-0"><?= h($pageCopy['catalog_copy']) ?></p>
|
|
</div>
|
|
<div class="small text-secondary">
|
|
<?= h((string) $totalDocuments) ?>
|
|
<?= h($totalDocuments === 1 ? $pageCopy['result_singular'] : $pageCopy['result_plural']) ?>
|
|
</div>
|
|
</div>
|
|
|
|
<?php if (!$documents): ?>
|
|
<div class="panel empty-panel text-center py-5">
|
|
<div class="empty-icon mx-auto mb-3">+</div>
|
|
<h3 class="h5 mb-2"><?= h($pageCopy['empty_title']) ?></h3>
|
|
<p class="text-secondary mb-4"><?= h($pageCopy['empty_copy']) ?></p>
|
|
<a class="btn btn-dark" href="/admin.php"><?= h($pageCopy['open_admin']) ?></a>
|
|
</div>
|
|
<?php else: ?>
|
|
<div class="row row-cols-1 row-cols-md-2 row-cols-xl-2 g-4">
|
|
<?php foreach ($documents as $document): ?>
|
|
<?php
|
|
$cardTitle = library_localized_document_title($document, $lang, $pageCopy['untitled']);
|
|
$cardTitleLang = library_text_lang($cardTitle, $lang);
|
|
$cardTitleDir = library_text_dir($cardTitle, $cardTitleLang);
|
|
?>
|
|
<div class="col">
|
|
<article class="panel catalog-card h-100 position-relative">
|
|
<div class="catalog-card-media">
|
|
<?php if (!empty($document['cover_image_path'])): ?>
|
|
<img
|
|
src="/<?= h((string) $document['cover_image_path']) ?>"
|
|
alt="<?= h($pageCopy['cover_alt'] . ' ' . $cardTitle) ?>"
|
|
class="catalog-card-cover"
|
|
width="128"
|
|
height="176"
|
|
>
|
|
<?php else: ?>
|
|
<div class="catalog-card-cover catalog-card-cover-placeholder" aria-hidden="true">
|
|
<span><?= h($pageCopy['no_cover']) ?></span>
|
|
</div>
|
|
<?php endif; ?>
|
|
</div>
|
|
|
|
<div class="catalog-card-body">
|
|
<div class="d-flex flex-wrap justify-content-between align-items-start gap-3 mb-2">
|
|
<div class="min-w-0">
|
|
<h3 class="h5 mb-1 catalog-card-title" lang="<?= h($cardTitleLang) ?>" dir="<?= h($cardTitleDir) ?>"><?= h($cardTitle) ?></h3>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="catalog-card-meta small text-secondary">
|
|
<div><span class="catalog-card-label"><?= h($pageCopy['author']) ?></span><span><?= h((string) ($document['author'] ?: $pageCopy['author_fallback'])) ?></span></div>
|
|
<div><span class="catalog-card-label"><?= h($pageCopy['views']) ?></span><span><?= h((string) $document['view_count']) ?></span></div>
|
|
<div><span class="catalog-card-label"><?= h($pageCopy['tags']) ?></span><span><?= h((string) ($document['tags'] ?: '—')) ?></span></div>
|
|
</div>
|
|
|
|
<div class="mt-3">
|
|
<a class="link-arrow stretched-link" href="/document.php?id=<?= h((string) $document['id']) ?>"><?= h($pageCopy['open_reader']) ?></a>
|
|
</div>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
|
|
<?php if ($totalPages > 1): ?>
|
|
<div class="mt-5">
|
|
<?php library_render_pagination($page, $totalPages, '/index.php'); ?>
|
|
</div>
|
|
<?php endif; ?>
|
|
|
|
<?php endif; ?>
|
|
</section>
|
|
|
|
<section class="row g-4">
|
|
<div class="col-lg-4">
|
|
<div class="panel h-100">
|
|
<div class="section-kicker"><?= h($pageCopy['workflow_kicker']) ?></div>
|
|
<h2 class="h5 mb-3"><?= h($pageCopy['workflow_title']) ?></h2>
|
|
<ol class="compact-list-numbered mb-0 text-secondary">
|
|
<li><?= h($pageCopy['workflow_item_1']) ?></li>
|
|
<li><?= h($pageCopy['workflow_item_2']) ?></li>
|
|
<li><?= h($pageCopy['workflow_item_3']) ?></li>
|
|
</ol>
|
|
</div>
|
|
</div>
|
|
<div class="col-lg-8">
|
|
<div class="panel h-100">
|
|
<div class="d-flex justify-content-between align-items-center mb-3 gap-3 flex-wrap">
|
|
<div>
|
|
<div class="section-kicker"><?= h($pageCopy['recent_kicker']) ?></div>
|
|
<h2 class="h5 mb-0"><?= h($pageCopy['recent_title']) ?></h2>
|
|
</div>
|
|
<a class="link-arrow" href="/admin.php"><?= h($pageCopy['manage_shelf']) ?></a>
|
|
</div>
|
|
<div class="row g-3">
|
|
<?php foreach ($recentDocuments as $document): ?>
|
|
<?php
|
|
$recentTitle = library_localized_document_title($document, $lang, $pageCopy['untitled']);
|
|
$recentTitleLang = library_text_lang($recentTitle, $lang);
|
|
$recentTitleDir = library_text_dir($recentTitle, $recentTitleLang);
|
|
?>
|
|
<div class="col-md-4">
|
|
<a class="recent-card text-decoration-none" href="/document.php?id=<?= h((string) $document['id']) ?>">
|
|
<span class="small text-secondary d-block mb-2"><?= h(library_language_label((string) $document['document_language'])) ?></span>
|
|
<strong class="d-block text-dark mb-1" lang="<?= h($recentTitleLang) ?>" dir="<?= h($recentTitleDir) ?>"><?= h($recentTitle) ?></strong>
|
|
<span class="small text-secondary"><?= h((string) ($document['author'] ?: $pageCopy['unknown_author'])) ?></span>
|
|
</a>
|
|
</div>
|
|
<?php endforeach; ?>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
<?php
|
|
library_render_footer();
|