diff --git a/css/post.css b/css/post.css new file mode 100644 index 0000000..f4beb73 --- /dev/null +++ b/css/post.css @@ -0,0 +1,490 @@ +/* =================================================== + JAESWIFT — Individual Post Page Styles + =================================================== */ + +/* ─── Post Header ─── */ +.post-header { + padding: 10rem 2rem 3rem; + position: relative; + border-bottom: 1px solid rgba(0, 255, 200, 0.1); +} + +.post-header-content { + max-width: 1100px; + margin: 0 auto; +} + +.post-back { + display: inline-block; + color: var(--accent, #00ffc8); + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + letter-spacing: 1px; + text-decoration: none; + margin-bottom: 1.5rem; + opacity: 0.6; + transition: opacity 0.3s; +} +.post-back:hover { + opacity: 1; +} + +.post-header-meta { + display: flex; + align-items: center; + gap: 1rem; + margin-bottom: 1rem; + flex-wrap: wrap; +} + +.post-header .post-date { + font-family: 'JetBrains Mono', monospace; + font-size: 0.8rem; + color: rgba(0, 255, 200, 0.6); + letter-spacing: 1px; +} + +.post-header .post-time { + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + color: rgba(200, 214, 229, 0.4); +} + +.post-header-title { + font-family: 'Orbitron', sans-serif; + font-size: clamp(1.4rem, 3vw, 2.2rem); + font-weight: 700; + color: #fff; + margin-bottom: 1rem; + line-height: 1.3; +} + +.post-header-tags { + display: flex; + gap: 0.5rem; + flex-wrap: wrap; +} + +.post-header-tags .post-tag { + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + padding: 0.25rem 0.6rem; + border: 1px solid rgba(0, 255, 200, 0.2); + color: var(--accent, #00ffc8); + letter-spacing: 1px; + text-transform: uppercase; +} + +/* ─── Threat Badges ─── */ +.post-threat { + font-family: 'JetBrains Mono', monospace; + font-size: 0.7rem; + padding: 0.2rem 0.6rem; + letter-spacing: 1px; + font-weight: 600; +} +.threat-low { + color: #00ffc8; + border: 1px solid rgba(0, 255, 200, 0.3); + text-shadow: 0 0 8px rgba(0, 255, 200, 0.3); +} +.threat-medium, .threat-med { + color: #ffa502; + border: 1px solid rgba(255, 165, 2, 0.3); + text-shadow: 0 0 8px rgba(255, 165, 2, 0.3); +} +.threat-high { + color: #ff4757; + border: 1px solid rgba(255, 71, 87, 0.3); + text-shadow: 0 0 8px rgba(255, 71, 87, 0.3); +} +.threat-critical { + color: #ff0040; + border: 1px solid rgba(255, 0, 64, 0.4); + text-shadow: 0 0 12px rgba(255, 0, 64, 0.4); + animation: threatPulse 1.5s ease-in-out infinite; +} +@keyframes threatPulse { + 0%, 100% { opacity: 1; } + 50% { opacity: 0.6; } +} + +/* ─── Post Layout ─── */ +.post-body-section { + padding: 3rem 2rem 5rem; +} + +.post-layout { + max-width: 1100px; + margin: 0 auto; + display: grid; + grid-template-columns: 1fr 300px; + gap: 2.5rem; + align-items: start; +} + +/* ─── Post Content ─── */ +.post-content-main { + min-width: 0; +} + +.post-rendered { + font-family: 'JetBrains Mono', monospace; + font-size: 0.9rem; + line-height: 1.8; + color: rgba(200, 214, 229, 0.85); +} + +.post-rendered h1, +.post-rendered h2, +.post-rendered h3, +.post-rendered h4 { + font-family: 'Orbitron', sans-serif; + color: #fff; + margin: 2rem 0 1rem; + line-height: 1.3; +} +.post-rendered h1 { font-size: 1.6rem; } +.post-rendered h2 { + font-size: 1.2rem; + padding-bottom: 0.5rem; + border-bottom: 1px solid rgba(0, 255, 200, 0.1); +} +.post-rendered h3 { font-size: 1rem; color: var(--accent, #00ffc8); } +.post-rendered h4 { font-size: 0.9rem; } + +.post-rendered p { + margin-bottom: 1.2rem; +} + +.post-rendered strong { + color: #fff; + font-weight: 600; +} + +.post-rendered em { + color: rgba(0, 255, 200, 0.7); + font-style: italic; +} + +.post-rendered a { + color: var(--accent, #00ffc8); + text-decoration: none; + border-bottom: 1px solid rgba(0, 255, 200, 0.3); + transition: border-color 0.3s; +} +.post-rendered a:hover { + border-color: var(--accent, #00ffc8); +} + +.post-rendered ul { + list-style: none; + padding-left: 0; + margin: 1rem 0; +} +.post-rendered ul li { + position: relative; + padding-left: 1.5rem; + margin-bottom: 0.5rem; +} +.post-rendered ul li::before { + content: '▸'; + position: absolute; + left: 0; + color: var(--accent, #00ffc8); + font-size: 0.8rem; +} + +.post-rendered hr { + border: none; + height: 1px; + background: linear-gradient(90deg, transparent, rgba(0, 255, 200, 0.2), transparent); + margin: 2rem 0; +} + +/* ─── Code Blocks ─── */ +.post-rendered pre { + background: rgba(0, 0, 0, 0.5); + border: 1px solid rgba(0, 255, 200, 0.1); + border-left: 3px solid var(--accent, #00ffc8); + border-radius: 0; + padding: 1.2rem 1.5rem; + margin: 1.5rem 0; + overflow-x: auto; + position: relative; +} + +.post-rendered pre::before { + content: attr(data-lang) 'TERMINAL'; + position: absolute; + top: 0; + right: 0; + font-family: 'JetBrains Mono', monospace; + font-size: 0.6rem; + padding: 0.2rem 0.6rem; + background: rgba(0, 255, 200, 0.08); + color: rgba(0, 255, 200, 0.4); + letter-spacing: 1px; +} + +.post-rendered pre code { + font-family: 'Share Tech Mono', 'JetBrains Mono', monospace; + font-size: 0.82rem; + color: var(--accent, #00ffc8); + line-height: 1.6; + background: none; + padding: 0; +} + +.post-rendered code.inline-code { + font-family: 'Share Tech Mono', monospace; + font-size: 0.82rem; + background: rgba(0, 255, 200, 0.06); + border: 1px solid rgba(0, 255, 200, 0.1); + padding: 0.15rem 0.4rem; + color: var(--accent, #00ffc8); +} + +/* ─── Sidebar ─── */ +.post-sidebar { + display: flex; + flex-direction: column; + gap: 1.5rem; + position: sticky; + top: 6rem; +} + +.post-stats-panel, +.post-meta-panel, +.post-nav-panel { + background: rgba(0, 255, 200, 0.02); + border: 1px solid rgba(0, 255, 200, 0.08); +} + +/* ─── Stat Rows (sidebar) ─── */ +.stat-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.4rem 0; +} + +.stat-label { + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + color: rgba(200, 214, 229, 0.5); + letter-spacing: 1px; + min-width: 80px; +} + +.stat-pips { + display: flex; + gap: 4px; +} + +.pip { + width: 14px; + height: 8px; + background: rgba(200, 214, 229, 0.08); + border: 1px solid rgba(200, 214, 229, 0.1); + transition: all 0.3s; +} +.pip.filled { + background: var(--accent, #00ffc8); + border-color: var(--accent, #00ffc8); + box-shadow: 0 0 6px rgba(0, 255, 200, 0.3); +} +.pip.filled.warn { + background: #ffa502; + border-color: #ffa502; + box-shadow: 0 0 6px rgba(255, 165, 2, 0.3); +} +.pip.filled.danger { + background: #ff4757; + border-color: #ff4757; + box-shadow: 0 0 6px rgba(255, 71, 87, 0.3); +} + +.stat-divider { + height: 1px; + background: rgba(0, 255, 200, 0.08); + margin: 0.5rem 0; +} + +/* ─── BPM Display ─── */ +.stat-big-row { + text-align: center; + padding: 0.5rem 0; +} + +.stat-bpm-display { + position: relative; +} + +.stat-bpm-value { + font-family: 'Orbitron', sans-serif; + font-size: 2rem; + font-weight: 700; + color: #ff4757; + text-shadow: 0 0 15px rgba(255, 71, 87, 0.4); + transition: transform 0.1s; +} +.stat-bpm-value.pulse { + transform: scale(1.15); +} + +.stat-bpm-unit { + font-family: 'JetBrains Mono', monospace; + font-size: 0.65rem; + color: rgba(255, 71, 87, 0.5); + letter-spacing: 2px; + display: block; + margin-top: -0.2rem; +} + +/* ─── Coffee Row ─── */ +.stat-coffee-row { + display: flex; + align-items: center; + justify-content: space-between; + padding: 0.4rem 0; +} + +.stat-coffee { + font-size: 0.9rem; +} + +/* ─── Metadata Panel ─── */ +.meta-row { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.4rem 0; + border-bottom: 1px solid rgba(0, 255, 200, 0.04); +} +.meta-row:last-child { + border-bottom: none; +} + +.meta-key { + font-family: 'JetBrains Mono', monospace; + font-size: 0.6rem; + color: rgba(200, 214, 229, 0.4); + letter-spacing: 1px; +} + +.meta-val { + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + color: rgba(200, 214, 229, 0.8); + text-align: right; +} +.meta-val.mono { + font-size: 0.65rem; + color: rgba(0, 255, 200, 0.5); +} + +/* ─── Navigation Panel ─── */ +.post-nav-link { + display: block; + padding: 0.8rem 0; + border-bottom: 1px solid rgba(0, 255, 200, 0.06); + text-decoration: none; + transition: all 0.3s; +} +.post-nav-link:last-child { + border-bottom: none; +} +.post-nav-link:hover { + background: rgba(0, 255, 200, 0.03); + padding-left: 0.5rem; +} + +.nav-dir { + font-family: 'JetBrains Mono', monospace; + font-size: 0.6rem; + color: var(--accent, #00ffc8); + letter-spacing: 1px; + display: block; + margin-bottom: 0.2rem; +} + +.nav-post-title { + font-family: 'JetBrains Mono', monospace; + font-size: 0.75rem; + color: rgba(200, 214, 229, 0.7); + display: block; + line-height: 1.4; +} + +.nav-all .nav-dir { + text-align: center; + margin-top: 0.3rem; +} + +/* ─── Loading & Error ─── */ +.post-loading { + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; + padding: 4rem 0; + color: rgba(0, 255, 200, 0.5); + font-family: 'JetBrains Mono', monospace; + font-size: 0.8rem; + letter-spacing: 2px; +} + +.post-error { + text-align: center; + padding: 4rem 2rem; + font-family: 'Orbitron', sans-serif; + font-size: 1.1rem; + color: #ff4757; + text-shadow: 0 0 20px rgba(255, 71, 87, 0.3); +} + +/* ─── Responsive ─── */ +@media (max-width: 900px) { + .post-layout { + grid-template-columns: 1fr; + } + + .post-sidebar { + position: static; + display: grid; + grid-template-columns: 1fr 1fr; + gap: 1rem; + } + + .post-nav-panel { + grid-column: 1 / -1; + } +} + +@media (max-width: 600px) { + .post-header { + padding: 8rem 1rem 2rem; + } + + .post-body-section { + padding: 2rem 1rem 3rem; + } + + .post-header-title { + font-size: 1.2rem; + } + + .post-sidebar { + grid-template-columns: 1fr; + } + + .post-rendered { + font-size: 0.82rem; + } + + .post-rendered pre { + padding: 0.8rem 1rem; + font-size: 0.75rem; + } +} diff --git a/js/post.js b/js/post.js new file mode 100644 index 0000000..046cc39 --- /dev/null +++ b/js/post.js @@ -0,0 +1,325 @@ +/* =================================================== + JAESWIFT BLOG — Individual Post Loader + Markdown rendering, operator stats, navigation + =================================================== */ +(function () { + 'use strict'; + + const API = window.location.hostname === 'localhost' + ? 'http://localhost:5000' + : '/api'; + + // ─── Simple Markdown Parser ─── + function parseMarkdown(md) { + let html = md; + + // Code blocks (```lang ... ```) + html = html.replace(/```(\w+)?\n([\s\S]*?)```/g, (_, lang, code) => { + const cls = lang ? ` class="language-${lang}"` : ''; + return `
${escapeHtml(code.trim())}`;
+ });
+
+ // Inline code
+ html = html.replace(/`([^`]+)`/g, '$1');
+
+ // Headers
+ html = html.replace(/^#### (.+)$/gm, ''); + html = '
' + html + '
'; + + // Clean up empty paragraphs and fix nesting + html = html.replace(/<(h[1-4]|pre|ul|hr|blockquote)/g, '<$1'); + html = html.replace(/<\/(h[1-4]|pre|ul|hr|blockquote)><\/p>/g, '$1>'); + html = html.replace(/
<\/p>/g, ''); + html = html.replace(/
\s*<\/p>/g, ''); + + return html; + } + + function escapeHtml(str) { + return str.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"'); + } + + // ─── Stat Pips ─── + function buildPips(val, max) { + max = max || 5; + let html = '