style(web): refine aesthetic to archival precision, remove generic gradients and glowing blobs
CI / typecheck + tests (push) Canceled after 0s
CI / build + deploy (Nix) — api (push) Canceled after 0s
CI / build + deploy (Nix) — mcp (push) Canceled after 0s
CI / build + deploy (Nix) — worker (push) Canceled after 0s
CI / build + deploy (web) (push) Canceled after 0s

This commit is contained in:
asepharyana committed 2026-08-22 15:12:00 +07:00
1 parent 011571e12d
commit 067115ba13
15 files changed
+604 -745

No files matched your search

+77 -85
View File
@@ -15,19 +15,16 @@ import {
Folder, Folder,
FileText, FileText,
Clock, Clock,
User,
Tag,
History, History,
FileDown, FileDown,
ArrowLeft, ArrowLeft,
BookOpen,
Sparkles, Sparkles,
ShieldCheck,
CheckCircle2, CheckCircle2,
XCircle, XCircle,
Target, Target,
Trophy, Trophy,
Zap, Zap,
Tag,
} from "lucide-react"; } from "lucide-react";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -63,7 +60,7 @@ function CustomFieldBadges({
if (customEntries.length === 0) return null; if (customEntries.length === 0) return null;
return ( return (
<div className="flex flex-wrap items-center gap-2 mt-4 pt-4 border-t border-[var(--border-color)]"> <div className="flex flex-wrap items-center gap-2 mt-4 pt-3.5 border-t border-[var(--border-color)]">
{customEntries.map(([key, value]) => { {customEntries.map(([key, value]) => {
const label = key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " "); const label = key.charAt(0).toUpperCase() + key.slice(1).replace(/_/g, " ");
let colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-secondary)]"; let colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-secondary)]";
@@ -71,36 +68,36 @@ function CustomFieldBadges({
let IconComponent = Tag; let IconComponent = Tag;
if (typeof value === "number") { if (typeof value === "number") {
colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-primary)] font-semibold";
IconComponent = Target; IconComponent = Target;
displayValue = `${value} pts`; displayValue = `${value} pts`;
} else if (typeof value === "boolean") { } else if (typeof value === "boolean") {
colorClass = value colorClass = value
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400" ? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400 font-semibold"
: "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400"; : "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400 font-semibold";
IconComponent = value ? CheckCircle2 : XCircle; IconComponent = value ? CheckCircle2 : XCircle;
displayValue = value ? "Solved" : "Pending"; displayValue = value ? "Solved" : "Pending";
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-secondary)]";
IconComponent = Folder; IconComponent = Folder;
displayValue = value.join(", "); displayValue = value.join(", ");
} else { } else {
const v = String(value).toLowerCase(); const v = String(value).toLowerCase();
if (/^(easy|simple|beginner)/i.test(v)) { if (/^(easy|simple|beginner)/i.test(v)) {
colorClass = "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"; colorClass = "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400 font-semibold";
displayValue = "Easy"; displayValue = "Easy";
} else if (/^(medium|intermediate)/i.test(v)) { } else if (/^(medium|intermediate)/i.test(v)) {
colorClass = "bg-amber-500/10 border-amber-500/30 text-amber-600 dark:text-amber-400"; colorClass = "bg-amber-500/10 border-amber-500/30 text-amber-600 dark:text-amber-400 font-semibold";
displayValue = "Medium"; displayValue = "Medium";
} else if (/^(hard|expert|advanced)/i.test(v)) { } else if (/^(hard|expert|advanced)/i.test(v)) {
colorClass = "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400"; colorClass = "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400 font-semibold";
displayValue = "Hard"; displayValue = "Hard";
} else if (/^(pwn|web|crypto|misc|forensic|reverse|binary)/i.test(v)) { } else if (/^(pwn|web|crypto|misc|forensic|reverse|binary)/i.test(v)) {
colorClass = "bg-indigo-500/10 border-indigo-500/30 text-indigo-600 dark:text-indigo-300"; colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-primary)] font-bold";
IconComponent = Zap; IconComponent = Zap;
displayValue = v.toUpperCase(); displayValue = v.toUpperCase();
} else if (/ctf|def.?con|event/i.test(v)) { } else if (/ctf|def.?con|event/i.test(v)) {
colorClass = "bg-[var(--brand)]/10 border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)]"; colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-primary)]";
IconComponent = Trophy; IconComponent = Trophy;
displayValue = String(value); displayValue = String(value);
} else { } else {
@@ -111,11 +108,11 @@ function CustomFieldBadges({
return ( return (
<span <span
key={key} key={key}
className={`inline-flex items-center gap-1.5 px-3 py-1 border rounded-lg text-xs font-mono transition-colors ${colorClass}`} className={`inline-flex items-center gap-1.5 px-2.5 py-1 border rounded-md text-xs font-mono transition-colors ${colorClass}`}
> >
<IconComponent className="w-3.5 h-3.5 opacity-80" /> <IconComponent className="w-3.5 h-3.5 opacity-80" />
<span className="text-[var(--text-muted)] font-sans">{label}:</span> <span className="text-[var(--text-muted)] font-sans">{label}:</span>
<span className="font-semibold">{displayValue}</span> <span>{displayValue}</span>
</span> </span>
); );
})} })}
@@ -187,33 +184,33 @@ function FolderIndexPage({
</nav> </nav>
{/* Header Card */} {/* Header Card */}
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl p-6 sm:p-7 shadow-sm"> <div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 shadow-xs">
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4"> <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4">
<div className="flex items-center gap-3.5"> <div className="flex items-center gap-3.5">
<div className="w-12 h-12 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border-color)] flex items-center justify-center text-[var(--brand)] dark:text-[var(--accent)] shadow-inner"> <div className="w-10 h-10 rounded-lg bg-[var(--bg-elevated)] border border-[var(--border-color)] flex items-center justify-center text-[var(--text-primary)]">
<Folder className="w-6 h-6" /> <Folder className="w-5 h-5" />
</div> </div>
<div> <div>
<h1 className="text-2xl sm:text-3xl font-extrabold text-[var(--text-primary)] tracking-tight"> <h1 className="text-2xl font-bold text-[var(--text-primary)] tracking-tight">
{folderName} {folderName}
</h1> </h1>
<p className="text-xs text-[var(--text-muted)] mt-1 font-mono"> <p className="text-xs text-[var(--text-muted)] mt-0.5 font-mono">
Directory: <code className="text-[var(--brand)] dark:text-[var(--accent)] font-semibold">{section}/{slug}</code> Directory: <code>{section}/{slug}</code>
</p> </p>
</div> </div>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs font-mono text-[var(--text-muted)] bg-[var(--bg-elevated)] border border-[var(--border-color)] px-3 py-1.5 rounded-lg"> <span className="text-xs font-mono text-[var(--text-muted)] bg-[var(--bg-elevated)] border border-[var(--border-color)] px-2.5 py-1 rounded">
{immediateDocs.length + folders.length} item{folders.length + immediateDocs.length !== 1 ? "s" : ""} {immediateDocs.length + folders.length} item{folders.length + immediateDocs.length !== 1 ? "s" : ""}
</span> </span>
<Link <Link
href={`/${section}/${slug}/export`} href={`/${section}/${slug}/export`}
className="inline-flex items-center gap-1.5 text-xs bg-[var(--brand)] hover:bg-[var(--brand-hover)] text-white px-3.5 py-1.5 rounded-lg font-semibold transition-all shadow-md shadow-[var(--brand)]/20" className="inline-flex items-center gap-1.5 text-xs bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] px-3 py-1.5 rounded font-semibold transition-all shadow-xs"
title={`Export all ${folderName} documents as a single PDF report`} title={`Export all ${folderName} documents as a single PDF report`}
> >
<FileDown className="w-3.5 h-3.5" /> <FileDown className="w-3.5 h-3.5" />
<span>Export PDF Report</span> <span>Export PDF</span>
</Link> </Link>
</div> </div>
</div> </div>
@@ -222,8 +219,8 @@ function FolderIndexPage({
{/* Subfolders Grid */} {/* Subfolders Grid */}
{folders.length > 0 && ( {folders.length > 0 && (
<div> <div>
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase mb-3.5 tracking-wider font-mono flex items-center gap-2"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase mb-3 tracking-wider font-mono flex items-center gap-2">
<Folder className="w-3.5 h-3.5 text-[var(--brand)] dark:text-[var(--accent)]" /> <Folder className="w-3.5 h-3.5 text-[var(--text-muted)]" />
<span>Subfolders ({folders.length})</span> <span>Subfolders ({folders.length})</span>
</h2> </h2>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3">
@@ -231,16 +228,14 @@ function FolderIndexPage({
<Link <Link
key={folder} key={folder}
href={`/${section}/${slug}/${folder}`.replace(/\/+/g, "/")} href={`/${section}/${slug}/${folder}`.replace(/\/+/g, "/")}
className="group flex items-center gap-3.5 p-4 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group flex items-center gap-3 p-3.5 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-lg transition-colors shadow-xs"
> >
<div className="p-2 rounded-xl bg-[var(--bg-elevated)] text-[var(--brand)] dark:text-[var(--accent)]"> <Folder className="w-4 h-4 text-[var(--text-muted)]" />
<Folder className="w-5 h-5" />
</div>
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="text-sm font-bold text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors truncate"> <div className="text-xs font-semibold text-[var(--text-primary)] group-hover:underline truncate">
{folder.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")} {folder.split(/[-_]/).map((w) => w.charAt(0).toUpperCase() + w.slice(1)).join(" ")}
</div> </div>
<div className="text-xs text-[var(--text-muted)] font-mono mt-0.5"> <div className="text-[11px] text-[var(--text-muted)] font-mono mt-0.5">
{info.docCount} document{info.docCount !== 1 ? "s" : ""} {info.docCount} document{info.docCount !== 1 ? "s" : ""}
</div> </div>
</div> </div>
@@ -253,31 +248,29 @@ function FolderIndexPage({
{/* Documents List */} {/* Documents List */}
{immediateDocs.length > 0 && ( {immediateDocs.length > 0 && (
<div> <div>
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase mb-3.5 tracking-wider font-mono flex items-center gap-2"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase mb-3 tracking-wider font-mono flex items-center gap-2">
<FileText className="w-3.5 h-3.5 text-[var(--brand)] dark:text-[var(--accent)]" /> <FileText className="w-3.5 h-3.5 text-[var(--text-muted)]" />
<span>Documents ({immediateDocs.length})</span> <span>Documents ({immediateDocs.length})</span>
</h2> </h2>
<div className="space-y-2.5"> <div className="space-y-2">
{immediateDocs.map((doc) => ( {immediateDocs.map((doc) => (
<Link <Link
key={doc.slug} key={doc.slug}
href={`/${doc.slug}`} href={`/${doc.slug}`}
className="group flex items-center justify-between gap-4 p-4 sm:p-5 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group flex items-center justify-between gap-4 p-4 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-lg transition-colors shadow-xs"
> >
<div className="flex items-center gap-3.5 min-w-0"> <div className="flex items-center gap-3 min-w-0">
<div className="p-2 rounded-xl bg-[var(--bg-elevated)] text-[var(--text-dim)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors"> <FileText className="w-4 h-4 text-[var(--text-muted)] shrink-0" />
<FileText className="w-4 h-4" />
</div>
<div className="min-w-0"> <div className="min-w-0">
<div className="text-sm font-bold text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors truncate"> <div className="text-xs sm:text-sm font-semibold text-[var(--text-primary)] group-hover:underline truncate">
{doc.title} {doc.title}
</div> </div>
{doc.tags.length > 0 && ( {doc.tags.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-2"> <div className="flex flex-wrap gap-1 mt-1.5">
{doc.tags.slice(0, 4).map((t) => ( {doc.tags.slice(0, 4).map((t) => (
<span <span
key={t} key={t}
className="text-[10px] px-2 py-0.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-md text-[var(--text-muted)] font-mono" className="text-[10px] px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] font-mono"
> >
#{t} #{t}
</span> </span>
@@ -303,8 +296,8 @@ function FolderIndexPage({
)} )}
{folders.length === 0 && immediateDocs.length === 0 && ( {folders.length === 0 && immediateDocs.length === 0 && (
<div className="text-center py-16 border border-[var(--border-color)] rounded-2xl bg-[var(--bg-surface)]"> <div className="text-center py-12 border border-[var(--border-color)] rounded-lg bg-[var(--bg-surface)]">
<p className="text-sm text-[var(--text-muted)]">No documents found in this directory.</p> <p className="text-sm text-[var(--text-muted)]">No documents in this directory.</p>
</div> </div>
)} )}
</div> </div>
@@ -316,7 +309,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
const searchParamsObj = await searchParams; const searchParamsObj = await searchParams;
const { edit, path: queryPath, sort } = searchParamsObj; const { edit, path: queryPath, sort } = searchParamsObj;
// Handle direct export views: /[section]/export, /[section]/[...folder]/export, or /export/pdf // Handle direct export views
if (section === "export" || (slug && slug[slug.length - 1] === "export")) { if (section === "export" || (slug && slug[slug.length - 1] === "export")) {
const isTrailingExport = slug && slug[slug.length - 1] === "export"; const isTrailingExport = slug && slug[slug.length - 1] === "export";
const targetSlug = isTrailingExport ? slug.slice(0, -1) : slug; const targetSlug = isTrailingExport ? slug.slice(0, -1) : slug;
@@ -363,7 +356,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
<ArrowLeft className="w-3.5 h-3.5" /> <ArrowLeft className="w-3.5 h-3.5" />
<span>Back to document</span> <span>Back to document</span>
</Link> </Link>
<h1 className="text-2xl sm:text-3xl font-extrabold text-[var(--text-primary)] tracking-tight"> <h1 className="text-2xl font-bold text-[var(--text-primary)] tracking-tight">
Edit: {doc.title} Edit: {doc.title}
</h1> </h1>
<DocForm <DocForm
@@ -399,7 +392,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
{/* Main Content Column */} {/* Main Content Column */}
<article className="flex-1 min-w-0"> <article className="flex-1 min-w-0">
{/* Breadcrumbs Navigation */} {/* Breadcrumbs Navigation */}
<nav className="flex items-center gap-2 text-xs text-[var(--text-dim)] mb-4 flex-wrap"> <nav className="flex items-center gap-2 text-xs text-[var(--text-dim)] mb-4 flex-wrap font-mono">
<Link href="/" className="hover:text-[var(--text-primary)] transition-colors"> <Link href="/" className="hover:text-[var(--text-primary)] transition-colors">
MCPedia MCPedia
</Link> </Link>
@@ -414,7 +407,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
<span>/</span> <span>/</span>
<Link <Link
href={`/${crumbPath}`} href={`/${crumbPath}`}
className={isLast ? "text-[var(--text-primary)] font-semibold" : "hover:text-[var(--text-primary)] transition-colors"} className={isLast ? "text-[var(--text-primary)] font-semibold font-sans" : "hover:text-[var(--text-primary)] transition-colors font-sans"}
> >
{label} {label}
</Link> </Link>
@@ -424,17 +417,17 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
</nav> </nav>
{/* Hero Metadata Card */} {/* Hero Metadata Card */}
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl p-6 sm:p-7 mb-8 shadow-sm"> <div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 sm:p-7 mb-8 shadow-xs">
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-5 mb-5"> <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-4">
<div className="flex-1 min-w-0"> <div className="flex-1 min-w-0">
<div className="flex items-center gap-2 mb-2.5 flex-wrap"> <div className="flex items-center gap-2 mb-2 flex-wrap text-xs">
<span className="inline-flex items-center gap-1.5 px-2.5 py-1 rounded-lg text-xs font-mono font-bold bg-[var(--brand)]/15 text-[var(--brand)] dark:text-[var(--accent)] border border-[var(--brand)]/30 uppercase"> <span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-mono font-bold bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-primary)] uppercase">
<span>{sectionInfo.icon}</span> <span>{sectionInfo.icon}</span>
<span>{sectionInfo.label}</span> <span>{sectionInfo.label}</span>
</span> </span>
<span className="text-xs text-[var(--text-dim)]">·</span> <span className="text-xs text-[var(--text-dim)]">·</span>
<span className="text-xs text-[var(--text-muted)] font-medium"> <span className="text-xs text-[var(--text-muted)] font-mono">
{doc.type.charAt(0).toUpperCase() + doc.type.slice(1)} {doc.type}
</span> </span>
<span className="text-xs text-[var(--text-dim)]">·</span> <span className="text-xs text-[var(--text-dim)]">·</span>
<span className="text-xs text-[var(--text-muted)] font-mono flex items-center gap-1"> <span className="text-xs text-[var(--text-muted)] font-mono flex items-center gap-1">
@@ -443,31 +436,30 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
</span> </span>
</div> </div>
<h1 className="text-2xl sm:text-3xl lg:text-4xl font-extrabold text-[var(--text-primary)] tracking-tight leading-tight"> <h1 className="text-2xl sm:text-3xl font-extrabold text-[var(--text-primary)] tracking-tight leading-tight">
{doc.title} {doc.title}
</h1> </h1>
</div> </div>
{/* Quick Actions Deck */} {/* Quick Actions */}
<DocActions slug={doc.slug} body={doc.body} canEdit={canEdit} /> <DocActions slug={doc.slug} body={doc.body} canEdit={canEdit} />
</div> </div>
{/* Author, Date, and Tags */} {/* Author, Date, Tags */}
<div className="flex flex-wrap items-center gap-3 text-xs text-[var(--text-muted)] pt-4 border-t border-[var(--border-color)]"> <div className="flex flex-wrap items-center gap-3 text-xs text-[var(--text-muted)] pt-3.5 border-t border-[var(--border-color)]">
{doc.author && ( {doc.author && (
<div className="flex items-center gap-1.5"> <div className="flex items-center gap-1.5">
<div className="w-4 h-4 rounded-full bg-[var(--brand)] text-white flex items-center justify-center text-[10px] font-bold"> <div className="w-4 h-4 rounded bg-[var(--brand)] text-[var(--brand-fg)] flex items-center justify-center text-[10px] font-bold font-mono">
{doc.author.charAt(0).toUpperCase()} {doc.author.charAt(0).toUpperCase()}
</div> </div>
<span className="text-[var(--text-secondary)] font-medium">{doc.author}</span> <span className="text-[var(--text-secondary)] font-medium">{doc.author}</span>
<span className="text-[var(--text-dim)]">·</span> <span className="text-[var(--text-dim)]">·</span>
</div> </div>
)} )}
<span className="flex items-center gap-1"> <span className="flex items-center gap-1 font-mono text-[11px]">
<Clock className="w-3 h-3 text-[var(--text-dim)]" />
<span> <span>
Updated {new Date(doc.updatedAt).toLocaleDateString(undefined, { Updated {new Date(doc.updatedAt).toLocaleDateString(undefined, {
month: "long", month: "short",
day: "numeric", day: "numeric",
year: "numeric", year: "numeric",
})} })}
@@ -481,7 +473,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
{doc.tags.map((t) => ( {doc.tags.map((t) => (
<span <span
key={t} key={t}
className="px-2 py-0.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-md text-[var(--text-muted)] text-[11px] font-mono" className="px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] text-[10px] font-mono"
> >
#{t} #{t}
</span> </span>
@@ -495,8 +487,8 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
<CustomFieldBadges doc={{ ...doc }} standardKeys={STANDARD_KEYS} /> <CustomFieldBadges doc={{ ...doc }} standardKeys={STANDARD_KEYS} />
</div> </div>
{/* Mobile TOC (rendered inline on smaller screens) */} {/* Mobile TOC */}
<div className="xl:hidden mb-6 p-5 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl shadow-xs"> <div className="xl:hidden mb-6 p-4 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl shadow-xs">
<TOC source={doc.body} /> <TOC source={doc.body} />
</div> </div>
@@ -507,24 +499,24 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
{/* Related Documents Grid */} {/* Related Documents Grid */}
{related.length > 0 && ( {related.length > 0 && (
<aside className="mt-14 pt-8 border-t border-[var(--border-color)]"> <aside className="mt-12 pt-8 border-t border-[var(--border-color)]">
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-3.5">
<Sparkles className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <Sparkles className="w-4 h-4 text-[var(--text-muted)]" />
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono">
Related Knowledge & Writeups Related Knowledge
</h2> </h2>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3.5"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
{related.map((r) => { {related.map((r) => {
const rInfo = getSectionInfo(r.section); const rInfo = getSectionInfo(r.section);
return ( return (
<Link <Link
key={r.slug} key={r.slug}
href={`/${r.slug}`} href={`/${r.slug}`}
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl p-5 transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-lg p-4 transition-colors shadow-xs"
> >
<div className="flex items-center justify-between gap-2 mb-2"> <div className="flex items-center justify-between gap-2 mb-1.5">
<span className="text-xs text-[var(--brand)] dark:text-[var(--accent)] font-mono uppercase font-semibold"> <span className="text-[10px] text-[var(--text-muted)] font-mono uppercase font-semibold">
{rInfo.icon} {r.section} {rInfo.icon} {r.section}
</span> </span>
<time className="text-[11px] text-[var(--text-dim)] font-mono"> <time className="text-[11px] text-[var(--text-dim)] font-mono">
@@ -534,7 +526,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
})} })}
</time> </time>
</div> </div>
<h3 className="text-sm font-bold text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors line-clamp-1 mb-1"> <h3 className="text-xs sm:text-sm font-semibold text-[var(--text-primary)] group-hover:underline line-clamp-1">
{r.title} {r.title}
</h3> </h3>
</Link> </Link>
@@ -544,12 +536,12 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
</aside> </aside>
)} )}
{/* Revision History Timeline */} {/* Revision History */}
{revisions.length > 0 && ( {revisions.length > 0 && (
<aside className="mt-10 pt-8 border-t border-[var(--border-color)]"> <aside className="mt-10 pt-8 border-t border-[var(--border-color)]">
<div className="flex items-center justify-between mb-4"> <div className="flex items-center justify-between mb-3.5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<History className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <History className="w-4 h-4 text-[var(--text-muted)]" />
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono">
Revision History ({revisions.length}) Revision History ({revisions.length})
</h2> </h2>
@@ -559,16 +551,16 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
{revisions.map((rev) => ( {revisions.map((rev) => (
<div <div
key={rev.id} key={rev.id}
className="flex items-center justify-between gap-3 p-3.5 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl text-xs shadow-xs" className="flex items-center justify-between gap-3 p-3 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-lg text-xs shadow-xs"
> >
<div className="flex items-center gap-2.5 min-w-0"> <div className="flex items-center gap-2 min-w-0 font-mono">
<span className="font-mono text-[var(--brand)] dark:text-[var(--accent)] bg-[var(--brand)]/10 px-2 py-0.5 rounded-md border border-[var(--brand)]/30 font-bold"> <span className="text-[var(--text-primary)] bg-[var(--bg-elevated)] px-1.5 py-0.5 rounded border border-[var(--border-color)] font-bold text-[10px]">
v{rev.revisionNo} v{rev.revisionNo}
</span> </span>
<span className="text-[var(--text-primary)] font-medium truncate"> <span className="text-[var(--text-secondary)] font-sans truncate text-xs">
{rev.reason || "Updated document in PostgreSQL"} {rev.reason || "Updated document in PostgreSQL"}
</span> </span>
<span className="text-[var(--text-dim)] hidden sm:inline font-mono"> <span className="text-[var(--text-dim)] hidden sm:inline text-[11px]">
· {new Date(rev.createdAt).toLocaleString()} · {new Date(rev.createdAt).toLocaleString()}
</span> </span>
</div> </div>
@@ -577,7 +569,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
<input type="hidden" name="id" value={rev.id} /> <input type="hidden" name="id" value={rev.id} />
<button <button
type="submit" type="submit"
className="text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] px-3 py-1 rounded-lg border border-[var(--border-color)] hover:border-[var(--brand)] transition-colors font-semibold shadow-xs cursor-pointer" className="text-xs text-[var(--text-primary)] hover:text-black dark:hover:text-white bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] px-2.5 py-1 rounded border border-[var(--border-color)] transition-colors font-medium cursor-pointer"
> >
Restore Restore
</button> </button>
+39 -47
View File
@@ -4,13 +4,9 @@ import { getSectionMeta } from "@mcpedia/config";
import { import {
Folder, Folder,
FileText, FileText,
ChevronRight,
FileDown, FileDown,
Plus, Plus,
Compass,
ArrowLeft,
Clock, Clock,
Tag,
} from "lucide-react"; } from "lucide-react";
export const dynamic = "force-dynamic"; export const dynamic = "force-dynamic";
@@ -92,28 +88,28 @@ function renderTree(nodes: TreeNode[], pathname: string) {
<li key={node.slug}> <li key={node.slug}>
<Link <Link
href={`/${node.slug}`} href={`/${node.slug}`}
className={`flex items-center justify-between gap-2 text-xs py-2 px-3 rounded-xl transition-all ${ className={`flex items-center justify-between gap-2 text-xs py-1.5 px-2.5 rounded-md transition-colors ${
isActive isActive
? "text-[var(--brand)] dark:text-[var(--accent)] bg-[var(--brand)]/15 font-semibold border border-[var(--brand)]/30 shadow-xs" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)] font-semibold border border-[var(--border-color)] shadow-xs"
: hasActiveChild : hasActiveChild
? "text-[var(--text-primary)] bg-[var(--bg-elevated)]" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)]"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
}`} }`}
> >
<span className="flex items-center gap-2.5 truncate"> <span className="flex items-center gap-2 truncate">
<span className="text-[var(--text-dim)]"> <span className="text-[var(--text-dim)]">
{isFolder ? <Folder className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> : <FileText className="w-4 h-4" />} {isFolder ? <Folder className="w-3.5 h-3.5" /> : <FileText className="w-3.5 h-3.5" />}
</span> </span>
<span className="truncate font-medium">{node.doc?.title || node.name}</span> <span className="truncate font-medium">{node.doc?.title || node.name}</span>
</span> </span>
{isFolder && ( {isFolder && (
<span className="text-[10px] text-[var(--text-dim)] bg-[var(--bg-surface)] border border-[var(--border-color)] px-2 py-0.5 rounded-md font-mono"> <span className="text-[10px] text-[var(--text-dim)] bg-[var(--bg-surface)] border border-[var(--border-color)] px-1.5 py-0.2 rounded font-mono">
{node.docCount} docs {node.docCount} docs
</span> </span>
)} )}
</Link> </Link>
{node.children.length > 0 && ( {node.children.length > 0 && (
<div className="ml-4 border-l border-[var(--border-color)] pl-3 mt-1 space-y-1"> <div className="ml-3.5 border-l border-[var(--border-color)] pl-2.5 mt-0.5 space-y-0.5">
{renderTree(node.children, pathname)} {renderTree(node.children, pathname)}
</div> </div>
)} )}
@@ -137,44 +133,42 @@ export default async function SectionIndexPage({
const tree = buildFolderTree(allDocs, section); const tree = buildFolderTree(allDocs, section);
return ( return (
<div className="space-y-10"> <div className="space-y-8">
{/* Breadcrumb Navigation */} {/* Breadcrumb Navigation */}
<nav className="flex items-center gap-2 text-xs text-[var(--text-dim)]"> <nav className="flex items-center gap-2 text-xs text-[var(--text-dim)] font-mono">
<Link href="/" className="hover:text-[var(--text-primary)] transition-colors flex items-center gap-1"> <Link href="/" className="hover:text-[var(--text-primary)] transition-colors">
<span>MCPedia</span> MCPedia
</Link> </Link>
<span>/</span> <span>/</span>
<span className="text-[var(--text-muted)] font-semibold">{sectionInfo.label}</span> <span className="text-[var(--text-muted)] font-semibold font-sans">{sectionInfo.label}</span>
</nav> </nav>
{/* Hero Card */} {/* Hero Card */}
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl p-6 sm:p-7 shadow-sm"> <div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 shadow-xs">
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-5 mb-4"> <div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-3">
<div className="flex items-center gap-4"> <div className="flex items-center gap-3.5">
<div className="w-14 h-14 rounded-2xl bg-[var(--bg-elevated)] border border-[var(--border-color)] flex items-center justify-center text-3xl shadow-inner"> <div className="w-12 h-12 rounded-xl bg-[var(--bg-elevated)] border border-[var(--border-color)] flex items-center justify-center text-2xl">
{sectionInfo.icon} {sectionInfo.icon}
</div> </div>
<div> <div>
<div className="flex items-center gap-2"> <h1 className="text-2xl sm:text-3xl font-extrabold text-[var(--text-primary)] tracking-tight">
<h1 className="text-2xl sm:text-3xl font-extrabold text-[var(--text-primary)] tracking-tight"> {sectionInfo.label}
{sectionInfo.label} </h1>
</h1> <p className="text-xs text-[var(--text-muted)] font-mono uppercase tracking-wider mt-0.5">
</div> Section: <code>{section}</code>
<p className="text-xs text-[var(--text-muted)] font-mono uppercase tracking-wider mt-1">
Section Domain: <span className="text-[var(--brand)] dark:text-[var(--accent)] font-semibold">{section}</span>
</p> </p>
</div> </div>
</div> </div>
<div className="flex items-center gap-2 flex-wrap sm:flex-nowrap"> <div className="flex items-center gap-2 flex-wrap sm:flex-nowrap">
<span className="text-xs font-mono text-[var(--brand)] dark:text-[var(--accent)] bg-[var(--brand)]/10 border border-[var(--brand)]/25 px-3 py-1.5 rounded-lg font-semibold"> <span className="text-xs font-mono text-[var(--text-muted)] bg-[var(--bg-elevated)] border border-[var(--border-color)] px-2.5 py-1 rounded">
{sectionDocs.length} document{sectionDocs.length !== 1 ? "s" : ""} {sectionDocs.length} document{sectionDocs.length !== 1 ? "s" : ""}
</span> </span>
{sectionDocs.length > 0 && ( {sectionDocs.length > 0 && (
<Link <Link
href={`/${section}/export`} href={`/${section}/export`}
className="inline-flex items-center gap-1.5 text-xs bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] px-3 py-1.5 rounded-lg font-semibold transition-all shadow-xs" className="inline-flex items-center gap-1.5 text-xs bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-primary)] px-3 py-1 rounded-md font-semibold transition-all shadow-xs"
title={`Export all ${sectionInfo.label} documents to publication-grade PDF`} title={`Export all ${sectionInfo.label} documents to publication-grade PDF`}
> >
<FileDown className="w-3.5 h-3.5" /> <FileDown className="w-3.5 h-3.5" />
@@ -184,7 +178,7 @@ export default async function SectionIndexPage({
<Link <Link
href="/create" href="/create"
className="inline-flex items-center gap-1.5 text-xs bg-[var(--brand)] hover:bg-[var(--brand-hover)] text-white px-3.5 py-1.5 rounded-lg font-semibold transition-all shadow-md shadow-[var(--brand)]/20" className="inline-flex items-center gap-1.5 text-xs bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] px-3 py-1 rounded-md font-semibold transition-all shadow-xs"
> >
<Plus className="w-3.5 h-3.5" /> <Plus className="w-3.5 h-3.5" />
<span>Create Doc</span> <span>Create Doc</span>
@@ -192,18 +186,18 @@ export default async function SectionIndexPage({
</div> </div>
</div> </div>
<p className="text-xs sm:text-sm text-[var(--text-muted)] leading-relaxed max-w-2xl mt-3"> <p className="text-xs sm:text-sm text-[var(--text-muted)] leading-relaxed max-w-2xl mt-2">
{sectionInfo.desc} {sectionInfo.desc}
</p> </p>
</div> </div>
{/* Hierarchical Folder Directory */} {/* Hierarchical Folder Directory */}
{tree.length > 0 && ( {tree.length > 0 && (
<section className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl p-6 shadow-sm"> <section className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-5 shadow-xs">
<div className="flex items-center gap-2 mb-4 pb-2 border-b border-[var(--border-color)]"> <div className="flex items-center gap-2 mb-3 pb-2 border-b border-[var(--border-color)]">
<Folder className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <Folder className="w-3.5 h-3.5 text-[var(--text-muted)]" />
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono">
Folder & Chapter Directory Folder & Chapter Structure
</h2> </h2>
</div> </div>
{renderTree(tree, section)} {renderTree(tree, section)}
@@ -213,14 +207,14 @@ export default async function SectionIndexPage({
{/* Document List */} {/* Document List */}
{sectionDocs.length > 0 ? ( {sectionDocs.length > 0 ? (
<section> <section>
<div className="flex items-center gap-2 mb-4"> <div className="flex items-center gap-2 mb-3.5">
<FileText className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <FileText className="w-3.5 h-3.5 text-[var(--text-muted)]" />
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono">
All Knowledge in {sectionInfo.label} All Documents in {sectionInfo.label}
</h2> </h2>
</div> </div>
<div className="space-y-2.5"> <div className="space-y-2">
{[...sectionDocs] {[...sectionDocs]
.sort( .sort(
(a, b) => (a, b) =>
@@ -230,22 +224,20 @@ export default async function SectionIndexPage({
<Link <Link
key={d.slug} key={d.slug}
href={`/${d.slug}`} href={`/${d.slug}`}
className="group flex items-center justify-between gap-4 p-4 sm:p-5 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group flex items-center justify-between gap-4 p-4 bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-xl transition-all shadow-xs"
> >
<div className="flex items-center gap-3.5 min-w-0"> <div className="flex items-center gap-3 min-w-0">
<div className="p-2 rounded-xl bg-[var(--bg-elevated)] text-[var(--text-dim)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors shrink-0"> <FileText className="w-4 h-4 text-[var(--text-muted)] shrink-0" />
<FileText className="w-4 h-4" />
</div>
<div className="min-w-0"> <div className="min-w-0">
<h3 className="text-sm font-bold text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors truncate"> <h3 className="text-xs sm:text-sm font-semibold text-[var(--text-primary)] group-hover:underline truncate">
{d.title} {d.title}
</h3> </h3>
{d.tags.length > 0 && ( {d.tags.length > 0 && (
<div className="flex flex-wrap gap-1.5 mt-2"> <div className="flex flex-wrap gap-1 mt-1.5">
{d.tags.slice(0, 4).map((t) => ( {d.tags.slice(0, 4).map((t) => (
<span <span
key={t} key={t}
className="text-[10px] px-2 py-0.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-md text-[var(--text-muted)] font-mono" className="text-[10px] px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] font-mono"
> >
#{t} #{t}
</span> </span>
@@ -270,7 +262,7 @@ export default async function SectionIndexPage({
</div> </div>
</section> </section>
) : ( ) : (
<div className="text-center py-16 border border-[var(--border-color)] rounded-2xl bg-[var(--bg-surface)]"> <div className="text-center py-12 border border-[var(--border-color)] rounded-xl bg-[var(--bg-surface)]">
<p className="text-sm text-[var(--text-muted)]">No documents in this section yet.</p> <p className="text-sm text-[var(--text-muted)]">No documents in this section yet.</p>
</div> </div>
)} )}
+38 -47
View File
@@ -5,18 +5,12 @@ import Link from "next/link";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import { import {
Search, Search,
FileText,
Plus, Plus,
Compass,
Sparkles,
ArrowRight, ArrowRight,
BookOpen, BookOpen,
Flag, Flag,
Layers, Sparkles,
SunMoon,
Command,
X, X,
Flame,
} from "lucide-react"; } from "lucide-react";
interface Hit { interface Hit {
@@ -49,7 +43,7 @@ export default function CommandMenu() {
{ {
id: "create-doc", id: "create-doc",
title: "Create New Document", title: "Create New Document",
subtitle: "Write technical doc, writeup, or research note", subtitle: "Write technical doc, writeup, or note",
icon: Plus, icon: Plus,
href: "/create", href: "/create",
}, },
@@ -69,14 +63,14 @@ export default function CommandMenu() {
}, },
{ {
id: "browse-writeups", id: "browse-writeups",
title: "CTF & Challenge Writeups", title: "CTF & Writeups Directory",
subtitle: "Pwn, Reverse, Web, Crypto writeups and solutions", subtitle: "Pwn, Reverse, Web, Crypto writeups and solutions",
icon: Flag, icon: Flag,
href: "/writeups", href: "/writeups",
}, },
{ {
id: "browse-research", id: "browse-research",
title: "Deep-Dive Research", title: "Research & Benchmarks",
subtitle: "Architecture notes, benchmarks, experiments", subtitle: "Architecture notes, benchmarks, experiments",
icon: Sparkles, icon: Sparkles,
href: "/research", href: "/research",
@@ -160,11 +154,11 @@ export default function CommandMenu() {
<button <button
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
type="button" type="button"
className="hidden md:flex items-center gap-2.5 px-3 py-1.5 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg transition-all shadow-xs cursor-pointer group" className="hidden md:flex items-center gap-2.5 px-3 py-1.5 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md transition-all shadow-xs cursor-pointer group"
> >
<Search className="w-3.5 h-3.5 text-[var(--text-dim)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors" /> <Search className="w-3.5 h-3.5 text-[var(--text-dim)] group-hover:text-[var(--text-primary)] transition-colors" />
<span className="font-sans">Search knowledge...</span> <span className="font-sans">Search...</span>
<kbd className="ml-2 font-mono bg-[var(--bg-surface)] px-1.5 py-0.5 rounded border border-[var(--border-color)] text-[10px] text-[var(--text-dim)] shadow-xs"> <kbd className="ml-2 font-mono bg-[var(--bg-surface)] px-1.5 py-0.2 rounded border border-[var(--border-color)] text-[10px] text-[var(--text-muted)]">
⌘K ⌘K
</kbd> </kbd>
</button> </button>
@@ -175,31 +169,31 @@ export default function CommandMenu() {
<div className="fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4"> <div className="fixed inset-0 z-50 flex items-start justify-center pt-16 sm:pt-24 px-4">
{/* Backdrop */} {/* Backdrop */}
<div <div
className="fixed inset-0 bg-slate-950/60 dark:bg-black/80 backdrop-blur-md transition-opacity" className="fixed inset-0 bg-black/60 backdrop-blur-xs transition-opacity"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
/> />
{/* Omnisearch Modal */} {/* Modal */}
<div className="relative w-full max-w-2xl bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl shadow-2xl overflow-hidden animate-fade-in ring-1 ring-white/10"> <div className="relative w-full max-w-2xl bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl shadow-2xl overflow-hidden animate-fade-in">
{/* Search input bar */} {/* Search input bar */}
<div className="flex items-center gap-3 px-4 sm:px-5 py-4 border-b border-[var(--border-color)] bg-[var(--bg-elevated)]/70"> <div className="flex items-center gap-3 px-4 sm:px-5 py-3.5 border-b border-[var(--border-color)] bg-[var(--bg-elevated)]">
<Search className="w-5 h-5 text-[var(--brand)] dark:text-[var(--accent)] shrink-0" /> <Search className="w-4 h-4 text-[var(--text-muted)] shrink-0" />
<input <input
ref={inputRef} ref={inputRef}
type="text" type="text"
value={query} value={query}
onChange={(e) => setQuery(e.target.value)} onChange={(e) => setQuery(e.target.value)}
onKeyDown={handleKeyNavigation} onKeyDown={handleKeyNavigation}
placeholder="Search documents, writeups, concepts, or commands..." placeholder="Search documents, writeups, concepts, or tools..."
className="w-full bg-transparent text-[var(--text-primary)] placeholder-[var(--text-dim)] text-sm sm:text-base focus:outline-none" className="w-full bg-transparent text-[var(--text-primary)] placeholder-[var(--text-dim)] text-sm focus:outline-none"
/> />
{loading && ( {loading && (
<div className="w-4 h-4 border-2 border-[var(--brand)] border-t-transparent rounded-full animate-spin shrink-0" /> <div className="w-4 h-4 border-2 border-[var(--text-muted)] border-t-transparent rounded-full animate-spin shrink-0" />
)} )}
<button <button
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
type="button" type="button"
className="text-[11px] font-mono text-[var(--text-dim)] hover:text-[var(--text-primary)] bg-[var(--bg-surface)] px-2 py-0.5 rounded border border-[var(--border-color)]" className="text-[10px] font-mono text-[var(--text-dim)] hover:text-[var(--text-primary)] bg-[var(--bg-surface)] px-1.5 py-0.5 rounded border border-[var(--border-color)]"
> >
ESC ESC
</button> </button>
@@ -208,9 +202,9 @@ export default function CommandMenu() {
{/* Results / Quick Actions List */} {/* Results / Quick Actions List */}
<div className="max-h-[22rem] overflow-y-auto p-2"> <div className="max-h-[22rem] overflow-y-auto p-2">
{query.trim() === "" ? ( {query.trim() === "" ? (
<div className="p-2 space-y-1"> <div className="p-1 space-y-0.5">
<div className="px-3 py-1.5 text-[10px] font-mono uppercase tracking-wider text-[var(--text-dim)]"> <div className="px-2.5 py-1 text-[10px] font-mono uppercase tracking-wider text-[var(--text-muted)]">
Quick Shortcuts & Actions Quick Shortcuts
</div> </div>
{quickActions.map((action, idx) => { {quickActions.map((action, idx) => {
const Icon = action.icon; const Icon = action.icon;
@@ -220,18 +214,18 @@ export default function CommandMenu() {
key={action.id} key={action.id}
onClick={() => action.href && handleNavigate(action.href)} onClick={() => action.href && handleNavigate(action.href)}
onMouseEnter={() => setSelectedIndex(idx)} onMouseEnter={() => setSelectedIndex(idx)}
className={`flex items-center justify-between gap-3 px-3 py-2.5 rounded-xl cursor-pointer transition-all ${ className={`flex items-center justify-between gap-3 px-3 py-2 rounded-lg cursor-pointer transition-colors ${
isSelected isSelected
? "bg-[var(--brand)]/15 border border-[var(--brand)]/40 text-[var(--text-primary)] shadow-xs" ? "bg-[var(--bg-elevated)] text-[var(--text-primary)]"
: "hover:bg-[var(--bg-elevated)] text-[var(--text-secondary)] border border-transparent" : "hover:bg-[var(--bg-elevated)] text-[var(--text-secondary)]"
}`} }`}
> >
<div className="flex items-center gap-3"> <div className="flex items-center gap-2.5">
<div className={`p-2 rounded-lg ${isSelected ? "bg-[var(--brand)] text-white" : "bg-[var(--bg-elevated)] text-[var(--text-muted)]"}`}> <div className={`p-1.5 rounded ${isSelected ? "bg-[var(--brand)] text-[var(--brand-fg)]" : "bg-[var(--bg-elevated)] text-[var(--text-muted)]"}`}>
<Icon className="w-4 h-4" /> <Icon className="w-3.5 h-3.5" />
</div> </div>
<div> <div>
<div className="text-xs sm:text-sm font-semibold text-[var(--text-primary)]"> <div className="text-xs font-semibold text-[var(--text-primary)]">
{action.title} {action.title}
</div> </div>
<div className="text-[11px] text-[var(--text-muted)]"> <div className="text-[11px] text-[var(--text-muted)]">
@@ -239,17 +233,14 @@ export default function CommandMenu() {
</div> </div>
</div> </div>
</div> </div>
<ArrowRight className={`w-3.5 h-3.5 text-[var(--text-dim)] ${isSelected ? "text-[var(--brand)] translate-x-0.5" : ""} transition-all`} /> <ArrowRight className={`w-3.5 h-3.5 text-[var(--text-dim)] ${isSelected ? "text-[var(--text-primary)]" : ""}`} />
</div> </div>
); );
})} })}
</div> </div>
) : results.length === 0 && !loading ? ( ) : results.length === 0 && !loading ? (
<div className="px-4 py-12 text-center text-xs text-[var(--text-muted)] space-y-2"> <div className="px-4 py-10 text-center text-xs text-[var(--text-muted)] space-y-1">
<p>No knowledge base items matched &quot;{query}&quot;</p> <p>No knowledge base items matched &quot;{query}&quot;</p>
<p className="text-[11px] text-[var(--text-dim)]">
Try searching with partial words or check semantic search
</p>
</div> </div>
) : ( ) : (
<ul className="space-y-1 p-1"> <ul className="space-y-1 p-1">
@@ -260,24 +251,24 @@ export default function CommandMenu() {
key={hit.slug} key={hit.slug}
onClick={() => handleNavigate(`/${hit.slug}`)} onClick={() => handleNavigate(`/${hit.slug}`)}
onMouseEnter={() => setSelectedIndex(index)} onMouseEnter={() => setSelectedIndex(index)}
className={`flex items-start justify-between gap-3 px-3 py-2.5 rounded-xl cursor-pointer transition-all ${ className={`flex items-start justify-between gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-colors ${
isSelected isSelected
? "bg-[var(--brand)]/15 border border-[var(--brand)]/40 text-[var(--text-primary)] shadow-xs" ? "bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)]"
: "hover:bg-[var(--bg-elevated)] text-[var(--text-secondary)] border border-transparent" : "hover:bg-[var(--bg-elevated)] text-[var(--text-secondary)] border border-transparent"
}`} }`}
> >
<div className="min-w-0 flex-1"> <div className="min-w-0 flex-1">
<div className="flex items-center gap-2 mb-0.5"> <div className="flex items-center gap-2 mb-0.5">
<span className="text-[10px] font-mono px-2 py-0.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--brand)] dark:text-[var(--accent)] uppercase font-semibold"> <span className="text-[10px] font-mono px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] uppercase font-semibold">
{hit.section} {hit.section}
</span> </span>
<span className={`text-xs sm:text-sm font-semibold ${isSelected ? "text-[var(--brand)] dark:text-[var(--accent)]" : "text-[var(--text-primary)]"} truncate`}> <span className={`text-xs sm:text-sm font-semibold ${isSelected ? "text-[var(--text-primary)]" : "text-[var(--text-primary)]"} truncate`}>
{hit.title} {hit.title}
</span> </span>
</div> </div>
{hit.snippet && ( {hit.snippet && (
<p <p
className="text-xs text-[var(--text-muted)] line-clamp-1 mt-1 [&>mark]:bg-[var(--brand)]/30 [&>mark]:text-[var(--text-primary)] [&>mark]:rounded [&>mark]:px-0.5" className="text-xs text-[var(--text-muted)] line-clamp-1 mt-1 [&>mark]:bg-[var(--bg-elevated-hover)] [&>mark]:text-[var(--text-primary)] [&>mark]:font-semibold"
dangerouslySetInnerHTML={{ __html: hit.snippet }} dangerouslySetInnerHTML={{ __html: hit.snippet }}
/> />
)} )}
@@ -293,15 +284,15 @@ export default function CommandMenu() {
</div> </div>
{/* Footer */} {/* Footer */}
<div className="flex items-center justify-between px-4 py-2.5 border-t border-[var(--border-color)] bg-[var(--bg-elevated)]/60 text-[11px] text-[var(--text-dim)]"> <div className="flex items-center justify-between px-4 py-2 border-t border-[var(--border-color)] bg-[var(--bg-elevated)] text-[10px] text-[var(--text-muted)] font-mono">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span>Navigate: <kbd className="bg-[var(--bg-surface)] px-1 py-0.5 rounded border border-[var(--border-color)]">↑↓</kbd></span> <span>[ ↑↓ NAVIGATE ]</span>
<span>Select: <kbd className="bg-[var(--bg-surface)] px-1 py-0.5 rounded border border-[var(--border-color)]">↵</kbd></span> <span>[ ↵ SELECT ]</span>
</div> </div>
<Link <Link
href={`/search?q=${encodeURIComponent(query)}`} href={`/search?q=${encodeURIComponent(query)}`}
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
className="text-[var(--brand)] dark:text-[var(--accent)] hover:underline font-medium flex items-center gap-1" className="hover:text-[var(--text-primary)] font-medium flex items-center gap-1"
> >
<span>Full Search Console</span> <span>Full Search Console</span>
<ArrowRight className="w-3 h-3" /> <ArrowRight className="w-3 h-3" />
+9 -10
View File
@@ -8,7 +8,6 @@ import {
Share2, Share2,
FileDown, FileDown,
Edit3, Edit3,
ExternalLink,
} from "lucide-react"; } from "lucide-react";
interface DocActionsProps { interface DocActionsProps {
@@ -39,13 +38,13 @@ export default function DocActions({ slug, body, canEdit }: DocActionsProps) {
<button <button
onClick={handleCopyMarkdown} onClick={handleCopyMarkdown}
type="button" type="button"
title="Copy raw markdown content to clipboard" title="Copy raw markdown content"
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg transition-all shadow-xs cursor-pointer" className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md transition-colors shadow-xs cursor-pointer"
> >
{copiedMd ? ( {copiedMd ? (
<> <>
<Check className="w-3.5 h-3.5 text-emerald-500" /> <Check className="w-3.5 h-3.5 text-emerald-500" />
<span className="text-emerald-500 font-medium">Copied MD</span> <span className="text-emerald-500 font-medium">Copied</span>
</> </>
) : ( ) : (
<> <>
@@ -60,12 +59,12 @@ export default function DocActions({ slug, body, canEdit }: DocActionsProps) {
onClick={handleCopyLink} onClick={handleCopyLink}
type="button" type="button"
title="Copy link to document" title="Copy link to document"
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg transition-all shadow-xs cursor-pointer" className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md transition-colors shadow-xs cursor-pointer"
> >
{copiedLink ? ( {copiedLink ? (
<> <>
<Check className="w-3.5 h-3.5 text-emerald-500" /> <Check className="w-3.5 h-3.5 text-emerald-500" />
<span className="text-emerald-500 font-medium">Copied Link</span> <span className="text-emerald-500 font-medium">Copied</span>
</> </>
) : ( ) : (
<> <>
@@ -78,17 +77,17 @@ export default function DocActions({ slug, body, canEdit }: DocActionsProps) {
{/* Export PDF */} {/* Export PDF */}
<Link <Link
href={`/${slug}/export`} href={`/${slug}/export`}
title="Export document or parent folder to publication-grade PDF" title="Export document to publication-grade PDF"
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg transition-all shadow-xs" className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md transition-colors shadow-xs"
> >
<FileDown className="w-3.5 h-3.5" /> <FileDown className="w-3.5 h-3.5" />
<span>Export PDF</span> <span>PDF</span>
</Link> </Link>
{/* Edit Link */} {/* Edit Link */}
<Link <Link
href={canEdit ? `/${slug}?edit=1` : `/login?redirect=/${slug}`} href={canEdit ? `/${slug}?edit=1` : `/login?redirect=/${slug}`}
className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs bg-[var(--brand)]/10 hover:bg-[var(--brand)]/20 border border-[var(--brand)]/30 hover:border-[var(--brand)]/60 text-[var(--brand)] dark:text-[var(--accent)] rounded-lg transition-all font-medium shadow-xs" className="inline-flex items-center gap-1.5 px-2.5 py-1 text-xs bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-primary)] rounded-md transition-colors font-medium shadow-xs"
> >
<Edit3 className="w-3.5 h-3.5" /> <Edit3 className="w-3.5 h-3.5" />
<span>Edit</span> <span>Edit</span>
+33 -57
View File
@@ -10,18 +10,10 @@ import {
Heading3, Heading3,
Code, Code,
Link as LinkIcon, Link as LinkIcon,
Table as TableIcon,
Info,
Lightbulb,
AlertTriangle,
Plus, Plus,
Trash2, Trash2,
Check,
Eye, Eye,
Edit3, Edit3,
FileCode,
Tag,
Folder,
Sliders, Sliders,
} from "lucide-react"; } from "lucide-react";
@@ -127,7 +119,7 @@ export default function DocForm({
const availableFolders = existingFolders?.[section] ?? []; const availableFolders = existingFolders?.[section] ?? [];
const baseInputCls = const baseInputCls =
"w-full px-3.5 py-2.5 bg-[var(--bg-surface)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl text-sm text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-2 focus:ring-[var(--brand)]/20 transition-all"; "w-full px-3.5 py-2 bg-[var(--bg-surface)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-lg text-sm text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--text-primary)] transition-colors";
function addCustomField(key = "", value = "") { function addCustomField(key = "", value = "") {
setCustomFields([...customFields, { key, value }]); setCustomFields([...customFields, { key, value }]);
@@ -229,7 +221,7 @@ export default function DocForm({
return ( return (
<form onSubmit={handleSubmit} className="space-y-6 max-w-4xl"> <form onSubmit={handleSubmit} className="space-y-6 max-w-4xl">
{error && ( {error && (
<div className="p-4 rounded-xl border border-rose-500/30 bg-rose-500/10 text-rose-500 dark:text-rose-400 text-xs sm:text-sm font-medium animate-fade-in"> <div className="p-3.5 rounded-lg border border-rose-500/30 bg-rose-500/10 text-rose-500 dark:text-rose-400 text-xs font-medium animate-fade-in">
{error} {error}
</div> </div>
)} )}
@@ -244,7 +236,7 @@ export default function DocForm({
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
placeholder="e.g. Exploiting Stack Alignment Issues in x86_64" placeholder="e.g. Exploiting Stack Alignment Issues in x86_64"
className={`${baseInputCls} text-base sm:text-lg font-semibold`} className={`${baseInputCls} text-base font-semibold`}
required required
/> />
</div> </div>
@@ -339,7 +331,7 @@ export default function DocForm({
type="text" type="text"
value={slug ?? ""} value={slug ?? ""}
readOnly readOnly
className="w-full px-3.5 py-2.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-xl text-sm text-[var(--text-muted)] font-mono" className="w-full px-3.5 py-2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-lg text-sm text-[var(--text-muted)] font-mono"
/> />
) : ( ) : (
<input <input
@@ -352,7 +344,7 @@ export default function DocForm({
/> />
)} )}
<p className="text-[11px] text-[var(--text-dim)] mt-1.5 font-mono"> <p className="text-[11px] text-[var(--text-dim)] mt-1.5 font-mono">
Full Slug: <span className="text-[var(--brand)] dark:text-[var(--accent)] font-semibold">{effectiveSlug || section}</span> Full Slug: <span className="text-[var(--text-primary)] font-semibold">{effectiveSlug || section}</span>
</p> </p>
</div> </div>
</div> </div>
@@ -386,26 +378,26 @@ export default function DocForm({
</div> </div>
{/* Custom Metadata Fields */} {/* Custom Metadata Fields */}
<div className="p-4 sm:p-5 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl space-y-4 shadow-sm"> <div className="p-4 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl space-y-3.5 shadow-xs">
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div> <div>
<h3 className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] font-mono flex items-center gap-1.5"> <h3 className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] font-mono flex items-center gap-1.5">
<Sliders className="w-3.5 h-3.5 text-[var(--brand)] dark:text-[var(--accent)]" /> <Sliders className="w-3.5 h-3.5 text-[var(--text-muted)]" />
<span>Dynamic Technical Metadata</span> <span>Technical Metadata</span>
</h3> </h3>
<p className="text-[11px] text-[var(--text-dim)] mt-0.5"> <p className="text-[11px] text-[var(--text-dim)] mt-0.5">
Custom fields (points, difficulty, event, solved) will render as styled badges in the document hero. Custom fields (points, difficulty, event, solved) will render as styled badges in the document hero.
</p> </p>
</div> </div>
<div className="flex items-center gap-1.5 flex-wrap"> <div className="flex items-center gap-1 flex-wrap">
<span className="text-[10px] font-mono text-[var(--text-dim)] uppercase">Presets:</span> <span className="text-[10px] font-mono text-[var(--text-dim)] uppercase">Presets:</span>
{PRESET_FIELDS.map((preset) => ( {PRESET_FIELDS.map((preset) => (
<button <button
key={preset.key} key={preset.key}
type="button" type="button"
onClick={() => addCustomField(preset.key, preset.value)} onClick={() => addCustomField(preset.key, preset.value)}
className="px-2 py-0.5 text-[11px] bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md transition-colors font-mono cursor-pointer" className="px-2 py-0.5 text-[10px] bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded transition-colors font-mono cursor-pointer"
> >
+{preset.key} +{preset.key}
</button> </button>
@@ -432,7 +424,7 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => removeCustomField(i)} onClick={() => removeCustomField(i)}
className="p-2 text-rose-500 hover:text-rose-600 rounded-lg hover:bg-[var(--bg-elevated)] transition-colors cursor-pointer" className="p-2 text-rose-500 hover:text-rose-600 rounded hover:bg-[var(--bg-elevated)] transition-colors cursor-pointer"
title="Remove field" title="Remove field"
> >
<Trash2 className="w-4 h-4" /> <Trash2 className="w-4 h-4" />
@@ -443,7 +435,7 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => addCustomField()} onClick={() => addCustomField()}
className="inline-flex items-center gap-1.5 text-xs text-[var(--brand)] dark:text-[var(--accent)] hover:underline px-3 py-1.5 rounded-lg border border-[var(--brand)]/30 hover:border-[var(--brand)]/60 bg-[var(--brand)]/5 transition-colors font-medium cursor-pointer" className="inline-flex items-center gap-1.5 text-xs text-[var(--text-primary)] hover:underline px-3 py-1 rounded-md border border-[var(--border-color)] bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] transition-colors font-medium cursor-pointer"
> >
<Plus className="w-3.5 h-3.5" /> <Plus className="w-3.5 h-3.5" />
<span>Add Custom Field</span> <span>Add Custom Field</span>
@@ -451,17 +443,17 @@ export default function DocForm({
</div> </div>
{/* Markdown Body Editor */} {/* Markdown Body Editor */}
<div className="border border-[var(--border-color)] rounded-2xl overflow-hidden bg-[var(--bg-surface)] shadow-sm"> <div className="border border-[var(--border-color)] rounded-xl overflow-hidden bg-[var(--bg-surface)] shadow-xs">
{/* Editor Toolbar */} {/* Editor Toolbar */}
<div className="flex items-center justify-between px-3 py-2 bg-[var(--bg-elevated)]/60 border-b border-[var(--border-color)] flex-wrap gap-2"> <div className="flex items-center justify-between px-3 py-2 bg-[var(--bg-elevated)] border-b border-[var(--border-color)] flex-wrap gap-2">
{/* Tabs */} {/* Tabs */}
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
<button <button
type="button" type="button"
onClick={() => setActiveTab("write")} onClick={() => setActiveTab("write")}
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-semibold transition-all cursor-pointer ${ className={`flex items-center gap-1.5 px-3 py-1 text-xs rounded font-semibold transition-all cursor-pointer ${
activeTab === "write" activeTab === "write"
? "bg-[var(--brand)] text-white shadow-xs" ? "bg-[var(--brand)] text-[var(--brand-fg)] shadow-xs"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)]"
}`} }`}
> >
@@ -471,14 +463,14 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => setActiveTab("preview")} onClick={() => setActiveTab("preview")}
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-lg font-semibold transition-all cursor-pointer ${ className={`flex items-center gap-1.5 px-3 py-1 text-xs rounded font-semibold transition-all cursor-pointer ${
activeTab === "preview" activeTab === "preview"
? "bg-[var(--brand)] text-white shadow-xs" ? "bg-[var(--brand)] text-[var(--brand-fg)] shadow-xs"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)]"
}`} }`}
> >
<Eye className="w-3.5 h-3.5" /> <Eye className="w-3.5 h-3.5" />
<span>Live Preview</span> <span>Preview</span>
</button> </button>
</div> </div>
@@ -488,23 +480,23 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => insertMarkdown("**", "**")} onClick={() => insertMarkdown("**", "**")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)]" className="p-1 hover:bg-[var(--bg-surface)] rounded hover:text-[var(--text-primary)]"
title="Bold (**text**)" title="Bold"
> >
<Bold className="w-3.5 h-3.5" /> <Bold className="w-3.5 h-3.5" />
</button> </button>
<button <button
type="button" type="button"
onClick={() => insertMarkdown("*", "*")} onClick={() => insertMarkdown("*", "*")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)]" className="p-1 hover:bg-[var(--bg-surface)] rounded hover:text-[var(--text-primary)]"
title="Italic (*text*)" title="Italic"
> >
<Italic className="w-3.5 h-3.5" /> <Italic className="w-3.5 h-3.5" />
</button> </button>
<button <button
type="button" type="button"
onClick={() => insertMarkdown("## ")} onClick={() => insertMarkdown("## ")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)]" className="p-1 hover:bg-[var(--bg-surface)] rounded hover:text-[var(--text-primary)]"
title="Heading 2" title="Heading 2"
> >
<Heading2 className="w-3.5 h-3.5" /> <Heading2 className="w-3.5 h-3.5" />
@@ -512,7 +504,7 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => insertMarkdown("### ")} onClick={() => insertMarkdown("### ")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)]" className="p-1 hover:bg-[var(--bg-surface)] rounded hover:text-[var(--text-primary)]"
title="Heading 3" title="Heading 3"
> >
<Heading3 className="w-3.5 h-3.5" /> <Heading3 className="w-3.5 h-3.5" />
@@ -520,7 +512,7 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => insertMarkdown("```\n", "\n```")} onClick={() => insertMarkdown("```\n", "\n```")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)]" className="p-1 hover:bg-[var(--bg-surface)] rounded hover:text-[var(--text-primary)]"
title="Code Block" title="Code Block"
> >
<Code className="w-3.5 h-3.5" /> <Code className="w-3.5 h-3.5" />
@@ -528,27 +520,11 @@ export default function DocForm({
<button <button
type="button" type="button"
onClick={() => insertMarkdown("[", "](https://)")} onClick={() => insertMarkdown("[", "](https://)")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)]" className="p-1 hover:bg-[var(--bg-surface)] rounded hover:text-[var(--text-primary)]"
title="Link" title="Link"
> >
<LinkIcon className="w-3.5 h-3.5" /> <LinkIcon className="w-3.5 h-3.5" />
</button> </button>
<button
type="button"
onClick={() => insertMarkdown("> [!NOTE]\n> ")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)] text-indigo-500"
title="Note Callout"
>
<Info className="w-3.5 h-3.5" />
</button>
<button
type="button"
onClick={() => insertMarkdown("> [!TIP]\n> ")}
className="p-1.5 hover:bg-[var(--bg-surface)] rounded-md hover:text-[var(--text-primary)] text-emerald-500"
title="Tip Callout"
>
<Lightbulb className="w-3.5 h-3.5" />
</button>
</div> </div>
)} )}
</div> </div>
@@ -559,7 +535,7 @@ export default function DocForm({
ref={textareaRef} ref={textareaRef}
value={body} value={body}
onChange={(e) => setBody(e.target.value)} onChange={(e) => setBody(e.target.value)}
className="w-full h-96 p-4 sm:p-5 bg-transparent text-[var(--text-primary)] placeholder-[var(--text-dim)] font-mono text-sm focus:outline-none resize-y leading-relaxed" className="w-full h-96 p-4 bg-transparent text-[var(--text-primary)] placeholder-[var(--text-dim)] font-mono text-sm focus:outline-none resize-y leading-relaxed"
placeholder="# Write your technical documentation or writeup here in Markdown..." placeholder="# Write your technical documentation or writeup here in Markdown..."
required required
/> />
@@ -568,7 +544,7 @@ export default function DocForm({
{body.trim() ? ( {body.trim() ? (
<Markdown source={body} /> <Markdown source={body} />
) : ( ) : (
<p className="text-xs text-[var(--text-dim)] italic">No content to preview yet.</p> <p className="text-xs text-[var(--text-dim)] italic font-mono">No content to preview yet.</p>
)} )}
</div> </div>
)} )}
@@ -579,18 +555,18 @@ export default function DocForm({
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="px-6 py-2.5 bg-[var(--brand)] hover:bg-[var(--brand-hover)] text-white rounded-xl font-semibold text-sm transition-all shadow-md shadow-[var(--brand)]/20 hover:shadow-[var(--brand)]/35 disabled:opacity-50 flex items-center gap-2 cursor-pointer" className="px-6 py-2.5 bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] rounded-lg font-semibold text-xs transition-all shadow-xs disabled:opacity-50 flex items-center gap-2 cursor-pointer font-mono uppercase tracking-wider"
> >
{loading && ( {loading && (
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-current border-t-transparent rounded-full animate-spin" />
)} )}
<span>{loading ? "Persisting Document..." : mode === "create" ? "Publish to PostgreSQL" : "Save Changes"}</span> <span>{loading ? "PERSISTING..." : mode === "create" ? "PUBLISH_DOCUMENT" : "SAVE_CHANGES"}</span>
</button> </button>
<button <button
type="button" type="button"
onClick={() => router.back()} onClick={() => router.back()}
className="px-4 py-2.5 border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-muted)] hover:text-[var(--text-primary)] bg-[var(--bg-surface)] rounded-xl text-sm transition-colors shadow-xs cursor-pointer" className="px-4 py-2.5 border border-[var(--border-color)] hover:border-[var(--text-muted)] text-[var(--text-muted)] hover:text-[var(--text-primary)] bg-[var(--bg-surface)] rounded-lg text-xs transition-colors shadow-xs cursor-pointer font-mono"
> >
Cancel Cancel
</button> </button>
+27 -38
View File
@@ -7,16 +7,11 @@ import { DEFAULT_SECTIONS, type SectionConfig } from "@mcpedia/config/sections";
import ThemeToggle from "@/components/ThemeToggle"; import ThemeToggle from "@/components/ThemeToggle";
import CommandMenu from "@/components/CommandMenu"; import CommandMenu from "@/components/CommandMenu";
import { import {
Sparkles,
Plus, Plus,
Search, Search,
Menu, Menu,
X, X,
Lock, Lock,
Layers,
FileText,
Activity,
Terminal,
} from "lucide-react"; } from "lucide-react";
export default function Header() { export default function Header() {
@@ -37,45 +32,39 @@ export default function Header() {
return ( return (
<header className="sticky top-0 z-40 border-b border-[var(--border-color)] glass-nav transition-all"> <header className="sticky top-0 z-40 border-b border-[var(--border-color)] glass-nav transition-all">
<div className="mx-auto max-w-7xl px-4 sm:px-6 h-15 flex items-center justify-between gap-3 sm:gap-4"> <div className="mx-auto max-w-7xl px-4 sm:px-6 h-14 flex items-center justify-between gap-3 sm:gap-4">
{/* Left: Brand Identity */} {/* Brand */}
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<Link href="/" className="flex items-center gap-2.5 group"> <Link href="/" className="flex items-center gap-2.5 group">
<div className="relative flex items-center justify-center w-8 h-8 rounded-xl bg-gradient-to-br from-indigo-500 via-indigo-600 to-violet-700 text-white shadow-lg shadow-indigo-500/25 group-hover:scale-105 group-hover:shadow-indigo-500/40 transition-all"> <div className="flex items-center justify-center w-7 h-7 rounded-md bg-[var(--brand)] text-[var(--brand-fg)] font-mono font-bold text-xs shadow-xs group-hover:opacity-90 transition-opacity">
<span className="font-mono font-bold text-sm tracking-tighter">M</span> M
<span className="absolute -bottom-0.5 -right-0.5 w-2 h-2 rounded-full bg-emerald-400 ring-2 ring-[var(--bg-app)] animate-pulse" />
</div> </div>
<div className="flex flex-col"> <div className="flex items-center gap-2">
<span className="font-bold text-[var(--text-primary)] text-base tracking-tight leading-none group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors"> <span className="font-bold text-[var(--text-primary)] text-sm tracking-tight leading-none">
MCPedia MCPedia
</span> </span>
<span className="text-[10px] font-mono text-[var(--text-dim)] uppercase tracking-wider leading-tight mt-0.5 hidden sm:block"> <span className="text-[10px] font-mono px-1.5 py-0.2 rounded bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] hidden sm:inline-block">
AI Knowledge Hub CORE::DB
</span> </span>
</div> </div>
</Link> </Link>
<div className="hidden xl:flex items-center gap-1.5 ml-2 px-2 py-0.5 rounded-full bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[10px] font-mono text-[var(--text-dim)]">
<Activity className="w-3 h-3 text-emerald-500" />
<span>MCP :4021</span>
</div>
</div> </div>
{/* Center: Desktop Dynamic Sections Nav */} {/* Center: Desktop Sections Nav */}
<nav className="hidden md:flex items-center gap-1 bg-[var(--bg-elevated)]/60 p-1 rounded-xl border border-[var(--border-color)]"> <nav className="hidden md:flex items-center gap-1">
{sections.map((s) => { {sections.map((s) => {
const isActive = pathname === `/${s.id}` || pathname.startsWith(`/${s.id}/`); const isActive = pathname === `/${s.id}` || pathname.startsWith(`/${s.id}/`);
return ( return (
<Link <Link
key={s.id} key={s.id}
href={`/${s.id}`} href={`/${s.id}`}
className={`px-3 py-1 rounded-lg text-xs font-medium transition-all flex items-center gap-1.5 ${ className={`px-3 py-1 rounded-md text-xs font-medium transition-colors flex items-center gap-1.5 ${
isActive isActive
? "text-[var(--text-primary)] bg-[var(--bg-surface)] shadow-xs font-semibold border border-[var(--border-color)]" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)] border border-[var(--border-color)] font-semibold"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-surface)]/50" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
}`} }`}
> >
<span className="text-xs">{s.icon}</span> <span>{s.icon}</span>
<span>{s.label}</span> <span>{s.label}</span>
</Link> </Link>
); );
@@ -88,7 +77,7 @@ export default function Header() {
<Link <Link
href="/search" href="/search"
className="md:hidden p-2 text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg bg-[var(--bg-surface)] border border-[var(--border-color)] transition-colors" className="md:hidden p-1.5 text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md bg-[var(--bg-surface)] border border-[var(--border-color)] transition-colors"
title="Search knowledge base" title="Search knowledge base"
> >
<Search className="w-4 h-4" /> <Search className="w-4 h-4" />
@@ -96,7 +85,7 @@ export default function Header() {
<Link <Link
href="/create" href="/create"
className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1.5 bg-[var(--brand)] hover:bg-[var(--brand-hover)] text-white text-xs font-medium rounded-lg shadow-md shadow-[var(--brand)]/20 hover:shadow-[var(--brand)]/35 transition-all cursor-pointer" className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1.5 bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] text-xs font-semibold rounded-md shadow-xs transition-all cursor-pointer"
> >
<Plus className="w-3.5 h-3.5" /> <Plus className="w-3.5 h-3.5" />
<span>New Doc</span> <span>New Doc</span>
@@ -104,7 +93,7 @@ export default function Header() {
<Link <Link
href="/login" href="/login"
className={`p-1.5 sm:px-2.5 sm:py-1.5 text-xs rounded-lg transition-colors flex items-center gap-1.5 ${ className={`p-1.5 sm:px-2.5 sm:py-1.5 text-xs rounded-md transition-colors flex items-center gap-1.5 ${
pathname === "/login" pathname === "/login"
? "text-[var(--text-primary)] bg-[var(--bg-elevated)] font-semibold border border-[var(--border-color)]" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)] font-semibold border border-[var(--border-color)]"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
@@ -119,10 +108,10 @@ export default function Header() {
<ThemeToggle /> <ThemeToggle />
</div> </div>
{/* Mobile menu hamburger toggle */} {/* Mobile menu toggle */}
<button <button
onClick={() => setMobileMenuOpen(!mobileMenuOpen)} onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
className="md:hidden p-2 text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg hover:bg-[var(--bg-elevated)] transition-colors" className="md:hidden p-1.5 text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-md hover:bg-[var(--bg-elevated)] transition-colors"
aria-label="Toggle navigation" aria-label="Toggle navigation"
> >
{mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />} {mobileMenuOpen ? <X className="w-5 h-5" /> : <Menu className="w-5 h-5" />}
@@ -132,9 +121,9 @@ export default function Header() {
{/* Mobile Drawer */} {/* Mobile Drawer */}
{mobileMenuOpen && ( {mobileMenuOpen && (
<div className="md:hidden border-t border-[var(--border-color)] bg-[var(--bg-surface)] px-4 py-4 space-y-2 animate-fade-in shadow-xl"> <div className="md:hidden border-t border-[var(--border-color)] bg-[var(--bg-surface)] px-4 py-4 space-y-2 animate-fade-in shadow-lg">
<div className="text-[11px] font-mono uppercase tracking-wider text-[var(--text-dim)] px-2 mb-1"> <div className="text-[10px] font-mono uppercase tracking-wider text-[var(--text-muted)] px-1 mb-1">
Sections Knowledge Sections
</div> </div>
<div className="grid grid-cols-2 gap-1.5"> <div className="grid grid-cols-2 gap-1.5">
{sections.map((s) => { {sections.map((s) => {
@@ -144,10 +133,10 @@ export default function Header() {
key={s.id} key={s.id}
href={`/${s.id}`} href={`/${s.id}`}
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className={`flex items-center gap-2 px-3 py-2 rounded-lg text-xs font-medium transition-colors ${ className={`flex items-center gap-2 px-3 py-2 rounded-md text-xs font-medium transition-colors ${
isActive isActive
? "bg-[var(--brand)]/15 text-[var(--brand)] dark:text-[var(--accent)] border border-[var(--brand)]/30 font-semibold" ? "bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)] font-semibold"
: "text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:text-[var(--text-primary)] border border-transparent" : "text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:text-[var(--text-primary)]"
}`} }`}
> >
<span>{s.icon}</span> <span>{s.icon}</span>
@@ -161,7 +150,7 @@ export default function Header() {
<Link <Link
href="/search" href="/search"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className="flex-1 flex items-center justify-center gap-1.5 py-2 text-xs bg-[var(--bg-elevated)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-lg font-medium border border-[var(--border-color)]" className="flex-1 flex items-center justify-center gap-1.5 py-2 text-xs bg-[var(--bg-elevated)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-md font-medium border border-[var(--border-color)]"
> >
<Search className="w-3.5 h-3.5" /> <Search className="w-3.5 h-3.5" />
<span>Search</span> <span>Search</span>
@@ -169,7 +158,7 @@ export default function Header() {
<Link <Link
href="/create" href="/create"
onClick={() => setMobileMenuOpen(false)} onClick={() => setMobileMenuOpen(false)}
className="flex-1 flex items-center justify-center gap-1.5 py-2 text-xs bg-[var(--brand)] text-white rounded-lg font-medium shadow-sm" className="flex-1 flex items-center justify-center gap-1.5 py-2 text-xs bg-[var(--brand)] text-[var(--brand-fg)] rounded-md font-semibold shadow-xs"
> >
<Plus className="w-3.5 h-3.5" /> <Plus className="w-3.5 h-3.5" />
<span>Create Doc</span> <span>Create Doc</span>
+75 -78
View File
@@ -9,12 +9,9 @@ import {
Sparkles, Sparkles,
Search, Search,
Globe, Globe,
Terminal,
Server, Server,
ArrowRight, ArrowRight,
CheckCircle2, CheckCircle2,
FileCode2,
ShieldCheck,
} from "lucide-react"; } from "lucide-react";
interface NodeDetail { interface NodeDetail {
@@ -33,7 +30,7 @@ const NODES: NodeDetail[] = [
{ {
id: "ai-agents", id: "ai-agents",
title: "AI Coding Agents", title: "AI Coding Agents",
badge: "MCP Consumer", badge: "MCP::CLIENT",
type: "client", type: "client",
icon: Sparkles, icon: Sparkles,
desc: "Claude Desktop, Cursor, Antigravity, and Zed connect via Model Context Protocol to read and write technical knowledge directly.", desc: "Claude Desktop, Cursor, Antigravity, and Zed connect via Model Context Protocol to read and write technical knowledge directly.",
@@ -52,10 +49,10 @@ const NODES: NodeDetail[] = [
{ {
id: "web-ui", id: "web-ui",
title: "Human Web Interface", title: "Human Web Interface",
badge: "Next.js 16 + React 19", badge: "UI::NEXTJS",
type: "client", type: "client",
icon: Globe, icon: Globe,
desc: "Fast, accessible, and responsive developer web interface with dark/light themes, keyboard shortcuts, and instant search.", desc: "Fast, accessible developer web interface with dark/light themes, keyboard shortcuts, and instant search.",
metrics: [ metrics: [
{ label: "Framework", value: "Next.js 16.3" }, { label: "Framework", value: "Next.js 16.3" },
{ label: "Styling", value: "Tailwind CSS v4" }, { label: "Styling", value: "Tailwind CSS v4" },
@@ -71,10 +68,10 @@ const NODES: NodeDetail[] = [
{ {
id: "mcp-server", id: "mcp-server",
title: "MCP Server Gateway", title: "MCP Server Gateway",
badge: "Streamable HTTP :4021", badge: "PORT::4021",
type: "protocol", type: "protocol",
icon: Server, icon: Server,
desc: "High-throughput streamable Model Context Protocol server exposing tool endpoints and resource URIs.", desc: "Streamable Model Context Protocol server exposing tool endpoints and resource URIs.",
metrics: [ metrics: [
{ label: "Port", value: ":4021" }, { label: "Port", value: ":4021" },
{ label: "Protocol", value: "MCP 2024-11-05" }, { label: "Protocol", value: "MCP 2024-11-05" },
@@ -90,7 +87,7 @@ const NODES: NodeDetail[] = [
{ {
id: "core-engine", id: "core-engine",
title: "@mcpedia/core", title: "@mcpedia/core",
badge: "Single Source of Truth", badge: "CORE::PACKAGE",
type: "core", type: "core",
icon: Layers, icon: Layers,
desc: "Unified business logic layer shared across Web, MCP Server, REST API, and Background Workers.", desc: "Unified business logic layer shared across Web, MCP Server, REST API, and Background Workers.",
@@ -109,10 +106,10 @@ const NODES: NodeDetail[] = [
{ {
id: "search-engine", id: "search-engine",
title: "Multi-Modal Search Engine", title: "Multi-Modal Search Engine",
badge: "RRF Algorithm", badge: "SEARCH::RRF",
type: "core", type: "core",
icon: Search, icon: Search,
desc: "Reciprocal Rank Fusion uniting Postgres Full-Text Search tsvectors with OpenAI/OpenRouter vector embeddings.", desc: "Reciprocal Rank Fusion uniting Postgres Full-Text Search tsvectors with vector embeddings.",
metrics: [ metrics: [
{ label: "FTS Algorithm", value: "tsvector + GIN" }, { label: "FTS Algorithm", value: "tsvector + GIN" },
{ label: "Vector Search", value: "Cosine Similarity" }, { label: "Vector Search", value: "Cosine Similarity" },
@@ -128,7 +125,7 @@ const NODES: NodeDetail[] = [
{ {
id: "postgres-db", id: "postgres-db",
title: "PostgreSQL 16 + pgvector", title: "PostgreSQL 16 + pgvector",
badge: "Primary Storage", badge: "DB::POSTGRES",
type: "storage", type: "storage",
icon: Database, icon: Database,
desc: "ACID-compliant relational database storing document bodies, revision trees, vector embeddings, and full-text indexes.", desc: "ACID-compliant relational database storing document bodies, revision trees, vector embeddings, and full-text indexes.",
@@ -153,38 +150,38 @@ export default function InteractiveTopology() {
const ActiveIcon = activeNode.icon; const ActiveIcon = activeNode.icon;
return ( return (
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl overflow-hidden shadow-sm"> <div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl overflow-hidden shadow-xs">
{/* Header Bar */} {/* Header Bar */}
<div className="p-5 sm:p-6 border-b border-[var(--border-color)] bg-[var(--bg-elevated)]/50 flex flex-col sm:flex-row sm:items-center justify-between gap-4"> <div className="p-4 sm:p-5 border-b border-[var(--border-color)] bg-[var(--bg-elevated)] flex flex-col sm:flex-row sm:items-center justify-between gap-3">
<div> <div>
<div className="flex items-center gap-2 mb-1.5"> <div className="flex items-center gap-2 mb-1">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
<span className="text-[11px] font-mono uppercase tracking-wider text-[var(--text-muted)]"> <span className="text-[10px] font-mono uppercase tracking-wider text-[var(--text-muted)]">
Interactive System Topology Architecture Schematic
</span> </span>
</div> </div>
<h3 className="text-xl font-bold text-[var(--text-primary)] tracking-tight"> <h3 className="text-base sm:text-lg font-bold text-[var(--text-primary)] tracking-tight">
How MCPedia Unifies Humans & AI Agents System Topology: Single Core Architecture
</h3> </h3>
</div> </div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs font-mono text-[var(--text-muted)] bg-[var(--bg-surface)] border border-[var(--border-color)] px-2.5 py-1 rounded-md"> <span className="text-[11px] font-mono text-[var(--text-muted)] bg-[var(--bg-surface)] border border-[var(--border-color)] px-2 py-1 rounded">
Click any node below to inspect Click nodes to inspect
</span> </span>
</div> </div>
</div> </div>
{/* Grid Layout: Visual Interactive Map & Inspector */} {/* Grid Layout */}
<div className="grid grid-cols-1 lg:grid-cols-12"> <div className="grid grid-cols-1 lg:grid-cols-12">
{/* Visual Map Column (Left) */} {/* Visual Map Column (Left) */}
<div className="lg:col-span-7 p-6 border-b lg:border-b-0 lg:border-r border-[var(--border-color)] bg-[var(--bg-app)]/50 flex flex-col justify-between space-y-6"> <div className="lg:col-span-7 p-5 border-b lg:border-b-0 lg:border-r border-[var(--border-color)] bg-[var(--bg-app)] flex flex-col justify-between space-y-5">
{/* Layer 1: Clients */} {/* Layer 1: Clients */}
<div> <div>
<span className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-dim)] mb-2 block"> <span className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-muted)] mb-2 block">
1. Ingestion & Access Layer 1. Ingestion & Access Layer
</span> </span>
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-2 gap-2.5">
{NODES.filter((n) => n.type === "client").map((node) => { {NODES.filter((n) => n.type === "client").map((node) => {
const Icon = node.icon; const Icon = node.icon;
const isSelected = activeNodeId === node.id; const isSelected = activeNodeId === node.id;
@@ -193,21 +190,21 @@ export default function InteractiveTopology() {
key={node.id} key={node.id}
onClick={() => setActiveNodeId(node.id)} onClick={() => setActiveNodeId(node.id)}
type="button" type="button"
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer ${ className={`p-3 rounded-lg border text-left transition-all cursor-pointer ${
isSelected isSelected
? "bg-[var(--brand)]/15 border-[var(--brand)] shadow-md shadow-[var(--brand)]/10 ring-1 ring-[var(--brand)]" ? "bg-[var(--bg-surface)] border-[var(--text-primary)] shadow-xs ring-1 ring-[var(--text-primary)]"
: "bg-[var(--bg-surface)] border-[var(--border-color)] hover:border-[var(--brand)]/50 hover:bg-[var(--bg-elevated)]" : "bg-[var(--bg-surface)] border-[var(--border-color)] hover:border-[var(--text-muted)]"
}`} }`}
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className={`p-2 rounded-lg ${isSelected ? "bg-[var(--brand)] text-white" : "bg-[var(--bg-elevated)] text-[var(--text-secondary)]"}`}> <div className={`p-1.5 rounded ${isSelected ? "bg-[var(--brand)] text-[var(--brand-fg)]" : "bg-[var(--bg-elevated)] text-[var(--text-muted)]"}`}>
<Icon className="w-4 h-4" /> <Icon className="w-3.5 h-3.5" />
</div> </div>
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-[var(--bg-elevated)] text-[var(--text-muted)] border border-[var(--border-color)]"> <span className="text-[10px] font-mono px-1.5 py-0.2 rounded bg-[var(--bg-elevated)] text-[var(--text-muted)] border border-[var(--border-color)]">
{node.badge} {node.badge}
</span> </span>
</div> </div>
<div className="font-semibold text-sm text-[var(--text-primary)]">{node.title}</div> <div className="font-semibold text-xs text-[var(--text-primary)]">{node.title}</div>
</button> </button>
); );
})} })}
@@ -216,17 +213,17 @@ export default function InteractiveTopology() {
{/* Flow Connector Arrow */} {/* Flow Connector Arrow */}
<div className="flex justify-center text-[var(--text-dim)]"> <div className="flex justify-center text-[var(--text-dim)]">
<span className="text-xs font-mono px-3 py-0.5 rounded-full bg-[var(--bg-surface)] border border-[var(--border-color)]"> <span className="text-[10px] font-mono px-2.5 py-0.5 rounded bg-[var(--bg-surface)] border border-[var(--border-color)]">
↓ Secure Protocol & Gateway ↓ Protocol Gateway
</span> </span>
</div> </div>
{/* Layer 2: Protocol & Core Services */} {/* Layer 2: Protocol & Core Services */}
<div> <div>
<span className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-dim)] mb-2 block"> <span className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-muted)] mb-2 block">
2. Gateway & Intelligence Core 2. Core Engine & Protocol Layer
</span> </span>
<div className="grid grid-cols-1 sm:grid-cols-3 gap-3"> <div className="grid grid-cols-1 sm:grid-cols-3 gap-2.5">
{NODES.filter((n) => n.type === "protocol" || n.type === "core").map((node) => { {NODES.filter((n) => n.type === "protocol" || n.type === "core").map((node) => {
const Icon = node.icon; const Icon = node.icon;
const isSelected = activeNodeId === node.id; const isSelected = activeNodeId === node.id;
@@ -235,18 +232,18 @@ export default function InteractiveTopology() {
key={node.id} key={node.id}
onClick={() => setActiveNodeId(node.id)} onClick={() => setActiveNodeId(node.id)}
type="button" type="button"
className={`p-3.5 rounded-xl border text-left transition-all cursor-pointer ${ className={`p-3 rounded-lg border text-left transition-all cursor-pointer ${
isSelected isSelected
? "bg-[var(--brand)]/15 border-[var(--brand)] shadow-md shadow-[var(--brand)]/10 ring-1 ring-[var(--brand)]" ? "bg-[var(--bg-surface)] border-[var(--text-primary)] shadow-xs ring-1 ring-[var(--text-primary)]"
: "bg-[var(--bg-surface)] border-[var(--border-color)] hover:border-[var(--brand)]/50 hover:bg-[var(--bg-elevated)]" : "bg-[var(--bg-surface)] border-[var(--border-color)] hover:border-[var(--text-muted)]"
}`} }`}
> >
<div className="flex items-center justify-between mb-2"> <div className="flex items-center justify-between mb-2">
<div className={`p-2 rounded-lg ${isSelected ? "bg-[var(--brand)] text-white" : "bg-[var(--bg-elevated)] text-[var(--text-secondary)]"}`}> <div className={`p-1.5 rounded ${isSelected ? "bg-[var(--brand)] text-[var(--brand-fg)]" : "bg-[var(--bg-elevated)] text-[var(--text-muted)]"}`}>
<Icon className="w-4 h-4" /> <Icon className="w-3.5 h-3.5" />
</div> </div>
</div> </div>
<div className="font-semibold text-xs text-[var(--text-primary)] leading-tight mb-1 truncate"> <div className="font-semibold text-xs text-[var(--text-primary)] leading-tight mb-0.5 truncate">
{node.title} {node.title}
</div> </div>
<div className="text-[10px] text-[var(--text-muted)] font-mono truncate"> <div className="text-[10px] text-[var(--text-muted)] font-mono truncate">
@@ -260,15 +257,15 @@ export default function InteractiveTopology() {
{/* Flow Connector Arrow */} {/* Flow Connector Arrow */}
<div className="flex justify-center text-[var(--text-dim)]"> <div className="flex justify-center text-[var(--text-dim)]">
<span className="text-xs font-mono px-3 py-0.5 rounded-full bg-[var(--bg-surface)] border border-[var(--border-color)]"> <span className="text-[10px] font-mono px-2.5 py-0.5 rounded bg-[var(--bg-surface)] border border-[var(--border-color)]">
↓ Persistent Layer (ACID + Vectors) ↓ Persistent Storage Layer
</span> </span>
</div> </div>
{/* Layer 3: Database & Vector Storage */} {/* Layer 3: Storage */}
<div> <div>
<span className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-dim)] mb-2 block"> <span className="text-[10px] font-mono uppercase tracking-widest text-[var(--text-muted)] mb-2 block">
3. Source of Truth Storage 3. Database (Source of Truth)
</span> </span>
{NODES.filter((n) => n.type === "storage").map((node) => { {NODES.filter((n) => n.type === "storage").map((node) => {
const Icon = node.icon; const Icon = node.icon;
@@ -278,23 +275,23 @@ export default function InteractiveTopology() {
key={node.id} key={node.id}
onClick={() => setActiveNodeId(node.id)} onClick={() => setActiveNodeId(node.id)}
type="button" type="button"
className={`w-full p-3.5 rounded-xl border text-left transition-all cursor-pointer ${ className={`w-full p-3 rounded-lg border text-left transition-all cursor-pointer ${
isSelected isSelected
? "bg-[var(--brand)]/15 border-[var(--brand)] shadow-md shadow-[var(--brand)]/10 ring-1 ring-[var(--brand)]" ? "bg-[var(--bg-surface)] border-[var(--text-primary)] shadow-xs ring-1 ring-[var(--text-primary)]"
: "bg-[var(--bg-surface)] border-[var(--border-color)] hover:border-[var(--brand)]/50 hover:bg-[var(--bg-elevated)]" : "bg-[var(--bg-surface)] border-[var(--border-color)] hover:border-[var(--text-muted)]"
}`} }`}
> >
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className={`p-2 rounded-lg ${isSelected ? "bg-[var(--brand)] text-white" : "bg-[var(--bg-elevated)] text-[var(--text-secondary)]"}`}> <div className={`p-1.5 rounded ${isSelected ? "bg-[var(--brand)] text-[var(--brand-fg)]" : "bg-[var(--bg-elevated)] text-[var(--text-muted)]"}`}>
<Icon className="w-4 h-4" /> <Icon className="w-4 h-4" />
</div> </div>
<div> <div>
<div className="font-semibold text-sm text-[var(--text-primary)]">{node.title}</div> <div className="font-semibold text-xs text-[var(--text-primary)]">{node.title}</div>
<div className="text-xs text-[var(--text-muted)]">Source of truth for all content & embeddings</div> <div className="text-[11px] text-[var(--text-muted)]">Source of truth for all content & embeddings</div>
</div> </div>
</div> </div>
<span className="text-[10px] font-mono px-2 py-0.5 rounded bg-[var(--bg-elevated)] text-[var(--text-muted)] border border-[var(--border-color)]"> <span className="text-[10px] font-mono px-1.5 py-0.2 rounded bg-[var(--bg-elevated)] text-[var(--text-muted)] border border-[var(--border-color)]">
{node.badge} {node.badge}
</span> </span>
</div> </div>
@@ -305,40 +302,40 @@ export default function InteractiveTopology() {
</div> </div>
{/* Node Inspector Details Column (Right) */} {/* Node Inspector Details Column (Right) */}
<div className="lg:col-span-5 p-6 bg-[var(--bg-surface)] flex flex-col justify-between space-y-6 animate-fade-in"> <div className="lg:col-span-5 p-5 bg-[var(--bg-surface)] flex flex-col justify-between space-y-5 animate-fade-in">
<div> <div>
{/* Inspector Header */} {/* Inspector Header */}
<div className="flex items-start justify-between gap-4 mb-4"> <div className="flex items-start justify-between gap-3 mb-3">
<div className="flex items-center gap-3"> <div className="flex items-center gap-2.5">
<div className="w-11 h-11 rounded-xl bg-[var(--brand)]/15 border border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)] flex items-center justify-center"> <div className="w-9 h-9 rounded-lg bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-primary)] flex items-center justify-center">
<ActiveIcon className="w-5 h-5" /> <ActiveIcon className="w-4 h-4" />
</div> </div>
<div> <div>
<h4 className="text-lg font-bold text-[var(--text-primary)]">{activeNode.title}</h4> <h4 className="text-sm font-bold text-[var(--text-primary)]">{activeNode.title}</h4>
<span className="text-xs font-mono text-[var(--brand)] dark:text-[var(--accent)]"> <span className="text-[10px] font-mono text-[var(--text-muted)]">
{activeNode.badge} {activeNode.badge}
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<p className="text-xs sm:text-sm text-[var(--text-muted)] leading-relaxed mb-6"> <p className="text-xs text-[var(--text-secondary)] leading-relaxed mb-5">
{activeNode.desc} {activeNode.desc}
</p> </p>
{/* Telemetry Metrics */} {/* Telemetry Metrics */}
<div className="mb-6"> <div className="mb-5">
<span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--text-dim)] font-mono mb-2.5 block"> <span className="text-[10px] font-semibold uppercase tracking-wider text-[var(--text-muted)] font-mono mb-2 block">
Telemetry & Architecture Specifications
</span> </span>
<div className="space-y-2"> <div className="space-y-1.5">
{activeNode.metrics.map((m) => ( {activeNode.metrics.map((m) => (
<div <div
key={m.label} key={m.label}
className="flex items-center justify-between p-2.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-lg text-xs" className="flex items-center justify-between p-2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-xs"
> >
<span className="text-[var(--text-muted)]">{m.label}</span> <span className="text-[var(--text-muted)] text-[11px]">{m.label}</span>
<span className="font-mono font-medium text-[var(--text-primary)]">{m.value}</span> <span className="font-mono font-medium text-[var(--text-primary)] text-[11px]">{m.value}</span>
</div> </div>
))} ))}
</div> </div>
@@ -346,13 +343,13 @@ export default function InteractiveTopology() {
{/* Key Capabilities */} {/* Key Capabilities */}
<div> <div>
<span className="text-[11px] font-semibold uppercase tracking-wider text-[var(--text-dim)] font-mono mb-2.5 block"> <span className="text-[10px] font-semibold uppercase tracking-wider text-[var(--text-muted)] font-mono mb-2 block">
Key Capabilities Capabilities
</span> </span>
<ul className="space-y-2"> <ul className="space-y-1.5">
{activeNode.features.map((feat, idx) => ( {activeNode.features.map((feat, idx) => (
<li key={idx} className="flex items-start gap-2 text-xs text-[var(--text-secondary)]"> <li key={idx} className="flex items-start gap-2 text-xs text-[var(--text-secondary)]">
<CheckCircle2 className="w-3.5 h-3.5 text-emerald-500 shrink-0 mt-0.5" /> <span className="text-emerald-500 shrink-0 font-mono text-[11px]">✓</span>
<span>{feat}</span> <span>{feat}</span>
</li> </li>
))} ))}
@@ -362,12 +359,12 @@ export default function InteractiveTopology() {
{/* Link to Documentation */} {/* Link to Documentation */}
{activeNode.docLink && ( {activeNode.docLink && (
<div className="pt-4 border-t border-[var(--border-color)]"> <div className="pt-3 border-t border-[var(--border-color)]">
<Link <Link
href={activeNode.docLink} href={activeNode.docLink}
className="inline-flex items-center justify-center gap-2 w-full py-2.5 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] text-xs font-semibold text-[var(--text-primary)] rounded-xl transition-all shadow-xs group" className="inline-flex items-center justify-center gap-1.5 w-full py-2 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-xs font-semibold text-[var(--text-primary)] rounded-lg transition-colors group"
> >
<span>Read technical writeup in {activeNode.title}</span> <span>Read documentation</span>
<ArrowRight className="w-3.5 h-3.5 group-hover:translate-x-0.5 transition-transform" /> <ArrowRight className="w-3.5 h-3.5 group-hover:translate-x-0.5 transition-transform" />
</Link> </Link>
</div> </div>
+17 -21
View File
@@ -6,11 +6,8 @@ import {
Sparkles, Sparkles,
Terminal, Terminal,
Server, Server,
Code2,
Copy, Copy,
Check, Check,
CheckCircle2,
Cpu,
Laptop, Laptop,
} from "lucide-react"; } from "lucide-react";
@@ -54,10 +51,10 @@ const CONFIGS = [
}, },
{ {
id: "http", id: "http",
label: "Streamable HTTP Endpoint", label: "Streamable HTTP (SSE)",
icon: Server, icon: Server,
lang: "json", lang: "json",
filename: "Streamable HTTP (SSE) Client Config", filename: "Streamable HTTP Client Config",
content: `{ content: `{
"mcpServers": { "mcpServers": {
"mcpedia-remote": { "mcpedia-remote": {
@@ -74,11 +71,11 @@ const CONFIGS = [
label: "CLI & Scripts", label: "CLI & Scripts",
icon: Terminal, icon: Terminal,
lang: "bash", lang: "bash",
filename: "Terminal / MCP Client Subprocess", filename: "Terminal Subprocess / REPL",
content: `# Ask questions over semantic knowledge content: `# Semantic question answering via MCP client
bun run apps/mcp-client/src/ask.ts "How does ret2win stack alignment work in x86_64?" bun run apps/mcp-client/src/ask.ts "How does ret2win stack alignment work in x86_64?"
# Run interactive REPL with native tool calls # Interactive REPL with tool discovery
bun run apps/mcp-client/src/index.ts`, bun run apps/mcp-client/src/index.ts`,
}, },
]; ];
@@ -100,9 +97,9 @@ ${current.content}
} }
return ( return (
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl overflow-hidden shadow-sm"> <div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl overflow-hidden shadow-xs">
{/* Header bar with tabs */} {/* Header bar with tabs */}
<div className="flex items-center justify-between px-4 sm:px-6 py-3 bg-[var(--bg-elevated)]/60 border-b border-[var(--border-color)] flex-wrap gap-2"> <div className="flex items-center justify-between px-4 py-2.5 bg-[var(--bg-elevated)] border-b border-[var(--border-color)] flex-wrap gap-2">
<div className="flex items-center gap-1.5 flex-wrap"> <div className="flex items-center gap-1.5 flex-wrap">
{CONFIGS.map((c) => { {CONFIGS.map((c) => {
const Icon = c.icon; const Icon = c.icon;
@@ -112,9 +109,9 @@ ${current.content}
key={c.id} key={c.id}
onClick={() => setActiveTab(c.id)} onClick={() => setActiveTab(c.id)}
type="button" type="button"
className={`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-xl font-semibold transition-all cursor-pointer ${ className={`flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md font-medium transition-all cursor-pointer ${
isSelected isSelected
? "bg-[var(--brand)] text-white shadow-md shadow-[var(--brand)]/20" ? "bg-[var(--brand)] text-[var(--brand-fg)] font-semibold shadow-xs"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-surface)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-surface)]"
}`} }`}
> >
@@ -128,12 +125,12 @@ ${current.content}
<button <button
onClick={handleCopyAll} onClick={handleCopyAll}
type="button" type="button"
className="flex items-center gap-1.5 px-3 py-1 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-lg transition-all shadow-xs cursor-pointer font-medium" className="flex items-center gap-1.5 px-2.5 py-1 text-xs bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-primary)] rounded-md transition-colors shadow-xs cursor-pointer font-medium"
> >
{copied ? ( {copied ? (
<> <>
<Check className="w-3.5 h-3.5 text-emerald-500" /> <Check className="w-3.5 h-3.5 text-emerald-500" />
<span className="text-emerald-500">Copied Config</span> <span className="text-emerald-500">Copied</span>
</> </>
) : ( ) : (
<> <>
@@ -144,15 +141,15 @@ ${current.content}
</button> </button>
</div> </div>
{/* Code preview via Markdown syntax highlighter */} {/* Code preview */}
<div className="p-4 sm:p-5 [&>.prose>.group]:my-0 [&>.prose]:max-w-none"> <div className="p-4 sm:p-5 [&>.prose>.group]:my-0 [&>.prose]:max-w-none">
<Markdown source={markdownSnippet} /> <Markdown source={markdownSnippet} />
</div> </div>
{/* Footer Info */} {/* Footer Info */}
<div className="px-4 sm:px-6 py-3 bg-[var(--bg-elevated)]/40 border-t border-[var(--border-color)] text-xs text-[var(--text-dim)] flex flex-col sm:flex-row sm:items-center justify-between gap-2"> <div className="px-4 py-2.5 bg-[var(--bg-elevated)] border-t border-[var(--border-color)] text-xs text-[var(--text-dim)] flex flex-col sm:flex-row sm:items-center justify-between gap-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="font-mono uppercase text-[10px] text-[var(--text-muted)] font-semibold"> <span className="font-mono uppercase text-[10px] text-[var(--text-muted)]">
Target File: Target File:
</span> </span>
<span className="font-mono text-[var(--text-secondary)] text-[11px]"> <span className="font-mono text-[var(--text-secondary)] text-[11px]">
@@ -160,10 +157,9 @@ ${current.content}
</span> </span>
</div> </div>
<div className="flex items-center gap-3 text-[11px]"> <div className="flex items-center gap-3 text-[11px] font-mono">
<span className="flex items-center gap-1 text-emerald-600 dark:text-emerald-400 font-medium"> <span className="flex items-center gap-1 text-[var(--text-muted)]">
<CheckCircle2 className="w-3.5 h-3.5" /> <span>[ 13_TOOLS_ACTIVE ]</span>
<span>13 MCP Tools Ready</span>
</span> </span>
</div> </div>
</div> </div>
+23 -25
View File
@@ -20,8 +20,6 @@ import {
Copy, Copy,
Check, Check,
ArrowLeft, ArrowLeft,
Filter,
ListOrdered,
BookOpen, BookOpen,
} from "lucide-react"; } from "lucide-react";
@@ -124,20 +122,20 @@ export default function PdfExportView({
return ( return (
<div className="pb-16 print:pb-0"> <div className="pb-16 print:pb-0">
{/* Export Control Toolbar (Screen Only — Hidden in Print) */} {/* Export Control Toolbar (Screen Only — Hidden in Print) */}
<aside className="no-print export-toolbar sticky top-16 z-30 mb-8 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl p-5 shadow-md"> <aside className="no-print export-toolbar sticky top-16 z-30 mb-8 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-4 sm:p-5 shadow-xs">
<div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4"> <div className="flex flex-col lg:flex-row lg:items-center justify-between gap-4">
{/* Left info & Back button */} {/* Left info & Back button */}
<div className="flex items-center gap-3.5"> <div className="flex items-center gap-3">
<Link <Link
href={backHref} href={backHref}
className="inline-flex items-center gap-1.5 px-3.5 py-2 text-xs font-semibold bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-all shadow-xs" className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] rounded-md text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors shadow-xs"
> >
<ArrowLeft className="w-3.5 h-3.5" /> <ArrowLeft className="w-3.5 h-3.5" />
<span>Back</span> <span>Back</span>
</Link> </Link>
<div> <div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-xs font-mono text-[var(--brand)] dark:text-[var(--accent)] font-bold uppercase"> <span className="text-xs font-mono text-[var(--text-primary)] font-bold uppercase">
{sectionMeta.label} {sectionMeta.label}
</span> </span>
<span className="text-xs text-[var(--text-dim)]">·</span> <span className="text-xs text-[var(--text-dim)]">·</span>
@@ -158,7 +156,7 @@ export default function PdfExportView({
<button <button
onClick={handleCopyMarkdown} onClick={handleCopyMarkdown}
type="button" type="button"
className="inline-flex items-center gap-1.5 px-3.5 py-2 text-xs font-semibold bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-xl transition-all shadow-xs cursor-pointer" className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-md transition-colors shadow-xs cursor-pointer"
title="Copy compiled markdown document" title="Copy compiled markdown document"
> >
{copiedMd ? ( {copiedMd ? (
@@ -169,7 +167,7 @@ export default function PdfExportView({
) : ( ) : (
<> <>
<Copy className="w-3.5 h-3.5" /> <Copy className="w-3.5 h-3.5" />
<span>Copy Markdown</span> <span>Copy MD</span>
</> </>
)} )}
</button> </button>
@@ -178,7 +176,7 @@ export default function PdfExportView({
<button <button
onClick={handleDownloadMarkdown} onClick={handleDownloadMarkdown}
type="button" type="button"
className="inline-flex items-center gap-1.5 px-3.5 py-2 text-xs font-semibold bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-xl transition-all shadow-xs cursor-pointer" className="inline-flex items-center gap-1.5 px-3 py-1.5 text-xs font-semibold bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-md transition-colors shadow-xs cursor-pointer"
title="Download compiled .md file" title="Download compiled .md file"
> >
<FileDown className="w-3.5 h-3.5" /> <FileDown className="w-3.5 h-3.5" />
@@ -189,24 +187,24 @@ export default function PdfExportView({
<button <button
onClick={handlePrint} onClick={handlePrint}
type="button" type="button"
className="inline-flex items-center gap-2 px-4 py-2 text-xs font-bold bg-slate-900 hover:bg-slate-800 dark:bg-white dark:hover:bg-slate-100 text-white dark:text-slate-900 rounded-xl shadow-md transition-all cursor-pointer hover:shadow-lg" className="inline-flex items-center gap-2 px-3.5 py-1.5 text-xs font-bold bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] rounded-md shadow-xs transition-all cursor-pointer"
title="Open browser print dialog to save as PDF" title="Open browser print dialog to save as PDF"
> >
<Printer className="w-4 h-4" /> <Printer className="w-4 h-4" />
<span>Print / Save as PDF</span> <span>Print / Save PDF</span>
</button> </button>
</div> </div>
</div> </div>
{/* Options & Filters Bar */} {/* Options & Filters Bar */}
<div className="mt-4 pt-3.5 border-t border-[var(--border-color)] flex flex-col sm:flex-row sm:items-center justify-between gap-3 text-xs"> <div className="mt-3.5 pt-3 border-t border-[var(--border-color)] flex flex-col sm:flex-row sm:items-center justify-between gap-3 text-xs">
{/* Sort Order Selector */} {/* Sort Order Selector */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[var(--text-muted)] font-medium font-mono text-[11px] uppercase">Sort by:</span> <span className="text-[var(--text-muted)] font-mono text-[10px] uppercase">Sort:</span>
<select <select
value={sortBy} value={sortBy}
onChange={(e) => setSortBy(e.target.value as ExportSortOption)} onChange={(e) => setSortBy(e.target.value as ExportSortOption)}
className="bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-lg px-3 py-1 text-xs text-[var(--text-primary)] focus:outline-none focus:border-[var(--brand)]" className="bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded px-2.5 py-1 text-xs text-[var(--text-primary)] focus:outline-none"
> >
<option value="category_points">Category & Points (Standard)</option> <option value="category_points">Category & Points (Standard)</option>
<option value="points_desc">Points (Highest First)</option> <option value="points_desc">Points (Highest First)</option>
@@ -225,7 +223,7 @@ export default function PdfExportView({
type="checkbox" type="checkbox"
checked={showTOC} checked={showTOC}
onChange={(e) => setShowTOC(e.target.checked)} onChange={(e) => setShowTOC(e.target.checked)}
className="rounded border-[var(--border-color)] text-indigo-600" className="rounded border-[var(--border-color)]"
/> />
<span>Table of Contents</span> <span>Table of Contents</span>
</label> </label>
@@ -236,7 +234,7 @@ export default function PdfExportView({
type="checkbox" type="checkbox"
checked={pageBreaks} checked={pageBreaks}
onChange={(e) => setPageBreaks(e.target.checked)} onChange={(e) => setPageBreaks(e.target.checked)}
className="rounded border-[var(--border-color)] text-indigo-600" className="rounded border-[var(--border-color)]"
/> />
<span>Page Break per Challenge</span> <span>Page Break per Challenge</span>
</label> </label>
@@ -246,13 +244,13 @@ export default function PdfExportView({
{/* Category filter pills */} {/* Category filter pills */}
{allCategories.length > 1 && ( {allCategories.length > 1 && (
<div className="mt-3 pt-3 border-t border-[var(--border-color)] flex items-center gap-1.5 flex-wrap text-xs"> <div className="mt-3 pt-3 border-t border-[var(--border-color)] flex items-center gap-1.5 flex-wrap text-xs">
<span className="text-[var(--text-muted)] font-mono text-[11px] uppercase mr-1">Filter:</span> <span className="text-[var(--text-muted)] font-mono text-[10px] uppercase mr-1">Filter:</span>
<button <button
onClick={() => setSelectedCategory("all")} onClick={() => setSelectedCategory("all")}
type="button" type="button"
className={`px-3 py-1 rounded-lg text-xs font-mono transition-all cursor-pointer ${ className={`px-2.5 py-0.5 rounded text-xs font-mono transition-all cursor-pointer ${
selectedCategory === "all" selectedCategory === "all"
? "bg-slate-900 dark:bg-white text-white dark:text-slate-900 font-bold" ? "bg-[var(--brand)] text-[var(--brand-fg)] font-bold"
: "bg-[var(--bg-elevated)] text-[var(--text-muted)] hover:text-[var(--text-primary)] border border-[var(--border-color)]" : "bg-[var(--bg-elevated)] text-[var(--text-muted)] hover:text-[var(--text-primary)] border border-[var(--border-color)]"
}`} }`}
> >
@@ -267,9 +265,9 @@ export default function PdfExportView({
key={cat} key={cat}
onClick={() => setSelectedCategory(cat)} onClick={() => setSelectedCategory(cat)}
type="button" type="button"
className={`px-3 py-1 rounded-lg text-xs font-mono transition-all cursor-pointer ${ className={`px-2.5 py-0.5 rounded text-xs font-mono transition-all cursor-pointer ${
selectedCategory.toLowerCase() === cat.toLowerCase() selectedCategory.toLowerCase() === cat.toLowerCase()
? "bg-slate-900 dark:bg-white text-white dark:text-slate-900 font-bold" ? "bg-[var(--brand)] text-[var(--brand-fg)] font-bold"
: "bg-[var(--bg-elevated)] text-[var(--text-muted)] hover:text-[var(--text-primary)] border border-[var(--border-color)]" : "bg-[var(--bg-elevated)] text-[var(--text-muted)] hover:text-[var(--text-primary)] border border-[var(--border-color)]"
}`} }`}
> >
@@ -282,7 +280,7 @@ export default function PdfExportView({
</aside> </aside>
{/* Writeup Document Flow */} {/* Writeup Document Flow */}
<main className="print-container bg-white text-slate-900 max-w-4xl mx-auto px-4 sm:px-8 py-6 print:p-0 print:max-w-none rounded-2xl border border-[var(--border-color)] print:border-none shadow-sm print:shadow-none"> <main className="print-container bg-white text-slate-900 max-w-4xl mx-auto px-4 sm:px-8 py-6 print:p-0 print:max-w-none rounded-xl border border-[var(--border-color)] print:border-none shadow-xs print:shadow-none">
{/* Document Header */} {/* Document Header */}
<header className="mb-8 pb-4 border-b border-slate-300"> <header className="mb-8 pb-4 border-b border-slate-300">
<h1 className="text-3xl sm:text-4xl font-extrabold text-slate-950 tracking-tight mb-2"> <h1 className="text-3xl sm:text-4xl font-extrabold text-slate-950 tracking-tight mb-2">
@@ -300,8 +298,8 @@ export default function PdfExportView({
{/* Table of Contents */} {/* Table of Contents */}
{showTOC && filteredAndSortedDocs.length > 1 && ( {showTOC && filteredAndSortedDocs.length > 1 && (
<section className="toc-section mb-10 pb-6 border-b border-slate-200"> <section className="toc-section mb-10 pb-6 border-b border-slate-200">
<h2 className="text-sm font-bold uppercase font-mono text-slate-700 mb-3 tracking-wider flex items-center gap-1.5"> <h2 className="text-xs font-bold uppercase font-mono text-slate-700 mb-3 tracking-wider flex items-center gap-1.5">
<BookOpen className="w-4 h-4" /> <BookOpen className="w-3.5 h-3.5" />
<span>Table of Contents</span> <span>Table of Contents</span>
</h2> </h2>
<ol className="list-decimal list-inside space-y-1.5 text-xs text-slate-800"> <ol className="list-decimal list-inside space-y-1.5 text-xs text-slate-800">
@@ -327,7 +325,7 @@ export default function PdfExportView({
</section> </section>
)} )}
{/* Challenges / Writeups Flow */} {/* Challenges Flow */}
<div className="challenges-flow space-y-10 print:space-y-0"> <div className="challenges-flow space-y-10 print:space-y-0">
{filteredAndSortedDocs.map((doc, idx) => { {filteredAndSortedDocs.map((doc, idx) => {
const chNum = idx + 1; const chNum = idx + 1;
+17 -18
View File
@@ -12,7 +12,6 @@ import {
Search, Search,
X, X,
Plus, Plus,
Compass,
} from "lucide-react"; } from "lucide-react";
interface Doc { interface Doc {
@@ -114,11 +113,11 @@ function TreeNodeItem({
return ( return (
<li className={`select-none ${depth > 0 ? "ml-3.5 pl-2 border-l border-[var(--border-color)]" : ""}`}> <li className={`select-none ${depth > 0 ? "ml-3.5 pl-2 border-l border-[var(--border-color)]" : ""}`}>
<div <div
className={`group flex items-center justify-between gap-1 text-xs py-1.5 px-2 rounded-lg transition-all ${ className={`group flex items-center justify-between gap-1 text-xs py-1.5 px-2 rounded-md transition-all ${
isActive isActive
? "text-[var(--brand)] dark:text-[var(--accent)] bg-[var(--brand)]/15 font-semibold border border-[var(--brand)]/30 shadow-xs" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)] font-semibold border border-[var(--border-color)] shadow-xs"
: isParentOfActive : isParentOfActive
? "text-[var(--text-primary)] bg-[var(--bg-elevated)]" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)]/60"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
}`} }`}
> >
@@ -127,12 +126,12 @@ function TreeNodeItem({
className="flex items-center gap-2 min-w-0 flex-1 py-0.5" className="flex items-center gap-2 min-w-0 flex-1 py-0.5"
title={node.title || node.name} title={node.title || node.name}
> >
<span className="shrink-0 text-[var(--text-dim)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors"> <span className="shrink-0 text-[var(--text-dim)] group-hover:text-[var(--text-primary)] transition-colors">
{isFolder ? ( {isFolder ? (
isCollapsed ? ( isCollapsed ? (
<Folder className="w-3.5 h-3.5" /> <Folder className="w-3.5 h-3.5" />
) : ( ) : (
<FolderOpen className="w-3.5 h-3.5 text-[var(--brand)] dark:text-[var(--accent)]" /> <FolderOpen className="w-3.5 h-3.5 text-[var(--text-primary)]" />
) )
) : ( ) : (
<FileText className="w-3.5 h-3.5" /> <FileText className="w-3.5 h-3.5" />
@@ -153,7 +152,7 @@ function TreeNodeItem({
e.stopPropagation(); e.stopPropagation();
toggleCollapse(node.slug); toggleCollapse(node.slug);
}} }}
className="p-1 text-[var(--text-dim)] hover:text-[var(--text-primary)] rounded hover:bg-[var(--bg-elevated-hover)] transition-transform" className="p-1 text-[var(--text-dim)] hover:text-[var(--text-primary)] rounded hover:bg-[var(--bg-elevated-hover)] transition-transform cursor-pointer"
aria-label={isCollapsed ? "Expand folder" : "Collapse folder"} aria-label={isCollapsed ? "Expand folder" : "Collapse folder"}
> >
<ChevronRight <ChevronRight
@@ -233,14 +232,14 @@ export default function Sidebar() {
type="text" type="text"
value={filterText} value={filterText}
onChange={(e) => setFilterText(e.target.value)} onChange={(e) => setFilterText(e.target.value)}
placeholder="Filter knowledge tree..." placeholder="Filter index..."
className="w-full pl-8 pr-7 py-1.5 bg-[var(--bg-surface)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-lg text-xs text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-1 focus:ring-[var(--brand)] transition-all" className="w-full pl-8 pr-7 py-1.5 bg-[var(--bg-surface)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-md text-xs text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--text-primary)] transition-all"
/> />
<Search className="w-3.5 h-3.5 text-[var(--text-dim)] absolute left-2.5 top-2.5 pointer-events-none" /> <Search className="w-3.5 h-3.5 text-[var(--text-dim)] absolute left-2.5 top-2.5 pointer-events-none" />
{filterText && ( {filterText && (
<button <button
onClick={() => setFilterText("")} onClick={() => setFilterText("")}
className="absolute right-2 top-2 p-0.5 text-[var(--text-dim)] hover:text-[var(--text-primary)] rounded hover:bg-[var(--bg-elevated)]" className="absolute right-2 top-2 p-0.5 text-[var(--text-dim)] hover:text-[var(--text-primary)] rounded"
aria-label="Clear filter" aria-label="Clear filter"
> >
<X className="w-3.5 h-3.5" /> <X className="w-3.5 h-3.5" />
@@ -250,7 +249,7 @@ export default function Sidebar() {
</div> </div>
{/* Summary count */} {/* Summary count */}
<div className="flex items-center justify-between px-1 mb-2 text-[10px] text-[var(--text-dim)] font-mono uppercase tracking-wider"> <div className="flex items-center justify-between px-1 mb-2 text-[10px] text-[var(--text-muted)] font-mono uppercase tracking-wider">
<span>Knowledge Index</span> <span>Knowledge Index</span>
<span>{filteredDocs.length} items</span> <span>{filteredDocs.length} items</span>
</div> </div>
@@ -268,9 +267,9 @@ export default function Sidebar() {
<div key={id} className="border-b border-[var(--border-color)] pb-3 last:border-0"> <div key={id} className="border-b border-[var(--border-color)] pb-3 last:border-0">
<Link <Link
href={`/${id}`} href={`/${id}`}
className={`flex items-center justify-between gap-1.5 px-2 py-1.5 mb-1 rounded-lg text-xs font-semibold uppercase tracking-wider transition-colors ${ className={`flex items-center justify-between gap-1.5 px-2 py-1 mb-1 rounded-md text-xs font-semibold uppercase tracking-wider transition-colors ${
isSectionActive isSectionActive
? "text-[var(--brand)] dark:text-[var(--accent)] bg-[var(--brand)]/10" ? "text-[var(--text-primary)] bg-[var(--bg-elevated)]"
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]" : "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
}`} }`}
> >
@@ -278,7 +277,7 @@ export default function Sidebar() {
<span className="text-xs">{icon}</span> <span className="text-xs">{icon}</span>
<span>{label}</span> <span>{label}</span>
</div> </div>
<span className="text-[10px] text-[var(--text-dim)] font-normal font-mono bg-[var(--bg-surface)] px-1.5 py-0.5 rounded border border-[var(--border-color)]"> <span className="text-[10px] text-[var(--text-muted)] font-normal font-mono bg-[var(--bg-surface)] px-1.5 py-0.2 rounded border border-[var(--border-color)]">
{sectionDocs.length} {sectionDocs.length}
</span> </span>
</Link> </Link>
@@ -300,7 +299,7 @@ export default function Sidebar() {
})} })}
{filteredDocs.length === 0 && ( {filteredDocs.length === 0 && (
<div className="text-center py-8 text-xs text-[var(--text-dim)]"> <div className="text-center py-8 text-xs text-[var(--text-dim)] font-mono">
No matching documents No matching documents
</div> </div>
)} )}
@@ -310,10 +309,10 @@ export default function Sidebar() {
<div className="pt-3 border-t border-[var(--border-color)] mt-auto"> <div className="pt-3 border-t border-[var(--border-color)] mt-auto">
<Link <Link
href="/create" href="/create"
className="flex items-center justify-center gap-2 w-full py-2 text-xs bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-lg transition-all font-medium shadow-xs group" className="flex items-center justify-center gap-2 w-full py-2 text-xs bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] text-[var(--text-primary)] rounded-md transition-all font-medium shadow-xs"
> >
<Plus className="w-3.5 h-3.5 text-[var(--brand)] dark:text-[var(--accent)] group-hover:scale-110 transition-transform" /> <Plus className="w-3.5 h-3.5 text-[var(--text-muted)]" />
<span>Create New Document</span> <span>Create Document</span>
</Link> </Link>
</div> </div>
</nav> </nav>
+121 -162
View File
@@ -1,34 +1,42 @@
@import "tailwindcss"; @import "tailwindcss";
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
/* Light mode defaults (:root) */ /*
MCPedia Design Philosophy: Archival Precision & Technical Monolith
- Strict monochromatic foundation (Graphite, Zinc, Charcoal)
- Zero decorative AI gradients or blurred blobs
- Typographic hierarchy: Strong contrast, razor-sharp hairline borders, monospace telemetry
- Functional signal indicators (Status Green, Alert Amber, Critical Coral)
*/
/* Light mode defaults (:root) - Technical Paper / Archival Slate */
:root { :root {
color-scheme: light; color-scheme: light;
--bg-app: #f8fafc; --bg-app: #f9fafb;
--bg-surface: #ffffff; --bg-surface: #ffffff;
--bg-surface-hover: #f1f5f9; --bg-surface-hover: #f3f4f6;
--bg-elevated: #f1f5f9; --bg-elevated: #f3f4f6;
--bg-elevated-hover: #e2e8f0; --bg-elevated-hover: #e5e7eb;
--border-color: #e2e8f0; --border-color: #e5e7eb;
--border-subtle: #edf2f7; --border-subtle: #f3f4f6;
--border-card: #e2e8f0; --border-card: #e5e7eb;
--text-primary: #0f172a; --text-primary: #111827;
--text-secondary: #334155; --text-secondary: #374151;
--text-muted: #64748b; --text-muted: #6b7280;
--text-dim: #94a3b8; --text-dim: #9ca3af;
--brand: #4f46e5; /* Accent: Precision Technical Ink */
--brand-hover: #4338ca; --brand: #18181b;
--brand-glow: rgba(79, 70, 229, 0.15); --brand-hover: #27272a;
--accent: #4f46e5; --brand-fg: #ffffff;
--accent-hover: #4338ca; --accent: #2563eb;
--cyan-accent: #0284c7; --accent-hover: #1d4ed8;
--nav-bg: rgba(255, 255, 255, 0.85); --nav-bg: rgba(255, 255, 255, 0.92);
/* Code & Syntax variables (Light Mode) */ /* Code & Syntax (Light Mode) */
--code-bg: #f8fafc; --code-bg: #f3f4f6;
--code-block-bg: #f8fafc; --code-block-bg: #f8fafc;
--code-header-bg: #f1f5f9; --code-header-bg: #f1f5f9;
--border-code: #e2e8f0; --border-code: #e2e8f0;
@@ -39,15 +47,15 @@
--code-inline-text: #0f172a; --code-inline-text: #0f172a;
--code-inline-border: #e2e8f0; --code-inline-border: #e2e8f0;
--table-zebra: rgba(241, 245, 249, 0.6); --table-zebra: rgba(243, 244, 246, 0.5);
--scrollbar-thumb: #cbd5e1; --scrollbar-thumb: #d1d5db;
--scrollbar-thumb-hover: #94a3b8; --scrollbar-thumb-hover: #9ca3af;
--shadow-card: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px -1px rgba(0, 0, 0, 0.05); --shadow-card: 0 1px 2px 0 rgba(0, 0, 0, 0.04);
/* Syntax Highlighting (Light Mode) */ /* Syntax Highlighting */
--hl-keyword: #cf222e; --hl-keyword: #cf222e;
--hl-string: #0a3069; --hl-string: #0a3069;
--hl-title: #8250df; --hl-title: #6f42c1;
--hl-attr: #953800; --hl-attr: #953800;
--hl-number: #0550ae; --hl-number: #0550ae;
--hl-comment: #6e7781; --hl-comment: #6e7781;
@@ -55,55 +63,52 @@
--hl-operator: #57606a; --hl-operator: #57606a;
--hl-variable: #953800; --hl-variable: #953800;
--hl-meta: #6e7781; --hl-meta: #6e7781;
/* Grid overlay */
--grid-pattern: rgba(15, 23, 42, 0.03);
} }
/* Dark mode overrides (html.dark) */ /* Dark mode overrides (html.dark) - Deep Basalt / Precision Carbon */
html.dark { html.dark {
color-scheme: dark; color-scheme: dark;
--bg-app: #08090d; --bg-app: #090a0c;
--bg-surface: #0e1117; --bg-surface: #101216;
--bg-surface-hover: #141822; --bg-surface-hover: #16181f;
--bg-elevated: #151a26; --bg-elevated: #161920;
--bg-elevated-hover: #1e2436; --bg-elevated-hover: #1e222b;
--border-color: #1e2536; --border-color: #222630;
--border-subtle: rgba(255, 255, 255, 0.06); --border-subtle: #181b22;
--border-card: #1e2536; --border-card: #222630;
--text-primary: #f8fafc; --text-primary: #f3f4f6;
--text-secondary: #cbd5e1; --text-secondary: #d1d5db;
--text-muted: #8492a6; --text-muted: #9ca3af;
--text-dim: #505c70; --text-dim: #6b7280;
--brand: #6366f1; /* Accent: Monolithic Snow Ink */
--brand-hover: #818cf8; --brand: #f3f4f6;
--brand-glow: rgba(99, 102, 241, 0.22); --brand-hover: #ffffff;
--accent: #818cf8; --brand-fg: #090a0c;
--accent-hover: #a5b4fc; --accent: #60a5fa;
--cyan-accent: #38bdf8; --accent-hover: #93c5fd;
--nav-bg: rgba(8, 9, 13, 0.85); --nav-bg: rgba(9, 10, 12, 0.90);
/* Code & Syntax variables (Dark Mode) */ /* Code & Syntax (Dark Mode) */
--code-bg: #0b0e14; --code-bg: #0c0e12;
--code-block-bg: #0a0d14; --code-block-bg: #0c0e12;
--code-header-bg: #111520; --code-header-bg: #14171d;
--border-code: #202738; --border-code: #222630;
--code-button-bg: #151a26; --code-button-bg: #181b22;
--code-button-hover-bg: #202738; --code-button-hover-bg: #222630;
--pre-text: #e2e8f0; --pre-text: #e5e7eb;
--code-inline-bg: #131722; --code-inline-bg: #161920;
--code-inline-text: #e2e8f0; --code-inline-text: #e5e7eb;
--code-inline-border: #202738; --code-inline-border: #222630;
--table-zebra: rgba(255, 255, 255, 0.02); --table-zebra: rgba(255, 255, 255, 0.015);
--scrollbar-thumb: #1e2536; --scrollbar-thumb: #222630;
--scrollbar-thumb-hover: #313c54; --scrollbar-thumb-hover: #374151;
--shadow-card: none; --shadow-card: none;
/* Syntax Highlighting (Dark Mode) */ /* Syntax Highlighting */
--hl-keyword: #ff7b72; --hl-keyword: #ff7b72;
--hl-string: #a5d6ff; --hl-string: #a5d6ff;
--hl-title: #d2a8ff; --hl-title: #d2a8ff;
@@ -114,13 +119,10 @@ html.dark {
--hl-operator: #8b949e; --hl-operator: #8b949e;
--hl-variable: #ffa657; --hl-variable: #ffa657;
--hl-meta: #8b949e; --hl-meta: #8b949e;
/* Grid overlay */
--grid-pattern: rgba(255, 255, 255, 0.025);
} }
@theme inline { @theme inline {
--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace; --font-mono: "JetBrains Mono", ui-monospace, "SF Mono", Menlo, Monaco, Consolas, monospace;
--color-background: var(--bg-app); --color-background: var(--bg-app);
@@ -151,25 +153,17 @@ body {
background-color: var(--bg-app); background-color: var(--bg-app);
color: var(--text-secondary); color: var(--text-secondary);
font-family: var(--font-sans); font-family: var(--font-sans);
font-feature-settings: "cv01", "ss03", "zero"; font-feature-settings: "cv01", "ss03", "zero", "calt";
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
transition: background-color 0.25s ease, color 0.25s ease; transition: background-color 0.15s ease, color 0.15s ease;
position: relative;
min-height: 100vh; min-height: 100vh;
} }
/* Background subtle tech grid */ /* Custom hairline scrollbar */
.tech-grid-bg {
background-size: 32px 32px;
background-image: linear-gradient(to right, var(--grid-pattern) 1px, transparent 1px),
linear-gradient(to bottom, var(--grid-pattern) 1px, transparent 1px);
}
/* Custom scrollbar */
::-webkit-scrollbar { ::-webkit-scrollbar {
width: 6px; width: 5px;
height: 6px; height: 5px;
} }
::-webkit-scrollbar-track { ::-webkit-scrollbar-track {
@@ -178,72 +172,54 @@ body {
::-webkit-scrollbar-thumb { ::-webkit-scrollbar-thumb {
background: var(--scrollbar-thumb); background: var(--scrollbar-thumb);
border-radius: 9999px; border-radius: 2px;
} }
::-webkit-scrollbar-thumb:hover { ::-webkit-scrollbar-thumb:hover {
background: var(--scrollbar-thumb-hover); background: var(--scrollbar-thumb-hover);
} }
/* Glassmorphism helpers */ /* Glass navigation */
.glass-panel {
background: var(--bg-surface);
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
border: 1px solid var(--border-color);
}
.glass-nav { .glass-nav {
background: var(--nav-bg); background: var(--nav-bg);
backdrop-filter: blur(20px); backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(20px); -webkit-backdrop-filter: blur(16px);
} }
.glow-card { /* Tactical Architectural Callouts */
position: relative;
transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
}
.glow-card:hover {
transform: translateY(-2px);
border-color: var(--brand);
box-shadow: 0 8px 30px -10px var(--brand-glow);
}
/* Callout Alert Styles */
.callout { .callout {
border-left-width: 4px; border-left-width: 3px;
border-radius: 0 0.5rem 0.5rem 0; border-radius: 0 0.375rem 0.375rem 0;
padding: 1rem 1.25rem; padding: 0.875rem 1.125rem;
margin: 1.5rem 0; margin: 1.5rem 0;
} }
.callout-note { .callout-note {
border-left-color: #6366f1; border-left-color: var(--text-muted);
background-color: rgba(99, 102, 241, 0.08); background-color: var(--bg-elevated);
} }
.callout-tip { .callout-tip {
border-left-color: #10b981; border-left-color: #10b981;
background-color: rgba(16, 185, 129, 0.08); background-color: rgba(16, 185, 129, 0.06);
} }
.callout-important { .callout-important {
border-left-color: #8b5cf6; border-left-color: #3b82f6;
background-color: rgba(139, 92, 246, 0.08); background-color: rgba(59, 130, 246, 0.06);
} }
.callout-warning { .callout-warning {
border-left-color: #f59e0b; border-left-color: #f59e0b;
background-color: rgba(245, 158, 11, 0.08); background-color: rgba(245, 158, 11, 0.06);
} }
.callout-caution { .callout-caution {
border-left-color: #ef4444; border-left-color: #ef4444;
background-color: rgba(239, 68, 68, 0.08); background-color: rgba(239, 68, 68, 0.06);
} }
/* Prose markdown typography for both Light and Dark modes */ /* Prose Typography */
.prose { .prose {
color: var(--text-secondary) !important; color: var(--text-secondary) !important;
max-width: 100% !important; max-width: 100% !important;
@@ -256,15 +232,16 @@ body {
} }
.prose a { .prose a {
color: var(--accent) !important; color: var(--text-primary) !important;
text-decoration: none; text-decoration: underline;
text-decoration-color: var(--border-color);
text-underline-offset: 3px;
font-weight: 500; font-weight: 500;
transition: color 0.15s ease; transition: all 0.15s ease;
} }
.prose a:hover { .prose a:hover {
color: var(--accent-hover) !important; text-decoration-color: var(--text-primary);
text-decoration: underline;
} }
.prose table { .prose table {
@@ -273,7 +250,7 @@ body {
border-spacing: 0; border-spacing: 0;
margin: 2rem 0; margin: 2rem 0;
font-size: 0.875rem; font-size: 0.875rem;
border-radius: 0.75rem; border-radius: 0.5rem;
overflow: hidden; overflow: hidden;
border: 1px solid var(--border-color); border: 1px solid var(--border-color);
background: var(--bg-surface); background: var(--bg-surface);
@@ -281,7 +258,7 @@ body {
.prose th, .prose th,
.prose td { .prose td {
padding: 0.875rem 1.125rem; padding: 0.75rem 1rem;
text-align: left; text-align: left;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
@@ -290,9 +267,9 @@ body {
background: var(--bg-elevated); background: var(--bg-elevated);
color: var(--text-primary); color: var(--text-primary);
font-weight: 600; font-weight: 600;
font-size: 0.8125rem; font-size: 0.75rem;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.06em; letter-spacing: 0.08em;
font-family: var(--font-mono); font-family: var(--font-mono);
} }
@@ -316,8 +293,8 @@ body {
background: var(--code-block-bg) !important; background: var(--code-block-bg) !important;
color: var(--pre-text) !important; color: var(--pre-text) !important;
border: 1px solid var(--border-code); border: 1px solid var(--border-code);
border-radius: 0.75rem; border-radius: 0.5rem;
padding: 1.25rem; padding: 1rem 1.25rem;
overflow-x: auto; overflow-x: auto;
position: relative; position: relative;
font-family: var(--font-mono); font-family: var(--font-mono);
@@ -333,8 +310,8 @@ body {
background: var(--code-inline-bg); background: var(--code-inline-bg);
color: var(--code-inline-text); color: var(--code-inline-text);
border: 1px solid var(--code-inline-border); border: 1px solid var(--code-inline-border);
padding: 0.15rem 0.4rem; padding: 0.15rem 0.35rem;
border-radius: 0.375rem; border-radius: 0.25rem;
font-weight: 500; font-weight: 500;
} }
@@ -344,11 +321,11 @@ body {
} }
.prose blockquote { .prose blockquote {
border-left: 3px solid var(--brand); border-left: 2px solid var(--text-muted);
background: rgba(99, 102, 241, 0.08); background: var(--bg-elevated);
border-radius: 0 0.5rem 0.5rem 0; border-radius: 0 0.375rem 0.375rem 0;
margin: 1.75em 0; margin: 1.5em 0;
padding: 0.875em 1.25em; padding: 0.75em 1.25em;
font-style: normal; font-style: normal;
color: var(--text-secondary); color: var(--text-secondary);
} }
@@ -358,7 +335,7 @@ body {
margin: 2.5rem 0; margin: 2.5rem 0;
} }
/* Syntax Highlighting Tokens (Highlight.js) */ /* Syntax Highlighting */
.hljs { .hljs {
color: var(--pre-text); color: var(--pre-text);
background: transparent; background: transparent;
@@ -446,32 +423,14 @@ body {
font-weight: bold; font-weight: bold;
} }
/* Keyframes & Animation utilities */ /* Transitions */
@keyframes fadeIn { @keyframes fadeIn {
from { opacity: 0; transform: translateY(6px); } from { opacity: 0; transform: translateY(4px); }
to { opacity: 1; transform: translateY(0); } to { opacity: 1; transform: translateY(0); }
} }
@keyframes slideDown {
from { opacity: 0; transform: translateY(-8px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes pulseGlow {
0%, 100% { opacity: 0.6; }
50% { opacity: 1; filter: drop-shadow(0 0 8px var(--brand)); }
}
.animate-fade-in { .animate-fade-in {
animation: fadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards; animation: fadeIn 0.18s ease-out forwards;
}
.animate-slide-down {
animation: slideDown 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
}
.animate-pulse-glow {
animation: pulseGlow 2.5s ease-in-out infinite;
} }
/* Print & PDF Export Stylesheet */ /* Print & PDF Export Stylesheet */
@@ -488,7 +447,7 @@ body {
html, body { html, body {
background-color: #ffffff !important; background-color: #ffffff !important;
color: #0f172a !important; color: #09090b !important;
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important; font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
font-size: 10pt !important; font-size: 10pt !important;
line-height: 1.5 !important; line-height: 1.5 !important;
@@ -547,18 +506,18 @@ body {
.code-block-container { .code-block-container {
break-inside: avoid !important; break-inside: avoid !important;
page-break-inside: avoid !important; page-break-inside: avoid !important;
border: 1px solid #cbd5e1 !important; border: 1px solid #d4d4d8 !important;
background-color: #f8fafc !important; background-color: #f4f4f5 !important;
border-radius: 4px !important; border-radius: 4px !important;
margin: 8pt 0 !important; margin: 8pt 0 !important;
overflow: hidden !important; overflow: hidden !important;
} }
.code-block-header { .code-block-header {
border-bottom: 1px solid #cbd5e1 !important; border-bottom: 1px solid #d4d4d8 !important;
background-color: #f1f5f9 !important; background-color: #e4e4e7 !important;
padding: 3pt 8pt !important; padding: 3pt 8pt !important;
color: #334155 !important; color: #27272a !important;
} }
.code-block-wrapper pre { .code-block-wrapper pre {
+17 -19
View File
@@ -3,12 +3,12 @@ import "./globals.css";
import Header from "@/components/Header"; import Header from "@/components/Header";
import Sidebar from "@/components/Sidebar"; import Sidebar from "@/components/Sidebar";
import Link from "next/link"; import Link from "next/link";
import { Database, Activity, Sparkles, Terminal } from "lucide-react"; import { Database } from "lucide-react";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "MCPedia — Knowledge Base for Humans and AI Agents", title: "MCPedia — Archival Knowledge Base for Humans & AI Agents",
description: description:
"A content-first knowledge base. Humans read the Web UI; AI agents use the MCP server. Both share one Core.", "A content-first knowledge base. Humans read the Web UI; AI agents use the Model Context Protocol (MCP). Both share one Core.",
}; };
export default function RootLayout({ export default function RootLayout({
@@ -37,20 +37,16 @@ export default function RootLayout({
}} }}
/> />
</head> </head>
<body className="min-h-screen bg-[var(--bg-app)] text-[var(--text-secondary)] font-sans antialiased selection:bg-[var(--brand)]/30 selection:text-white flex flex-col transition-colors duration-150 tech-grid-bg relative overflow-x-hidden"> <body className="min-h-screen bg-[var(--bg-app)] text-[var(--text-secondary)] font-sans antialiased selection:bg-[var(--text-primary)] selection:text-[var(--bg-app)] flex flex-col transition-colors duration-150">
{/* Top ambient radial glow effect */}
<div className="absolute top-0 left-1/2 -translate-x-1/2 w-[48rem] h-64 bg-indigo-500/10 dark:bg-indigo-600/15 blur-[120px] pointer-events-none rounded-full" />
<div className="absolute top-48 left-1/4 w-80 h-80 bg-cyan-500/5 dark:bg-cyan-500/10 blur-[100px] pointer-events-none rounded-full" />
<div hidden> <div hidden>
<div id="a11y-live-region" aria-live="polite" aria-atomic="true" /> <div id="a11y-live-region" aria-live="polite" aria-atomic="true" />
</div> </div>
<Header /> <Header />
<div className="mx-auto max-w-7xl w-full flex-1 flex flex-col relative z-10"> <div className="mx-auto max-w-7xl w-full flex-1 flex flex-col">
<div className="flex flex-1"> <div className="flex flex-1">
<aside className="hidden lg:block w-72 shrink-0 border-r border-[var(--border-color)] sticky top-15 h-[calc(100vh-3.75rem)] overflow-y-auto bg-[var(--bg-app)]/80 backdrop-blur-md"> <aside className="hidden lg:block w-72 shrink-0 border-r border-[var(--border-color)] sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto bg-[var(--bg-app)]">
<Sidebar /> <Sidebar />
</aside> </aside>
<main className="flex-1 min-w-0"> <main className="flex-1 min-w-0">
@@ -61,26 +57,28 @@ export default function RootLayout({
</div> </div>
</div> </div>
{/* Global Telemetry & Status Footer */} {/* Global Precision Status Footer */}
<footer className="no-print border-t border-[var(--border-color)] bg-[var(--bg-surface)]/60 backdrop-blur-md py-4 text-xs text-[var(--text-dim)] relative z-10"> <footer className="no-print border-t border-[var(--border-color)] bg-[var(--bg-surface)] py-4 text-xs text-[var(--text-muted)]">
<div className="mx-auto max-w-7xl px-4 sm:px-8 flex flex-col sm:flex-row items-center justify-between gap-3 font-mono"> <div className="mx-auto max-w-7xl px-4 sm:px-8 flex flex-col sm:flex-row items-center justify-between gap-3 font-mono">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
<span>MCPedia Intelligence Engine</span> <span className="text-[var(--text-primary)] font-semibold">MCPedia</span>
<span>·</span> <span className="text-[var(--border-color)]">/</span>
<span className="text-[var(--text-muted)]">PostgreSQL 16 Core</span> <span>PostgreSQL 16 Core</span>
<span className="text-[var(--border-color)]">/</span>
<span>pgvector</span>
</div> </div>
<div className="flex items-center gap-4 text-[11px] text-[var(--text-muted)]"> <div className="flex items-center gap-4 text-[11px] text-[var(--text-muted)]">
<Link href="/search" className="hover:text-[var(--brand)] dark:hover:text-[var(--accent)] transition-colors"> <Link href="/search" className="hover:text-[var(--text-primary)] transition-colors">
RRF Hybrid Search RRF Hybrid Search
</Link> </Link>
<span>·</span> <span>·</span>
<Link href="/export/pdf" className="hover:text-[var(--brand)] dark:hover:text-[var(--accent)] transition-colors"> <Link href="/export/pdf" className="hover:text-[var(--text-primary)] transition-colors">
Publication PDF Publication PDF
</Link> </Link>
<span>·</span> <span>·</span>
<span className="text-[var(--text-dim)]">Native MCP :4021</span> <span>HTTP :4021</span>
</div> </div>
</div> </div>
</footer> </footer>
+21 -24
View File
@@ -3,7 +3,7 @@
import { useState } from "react"; import { useState } from "react";
import { useRouter } from "next/navigation"; import { useRouter } from "next/navigation";
import Link from "next/link"; import Link from "next/link";
import { Lock, Key, Eye, EyeOff, ArrowLeft, ShieldCheck } from "lucide-react"; import { Lock, Key, Eye, EyeOff, ArrowLeft } from "lucide-react";
export default function LoginPage() { export default function LoginPage() {
const router = useRouter(); const router = useRouter();
@@ -39,22 +39,19 @@ export default function LoginPage() {
return ( return (
<div className="flex min-h-[65vh] items-center justify-center px-4"> <div className="flex min-h-[65vh] items-center justify-center px-4">
<div className="w-full max-w-md bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-3xl p-6 sm:p-8 shadow-2xl relative overflow-hidden"> <div className="w-full max-w-md bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 sm:p-8 shadow-xs relative">
{/* Top decorative glow */} <div className="text-center mb-7">
<div className="absolute -top-12 left-1/2 -translate-x-1/2 w-48 h-24 bg-indigo-500/20 blur-2xl pointer-events-none rounded-full" /> <div className="w-10 h-10 rounded-lg bg-[var(--brand)] text-[var(--brand-fg)] font-bold text-sm flex items-center justify-center mx-auto mb-3 shadow-xs font-mono">
<Lock className="w-4 h-4" />
<div className="text-center mb-8">
<div className="w-12 h-12 rounded-2xl bg-gradient-to-br from-indigo-500 to-violet-700 text-white font-bold text-xl flex items-center justify-center mx-auto mb-3 shadow-lg shadow-indigo-500/25">
<Lock className="w-6 h-6" />
</div> </div>
<h1 className="text-2xl font-extrabold text-[var(--text-primary)] tracking-tight">Admin Vault Authentication</h1> <h1 className="text-xl font-bold text-[var(--text-primary)] tracking-tight">Admin Authentication</h1>
<p className="text-xs text-[var(--text-muted)] mt-1.5"> <p className="text-xs text-[var(--text-muted)] mt-1">
Enter your admin password to manage documents and trigger schema mutations. Enter administrative password to authorize document mutations.
</p> </p>
</div> </div>
{error && ( {error && (
<div className="p-3.5 mb-6 rounded-xl border border-rose-500/30 bg-rose-500/10 text-rose-500 dark:text-rose-400 text-xs font-medium animate-fade-in"> <div className="p-3 mb-5 rounded-lg border border-rose-500/30 bg-rose-500/10 text-rose-500 dark:text-rose-400 text-xs font-medium animate-fade-in">
{error} {error}
</div> </div>
)} )}
@@ -62,26 +59,26 @@ export default function LoginPage() {
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<div> <div>
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5 font-mono"> <label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5 font-mono">
Admin Password Password
</label> </label>
<div className="relative"> <div className="relative">
<input <input
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}
value={password} value={password}
onChange={(e) => setPassword(e.target.value)} onChange={(e) => setPassword(e.target.value)}
placeholder="Enter password..." placeholder="Enter admin password..."
className="w-full pl-10 pr-10 py-3 bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl text-sm text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-2 focus:ring-[var(--brand)]/20 transition-all" className="w-full pl-9 pr-10 py-2.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-lg text-sm text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--text-primary)] transition-colors font-mono"
required required
autoFocus autoFocus
/> />
<Key className="w-4 h-4 text-[var(--text-dim)] absolute left-3.5 top-3.5 pointer-events-none" /> <Key className="w-3.5 h-3.5 text-[var(--text-dim)] absolute left-3 top-3.5 pointer-events-none" />
<button <button
type="button" type="button"
onClick={() => setShowPassword(!showPassword)} onClick={() => setShowPassword(!showPassword)}
className="absolute right-3.5 top-3 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] p-1 cursor-pointer" className="absolute right-3 top-2.5 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] p-1 cursor-pointer"
aria-label={showPassword ? "Hide password" : "Show password"} aria-label={showPassword ? "Hide password" : "Show password"}
> >
{showPassword ? <EyeOff className="w-4 h-4" /> : <Eye className="w-4 h-4" />} {showPassword ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
</button> </button>
</div> </div>
</div> </div>
@@ -89,24 +86,24 @@ export default function LoginPage() {
<button <button
type="submit" type="submit"
disabled={loading} disabled={loading}
className="w-full py-3 bg-[var(--brand)] hover:bg-[var(--brand-hover)] text-white rounded-xl transition-all font-semibold text-sm shadow-md shadow-[var(--brand)]/20 hover:shadow-[var(--brand)]/35 disabled:opacity-50 flex items-center justify-center gap-2 cursor-pointer" className="w-full py-2.5 bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] rounded-lg transition-all font-semibold text-xs shadow-xs disabled:opacity-50 flex items-center justify-center gap-2 cursor-pointer uppercase tracking-wider font-mono"
> >
{loading && ( {loading && (
<div className="w-4 h-4 border-2 border-white border-t-transparent rounded-full animate-spin" /> <div className="w-3.5 h-3.5 border-2 border-current border-t-transparent rounded-full animate-spin" />
)} )}
<span>{loading ? "Authenticating Session..." : "Authorize Access"}</span> <span>{loading ? "AUTHENTICATING..." : "AUTHORIZE_ACCESS"}</span>
</button> </button>
</form> </form>
<div className="mt-8 pt-6 border-t border-[var(--border-color)] flex items-center justify-between text-xs text-[var(--text-muted)]"> <div className="mt-7 pt-5 border-t border-[var(--border-color)] flex items-center justify-between text-xs text-[var(--text-muted)]">
<Link <Link
href="/" href="/"
className="hover:text-[var(--text-primary)] transition-colors flex items-center gap-1" className="hover:text-[var(--text-primary)] transition-colors flex items-center gap-1"
> >
<ArrowLeft className="w-3.5 h-3.5" /> <ArrowLeft className="w-3.5 h-3.5" />
<span>Return to Knowledge Base</span> <span>Return to Home</span>
</Link> </Link>
<span className="text-[11px] text-[var(--text-dim)] font-mono">Secure HTTP Cookie</span> <span className="text-[10px] text-[var(--text-dim)] font-mono">HTTP Cookie</span>
</div> </div>
</div> </div>
</div> </div>
+53 -60
View File
@@ -4,19 +4,15 @@ import { getSectionMeta, type SectionConfig } from "@mcpedia/config";
import McpConfigSnippet from "@/components/McpConfigSnippet"; import McpConfigSnippet from "@/components/McpConfigSnippet";
import InteractiveTopology from "@/components/InteractiveTopology"; import InteractiveTopology from "@/components/InteractiveTopology";
import { import {
Sparkles,
Search, Search,
Plus, Plus,
BookOpen, BookOpen,
ArrowRight, ArrowRight,
Database, Database,
Cpu,
Layers, Layers,
Activity,
Folder, Folder,
FileText, FileText,
Clock, Clock,
CheckCircle2,
Terminal, Terminal,
} from "lucide-react"; } from "lucide-react";
@@ -102,11 +98,11 @@ function renderTree(nodes: TreeNode[]) {
className="flex items-center gap-2 text-xs py-1 px-2 rounded-md text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)] transition-colors group" className="flex items-center gap-2 text-xs py-1 px-2 rounded-md text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)] transition-colors group"
title={node.title} title={node.title}
> >
<span className="shrink-0 text-[var(--text-dim)] group-hover:text-[var(--brand)] transition-colors"> <span className="shrink-0 text-[var(--text-dim)] group-hover:text-[var(--text-primary)] transition-colors">
{node.isLeaf && node.children.length === 0 ? ( {node.isLeaf && node.children.length === 0 ? (
<FileText className="w-3 h-3" /> <FileText className="w-3.5 h-3.5" />
) : ( ) : (
<Folder className="w-3 h-3" /> <Folder className="w-3.5 h-3.5" />
)} )}
</span> </span>
<span className="truncate">{node.title || node.name}</span> <span className="truncate">{node.title || node.name}</span>
@@ -133,7 +129,7 @@ function SectionTree({ section, docs }: { section: string; docs: { slug: string;
{tree.length > 0 ? ( {tree.length > 0 ? (
renderTree(tree) renderTree(tree)
) : ( ) : (
<p className="text-xs text-[var(--text-dim)] pl-2">No documents yet</p> <p className="text-xs text-[var(--text-dim)] pl-2">No documents</p>
)} )}
</div> </div>
); );
@@ -156,30 +152,28 @@ export default async function HomePage() {
<div className="space-y-16"> <div className="space-y-16">
{/* Hero Section */} {/* Hero Section */}
<section className="relative pt-2 pb-4"> <section className="relative pt-2 pb-4">
{/* Floating status pill */} {/* Status indicator stamp */}
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[var(--brand)]/10 border border-[var(--brand)]/25 text-[var(--brand)] dark:text-[var(--accent)] text-xs font-semibold mb-6 shadow-xs animate-fade-in"> <div className="inline-flex items-center gap-2 px-2.5 py-1 rounded-md bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-secondary)] text-xs font-mono mb-6">
<span className="w-2 h-2 rounded-full bg-emerald-500 animate-pulse" /> <span className="w-1.5 h-1.5 rounded-full bg-emerald-500" />
<span>Model Context Protocol · PostgreSQL Source of Truth</span> <span>PostgreSQL 16 Core · Model Context Protocol</span>
</div> </div>
{/* Primary Title */} {/* Primary Title with strong monolithic weight */}
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold text-[var(--text-primary)] tracking-tight mb-5 leading-[1.12]"> <h1 className="text-4xl sm:text-5xl lg:text-6xl font-extrabold text-[var(--text-primary)] tracking-tight mb-5 leading-[1.1]">
Knowledge Base for <br /> Knowledge Base for <br />
<span className="bg-gradient-to-r from-indigo-500 via-indigo-600 to-cyan-500 dark:to-cyan-400 bg-clip-text text-transparent"> Humans and AI Agents.
Humans and AI Agents
</span>
</h1> </h1>
{/* Subtitle */} {/* Subtitle */}
<p className="text-base sm:text-lg text-[var(--text-muted)] max-w-2xl leading-relaxed mb-8"> <p className="text-base sm:text-lg text-[var(--text-muted)] max-w-2xl leading-relaxed mb-8">
A high-performance technical knowledge repository. Browse structured notes and CTF writeups in your browser, or equip AI coding assistants directly via native MCP tools. A high-performance technical knowledge repository. Humans read the web interface; AI assistants connect directly via native MCP tools. Both share one unified database source of truth.
</p> </p>
{/* Quick Action Buttons */} {/* Quick Action Buttons */}
<div className="flex flex-wrap items-center gap-3 mb-10"> <div className="flex flex-wrap items-center gap-3 mb-10">
<Link <Link
href="/docs" href="/docs"
className="inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--brand)] hover:bg-[var(--brand-hover)] text-white rounded-xl font-semibold text-sm transition-all shadow-md shadow-[var(--brand)]/25 hover:shadow-[var(--brand)]/40 hover:-translate-y-0.5 cursor-pointer" className="inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--brand)] hover:opacity-90 text-[var(--brand-fg)] rounded-lg font-semibold text-sm transition-all shadow-xs cursor-pointer"
> >
<BookOpen className="w-4 h-4" /> <BookOpen className="w-4 h-4" />
<span>Browse Documentation</span> <span>Browse Documentation</span>
@@ -188,15 +182,15 @@ export default async function HomePage() {
<Link <Link
href="/search" href="/search"
className="inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-secondary)] hover:text-[var(--text-primary)] rounded-xl font-semibold text-sm transition-all shadow-xs hover:-translate-y-0.5" className="inline-flex items-center gap-2 px-5 py-2.5 bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--text-muted)] text-[var(--text-primary)] rounded-lg font-semibold text-sm transition-colors shadow-xs"
> >
<Search className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <Search className="w-4 h-4 text-[var(--text-muted)]" />
<span>Hybrid Search Console</span> <span>Hybrid Search</span>
</Link> </Link>
<Link <Link
href="/create" href="/create"
className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-xl text-sm transition-all shadow-xs" className="inline-flex items-center gap-1.5 px-4 py-2.5 bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg text-sm transition-colors shadow-xs"
> >
<Plus className="w-4 h-4" /> <Plus className="w-4 h-4" />
<span>Create Document</span> <span>Create Document</span>
@@ -204,44 +198,44 @@ export default async function HomePage() {
</div> </div>
{/* Live Telemetry Metric Strip */} {/* Live Telemetry Metric Strip */}
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 p-4 sm:p-5 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl shadow-sm"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-3 p-4 sm:p-5 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl shadow-xs">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-indigo-500/10 text-indigo-600 dark:text-indigo-400"> <div className="p-2 rounded-lg bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)]">
<Database className="w-5 h-5" /> <Database className="w-4 h-4" />
</div> </div>
<div> <div>
<div className="text-xl font-bold text-[var(--text-primary)]">{all.length}</div> <div className="text-xl font-bold text-[var(--text-primary)] font-mono">{all.length}</div>
<div className="text-[11px] text-[var(--text-muted)] font-mono">Documents in DB</div> <div className="text-[11px] text-[var(--text-muted)] font-mono">DB Documents</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-cyan-500/10 text-cyan-600 dark:text-cyan-400"> <div className="p-2 rounded-lg bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)]">
<Layers className="w-5 h-5" /> <Layers className="w-4 h-4" />
</div> </div>
<div> <div>
<div className="text-xl font-bold text-[var(--text-primary)]">{sections.length}</div> <div className="text-xl font-bold text-[var(--text-primary)] font-mono">{sections.length}</div>
<div className="text-[11px] text-[var(--text-muted)] font-mono">Active Sections</div> <div className="text-[11px] text-[var(--text-muted)] font-mono">Active Sections</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-violet-500/10 text-violet-600 dark:text-violet-400"> <div className="p-2 rounded-lg bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)]">
<Search className="w-5 h-5" /> <Search className="w-4 h-4" />
</div> </div>
<div> <div>
<div className="text-xl font-bold text-[var(--brand)] dark:text-[var(--accent)]">RRF Hybrid</div> <div className="text-xl font-bold text-[var(--text-primary)] font-mono">RRF</div>
<div className="text-[11px] text-[var(--text-muted)] font-mono">FTS + Cosine Vectors</div> <div className="text-[11px] text-[var(--text-muted)] font-mono">FTS + Cosine Vectors</div>
</div> </div>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<div className="p-2.5 rounded-xl bg-emerald-500/10 text-emerald-600 dark:text-emerald-400"> <div className="p-2 rounded-lg bg-[var(--bg-elevated)] text-[var(--text-primary)] border border-[var(--border-color)]">
<Terminal className="w-5 h-5" /> <Terminal className="w-4 h-4" />
</div> </div>
<div> <div>
<div className="text-xl font-bold text-emerald-600 dark:text-emerald-400">13 Tools</div> <div className="text-xl font-bold text-[var(--text-primary)] font-mono">13 Tools</div>
<div className="text-[11px] text-[var(--text-muted)] font-mono">Streamable HTTP :4021</div> <div className="text-[11px] text-[var(--text-muted)] font-mono">HTTP :4021 MCP</div>
</div> </div>
</div> </div>
</div> </div>
@@ -254,11 +248,11 @@ export default async function HomePage() {
{/* Browse Knowledge by Section */} {/* Browse Knowledge by Section */}
<section> <section>
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Layers className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <Layers className="w-4 h-4 text-[var(--text-muted)]" />
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono">
Browse Knowledge by Section Knowledge Sections
</h2> </h2>
</div> </div>
</div> </div>
@@ -269,19 +263,19 @@ export default async function HomePage() {
return ( return (
<div <div
key={s.id} key={s.id}
className="group flex flex-col justify-between bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl p-5 transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group flex flex-col justify-between bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-xl p-5 transition-all shadow-xs"
> >
<div> <div>
<div className="flex items-center justify-between gap-2 mb-3"> <div className="flex items-center justify-between gap-2 mb-3">
<span className="text-2xl">{s.icon}</span> <span className="text-2xl">{s.icon}</span>
<Link <Link
href={`/${s.id}`} href={`/${s.id}`}
className="text-xs font-mono text-[var(--brand)] dark:text-[var(--accent)] hover:underline font-semibold" className="text-xs font-mono text-[var(--text-muted)] hover:text-[var(--text-primary)] font-semibold"
> >
{sectionDocs.length} docs → {sectionDocs.length} docs →
</Link> </Link>
</div> </div>
<h3 className="font-bold text-base text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors mb-1.5"> <h3 className="font-bold text-sm text-[var(--text-primary)] mb-1">
{s.label} {s.label}
</h3> </h3>
<p className="text-xs text-[var(--text-muted)] mb-4 line-clamp-2 leading-relaxed"> <p className="text-xs text-[var(--text-muted)] mb-4 line-clamp-2 leading-relaxed">
@@ -305,33 +299,33 @@ export default async function HomePage() {
{/* Recently Updated Knowledge */} {/* Recently Updated Knowledge */}
{recent.length > 0 && ( {recent.length > 0 && (
<section> <section>
<div className="flex items-center justify-between mb-6"> <div className="flex items-center justify-between mb-5">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Clock className="w-4 h-4 text-[var(--brand)] dark:text-[var(--accent)]" /> <Clock className="w-4 h-4 text-[var(--text-muted)]" />
<h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono"> <h2 className="text-xs font-bold text-[var(--text-muted)] uppercase tracking-wider font-mono">
Recently Updated Knowledge Recently Updated
</h2> </h2>
</div> </div>
<Link <Link
href="/docs" href="/docs"
className="text-xs text-[var(--brand)] dark:text-[var(--accent)] hover:underline font-semibold flex items-center gap-1" className="text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] font-semibold flex items-center gap-1 font-mono"
> >
<span>View all ({all.length})</span> <span>View all ({all.length})</span>
<ArrowRight className="w-3.5 h-3.5" /> <ArrowRight className="w-3 h-3" />
</Link> </Link>
</div> </div>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3.5">
{recent.map((d) => { {recent.map((d) => {
const sInfo = getSectionMeta(d.section); const sInfo = getSectionMeta(d.section);
return ( return (
<Link <Link
key={d.slug} key={d.slug}
href={`/${d.slug}`} href={`/${d.slug}`}
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl p-5 transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-xl p-4.5 transition-all shadow-xs"
> >
<div className="flex items-center justify-between gap-2 mb-2.5"> <div className="flex items-center justify-between gap-2 mb-2">
<span className="text-[11px] font-mono font-semibold text-[var(--brand)] dark:text-[var(--accent)] uppercase flex items-center gap-1"> <span className="text-[11px] font-mono font-semibold text-[var(--text-muted)] uppercase flex items-center gap-1">
<span>{sInfo?.icon}</span> <span>{sInfo?.icon}</span>
<span>{d.section}</span> <span>{d.section}</span>
</span> </span>
@@ -342,15 +336,15 @@ export default async function HomePage() {
})} })}
</time> </time>
</div> </div>
<h3 className="font-bold text-sm text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors line-clamp-1 mb-2"> <h3 className="font-semibold text-sm text-[var(--text-primary)] group-hover:underline line-clamp-1 mb-2">
{d.title} {d.title}
</h3> </h3>
{d.tags.length > 0 && ( {d.tags.length > 0 && (
<div className="flex flex-wrap gap-1 mt-3"> <div className="flex flex-wrap gap-1 mt-2.5">
{d.tags.slice(0, 3).map((t) => ( {d.tags.slice(0, 3).map((t) => (
<span <span
key={t} key={t}
className="text-[10px] px-2 py-0.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded-md text-[var(--text-muted)] font-mono" className="text-[10px] px-1.5 py-0.2 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] font-mono"
> >
#{t} #{t}
</span> </span>
@@ -367,11 +361,10 @@ export default async function HomePage() {
{/* Connect AI Assistant Showcase */} {/* Connect AI Assistant Showcase */}
<section className="border-t border-[var(--border-color)] pt-12 pb-6"> <section className="border-t border-[var(--border-color)] pt-12 pb-6">
<div className="mb-6"> <div className="mb-6">
<div className="inline-flex items-center gap-1.5 px-3 py-1 rounded-full bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] text-xs font-mono mb-3"> <div className="inline-flex items-center gap-1.5 px-2.5 py-0.5 rounded bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] text-xs font-mono mb-2">
<Sparkles className="w-3.5 h-3.5 text-indigo-500" /> <span>[ MCP_INTEGRATION ]</span>
<span>Connect Coding Assistants</span>
</div> </div>
<h2 className="text-2xl sm:text-3xl font-extrabold text-[var(--text-primary)] tracking-tight mb-2"> <h2 className="text-2xl font-bold text-[var(--text-primary)] tracking-tight mb-1.5">
Model Context Protocol Configuration Model Context Protocol Configuration
</h2> </h2>
<p className="text-xs sm:text-sm text-[var(--text-muted)] max-w-xl leading-relaxed"> <p className="text-xs sm:text-sm text-[var(--text-muted)] max-w-xl leading-relaxed">
+37 -54
View File
@@ -6,16 +6,7 @@ import { useSearchParams } from "next/navigation";
import { DEFAULT_SECTIONS, type SectionConfig } from "@mcpedia/config/sections"; import { DEFAULT_SECTIONS, type SectionConfig } from "@mcpedia/config/sections";
import { import {
Search, Search,
Sparkles,
Zap,
Cpu,
Layers,
FileText,
Clock,
ArrowRight,
Filter,
X, X,
Compass,
} from "lucide-react"; } from "lucide-react";
interface DocHit { interface DocHit {
@@ -100,21 +91,16 @@ function SearchContent() {
<div className="max-w-4xl space-y-8"> <div className="max-w-4xl space-y-8">
{/* Header */} {/* Header */}
<div> <div>
<div className="flex items-center gap-2 mb-2"> <h1 className="text-3xl font-extrabold text-[var(--text-primary)] tracking-tight mb-2">
<span className="p-2 rounded-xl bg-[var(--brand)]/15 text-[var(--brand)] dark:text-[var(--accent)]"> Multi-Modal Search Console
<Search className="w-5 h-5" /> </h1>
</span> <p className="text-xs sm:text-sm text-[var(--text-muted)] leading-relaxed">
<h1 className="text-3xl font-extrabold text-[var(--text-primary)] tracking-tight"> Query across knowledge documents and CTF writeups using PostgreSQL Full-Text Search (tsvector), Cosine Vector Embeddings, or Reciprocal Rank Fusion (RRF).
Multi-Modal Search Console
</h1>
</div>
<p className="text-xs sm:text-sm text-[var(--text-muted)]">
Search across documentation, CTF writeups, and research notes using PostgreSQL Full-Text Search, Cosine Embeddings, or Reciprocal Rank Fusion.
</p> </p>
</div> </div>
{/* Search Box & Controls */} {/* Search Box & Controls */}
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl p-5 sm:p-6 shadow-sm space-y-5"> <div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-4 sm:p-5 shadow-xs space-y-4">
{/* Search Input Bar */} {/* Search Input Bar */}
<div className="relative"> <div className="relative">
<input <input
@@ -122,17 +108,17 @@ function SearchContent() {
value={query} value={query}
onChange={handleChange} onChange={handleChange}
placeholder="Search by keywords, challenge names, tools, concepts..." placeholder="Search by keywords, challenge names, tools, concepts..."
className="w-full pl-12 pr-10 py-3.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-2 focus:ring-[var(--brand)]/20 text-base transition-all" className="w-full pl-10 pr-9 py-3 bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-lg text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--text-primary)] text-sm transition-all"
autoFocus autoFocus
/> />
<Search className="w-5 h-5 text-[var(--brand)] dark:text-[var(--accent)] absolute left-4 top-4 pointer-events-none" /> <Search className="w-4 h-4 text-[var(--text-muted)] absolute left-3.5 top-3.5 pointer-events-none" />
{query && ( {query && (
<button <button
onClick={() => { onClick={() => {
setQuery(""); setQuery("");
setResults([]); setResults([]);
}} }}
className="absolute right-3.5 top-3.5 p-1 text-[var(--text-dim)] hover:text-[var(--text-primary)] rounded-lg hover:bg-[var(--bg-surface)]" className="absolute right-3 top-3 p-0.5 text-[var(--text-dim)] hover:text-[var(--text-primary)] rounded"
aria-label="Clear query" aria-label="Clear query"
> >
<X className="w-4 h-4" /> <X className="w-4 h-4" />
@@ -141,23 +127,23 @@ function SearchContent() {
</div> </div>
{/* Algorithm & Filter Controls */} {/* Algorithm & Filter Controls */}
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pt-3 border-t border-[var(--border-color)] text-xs"> <div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pt-2.5 border-t border-[var(--border-color)] text-xs">
{/* Mode Selector */} {/* Mode Selector */}
<div className="flex items-center gap-1.5 flex-wrap"> <div className="flex items-center gap-1.5 flex-wrap">
<span className="text-[var(--text-dim)] font-mono uppercase font-semibold mr-1">Algorithm:</span> <span className="text-[var(--text-dim)] font-mono uppercase text-[10px] mr-1">Algorithm:</span>
{[ {[
{ id: "hybrid", label: "⚡ Hybrid (RRF)", desc: "Reciprocal Rank Fusion (FTS + Vector)" }, { id: "hybrid", label: "Hybrid (RRF)", desc: "Reciprocal Rank Fusion (FTS + Vector)" },
{ id: "keyword", label: "🔍 Keyword (FTS)", desc: "PostgreSQL GIN tsvector" }, { id: "keyword", label: "Keyword (FTS)", desc: "PostgreSQL GIN tsvector" },
{ id: "semantic", label: "🧠 Semantic Vector", desc: "Cosine Similarity over Embeddings" }, { id: "semantic", label: "Semantic Vector", desc: "Cosine Similarity Embeddings" },
].map((m) => ( ].map((m) => (
<button <button
key={m.id} key={m.id}
type="button" type="button"
onClick={() => handleModeChange(m.id as SearchMode)} onClick={() => handleModeChange(m.id as SearchMode)}
title={m.desc} title={m.desc}
className={`px-3 py-1.5 rounded-lg border font-semibold transition-all cursor-pointer ${ className={`px-3 py-1 rounded-md border text-xs font-medium transition-all cursor-pointer ${
mode === m.id mode === m.id
? "bg-[var(--brand)] border-[var(--brand)] text-white shadow-sm shadow-[var(--brand)]/25" ? "bg-[var(--brand)] border-[var(--brand)] text-[var(--brand-fg)] font-semibold shadow-xs"
: "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)]" : "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)]"
}`} }`}
> >
@@ -171,9 +157,9 @@ function SearchContent() {
<button <button
type="button" type="button"
onClick={() => setSelectedSection("all")} onClick={() => setSelectedSection("all")}
className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-colors ${ className={`px-2.5 py-1 rounded-md text-xs font-medium transition-colors ${
selectedSection === "all" selectedSection === "all"
? "bg-[var(--bg-elevated-hover)] text-[var(--text-primary)] font-bold" ? "bg-[var(--bg-elevated-hover)] text-[var(--text-primary)] font-semibold"
: "text-[var(--text-dim)] hover:text-[var(--text-muted)]" : "text-[var(--text-dim)] hover:text-[var(--text-muted)]"
}`} }`}
> >
@@ -184,9 +170,9 @@ function SearchContent() {
key={s.id} key={s.id}
type="button" type="button"
onClick={() => setSelectedSection(s.id)} onClick={() => setSelectedSection(s.id)}
className={`px-2.5 py-1 rounded-lg text-xs font-medium transition-colors ${ className={`px-2.5 py-1 rounded-md text-xs font-medium transition-colors ${
selectedSection === s.id selectedSection === s.id
? "bg-[var(--bg-elevated-hover)] text-[var(--text-primary)] font-bold" ? "bg-[var(--bg-elevated-hover)] text-[var(--text-primary)] font-semibold"
: "text-[var(--text-dim)] hover:text-[var(--text-muted)]" : "text-[var(--text-dim)] hover:text-[var(--text-muted)]"
}`} }`}
> >
@@ -199,19 +185,16 @@ function SearchContent() {
{/* Loading Spinner */} {/* Loading Spinner */}
{loading && ( {loading && (
<div className="flex items-center justify-center gap-3 py-12 text-[var(--text-muted)] text-sm animate-fade-in"> <div className="flex items-center justify-center gap-3 py-10 text-[var(--text-muted)] text-xs font-mono">
<div className="w-5 h-5 border-2 border-[var(--brand)] border-t-transparent rounded-full animate-spin" /> <div className="w-4 h-4 border-2 border-[var(--text-muted)] border-t-transparent rounded-full animate-spin" />
<span>Searching knowledge vector space...</span> <span>SEARCHING_VECTOR_SPACE...</span>
</div> </div>
)} )}
{/* Empty State */} {/* Empty State */}
{!loading && query && filteredResults.length === 0 && ( {!loading && query && filteredResults.length === 0 && (
<div className="text-center py-16 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl shadow-sm space-y-3 animate-fade-in"> <div className="text-center py-12 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl shadow-xs space-y-2 animate-fade-in">
<div className="p-3 w-12 h-12 rounded-2xl bg-[var(--bg-elevated)] text-[var(--text-dim)] mx-auto flex items-center justify-center"> <h3 className="text-sm font-bold text-[var(--text-primary)]">No matching documents found</h3>
<Search className="w-6 h-6" />
</div>
<h3 className="text-base font-bold text-[var(--text-primary)]">No matching documents found</h3>
<p className="text-xs text-[var(--text-muted)] max-w-sm mx-auto"> <p className="text-xs text-[var(--text-muted)] max-w-sm mx-auto">
Try switching algorithm mode to <strong>Hybrid</strong> or <strong>Semantic</strong>, or search with different keywords. Try switching algorithm mode to <strong>Hybrid</strong> or <strong>Semantic</strong>, or search with different keywords.
</p> </p>
@@ -220,8 +203,8 @@ function SearchContent() {
{/* Initial Suggestions */} {/* Initial Suggestions */}
{!loading && !query && ( {!loading && !query && (
<div className="p-6 sm:p-8 text-center bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-2xl space-y-4 shadow-sm"> <div className="p-6 text-center bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl space-y-3 shadow-xs">
<p className="text-xs font-mono uppercase tracking-wider text-[var(--text-dim)]">Popular search queries:</p> <p className="text-[10px] font-mono uppercase tracking-wider text-[var(--text-muted)]">Popular Queries:</p>
<div className="flex justify-center flex-wrap gap-2 text-xs"> <div className="flex justify-center flex-wrap gap-2 text-xs">
{[ {[
"stack alignment", "stack alignment",
@@ -238,7 +221,7 @@ function SearchContent() {
setQuery(term); setQuery(term);
handleSearch(term, mode); handleSearch(term, mode);
}} }}
className="px-3.5 py-1.5 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-all font-mono cursor-pointer" className="px-3 py-1 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-md text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-all font-mono text-xs cursor-pointer"
> >
{term} {term}
</button> </button>
@@ -249,40 +232,40 @@ function SearchContent() {
{/* Results List */} {/* Results List */}
{!loading && filteredResults.length > 0 && ( {!loading && filteredResults.length > 0 && (
<div className="space-y-4 animate-fade-in"> <div className="space-y-3 animate-fade-in">
<div className="flex items-center justify-between text-xs text-[var(--text-dim)] font-mono"> <div className="flex items-center justify-between text-xs text-[var(--text-dim)] font-mono">
<span>Found {filteredResults.length} result{filteredResults.length !== 1 ? "s" : ""}</span> <span>Found {filteredResults.length} result{filteredResults.length !== 1 ? "s" : ""}</span>
<span>Ranked by {mode} score</span> <span>Ranked by {mode} score</span>
</div> </div>
<div className="space-y-3"> <div className="space-y-2.5">
{filteredResults.map((r) => ( {filteredResults.map((r) => (
<Link <Link
key={r.slug} key={r.slug}
href={`/${r.slug}`} href={`/${r.slug}`}
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-2xl p-5 transition-all shadow-xs hover:shadow-md hover:-translate-y-0.5" className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--text-muted)] rounded-xl p-4.5 transition-all shadow-xs"
> >
<div className="flex items-center justify-between gap-3 mb-2.5"> <div className="flex items-center justify-between gap-3 mb-2">
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<span className="text-[10px] font-mono px-2 py-0.5 rounded-md bg-[var(--brand)]/10 border border-[var(--brand)]/30 text-[var(--brand)] dark:text-[var(--accent)] uppercase font-bold"> <span className="text-[10px] font-mono px-1.5 py-0.2 rounded bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)] uppercase font-semibold">
{r.section} {r.section}
</span> </span>
<span className="text-xs text-[var(--text-dim)] font-mono truncate"> <span className="text-xs text-[var(--text-dim)] font-mono truncate">
/{r.slug} /{r.slug}
</span> </span>
</div> </div>
<span className="text-xs font-mono text-[var(--text-muted)] bg-[var(--bg-elevated)] border border-[var(--border-color)] px-2.5 py-0.5 rounded-md font-semibold"> <span className="text-xs font-mono text-[var(--text-dim)] bg-[var(--bg-elevated)] px-2 py-0.5 rounded">
Score: {r.score.toFixed(3)} {r.score.toFixed(3)}
</span> </span>
</div> </div>
<h2 className="text-base font-bold text-[var(--text-primary)] group-hover:text-[var(--brand)] dark:group-hover:text-[var(--accent)] transition-colors mb-2"> <h2 className="text-sm font-semibold text-[var(--text-primary)] group-hover:underline mb-1.5">
{r.title} {r.title}
</h2> </h2>
{r.snippet && ( {r.snippet && (
<p <p
className="text-xs text-[var(--text-muted)] leading-relaxed line-clamp-2 [&>mark]:bg-[var(--brand)]/30 [&>mark]:text-[var(--text-primary)] [&>mark]:px-1 [&>mark]:rounded [&>mark]:font-semibold" className="text-xs text-[var(--text-muted)] leading-relaxed line-clamp-2 [&>mark]:bg-[var(--bg-elevated-hover)] [&>mark]:text-[var(--text-primary)] [&>mark]:font-semibold"
dangerouslySetInnerHTML={{ __html: r.snippet }} dangerouslySetInnerHTML={{ __html: r.snippet }}
/> />
)} )}