diff --git a/apps/web/app/export/pdf/page.tsx b/apps/web/app/export/pdf/page.tsx
index b167af8..46af142 100644
--- a/apps/web/app/export/pdf/page.tsx
+++ b/apps/web/app/export/pdf/page.tsx
@@ -1,8 +1,9 @@
-import React from "react";
+import React from "next/link";
import Link from "next/link";
import { notFound } from "next/navigation";
import { getExportDocuments, formatScopeTitle, type ExportSortOption } from "@mcpedia/core";
import PdfExportView from "@/components/PdfExportView";
+import { FileText, ArrowLeft } from "lucide-react";
export const dynamic = "force-dynamic";
@@ -43,14 +44,16 @@ export default async function ExportPdfPage({
if (exportData.documents.length === 0) {
return (
-
-
๐
+
+
+
+
No Documents Found for Export
-
+
Could not find any published documents matching the path{" "}
-
+
{targetPath || section || "root"}
.
@@ -58,9 +61,10 @@ export default async function ExportPdfPage({
-
โ Return to Knowledge Base
+
+
Return to Knowledge Base
diff --git a/apps/web/app/globals.css b/apps/web/app/globals.css
index cfe15d1..ccfe6ae 100644
--- a/apps/web/app/globals.css
+++ b/apps/web/app/globals.css
@@ -20,13 +20,15 @@
--brand: #4f46e5;
--brand-hover: #4338ca;
+ --brand-glow: rgba(79, 70, 229, 0.15);
--accent: #4f46e5;
--accent-hover: #4338ca;
+ --cyan-accent: #0284c7;
- --nav-bg: rgba(255, 255, 255, 0.88);
+ --nav-bg: rgba(255, 255, 255, 0.85);
/* Code & Syntax variables (Light Mode) */
- --code-bg: #f1f5f9;
+ --code-bg: #f8fafc;
--code-block-bg: #f8fafc;
--code-header-bg: #f1f5f9;
--border-code: #e2e8f0;
@@ -53,47 +55,52 @@
--hl-operator: #57606a;
--hl-variable: #953800;
--hl-meta: #6e7781;
+
+ /* Grid overlay */
+ --grid-pattern: rgba(15, 23, 42, 0.03);
}
/* 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;
+ --bg-app: #08090d;
+ --bg-surface: #0e1117;
+ --bg-surface-hover: #141822;
+ --bg-elevated: #151a26;
+ --bg-elevated-hover: #1e2436;
+ --border-color: #1e2536;
--border-subtle: rgba(255, 255, 255, 0.06);
- --border-card: #1f2022;
+ --border-card: #1e2536;
- --text-primary: #f7f8f8;
- --text-secondary: #d0d6e0;
- --text-muted: #8a8f98;
- --text-dim: #62666d;
+ --text-primary: #f8fafc;
+ --text-secondary: #cbd5e1;
+ --text-muted: #8492a6;
+ --text-dim: #505c70;
- --brand: #5e6ad2;
- --brand-hover: #7170ff;
- --accent: #7170ff;
- --accent-hover: #828fff;
+ --brand: #6366f1;
+ --brand-hover: #818cf8;
+ --brand-glow: rgba(99, 102, 241, 0.22);
+ --accent: #818cf8;
+ --accent-hover: #a5b4fc;
+ --cyan-accent: #38bdf8;
- --nav-bg: rgba(8, 9, 10, 0.85);
+ --nav-bg: rgba(8, 9, 13, 0.85);
/* Code & Syntax variables (Dark Mode) */
- --code-bg: #141517;
- --code-block-bg: #0d1117;
- --code-header-bg: #161b22;
- --border-code: #30363d;
- --code-button-bg: #21262d;
- --code-button-hover-bg: #30363d;
- --pre-text: #e6edf3;
- --code-inline-bg: #161b22;
- --code-inline-text: #e6edf3;
- --code-inline-border: #30363d;
+ --code-bg: #0b0e14;
+ --code-block-bg: #0a0d14;
+ --code-header-bg: #111520;
+ --border-code: #202738;
+ --code-button-bg: #151a26;
+ --code-button-hover-bg: #202738;
+ --pre-text: #e2e8f0;
+ --code-inline-bg: #131722;
+ --code-inline-text: #e2e8f0;
+ --code-inline-border: #202738;
--table-zebra: rgba(255, 255, 255, 0.02);
- --scrollbar-thumb: #23252a;
- --scrollbar-thumb-hover: #383b42;
+ --scrollbar-thumb: #1e2536;
+ --scrollbar-thumb-hover: #313c54;
--shadow-card: none;
/* Syntax Highlighting (Dark Mode) */
@@ -107,6 +114,9 @@ html.dark {
--hl-operator: #8b949e;
--hl-variable: #ffa657;
--hl-meta: #8b949e;
+
+ /* Grid overlay */
+ --grid-pattern: rgba(255, 255, 255, 0.025);
}
@theme inline {
@@ -141,10 +151,19 @@ body {
background-color: var(--bg-app);
color: var(--text-secondary);
font-family: var(--font-sans);
- font-feature-settings: "cv01", "ss03";
+ font-feature-settings: "cv01", "ss03", "zero";
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
- transition: background-color 0.2s ease, color 0.2s ease;
+ transition: background-color 0.25s ease, color 0.25s ease;
+ position: relative;
+ min-height: 100vh;
+}
+
+/* Background subtle tech grid */
+.tech-grid-bg {
+ background-size: 32px 32px;
+ background-image: linear-gradient(to right, var(--grid-pattern) 1px, transparent 1px),
+ linear-gradient(to bottom, var(--grid-pattern) 1px, transparent 1px);
}
/* Custom scrollbar */
@@ -169,44 +188,100 @@ body {
/* Glassmorphism helpers */
.glass-panel {
background: var(--bg-surface);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
+ backdrop-filter: blur(16px);
+ -webkit-backdrop-filter: blur(16px);
border: 1px solid var(--border-color);
}
.glass-nav {
background: var(--nav-bg);
- backdrop-filter: blur(16px);
- -webkit-backdrop-filter: blur(16px);
+ backdrop-filter: blur(20px);
+ -webkit-backdrop-filter: blur(20px);
+}
+
+.glow-card {
+ position: relative;
+ transition: all 0.25s cubic-bezier(0.16, 1, 0.3, 1);
+}
+
+.glow-card:hover {
+ transform: translateY(-2px);
+ border-color: var(--brand);
+ box-shadow: 0 8px 30px -10px var(--brand-glow);
+}
+
+/* Callout Alert Styles */
+.callout {
+ border-left-width: 4px;
+ border-radius: 0 0.5rem 0.5rem 0;
+ padding: 1rem 1.25rem;
+ margin: 1.5rem 0;
+}
+
+.callout-note {
+ border-left-color: #6366f1;
+ background-color: rgba(99, 102, 241, 0.08);
+}
+
+.callout-tip {
+ border-left-color: #10b981;
+ background-color: rgba(16, 185, 129, 0.08);
+}
+
+.callout-important {
+ border-left-color: #8b5cf6;
+ background-color: rgba(139, 92, 246, 0.08);
+}
+
+.callout-warning {
+ border-left-color: #f59e0b;
+ background-color: rgba(245, 158, 11, 0.08);
+}
+
+.callout-caution {
+ border-left-color: #ef4444;
+ background-color: rgba(239, 68, 68, 0.08);
}
/* Prose markdown typography for both Light and Dark modes */
.prose {
color: var(--text-secondary) !important;
+ max-width: 100% !important;
}
.prose h1, .prose h2, .prose h3, .prose h4, .prose strong {
color: var(--text-primary) !important;
+ font-weight: 700;
+ letter-spacing: -0.02em;
}
.prose a {
color: var(--accent) !important;
+ text-decoration: none;
+ font-weight: 500;
+ transition: color 0.15s ease;
+}
+
+.prose a:hover {
+ color: var(--accent-hover) !important;
+ text-decoration: underline;
}
.prose table {
width: 100%;
border-collapse: separate;
border-spacing: 0;
- margin: 1.75rem 0;
+ margin: 2rem 0;
font-size: 0.875rem;
- border-radius: 0.5rem;
+ border-radius: 0.75rem;
overflow: hidden;
border: 1px solid var(--border-color);
+ background: var(--bg-surface);
}
.prose th,
.prose td {
- padding: 0.75rem 1rem;
+ padding: 0.875rem 1.125rem;
text-align: left;
border-bottom: 1px solid var(--border-color);
}
@@ -217,7 +292,8 @@ body {
font-weight: 600;
font-size: 0.8125rem;
text-transform: uppercase;
- letter-spacing: 0.05em;
+ letter-spacing: 0.06em;
+ font-family: var(--font-mono);
}
.prose td {
@@ -228,6 +304,10 @@ body {
border-bottom: none;
}
+.prose tbody tr:hover {
+ background: var(--bg-elevated-hover);
+}
+
.prose tbody tr:nth-child(even) {
background: var(--table-zebra);
}
@@ -237,10 +317,11 @@ body {
color: var(--pre-text) !important;
border: 1px solid var(--border-code);
border-radius: 0.75rem;
- padding: 1rem 1.25rem;
+ padding: 1.25rem;
overflow-x: auto;
position: relative;
font-family: var(--font-mono);
+ line-height: 1.6;
}
.prose code {
@@ -252,7 +333,7 @@ body {
background: var(--code-inline-bg);
color: var(--code-inline-text);
border: 1px solid var(--code-inline-border);
- padding: 0.15rem 0.375rem;
+ padding: 0.15rem 0.4rem;
border-radius: 0.375rem;
font-weight: 500;
}
@@ -264,12 +345,12 @@ body {
.prose blockquote {
border-left: 3px solid var(--brand);
- background: rgba(94, 106, 210, 0.08);
- border-radius: 0 0.375rem 0.375rem 0;
- margin: 1.5em 0;
- padding: 0.75em 1.25em;
+ background: rgba(99, 102, 241, 0.08);
+ border-radius: 0 0.5rem 0.5rem 0;
+ margin: 1.75em 0;
+ padding: 0.875em 1.25em;
font-style: normal;
- color: var(--text-muted);
+ color: var(--text-secondary);
}
.prose hr {
@@ -346,14 +427,14 @@ body {
}
.hljs-addition {
- color: #1a7f37;
- background-color: rgba(46, 160, 67, 0.15);
+ color: #10b981;
+ background-color: rgba(16, 185, 129, 0.15);
border-radius: 2px;
}
.hljs-deletion {
- color: #cf222e;
- background-color: rgba(248, 81, 73, 0.15);
+ color: #ef4444;
+ background-color: rgba(239, 68, 68, 0.15);
border-radius: 2px;
}
@@ -365,37 +446,50 @@ body {
font-weight: bold;
}
-/* Animations */
+/* Keyframes & Animation utilities */
@keyframes fadeIn {
- from { opacity: 0; transform: translateY(4px); }
+ from { opacity: 0; transform: translateY(6px); }
to { opacity: 1; transform: translateY(0); }
}
-.animate-fade-in {
- animation: fadeIn 0.2s ease-out forwards;
+@keyframes slideDown {
+ from { opacity: 0; transform: translateY(-8px); }
+ to { opacity: 1; transform: translateY(0); }
}
-/* ==========================================================================
- Print & PDF Export Stylesheet
- Optimized for publication-grade PDF generation via browser / window.print()
- ========================================================================== */
+@keyframes pulseGlow {
+ 0%, 100% { opacity: 0.6; }
+ 50% { opacity: 1; filter: drop-shadow(0 0 8px var(--brand)); }
+}
+
+.animate-fade-in {
+ animation: fadeIn 0.25s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+.animate-slide-down {
+ animation: slideDown 0.2s cubic-bezier(0.16, 1, 0.3, 1) forwards;
+}
+
+.animate-pulse-glow {
+ animation: pulseGlow 2.5s ease-in-out infinite;
+}
+
+/* Print & PDF Export Stylesheet */
@media print {
@page {
size: A4 portrait;
margin: 18mm 15mm 18mm 15mm;
}
- /* Force exact background colors and graphics */
*, *::before, *::after {
-webkit-print-color-adjust: exact !important;
print-color-adjust: exact !important;
}
- /* Reset document canvas to white for crisp printing & ink economy */
html, body {
background-color: #ffffff !important;
color: #0f172a !important;
- font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif !important;
+ font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif !important;
font-size: 10pt !important;
line-height: 1.5 !important;
margin: 0 !important;
@@ -403,9 +497,9 @@ body {
width: 100% !important;
}
- /* Hide app navigation, headers, sidebars, interactive controls */
header,
aside,
+ footer,
nav.site-header,
.no-print,
.export-toolbar,
@@ -413,7 +507,6 @@ body {
display: none !important;
}
- /* Full width container in print */
main,
.print-container {
max-width: 100% !important;
@@ -425,7 +518,6 @@ body {
background: transparent !important;
}
- /* Page break rules */
.page-break-before,
.chapter-page,
.challenge-entry {
@@ -433,14 +525,6 @@ body {
page-break-before: always !important;
}
- .page-break-after,
- .cover-page,
- .scoreboard-page,
- .toc-page {
- break-after: page !important;
- page-break-after: always !important;
- }
-
.avoid-break-inside,
.code-block-wrapper,
.code-block-container,
@@ -459,7 +543,6 @@ body {
height: auto !important;
}
- /* Code block wrapping and styling in print */
.code-block-wrapper,
.code-block-container {
break-inside: avoid !important;
@@ -492,87 +575,8 @@ body {
white-space: pre-wrap !important;
word-break: break-all !important;
overflow-wrap: anywhere !important;
- font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace !important;
+ font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace !important;
font-size: 8.5pt !important;
line-height: 1.4 !important;
}
-
- code {
- background-color: #f1f5f9 !important;
- color: #0f172a !important;
- padding: 1pt 3pt !important;
- border-radius: 3px !important;
- border: 1px solid #e2e8f0 !important;
- }
-
- pre {
- background-color: #f8fafc !important;
- color: #0f172a !important;
- border: 1px solid #cbd5e1 !important;
- border-radius: 4px !important;
- padding: 8pt 10pt !important;
- margin: 8pt 0 !important;
- }
-
- pre code {
- background-color: transparent !important;
- border: none !important;
- padding: 0 !important;
- }
-
- /* Typography tweaks for formal print */
- .prose {
- color: #0f172a !important;
- font-size: 10pt !important;
- }
-
- .prose h1,
- .prose h2,
- .prose h3,
- .prose h4,
- .prose h5,
- .prose h6 {
- color: #020617 !important;
- font-weight: 700 !important;
- page-break-after: avoid !important;
- break-after: avoid !important;
- }
-
- .prose p,
- .prose li {
- color: #334155 !important;
- }
-
- .prose a {
- color: #0f172a !important;
- text-decoration: underline !important;
- }
-
- .prose blockquote {
- background-color: #f8fafc !important;
- border-left: 3pt solid #334155 !important;
- color: #334155 !important;
- padding: 6pt 10pt !important;
- margin: 8pt 0 !important;
- }
-
- .prose table {
- border: 1px solid #cbd5e1 !important;
- font-size: 9pt !important;
- width: 100% !important;
- border-collapse: collapse !important;
- }
-
- .prose th {
- background-color: #f1f5f9 !important;
- color: #0f172a !important;
- font-weight: 600 !important;
- border-bottom: 1.5px solid #94a3b8 !important;
- padding: 4pt 6pt !important;
- }
-
- .prose td {
- border-bottom: 1px solid #e2e8f0 !important;
- padding: 4pt 6pt !important;
- }
}
diff --git a/apps/web/app/layout.tsx b/apps/web/app/layout.tsx
index 08b07d0..8c111b0 100644
--- a/apps/web/app/layout.tsx
+++ b/apps/web/app/layout.tsx
@@ -2,9 +2,11 @@ import type { Metadata } from "next";
import "./globals.css";
import Header from "@/components/Header";
import Sidebar from "@/components/Sidebar";
+import Link from "next/link";
+import { Database, Activity, Sparkles, Terminal } from "lucide-react";
export const metadata: Metadata = {
- title: "MCPedia โ Knowledge base for humans and AI agents",
+ title: "MCPedia โ Knowledge Base for Humans and AI Agents",
description:
"A content-first knowledge base. Humans read the Web UI; AI agents use the MCP server. Both share one Core.",
};
@@ -35,16 +37,20 @@ export default function RootLayout({
}}
/>
-
+
+ {/* Top ambient radial glow effect */}
+
+
+
-
+
-
+
+ {/* Global Telemetry & Status Footer */}
+
);
diff --git a/apps/web/app/login/page.tsx b/apps/web/app/login/page.tsx
index 9866279..f5bcf7c 100644
--- a/apps/web/app/login/page.tsx
+++ b/apps/web/app/login/page.tsx
@@ -3,6 +3,7 @@
import { useState } from "react";
import { useRouter } from "next/navigation";
import Link from "next/link";
+import { Lock, Key, Eye, EyeOff, ArrowLeft, ShieldCheck } from "lucide-react";
export default function LoginPage() {
const router = useRouter();
@@ -38,29 +39,29 @@ export default function LoginPage() {
return (
-
+
{/* Top decorative glow */}
-
+
-
- M
+
+
-
Admin Authentication
+
Admin Vault Authentication
- Enter your admin password to create, edit, or delete documents.
+ Enter your admin password to manage documents and trigger schema mutations.
{error && (
-
+
{error}
)}
- โ Back to Home
+
+
Return to Knowledge Base
-
HTTP-only Secure Cookie
+
Secure HTTP Cookie
diff --git a/apps/web/app/page.tsx b/apps/web/app/page.tsx
index acae67f..b678d77 100644
--- a/apps/web/app/page.tsx
+++ b/apps/web/app/page.tsx
@@ -2,6 +2,23 @@ import Link from "next/link";
import { listDocuments, listSections } from "@mcpedia/core";
import { getSectionMeta, type SectionConfig } from "@mcpedia/config";
import McpConfigSnippet from "@/components/McpConfigSnippet";
+import InteractiveTopology from "@/components/InteractiveTopology";
+import {
+ Sparkles,
+ Search,
+ Plus,
+ BookOpen,
+ ArrowRight,
+ Database,
+ Cpu,
+ Layers,
+ Activity,
+ Folder,
+ FileText,
+ Clock,
+ CheckCircle2,
+ Terminal,
+} from "lucide-react";
export const dynamic = "force-dynamic";
@@ -22,7 +39,6 @@ function slugToTitle(segment: string): string {
function buildFolderTree(docs: { slug: string; title: string }[], section: string): TreeNode[] {
const tree: TreeNode[] = [];
- const treeIndex = new Map
();
for (const doc of docs) {
const parts = doc.slug.split("/");
@@ -44,7 +60,6 @@ function buildFolderTree(docs: { slug: string; title: string }[], section: strin
isLeaf: false,
};
current.push(node);
- treeIndex.set(currentPath, node);
}
if (isLeaf) {
@@ -77,26 +92,28 @@ function buildFolderTree(docs: { slug: string; title: string }[], section: strin
return sortAll(tree);
}
-function renderTree(nodes: TreeNode[], pathname: string) {
+function renderTree(nodes: TreeNode[]) {
return (
- {nodes.map((node) => (
+ {nodes.slice(0, 5).map((node) => (
-
- {node.isLeaf && node.children.length === 0 ? "๐" : "๐"}
+
+ {node.isLeaf && node.children.length === 0 ? (
+
+ ) : (
+
+ )}
+
{node.title || node.name}
{node.children.length > 0 && (
-
- {renderTree(node.children, pathname)}
+
+ {renderTree(node.children)}
)}
@@ -105,13 +122,7 @@ function renderTree(nodes: TreeNode[], pathname: string) {
);
}
-interface SectionTreeProps {
- section: string;
- docs: { slug: string; title: string }[];
- pathname: string;
-}
-
-function SectionTree({ section, docs, pathname }: SectionTreeProps) {
+function SectionTree({ section, docs }: { section: string; docs: { slug: string; title: string }[] }) {
const tree = buildFolderTree(
docs.filter((d) => d.slug.startsWith(`${section}/`)),
section,
@@ -120,9 +131,9 @@ function SectionTree({ section, docs, pathname }: SectionTreeProps) {
return (
{tree.length > 0 ? (
- renderTree(tree, pathname)
+ renderTree(tree)
) : (
-
No documents
+
No documents yet
)}
);
@@ -144,76 +155,112 @@ export default async function HomePage() {
return (
{/* Hero Section */}
-
-
-
-
Model Context Protocol + PostgreSQL Core
+
+ {/* Floating status pill */}
+
+
+ Model Context Protocol ยท PostgreSQL Source of Truth
-
+ {/* Primary Title */}
+
Knowledge Base for
-
+
Humans and AI Agents
+ {/* Subtitle */}
- A high-performance, database-backed documentation system. Browse hierarchical notes and technical writeups in your browser, or connect AI coding assistants directly via native MCP tools.
+ A high-performance technical knowledge repository. Browse structured notes and CTF writeups in your browser, or equip AI coding assistants directly via native MCP tools.
- {/* Quick action buttons & stats */}
+ {/* Quick Action Buttons */}
+
Browse Documentation
-
โ
+
+
-
-
Hybrid Search
+
+
Hybrid Search Console
+
-
+ Create Document
+
+
Create Document
- {/* Stats bar */}
-
-
-
{all.length}
-
Documents in DB
+ {/* Live Telemetry Metric Strip */}
+
+
+
+
+
+
+
{all.length}
+
Documents in DB
+
-
-
{sections.length}
-
Active sections
+
+
+
+
+
+
+
{sections.length}
+
Active Sections
+
-
-
RRF Hybrid
-
FTS + Cosine Vectors
+
+
+
+
+
+
+
RRF Hybrid
+
FTS + Cosine Vectors
+
-
-
13 MCP Tools
-
Streamable HTTP :4021
+
+
+
+
+
+
+
13 Tools
+
Streamable HTTP :4021
+
- {/* Browse by Section */}
+ {/* Interactive System Architecture & Topology */}
+
+
+ {/* Browse Knowledge by Section */}
-
- Browse by Section
-
+
+
+
+ Browse Knowledge by Section
+
+
@@ -222,19 +269,19 @@ export default async function HomePage() {
return (
{s.icon}
{sectionDocs.length} docs โ
-
+
{s.label}
@@ -242,12 +289,11 @@ export default async function HomePage() {
- {/* Inline folder tree */}
+ {/* Inline folder directory preview */}
({ slug: d.slug, title: d.title }))}
- pathname="/"
/>
@@ -256,18 +302,22 @@ export default async function HomePage() {
- {/* Recent Documents */}
+ {/* Recently Updated Knowledge */}
{recent.length > 0 && (
-
- Recently Updated Knowledge
-
+
+
+
+ Recently Updated Knowledge
+
+
- View all ({all.length}) โ
+
View all ({all.length})
+
@@ -278,11 +328,12 @@ export default async function HomePage() {
-
-
- {sInfo?.icon} {d.section}
+
+
+ {sInfo?.icon}
+ {d.section}
-
+
{d.title}
{d.tags.length > 0 && (
-
+
{d.tags.slice(0, 3).map((t) => (
#{t}
@@ -313,17 +364,18 @@ export default async function HomePage() {
)}
- {/* MCP Agent Integration Showcase */}
+ {/* Connect AI Assistant Showcase */}
-
-
โก Native Model Context Protocol (MCP)
+
+
+ Connect Coding Assistants
-
- Connect AI Coding Assistants
+
+ Model Context Protocol Configuration
-
- Give Claude Desktop, Cursor, Antigravity, or Zed direct access to your knowledge base via semantic search, read resources, and document tools.
+
+ Equip Claude Desktop, Cursor, Antigravity, or custom agents with direct access to your knowledge base via semantic search, read resources, and document tools.
diff --git a/apps/web/app/search/page.tsx b/apps/web/app/search/page.tsx
index b1c8853..8b2b614 100644
--- a/apps/web/app/search/page.tsx
+++ b/apps/web/app/search/page.tsx
@@ -4,6 +4,19 @@ import { useState, useEffect, useCallback, Suspense } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { DEFAULT_SECTIONS, type SectionConfig } from "@mcpedia/config/sections";
+import {
+ Search,
+ Sparkles,
+ Zap,
+ Cpu,
+ Layers,
+ FileText,
+ Clock,
+ ArrowRight,
+ Filter,
+ X,
+ Compass,
+} from "lucide-react";
interface DocHit {
slug: string;
@@ -78,73 +91,73 @@ function SearchContent() {
}
}
- const filteredResults = selectedSection === "all"
- ? results
- : results.filter((r) => r.section === selectedSection);
+ const filteredResults =
+ selectedSection === "all"
+ ? results
+ : results.filter((r) => r.section === selectedSection);
return (
{/* Header */}
-
- Search Knowledge Base
-
-
- Find answers across all documentation, CTF writeups, research notes, and semantic vector chunks.
+
+
+
+
+
+ Multi-Modal Search Console
+
+
+
+ Search across documentation, CTF writeups, and research notes using PostgreSQL Full-Text Search, Cosine Embeddings, or Reciprocal Rank Fusion.
{/* Search Box & Controls */}
-
- {/* Search Input */}
+
+ {/* Search Input Bar */}
-
+
{query && (
)}
- {/* Search Mode & Section Filters */}
-
+ {/* Algorithm & Filter Controls */}
+
{/* Mode Selector */}
- Algorithm:
+ Algorithm:
{[
- { id: "hybrid", label: "โก Hybrid (RRF)", desc: "FTS + Vector Fusion" },
- { id: "keyword", label: "๐ Keyword", desc: "PostgreSQL FTS" },
- { id: "semantic", label: "๐ง Semantic", desc: "Embeddings Cosine" },
+ { id: "hybrid", label: "โก Hybrid (RRF)", desc: "Reciprocal Rank Fusion (FTS + Vector)" },
+ { id: "keyword", label: "๐ Keyword (FTS)", desc: "PostgreSQL GIN tsvector" },
+ { id: "semantic", label: "๐ง Semantic Vector", desc: "Cosine Similarity over Embeddings" },
].map((m) => (
- {/* Loading State */}
+ {/* Loading Spinner */}
{loading && (
-
+
-
Searching knowledge base...
+
Searching knowledge vector space...
)}
{/* Empty State */}
{!loading && query && filteredResults.length === 0 && (
-
-
๐
-
No matching documents
+
+
+
+
+
No matching documents found
Try switching algorithm mode to Hybrid or Semantic, or search with different keywords.
)}
- {/* Initial Hint */}
+ {/* Initial Suggestions */}
{!loading && !query && (
-
-
Popular search queries:
+
+
Popular search queries:
- {["stack alignment", "ret2win", "bullmq worker", "streamable http", "full-text search", "caddy reverse proxy"].map((term) => (
+ {[
+ "stack alignment",
+ "ret2win exploit",
+ "bullmq worker",
+ "streamable http",
+ "full-text search",
+ "caddy reverse proxy",
+ "pgvector cosine",
+ ].map((term) => (
{
setQuery(term);
handleSearch(term, mode);
}}
- 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"
+ className="px-3.5 py-1.5 bg-[var(--bg-elevated)] hover:bg-[var(--bg-elevated-hover)] border border-[var(--border-color)] hover:border-[var(--brand)] rounded-xl text-[var(--text-secondary)] hover:text-[var(--text-primary)] transition-all font-mono cursor-pointer"
>
{term}
@@ -226,10 +249,10 @@ function SearchContent() {
{/* Results List */}
{!loading && filteredResults.length > 0 && (
-
-
+
+
Found {filteredResults.length} result{filteredResults.length !== 1 ? "s" : ""}
- Ranked by {mode} score
+ Ranked by {mode} score
@@ -237,29 +260,29 @@ function SearchContent() {
-
+
-
+
{r.section}
/{r.slug}
-
+
Score: {r.score.toFixed(3)}
-
+
{r.title}
{r.snippet && (
)}
diff --git a/apps/web/package.json b/apps/web/package.json
index 9a69582..9dddb88 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -14,6 +14,7 @@
"@mcpedia/core": "workspace:*",
"@tailwindcss/typography": "^0.5.20",
"github-slugger": "^2.0.0",
+ "lucide-react": "^1.33.0",
"next": "16.3.1",
"react": "19.2.8",
"react-dom": "19.2.8",
diff --git a/bun.lock b/bun.lock
index 288e175..8603549 100644
--- a/bun.lock
+++ b/bun.lock
@@ -72,6 +72,7 @@
"@mcpedia/core": "workspace:*",
"@tailwindcss/typography": "^0.5.20",
"github-slugger": "^2.0.0",
+ "lucide-react": "^1.33.0",
"next": "16.3.1",
"react": "19.2.8",
"react-dom": "19.2.8",
@@ -1120,6 +1121,8 @@
"lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="],
+ "lucide-react": ["lucide-react@1.33.0", "", { "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-MTRwMy0ZlL8Ur/vOAiJ9XGHE+kFPC7brq6MxAm0GiGXEBj0qy0jA/pG4N675oSzciO/UCdX8T+5yUQdmDeTLxg=="],
+
"luxon": ["luxon@3.7.2", "", {}, "sha512-vtEhXh/gNjI9Yg1u4jX/0YVPMvxzHuGgCm6tC5kZyb08yjGWGnqAjGJvcXbqQR2P3MyMEFnRbpcdFS6PBcLqew=="],
"magic-string": ["magic-string@0.30.21", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],