dhammawatthumpra's picture
Implement expandable volume breakdown with page chips in search panel
c1f9028
Raw
History Blame Contribute Delete
33.1 kB
import React, { useState, useEffect, useRef, useCallback } from 'react';
import { useUIStore, useReaderStore, useThemeStore } from '../../stores/appStore';
import { useSearchStore } from '../../stores/searchStore';
import type { SearchMode } from '../../stores/searchStore';
import api from '../../lib/api';
import { X, Book, Search, BarChart2, ChevronRight, Loader2, ChevronDown, Zap, Sparkles } from 'lucide-react';
import { motion, AnimatePresence } from 'framer-motion';
// NavDrawer bg matches Shell bg per theme — seamless sidebar
// Dark: bg=#1a1a2e text=#e0e0e0 border=#3a3a5e
// Light: bg=#f8f6f0 text=#1a1a1a border=#e0d0b0
// Classic:bg=#f5edd8 text=#1a1a1a border=#d4c4a0
const DRAWER_STYLES: Record<string, { bg: string; text: string; border: string; tab: string; input: string }> = {
dark: { bg: 'bg-[#1a1a2e]', text: 'text-[#e0e0e0]', border: 'border-[#3a3a5e]', tab: 'bg-[#222242]', input: 'bg-[#222242]' },
light: { bg: 'bg-[#f8f6f0]', text: 'text-[#1a1a1a]', border: 'border-[#e0d0b0]', tab: 'bg-[#fdfaf5]', input: 'bg-white' },
classic: { bg: 'bg-[#f5edd8]', text: 'text-[#1a1a1a]', border: 'border-[#d4c4a0]', tab: 'bg-[#fdfaf5]', input: 'bg-white' },
};
/** Helper to extract all highlighted terms inside <mark> tags from search snippets,
* so we can highlight individual matched words in the reader page.
* Falls back to the original query if no marks are found. */
const getHighlightQuery = (originalQuery: string, results: any[]): string => {
const terms = new Set<string>();
const regex = /<mark>(.*?)<\/mark>/gi;
results.forEach(res => {
if (res.snippet) {
let match;
regex.lastIndex = 0;
while ((match = regex.exec(res.snippet)) !== null) {
const term = match[1].replace(/<[^>]+>/g, '').trim(); // clean any nested tags
if (term.length > 1) {
terms.add(term);
}
}
}
});
if (terms.size > 0) {
return Array.from(terms).join(' ');
}
return originalQuery;
};
const NavDrawer: React.FC = () => {
const { isNavOpen, setNavOpen, activeTab, setActiveTab } = useUIStore();
const { currentVolume, setVolume, setPage, setHighlightQuery } = useReaderStore();
const { theme } = useThemeStore();
const {
query, setQuery, results, totalResults,
isLoading, isAiLoading, performSearch, performAiSearch,
breakdown, timeTaken, searchMode, preferredMode, setPreferredMode,
suggestions, showSuggestions, fetchSuggestions, setShowSuggestions,
highlightWords,
} = useSearchStore();
const [modeDropdownOpen, setModeDropdownOpen] = useState(false);
const modeDropdownRef = useRef<HTMLDivElement>(null);
const [expandedVols, setExpandedVols] = useState<Record<number, boolean>>({});
const toggleVolExpand = (volId: number) => {
setExpandedVols(prev => ({
...prev,
[volId]: !prev[volId]
}));
};
const [toc, setToc] = useState<any[]>([]);
const [loadingToc, setLoadingToc] = useState(false);
const [volumes, setVolumes] = useState<any[]>([]);
const [activeSuggestion, setActiveSuggestion] = useState(-1);
const searchRef = useRef<HTMLFormElement>(null);
const debounceRef = useRef<ReturnType<typeof setTimeout> | null>(null);
const getHighlightString = () => {
if (highlightWords && highlightWords.length > 0) {
return highlightWords.join(' ');
}
return getHighlightQuery(query, results);
};
const s = DRAWER_STYLES[theme] ?? DRAWER_STYLES.dark;
useEffect(() => {
api.get('/api/pages/volumes').then(res => setVolumes(res.data));
}, []);
useEffect(() => {
if (activeTab !== 'toc') return;
setLoadingToc(true);
// volume_number = currentVolume → matches WHERE v.volume_number = ?
api.get(`/api/pages/volumes/${currentVolume}/toc`)
.then(res => setToc(res.data))
.finally(() => setLoadingToc(false));
}, [activeTab, currentVolume]);
const jumpToPage = (volumeNumber: number, pageNum: number, highlight?: string) => {
if (window.innerWidth < 1024) {
setNavOpen(false);
setTimeout(() => {
setVolume(volumeNumber);
setPage(pageNum);
setHighlightQuery(highlight ?? '');
}, 250);
} else {
setVolume(volumeNumber);
setPage(pageNum);
setHighlightQuery(highlight ?? '');
}
};
// Debounced suggestion fetch
const handleQueryChange = useCallback((value: string) => {
setQuery(value);
setActiveSuggestion(-1);
if (debounceRef.current) clearTimeout(debounceRef.current);
debounceRef.current = setTimeout(() => {
fetchSuggestions(value);
}, 280);
}, [setQuery, fetchSuggestions]);
// Close suggestions + mode dropdown on outside click
useEffect(() => {
const handler = (e: MouseEvent) => {
if (searchRef.current && !searchRef.current.contains(e.target as Node)) {
setShowSuggestions(false);
}
if (modeDropdownRef.current && !modeDropdownRef.current.contains(e.target as Node)) {
setModeDropdownOpen(false);
}
};
document.addEventListener('mousedown', handler);
return () => document.removeEventListener('mousedown', handler);
}, [setShowSuggestions]);
const handleModeSelect = (mode: SearchMode) => {
setPreferredMode(mode);
setModeDropdownOpen(false);
};
const selectSuggestion = (term: string) => {
setQuery(term);
setShowSuggestions(false);
setActiveSuggestion(-1);
setActiveTab('search');
setTimeout(() => performSearch(), 0);
};
const handleKeyDown = (e: React.KeyboardEvent<HTMLInputElement>) => {
if (!showSuggestions || suggestions.length === 0) {
if (e.key === 'Enter') {
e.preventDefault();
setActiveTab('search');
performSearch();
}
return;
}
if (e.key === 'ArrowDown') {
e.preventDefault();
setActiveSuggestion(i => Math.min(i + 1, suggestions.length - 1));
} else if (e.key === 'ArrowUp') {
e.preventDefault();
setActiveSuggestion(i => Math.max(i - 1, -1));
} else if (e.key === 'Enter') {
e.preventDefault();
if (activeSuggestion >= 0) {
selectSuggestion(suggestions[activeSuggestion]);
} else {
setShowSuggestions(false);
setActiveTab('search');
performSearch();
}
} else if (e.key === 'Escape') {
setShowSuggestions(false);
setActiveSuggestion(-1);
}
};
const handleSearch = (e: React.FormEvent) => {
e.preventDefault();
setShowSuggestions(false);
setActiveTab('search');
performSearch();
};
const tabs = [
{ id: 'toc', icon: <Book size={18} />, label: 'สารบัญ' },
{ id: 'search', icon: <Search size={18} />, label: 'ค้นหา' },
{ id: 'overview', icon: <BarChart2 size={18} />, label: 'ภาพรวม' },
] as const;
return (
<>
{/* Mobile backdrop */}
<AnimatePresence>
{isNavOpen && (
<motion.div
key="backdrop"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
onClick={() => setNavOpen(false)}
className="fixed inset-0 bg-black/50 z-40 lg:hidden"
/>
)}
</AnimatePresence>
{/* Drawer */}
<motion.aside
initial={false}
animate={{ x: isNavOpen ? 0 : -320, width: isNavOpen ? 320 : 0 }}
transition={{ type: 'tween', ease: 'easeOut', duration: 0.25 }}
className={`fixed lg:relative inset-y-0 left-0 z-50 flex flex-col overflow-hidden border-r ${s.bg} ${s.text} ${s.border}`}
>
{/* Header */}
<div className={`flex items-center justify-between px-4 py-3 border-b ${s.border}`}>
<div className="flex flex-col">
<span className="font-bold text-sm leading-tight">พระไตรปิฎก</span>
<span className="text-[10px] opacity-60 leading-tight">ฉบับมหาจุฬาลงกรณราชวิทยาลัย</span>
</div>
{/* X button — ทุก screen size (ไม่ใช่แค่ mobile) */}
<button onClick={() => setNavOpen(false)} className="p-1 rounded hover:bg-black/5">
<X size={18} />
</button>
</div>
{/* Tab bar */}
<div className={`flex border-b ${s.border}`}>
{tabs.map(tab => (
<button
key={tab.id}
onClick={() => setActiveTab(tab.id)}
className={`flex-1 flex flex-col items-center py-2.5 gap-1 text-[10px] font-bold uppercase tracking-wider transition-colors ${
activeTab === tab.id
? 'text-[#c8860a] border-b-2 border-[#c8860a]'
: 'text-[#888] hover:text-[#555]'
}`}
>
{tab.icon}
{tab.label}
</button>
))}
</div>
{/* Content */}
<div className="flex-1 overflow-y-auto">
{/* ── TOC ── */}
{activeTab === 'toc' && (
<div className="p-3 space-y-3">
<div>
<label className="text-[10px] font-bold text-[#888] uppercase block mb-1">เลือกเล่ม</label>
<select
value={currentVolume}
onChange={e => setVolume(Number(e.target.value))}
className={`w-full p-2 rounded-lg text-sm outline-none border ${s.border} ${s.input} ${s.text}`}
>
{volumes.map(v => (
<option key={v.id} value={v.volume_number}>
เล่ม {v.volume_number} — {v.title}
</option>
))}
</select>
</div>
{loadingToc ? (
<div className="flex justify-center py-12">
<Loader2 size={20} className="animate-spin text-[#c8860a]" />
</div>
) : (
<div className="space-y-0.5">
{toc.map((item, i) => (
<button
key={`${item.id}-${i}`}
onClick={() => jumpToPage(currentVolume, item.page_number)}
style={{ paddingLeft: `${(item.level ?? 0) * 12 + 8}px` }}
className="w-full text-left py-2 pr-3 text-sm rounded flex items-start gap-2 hover:bg-white/10 transition-colors group"
>
<ChevronRight size={12} className="mt-1 opacity-30 group-hover:opacity-80 flex-shrink-0 text-[#c8860a]" />
<span className="flex-1 leading-snug">{item.title}</span>
<span className="text-[10px] opacity-40 font-mono mt-0.5">{item.page_number}</span>
</button>
))}
</div>
)}
</div>
)}
{/* ── Search ── */}
{activeTab === 'search' && (
<div className="p-3 space-y-3">
<form onSubmit={handleSearch} className="relative" ref={searchRef}>
{/* Search input */}
<input
type="text"
value={query}
onChange={e => handleQueryChange(e.target.value)}
onKeyDown={handleKeyDown}
onFocus={() => query && fetchSuggestions(query)}
placeholder="ค้นหาข้อความ เช่น อริยสัจ…"
autoComplete="off"
className={`w-full pl-9 pr-3 py-2.5 rounded-xl text-sm outline-none border ${s.border} ${s.input} ${s.text} focus:border-[#c8860a] transition-colors`}
/>
<Search size={16} className="absolute left-3 top-3 text-[#888]" />
{isLoading && <Loader2 size={14} className="absolute right-3 top-3.5 animate-spin text-[#c8860a]" />}
{/* Suggestions dropdown */}
<AnimatePresence>
{showSuggestions && suggestions.length > 0 && (
<motion.ul
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.12 }}
className={`absolute left-0 right-0 top-full mt-1 z-50 rounded-xl border shadow-lg overflow-hidden ${s.bg} ${s.border}`}
>
{suggestions.map((term, i) => (
<li key={term}>
<button
type="button"
onMouseDown={() => selectSuggestion(term)}
className={`w-full text-left px-4 py-2.5 text-sm flex items-center gap-2 transition-colors ${
i === activeSuggestion
? 'bg-[#c8860a]/15 text-[#c8860a]'
: `${s.text} hover:bg-white/10`
}`}
>
<Search size={12} className="opacity-40 flex-shrink-0" />
{term}
</button>
</li>
))}
</motion.ul>
)}
</AnimatePresence>
</form>
{/* Split search button with mode dropdown */}
<div className="flex gap-1.5" ref={modeDropdownRef}>
{/* Main search button */}
<button
type="button"
onClick={() => { setModeDropdownOpen(false); performSearch(); }}
disabled={isLoading || isAiLoading || !query.trim()}
className="flex-1 flex items-center justify-center gap-1.5 py-2 px-3 rounded-xl text-sm font-bold bg-[#c8860a] hover:bg-[#a86e08] disabled:opacity-50 disabled:cursor-not-allowed text-white transition-colors"
>
{isLoading ? (
<Loader2 size={14} className="animate-spin" />
) : preferredMode === 'ai' ? (
<Sparkles size={14} />
) : (
<Zap size={14} />
)}
{preferredMode === 'ai' ? 'ค้นหาความหมาย' : 'ค้นหาคำ'}
</button>
{/* Mode dropdown toggle */}
<div className="relative">
<button
type="button"
onClick={() => setModeDropdownOpen(o => !o)}
className={`h-full px-2.5 rounded-xl border font-bold text-sm transition-colors ${s.border} ${s.tab} ${s.text} hover:border-[#c8860a]`}
title="เลือกโหมดการค้นหา"
>
<ChevronDown size={14} className={`transition-transform ${modeDropdownOpen ? 'rotate-180' : ''}`} />
</button>
<AnimatePresence>
{modeDropdownOpen && (
<motion.div
initial={{ opacity: 0, y: -4 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -4 }}
transition={{ duration: 0.12 }}
className={`absolute right-0 top-full mt-1 z-50 w-56 rounded-xl border shadow-lg overflow-hidden ${s.bg} ${s.border}`}
>
<button
type="button"
onClick={() => handleModeSelect('fts')}
className={`w-full text-left px-4 py-3 text-sm flex items-start gap-3 transition-colors ${
preferredMode === 'fts' ? 'bg-[#c8860a]/15' : `hover:bg-white/10`
}`}
>
<Zap size={16} className="mt-0.5 text-[#c8860a] flex-shrink-0" />
<div>
<p className={`font-bold text-sm ${s.text}`}>ค้นหาคำ <span className="text-[10px] font-normal opacity-60">(เร็ว)</span></p>
<p className="text-[10px] text-[#888] mt-0.5">ค้นชื่อคน สถานที่ คำบาลี</p>
</div>
{preferredMode === 'fts' && <span className="ml-auto text-[#c8860a] text-xs"></span>}
</button>
<div className={`h-px ${s.border} bg-current opacity-20`} />
<button
type="button"
onClick={() => handleModeSelect('ai')}
className={`w-full text-left px-4 py-3 text-sm flex items-start gap-3 transition-colors ${
preferredMode === 'ai' ? 'bg-[#c8860a]/15' : `hover:bg-white/10`
}`}
>
<Sparkles size={16} className="mt-0.5 text-[#c8860a] flex-shrink-0" />
<div>
<p className={`font-bold text-sm ${s.text}`}>ค้นหาความหมาย <span className="text-[10px] font-normal opacity-60">(AI)</span></p>
<p className="text-[10px] text-[#888] mt-0.5">ค้นหาตามใจความ เหมาะกับคำถาม</p>
</div>
{preferredMode === 'ai' && <span className="ml-auto text-[#c8860a] text-xs"></span>}
</button>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
{results.length > 0 && (
<>
{/* Result count + mode badge */}
<div className="flex items-center justify-between">
<p className="text-[10px] text-[#888] font-bold uppercase">
พบ {totalResults} หน้า ({timeTaken.toFixed(0)}ms)
</p>
<span className={`text-[9px] font-bold px-2 py-0.5 rounded-full ${
searchMode === 'ai'
? 'bg-purple-500/20 text-purple-400'
: 'bg-amber-500/20 text-amber-500'
}`}>
{searchMode === 'ai' ? '✦ AI' : '⚡ FTS'}
</span>
</div>
{/* Volume breakdown bars — แสดงทุกเล่มที่พบ ไม่จำกัดจำนวน */}
{Object.keys(breakdown).length > 1 && (
<div className={`p-3 rounded-xl border ${s.border} ${s.tab} space-y-2`}>
<p className="text-[10px] font-bold text-[#888] uppercase mb-1">การกระจายตัว</p>
{Object.entries(breakdown)
.sort((a, b) => Number(a[0]) - Number(b[0]))
.map(([volId, stats]) => {
const numericVolId = Number(volId);
const vol = volumes.find(v => v.id === numericVolId);
const maxCount = Math.max(...Object.values(breakdown).map(s => s.count));
const pct = (stats.count / maxCount) * 100;
const isExpanded = !!expandedVols[numericVolId];
const hasPages = stats.pages && stats.pages.length > 0;
return (
<div key={volId} className="space-y-1.5 border-b border-white/5 last:border-b-0 pb-1.5 last:pb-0">
{/* Header Bar Area (คลิกเพื่อขยาย) */}
<div
onClick={() => toggleVolExpand(numericVolId)}
className="w-full text-left space-y-0.5 cursor-pointer group/bar"
>
<div className="flex justify-between text-[9px] text-[#888] group-hover/bar:text-[#c8860a] transition-colors font-bold">
<span className="truncate max-w-[185px]">เล่ม {vol?.volume_number} — {vol?.title}</span>
<span className="flex items-center gap-1">
<span>{stats.count}</span>
{hasPages && (
<ChevronDown size={10} className={`transform transition-transform ${isExpanded ? 'rotate-180 text-[#c8860a]' : 'opacity-40'}`} />
)}
</span>
</div>
<div className="h-1 bg-white/10 rounded-full">
<motion.div
initial={{ width: 0 }}
animate={{ width: `${pct}%` }}
className="h-full bg-[#c8860a] rounded-full group-hover/bar:bg-[#9a6307] transition-colors"
/>
</div>
</div>
{/* Expanded Page Chips */}
<AnimatePresence>
{isExpanded && hasPages && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.2 }}
className="overflow-hidden pl-1 pr-1 pt-1"
>
<div className="flex flex-wrap gap-1 max-h-36 overflow-y-auto custom-scrollbar">
{stats.pages?.map((pageNum) => (
<button
key={pageNum}
type="button"
onClick={() => vol && jumpToPage(vol.volume_number, pageNum, getHighlightString())}
className="text-[9px] font-mono px-1.5 py-0.5 rounded bg-white/10 hover:bg-[#c8860a] hover:text-white transition-colors text-center min-w-[32px] border border-white/5"
>
{pageNum}
</button>
))}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
);
})}
</div>
)}
{/* Result cards */}
<div className="space-y-2">
{results.map((res, i) => {
const vol = volumes.find(v => v.id === res.volume_id);
const volNum = res.volume_number ?? vol?.volume_number;
return (
<div
key={i}
className={`w-full text-left p-3 rounded-xl border ${s.border} ${s.tab} hover:border-[#c8860a] hover:shadow-sm transition-all`}
>
{/* Google-style breadcrumb */}
<div className="flex items-center flex-wrap gap-x-0.5 gap-y-0.5 mb-1.5 text-[10px] font-bold">
{/* เล่ม N */}
<button
type="button"
onClick={() => volNum && jumpToPage(volNum, 1)}
className="text-[#c8860a] hover:underline hover:text-[#e0980f] transition-colors"
>
เล่ม {volNum}
</button>
{/* › พระสูตร/หัวข้อ */}
{res.toc_title && (
<>
<span className="text-[#888] mx-0.5"></span>
<button
type="button"
onClick={() => volNum && res.toc_page && jumpToPage(volNum, res.toc_page, getHighlightString())}
className="text-[#c8860a] hover:underline hover:text-[#e0980f] transition-colors max-w-[160px] truncate text-left"
title={res.toc_title}
>
{res.toc_title}
</button>
</>
)}
{/* › หน้า N */}
<>
<span className="text-[#888] mx-0.5"></span>
<button
type="button"
onClick={() => volNum && jumpToPage(volNum, res.page_number, getHighlightString())}
className="text-[#888] hover:text-[#c8860a] hover:underline transition-colors"
>
หน้า {res.page_number}
</button>
</>
</div>
{/* Snippet — กดทั้ง card ไปหน้าที่พบ */}
<button
type="button"
onClick={() => volNum && jumpToPage(volNum, res.page_number, getHighlightString())}
className="w-full text-left"
>
<span
className="block text-xs text-[#aaa] line-clamp-3 leading-relaxed"
dangerouslySetInnerHTML={{ __html: res.snippet }}
/>
</button>
</div>
);
})}
</div>
{/* AI upgrade button — แสดงเฉพาะเมื่อผลเป็น FTS และ preferredMode ไม่ใช่ ai */}
{searchMode === 'fts' && (
<button
type="button"
onClick={() => performAiSearch()}
disabled={isAiLoading}
className={`w-full flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl border text-sm font-bold transition-all ${
isAiLoading
? `${s.border} opacity-60 cursor-not-allowed ${s.text}`
: `border-purple-500/40 text-purple-400 hover:bg-purple-500/10 hover:border-purple-500/70`
}`}
>
{isAiLoading ? (
<><Loader2 size={14} className="animate-spin" /> กำลังค้นหาด้วย AI…</>
) : (
<><Sparkles size={14} /> ค้นหาเพิ่มเติมด้วย AI</>
)}
</button>
)}
</>
)}
{!isLoading && results.length === 0 && query && (
<div className="text-center py-12 space-y-4">
<div className="opacity-40">
<Search size={36} className="mx-auto mb-3 opacity-20" />
<p className="text-sm">ไม่พบ «{query}»</p>
</div>
{searchMode === 'fts' && (
<div className="pt-2 px-4">
<button
type="button"
onClick={() => performAiSearch()}
disabled={isAiLoading}
className={`w-full flex items-center justify-center gap-2 py-2.5 px-4 rounded-xl border text-sm font-bold transition-all ${
isAiLoading
? `${s.border} opacity-60 cursor-not-allowed ${s.text}`
: `border-purple-500/40 text-purple-400 hover:bg-purple-500/10 hover:border-purple-500/70`
}`}
>
{isAiLoading ? (
<><Loader2 size={14} className="animate-spin" /> กำลังค้นหาด้วย AI…</>
) : (
<><Sparkles size={14} /> ลองค้นหาความหมายด้วย AI</>
)}
</button>
</div>
)}
</div>
)}
</div>
)}
{/* ── Overview ── */}
{activeTab === 'overview' && (
<div className="p-4 space-y-4 text-sm">
<div className={`p-4 rounded-xl border ${s.border} ${s.tab}`}>
<p className="font-bold text-[#c8860a] mb-1">พระไตรปิฎก AI Expert</p>
<p className="text-xs text-[#888] leading-relaxed">
โครงการสืบค้นพระไตรปิฎก ฉบับมหาจุฬาลงกรณราชวิทยาลัย ด้วย AI และ Semantic Search
</p>
</div>
<div>
<p className="text-[10px] font-bold text-[#888] uppercase mb-2">เทคโนโลยีที่ใช้</p>
<div className="grid grid-cols-2 gap-2 text-[10px] font-bold text-[#aaa]">
{['FastAPI + Python', 'Vite + React', 'SQLite FTS5', 'Turbovec + Reranker'].map(t => (
<div key={t} className={`p-2 rounded-lg border ${s.border} ${s.tab}`}>{t}</div>
))}
</div>
</div>
<div>
<p className="text-[10px] font-bold text-[#888] uppercase mb-2">แหล่งข้อมูลอ้างอิง</p>
<div className={`p-4 rounded-xl border ${s.border} ${s.tab} text-xs text-[#888] space-y-3`}>
<p className="leading-relaxed">
พระไตรปิฎกฉบับมหาจุฬาลงกรณราชวิทยาลัย อ้างอิงต้นฉบับดิจิทัลและเผยแผ่โดยมีเว็บไซต์{' '}
<a
href="https://84000.org"
target="_blank"
rel="noopener noreferrer"
className="text-[#c8860a] hover:underline font-semibold"
>
84000.org
</a>{' '}
เป็นแหล่งข้อมูลปฐมภูมิ (Primary Data Source) ของโครงการ
</p>
<div className="p-2.5 rounded bg-black/10 text-[10px] text-[#aaa] border-l-2 border-[#c8860a]/60 leading-relaxed italic">
<strong>ข้อสงวนสิทธิ์ (Disclaimer):</strong> “84000.org เป็นผู้เอื้อเฟื้อข้อมูลต้นฉบับ แต่ไม่มีส่วนรับผิดชอบต่อผลลัพธ์ที่เกิดจากการประมวลผลของระบบ AI”
</div>
<hr className={`border-t ${s.border}`} />
<a
href="https://84000.org/tipitaka/read/m_siri.php?B=1&siri=1&fontsz=0"
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center text-[#c8860a] hover:text-[#e0980f] font-semibold transition-colors gap-1 text-[11px]"
>
<span>เข้าชมเว็บไซต์ต้นฉบับ</span>
<ChevronRight size={12} className="mt-[1px]" />
</a>
</div>
</div>
</div>
)}
</div>
</motion.aside>
</>
);
};
export default NavDrawer;