2025-11-09 19:37:10 +00:00
/ * *
2025-11-09 19:58:05 +00:00
* app . js – v3 . 3.4 DIAGNOSTIC RESONANCE
* High - coherence , readable , maintainable .
2025-11-09 19:37:10 +00:00
* No hacks . No surgery . Only truth .
2025-11-09 19:58:05 +00:00
* Now with diagnostic overlays for rupture illumination .
2025-11-09 19:37:10 +00:00
* /
const els = {
2025-11-09 19:58:05 +00:00
menuBtn : document . getElementById ( "menuBtn" ) ,
primaryNav : document . getElementById ( "primaryNav" ) ,
subNav : document . getElementById ( "subNav" ) ,
sectionSelect : document . getElementById ( "sectionSelect" ) ,
tagSelect : document . getElementById ( "tagSelect" ) ,
sortSelect : document . getElementById ( "sortSelect" ) ,
searchMode : document . getElementById ( "searchMode" ) ,
searchBox : document . getElementById ( "searchBox" ) ,
postList : document . getElementById ( "postList" ) ,
viewer : document . getElementById ( "viewer" ) ,
content : document . getElementById ( "content" ) ,
toggleControls : document . getElementById ( "toggleControls" ) ,
filterPanel : document . getElementById ( "filterPanel" )
2025-11-09 19:37:10 +00:00
} ;
2025-11-09 19:58:05 +00:00
let indexData = null ;
let sidebarOpen = false ;
let currentParent = null ;
let indexFiles = null ; // Cached index files
// ΔTRUTH: Diagnostic overlay for error/clarity banners.
// Rationale: Fixed red banner for immediate visibility; z-index above topbar.
function showDiagnostic ( message ) {
const banner = document . createElement ( 'div' ) ;
banner . style = 'position: fixed; top: 0; left: 0; width: 100%; background: #ff4d4d; color: white; padding: 10px; z-index: 1001; text-align: center; font-weight: bold;' ;
banner . innerHTML = message ;
document . body . appendChild ( banner ) ;
}
2025-11-09 19:37:10 +00:00
// === INITIALIZATION ===
async function init ( ) {
try {
2025-11-09 19:58:05 +00:00
indexData = await ( await fetch ( "index.json" ) ) . json ( ) ;
if ( indexData . flat . length === 0 ) {
showDiagnostic ( 'index.json loaded but no content files found. Add .md or .html files to public/ sections and run node tools/generate-index.mjs.' ) ;
}
indexFiles = indexData . flat . filter ( f => f . isIndex ) ;
populateNav ( ) ;
populateSections ( ) ;
populateTags ( ) ;
wireUI ( ) ;
renderList ( ) ;
handleHash ( ) ;
window . addEventListener ( "hashchange" , handleHash ) ;
console . info ( '%cThe Fold Within: Harmony sustained.' , 'color:#e0b84b' ) ;
2025-11-09 19:37:10 +00:00
} catch ( e ) {
2025-11-09 19:58:05 +00:00
showDiagnostic ( 'Failed to load index.json. Check Network tab for 404 or console for errors. Ensure deployed from public/ directory and index.json is generated.' ) ;
els . viewer . innerHTML = "<h1>Error</h1><p>Failed to load site data. See diagnostic banner for fixes.</p>" ;
2025-11-09 19:37:10 +00:00
}
2025-11-09 19:22:23 +00:00
}
2025-11-09 19:37:10 +00:00
// === NAVIGATION ===
function populateNav ( ) {
2025-11-09 19:58:05 +00:00
els . primaryNav . innerHTML = '<a href="#/">Home</a>' ;
2025-11-09 19:37:10 +00:00
const navSections = [ ... new Set (
indexData . flat
. filter ( f => f . isIndex && f . path . split ( "/" ) . length > 1 )
. map ( f => f . path . split ( "/" ) [ 0 ] )
) ] . sort ( ) ;
navSections . forEach ( s => {
els . primaryNav . innerHTML += ` <a href="#/ ${ s } /"> ${ s . charAt ( 0 ) . toUpperCase ( ) + s . slice ( 1 ) } </a> ` ;
} ) ;
}
function populateSections ( ) {
els . sectionSelect . innerHTML = '<option value="all">All Sections</option>' ;
indexData . sections . forEach ( s => {
const opt = document . createElement ( "option" ) ;
opt . value = s ; opt . textContent = s ;
els . sectionSelect . appendChild ( opt ) ;
} ) ;
const defaultSection = indexData . sections . includes ( "posts" ) ? "posts" : indexData . sections [ 0 ] ;
if ( defaultSection ) els . sectionSelect . value = defaultSection ;
}
function populateTags ( ) {
indexData . tags . forEach ( t => {
const opt = document . createElement ( "option" ) ;
opt . value = t ; opt . textContent = t ;
els . tagSelect . appendChild ( opt ) ;
} ) ;
}
// === UI WIRING ===
function wireUI ( ) {
els . menuBtn . addEventListener ( "click" , ( ) => {
sidebarOpen = ! sidebarOpen ;
2025-11-09 19:58:05 +00:00
document . body . classList . toggle ( "sidebar-open" , sidebarOpen ) ;
2025-11-09 19:37:10 +00:00
} ) ;
els . toggleControls . addEventListener ( "click" , ( ) => {
const open = els . filterPanel . open ;
els . filterPanel . open = ! open ;
2025-11-09 19:58:05 +00:00
els . toggleControls . textContent = open ? "Filters" : "Hide" ;
2025-11-09 19:37:10 +00:00
} ) ;
els . sectionSelect . addEventListener ( "change" , ( ) => {
renderList ( ) ;
2025-11-09 19:58:05 +00:00
if ( els . sectionSelect . value !== "all" ) loadDefaultForSection ( els . sectionSelect . value ) ;
2025-11-09 19:37:10 +00:00
} ) ;
[ els . tagSelect , els . sortSelect , els . searchMode ] . forEach ( el => el . addEventListener ( "change" , renderList ) ) ;
2025-11-09 19:58:05 +00:00
els . searchBox . addEventListener ( "input" , renderList ) ;
2025-11-09 19:37:10 +00:00
// Close sidebar on content click (mobile)
els . content . addEventListener ( "click" , ( e ) => {
if ( window . innerWidth < 1024 && document . body . classList . contains ( "sidebar-open" ) ) {
if ( ! e . target . closest ( "#sidebar" ) ) {
document . body . classList . remove ( "sidebar-open" ) ;
2025-11-09 19:58:05 +00:00
sidebarOpen = false ;
2025-11-09 19:37:10 +00:00
}
}
} ) ;
}
// === LIST RENDERING ===
function renderList ( ) {
const section = els . sectionSelect . value ;
const tags = Array . from ( els . tagSelect . selectedOptions ) . map ( o => o . value . toLowerCase ( ) ) ;
const sort = els . sortSelect . value ;
const mode = els . searchMode . value ;
const query = els . searchBox . value . toLowerCase ( ) ;
2025-11-09 19:58:05 +00:00
let posts = indexData . flat . filter ( p => ! p . isIndex ) ;
2025-11-09 19:37:10 +00:00
if ( section !== "all" ) posts = posts . filter ( p => p . path . split ( '/' ) [ 0 ] === section ) ;
2025-11-09 19:58:05 +00:00
if ( tags . length ) posts = posts . filter ( p => tags . every ( t => p . tags . includes ( t ) ) ) ;
2025-11-09 19:37:10 +00:00
if ( query ) {
posts = posts . filter ( p => {
const text = mode === "content" ? p . title + " " + p . excerpt : p . title ;
2025-11-09 19:58:05 +00:00
return text . toLowerCase ( ) . includes ( query ) ;
2025-11-09 19:37:10 +00:00
} ) ;
}
2025-11-09 19:58:05 +00:00
posts . sort ( ( a , b ) => sort === "newest" ? b . mtime - a . mtime : a . mtime - b . mtime ) ;
2025-11-09 19:37:10 +00:00
els . postList . innerHTML = posts . length ? "" : "<li>No posts found.</li>" ;
2025-11-09 19:58:05 +00:00
if ( ! posts . length ) {
showDiagnostic ( 'No posts found in current filters. If persistent, check index.json for flat entries or add content files and regenerate.' ) ;
}
2025-11-09 19:37:10 +00:00
posts . forEach ( p => {
const li = document . createElement ( "li" ) ;
const pin = p . isPinned ? "Star " : "" ;
2025-11-09 19:58:05 +00:00
const time = new Date ( p . ctime ) . toLocaleDateString ( ) ;
2025-11-09 19:37:10 +00:00
li . innerHTML = ` <a href="#/ ${ p . path } "> ${ pin } ${ p . title } </a><small> ${ time } </small> ` ;
els . postList . appendChild ( li ) ;
} ) ;
}
function loadDefaultForSection ( section ) {
const posts = indexData . flat . filter ( p => p . path . split ( '/' ) [ 0 ] === section && ! p . isIndex ) ;
if ( ! posts . length ) {
els . viewer . innerHTML = ` <h1> ${ section } </h1><p>No content yet.</p> ` ;
return ;
}
const pinned = posts . find ( p => p . isPinned ) || posts . sort ( ( a , b ) => b . mtime - a . mtime ) [ 0 ] ;
location . hash = ` #/ ${ pinned . path } ` ;
}
// === SUBNAV (NESTED HORIZON) ===
function renderSubNav ( parent ) {
const subnav = els . subNav ;
subnav . innerHTML = "" ;
subnav . classList . remove ( "visible" ) ;
2025-11-09 19:58:05 +00:00
if ( ! parent || ! indexData . hierarchies ? . [ parent ] ) return ;
2025-11-09 19:37:10 +00:00
const subs = indexData . hierarchies [ parent ] ;
subs . forEach ( child => {
const link = document . createElement ( "a" ) ;
link . href = ` #/ ${ parent } / ${ child } / ` ;
link . textContent = child . charAt ( 0 ) . toUpperCase ( ) + child . slice ( 1 ) ;
subnav . appendChild ( link ) ;
} ) ;
2025-11-09 19:58:05 +00:00
requestAnimationFrame ( ( ) => subnav . classList . add ( "visible" ) ) ;
2025-11-09 19:37:10 +00:00
}
// === HASH ROUTING ===
async function handleHash ( ) {
2025-11-09 19:58:05 +00:00
els . viewer . innerHTML = "" ;
2025-11-09 19:37:10 +00:00
const rel = location . hash . replace ( /^#\// , "" ) ;
const parts = rel . split ( "/" ) . filter ( Boolean ) ;
const currentParentPath = parts . slice ( 0 , - 1 ) . join ( "/" ) || parts [ 0 ] || null ;
if ( currentParentPath !== currentParent ) {
currentParent = currentParentPath ;
2025-11-09 19:58:05 +00:00
renderSubNav ( currentParent ) ;
2025-11-09 19:37:10 +00:00
}
2025-11-09 19:22:23 +00:00
2025-11-09 19:37:10 +00:00
const topSection = parts [ 0 ] || null ;
if ( topSection && indexData . sections . includes ( topSection ) ) {
els . sectionSelect . value = topSection ;
2025-11-09 19:58:05 +00:00
renderList ( ) ;
2025-11-09 19:37:10 +00:00
}
2025-11-09 19:22:23 +00:00
2025-11-09 19:58:05 +00:00
if ( rel === '' || rel === '#' ) return renderDefault ( ) ;
2025-11-09 19:37:10 +00:00
if ( ! rel ) return renderDefault ( ) ;
if ( rel . endsWith ( '/' ) ) {
const currentPath = parts . join ( "/" ) ;
const indexFile = indexFiles . find ( f => {
const dir = f . path . split ( "/" ) . slice ( 0 , - 1 ) . join ( "/" ) ;
2025-11-09 19:58:05 +00:00
return dir === currentPath ;
2025-11-09 19:37:10 +00:00
} ) ;
if ( indexFile ) {
if ( indexFile . ext === ".md" ) {
await renderMarkdown ( indexFile . path ) ;
} else {
await renderIframe ( "/" + indexFile . path ) ;
}
} else {
if ( topSection ) loadDefaultForSection ( topSection ) ;
2025-11-09 19:58:05 +00:00
else els . viewer . innerHTML = ` <h1> ${ currentPath . split ( "/" ) . pop ( ) } </h1><p>No content yet.</p> ` ;
2025-11-09 19:37:10 +00:00
}
} else {
const file = indexData . flat . find ( f => f . path === rel ) ;
if ( ! file ) {
2025-11-09 19:58:05 +00:00
els . viewer . innerHTML = "<h1>404</h1><p>Not found.</p>" ;
2025-11-09 19:37:10 +00:00
return ;
}
2025-11-09 19:58:05 +00:00
file . ext === ".md" ? await renderMarkdown ( file . path ) : await renderIframe ( "/" + file . path ) ;
2025-11-09 19:37:10 +00:00
}
2025-11-09 19:22:23 +00:00
}
2025-11-09 19:37:10 +00:00
async function renderMarkdown ( rel ) {
const src = await fetch ( rel ) . then ( r => r . ok ? r . text ( ) : "" ) ;
els . viewer . innerHTML = ` <article class="markdown"> ${ marked . parse ( src || "# Untitled" ) } </article> ` ;
}
// === PREVIEW + PORTAL ENGINE ===
async function renderIframe ( rel ) {
const preview = await generatePreview ( rel ) ;
const portalBtn = ` <button class="portal-btn" data-src=" ${ rel } ">Open Full Experience</button> ` ;
els . viewer . innerHTML = `
< div class = "preview-header" > $ { portalBtn } < / d i v >
< article class = "preview-content" > $ { preview } < / a r t i c l e >
` ;
els . viewer . querySelector ( ".portal-btn" ) . addEventListener ( "click" , e => {
2025-11-09 19:58:05 +00:00
window . open ( e . target . dataset . src , "_blank" , "noopener,noreferrer" ) ;
2025-11-09 19:37:10 +00:00
} ) ;
}
async function generatePreview ( rel ) {
try {
const res = await fetch ( rel ) ;
if ( ! res . ok ) throw new Error ( ) ;
const html = await res . text ( ) ;
function sanitizeHTML ( html ) {
return html
. replace ( /<script[\s\S]*?<\/script>/gi , "" )
. replace ( /<style[\s\S]*?<\/style>/gi , "" )
. replace ( /<link[^>]*rel=["']stylesheet["'][^>]*>/gi , "" )
. replace ( /\s+(on\w+)=["'][^"']*["']/gi , "" )
. replace ( /\s+style=["'][^"']*["']/gi , "" )
. replace ( /^\s+|\s+$/g , '' )
. replace ( /(\n\s*){2,}/g , '\n' )
. replace ( /<p>\s*<\/p>/gi , '' )
. replace ( /<br\s*\/?>/gi , '' ) ;
}
let content = sanitizeHTML ( html . match ( /<body[^>]*>([\s\S]*)<\/body>/i ) ? . [ 1 ] || html ) ;
const div = document . createElement ( "div" ) ;
div . innerHTML = content ;
2025-11-09 19:58:05 +00:00
trimPreview ( div , 3 , 3000 ) ; // depth, char limit
2025-11-09 19:37:10 +00:00
return div . innerHTML || ` <p>Empty content.</p> ` ;
} catch {
2025-11-09 19:58:05 +00:00
return ` <p>Preview unavailable. <a href=" ${ rel } " target="_blank" rel="noopener">Open directly</a>.</p> ` ;
2025-11-09 19:37:10 +00:00
}
2025-11-09 19:22:23 +00:00
}
2025-11-09 19:37:10 +00:00
function trimPreview ( el , maxDepth , charLimit , depth = 0 , chars = 0 ) {
if ( depth > maxDepth || chars > charLimit ) {
2025-11-09 19:58:05 +00:00
el . innerHTML = "..." ;
2025-11-09 19:37:10 +00:00
return ;
}
let total = chars ;
for ( const child of [ ... el . children ] ) {
2025-11-09 19:58:05 +00:00
total += child . textContent . length ;
2025-11-09 19:37:10 +00:00
if ( total > charLimit || depth > maxDepth ) {
child . remove ( ) ;
} else {
trimPreview ( child , maxDepth , charLimit , depth + 1 , total ) ;
}
2025-11-08 23:29:53 -06:00
}
}
2025-11-09 19:37:10 +00:00
// === DEFAULT VIEW ===
function renderDefault ( ) {
const defaultSection = indexData . sections . includes ( "posts" ) ? "posts" : indexData . sections [ 0 ] ;
if ( defaultSection ) {
els . sectionSelect . value = defaultSection ;
renderList ( ) ;
loadDefaultForSection ( defaultSection ) ;
} else {
2025-11-09 19:58:05 +00:00
showDiagnostic ( 'No sections detected in index.json. Create folders with .md/.html files in public/ and run node tools/generate-index.mjs to regenerate.' ) ;
els . viewer . innerHTML = "<h1>Welcome</h1><p>Add content to begin. See diagnostic banner for details.</p>" ;
2025-11-09 19:37:10 +00:00
}
2025-11-09 19:22:23 +00:00
}
2025-11-09 19:37:10 +00:00
// === START ===
2025-11-09 19:58:05 +00:00
init ( ) ;