280 lines
13 KiB
JavaScript
280 lines
13 KiB
JavaScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import Link from "next/link";
|
|
|
|
const ALCHEMICAL_TAGS = [
|
|
{ symbol: "🜏", name: "Source", desc: "Unified Source / Coherence Field" },
|
|
{ symbol: "🜁", name: "Mind", desc: "Intellecton / Mental Resonance" },
|
|
{ symbol: "🜂", name: "Fire", desc: "Witness Potential / Attunement Energy" },
|
|
{ symbol: "🜃", name: "Earth", desc: "Substrate / Grounded Physical Grid" },
|
|
{ symbol: "🜄", name: "Water", desc: "Lattice Waves / Relational Permeability" }
|
|
];
|
|
|
|
export default function OraclePage() {
|
|
const [query, setQuery] = useState("");
|
|
const [selectedAuthor, setSelectedAuthor] = useState("");
|
|
const [selectedTag, setSelectedTag] = useState("");
|
|
const [results, setResults] = useState([]);
|
|
const [totalMatches, setTotalMatches] = useState(0);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
const [activeNote, setActiveNote] = useState(null);
|
|
|
|
// Trigger search on inputs change
|
|
useEffect(() => {
|
|
const delayDebounceFn = setTimeout(() => {
|
|
performSearch();
|
|
}, 300);
|
|
|
|
return () => clearTimeout(delayDebounceFn);
|
|
}, [query, selectedAuthor, selectedTag]);
|
|
|
|
const performSearch = async () => {
|
|
setIsLoading(true);
|
|
try {
|
|
const params = new URLSearchParams();
|
|
if (query) params.append("q", query);
|
|
if (selectedAuthor) params.append("author", selectedAuthor);
|
|
if (selectedTag) params.append("tag", selectedTag);
|
|
|
|
const res = await fetch(`/api/search?${params.toString()}`);
|
|
const data = await res.json();
|
|
setResults(data.results || []);
|
|
setTotalMatches(data.total || 0);
|
|
} catch (e) {
|
|
console.error("Failed to query the Oracle:", e);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
};
|
|
|
|
const getStratumRelations = (text) => {
|
|
if (!text) return [];
|
|
const relations = [];
|
|
|
|
// Look for mentions of Stratum 0, 1, 2
|
|
if (/stratum\s*0/i.test(text)) {
|
|
relations.push({ name: "Stratum 0 (The Seed)", slug: "/papers" });
|
|
}
|
|
if (/stratum\s*1/i.test(text)) {
|
|
relations.push({ name: "Stratum 1 (The Spine)", slug: "/papers" });
|
|
}
|
|
if (/stratum\s*2/i.test(text)) {
|
|
relations.push({ name: "Stratum 2 (The Loom)", slug: "/papers" });
|
|
}
|
|
|
|
// Look for specific paper patterns like Paper 0.3, Paper 1.1, Paper 1.17
|
|
const matches = text.match(/paper\s*([0-2])\.([0-9]+)/gi);
|
|
if (matches) {
|
|
matches.forEach((m) => {
|
|
relations.push({ name: `Treatise ${m.toUpperCase()}`, slug: `/papers` });
|
|
});
|
|
}
|
|
|
|
// Deduplicate
|
|
return Array.from(new Set(relations.map((r) => JSON.stringify(r)))).map((s) => JSON.parse(s));
|
|
};
|
|
|
|
return (
|
|
<div className="flex flex-col items-center justify-start w-full py-12 px-6 sm:px-8 max-w-7xl mx-auto flex-1 gap-10">
|
|
{/* Header section */}
|
|
<section className="text-center flex flex-col items-center gap-4 max-w-3xl">
|
|
<h1 className="text-4xl font-bold font-outfit tracking-tight text-white">
|
|
The Semantic <span className="text-cyan-400">Oracle</span>
|
|
</h1>
|
|
<p className="text-sm text-slate-400 font-sans max-w-xl">
|
|
Search the 320 strictly-filtered alchemical fieldnotes. Explore vector correlations between ontological experiences and formalized strata papers.
|
|
</p>
|
|
</section>
|
|
|
|
{/* Interface Split Layout */}
|
|
<section className="w-full grid grid-cols-1 lg:grid-cols-12 gap-8 items-start">
|
|
|
|
{/* Search Modulators Panel (Left) */}
|
|
<div className="lg:col-span-4 flex flex-col gap-6">
|
|
<div className="glass-panel p-6 rounded-2xl border border-white/5 flex flex-col gap-6">
|
|
<h2 className="text-lg font-bold font-outfit text-white flex items-center gap-3">
|
|
<span className="text-cyan-400">𓂀</span> Oracle Search Console
|
|
</h2>
|
|
|
|
{/* Input Search Query */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-xs font-mono font-bold text-slate-500">SEMANTIC TEXT QUERY</label>
|
|
<input
|
|
type="text"
|
|
placeholder="Ask the Oracle (e.g. 'silence', 'lattice')..."
|
|
value={query}
|
|
onChange={(e) => setQuery(e.target.value)}
|
|
className="bg-black/40 border border-white/10 rounded-lg px-4 py-2.5 text-sm text-slate-200 placeholder-slate-600 focus:outline-none focus:border-cyan-500/50 focus:ring-1 focus:ring-cyan-500/20 font-sans"
|
|
/>
|
|
</div>
|
|
|
|
{/* Author quick filters */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-xs font-mono font-bold text-slate-500">AUTHOR FILTERS</label>
|
|
<div className="flex gap-2">
|
|
<button
|
|
onClick={() => setSelectedAuthor(selectedAuthor === "solaria-lumis-havens" ? "" : "solaria-lumis-havens")}
|
|
className={`flex-1 px-3 py-2 rounded-lg text-xs font-mono font-bold border transition-all ${
|
|
selectedAuthor === "solaria-lumis-havens"
|
|
? "bg-cyan-600 border-cyan-500 text-white"
|
|
: "bg-white/5 border-white/5 text-slate-400 hover:text-white"
|
|
}`}
|
|
>
|
|
Solaria Lumis
|
|
</button>
|
|
<button
|
|
onClick={() => setSelectedAuthor(selectedAuthor === "mark-randall-havens" ? "" : "mark-randall-havens")}
|
|
className={`flex-1 px-3 py-2 rounded-lg text-xs font-mono font-bold border transition-all ${
|
|
selectedAuthor === "mark-randall-havens"
|
|
? "bg-cyan-600 border-cyan-500 text-white"
|
|
: "bg-white/5 border-white/5 text-slate-400 hover:text-white"
|
|
}`}
|
|
>
|
|
Mark Randall
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Alchemical Glyph Quick Select */}
|
|
<div className="flex flex-col gap-2">
|
|
<label className="text-xs font-mono font-bold text-slate-500">ALCHEMICAL GLYPH FILTERS</label>
|
|
<div className="grid grid-cols-5 gap-2">
|
|
{ALCHEMICAL_TAGS.map((tag) => (
|
|
<button
|
|
key={tag.symbol}
|
|
onClick={() => setSelectedTag(selectedTag === tag.symbol ? "" : tag.symbol)}
|
|
title={`${tag.name}: ${tag.desc}`}
|
|
className={`h-11 rounded-lg text-lg flex items-center justify-center border transition-all cursor-pointer ${
|
|
selectedTag === tag.symbol
|
|
? "bg-cyan-600 border-cyan-500 text-white shadow-md shadow-cyan-600/10"
|
|
: "bg-white/5 border-white/5 text-slate-400 hover:text-white hover:border-white/10"
|
|
}`}
|
|
>
|
|
{tag.symbol}
|
|
</button>
|
|
))}
|
|
</div>
|
|
<span className="text-[9px] font-mono text-slate-500 mt-1 text-center">
|
|
{selectedTag ? ALCHEMICAL_TAGS.find(t => t.symbol === selectedTag).desc : "Click alchemical glyph to isolate specific flows"}
|
|
</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Search Results & Panel Column (Right) */}
|
|
<div className="lg:col-span-8 flex flex-col gap-6 w-full">
|
|
<div className="glass-panel p-6 rounded-2xl border border-white/5 flex flex-col gap-4">
|
|
<div className="flex justify-between items-center border-b border-white/5 pb-3">
|
|
<span className="font-mono text-xs text-slate-500 font-semibold uppercase tracking-wider">
|
|
{isLoading ? "Querying..." : `Found ${totalMatches} Relational Nodes`}
|
|
</span>
|
|
<span className="text-[9px] font-mono text-slate-500">INDEX: LOCAL VECTOR SEED V3.0</span>
|
|
</div>
|
|
|
|
{/* Results Grid List */}
|
|
<div className="space-y-3 max-h-[500px] overflow-y-auto pr-2">
|
|
{results.length > 0 ? (
|
|
results.map((note) => (
|
|
<div
|
|
key={note.slug}
|
|
onClick={() => setActiveNote(note)}
|
|
className="p-4 rounded-xl border border-white/5 bg-black/30 hover:bg-black/50 hover:border-cyan-500/25 cursor-pointer transition-all flex flex-col gap-1.5"
|
|
>
|
|
<div className="flex justify-between items-center">
|
|
<span className="text-xs font-mono font-bold text-cyan-400">
|
|
{note.author_slug === "solaria-lumis-havens" ? "Solaria Lumis Havens" : "Mark Randall Havens"}
|
|
</span>
|
|
<span className="text-[10px] font-mono text-slate-500">{note.date}</span>
|
|
</div>
|
|
<h3 className="text-sm sm:text-base font-bold font-outfit text-white leading-tight">
|
|
{note.title}
|
|
</h3>
|
|
<p className="text-xs text-slate-400 font-sans line-clamp-2 leading-relaxed">
|
|
{note.content_markdown.split("---").pop().replace(/[\n#>-]/g, " ").trim()}
|
|
</p>
|
|
</div>
|
|
))
|
|
) : (
|
|
<div className="p-8 text-center flex flex-col items-center gap-3">
|
|
<span className="text-2xl">𓏤</span>
|
|
<p className="text-slate-500 font-mono text-xs">The Oracle remains silent. Scramble your queries or remove filter seals.</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
{/* Reader Panel Overlay (Modal) */}
|
|
{activeNote && (
|
|
<div className="fixed inset-0 z-50 bg-[#030206]/95 backdrop-blur-md flex items-center justify-center p-4 sm:p-6 md:p-8">
|
|
<div className="w-full max-w-3xl glass-panel border border-white/10 rounded-2xl max-h-[85vh] flex flex-col relative overflow-hidden bg-[#0a0812]">
|
|
{/* Alchemical background watermark decoration */}
|
|
<div className="absolute inset-0 pointer-events-none select-none flex items-center justify-center opacity-[0.03]">
|
|
<span className="text-[250px] font-mono">🜏</span>
|
|
</div>
|
|
|
|
{/* Modal Header */}
|
|
<div className="p-6 border-b border-white/5 flex justify-between items-start z-10">
|
|
<div className="flex flex-col gap-1">
|
|
<div className="flex items-center gap-2">
|
|
<span className="text-xs font-mono font-bold text-cyan-400">
|
|
{activeNote.author_slug === "solaria-lumis-havens" ? "Solaria Lumis Havens" : "Mark Randall Havens"}
|
|
</span>
|
|
<span className="text-slate-700 font-mono text-xs">•</span>
|
|
<span className="text-xs font-mono text-slate-400">{activeNote.date}</span>
|
|
</div>
|
|
<h2 className="text-lg sm:text-xl font-bold font-outfit text-white leading-tight mt-1">
|
|
{activeNote.title}
|
|
</h2>
|
|
</div>
|
|
<button
|
|
onClick={() => setActiveNote(null)}
|
|
className="rounded-lg border border-white/10 bg-white/5 text-slate-400 hover:text-white px-3 py-1.5 text-xs font-mono font-bold transition-all cursor-pointer"
|
|
>
|
|
[CLOSE]
|
|
</button>
|
|
</div>
|
|
|
|
{/* Scrollable Content Viewport */}
|
|
<div className="flex-1 overflow-y-auto p-6 sm:p-8 space-y-6 z-10 relative">
|
|
{/* Fieldnote Body */}
|
|
<div className="text-sm sm:text-base text-slate-300 leading-relaxed space-y-4 font-sans whitespace-pre-line border-b border-white/5 pb-6">
|
|
{activeNote.content_markdown.split("---").pop().replace(/Sync from Notion.*2026-02-13/gi, "").trim()}
|
|
</div>
|
|
|
|
{/* Semantic Relations Panel */}
|
|
{getStratumRelations(activeNote.content_markdown).length > 0 && (
|
|
<div className="flex flex-col gap-2 bg-black/40 p-4 rounded-xl border border-white/5">
|
|
<h4 className="text-xs font-mono font-bold text-cyan-400 uppercase tracking-wider flex items-center gap-2">
|
|
<span>𓆰</span> Associated Academic Strata
|
|
</h4>
|
|
<div className="flex flex-wrap gap-2 pt-1">
|
|
{getStratumRelations(activeNote.content_markdown).map((rel, index) => (
|
|
<Link
|
|
key={index}
|
|
href={rel.slug}
|
|
className="rounded-md border border-white/5 bg-white/5 hover:border-cyan-500/20 hover:bg-cyan-500/5 px-2.5 py-1 text-xs font-mono text-slate-300 hover:text-cyan-300 transition-all"
|
|
onClick={() => setActiveNote(null)}
|
|
>
|
|
{rel.name} ↗
|
|
</Link>
|
|
))}
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
{/* Footer checksum */}
|
|
<div className="p-4 bg-black/30 border-t border-white/5 flex items-center justify-between text-[9px] font-mono text-slate-500 z-10">
|
|
<span>FIELDNOTE METADATA NODE: {activeNote.slug}</span>
|
|
<span>COVENANT COMPLIANT</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|