911 lines
31 KiB
HTML
911 lines
31 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>River Rooks | Design Spec Samples Framework</title>
|
|
<style>
|
|
@import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Mono:wght@400;500;700&family=Inter:wght@400;500;700&family=JetBrains+Mono:wght@400;500;700&display=swap');
|
|
|
|
:root {
|
|
--bg-canvas: #0d0f12;
|
|
--bg-surface: #121417;
|
|
--bg-elevated: #1c1f26;
|
|
--border-color: #2b303b;
|
|
--text-primary: #e0e6f0;
|
|
--text-muted: #8a95a5;
|
|
--accent-cyan: #00f0ff;
|
|
--accent-green: #00e676;
|
|
--accent-amber: #ffb700;
|
|
}
|
|
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
|
|
body {
|
|
background-color: var(--bg-canvas);
|
|
color: var(--text-primary);
|
|
font-family: 'Inter', sans-serif;
|
|
line-height: 1.6;
|
|
min-height: 100vh;
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* Top Master Navigation Bar */
|
|
.master-header {
|
|
background-color: var(--bg-surface);
|
|
border-bottom: 1px solid var(--border-color);
|
|
padding: 10px 20px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
position: sticky;
|
|
top: 0;
|
|
z-index: 1000;
|
|
}
|
|
|
|
.header-top-row {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 12px;
|
|
}
|
|
|
|
.brand-title {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 1.05rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 10px;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.status-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
background-color: var(--accent-cyan);
|
|
display: inline-block;
|
|
}
|
|
|
|
.header-tools {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 12px;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.8rem;
|
|
}
|
|
|
|
/* Samples Selector Pill Bar */
|
|
.samples-nav-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
overflow-x: auto;
|
|
padding-bottom: 4px;
|
|
scrollbar-width: thin;
|
|
scrollbar-color: var(--border-color) transparent;
|
|
}
|
|
|
|
.sample-btn {
|
|
background-color: var(--bg-canvas);
|
|
color: var(--text-muted);
|
|
border: 1px solid var(--border-color);
|
|
padding: 6px 12px;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.78rem;
|
|
cursor: pointer;
|
|
border-radius: 3px;
|
|
white-space: nowrap;
|
|
transition: none; /* Instant snap */
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 6px;
|
|
text-decoration: none;
|
|
}
|
|
|
|
.sample-btn:hover {
|
|
color: var(--text-primary);
|
|
border-color: var(--accent-cyan);
|
|
background-color: var(--bg-elevated);
|
|
}
|
|
|
|
.sample-btn.active {
|
|
background-color: var(--accent-cyan);
|
|
color: var(--bg-canvas);
|
|
border-color: var(--accent-cyan);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.btn-badge {
|
|
font-size: 0.68rem;
|
|
opacity: 0.85;
|
|
}
|
|
|
|
/* Sub-bar / Viewport Controls */
|
|
.viewport-bar {
|
|
background-color: var(--bg-canvas);
|
|
border-bottom: 1px solid var(--border-color);
|
|
padding: 6px 20px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.78rem;
|
|
color: var(--text-muted);
|
|
flex-wrap: wrap;
|
|
gap: 10px;
|
|
}
|
|
|
|
.current-url-display {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.prompt-tag {
|
|
color: var(--accent-cyan);
|
|
font-weight: 700;
|
|
}
|
|
|
|
.frame-controls {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
|
|
.ctrl-btn {
|
|
background-color: var(--bg-surface);
|
|
border: 1px solid var(--border-color);
|
|
color: var(--text-muted);
|
|
padding: 3px 8px;
|
|
font-family: inherit;
|
|
font-size: 0.75rem;
|
|
cursor: pointer;
|
|
border-radius: 2px;
|
|
transition: none;
|
|
text-decoration: none;
|
|
display: inline-flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
}
|
|
|
|
.ctrl-btn:hover {
|
|
color: var(--text-primary);
|
|
border-color: var(--accent-cyan);
|
|
}
|
|
|
|
.ctrl-btn.active {
|
|
color: var(--accent-cyan);
|
|
border-color: var(--accent-cyan);
|
|
background-color: var(--bg-elevated);
|
|
}
|
|
|
|
/* Main Workspace / Viewport */
|
|
.workspace-wrapper {
|
|
flex: 1;
|
|
display: flex;
|
|
flex-direction: column;
|
|
align-items: center;
|
|
padding: 16px;
|
|
background-color: var(--bg-canvas);
|
|
}
|
|
|
|
.frame-container {
|
|
width: 100%;
|
|
max-width: 100%;
|
|
height: calc(100vh - 165px);
|
|
min-height: 560px;
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
background-color: var(--bg-surface);
|
|
display: flex;
|
|
flex-direction: column;
|
|
overflow: hidden;
|
|
transition: max-width 0.15s ease-in-out;
|
|
}
|
|
|
|
.frame-header {
|
|
background-color: var(--bg-canvas);
|
|
border-bottom: 1px solid var(--border-color);
|
|
padding: 6px 14px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
}
|
|
|
|
.window-dots {
|
|
display: flex;
|
|
gap: 6px;
|
|
}
|
|
|
|
.window-dot {
|
|
width: 8px;
|
|
height: 8px;
|
|
border-radius: 50%;
|
|
background-color: var(--border-color);
|
|
}
|
|
|
|
.preview-iframe {
|
|
width: 100%;
|
|
height: 100%;
|
|
border: none;
|
|
background-color: #0d0f12;
|
|
flex: 1;
|
|
}
|
|
|
|
/* Markdown Reader Mode Fallback Container */
|
|
.markdown-viewer {
|
|
display: none;
|
|
padding: 32px 24px;
|
|
overflow-y: auto;
|
|
height: 100%;
|
|
background-color: var(--bg-surface);
|
|
font-family: 'Inter', sans-serif;
|
|
}
|
|
|
|
.markdown-prose {
|
|
max-width: 75ch;
|
|
margin: 0 auto;
|
|
color: var(--text-primary);
|
|
}
|
|
|
|
.markdown-prose h1, .markdown-prose h2, .markdown-prose h3 {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
color: var(--text-primary);
|
|
margin: 24px 0 12px 0;
|
|
}
|
|
|
|
.markdown-prose pre {
|
|
background-color: var(--bg-canvas);
|
|
border: 1px solid var(--border-color);
|
|
padding: 14px;
|
|
border-radius: 4px;
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.82rem;
|
|
overflow-x: auto;
|
|
margin: 16px 0;
|
|
}
|
|
|
|
.markdown-prose code {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
color: var(--accent-cyan);
|
|
}
|
|
|
|
.markdown-prose table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 20px 0;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.85rem;
|
|
}
|
|
|
|
.markdown-prose th, .markdown-prose td {
|
|
border: 1px solid var(--border-color);
|
|
padding: 8px 12px;
|
|
text-align: left;
|
|
}
|
|
|
|
.markdown-prose th {
|
|
background-color: var(--bg-canvas);
|
|
}
|
|
|
|
/* Spec Catalog Grid Drawer */
|
|
.catalog-drawer {
|
|
display: none;
|
|
padding: 24px 16px 40px 16px;
|
|
max-width: 1100px;
|
|
margin: 0 auto;
|
|
width: 100%;
|
|
}
|
|
|
|
.drawer-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
|
gap: 16px;
|
|
margin-top: 16px;
|
|
}
|
|
|
|
.card {
|
|
background-color: var(--bg-surface);
|
|
border: 1px solid var(--border-color);
|
|
border-radius: 4px;
|
|
padding: 16px;
|
|
cursor: pointer;
|
|
display: flex;
|
|
flex-direction: column;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
.card:hover {
|
|
border-color: var(--accent-cyan);
|
|
background-color: var(--bg-elevated);
|
|
}
|
|
|
|
.card-title {
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.95rem;
|
|
font-weight: 700;
|
|
color: var(--text-primary);
|
|
margin-bottom: 6px;
|
|
}
|
|
|
|
.card:hover .card-title {
|
|
color: var(--accent-cyan);
|
|
}
|
|
|
|
.card-desc {
|
|
font-size: 0.85rem;
|
|
color: var(--text-muted);
|
|
margin-bottom: 12px;
|
|
}
|
|
|
|
.card-footer {
|
|
font-family: 'JetBrains Mono', monospace;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
border-top: 1px solid var(--border-color);
|
|
padding-top: 8px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
}
|
|
|
|
/* Bottom Hotkey Help Footer */
|
|
.master-footer {
|
|
background-color: var(--bg-surface);
|
|
border-top: 1px solid var(--border-color);
|
|
padding: 8px 20px;
|
|
font-family: 'IBM Plex Mono', monospace;
|
|
font-size: 0.75rem;
|
|
color: var(--text-muted);
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
flex-wrap: wrap;
|
|
gap: 8px;
|
|
}
|
|
|
|
kbd {
|
|
background-color: var(--bg-canvas);
|
|
border: 1px solid var(--border-color);
|
|
border-bottom: 1.5px solid var(--text-muted);
|
|
border-radius: 3px;
|
|
padding: 1px 4px;
|
|
font-size: 0.72rem;
|
|
color: var(--text-primary);
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- Top Master Navigation Bar with Full Sample Options -->
|
|
<header class="master-header">
|
|
<div class="header-top-row">
|
|
<a href="index.html" class="brand-title">
|
|
<span class="status-dot"></span>
|
|
<span>RIVER ROOKS // SAMPLES VIEWER</span>
|
|
</a>
|
|
|
|
<div class="header-tools">
|
|
<span>Hotkeys: <kbd>1</kbd>-<kbd>9</kbd>, <kbd>0</kbd></span>
|
|
<button class="ctrl-btn" id="btn-toggle-mode" onclick="toggleViewMode()">View: Frame Preview</button>
|
|
<a href="../index.html" class="ctrl-btn">Root Lander ↗</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Options Bar to Select Each Sample -->
|
|
<nav class="samples-nav-bar" id="sampleOptionsBar" aria-label="Sample Selection Options">
|
|
<button class="sample-btn active" data-file="homepage.html" onclick="loadSample('homepage.html', this)">
|
|
<span>[1] Homepage</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="split-panel.html" onclick="loadSample('split-panel.html', this)">
|
|
<span>[2] Split Panel</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="companion.html" onclick="loadSample('companion.html', this)">
|
|
<span>[3] Companion</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="document-flow.html" onclick="loadSample('document-flow.html', this)">
|
|
<span>[4] Doc Flow</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="portfolio.html" onclick="loadSample('portfolio.html', this)">
|
|
<span>[5] Portfolio</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="homelab.html" onclick="loadSample('homelab.html', this)">
|
|
<span>[6] Homelab</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="public-services.html" onclick="loadSample('public-services.html', this)">
|
|
<span>[7] Services</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="sample-dashboard.html" onclick="loadSample('sample-dashboard.html', this)">
|
|
<span>[8] Dashboard</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="sample-command-center.html" onclick="loadSample('sample-command-center.html', this)">
|
|
<span>[9] Command</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="sample-dual-pane-explorer.html" onclick="loadSample('sample-dual-pane-explorer.html', this)">
|
|
<span>[0] Explorer</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="blog-lander-stream.html" onclick="loadSample('blog-lander-stream.html', this)">
|
|
<span>[Lander 1] Stream</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="blog-lander-terminal.html" onclick="loadSample('blog-lander-terminal.html', this)">
|
|
<span>[Lander 2] Terminal</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="blog-lander-cards.html" onclick="loadSample('blog-lander-cards.html', this)">
|
|
<span>[Lander 3] Cards</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="service-gateway-manifesto.html" onclick="loadSample('service-gateway-manifesto.html', this)">
|
|
<span>[Gateway 1] Manifesto</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="service-gateway-terminal.html" onclick="loadSample('service-gateway-terminal.html', this)">
|
|
<span>[Gateway 2] Host Console</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="service-gateway-tiling.html" onclick="loadSample('service-gateway-tiling.html', this)">
|
|
<span>[Gateway 3] Tiling Hub</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="blog-tiling-lead.html" onclick="loadSample('blog-tiling-lead.html', this)">
|
|
<span>[Tiling Blog 1] Lead</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="blog-tiling-split.html" onclick="loadSample('blog-tiling-split.html', this)">
|
|
<span>[Tiling Blog 2] Workspace</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="blog-tiling-catalog.html" onclick="loadSample('blog-tiling-catalog.html', this)">
|
|
<span>[Tiling Blog 3] Dual-Lead</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="article-tiling-firmware.html" onclick="loadSample('article-tiling-firmware.html', this)">
|
|
<span>[Article 1] Firmware</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="article-tiling-traefik.html" onclick="loadSample('article-tiling-traefik.html', this)">
|
|
<span>[Article 2] Traefik</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="article-tiling-heap.html" onclick="loadSample('article-tiling-heap.html', this)">
|
|
<span>[Article 3] Heap CTF</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="canonical-sample-content.md" onclick="loadSample('canonical-sample-content.md', this)">
|
|
<span>[Dataset] Content Spec</span>
|
|
</button>
|
|
<button class="sample-btn" data-file="unified-design-specification.md" onclick="loadSample('unified-design-specification.md', this)">
|
|
<span>[Spec] Master Brief</span>
|
|
</button>
|
|
</nav>
|
|
</header>
|
|
|
|
<!-- Viewport & Iframe Tool Control Bar -->
|
|
<div class="viewport-bar">
|
|
<div class="current-url-display">
|
|
<span class="prompt-tag">$ agy --view=</span>
|
|
<span id="activeFilePath">homepage.html</span>
|
|
<span id="activeFileDesc" style="color: var(--text-muted); font-size: 0.72rem; margin-left: 6px;">[Sample 1: Developer Homepage]</span>
|
|
</div>
|
|
|
|
<div class="frame-controls">
|
|
<!-- Responsive Width Selector -->
|
|
<span style="color: var(--text-muted); margin-right: 4px;">Viewport:</span>
|
|
<button class="ctrl-btn active" id="vp-full" onclick="setViewport('100%', this)">100%</button>
|
|
<button class="ctrl-btn" id="vp-tablet" onclick="setViewport('768px', this)">768px</button>
|
|
<button class="ctrl-btn" id="vp-mobile" onclick="setViewport('380px', this)">380px</button>
|
|
<span style="color: var(--border-color);">|</span>
|
|
<!-- Direct Link Actions -->
|
|
<button class="ctrl-btn" onclick="reloadIframe()" title="Reload current sample">⟳ Reload</button>
|
|
<a href="homepage.html" id="openTabBtn" target="_blank" class="ctrl-btn" title="Open directly in new tab">Open Tab ↗</a>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Workspace: Live Iframe Container -->
|
|
<main class="workspace-wrapper" id="frameWrapper">
|
|
<div class="frame-container" id="frameContainer">
|
|
<div class="frame-header">
|
|
<div class="window-dots">
|
|
<span class="window-dot"></span>
|
|
<span class="window-dot"></span>
|
|
<span class="window-dot"></span>
|
|
</div>
|
|
<div id="frameStatusText">SANDBOX IFRAME READY • ISOLATED WORKSPACE</div>
|
|
<div>STDOUT [200 OK]</div>
|
|
</div>
|
|
|
|
<!-- Live Interactive Iframe -->
|
|
<iframe src="homepage.html" id="sampleIframe" class="preview-iframe" title="Sample Live Viewport"></iframe>
|
|
|
|
<!-- Markdown Viewer Fallback (for .md files) -->
|
|
<div id="markdownViewer" class="markdown-viewer">
|
|
<div class="markdown-prose" id="markdownContent"></div>
|
|
</div>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Spec Catalog Grid Drawer (Alternate View Mode) -->
|
|
<section class="catalog-drawer" id="catalogDrawer" aria-label="Catalog Grid View">
|
|
<div style="display: flex; justify-content: space-between; align-items: baseline; margin-bottom: 12px;">
|
|
<h2 style="font-family: 'IBM Plex Mono', monospace; font-size: 1.2rem; color: var(--text-primary);">Design Spec Samples Catalog</h2>
|
|
<span style="font-family: 'IBM Plex Mono', monospace; font-size: 0.8rem; color: var(--text-muted);">27 Artifacts Registered</span>
|
|
</div>
|
|
<div class="drawer-grid" id="drawerGrid"></div>
|
|
</section>
|
|
|
|
<!-- Bottom Global Status Footer -->
|
|
<footer class="master-footer">
|
|
<div>Unified Design Language • Dark Slate Native (#0d0f12)</div>
|
|
<div>Press <kbd>1</kbd>-<kbd>0</kbd> to quick-switch • Canonical Dataset Compliant</div>
|
|
</footer>
|
|
|
|
<script>
|
|
const catalogData = [
|
|
{
|
|
key: '1',
|
|
file: 'homepage.html',
|
|
title: 'Sample 1: Developer Homepage',
|
|
type: 'html',
|
|
desc: 'Dark-mode developer homepage with sidebar navigation, active stack cluster, featured firmware SPI extraction write-up, and dense archive table.'
|
|
},
|
|
{
|
|
key: '2',
|
|
file: 'split-panel.html',
|
|
title: 'Sample 2: Split Panel Layout',
|
|
type: 'html',
|
|
desc: 'Fixed left terminal mascot with binary animation frames, and scrollable right panel showcasing the 5 canonical system modules.'
|
|
},
|
|
{
|
|
key: '3',
|
|
file: 'companion.html',
|
|
title: 'Sample 3: Companion Layout',
|
|
type: 'html',
|
|
desc: '50/50 vertical split layout with system module cards on the left and a reactive ASCII companion daemon (sys-daemon) on the right.'
|
|
},
|
|
{
|
|
key: '4',
|
|
file: 'document-flow.html',
|
|
title: 'Sample 4: Continuous Document Flow',
|
|
type: 'html',
|
|
desc: 'Continuous reading stream across system modules with pinned IntersectionObserver scroll-tracker daemon observing scroll depth.'
|
|
},
|
|
{
|
|
key: '5',
|
|
file: 'portfolio.html',
|
|
title: 'Sample 5: River Rooks Portfolio',
|
|
type: 'html',
|
|
desc: 'Single-column digital business card landing page with stdout terminal status block, 11-item active stack tags, and cryptographic links.'
|
|
},
|
|
{
|
|
key: '6',
|
|
file: 'homelab.html',
|
|
title: 'Sample 6: Homelab Lander',
|
|
type: 'html',
|
|
desc: 'Infrastructure catalog with category filter tabs, 12 microservices, 3 bare-metal server specs, and ASCII network topology routing map.'
|
|
},
|
|
{
|
|
key: '7',
|
|
file: 'public-services.html',
|
|
title: 'Sample 7: Public Services Portal',
|
|
type: 'html',
|
|
desc: 'Launcher portal for self-hosted web applications with real-time keyword filter, category pills, access levels, and PGP/SSH identity verification box.'
|
|
},
|
|
{
|
|
key: '8',
|
|
file: 'sample-dashboard.html',
|
|
title: 'Sample 8: 3-Tile Workspace Dashboard',
|
|
type: 'html',
|
|
desc: 'Neovim/Tmux 3-tile modal workspace featuring live services in left tile, River Rooks resume/bio in center, and 5-post blog archive in right tile.'
|
|
},
|
|
{
|
|
key: '9',
|
|
file: 'sample-command-center.html',
|
|
title: 'Sample 9: Tabbed Command Center',
|
|
type: 'html',
|
|
desc: 'Zed Editor / Ghostty tabbed utility layout with keyboard shortcuts [1], [2], [3] switching between Services, Blog Archive, and Resume Config.'
|
|
},
|
|
{
|
|
key: '0',
|
|
file: 'sample-dual-pane-explorer.html',
|
|
title: 'Sample 10: Dual-Pane Helix Tree Explorer',
|
|
type: 'html',
|
|
desc: 'Split-screen file-tree explorer with interactive document viewer and sys-daemon mascot reacting to folder item selection.'
|
|
},
|
|
{
|
|
key: 'l1',
|
|
file: 'blog-lander-stream.html',
|
|
title: 'Blog Lander 1: Minimalist Stream',
|
|
type: 'html',
|
|
desc: 'Single-column 65ch linear reading stream with topic filter pills, canonical article abstracts, tag clusters, and PGP/RSS links.'
|
|
},
|
|
{
|
|
key: 'l2',
|
|
file: 'blog-lander-terminal.html',
|
|
title: 'Blog Lander 2: Terminal Directory Index',
|
|
type: 'html',
|
|
desc: 'High-density POSIX tabular directory index mimicking ls -la with real-time grep filter and inline code inspection drawer.'
|
|
},
|
|
{
|
|
key: 'l3',
|
|
file: 'blog-lander-cards.html',
|
|
title: 'Blog Lander 3: Curated Dispatch & Cards',
|
|
type: 'html',
|
|
desc: 'Structured blog lander with a pinned lead article card featuring code snippet preview, and a 2-column grid of recent technical dispatches.'
|
|
},
|
|
{
|
|
key: 'g1',
|
|
file: 'service-gateway-manifesto.html',
|
|
title: 'Gateway Lander 1: Manifesto & System Hub',
|
|
type: 'html',
|
|
desc: 'Gateway portal where the homepage manifesto is the central focus, routing visitors to homelab services (with the blog highlighted) and recent dispatches.'
|
|
},
|
|
{
|
|
key: 'g2',
|
|
file: 'service-gateway-terminal.html',
|
|
title: 'Gateway Lander 2: Host Console & MOTD Router',
|
|
type: 'html',
|
|
desc: 'Terminal-style MOTD gateway centering on system landing prose, an interactive service routing table featuring the blog, and live dispatches log.'
|
|
},
|
|
{
|
|
key: 'g3',
|
|
file: 'service-gateway-tiling.html',
|
|
title: 'Gateway Lander 3: Tiling Workspace Hub',
|
|
type: 'html',
|
|
desc: 'Tiling window manager aesthetic with a prominent hero landing tile, a dedicated core blog feature tile, homelab services grid, and node telemetry.'
|
|
},
|
|
{
|
|
key: 'tb1',
|
|
file: 'blog-tiling-lead.html',
|
|
title: 'Tiling Blog 1: Hero Dispatch & Dual Stream',
|
|
type: 'html',
|
|
desc: 'Gateway 3 tiling aesthetic applied to the blog, featuring a pinned lead write-up with code preview, research tracks, and full-width chronological feed.'
|
|
},
|
|
{
|
|
key: 'tb2',
|
|
file: 'blog-tiling-split.html',
|
|
title: 'Tiling Blog 2: Asymmetric 3-Tile Workspace',
|
|
type: 'html',
|
|
desc: 'Asymmetric 3-tile blog workspace with interactive live grep filter, embedded code blocks, curated tracks, and security wire.'
|
|
},
|
|
{
|
|
key: 'tb3',
|
|
file: 'blog-tiling-catalog.html',
|
|
title: 'Tiling Blog 3: Dual-Lead Grid & Inspection Feed',
|
|
type: 'html',
|
|
desc: 'Dual-lead side-by-side featured article tiles with code snippets, plus an archive feed table with an interactive inline note inspector.'
|
|
},
|
|
{
|
|
key: 'a1',
|
|
file: 'article-tiling-firmware.html',
|
|
title: 'Article 1: IoT Firmware Reverse Engineering',
|
|
type: 'html',
|
|
desc: 'Article reading template featuring left sidebar TOC, collapsible related dispatches drawer, SOP8 pinout matrix, flashrom code blocks with copy utility, alerts, and task lists.'
|
|
},
|
|
{
|
|
key: 'a2',
|
|
file: 'article-tiling-traefik.html',
|
|
title: 'Article 2: High-Availability Traefik Router',
|
|
type: 'html',
|
|
desc: 'Article reading template featuring right sidebar TOC, collapsible homelab series drawer, ASCII network routing diagram, YAML compose code blocks, and configuration matrix.'
|
|
},
|
|
{
|
|
key: 'a3',
|
|
file: 'article-tiling-heap.html',
|
|
title: 'Article 3: CTF Heap Exploitation in glibc 2.35',
|
|
type: 'html',
|
|
desc: 'Article reading template featuring left sidebar TOC, collapsible security drawer, safe-linking formula box, GDB stdout terminal blocks, C structs, and mitigation history table.'
|
|
},
|
|
{
|
|
key: 'd',
|
|
file: 'canonical-sample-content.md',
|
|
title: 'Canonical Content Dataset (Single Source of Truth)',
|
|
type: 'md',
|
|
desc: 'Master markdown dataset specifying canonical persona profile, services catalog, blog articles, engineering modules, and telemetry nodes.'
|
|
},
|
|
{
|
|
key: 'u',
|
|
file: 'unified-design-specification.md',
|
|
title: 'Unified Design Specification (Master Brief)',
|
|
type: 'md',
|
|
desc: 'Master specification uniting design language across homelab, technical notes, and portfolio.'
|
|
},
|
|
{
|
|
key: 's',
|
|
file: 'design-specification.md',
|
|
title: 'Design Specification (Primary Technical Brief)',
|
|
type: 'md',
|
|
desc: 'Primary specification document detailing technical web architecture guidelines, typography rules, color tokens, and anti-patterns.'
|
|
},
|
|
{
|
|
key: 'p',
|
|
file: 'portfolio-spec.md',
|
|
title: 'Portfolio Specification (Landing Page Brief)',
|
|
type: 'md',
|
|
desc: 'Specification sheet for the single-page under-construction portfolio card layout.'
|
|
},
|
|
{
|
|
key: 'c',
|
|
file: 'sample3-spec-sheet.md',
|
|
title: 'Sample 3 Specification (Companion Layout Brief)',
|
|
type: 'md',
|
|
desc: 'Specification sheet detailing the split-screen ASCII companion interaction suppression policy.'
|
|
}
|
|
];
|
|
|
|
let currentFile = 'homepage.html';
|
|
let currentMode = 'frame'; // 'frame' or 'grid'
|
|
|
|
function loadSample(fileName, btnElement) {
|
|
currentFile = fileName;
|
|
|
|
// Update active state in top options bar
|
|
document.querySelectorAll('#sampleOptionsBar .sample-btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
if (btn.getAttribute('data-file') === fileName) {
|
|
btn.classList.add('active');
|
|
}
|
|
});
|
|
|
|
// Update active file label and direct open link
|
|
document.getElementById('activeFilePath').innerText = fileName;
|
|
document.getElementById('openTabBtn').href = fileName;
|
|
|
|
const itemInfo = catalogData.find(item => item.file === fileName);
|
|
if (itemInfo) {
|
|
document.getElementById('activeFileDesc').innerText = `[${itemInfo.title}]`;
|
|
}
|
|
|
|
const iframe = document.getElementById('sampleIframe');
|
|
const mdViewer = document.getElementById('markdownViewer');
|
|
|
|
if (fileName.endsWith('.html')) {
|
|
mdViewer.style.display = 'none';
|
|
iframe.style.display = 'block';
|
|
iframe.src = fileName;
|
|
} else {
|
|
// Render Markdown file via simple fetch & format
|
|
iframe.style.display = 'none';
|
|
mdViewer.style.display = 'block';
|
|
renderMarkdownFile(fileName);
|
|
}
|
|
|
|
// If currently in grid mode, switch back to frame preview mode
|
|
if (currentMode === 'grid') {
|
|
toggleViewMode();
|
|
}
|
|
}
|
|
|
|
async function renderMarkdownFile(fileName) {
|
|
const mdContent = document.getElementById('markdownContent');
|
|
mdContent.innerHTML = `<div style="font-family: monospace; color: var(--text-muted);">Fetching and parsing ${fileName}...</div>`;
|
|
|
|
try {
|
|
const response = await fetch(fileName);
|
|
if (!response.ok) throw new Error('File fetch failed');
|
|
const text = await response.text();
|
|
|
|
// Simple high-readability markdown format converter
|
|
let html = text
|
|
.replace(/^### (.*$)/gim, '<h3>$1</h3>')
|
|
.replace(/^## (.*$)/gim, '<h2>$1</h2>')
|
|
.replace(/^# (.*$)/gim, '<h1>$1</h1>')
|
|
.replace(/^\> (.*$)/gim, '<blockquote>$1</blockquote>')
|
|
.replace(/\*\*(.*)\*\*/gim, '<strong>$1</strong>')
|
|
.replace(/\*(.*)\*/gim, '<em>$1</em>')
|
|
.replace(/```([\s\S]*?)```/gim, '<pre><code>$1</code></pre>')
|
|
.replace(/`([^`]+)`/gim, '<code>$1</code>')
|
|
.replace(/\n\n/gim, '<br><br>');
|
|
|
|
mdContent.innerHTML = html;
|
|
} catch (e) {
|
|
mdContent.innerHTML = `<div style="color: var(--accent-amber); font-family: monospace;">Failed to load markdown directly in browser. <a href="${fileName}" target="_blank" style="color: var(--accent-cyan);">Click here to view raw file</a></div>`;
|
|
}
|
|
}
|
|
|
|
function setViewport(width, btnElement) {
|
|
const container = document.getElementById('frameContainer');
|
|
container.style.maxWidth = width;
|
|
|
|
document.querySelectorAll('.frame-controls .ctrl-btn').forEach(btn => {
|
|
if (btn.id && btn.id.startsWith('vp-')) {
|
|
btn.classList.remove('active');
|
|
}
|
|
});
|
|
if (btnElement) btnElement.classList.add('active');
|
|
}
|
|
|
|
function reloadIframe() {
|
|
const iframe = document.getElementById('sampleIframe');
|
|
if (iframe.style.display !== 'none') {
|
|
iframe.src = iframe.src;
|
|
} else {
|
|
renderMarkdownFile(currentFile);
|
|
}
|
|
}
|
|
|
|
function toggleViewMode() {
|
|
const frameWrapper = document.getElementById('frameWrapper');
|
|
const catalogDrawer = document.getElementById('catalogDrawer');
|
|
const modeBtn = document.getElementById('btn-toggle-mode');
|
|
|
|
if (currentMode === 'frame') {
|
|
currentMode = 'grid';
|
|
frameWrapper.style.display = 'none';
|
|
catalogDrawer.style.display = 'block';
|
|
modeBtn.innerText = 'View: Grid Catalog';
|
|
renderDrawerGrid();
|
|
} else {
|
|
currentMode = 'frame';
|
|
frameWrapper.style.display = 'flex';
|
|
catalogDrawer.style.display = 'none';
|
|
modeBtn.innerText = 'View: Frame Preview';
|
|
}
|
|
}
|
|
|
|
function renderDrawerGrid() {
|
|
const grid = document.getElementById('drawerGrid');
|
|
grid.innerHTML = '';
|
|
|
|
catalogData.forEach(item => {
|
|
const card = document.createElement('div');
|
|
card.className = 'card';
|
|
card.onclick = () => loadSample(item.file, null);
|
|
|
|
card.innerHTML = `
|
|
<div>
|
|
<div class="card-title">${item.title}</div>
|
|
<div class="card-desc">${item.desc}</div>
|
|
</div>
|
|
<div class="card-footer">
|
|
<span>${item.file}</span>
|
|
<span style="color: var(--accent-cyan);">[LOAD FRAME ↗]</span>
|
|
</div>
|
|
`;
|
|
grid.appendChild(card);
|
|
});
|
|
}
|
|
|
|
// Keyboard Shortcuts (1-9, 0)
|
|
document.addEventListener('keydown', (e) => {
|
|
// If user is not typing in an input
|
|
if (['input', 'textarea'].includes(document.activeElement.tagName.toLowerCase())) return;
|
|
|
|
const keyMap = {
|
|
'1': 'homepage.html',
|
|
'2': 'split-panel.html',
|
|
'3': 'companion.html',
|
|
'4': 'document-flow.html',
|
|
'5': 'portfolio.html',
|
|
'6': 'homelab.html',
|
|
'7': 'public-services.html',
|
|
'8': 'sample-dashboard.html',
|
|
'9': 'sample-command-center.html',
|
|
'0': 'sample-dual-pane-explorer.html',
|
|
'b': 'blog-lander-stream.html',
|
|
't': 'blog-lander-terminal.html',
|
|
'c': 'blog-lander-cards.html'
|
|
};
|
|
|
|
if (keyMap[e.key]) {
|
|
loadSample(keyMap[e.key], null);
|
|
}
|
|
});
|
|
|
|
// Check URL parameters on initialization
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
const params = new URLSearchParams(window.location.search);
|
|
const requestedSample = params.get('sample') || params.get('view');
|
|
|
|
if (requestedSample) {
|
|
const match = catalogData.find(item => item.file === requestedSample || item.key === requestedSample);
|
|
if (match) {
|
|
loadSample(match.file, null);
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Default load Sample 1
|
|
loadSample('homepage.html', null);
|
|
});
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|