fix(theme): support full dynamic Light Mode and Dark Mode with CSS variables and storage sync
This commit is contained in:
@@ -54,47 +54,47 @@ 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-3.5 border-t border-[#1f2022]">
|
<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-[#141517] border-[#23252a] text-[#d0d6e0]";
|
let colorClass = "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-secondary)]";
|
||||||
let icon = "🏷️";
|
let icon = "🏷️";
|
||||||
let displayValue: string;
|
let displayValue: string;
|
||||||
|
|
||||||
if (typeof value === "number") {
|
if (typeof value === "number") {
|
||||||
colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#7170ff]";
|
colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff]";
|
||||||
icon = "🎯";
|
icon = "🎯";
|
||||||
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-400"
|
? "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400"
|
||||||
: "bg-rose-500/10 border-rose-500/30 text-rose-400";
|
: "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400";
|
||||||
icon = value ? "✓" : "✕";
|
icon = value ? "✓" : "✕";
|
||||||
displayValue = value ? "Solved" : "Pending";
|
displayValue = value ? "Solved" : "Pending";
|
||||||
} else if (Array.isArray(value)) {
|
} else if (Array.isArray(value)) {
|
||||||
colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#7170ff]";
|
colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff]";
|
||||||
icon = "🗂️";
|
icon = "🗂️";
|
||||||
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-400";
|
colorClass = "bg-emerald-500/10 border-emerald-500/30 text-emerald-600 dark:text-emerald-400";
|
||||||
icon = "🟢";
|
icon = "🟢";
|
||||||
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-400";
|
colorClass = "bg-amber-500/10 border-amber-500/30 text-amber-600 dark:text-amber-400";
|
||||||
icon = "🟡";
|
icon = "🟡";
|
||||||
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-400";
|
colorClass = "bg-rose-500/10 border-rose-500/30 text-rose-600 dark:text-rose-400";
|
||||||
icon = "🔴";
|
icon = "🔴";
|
||||||
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-300";
|
colorClass = "bg-indigo-500/10 border-indigo-500/30 text-indigo-600 dark:text-indigo-300";
|
||||||
icon = "⚡";
|
icon = "⚡";
|
||||||
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-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#7170ff]";
|
colorClass = "bg-[#5e6ad2]/10 border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff]";
|
||||||
icon = "🏆";
|
icon = "🏆";
|
||||||
displayValue = String(value);
|
displayValue = String(value);
|
||||||
} else {
|
} else {
|
||||||
@@ -108,7 +108,7 @@ function CustomFieldBadges({
|
|||||||
className={`inline-flex items-center gap-1.5 px-2.5 py-1 border rounded-md 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}`}
|
||||||
>
|
>
|
||||||
<span className="text-[10px] opacity-70">{icon}</span>
|
<span className="text-[10px] opacity-70">{icon}</span>
|
||||||
<span className="text-[#8a8f98] font-sans">{label}:</span>
|
<span className="text-[var(--text-muted)] font-sans">{label}:</span>
|
||||||
<span className="font-medium">{displayValue}</span>
|
<span className="font-medium">{displayValue}</span>
|
||||||
</span>
|
</span>
|
||||||
);
|
);
|
||||||
@@ -157,12 +157,12 @@ function FolderIndexPage({
|
|||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<nav className="flex items-center gap-2 text-xs text-[#62666d] mb-6 flex-wrap">
|
<nav className="flex items-center gap-2 text-xs text-[var(--text-dim)] mb-6 flex-wrap">
|
||||||
<Link href="/" className="hover:text-[#d0d6e0] transition-colors">
|
<Link href="/" className="hover:text-[var(--text-primary)] transition-colors">
|
||||||
MCPedia
|
MCPedia
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<Link href={`/${section}`} className="hover:text-[#d0d6e0] transition-colors">
|
<Link href={`/${section}`} className="hover:text-[var(--text-primary)] transition-colors">
|
||||||
{sectionInfo.label}
|
{sectionInfo.label}
|
||||||
</Link>
|
</Link>
|
||||||
{slug.split("/").map((part, i) => {
|
{slug.split("/").map((part, i) => {
|
||||||
@@ -172,7 +172,7 @@ function FolderIndexPage({
|
|||||||
return (
|
return (
|
||||||
<span key={path} className="flex items-center gap-2">
|
<span key={path} className="flex items-center gap-2">
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className={isLast ? "text-[#8a8f98] font-medium" : "hover:text-[#d0d6e0]"}>
|
<span className={isLast ? "text-[var(--text-primary)] font-semibold" : "hover:text-[var(--text-primary)]"}>
|
||||||
{label}
|
{label}
|
||||||
</span>
|
</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -181,22 +181,22 @@ function FolderIndexPage({
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Header card */}
|
{/* Header card */}
|
||||||
<div className="bg-[#0f1011] border border-[#1f2022] rounded-xl p-6 mb-8">
|
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 mb-8 shadow-sm">
|
||||||
<div className="flex items-center justify-between gap-4">
|
<div className="flex items-center justify-between gap-4">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-lg bg-[#141517] border border-[#23252a] flex items-center justify-center text-xl">
|
<div className="w-10 h-10 rounded-lg bg-[var(--bg-elevated)] border border-[var(--border-color)] flex items-center justify-center text-xl">
|
||||||
📁
|
📁
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl sm:text-3xl font-semibold text-[#f7f8f8] tracking-tight">
|
<h1 className="text-2xl sm:text-3xl font-bold text-[var(--text-primary)] tracking-tight">
|
||||||
{folderName}
|
{folderName}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-[#8a8f98] mt-0.5">
|
<p className="text-xs text-[var(--text-muted)] mt-0.5">
|
||||||
Directory path: <code className="text-[#7170ff] font-mono">{section}/{slug}</code>
|
Directory path: <code className="text-[#5e6ad2] dark:text-[#7170ff] font-mono">{section}/{slug}</code>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-xs font-mono text-[#8a8f98] bg-[#141517] border border-[#23252a] px-2.5 py-1 rounded-md">
|
<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-md">
|
||||||
{immediateDocs.length + folders.length} item{folders.length + immediateDocs.length !== 1 ? "s" : ""}
|
{immediateDocs.length + folders.length} item{folders.length + immediateDocs.length !== 1 ? "s" : ""}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -205,23 +205,23 @@ function FolderIndexPage({
|
|||||||
{/* Subfolders */}
|
{/* Subfolders */}
|
||||||
{folders.length > 0 && (
|
{folders.length > 0 && (
|
||||||
<div className="mb-8">
|
<div className="mb-8">
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase mb-3 tracking-wider flex items-center gap-2">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase mb-3 tracking-wider flex items-center gap-2">
|
||||||
<span>Subfolders</span>
|
<span>Subfolders</span>
|
||||||
<span className="text-[10px] text-[#62666d]">({folders.length})</span>
|
<span className="text-[10px] text-[var(--text-dim)]">({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">
|
||||||
{folders.map(([folder, info]) => (
|
{folders.map(([folder, info]) => (
|
||||||
<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 p-3.5 bg-[#0f1011] hover:bg-[#141517] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-lg transition-all shadow-sm"
|
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(--brand)] rounded-lg transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<span className="text-2xl group-hover:scale-110 transition-transform">📁</span>
|
<span className="text-2xl group-hover:scale-110 transition-transform">📁</span>
|
||||||
<div className="flex-1 min-w-0">
|
<div className="flex-1 min-w-0">
|
||||||
<div className="text-sm font-medium text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors truncate">
|
<div className="text-sm font-semibold text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors 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-[#62666d] mt-0.5 font-mono">
|
<div className="text-xs text-[var(--text-muted)] mt-0.5 font-mono">
|
||||||
{info.docCount} document{info.docCount !== 1 ? "s" : ""}
|
{info.docCount} document{info.docCount !== 1 ? "s" : ""}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -234,21 +234,21 @@ function FolderIndexPage({
|
|||||||
{/* Documents */}
|
{/* Documents */}
|
||||||
{immediateDocs.length > 0 && (
|
{immediateDocs.length > 0 && (
|
||||||
<div>
|
<div>
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase mb-3 tracking-wider flex items-center gap-2">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase mb-3 tracking-wider flex items-center gap-2">
|
||||||
<span>Documents</span>
|
<span>Documents</span>
|
||||||
<span className="text-[10px] text-[#62666d]">({immediateDocs.length})</span>
|
<span className="text-[10px] text-[var(--text-dim)]">({immediateDocs.length})</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-2">
|
<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 bg-[#0f1011] hover:bg-[#141517] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-lg transition-all shadow-sm"
|
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(--brand)] rounded-lg transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<span className="text-xl opacity-70 group-hover:opacity-100">📄</span>
|
<span className="text-xl opacity-70 group-hover:opacity-100">📄</span>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<div className="text-sm font-medium text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors truncate">
|
<div className="text-sm font-semibold text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors truncate">
|
||||||
{doc.title}
|
{doc.title}
|
||||||
</div>
|
</div>
|
||||||
{doc.tags.length > 0 && (
|
{doc.tags.length > 0 && (
|
||||||
@@ -256,7 +256,7 @@ function FolderIndexPage({
|
|||||||
{doc.tags.slice(0, 4).map((t) => (
|
{doc.tags.slice(0, 4).map((t) => (
|
||||||
<span
|
<span
|
||||||
key={t}
|
key={t}
|
||||||
className="text-[11px] px-1.5 py-0.25 bg-[#141517] border border-[#23252a] rounded text-[#8a8f98]"
|
className="text-[11px] px-1.5 py-0.25 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)]"
|
||||||
>
|
>
|
||||||
#{t}
|
#{t}
|
||||||
</span>
|
</span>
|
||||||
@@ -265,7 +265,7 @@ function FolderIndexPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<time className="text-xs text-[#62666d] shrink-0 font-mono">
|
<time className="text-xs text-[var(--text-dim)] shrink-0 font-mono">
|
||||||
{new Date(doc.updatedAt).toLocaleDateString(undefined, {
|
{new Date(doc.updatedAt).toLocaleDateString(undefined, {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
@@ -278,8 +278,8 @@ function FolderIndexPage({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{folders.length === 0 && immediateDocs.length === 0 && (
|
{folders.length === 0 && immediateDocs.length === 0 && (
|
||||||
<div className="text-center py-12 border border-[#1f2022] rounded-xl bg-[#0f1011]">
|
<div className="text-center py-12 border border-[var(--border-color)] rounded-xl bg-[var(--bg-surface)]">
|
||||||
<p className="text-sm text-[#8a8f98]">No documents found in this directory.</p>
|
<p className="text-sm text-[var(--text-muted)]">No documents found in this directory.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -317,11 +317,11 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
<div>
|
<div>
|
||||||
<Link
|
<Link
|
||||||
href={`/${doc.slug}`}
|
href={`/${doc.slug}`}
|
||||||
className="inline-flex items-center gap-1 text-xs text-[#8a8f98] hover:text-[#d0d6e0] mb-4 transition-colors"
|
className="inline-flex items-center gap-1 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] mb-4 transition-colors"
|
||||||
>
|
>
|
||||||
<span>← Back to document</span>
|
<span>← Back to document</span>
|
||||||
</Link>
|
</Link>
|
||||||
<h1 className="text-2xl font-semibold text-[#f7f8f8] mb-6">
|
<h1 className="text-2xl font-bold text-[var(--text-primary)] mb-6">
|
||||||
Edit: {doc.title}
|
Edit: {doc.title}
|
||||||
</h1>
|
</h1>
|
||||||
<DocForm
|
<DocForm
|
||||||
@@ -356,8 +356,8 @@ 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">
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<nav className="flex items-center gap-2 text-xs text-[#62666d] mb-4 flex-wrap">
|
<nav className="flex items-center gap-2 text-xs text-[var(--text-dim)] mb-4 flex-wrap">
|
||||||
<Link href="/" className="hover:text-[#d0d6e0] transition-colors">
|
<Link href="/" className="hover:text-[var(--text-primary)] transition-colors">
|
||||||
MCPedia
|
MCPedia
|
||||||
</Link>
|
</Link>
|
||||||
{doc.slug.split("/").map((part, i) => {
|
{doc.slug.split("/").map((part, i) => {
|
||||||
@@ -371,7 +371,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
<span>/</span>
|
<span>/</span>
|
||||||
<Link
|
<Link
|
||||||
href={`/${crumbPath}`}
|
href={`/${crumbPath}`}
|
||||||
className={isLast ? "text-[#8a8f98] font-medium" : "hover:text-[#d0d6e0] transition-colors"}
|
className={isLast ? "text-[var(--text-primary)] font-semibold" : "hover:text-[var(--text-primary)] transition-colors"}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</Link>
|
</Link>
|
||||||
@@ -381,24 +381,24 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Hero Metadata Card */}
|
{/* Hero Metadata Card */}
|
||||||
<div className="bg-[#0f1011] border border-[#1f2022] rounded-xl p-6 mb-8 shadow-sm">
|
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 mb-8 shadow-sm">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-4">
|
<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 flex-wrap">
|
<div className="flex items-center gap-2 mb-2 flex-wrap">
|
||||||
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-mono font-medium bg-[#5e6ad2]/15 text-[#7170ff] border border-[#5e6ad2]/30">
|
<span className="inline-flex items-center gap-1 px-2 py-0.5 rounded text-xs font-mono font-medium bg-[#5e6ad2]/15 text-[#5e6ad2] dark:text-[#7170ff] border border-[#5e6ad2]/30">
|
||||||
<span>{sectionInfo.icon}</span>
|
<span>{sectionInfo.icon}</span>
|
||||||
<span className="uppercase">{sectionInfo.label}</span>
|
<span className="uppercase">{sectionInfo.label}</span>
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-[#62666d]">·</span>
|
<span className="text-xs text-[var(--text-dim)]">·</span>
|
||||||
<span className="text-xs text-[#8a8f98] capitalize">
|
<span className="text-xs text-[var(--text-muted)] capitalize">
|
||||||
{doc.type}
|
{doc.type}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-[#62666d]">·</span>
|
<span className="text-xs text-[var(--text-dim)]">·</span>
|
||||||
<span className="text-xs text-[#8a8f98] font-mono">
|
<span className="text-xs text-[var(--text-muted)] font-mono">
|
||||||
{readingTime}
|
{readingTime}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl sm:text-3xl font-semibold text-[#f7f8f8] tracking-tight leading-tight">
|
<h1 className="text-2xl sm:text-3xl font-bold text-[var(--text-primary)] tracking-tight leading-tight">
|
||||||
{doc.title}
|
{doc.title}
|
||||||
</h1>
|
</h1>
|
||||||
</div>
|
</div>
|
||||||
@@ -408,14 +408,14 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Author + Date + Tags */}
|
{/* Author + Date + Tags */}
|
||||||
<div className="flex flex-wrap items-center gap-3 text-xs text-[#8a8f98] pt-3 border-t border-[#1a1b1d]">
|
<div className="flex flex-wrap items-center gap-3 text-xs text-[var(--text-muted)] pt-3 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-[#5e6ad2] text-white flex items-center justify-center text-[10px] font-bold">
|
<div className="w-4 h-4 rounded-full bg-[#5e6ad2] text-white flex items-center justify-center text-[10px] font-bold">
|
||||||
{doc.author.charAt(0).toUpperCase()}
|
{doc.author.charAt(0).toUpperCase()}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[#d0d6e0]">{doc.author}</span>
|
<span className="text-[var(--text-secondary)]">{doc.author}</span>
|
||||||
<span className="text-[#62666d]">·</span>
|
<span className="text-[var(--text-dim)]">·</span>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<span>
|
<span>
|
||||||
@@ -427,12 +427,12 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
</span>
|
</span>
|
||||||
{doc.tags.length > 0 && (
|
{doc.tags.length > 0 && (
|
||||||
<>
|
<>
|
||||||
<span className="text-[#62666d]">·</span>
|
<span className="text-[var(--text-dim)]">·</span>
|
||||||
<div className="flex flex-wrap gap-1">
|
<div className="flex flex-wrap gap-1">
|
||||||
{doc.tags.map((t) => (
|
{doc.tags.map((t) => (
|
||||||
<span
|
<span
|
||||||
key={t}
|
key={t}
|
||||||
className="px-2 py-0.5 bg-[#141517] border border-[#23252a] rounded text-[#8a8f98] text-[11px]"
|
className="px-2 py-0.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)] text-[11px]"
|
||||||
>
|
>
|
||||||
#{t}
|
#{t}
|
||||||
</span>
|
</span>
|
||||||
@@ -447,7 +447,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile TOC (rendered inline on smaller screens) */}
|
{/* Mobile TOC (rendered inline on smaller screens) */}
|
||||||
<div className="xl:hidden mb-6 p-4 bg-[#0f1011] border border-[#1f2022] rounded-lg">
|
<div className="xl:hidden mb-6 p-4 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-lg shadow-sm">
|
||||||
<TOC source={doc.body} />
|
<TOC source={doc.body} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -458,8 +458,8 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
|
|
||||||
{/* Related Documents */}
|
{/* Related Documents */}
|
||||||
{related.length > 0 && (
|
{related.length > 0 && (
|
||||||
<aside className="mt-12 pt-8 border-t border-[#1f2022]">
|
<aside className="mt-12 pt-8 border-t border-[var(--border-color)]">
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase tracking-wider mb-4 flex items-center gap-2">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-4 flex items-center gap-2">
|
||||||
<span>Related Knowledge</span>
|
<span>Related Knowledge</span>
|
||||||
</h2>
|
</h2>
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3">
|
||||||
@@ -469,20 +469,20 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
<Link
|
<Link
|
||||||
key={r.slug}
|
key={r.slug}
|
||||||
href={`/${r.slug}`}
|
href={`/${r.slug}`}
|
||||||
className="group block bg-[#0f1011] hover:bg-[#141517] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-lg p-4 transition-all shadow-sm"
|
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-lg p-4 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2 mb-2">
|
<div className="flex items-center justify-between gap-2 mb-2">
|
||||||
<span className="text-xs text-[#7170ff] font-mono uppercase">
|
<span className="text-xs text-[#5e6ad2] dark:text-[#7170ff] font-mono uppercase">
|
||||||
{rInfo.icon} {r.section}
|
{rInfo.icon} {r.section}
|
||||||
</span>
|
</span>
|
||||||
<time className="text-[11px] text-[#62666d] font-mono">
|
<time className="text-[11px] text-[var(--text-dim)] font-mono">
|
||||||
{new Date(r.updatedAt).toLocaleDateString(undefined, {
|
{new Date(r.updatedAt).toLocaleDateString(undefined, {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
})}
|
})}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="text-sm font-medium text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors line-clamp-1 mb-1">
|
<h3 className="text-sm font-semibold text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors line-clamp-1 mb-1">
|
||||||
{r.title}
|
{r.title}
|
||||||
</h3>
|
</h3>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -494,9 +494,9 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
|
|
||||||
{/* Revision History */}
|
{/* Revision History */}
|
||||||
{revisions.length > 0 && (
|
{revisions.length > 0 && (
|
||||||
<aside className="mt-10 pt-8 border-t border-[#1f2022]">
|
<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-4">
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase tracking-wider">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider">
|
||||||
Revision History ({revisions.length})
|
Revision History ({revisions.length})
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -504,16 +504,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 bg-[#0f1011] border border-[#1f2022] rounded-lg text-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-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
<span className="font-mono text-[#7170ff] bg-[#5e6ad2]/10 px-1.5 py-0.5 rounded border border-[#5e6ad2]/30">
|
<span className="font-mono text-[#5e6ad2] dark:text-[#7170ff] bg-[#5e6ad2]/10 px-1.5 py-0.5 rounded border border-[#5e6ad2]/30 font-semibold">
|
||||||
v{rev.revisionNo}
|
v{rev.revisionNo}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#d0d6e0] truncate">
|
<span className="text-[var(--text-primary)] truncate">
|
||||||
{rev.reason || "Updated document"}
|
{rev.reason || "Updated document"}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-[#62666d] hidden sm:inline">
|
<span className="text-[var(--text-dim)] hidden sm:inline">
|
||||||
· {new Date(rev.createdAt).toLocaleString()}
|
· {new Date(rev.createdAt).toLocaleString()}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -522,7 +522,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-[#d0d6e0] hover:text-white bg-[#141517] hover:bg-[#23252a] px-2.5 py-1 rounded border border-[#23252a] hover:border-[#5e6ad2] transition-colors"
|
className="text-xs text-[var(--text-secondary)] hover:text-[var(--text-primary)] bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] px-2.5 py-1 rounded border border-[var(--border-color)] hover:border-[var(--brand)] transition-colors font-medium shadow-sm"
|
||||||
>
|
>
|
||||||
Restore
|
Restore
|
||||||
</button>
|
</button>
|
||||||
@@ -536,7 +536,7 @@ export default async function DocPage({ params, searchParams }: DocPageProps) {
|
|||||||
</article>
|
</article>
|
||||||
|
|
||||||
{/* Desktop Sticky Table of Contents Column */}
|
{/* Desktop Sticky Table of Contents Column */}
|
||||||
<aside className="hidden xl:block w-64 shrink-0 sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto pl-6 border-l border-[#1f2022]">
|
<aside className="hidden xl:block w-64 shrink-0 sticky top-20 max-h-[calc(100vh-6rem)] overflow-y-auto pl-6 border-l border-[var(--border-color)]">
|
||||||
<TOC source={doc.body} />
|
<TOC source={doc.body} />
|
||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -84,10 +84,10 @@ function renderTree(nodes: TreeNode[], pathname: string) {
|
|||||||
href={`/${node.slug}`}
|
href={`/${node.slug}`}
|
||||||
className={`flex items-center justify-between gap-2 text-xs py-1.5 px-2.5 rounded-lg transition-all ${
|
className={`flex items-center justify-between gap-2 text-xs py-1.5 px-2.5 rounded-lg transition-all ${
|
||||||
isActive
|
isActive
|
||||||
? "text-[#7170ff] bg-[#5e6ad2]/15 font-medium border border-[#5e6ad2]/30"
|
? "text-[#5e6ad2] dark:text-[#7170ff] bg-[#5e6ad2]/15 font-semibold border border-[#5e6ad2]/30"
|
||||||
: hasActiveChild
|
: hasActiveChild
|
||||||
? "text-[#d0d6e0] bg-[#141517]"
|
? "text-[var(--text-primary)] bg-[var(--bg-elevated)]"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0] hover:bg-[#141517]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="flex items-center gap-2 truncate">
|
<span className="flex items-center gap-2 truncate">
|
||||||
@@ -95,13 +95,13 @@ function renderTree(nodes: TreeNode[], pathname: string) {
|
|||||||
<span className="truncate">{node.doc?.title || node.name}</span>
|
<span className="truncate">{node.doc?.title || node.name}</span>
|
||||||
</span>
|
</span>
|
||||||
{isFolder && (
|
{isFolder && (
|
||||||
<span className="text-[10px] text-[#62666d] bg-[#191a1b] px-1.5 py-0.5 rounded font-mono">
|
<span className="text-[10px] text-[var(--text-dim)] bg-[var(--bg-surface)] border border-[var(--border-color)] px-1.5 py-0.5 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-[#1f2022] pl-2 mt-1">
|
<div className="ml-4 border-l border-[var(--border-color)] pl-2 mt-1">
|
||||||
{renderTree(node.children, pathname)}
|
{renderTree(node.children, pathname)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -123,8 +123,8 @@ export default async function SectionIndexPage({
|
|||||||
if (!sectionInfo) {
|
if (!sectionInfo) {
|
||||||
return (
|
return (
|
||||||
<div className="py-12 text-center">
|
<div className="py-12 text-center">
|
||||||
<h1 className="text-2xl font-semibold text-[#f7f8f8]">Unknown section</h1>
|
<h1 className="text-2xl font-semibold text-[var(--text-primary)]">Unknown section</h1>
|
||||||
<p className="text-sm text-[#8a8f98] mt-2">Section "{section}" does not exist.</p>
|
<p className="text-sm text-[var(--text-muted)] mt-2">Section "{section}" does not exist.</p>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -136,33 +136,33 @@ export default async function SectionIndexPage({
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-10">
|
<div className="space-y-10">
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<nav className="flex items-center gap-2 text-xs text-[#62666d]">
|
<nav className="flex items-center gap-2 text-xs text-[var(--text-dim)]">
|
||||||
<Link href="/" className="hover:text-[#d0d6e0] transition-colors">
|
<Link href="/" className="hover:text-[var(--text-primary)] transition-colors">
|
||||||
MCPedia
|
MCPedia
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-[#8a8f98] font-medium">{sectionInfo.label}</span>
|
<span className="text-[var(--text-muted)] font-medium">{sectionInfo.label}</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Hero Card */}
|
{/* Hero Card */}
|
||||||
<div className="bg-[#0f1011] border border-[#1f2022] rounded-xl p-6 shadow-sm">
|
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 shadow-sm">
|
||||||
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-3">
|
<div className="flex flex-col sm:flex-row sm:items-start justify-between gap-4 mb-3">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-12 h-12 rounded-xl bg-[#141517] border border-[#23252a] flex items-center justify-center text-2xl 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 shadow-inner">
|
||||||
{sectionInfo.icon}
|
{sectionInfo.icon}
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl sm:text-3xl font-bold text-[#f7f8f8] tracking-tight">
|
<h1 className="text-2xl sm:text-3xl font-bold text-[var(--text-primary)] tracking-tight">
|
||||||
{sectionInfo.label}
|
{sectionInfo.label}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-xs text-[#8a8f98] mt-0.5 font-mono uppercase">
|
<p className="text-xs text-[var(--text-muted)] mt-0.5 font-mono uppercase">
|
||||||
Section: {section}
|
Section: {section}
|
||||||
</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-[#7170ff] bg-[#5e6ad2]/10 border border-[#5e6ad2]/25 px-2.5 py-1 rounded-md">
|
<span className="text-xs font-mono text-[#5e6ad2] dark:text-[#7170ff] bg-[#5e6ad2]/10 border border-[#5e6ad2]/25 px-2.5 py-1 rounded-md">
|
||||||
{sectionDocs.length} document{sectionDocs.length !== 1 ? "s" : ""}
|
{sectionDocs.length} document{sectionDocs.length !== 1 ? "s" : ""}
|
||||||
</span>
|
</span>
|
||||||
<Link
|
<Link
|
||||||
@@ -174,15 +174,15 @@ export default async function SectionIndexPage({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p className="text-sm text-[#8a8f98] leading-relaxed max-w-2xl mt-2">
|
<p className="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-[#0c0d0e] border border-[#1f2022] rounded-xl p-6">
|
<section className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-6 shadow-sm">
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase tracking-wider mb-4 flex items-center gap-2">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-4 flex items-center gap-2">
|
||||||
<span>Folder Structure</span>
|
<span>Folder Structure</span>
|
||||||
</h2>
|
</h2>
|
||||||
{renderTree(tree, section)}
|
{renderTree(tree, section)}
|
||||||
@@ -192,7 +192,7 @@ export default async function SectionIndexPage({
|
|||||||
{/* Document List */}
|
{/* Document List */}
|
||||||
{sectionDocs.length > 0 ? (
|
{sectionDocs.length > 0 ? (
|
||||||
<section>
|
<section>
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase tracking-wider mb-4">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider mb-4">
|
||||||
All Documents in {sectionInfo.label}
|
All Documents in {sectionInfo.label}
|
||||||
</h2>
|
</h2>
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
@@ -205,12 +205,12 @@ 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 bg-[#0f1011] hover:bg-[#141517] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-xl transition-all shadow-sm"
|
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(--brand)] rounded-xl transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-3 min-w-0">
|
<div className="flex items-center gap-3 min-w-0">
|
||||||
<span className="text-lg opacity-70 group-hover:opacity-100">📄</span>
|
<span className="text-lg opacity-70 group-hover:opacity-100">📄</span>
|
||||||
<div className="min-w-0">
|
<div className="min-w-0">
|
||||||
<h3 className="text-sm font-medium text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors truncate">
|
<h3 className="text-sm font-medium text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors truncate">
|
||||||
{d.title}
|
{d.title}
|
||||||
</h3>
|
</h3>
|
||||||
{d.tags.length > 0 && (
|
{d.tags.length > 0 && (
|
||||||
@@ -218,7 +218,7 @@ export default async function SectionIndexPage({
|
|||||||
{d.tags.slice(0, 4).map((t) => (
|
{d.tags.slice(0, 4).map((t) => (
|
||||||
<span
|
<span
|
||||||
key={t}
|
key={t}
|
||||||
className="text-[10px] px-1.5 py-0.25 bg-[#141517] border border-[#23252a] rounded text-[#8a8f98]"
|
className="text-[10px] px-1.5 py-0.25 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)]"
|
||||||
>
|
>
|
||||||
#{t}
|
#{t}
|
||||||
</span>
|
</span>
|
||||||
@@ -227,7 +227,7 @@ export default async function SectionIndexPage({
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<time className="text-xs text-[#62666d] font-mono shrink-0">
|
<time className="text-xs text-[var(--text-dim)] font-mono shrink-0">
|
||||||
{new Date(d.updatedAt).toLocaleDateString(undefined, {
|
{new Date(d.updatedAt).toLocaleDateString(undefined, {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
@@ -239,8 +239,8 @@ export default async function SectionIndexPage({
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
) : (
|
) : (
|
||||||
<div className="text-center py-12 border border-[#1f2022] rounded-xl bg-[#0f1011]">
|
<div className="text-center py-12 border border-[var(--border-color)] rounded-xl bg-[var(--bg-surface)]">
|
||||||
<p className="text-sm text-[#8a8f98]">No documents in this section yet.</p>
|
<p className="text-sm text-[var(--text-muted)]">No documents in this section yet.</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -91,13 +91,13 @@ export default function CommandMenu() {
|
|||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(true)}
|
onClick={() => setOpen(true)}
|
||||||
className="hidden md:flex items-center gap-2 px-3 py-1.5 bg-[#141517] hover:bg-[#1b1d20] border border-[#23252a] hover:border-[#383b42] text-xs text-[#8a8f98] hover:text-[#d0d6e0] rounded-lg transition-all shadow-inner"
|
className="hidden md:flex items-center gap-2 px-3 py-1.5 bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated)] 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-sm"
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5 text-[#62666d]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-3.5 h-3.5 text-[var(--text-dim)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Search documentation...</span>
|
<span>Search docs...</span>
|
||||||
<kbd className="ml-2 font-mono bg-[#08090a] px-1.5 py-0.5 rounded border border-[#1f2022] text-[10px] text-[#62666d]">
|
<kbd className="ml-2 font-mono bg-[var(--bg-elevated)] px-1.5 py-0.5 rounded border border-[var(--border-color)] text-[10px] text-[var(--text-dim)]">
|
||||||
⌘K
|
⌘K
|
||||||
</kbd>
|
</kbd>
|
||||||
</button>
|
</button>
|
||||||
@@ -108,15 +108,15 @@ export default function CommandMenu() {
|
|||||||
<div className="fixed inset-0 z-50 flex items-start justify-center pt-20 px-4">
|
<div className="fixed inset-0 z-50 flex items-start justify-center pt-20 px-4">
|
||||||
{/* Backdrop */}
|
{/* Backdrop */}
|
||||||
<div
|
<div
|
||||||
className="fixed inset-0 bg-black/70 backdrop-blur-sm transition-opacity"
|
className="fixed inset-0 bg-black/60 dark:bg-black/80 backdrop-blur-sm transition-opacity"
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* Modal */}
|
{/* Modal */}
|
||||||
<div className="relative w-full max-w-2xl bg-[#0f1011] border border-[#27292d] rounded-xl shadow-2xl overflow-hidden animate-fade-in">
|
<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 py-3.5 border-b border-[#1f2022] bg-[#141517]">
|
<div className="flex items-center gap-3 px-4 py-3.5 border-b border-[var(--border-color)] bg-[var(--bg-elevated)]">
|
||||||
<svg className="w-5 h-5 text-[#7170ff] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-5 h-5 text-[#5e6ad2] dark:text-[#7170ff] shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<input
|
<input
|
||||||
@@ -126,14 +126,14 @@ export default function CommandMenu() {
|
|||||||
onChange={(e) => setQuery(e.target.value)}
|
onChange={(e) => setQuery(e.target.value)}
|
||||||
onKeyDown={handleKeyNavigation}
|
onKeyDown={handleKeyNavigation}
|
||||||
placeholder="Type to search (titles, keywords, concepts)..."
|
placeholder="Type to search (titles, keywords, concepts)..."
|
||||||
className="w-full bg-transparent text-[#f7f8f8] placeholder-[#62666d] text-sm 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-[#5e6ad2] border-t-transparent rounded-full animate-spin shrink-0" />
|
<div className="w-4 h-4 border-2 border-[#5e6ad2] border-t-transparent rounded-full animate-spin shrink-0" />
|
||||||
)}
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
className="text-xs text-[#62666d] hover:text-[#d0d6e0] bg-[#1b1d20] px-1.5 py-0.5 rounded border border-[#27292d]"
|
className="text-xs text-[var(--text-muted)] 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>
|
||||||
@@ -143,14 +143,14 @@ export default function CommandMenu() {
|
|||||||
<div className="max-h-96 overflow-y-auto p-2">
|
<div className="max-h-96 overflow-y-auto p-2">
|
||||||
{query.trim() === "" ? (
|
{query.trim() === "" ? (
|
||||||
<div className="px-3 py-6 text-center">
|
<div className="px-3 py-6 text-center">
|
||||||
<p className="text-xs text-[#8a8f98]">Search full-text, semantic embeddings, and CTF writeups.</p>
|
<p className="text-xs text-[var(--text-muted)]">Search full-text, semantic embeddings, and CTF writeups.</p>
|
||||||
<div className="flex justify-center gap-2 mt-3 text-[11px] text-[#62666d]">
|
<div className="flex justify-center gap-2 mt-3 text-[11px] text-[var(--text-dim)]">
|
||||||
<span>Navigation: <kbd className="bg-[#141517] px-1 py-0.5 rounded border border-[#1f2022]">↑</kbd> <kbd className="bg-[#141517] px-1 py-0.5 rounded border border-[#1f2022]">↓</kbd></span>
|
<span>Navigation: <kbd className="bg-[var(--bg-elevated)] px-1 py-0.5 rounded border border-[var(--border-color)]">↑</kbd> <kbd className="bg-[var(--bg-elevated)] px-1 py-0.5 rounded border border-[var(--border-color)]">↓</kbd></span>
|
||||||
<span>Select: <kbd className="bg-[#141517] px-1 py-0.5 rounded border border-[#1f2022]">↵</kbd></span>
|
<span>Select: <kbd className="bg-[var(--bg-elevated)] px-1 py-0.5 rounded border border-[var(--border-color)]">↵</kbd></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : results.length === 0 && !loading ? (
|
) : results.length === 0 && !loading ? (
|
||||||
<div className="px-4 py-8 text-center text-xs text-[#8a8f98]">
|
<div className="px-4 py-8 text-center text-xs text-[var(--text-muted)]">
|
||||||
No documents matching "{query}"
|
No documents matching "{query}"
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
@@ -164,27 +164,27 @@ export default function CommandMenu() {
|
|||||||
onMouseEnter={() => setSelectedIndex(index)}
|
onMouseEnter={() => setSelectedIndex(index)}
|
||||||
className={`flex items-start justify-between gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all ${
|
className={`flex items-start justify-between gap-3 px-3 py-2.5 rounded-lg cursor-pointer transition-all ${
|
||||||
isSelected
|
isSelected
|
||||||
? "bg-[#5e6ad2]/15 border border-[#5e6ad2]/40 text-[#f7f8f8]"
|
? "bg-[#5e6ad2]/15 border border-[#5e6ad2]/40 text-[var(--text-primary)]"
|
||||||
: "hover:bg-[#141517] text-[#d0d6e0] 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">
|
<div className="flex items-center gap-2">
|
||||||
<span className="text-xs px-1.5 py-0.25 bg-[#191a1b] border border-[#23252a] rounded text-[#7170ff] font-mono">
|
<span className="text-xs px-1.5 py-0.25 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[#5e6ad2] dark:text-[#7170ff] font-mono">
|
||||||
{hit.section}
|
{hit.section}
|
||||||
</span>
|
</span>
|
||||||
<span className={`text-sm font-medium ${isSelected ? "text-[#7170ff]" : "text-[#f7f8f8]"} truncate`}>
|
<span className={`text-sm font-medium ${isSelected ? "text-[#5e6ad2] dark:text-[#7170ff]" : "text-[var(--text-primary)]"} truncate`}>
|
||||||
{hit.title}
|
{hit.title}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
{hit.snippet && (
|
{hit.snippet && (
|
||||||
<p
|
<p
|
||||||
className="text-xs text-[#8a8f98] mt-1 line-clamp-1 [&>mark]:bg-[#5e6ad2]/30 [&>mark]:text-[#f7f8f8] [&>mark]:rounded [&>mark]:px-0.5"
|
className="text-xs text-[var(--text-muted)] mt-1 line-clamp-1 [&>mark]:bg-[#5e6ad2]/30 [&>mark]:text-[var(--text-primary)] [&>mark]:rounded [&>mark]:px-0.5"
|
||||||
dangerouslySetInnerHTML={{ __html: hit.snippet }}
|
dangerouslySetInnerHTML={{ __html: hit.snippet }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[11px] text-[#62666d] shrink-0 self-center">
|
<span className="text-[11px] text-[var(--text-dim)] shrink-0 self-center">
|
||||||
{hit.slug}
|
{hit.slug}
|
||||||
</span>
|
</span>
|
||||||
</li>
|
</li>
|
||||||
@@ -195,12 +195,12 @@ export default function CommandMenu() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Footer */}
|
{/* Footer */}
|
||||||
<div className="flex items-center justify-between px-4 py-2.5 border-t border-[#1f2022] bg-[#0c0d0e] text-[11px] text-[#62666d]">
|
<div className="flex items-center justify-between px-4 py-2.5 border-t border-[var(--border-color)] bg-[var(--bg-elevated)] text-[11px] text-[var(--text-dim)]">
|
||||||
<span>Tip: Press <kbd className="bg-[#141517] px-1 py-0.5 rounded border border-[#1f2022]">Hybrid</kbd> search is enabled by default</span>
|
<span>Tip: Press <kbd className="bg-[var(--bg-surface)] px-1 py-0.5 rounded border border-[var(--border-color)]">Hybrid</kbd> search is enabled</span>
|
||||||
<Link
|
<Link
|
||||||
href={`/search?q=${encodeURIComponent(query)}`}
|
href={`/search?q=${encodeURIComponent(query)}`}
|
||||||
onClick={() => setOpen(false)}
|
onClick={() => setOpen(false)}
|
||||||
className="hover:text-[#7170ff] transition-colors"
|
className="hover:text-[#5e6ad2] dark:hover:text-[#7170ff] transition-colors"
|
||||||
>
|
>
|
||||||
Open full search page →
|
Open full search page →
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -32,14 +32,14 @@ export default function DocActions({ slug, body, canEdit }: DocActionsProps) {
|
|||||||
onClick={handleCopyMarkdown}
|
onClick={handleCopyMarkdown}
|
||||||
type="button"
|
type="button"
|
||||||
title="Copy raw markdown content"
|
title="Copy raw markdown content"
|
||||||
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs bg-[#141517] hover:bg-[#1b1d20] border border-[#23252a] hover:border-[#383b42] text-[#8a8f98] hover:text-[#f7f8f8] rounded-md transition-all"
|
className="inline-flex items-center gap-1 px-2.5 py-1 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-md transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
{copiedMd ? (
|
{copiedMd ? (
|
||||||
<>
|
<>
|
||||||
<svg className="w-3.5 h-3.5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-3.5 h-3.5 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-emerald-400">Copied MD</span>
|
<span className="text-emerald-500 font-medium">Copied MD</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -56,14 +56,14 @@ 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 px-2.5 py-1 text-xs bg-[#141517] hover:bg-[#1b1d20] border border-[#23252a] hover:border-[#383b42] text-[#8a8f98] hover:text-[#f7f8f8] rounded-md transition-all"
|
className="inline-flex items-center gap-1 px-2.5 py-1 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-md transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
{copiedLink ? (
|
{copiedLink ? (
|
||||||
<>
|
<>
|
||||||
<svg className="w-3.5 h-3.5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-3.5 h-3.5 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-emerald-400">Copied</span>
|
<span className="text-emerald-500 font-medium">Copied Link</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -78,12 +78,12 @@ export default function DocActions({ slug, body, canEdit }: DocActionsProps) {
|
|||||||
{/* 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 px-2.5 py-1 text-xs bg-[#5e6ad2]/15 hover:bg-[#5e6ad2]/25 border border-[#5e6ad2]/30 hover:border-[#5e6ad2]/60 text-[#7170ff] hover:text-white rounded-md transition-all"
|
className="inline-flex items-center gap-1 px-2.5 py-1 text-xs bg-[#5e6ad2]/10 hover:bg-[#5e6ad2]/20 border border-[#5e6ad2]/30 hover:border-[#5e6ad2]/60 text-[#5e6ad2] dark:text-[#7170ff] rounded-md transition-all font-medium"
|
||||||
>
|
>
|
||||||
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M11 5H6a2 2 0 00-2 2v11a2 2 0 002 2h11a2 2 0 002-2v-5m-1.414-9.414a2 2 0 112.828 2.828L11.828 15H9v-2.828l8.586-8.586z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>{canEdit ? "Edit" : "Edit"}</span>
|
<span>Edit</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ export default function DocForm({
|
|||||||
const availableFolders = existingFolders?.[section] ?? [];
|
const availableFolders = existingFolders?.[section] ?? [];
|
||||||
|
|
||||||
const baseInputCls =
|
const baseInputCls =
|
||||||
"w-full px-3.5 py-2 bg-[#0f1011] border border-[#23252a] hover:border-[#383b42] rounded-lg text-sm text-[#f7f8f8] placeholder-[#62666d] focus:outline-none focus:border-[#5e6ad2] focus:ring-1 focus:ring-[#5e6ad2] transition-colors";
|
"w-full px-3.5 py-2 bg-[var(--bg-surface)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-lg text-sm text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-1 focus:ring-[#5e6ad2] transition-colors";
|
||||||
|
|
||||||
function addCustomField(key = "", value = "") {
|
function addCustomField(key = "", value = "") {
|
||||||
setCustomFields([...customFields, { key, value }]);
|
setCustomFields([...customFields, { key, value }]);
|
||||||
@@ -184,14 +184,14 @@ 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-3.5 rounded-lg border border-rose-500/30 bg-rose-500/10 text-rose-400 text-sm">
|
<div className="p-3.5 rounded-lg border border-rose-500/30 bg-rose-500/10 text-rose-500 dark:text-rose-400 text-sm">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{/* Title */}
|
{/* Title */}
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Document Title *
|
Document Title *
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -207,7 +207,7 @@ export default function DocForm({
|
|||||||
{/* Section + Type + Status */}
|
{/* Section + Type + Status */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Section
|
Section
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@@ -224,7 +224,7 @@ export default function DocForm({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Type
|
Type
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@@ -240,7 +240,7 @@ export default function DocForm({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Status
|
Status
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@@ -257,7 +257,7 @@ export default function DocForm({
|
|||||||
{/* Folder + Slug */}
|
{/* Folder + Slug */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Parent Folder
|
Parent Folder
|
||||||
</label>
|
</label>
|
||||||
<select
|
<select
|
||||||
@@ -275,7 +275,7 @@ export default function DocForm({
|
|||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
{isEdit ? "Full Slug" : "Slug (File name) *"}
|
{isEdit ? "Full Slug" : "Slug (File name) *"}
|
||||||
</label>
|
</label>
|
||||||
{isEdit ? (
|
{isEdit ? (
|
||||||
@@ -283,7 +283,7 @@ export default function DocForm({
|
|||||||
type="text"
|
type="text"
|
||||||
value={slug ?? ""}
|
value={slug ?? ""}
|
||||||
readOnly
|
readOnly
|
||||||
className="w-full px-3.5 py-2 bg-[#141517] border border-[#23252a] rounded-lg text-sm text-[#8a8f98] 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
|
||||||
@@ -295,8 +295,8 @@ export default function DocForm({
|
|||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<p className="text-[11px] text-[#62666d] mt-1 font-mono">
|
<p className="text-[11px] text-[var(--text-dim)] mt-1 font-mono">
|
||||||
Path: <span className="text-[#7170ff]">{effectiveSlug || section}</span>
|
Path: <span className="text-[#5e6ad2] dark:text-[#7170ff]">{effectiveSlug || section}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -304,7 +304,7 @@ export default function DocForm({
|
|||||||
{/* Tags + Author */}
|
{/* Tags + Author */}
|
||||||
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
<div className="grid grid-cols-1 sm:grid-cols-2 gap-4">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Tags (comma separated)
|
Tags (comma separated)
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -316,7 +316,7 @@ export default function DocForm({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-xs font-semibold uppercase tracking-wider text-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Author
|
Author
|
||||||
</label>
|
</label>
|
||||||
<input
|
<input
|
||||||
@@ -330,25 +330,25 @@ export default function DocForm({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Custom Metadata Fields */}
|
{/* Custom Metadata Fields */}
|
||||||
<div className="p-4 bg-[#0c0d0e] border border-[#1f2022] rounded-xl space-y-3">
|
<div className="p-4 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl space-y-3 shadow-sm">
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<div>
|
<div>
|
||||||
<h3 className="text-xs font-semibold uppercase tracking-wider text-[#8a8f98]">
|
<h3 className="text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)]">
|
||||||
Dynamic Custom Metadata
|
Dynamic Custom Metadata
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-[11px] text-[#62666d] mt-0.5">
|
<p className="text-[11px] text-[var(--text-dim)] mt-0.5">
|
||||||
Add custom key-values (event, difficulty, points, etc.) rendered as styled badges.
|
Add custom key-values (event, difficulty, points, etc.) rendered as styled badges.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex items-center gap-1.5 flex-wrap">
|
<div className="flex items-center gap-1.5 flex-wrap">
|
||||||
<span className="text-[11px] text-[#62666d]">Presets:</span>
|
<span className="text-[11px] text-[var(--text-dim)]">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-[#141517] hover:bg-[#1b1d20] border border-[#23252a] text-[#8a8f98] hover:text-[#d0d6e0] rounded transition-colors"
|
className="px-2 py-0.5 text-[11px] 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"
|
||||||
>
|
>
|
||||||
+{preset.key}
|
+{preset.key}
|
||||||
</button>
|
</button>
|
||||||
@@ -375,7 +375,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => removeCustomField(i)}
|
onClick={() => removeCustomField(i)}
|
||||||
className="p-2 text-[#fca5a5] hover:text-rose-400 rounded hover:bg-[#191a1b] transition-colors"
|
className="p-2 text-rose-500 hover:text-rose-600 rounded hover:bg-[var(--bg-elevated)] transition-colors"
|
||||||
title="Remove field"
|
title="Remove field"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
@@ -386,16 +386,16 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => addCustomField()}
|
onClick={() => addCustomField()}
|
||||||
className="text-xs text-[#7170ff] hover:text-[#828fff] px-3 py-1.5 rounded-lg border border-[#5e6ad2]/30 hover:border-[#5e6ad2]/60 bg-[#5e6ad2]/5 transition-colors"
|
className="text-xs text-[#5e6ad2] dark:text-[#7170ff] hover:underline px-3 py-1.5 rounded-lg border border-[#5e6ad2]/30 hover:border-[#5e6ad2]/60 bg-[#5e6ad2]/5 transition-colors font-medium"
|
||||||
>
|
>
|
||||||
+ Add Custom Field
|
+ Add Custom Field
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Body with Write / Preview Tabs */}
|
{/* Body with Write / Preview Tabs */}
|
||||||
<div className="border border-[#1f2022] rounded-xl overflow-hidden bg-[#0c0d0e]">
|
<div className="border border-[var(--border-color)] rounded-xl overflow-hidden bg-[var(--bg-surface)] shadow-sm">
|
||||||
{/* Editor Toolbar */}
|
{/* Editor Toolbar */}
|
||||||
<div className="flex items-center justify-between px-3 py-2 bg-[#141517] border-b border-[#1f2022] 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
|
||||||
@@ -403,8 +403,8 @@ export default function DocForm({
|
|||||||
onClick={() => setActiveTab("write")}
|
onClick={() => setActiveTab("write")}
|
||||||
className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${
|
className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${
|
||||||
activeTab === "write"
|
activeTab === "write"
|
||||||
? "bg-[#5e6ad2] text-white"
|
? "bg-[#5e6ad2] text-white shadow-sm"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Write
|
Write
|
||||||
@@ -414,8 +414,8 @@ export default function DocForm({
|
|||||||
onClick={() => setActiveTab("preview")}
|
onClick={() => setActiveTab("preview")}
|
||||||
className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${
|
className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${
|
||||||
activeTab === "preview"
|
activeTab === "preview"
|
||||||
? "bg-[#5e6ad2] text-white"
|
? "bg-[#5e6ad2] text-white shadow-sm"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Live Preview
|
Live Preview
|
||||||
@@ -424,11 +424,11 @@ export default function DocForm({
|
|||||||
|
|
||||||
{/* Markdown Action Shortcuts */}
|
{/* Markdown Action Shortcuts */}
|
||||||
{activeTab === "write" && (
|
{activeTab === "write" && (
|
||||||
<div className="flex items-center gap-1 text-xs text-[#8a8f98]">
|
<div className="flex items-center gap-1 text-xs text-[var(--text-muted)]">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertMarkdown("**", "**")}
|
onClick={() => insertMarkdown("**", "**")}
|
||||||
className="px-2 py-0.5 hover:bg-[#23252a] rounded text-[#d0d6e0]"
|
className="px-2 py-0.5 hover:bg-[var(--bg-surface)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
||||||
title="Bold"
|
title="Bold"
|
||||||
>
|
>
|
||||||
<b>B</b>
|
<b>B</b>
|
||||||
@@ -436,7 +436,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertMarkdown("*", "*")}
|
onClick={() => insertMarkdown("*", "*")}
|
||||||
className="px-2 py-0.5 hover:bg-[#23252a] rounded text-[#d0d6e0]"
|
className="px-2 py-0.5 hover:bg-[var(--bg-surface)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
||||||
title="Italic"
|
title="Italic"
|
||||||
>
|
>
|
||||||
<i>I</i>
|
<i>I</i>
|
||||||
@@ -444,7 +444,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertMarkdown("## ")}
|
onClick={() => insertMarkdown("## ")}
|
||||||
className="px-2 py-0.5 hover:bg-[#23252a] rounded text-[#d0d6e0]"
|
className="px-2 py-0.5 hover:bg-[var(--bg-surface)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
||||||
title="Heading 2"
|
title="Heading 2"
|
||||||
>
|
>
|
||||||
H2
|
H2
|
||||||
@@ -452,7 +452,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertMarkdown("```\n", "\n```")}
|
onClick={() => insertMarkdown("```\n", "\n```")}
|
||||||
className="px-2 py-0.5 hover:bg-[#23252a] rounded text-[#d0d6e0] font-mono"
|
className="px-2 py-0.5 hover:bg-[var(--bg-surface)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)] font-mono"
|
||||||
title="Code Block"
|
title="Code Block"
|
||||||
>
|
>
|
||||||
{"</>"}
|
{"</>"}
|
||||||
@@ -460,7 +460,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertMarkdown("[", "](https://)")}
|
onClick={() => insertMarkdown("[", "](https://)")}
|
||||||
className="px-2 py-0.5 hover:bg-[#23252a] rounded text-[#d0d6e0]"
|
className="px-2 py-0.5 hover:bg-[var(--bg-surface)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
||||||
title="Link"
|
title="Link"
|
||||||
>
|
>
|
||||||
Link
|
Link
|
||||||
@@ -468,7 +468,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => insertMarkdown("> [!NOTE]\n> ")}
|
onClick={() => insertMarkdown("> [!NOTE]\n> ")}
|
||||||
className="px-2 py-0.5 hover:bg-[#23252a] rounded text-[#d0d6e0]"
|
className="px-2 py-0.5 hover:bg-[var(--bg-surface)] rounded text-[var(--text-secondary)] hover:text-[var(--text-primary)]"
|
||||||
title="Note Callout"
|
title="Note Callout"
|
||||||
>
|
>
|
||||||
Note
|
Note
|
||||||
@@ -483,16 +483,16 @@ 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 bg-transparent text-[#f7f8f8] placeholder-[#62666d] 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 markdown document here..."
|
placeholder="# Write your markdown document here..."
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
) : (
|
) : (
|
||||||
<div className="p-6 h-96 overflow-y-auto bg-[#08090a]">
|
<div className="p-6 h-96 overflow-y-auto bg-[var(--bg-app)]">
|
||||||
{body.trim() ? (
|
{body.trim() ? (
|
||||||
<Markdown source={body} />
|
<Markdown source={body} />
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-[#62666d] italic">Nothing to preview yet.</p>
|
<p className="text-xs text-[var(--text-dim)] italic">Nothing to preview yet.</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -513,7 +513,7 @@ export default function DocForm({
|
|||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => router.back()}
|
onClick={() => router.back()}
|
||||||
className="px-4 py-2.5 border border-[#23252a] hover:border-[#383b42] text-[#8a8f98] hover:text-[#d0d6e0] rounded-lg text-sm transition-colors"
|
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-lg text-sm transition-colors shadow-sm"
|
||||||
>
|
>
|
||||||
Cancel
|
Cancel
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ export default function Header() {
|
|||||||
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className="sticky top-0 z-30 border-b border-[#1f2022] glass-nav">
|
<header className="sticky top-0 z-30 border-b border-[var(--border-color)] glass-nav">
|
||||||
<div className="mx-auto max-w-7xl px-4 sm:px-6 h-14 flex items-center justify-between gap-4">
|
<div className="mx-auto max-w-7xl px-4 sm:px-6 h-14 flex items-center justify-between gap-4">
|
||||||
{/* Brand */}
|
{/* Brand */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
@@ -20,11 +20,11 @@ export default function Header() {
|
|||||||
<div className="w-7 h-7 rounded-lg bg-gradient-to-br from-[#5e6ad2] to-[#7170ff] flex items-center justify-center text-white font-bold text-xs shadow-md shadow-[#5e6ad2]/20 group-hover:scale-105 transition-transform">
|
<div className="w-7 h-7 rounded-lg bg-gradient-to-br from-[#5e6ad2] to-[#7170ff] flex items-center justify-center text-white font-bold text-xs shadow-md shadow-[#5e6ad2]/20 group-hover:scale-105 transition-transform">
|
||||||
M
|
M
|
||||||
</div>
|
</div>
|
||||||
<span className="font-semibold text-[#f7f8f8] text-base tracking-tight group-hover:text-white transition-colors">
|
<span className="font-semibold text-[var(--text-primary)] text-base tracking-tight transition-colors">
|
||||||
MCPedia
|
MCPedia
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
<span className="hidden sm:inline-block text-[10px] uppercase font-mono px-1.5 py-0.5 rounded bg-[#141517] border border-[#23252a] text-[#8a8f98]">
|
<span className="hidden sm:inline-block text-[10px] uppercase font-mono px-1.5 py-0.5 rounded bg-[var(--bg-elevated)] border border-[var(--border-color)] text-[var(--text-muted)]">
|
||||||
v0.1.0
|
v0.1.0
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -39,11 +39,11 @@ export default function Header() {
|
|||||||
href={`/${s.id}`}
|
href={`/${s.id}`}
|
||||||
className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
|
className={`px-3 py-1.5 rounded-md text-xs font-medium transition-all ${
|
||||||
isActive
|
isActive
|
||||||
? "text-[#f7f8f8] bg-[#141517] border border-[#23252a]"
|
? "text-[var(--text-primary)] bg-[var(--bg-elevated)] border border-[var(--border-color)]"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0] hover:bg-[#141517]/50"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-surface-hover)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<span className="mr-1.5 opacity-70">{s.icon}</span>
|
<span className="mr-1.5 opacity-80">{s.icon}</span>
|
||||||
{s.label}
|
{s.label}
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
@@ -56,7 +56,7 @@ export default function Header() {
|
|||||||
|
|
||||||
<Link
|
<Link
|
||||||
href="/search"
|
href="/search"
|
||||||
className="md:hidden p-1.5 text-[#8a8f98] hover:text-[#f7f8f8] rounded-lg hover:bg-[#141517] transition-colors"
|
className="md:hidden p-1.5 text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded-lg hover:bg-[var(--bg-elevated)] transition-colors"
|
||||||
title="Search"
|
title="Search"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
@@ -66,7 +66,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 bg-[#5e6ad2] hover:bg-[#7170ff] text-white text-xs font-medium rounded-lg transition-all shadow-sm hover:shadow-[#5e6ad2]/20"
|
className="hidden sm:inline-flex items-center gap-1.5 px-3 py-1 bg-[#5e6ad2] hover:bg-[#7170ff] text-white text-xs font-medium rounded-lg transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<span>+</span>
|
<span>+</span>
|
||||||
<span>New Doc</span>
|
<span>New Doc</span>
|
||||||
@@ -76,21 +76,21 @@ export default function Header() {
|
|||||||
href="/login"
|
href="/login"
|
||||||
className={`px-2.5 py-1 text-xs rounded-lg transition-colors ${
|
className={`px-2.5 py-1 text-xs rounded-lg transition-colors ${
|
||||||
pathname === "/login"
|
pathname === "/login"
|
||||||
? "text-[#f7f8f8] bg-[#141517]"
|
? "text-[var(--text-primary)] bg-[var(--bg-elevated)] font-medium"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0] hover:bg-[#141517]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</Link>
|
</Link>
|
||||||
|
|
||||||
<div className="border-l border-[#1f2022] pl-2.5 flex items-center">
|
<div className="border-l border-[var(--border-color)] pl-2.5 flex items-center">
|
||||||
<ThemeToggle />
|
<ThemeToggle />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Mobile menu toggle */}
|
{/* Mobile menu toggle */}
|
||||||
<button
|
<button
|
||||||
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
onClick={() => setMobileMenuOpen(!mobileMenuOpen)}
|
||||||
className="md:hidden p-1 text-[#8a8f98] hover:text-[#f7f8f8]"
|
className="md:hidden p-1 text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
aria-label="Toggle navigation"
|
aria-label="Toggle navigation"
|
||||||
>
|
>
|
||||||
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-5 h-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
@@ -106,23 +106,23 @@ export default function Header() {
|
|||||||
|
|
||||||
{/* Mobile Drawer */}
|
{/* Mobile Drawer */}
|
||||||
{mobileMenuOpen && (
|
{mobileMenuOpen && (
|
||||||
<div className="md:hidden border-t border-[#1f2022] bg-[#0c0d0e] px-4 py-3 space-y-1 animate-fade-in">
|
<div className="md:hidden border-t border-[var(--border-color)] bg-[var(--bg-surface)] px-4 py-3 space-y-1 animate-fade-in shadow-lg">
|
||||||
{SECTIONS.map((s) => (
|
{SECTIONS.map((s) => (
|
||||||
<Link
|
<Link
|
||||||
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-sm text-[#d0d6e0] hover:bg-[#141517] hover:text-[#f7f8f8]"
|
className="flex items-center gap-2 px-3 py-2 rounded-lg text-sm text-[var(--text-secondary)] hover:bg-[var(--bg-elevated)] hover:text-[var(--text-primary)]"
|
||||||
>
|
>
|
||||||
<span>{s.icon}</span>
|
<span>{s.icon}</span>
|
||||||
<span>{s.label}</span>
|
<span>{s.label}</span>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
<div className="pt-2 border-t border-[#1f2022] flex gap-2">
|
<div className="pt-2 border-t border-[var(--border-color)] flex gap-2">
|
||||||
<Link
|
<Link
|
||||||
href="/search"
|
href="/search"
|
||||||
onClick={() => setMobileMenuOpen(false)}
|
onClick={() => setMobileMenuOpen(false)}
|
||||||
className="flex-1 text-center py-2 text-xs bg-[#141517] text-[#d0d6e0] rounded-lg"
|
className="flex-1 text-center py-2 text-xs bg-[var(--bg-elevated)] text-[var(--text-secondary)] rounded-lg font-medium border border-[var(--border-color)]"
|
||||||
>
|
>
|
||||||
Search
|
Search
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -60,9 +60,9 @@ export default function McpConfigSnippet() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-[#0c0d0e] border border-[#1f2022] rounded-xl overflow-hidden shadow-lg">
|
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl overflow-hidden shadow-md">
|
||||||
{/* Header bar with tabs */}
|
{/* Header bar with tabs */}
|
||||||
<div className="flex items-center justify-between px-4 py-2.5 bg-[#141517] border-b border-[#1f2022] 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">
|
<div className="flex items-center gap-1.5">
|
||||||
{CONFIGS.map((c) => (
|
{CONFIGS.map((c) => (
|
||||||
<button
|
<button
|
||||||
@@ -71,7 +71,7 @@ export default function McpConfigSnippet() {
|
|||||||
className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${
|
className={`px-3 py-1 text-xs rounded-md font-medium transition-all ${
|
||||||
activeTab === c.id
|
activeTab === c.id
|
||||||
? "bg-[#5e6ad2] text-white shadow-sm"
|
? "bg-[#5e6ad2] text-white shadow-sm"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0] hover:bg-[#1b1d20]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-surface)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{c.label}
|
{c.label}
|
||||||
@@ -82,14 +82,14 @@ export default function McpConfigSnippet() {
|
|||||||
<button
|
<button
|
||||||
onClick={handleCopy}
|
onClick={handleCopy}
|
||||||
type="button"
|
type="button"
|
||||||
className="flex items-center gap-1.5 text-xs text-[#8a8f98] hover:text-white bg-[#1a1b1d] hover:bg-[#23252a] px-2.5 py-1 rounded border border-[#27292d] transition-all"
|
className="flex items-center gap-1.5 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] bg-[var(--bg-surface)] hover:bg-[var(--bg-elevated-hover)] px-2.5 py-1 rounded border border-[var(--border-color)] transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
{copied ? (
|
{copied ? (
|
||||||
<>
|
<>
|
||||||
<svg className="w-3.5 h-3.5 text-emerald-400" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-3.5 h-3.5 text-emerald-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M5 13l4 4L19 7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="text-emerald-400 font-medium">Copied!</span>
|
<span className="text-emerald-500 font-medium">Copied!</span>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
@@ -103,12 +103,12 @@ export default function McpConfigSnippet() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Code preview */}
|
{/* Code preview */}
|
||||||
<pre className="p-4 text-xs font-mono text-[#d0d6e0] overflow-x-auto leading-relaxed">
|
<pre className="p-4 text-xs font-mono text-[var(--pre-text)] bg-[var(--code-block-bg)] overflow-x-auto leading-relaxed">
|
||||||
<code>{current.content}</code>
|
<code>{current.content}</code>
|
||||||
</pre>
|
</pre>
|
||||||
|
|
||||||
<div className="px-4 py-2 bg-[#090a0b] border-t border-[#1a1b1d] text-[11px] text-[#62666d]">
|
<div className="px-4 py-2 bg-[var(--bg-elevated)] border-t border-[var(--border-color)] text-[11px] text-[var(--text-dim)]">
|
||||||
Config location: <span className="font-mono text-[#8a8f98]">{current.filename}</span>
|
Config location: <span className="font-mono text-[var(--text-muted)]">{current.filename}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -102,14 +102,14 @@ function TreeNodeItem({
|
|||||||
const isCollapsed = collapsedMap[node.slug] ?? false;
|
const isCollapsed = collapsedMap[node.slug] ?? false;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className={`select-none ${depth > 0 ? "ml-3 pl-2 border-l border-[#1f2022]" : ""}`}>
|
<li className={`select-none ${depth > 0 ? "ml-3 pl-2 border-l border-[var(--border-color)]" : ""}`}>
|
||||||
<div
|
<div
|
||||||
className={`group flex items-center justify-between gap-1.5 text-xs py-1.5 px-2 rounded-md transition-all ${
|
className={`group flex items-center justify-between gap-1.5 text-xs py-1.5 px-2 rounded-md transition-all ${
|
||||||
isActive
|
isActive
|
||||||
? "text-[#7170ff] bg-[#5e6ad2]/15 font-medium border border-[#5e6ad2]/30 shadow-sm"
|
? "text-[#5e6ad2] dark:text-[#7170ff] bg-[#5e6ad2]/15 font-semibold border border-[#5e6ad2]/30 shadow-sm"
|
||||||
: isParentOfActive
|
: isParentOfActive
|
||||||
? "text-[#d0d6e0] bg-[#141517]/80"
|
? "text-[var(--text-primary)] bg-[var(--bg-elevated)]"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0] hover:bg-[#141517]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<Link
|
<Link
|
||||||
@@ -125,7 +125,7 @@ function TreeNodeItem({
|
|||||||
|
|
||||||
{isFolder && (
|
{isFolder && (
|
||||||
<div className="flex items-center gap-1 shrink-0">
|
<div className="flex items-center gap-1 shrink-0">
|
||||||
<span className="text-[10px] text-[#62666d] bg-[#191a1b] px-1 py-0.2 rounded font-mono">
|
<span className="text-[10px] text-[var(--text-muted)] bg-[var(--bg-surface)] border border-[var(--border-color)] px-1 py-0.2 rounded font-mono">
|
||||||
{node.docCount}
|
{node.docCount}
|
||||||
</span>
|
</span>
|
||||||
<button
|
<button
|
||||||
@@ -135,7 +135,7 @@ function TreeNodeItem({
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
toggleCollapse(node.slug);
|
toggleCollapse(node.slug);
|
||||||
}}
|
}}
|
||||||
className="p-0.5 text-[#62666d] hover:text-[#d0d6e0] rounded hover:bg-[#23252a] transition-transform"
|
className="p-0.5 text-[var(--text-muted)] hover:text-[var(--text-primary)] rounded hover:bg-[var(--bg-elevated-hover)] transition-transform"
|
||||||
aria-label={isCollapsed ? "Expand folder" : "Collapse folder"}
|
aria-label={isCollapsed ? "Expand folder" : "Collapse folder"}
|
||||||
>
|
>
|
||||||
<svg
|
<svg
|
||||||
@@ -210,10 +210,10 @@ export default function Sidebar() {
|
|||||||
value={filterText}
|
value={filterText}
|
||||||
onChange={(e) => setFilterText(e.target.value)}
|
onChange={(e) => setFilterText(e.target.value)}
|
||||||
placeholder="Filter docs..."
|
placeholder="Filter docs..."
|
||||||
className="w-full pl-7 pr-3 py-1.5 bg-[#141517] border border-[#23252a] hover:border-[#383b42] rounded-md text-xs text-[#f7f8f8] placeholder-[#62666d] focus:outline-none focus:border-[#5e6ad2] transition-colors"
|
className="w-full pl-7 pr-3 py-1.5 bg-[var(--bg-surface)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-md text-xs text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] transition-colors"
|
||||||
/>
|
/>
|
||||||
<svg
|
<svg
|
||||||
className="w-3.5 h-3.5 text-[#62666d] absolute left-2 top-2.5"
|
className="w-3.5 h-3.5 text-[var(--text-muted)] absolute left-2 top-2.5"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -223,7 +223,7 @@ export default function Sidebar() {
|
|||||||
{filterText && (
|
{filterText && (
|
||||||
<button
|
<button
|
||||||
onClick={() => setFilterText("")}
|
onClick={() => setFilterText("")}
|
||||||
className="absolute right-2 top-2 text-xs text-[#62666d] hover:text-[#d0d6e0]"
|
className="absolute right-2 top-2 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
@@ -232,7 +232,7 @@ export default function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Summary count */}
|
{/* Summary count */}
|
||||||
<div className="flex items-center justify-between px-1 mb-3 text-[11px] text-[#62666d]">
|
<div className="flex items-center justify-between px-1 mb-3 text-[11px] text-[var(--text-dim)]">
|
||||||
<span className="uppercase tracking-wider font-mono">Documentation</span>
|
<span className="uppercase tracking-wider font-mono">Documentation</span>
|
||||||
<span>{filteredDocs.length} items</span>
|
<span>{filteredDocs.length} items</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -247,20 +247,20 @@ export default function Sidebar() {
|
|||||||
const isSectionActive = pathname === `/${id}` || pathname.startsWith(`/${id}/`);
|
const isSectionActive = pathname === `/${id}` || pathname.startsWith(`/${id}/`);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={id} className="border-b border-[#141516] 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-1 py-1 mb-1 rounded text-xs font-semibold uppercase tracking-wider transition-colors ${
|
className={`flex items-center justify-between gap-1.5 px-1 py-1 mb-1 rounded text-xs font-semibold uppercase tracking-wider transition-colors ${
|
||||||
isSectionActive
|
isSectionActive
|
||||||
? "text-[#7170ff]"
|
? "text-[#5e6ad2] dark:text-[#7170ff]"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="flex items-center gap-1.5">
|
<div className="flex items-center gap-1.5">
|
||||||
<span>{icon}</span>
|
<span>{icon}</span>
|
||||||
<span>{label}</span>
|
<span>{label}</span>
|
||||||
</div>
|
</div>
|
||||||
<span className="text-[10px] text-[#62666d] font-normal font-mono">
|
<span className="text-[10px] text-[var(--text-dim)] font-normal font-mono">
|
||||||
{sectionDocs.length}
|
{sectionDocs.length}
|
||||||
</span>
|
</span>
|
||||||
</Link>
|
</Link>
|
||||||
@@ -283,10 +283,10 @@ export default function Sidebar() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Bottom quick links */}
|
{/* Bottom quick links */}
|
||||||
<div className="pt-3 border-t border-[#1f2022] 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-1.5 w-full py-1.5 text-xs bg-[#141517] hover:bg-[#1b1d20] border border-[#23252a] text-[#d0d6e0] hover:text-white rounded-md transition-colors font-medium"
|
className="flex items-center justify-center gap-1.5 w-full py-1.5 text-xs 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 font-medium"
|
||||||
>
|
>
|
||||||
<span>+ Create New Document</span>
|
<span>+ Create New Document</span>
|
||||||
</Link>
|
</Link>
|
||||||
|
|||||||
@@ -60,16 +60,16 @@ export default function TOC({ source }: { source: string }) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<nav className="text-xs">
|
<nav className="text-xs">
|
||||||
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-[#1f2022]">
|
<div className="flex items-center gap-2 mb-3 pb-2 border-b border-[var(--border-color)]">
|
||||||
<svg className="w-3.5 h-3.5 text-[#7170ff]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-3.5 h-3.5 text-[#5e6ad2] dark:text-[#7170ff]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h7" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 6h16M4 12h16M4 18h7" />
|
||||||
</svg>
|
</svg>
|
||||||
<span className="font-semibold uppercase tracking-wider text-[#8a8f98] text-[11px]">
|
<span className="font-semibold uppercase tracking-wider text-[var(--text-muted)] text-[11px]">
|
||||||
On this page
|
On this page
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<ul className="space-y-1.5 border-l border-[#1f2022] pl-2.5">
|
<ul className="space-y-1.5 border-l border-[var(--border-color)] pl-2.5">
|
||||||
{toc.map((entry) => {
|
{toc.map((entry) => {
|
||||||
const isActive = activeId === entry.id;
|
const isActive = activeId === entry.id;
|
||||||
return (
|
return (
|
||||||
@@ -92,8 +92,8 @@ export default function TOC({ source }: { source: string }) {
|
|||||||
}}
|
}}
|
||||||
className={`block py-0.5 transition-colors line-clamp-1 ${
|
className={`block py-0.5 transition-colors line-clamp-1 ${
|
||||||
isActive
|
isActive
|
||||||
? "text-[#7170ff] font-medium translate-x-0.5"
|
? "text-[#5e6ad2] dark:text-[#7170ff] font-semibold translate-x-0.5"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
}`}
|
}`}
|
||||||
title={entry.text}
|
title={entry.text}
|
||||||
>
|
>
|
||||||
|
|||||||
@@ -4,33 +4,59 @@ import { useEffect, useState } from "react";
|
|||||||
|
|
||||||
const STORAGE_KEY = "mcpedia-theme";
|
const STORAGE_KEY = "mcpedia-theme";
|
||||||
|
|
||||||
/** Dark mode toggle — persisted in localStorage, defaults to system preference. */
|
|
||||||
export default function ThemeToggle() {
|
export default function ThemeToggle() {
|
||||||
const [dark, setDark] = useState(false);
|
const [mounted, setMounted] = useState(false);
|
||||||
|
const [isDark, setIsDark] = useState(true);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
setMounted(true);
|
||||||
const stored = localStorage.getItem(STORAGE_KEY);
|
const stored = localStorage.getItem(STORAGE_KEY);
|
||||||
const initial =
|
const darkActive =
|
||||||
stored === "dark" ||
|
stored === "dark" ||
|
||||||
(stored === null &&
|
(stored === null &&
|
||||||
window.matchMedia("(prefers-color-scheme: dark)").matches);
|
window.matchMedia("(prefers-color-scheme: dark)").matches);
|
||||||
setDark(initial);
|
setIsDark(darkActive);
|
||||||
|
if (darkActive) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
function toggle() {
|
function toggle() {
|
||||||
const next = !dark;
|
const next = !isDark;
|
||||||
setDark(next);
|
setIsDark(next);
|
||||||
localStorage.setItem(STORAGE_KEY, next ? "dark" : "light");
|
localStorage.setItem(STORAGE_KEY, next ? "dark" : "light");
|
||||||
document.documentElement.classList.toggle("dark", next);
|
if (next) {
|
||||||
|
document.documentElement.classList.add("dark");
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove("dark");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mounted) {
|
||||||
|
return (
|
||||||
|
<div className="w-8 h-8 rounded-lg bg-[var(--bg-elevated)] border border-[var(--border-color)] opacity-50" />
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<button
|
||||||
onClick={toggle}
|
onClick={toggle}
|
||||||
aria-label="Toggle dark mode"
|
type="button"
|
||||||
className="text-[#d0d6e0] hover:text-[#f7f8f8] p-1 rounded transition-colors"
|
aria-label={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
||||||
|
title={isDark ? "Switch to light mode" : "Switch to dark mode"}
|
||||||
|
className="p-1.5 rounded-lg text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)] border border-transparent hover:border-[var(--border-color)] transition-all flex items-center justify-center"
|
||||||
>
|
>
|
||||||
{dark ? "☀️" : "🌙"}
|
{isDark ? (
|
||||||
|
<svg className="w-4 h-4 text-amber-300" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M12 3v1m0 16v1m9-9h-1M4 12H3m15.364 6.364l-.707-.707M6.343 6.343l-.707-.707m12.728 0l-.707.707M6.343 17.657l-.707.707M16 12a4 4 0 11-8 0 4 4 0 018 0z" />
|
||||||
|
</svg>
|
||||||
|
) : (
|
||||||
|
<svg className="w-4 h-4 text-indigo-600" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
|
||||||
|
</svg>
|
||||||
|
)}
|
||||||
</button>
|
</button>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,23 +19,23 @@ export default async function CreatePage() {
|
|||||||
return (
|
return (
|
||||||
<div className="space-y-6">
|
<div className="space-y-6">
|
||||||
{/* Breadcrumb */}
|
{/* Breadcrumb */}
|
||||||
<nav className="flex items-center gap-2 text-xs text-[#62666d]">
|
<nav className="flex items-center gap-2 text-xs text-[var(--text-dim)]">
|
||||||
<Link href="/" className="hover:text-[#d0d6e0] transition-colors">
|
<Link href="/" className="hover:text-[var(--text-primary)] transition-colors">
|
||||||
MCPedia
|
MCPedia
|
||||||
</Link>
|
</Link>
|
||||||
<span>/</span>
|
<span>/</span>
|
||||||
<span className="text-[#8a8f98] font-medium">Create Document</span>
|
<span className="text-[var(--text-muted)] font-medium">Create Document</span>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div className="flex items-center justify-between pb-4 border-b border-[#1f2022]">
|
<div className="flex items-center justify-between pb-4 border-b border-[var(--border-color)]">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<div className="w-10 h-10 rounded-lg bg-[#5e6ad2]/15 border border-[#5e6ad2]/30 flex items-center justify-center text-xl text-[#7170ff]">
|
<div className="w-10 h-10 rounded-lg bg-[#5e6ad2]/15 border border-[#5e6ad2]/30 flex items-center justify-center text-xl text-[#5e6ad2] dark:text-[#7170ff]">
|
||||||
✏️
|
✏️
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold text-[#f7f8f8] tracking-tight">Create Document</h1>
|
<h1 className="text-2xl font-bold text-[var(--text-primary)] tracking-tight">Create Document</h1>
|
||||||
<p className="text-xs text-[#8a8f98] mt-0.5">
|
<p className="text-xs text-[var(--text-muted)] mt-0.5">
|
||||||
Writes markdown file to disk, records revision, and computes 2048-dim vector embeddings.
|
Writes markdown file to disk, records revision, and computes 2048-dim vector embeddings.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+113
-42
@@ -1,43 +1,102 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin "@tailwindcss/typography";
|
@plugin "@tailwindcss/typography";
|
||||||
|
|
||||||
/* Modern Linear / Vercel style dark-mode-first tokens */
|
/* Light mode defaults (:root) */
|
||||||
@custom-variant dark (&:where(.dark, .dark *));
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--bg-app: #f8fafc;
|
||||||
|
--bg-surface: #ffffff;
|
||||||
|
--bg-surface-hover: #f1f5f9;
|
||||||
|
--bg-elevated: #f1f5f9;
|
||||||
|
--bg-elevated-hover: #e2e8f0;
|
||||||
|
--border-color: #e2e8f0;
|
||||||
|
--border-subtle: #edf2f7;
|
||||||
|
--border-card: #e2e8f0;
|
||||||
|
|
||||||
|
--text-primary: #0f172a;
|
||||||
|
--text-secondary: #334155;
|
||||||
|
--text-muted: #64748b;
|
||||||
|
--text-dim: #94a3b8;
|
||||||
|
|
||||||
|
--brand: #5e6ad2;
|
||||||
|
--accent: #4f46e5;
|
||||||
|
--accent-hover: #4338ca;
|
||||||
|
|
||||||
|
--nav-bg: rgba(255, 255, 255, 0.88);
|
||||||
|
--code-bg: #f1f5f9;
|
||||||
|
--code-block-bg: #0f172a;
|
||||||
|
--pre-text: #f8fafc;
|
||||||
|
--table-zebra: rgba(241, 245, 249, 0.6);
|
||||||
|
--scrollbar-thumb: #cbd5e1;
|
||||||
|
--scrollbar-thumb-hover: #94a3b8;
|
||||||
|
--shadow-card: 0 1px 3px 0 rgba(0, 0, 0, 0.05), 0 1px 2px -1px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Dark mode overrides (html.dark) */
|
||||||
|
html.dark {
|
||||||
|
color-scheme: dark;
|
||||||
|
--bg-app: #08090a;
|
||||||
|
--bg-surface: #0f1011;
|
||||||
|
--bg-surface-hover: #131415;
|
||||||
|
--bg-elevated: #141517;
|
||||||
|
--bg-elevated-hover: #1b1d20;
|
||||||
|
--border-color: #1f2022;
|
||||||
|
--border-subtle: rgba(255, 255, 255, 0.06);
|
||||||
|
--border-card: #1f2022;
|
||||||
|
|
||||||
|
--text-primary: #f7f8f8;
|
||||||
|
--text-secondary: #d0d6e0;
|
||||||
|
--text-muted: #8a8f98;
|
||||||
|
--text-dim: #62666d;
|
||||||
|
|
||||||
|
--brand: #5e6ad2;
|
||||||
|
--accent: #7170ff;
|
||||||
|
--accent-hover: #828fff;
|
||||||
|
|
||||||
|
--nav-bg: rgba(8, 9, 10, 0.85);
|
||||||
|
--code-bg: #141517;
|
||||||
|
--code-block-bg: #0c0d0e;
|
||||||
|
--pre-text: #e2e4e7;
|
||||||
|
--table-zebra: rgba(255, 255, 255, 0.02);
|
||||||
|
--scrollbar-thumb: #23252a;
|
||||||
|
--scrollbar-thumb-hover: #383b42;
|
||||||
|
--shadow-card: none;
|
||||||
|
}
|
||||||
|
|
||||||
@theme inline {
|
@theme inline {
|
||||||
--font-sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
|
--font-sans: "Inter", system-ui, -apple-system, "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: #08090a;
|
--color-background: var(--bg-app);
|
||||||
--color-surface: #0f1011;
|
--color-surface: var(--bg-surface);
|
||||||
--color-elevated: #141517;
|
--color-surface-hover: var(--bg-surface-hover);
|
||||||
--color-elevated-hover: #1b1d20;
|
--color-elevated: var(--bg-elevated);
|
||||||
--color-border: #1f2022;
|
--color-elevated-hover: var(--bg-elevated-hover);
|
||||||
--color-border-subtle: rgba(255, 255, 255, 0.06);
|
--color-border: var(--border-color);
|
||||||
--color-border-standard: rgba(255, 255, 255, 0.1);
|
--color-border-subtle: var(--border-subtle);
|
||||||
|
|
||||||
--color-text-primary: #f7f8f8;
|
--color-text-primary: var(--text-primary);
|
||||||
--color-text-secondary: #d0d6e0;
|
--color-text-secondary: var(--text-secondary);
|
||||||
--color-text-tertiary: #8a8f98;
|
--color-text-tertiary: var(--text-muted);
|
||||||
--color-text-quaternary: #62666d;
|
--color-text-quaternary: var(--text-dim);
|
||||||
|
|
||||||
--color-brand: #5e6ad2;
|
--color-brand: var(--brand);
|
||||||
--color-accent: #7170ff;
|
--color-accent: var(--accent);
|
||||||
--color-accent-hover: #828fff;
|
--color-accent-hover: var(--accent-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
html {
|
html {
|
||||||
scroll-behavior: smooth;
|
scroll-behavior: smooth;
|
||||||
color-scheme: dark;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
background: var(--color-background);
|
background-color: var(--bg-app);
|
||||||
color: var(--color-text-secondary);
|
color: var(--text-secondary);
|
||||||
font-family: var(--font-sans);
|
font-family: var(--font-sans);
|
||||||
font-feature-settings: "cv01", "ss03";
|
font-feature-settings: "cv01", "ss03";
|
||||||
-webkit-font-smoothing: antialiased;
|
-webkit-font-smoothing: antialiased;
|
||||||
-moz-osx-font-smoothing: grayscale;
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
transition: background-color 0.2s ease, color 0.2s ease;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Custom scrollbar */
|
/* Custom scrollbar */
|
||||||
@@ -51,29 +110,41 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
::-webkit-scrollbar-thumb {
|
||||||
background: #23252a;
|
background: var(--scrollbar-thumb);
|
||||||
border-radius: 9999px;
|
border-radius: 9999px;
|
||||||
}
|
}
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb:hover {
|
::-webkit-scrollbar-thumb:hover {
|
||||||
background: #383b42;
|
background: var(--scrollbar-thumb-hover);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Glassmorphism helpers */
|
/* Glassmorphism helpers */
|
||||||
.glass-panel {
|
.glass-panel {
|
||||||
background: rgba(15, 16, 17, 0.75);
|
background: var(--bg-surface);
|
||||||
backdrop-filter: blur(12px);
|
backdrop-filter: blur(12px);
|
||||||
-webkit-backdrop-filter: blur(12px);
|
-webkit-backdrop-filter: blur(12px);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.glass-nav {
|
.glass-nav {
|
||||||
background: rgba(8, 9, 10, 0.85);
|
background: var(--nav-bg);
|
||||||
backdrop-filter: blur(16px);
|
backdrop-filter: blur(16px);
|
||||||
-webkit-backdrop-filter: blur(16px);
|
-webkit-backdrop-filter: blur(16px);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Prose markdown typography */
|
/* Prose markdown typography for both Light and Dark modes */
|
||||||
|
.prose {
|
||||||
|
color: var(--text-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose h1, .prose h2, .prose h3, .prose h4, .prose strong {
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.prose a {
|
||||||
|
color: var(--accent) !important;
|
||||||
|
}
|
||||||
|
|
||||||
.prose table {
|
.prose table {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border-collapse: separate;
|
border-collapse: separate;
|
||||||
@@ -82,19 +153,19 @@ body {
|
|||||||
font-size: 0.875rem;
|
font-size: 0.875rem;
|
||||||
border-radius: 0.5rem;
|
border-radius: 0.5rem;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border: 1px solid #1f2022;
|
border: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose th,
|
.prose th,
|
||||||
.prose td {
|
.prose td {
|
||||||
padding: 0.75rem 1rem;
|
padding: 0.75rem 1rem;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
border-bottom: 1px solid #1f2022;
|
border-bottom: 1px solid var(--border-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose th {
|
.prose th {
|
||||||
background: #141517;
|
background: var(--bg-elevated);
|
||||||
color: #f7f8f8;
|
color: var(--text-primary);
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
font-size: 0.8125rem;
|
font-size: 0.8125rem;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
@@ -102,20 +173,20 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prose td {
|
.prose td {
|
||||||
color: #d0d6e0;
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose tbody tr:last-child td {
|
.prose tbody tr:last-child td {
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose tbody tr:hover {
|
.prose tbody tr:nth-child(even) {
|
||||||
background: rgba(255, 255, 255, 0.02);
|
background: var(--table-zebra);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose pre {
|
.prose pre {
|
||||||
background: #0d0e0f !important;
|
background: var(--code-block-bg) !important;
|
||||||
border: 1px solid #1f2022;
|
border: 1px solid var(--border-color);
|
||||||
border-radius: 0.625rem;
|
border-radius: 0.625rem;
|
||||||
padding: 1rem 1.25rem;
|
padding: 1rem 1.25rem;
|
||||||
overflow-x: auto;
|
overflow-x: auto;
|
||||||
@@ -125,15 +196,15 @@ body {
|
|||||||
.prose code {
|
.prose code {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 0.85em;
|
font-size: 0.85em;
|
||||||
color: #e2e4e7;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose :not(pre) > code {
|
.prose :not(pre) > code {
|
||||||
background: #141517;
|
background: var(--code-bg);
|
||||||
border: 1px solid rgba(255, 255, 255, 0.08);
|
color: var(--text-primary);
|
||||||
|
border: 1px solid var(--border-color);
|
||||||
padding: 0.15rem 0.35rem;
|
padding: 0.15rem 0.35rem;
|
||||||
border-radius: 0.25rem;
|
border-radius: 0.25rem;
|
||||||
font-weight: 400;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose :not(pre) > code::before,
|
.prose :not(pre) > code::before,
|
||||||
@@ -142,17 +213,17 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.prose blockquote {
|
.prose blockquote {
|
||||||
border-left: 3px solid #5e6ad2;
|
border-left: 3px solid var(--brand);
|
||||||
background: rgba(94, 106, 210, 0.05);
|
background: rgba(94, 106, 210, 0.08);
|
||||||
border-radius: 0 0.375rem 0.375rem 0;
|
border-radius: 0 0.375rem 0.375rem 0;
|
||||||
margin: 1.5em 0;
|
margin: 1.5em 0;
|
||||||
padding: 0.75em 1.25em;
|
padding: 0.75em 1.25em;
|
||||||
font-style: normal;
|
font-style: normal;
|
||||||
color: #a0a4a8;
|
color: var(--text-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.prose hr {
|
.prose hr {
|
||||||
border-color: #1f2022;
|
border-color: var(--border-color);
|
||||||
margin: 2.5rem 0;
|
margin: 2.5rem 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+22
-3
@@ -15,8 +15,27 @@ export default function RootLayout({
|
|||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}>) {
|
}>) {
|
||||||
return (
|
return (
|
||||||
<html lang="en" className="h-full scroll-smooth">
|
<html lang="en" className="h-full scroll-smooth" suppressHydrationWarning>
|
||||||
<body className="min-h-screen bg-[#08090a] text-[#e2e4e7] font-sans antialiased selection:bg-[#5e6ad2]/30 selection:text-white flex flex-col">
|
<head>
|
||||||
|
<script
|
||||||
|
dangerouslySetInnerHTML={{
|
||||||
|
__html: `
|
||||||
|
(function() {
|
||||||
|
try {
|
||||||
|
var stored = localStorage.getItem('mcpedia-theme');
|
||||||
|
var isDark = stored === 'dark' || (stored === null && window.matchMedia('(prefers-color-scheme: dark)').matches);
|
||||||
|
if (isDark) {
|
||||||
|
document.documentElement.classList.add('dark');
|
||||||
|
} else {
|
||||||
|
document.documentElement.classList.remove('dark');
|
||||||
|
}
|
||||||
|
} catch(e) {}
|
||||||
|
})();
|
||||||
|
`,
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</head>
|
||||||
|
<body className="min-h-screen bg-[var(--bg-app)] text-[var(--text-secondary)] font-sans antialiased selection:bg-[#5e6ad2]/30 selection:text-white flex flex-col transition-colors duration-150">
|
||||||
<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>
|
||||||
@@ -25,7 +44,7 @@ export default function RootLayout({
|
|||||||
|
|
||||||
<div className="mx-auto max-w-7xl w-full flex-1 flex flex-col">
|
<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-[#1f2022] sticky top-14 h-[calc(100vh-3.5rem)] overflow-y-auto">
|
<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">
|
||||||
|
|||||||
+10
-10
@@ -38,7 +38,7 @@ 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-[#0f1011] border border-[#1f2022] rounded-2xl 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-2xl p-6 sm:p-8 shadow-2xl relative overflow-hidden">
|
||||||
{/* Top decorative glow */}
|
{/* Top decorative glow */}
|
||||||
<div className="absolute -top-12 left-1/2 -translate-x-1/2 w-48 h-24 bg-[#5e6ad2]/20 blur-2xl pointer-events-none rounded-full" />
|
<div className="absolute -top-12 left-1/2 -translate-x-1/2 w-48 h-24 bg-[#5e6ad2]/20 blur-2xl pointer-events-none rounded-full" />
|
||||||
|
|
||||||
@@ -46,21 +46,21 @@ export default function LoginPage() {
|
|||||||
<div className="w-12 h-12 rounded-xl bg-[#5e6ad2] text-white font-bold text-xl flex items-center justify-center mx-auto mb-3 shadow-lg shadow-[#5e6ad2]/25">
|
<div className="w-12 h-12 rounded-xl bg-[#5e6ad2] text-white font-bold text-xl flex items-center justify-center mx-auto mb-3 shadow-lg shadow-[#5e6ad2]/25">
|
||||||
M
|
M
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-bold text-[#f7f8f8] tracking-tight">Admin Authentication</h1>
|
<h1 className="text-2xl font-bold text-[var(--text-primary)] tracking-tight">Admin Authentication</h1>
|
||||||
<p className="text-xs text-[#8a8f98] mt-1.5">
|
<p className="text-xs text-[var(--text-muted)] mt-1.5">
|
||||||
Enter your admin password to create, edit, or delete documents.
|
Enter your admin password to create, edit, or delete documents.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{error && (
|
{error && (
|
||||||
<div className="p-3.5 mb-6 rounded-lg border border-rose-500/30 bg-rose-500/10 text-rose-400 text-xs">
|
<div className="p-3.5 mb-6 rounded-lg border border-rose-500/30 bg-rose-500/10 text-rose-500 dark:text-rose-400 text-xs">
|
||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<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-[#8a8f98] mb-1.5">
|
<label className="block text-xs font-semibold uppercase tracking-wider text-[var(--text-muted)] mb-1.5">
|
||||||
Admin Password
|
Admin Password
|
||||||
</label>
|
</label>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
@@ -69,14 +69,14 @@ export default function LoginPage() {
|
|||||||
value={password}
|
value={password}
|
||||||
onChange={(e) => setPassword(e.target.value)}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
placeholder="Enter password..."
|
placeholder="Enter password..."
|
||||||
className="w-full px-3.5 py-2.5 bg-[#141517] border border-[#23252a] hover:border-[#383b42] rounded-lg text-sm text-[#f7f8f8] placeholder-[#62666d] focus:outline-none focus:border-[#5e6ad2] focus:ring-1 focus:ring-[#5e6ad2] transition-colors"
|
className="w-full px-3.5 py-2.5 bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-lg text-sm text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-1 focus:ring-[#5e6ad2] transition-colors"
|
||||||
required
|
required
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
onClick={() => setShowPassword(!showPassword)}
|
||||||
className="absolute right-3 top-2.5 text-xs text-[#62666d] hover:text-[#d0d6e0]"
|
className="absolute right-3 top-2.5 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
>
|
>
|
||||||
{showPassword ? "Hide" : "Show"}
|
{showPassword ? "Hide" : "Show"}
|
||||||
</button>
|
</button>
|
||||||
@@ -95,14 +95,14 @@ export default function LoginPage() {
|
|||||||
</button>
|
</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
<div className="mt-8 pt-6 border-t border-[#1a1b1d] flex items-center justify-between text-xs text-[#8a8f98]">
|
<div className="mt-8 pt-6 border-t border-[var(--border-color)] flex items-center justify-between text-xs text-[var(--text-muted)]">
|
||||||
<Link
|
<Link
|
||||||
href="/"
|
href="/"
|
||||||
className="hover:text-[#d0d6e0] transition-colors"
|
className="hover:text-[var(--text-primary)] transition-colors"
|
||||||
>
|
>
|
||||||
← Back to Home
|
← Back to Home
|
||||||
</Link>
|
</Link>
|
||||||
<span className="text-[11px] text-[#62666d]">HTTP-only Secure Cookie</span>
|
<span className="text-[11px] text-[var(--text-dim)]">HTTP-only Secure Cookie</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+39
-39
@@ -62,7 +62,7 @@ function buildFolderTree(docs: { slug: string; title: string }[], section: strin
|
|||||||
const aFolder = a.children.length > 0 ? 0 : 1;
|
const aFolder = a.children.length > 0 ? 0 : 1;
|
||||||
const bFolder = b.children.length > 0 ? 0 : 1;
|
const bFolder = b.children.length > 0 ? 0 : 1;
|
||||||
if (aFolder !== bFolder) return aFolder - bFolder;
|
if (aFolder !== bFolder) return aFolder - bFolder;
|
||||||
return a.title.localeCompare(b.title);
|
return (a.title || a.name).localeCompare(b.title || b.name);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,16 +86,16 @@ function renderTree(nodes: TreeNode[], pathname: string) {
|
|||||||
href={`/${node.slug}`}
|
href={`/${node.slug}`}
|
||||||
className={`flex items-center gap-1.5 text-xs py-1 px-1.5 rounded transition-all ${
|
className={`flex items-center gap-1.5 text-xs py-1 px-1.5 rounded transition-all ${
|
||||||
pathname === `/${node.slug}`
|
pathname === `/${node.slug}`
|
||||||
? "text-[#7170ff] bg-[#5e6ad2]/15 font-medium"
|
? "text-[#5e6ad2] dark:text-[#7170ff] bg-[#5e6ad2]/15 font-medium"
|
||||||
: "text-[#8a8f98] hover:text-[#d0d6e0] hover:bg-[#141517]"
|
: "text-[var(--text-muted)] hover:text-[var(--text-primary)] hover:bg-[var(--bg-elevated)]"
|
||||||
}`}
|
}`}
|
||||||
title={node.title}
|
title={node.title}
|
||||||
>
|
>
|
||||||
<span className="opacity-70">{node.isLeaf && node.children.length === 0 ? "📄" : "📁"}</span>
|
<span className="opacity-80">{node.isLeaf && node.children.length === 0 ? "📄" : "📁"}</span>
|
||||||
<span className="truncate">{node.title || node.name}</span>
|
<span className="truncate">{node.title || node.name}</span>
|
||||||
</Link>
|
</Link>
|
||||||
{node.children.length > 0 && (
|
{node.children.length > 0 && (
|
||||||
<div className="ml-3.5 border-l border-[#1f2022] pl-2 mt-0.5">
|
<div className="ml-3.5 border-l border-[var(--border-color)] pl-2 mt-0.5">
|
||||||
{renderTree(node.children, pathname)}
|
{renderTree(node.children, pathname)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -124,7 +124,7 @@ function SectionTree({ section, docs, pathname }: SectionTreeProps) {
|
|||||||
{tree.length > 0 ? (
|
{tree.length > 0 ? (
|
||||||
renderTree(tree, pathname)
|
renderTree(tree, pathname)
|
||||||
) : (
|
) : (
|
||||||
<p className="text-xs text-[#62666d] pl-1">No documents</p>
|
<p className="text-xs text-[var(--text-dim)] pl-1">No documents</p>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@@ -144,19 +144,19 @@ export default async function HomePage() {
|
|||||||
<div className="space-y-16">
|
<div className="space-y-16">
|
||||||
{/* Hero Section */}
|
{/* Hero Section */}
|
||||||
<section className="relative pt-4 pb-2">
|
<section className="relative pt-4 pb-2">
|
||||||
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#5e6ad2]/10 border border-[#5e6ad2]/25 text-[#7170ff] text-xs font-medium mb-6">
|
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-[#5e6ad2]/10 border border-[#5e6ad2]/25 text-[#5e6ad2] dark:text-[#7170ff] text-xs font-medium mb-6">
|
||||||
<span className="w-2 h-2 rounded-full bg-[#5e6ad2] animate-pulse" />
|
<span className="w-2 h-2 rounded-full bg-[#5e6ad2] animate-pulse" />
|
||||||
<span>Model Context Protocol + Unified Knowledge Base</span>
|
<span>Model Context Protocol + Unified Knowledge Base</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-[#f7f8f8] tracking-tight mb-5 leading-tight">
|
<h1 className="text-4xl sm:text-5xl lg:text-6xl font-bold text-[var(--text-primary)] tracking-tight mb-5 leading-tight">
|
||||||
Knowledge Base for <br />
|
Knowledge Base for <br />
|
||||||
<span className="bg-gradient-to-r from-[#7170ff] via-[#828fff] to-white bg-clip-text text-transparent">
|
<span className="bg-gradient-to-r from-[#5e6ad2] via-[#7170ff] to-[#4338ca] dark:to-white bg-clip-text text-transparent">
|
||||||
Humans and AI Agents
|
Humans and AI Agents
|
||||||
</span>
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
|
|
||||||
<p className="text-base sm:text-lg text-[#8a8f98] 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, content-first documentation system. Browse hierarchical notes and CTF writeups in your browser, or connect AI coding assistants directly via native MCP tools.
|
A high-performance, content-first documentation system. Browse hierarchical notes and CTF writeups in your browser, or connect AI coding assistants directly via native MCP tools.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@@ -171,38 +171,38 @@ export default async function HomePage() {
|
|||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href="/search"
|
href="/search"
|
||||||
className="inline-flex items-center gap-2 px-5 py-2.5 bg-[#141517] hover:bg-[#1b1d20] border border-[#23252a] hover:border-[#383b42] text-[#d0d6e0] hover:text-white rounded-lg font-medium text-sm transition-all"
|
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-lg font-medium text-sm transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<svg className="w-4 h-4 text-[#8a8f98]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg className="w-4 h-4 text-[var(--text-muted)]" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" />
|
||||||
</svg>
|
</svg>
|
||||||
<span>Hybrid Search</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-[#141517] hover:bg-[#1b1d20] border border-[#23252a] text-[#8a8f98] hover:text-[#d0d6e0] rounded-lg text-sm transition-colors"
|
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-sm"
|
||||||
>
|
>
|
||||||
<span>+ Create Document</span>
|
<span>+ Create Document</span>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Stats bar */}
|
{/* Stats bar */}
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 p-4 bg-[#0c0d0e] border border-[#1f2022] rounded-xl max-w-3xl">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-3 p-4 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl max-w-3xl shadow-sm">
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xl font-semibold text-[#f7f8f8]">{all.length}</div>
|
<div className="text-xl font-bold text-[var(--text-primary)]">{all.length}</div>
|
||||||
<div className="text-xs text-[#62666d]">Documents indexed</div>
|
<div className="text-xs text-[var(--text-muted)]">Documents indexed</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xl font-semibold text-[#f7f8f8]">{SECTIONS.length}</div>
|
<div className="text-xl font-bold text-[var(--text-primary)]">{SECTIONS.length}</div>
|
||||||
<div className="text-xs text-[#62666d]">Core sections</div>
|
<div className="text-xs text-[var(--text-muted)]">Core sections</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xl font-semibold text-[#7170ff]">RRF Hybrid</div>
|
<div className="text-xl font-bold text-[#5e6ad2] dark:text-[#7170ff]">RRF Hybrid</div>
|
||||||
<div className="text-xs text-[#62666d]">FTS + 2048-dim vectors</div>
|
<div className="text-xs text-[var(--text-muted)]">FTS + 2048-dim vectors</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<div className="text-xl font-semibold text-emerald-400">10 MCP Tools</div>
|
<div className="text-xl font-bold text-emerald-600 dark:text-emerald-400">10 MCP Tools</div>
|
||||||
<div className="text-xs text-[#62666d]">Streamable HTTP :4021</div>
|
<div className="text-xs text-[var(--text-muted)]">Streamable HTTP :4021</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
@@ -210,7 +210,7 @@ export default async function HomePage() {
|
|||||||
{/* Browse by Section */}
|
{/* Browse by Section */}
|
||||||
<section>
|
<section>
|
||||||
<div className="flex items-center justify-between mb-6">
|
<div className="flex items-center justify-between mb-6">
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase tracking-wider">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider">
|
||||||
Browse by Section
|
Browse by Section
|
||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
@@ -221,28 +221,28 @@ export default async function HomePage() {
|
|||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
key={s.id}
|
key={s.id}
|
||||||
className="group flex flex-col justify-between bg-[#0f1011] hover:bg-[#131415] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-xl p-5 transition-all shadow-sm"
|
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-xl p-5 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<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-[#7170ff] hover:underline"
|
className="text-xs font-mono text-[#5e6ad2] dark:text-[#7170ff] hover:underline"
|
||||||
>
|
>
|
||||||
{sectionDocs.length} docs →
|
{sectionDocs.length} docs →
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-semibold text-base text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors mb-1.5">
|
<h3 className="font-semibold text-base text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors mb-1.5">
|
||||||
{s.label}
|
{s.label}
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-xs text-[#8a8f98] mb-4 line-clamp-2 leading-relaxed">
|
<p className="text-xs text-[var(--text-muted)] mb-4 line-clamp-2 leading-relaxed">
|
||||||
{s.desc}
|
{s.desc}
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Inline folder tree */}
|
{/* Inline folder tree */}
|
||||||
<div className="pt-3 border-t border-[#1a1b1d]">
|
<div className="pt-3 border-t border-[var(--border-color)]">
|
||||||
<SectionTree
|
<SectionTree
|
||||||
section={s.id}
|
section={s.id}
|
||||||
docs={all.map((d) => ({ slug: d.slug, title: d.title }))}
|
docs={all.map((d) => ({ slug: d.slug, title: d.title }))}
|
||||||
@@ -259,12 +259,12 @@ export default async function HomePage() {
|
|||||||
{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-6">
|
||||||
<h2 className="text-xs font-semibold text-[#8a8f98] uppercase tracking-wider">
|
<h2 className="text-xs font-semibold text-[var(--text-muted)] uppercase tracking-wider">
|
||||||
Recently Updated Knowledge
|
Recently Updated Knowledge
|
||||||
</h2>
|
</h2>
|
||||||
<Link
|
<Link
|
||||||
href="/docs"
|
href="/docs"
|
||||||
className="text-xs text-[#7170ff] hover:underline"
|
className="text-xs text-[#5e6ad2] dark:text-[#7170ff] hover:underline font-medium"
|
||||||
>
|
>
|
||||||
View all ({all.length}) →
|
View all ({all.length}) →
|
||||||
</Link>
|
</Link>
|
||||||
@@ -277,20 +277,20 @@ export default async function HomePage() {
|
|||||||
<Link
|
<Link
|
||||||
key={d.slug}
|
key={d.slug}
|
||||||
href={`/${d.slug}`}
|
href={`/${d.slug}`}
|
||||||
className="group block bg-[#0f1011] hover:bg-[#131415] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-xl p-4 transition-all shadow-sm"
|
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl p-4 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2 mb-2">
|
<div className="flex items-center justify-between gap-2 mb-2">
|
||||||
<span className="text-xs font-mono text-[#7170ff] uppercase">
|
<span className="text-xs font-mono text-[#5e6ad2] dark:text-[#7170ff] uppercase">
|
||||||
{sInfo?.icon} {d.section}
|
{sInfo?.icon} {d.section}
|
||||||
</span>
|
</span>
|
||||||
<time className="text-[11px] text-[#62666d] font-mono">
|
<time className="text-[11px] text-[var(--text-dim)] font-mono">
|
||||||
{new Date(d.updatedAt).toLocaleDateString(undefined, {
|
{new Date(d.updatedAt).toLocaleDateString(undefined, {
|
||||||
month: "short",
|
month: "short",
|
||||||
day: "numeric",
|
day: "numeric",
|
||||||
})}
|
})}
|
||||||
</time>
|
</time>
|
||||||
</div>
|
</div>
|
||||||
<h3 className="font-medium text-sm text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors line-clamp-1 mb-2">
|
<h3 className="font-medium text-sm text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors line-clamp-1 mb-2">
|
||||||
{d.title}
|
{d.title}
|
||||||
</h3>
|
</h3>
|
||||||
{d.tags.length > 0 && (
|
{d.tags.length > 0 && (
|
||||||
@@ -298,7 +298,7 @@ export default async function HomePage() {
|
|||||||
{d.tags.slice(0, 3).map((t) => (
|
{d.tags.slice(0, 3).map((t) => (
|
||||||
<span
|
<span
|
||||||
key={t}
|
key={t}
|
||||||
className="text-[10px] px-1.5 py-0.25 bg-[#141517] border border-[#23252a] rounded text-[#8a8f98]"
|
className="text-[10px] px-1.5 py-0.25 bg-[var(--bg-elevated)] border border-[var(--border-color)] rounded text-[var(--text-muted)]"
|
||||||
>
|
>
|
||||||
#{t}
|
#{t}
|
||||||
</span>
|
</span>
|
||||||
@@ -313,15 +313,15 @@ export default async function HomePage() {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{/* MCP Agent Integration Showcase */}
|
{/* MCP Agent Integration Showcase */}
|
||||||
<section className="border-t border-[#1f2022] 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-2.5 py-0.5 rounded bg-[#141517] border border-[#23252a] text-[#8a8f98] text-xs font-mono mb-2">
|
<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">
|
||||||
<span>⚡ Native Model Context Protocol (MCP)</span>
|
<span>⚡ Native Model Context Protocol (MCP)</span>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-2xl font-bold text-[#f7f8f8] tracking-tight mb-2">
|
<h2 className="text-2xl font-bold text-[var(--text-primary)] tracking-tight mb-2">
|
||||||
Connect AI Coding Assistants
|
Connect AI Coding Assistants
|
||||||
</h2>
|
</h2>
|
||||||
<p className="text-sm text-[#8a8f98] max-w-xl">
|
<p className="text-sm text-[var(--text-muted)] max-w-xl">
|
||||||
Give Claude Desktop, Cursor, Antigravity, or Zed direct access to your knowledge base via semantic search, read resources, and document tools.
|
Give Claude Desktop, Cursor, Antigravity, or Zed direct access to your knowledge base via semantic search, read resources, and document tools.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -74,16 +74,16 @@ function SearchContent() {
|
|||||||
<div className="max-w-4xl space-y-8">
|
<div className="max-w-4xl space-y-8">
|
||||||
{/* Header */}
|
{/* Header */}
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-3xl font-bold text-[#f7f8f8] tracking-tight mb-2">
|
<h1 className="text-3xl font-bold text-[var(--text-primary)] tracking-tight mb-2">
|
||||||
Search Knowledge Base
|
Search Knowledge Base
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-sm text-[#8a8f98]">
|
<p className="text-sm text-[var(--text-muted)]">
|
||||||
Find answers across all documentation, CTF writeups, research notes, and semantic vector chunks.
|
Find answers across all documentation, CTF writeups, research notes, and semantic vector chunks.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search Box & Controls */}
|
{/* Search Box & Controls */}
|
||||||
<div className="bg-[#0f1011] border border-[#1f2022] rounded-xl p-4 sm:p-5 shadow-sm space-y-4">
|
<div className="bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl p-4 sm:p-5 shadow-sm space-y-4">
|
||||||
{/* Search Input */}
|
{/* Search Input */}
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<input
|
<input
|
||||||
@@ -91,11 +91,11 @@ 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-11 pr-10 py-3 bg-[#141517] border border-[#23252a] hover:border-[#383b42] rounded-lg text-[#f7f8f8] placeholder-[#62666d] focus:outline-none focus:border-[#5e6ad2] focus:ring-2 focus:ring-[#5e6ad2]/20 text-base transition-all"
|
className="w-full pl-11 pr-10 py-3 bg-[var(--bg-elevated)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-lg text-[var(--text-primary)] placeholder-[var(--text-dim)] focus:outline-none focus:border-[var(--brand)] focus:ring-2 focus:ring-[#5e6ad2]/20 text-base transition-all"
|
||||||
autoFocus
|
autoFocus
|
||||||
/>
|
/>
|
||||||
<svg
|
<svg
|
||||||
className="w-5 h-5 text-[#7170ff] absolute left-3.5 top-3.5"
|
className="w-5 h-5 text-[#5e6ad2] dark:text-[#7170ff] absolute left-3.5 top-3.5"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
@@ -108,7 +108,7 @@ function SearchContent() {
|
|||||||
setQuery("");
|
setQuery("");
|
||||||
setResults([]);
|
setResults([]);
|
||||||
}}
|
}}
|
||||||
className="absolute right-3.5 top-3.5 text-xs text-[#62666d] hover:text-[#d0d6e0] p-0.5"
|
className="absolute right-3.5 top-3.5 text-xs text-[var(--text-muted)] hover:text-[var(--text-primary)] p-0.5"
|
||||||
>
|
>
|
||||||
✕
|
✕
|
||||||
</button>
|
</button>
|
||||||
@@ -116,10 +116,10 @@ function SearchContent() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Search Mode & Section Filters */}
|
{/* Search Mode & Section Filters */}
|
||||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pt-2 border-t border-[#1a1b1d] text-xs">
|
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-3 pt-2 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-[#62666d] mr-1">Algorithm:</span>
|
<span className="text-[var(--text-muted)] mr-1">Algorithm:</span>
|
||||||
{[
|
{[
|
||||||
{ id: "hybrid", label: "⚡ Hybrid (RRF)", desc: "FTS + Vector Fusion" },
|
{ id: "hybrid", label: "⚡ Hybrid (RRF)", desc: "FTS + Vector Fusion" },
|
||||||
{ id: "keyword", label: "🔍 Keyword", desc: "PostgreSQL FTS" },
|
{ id: "keyword", label: "🔍 Keyword", desc: "PostgreSQL FTS" },
|
||||||
@@ -133,7 +133,7 @@ function SearchContent() {
|
|||||||
className={`px-3 py-1 rounded-md border font-medium transition-all ${
|
className={`px-3 py-1 rounded-md border font-medium transition-all ${
|
||||||
mode === m.id
|
mode === m.id
|
||||||
? "bg-[#5e6ad2] border-[#5e6ad2] text-white shadow-sm"
|
? "bg-[#5e6ad2] border-[#5e6ad2] text-white shadow-sm"
|
||||||
: "bg-[#141517] border-[#23252a] text-[#8a8f98] hover:text-[#d0d6e0]"
|
: "bg-[var(--bg-elevated)] border-[var(--border-color)] text-[var(--text-muted)] hover:text-[var(--text-primary)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{m.label}
|
{m.label}
|
||||||
@@ -148,8 +148,8 @@ function SearchContent() {
|
|||||||
onClick={() => setSelectedSection("all")}
|
onClick={() => setSelectedSection("all")}
|
||||||
className={`px-2.5 py-1 rounded-md text-xs transition-colors ${
|
className={`px-2.5 py-1 rounded-md text-xs transition-colors ${
|
||||||
selectedSection === "all"
|
selectedSection === "all"
|
||||||
? "bg-[#23252a] text-white"
|
? "bg-[var(--bg-elevated-hover)] text-[var(--text-primary)] font-medium"
|
||||||
: "text-[#62666d] hover:text-[#8a8f98]"
|
: "text-[var(--text-dim)] hover:text-[var(--text-muted)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
All Sections
|
All Sections
|
||||||
@@ -161,8 +161,8 @@ function SearchContent() {
|
|||||||
onClick={() => setSelectedSection(s.id)}
|
onClick={() => setSelectedSection(s.id)}
|
||||||
className={`px-2.5 py-1 rounded-md text-xs transition-colors ${
|
className={`px-2.5 py-1 rounded-md text-xs transition-colors ${
|
||||||
selectedSection === s.id
|
selectedSection === s.id
|
||||||
? "bg-[#23252a] text-white"
|
? "bg-[var(--bg-elevated-hover)] text-[var(--text-primary)] font-medium"
|
||||||
: "text-[#62666d] hover:text-[#8a8f98]"
|
: "text-[var(--text-dim)] hover:text-[var(--text-muted)]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
{s.label}
|
{s.label}
|
||||||
@@ -174,7 +174,7 @@ function SearchContent() {
|
|||||||
|
|
||||||
{/* Loading State */}
|
{/* Loading State */}
|
||||||
{loading && (
|
{loading && (
|
||||||
<div className="flex items-center gap-3 py-8 text-[#8a8f98] text-sm">
|
<div className="flex items-center gap-3 py-8 text-[var(--text-muted)] text-sm">
|
||||||
<div className="w-5 h-5 border-2 border-[#5e6ad2] border-t-transparent rounded-full animate-spin" />
|
<div className="w-5 h-5 border-2 border-[#5e6ad2] border-t-transparent rounded-full animate-spin" />
|
||||||
<span>Searching knowledge base...</span>
|
<span>Searching knowledge base...</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -182,10 +182,10 @@ function SearchContent() {
|
|||||||
|
|
||||||
{/* Empty State */}
|
{/* Empty State */}
|
||||||
{!loading && query && filteredResults.length === 0 && (
|
{!loading && query && filteredResults.length === 0 && (
|
||||||
<div className="text-center py-16 bg-[#0f1011] border border-[#1f2022] rounded-xl">
|
<div className="text-center py-16 bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl shadow-sm">
|
||||||
<div className="text-3xl mb-3">🔍</div>
|
<div className="text-3xl mb-3">🔍</div>
|
||||||
<h3 className="text-base font-medium text-[#f7f8f8] mb-1">No matching documents</h3>
|
<h3 className="text-base font-semibold text-[var(--text-primary)] mb-1">No matching documents</h3>
|
||||||
<p className="text-xs text-[#8a8f98] 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>
|
||||||
</div>
|
</div>
|
||||||
@@ -193,8 +193,8 @@ function SearchContent() {
|
|||||||
|
|
||||||
{/* Initial Hint */}
|
{/* Initial Hint */}
|
||||||
{!loading && !query && (
|
{!loading && !query && (
|
||||||
<div className="p-8 text-center bg-[#0c0d0e] border border-[#1f2022] rounded-xl space-y-4">
|
<div className="p-8 text-center bg-[var(--bg-surface)] border border-[var(--border-color)] rounded-xl space-y-4 shadow-sm">
|
||||||
<p className="text-xs text-[#8a8f98]">Popular search queries:</p>
|
<p className="text-xs text-[var(--text-muted)]">Popular search 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", "ret2win", "bullmq worker", "streamable http", "full-text search", "caddy reverse proxy"].map((term) => (
|
{["stack alignment", "ret2win", "bullmq worker", "streamable http", "full-text search", "caddy reverse proxy"].map((term) => (
|
||||||
<button
|
<button
|
||||||
@@ -203,7 +203,7 @@ function SearchContent() {
|
|||||||
setQuery(term);
|
setQuery(term);
|
||||||
handleSearch(term, mode);
|
handleSearch(term, mode);
|
||||||
}}
|
}}
|
||||||
className="px-3 py-1 bg-[#141517] hover:bg-[#1b1d20] border border-[#23252a] hover:border-[#5e6ad2]/40 rounded-md text-[#d0d6e0] transition-colors"
|
className="px-3 py-1 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-md text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-colors"
|
||||||
>
|
>
|
||||||
{term}
|
{term}
|
||||||
</button>
|
</button>
|
||||||
@@ -215,7 +215,7 @@ function SearchContent() {
|
|||||||
{/* Results List */}
|
{/* Results List */}
|
||||||
{!loading && filteredResults.length > 0 && (
|
{!loading && filteredResults.length > 0 && (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div className="flex items-center justify-between text-xs text-[#62666d]">
|
<div className="flex items-center justify-between text-xs text-[var(--text-dim)]">
|
||||||
<span>Found {filteredResults.length} result{filteredResults.length !== 1 ? "s" : ""}</span>
|
<span>Found {filteredResults.length} result{filteredResults.length !== 1 ? "s" : ""}</span>
|
||||||
<span className="font-mono">Ranked by {mode} score</span>
|
<span className="font-mono">Ranked by {mode} score</span>
|
||||||
</div>
|
</div>
|
||||||
@@ -225,29 +225,29 @@ function SearchContent() {
|
|||||||
<Link
|
<Link
|
||||||
key={r.slug}
|
key={r.slug}
|
||||||
href={`/${r.slug}`}
|
href={`/${r.slug}`}
|
||||||
className="group block bg-[#0f1011] hover:bg-[#131415] border border-[#1f2022] hover:border-[#5e6ad2]/40 rounded-xl p-4 sm:p-5 transition-all shadow-sm"
|
className="group block bg-[var(--bg-surface)] hover:bg-[var(--bg-surface-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl p-4 sm:p-5 transition-all shadow-sm"
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-3 mb-2">
|
<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-xs font-mono px-2 py-0.5 rounded bg-[#5e6ad2]/10 border border-[#5e6ad2]/30 text-[#7170ff] uppercase">
|
<span className="text-xs font-mono px-2 py-0.5 rounded bg-[#5e6ad2]/10 border border-[#5e6ad2]/30 text-[#5e6ad2] dark:text-[#7170ff] uppercase">
|
||||||
{r.section}
|
{r.section}
|
||||||
</span>
|
</span>
|
||||||
<span className="text-xs text-[#62666d] 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-[#8a8f98] bg-[#141517] px-2 py-0.5 rounded">
|
<span className="text-xs font-mono text-[var(--text-muted)] bg-[var(--bg-elevated)] border border-[var(--border-color)] px-2 py-0.5 rounded">
|
||||||
Score: {r.score.toFixed(3)}
|
Score: {r.score.toFixed(3)}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 className="text-base font-semibold text-[#f7f8f8] group-hover:text-[#7170ff] transition-colors mb-2">
|
<h2 className="text-base font-semibold text-[var(--text-primary)] group-hover:text-[#5e6ad2] dark:group-hover:text-[#7170ff] transition-colors mb-2">
|
||||||
{r.title}
|
{r.title}
|
||||||
</h2>
|
</h2>
|
||||||
|
|
||||||
{r.snippet && (
|
{r.snippet && (
|
||||||
<p
|
<p
|
||||||
className="text-xs text-[#8a8f98] leading-relaxed line-clamp-2 [&>mark]:bg-[#5e6ad2]/30 [&>mark]:text-white [&>mark]:px-1 [&>mark]:rounded [&>mark]:font-medium"
|
className="text-xs text-[var(--text-muted)] leading-relaxed line-clamp-2 [&>mark]:bg-[#5e6ad2]/30 [&>mark]:text-[var(--text-primary)] [&>mark]:px-1 [&>mark]:rounded [&>mark]:font-medium"
|
||||||
dangerouslySetInnerHTML={{ __html: r.snippet }}
|
dangerouslySetInnerHTML={{ __html: r.snippet }}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
@@ -265,8 +265,8 @@ export default function SearchPage() {
|
|||||||
<Suspense
|
<Suspense
|
||||||
fallback={
|
fallback={
|
||||||
<div className="max-w-4xl py-12">
|
<div className="max-w-4xl py-12">
|
||||||
<h1 className="text-2xl font-bold text-[#f7f8f8] mb-4">Search</h1>
|
<h1 className="text-2xl font-bold text-[var(--text-primary)] mb-4">Search</h1>
|
||||||
<p className="text-sm text-[#8a8f98]">Loading search module...</p>
|
<p className="text-sm text-[var(--text-muted)]">Loading search module...</p>
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
|||||||
Reference in New Issue
Block a user