Files
blog/samples/index.html
T

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>