feat: changelog page with timeline UI, API endpoint, auto-loaded data

This commit is contained in:
jae 2026-04-03 00:29:44 +00:00
parent a7b9f3225a
commit ec50baa369
5 changed files with 516 additions and 0 deletions

View file

@ -896,5 +896,16 @@ def contraband_search():
# ─── Run ───────────────────────────────────────────── # ─── Run ─────────────────────────────────────────────
# ─── Changelog ────────────────────────────────────────
@app.route('/api/changelog')
def get_changelog():
try:
with open(os.path.join(DATA_DIR, 'changelog.json'), 'r') as f:
return json.load(f)
except Exception as e:
return {"error": str(e)}, 500
if __name__ == '__main__': if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=False) app.run(host='0.0.0.0', port=5000, debug=False)

134
api/data/changelog.json Normal file
View file

@ -0,0 +1,134 @@
{
"site": "jaeswift.xyz",
"entries": [
{
"version": "1.8.0",
"date": "2026-04-03",
"title": "CONTRABAND Auto-Sync & Grid Overhaul",
"category": "feature",
"changes": [
"Subcategories now display as 2-column card grid with expandable detail panels",
"Added weekly auto-sync — resource database updates every Sunday at 03:00",
"Click any subcategory card to expand/collapse its entries below",
"Active card highlighting with amber glow",
"Responsive grid: 2-col desktop, 1-col mobile"
]
},
{
"version": "1.7.0",
"date": "2026-04-03",
"title": "Sitewide Visual Overhaul",
"category": "fix",
"changes": [
"Bumped 64 font sizes sitewide — no more microscopic text",
"Brightened all text colours: primary #c0c0c0→#d8d8d8, secondary #707070→#999999, muted #3a3a3a→#666666",
"CONTRABAND page: 4-column category grid with responsive breakpoints",
"Purged all third-party attribution references from entire codebase"
]
},
{
"version": "1.6.0",
"date": "2026-04-03",
"title": "CONTRABAND — Classified Resource Index",
"category": "feature",
"changes": [
"Launched CONTRABAND page at /depot/contraband with 15,800+ indexed assets",
"24 categories with military codenames (CRT-001 through CRT-024)",
"Full-text search across all entries via API",
"Starred/top-pick filter system with ⭐ indicators",
"Collapsible subcategories with item counts",
"Flask API endpoints: /api/contraband, /api/contraband/<slug>, /api/contraband/search"
]
},
{
"version": "1.5.1",
"date": "2026-04-03",
"title": "Navbar Dropdown Fix",
"category": "fix",
"changes": [
"Fixed dropdown menus disappearing on all subpages",
"Root cause: 25 subpages used class 'navbar' instead of 'nav-main'",
"All pages now use correct nav class with proper positioning and z-index"
]
},
{
"version": "1.5.0",
"date": "2026-04-02",
"title": "Globe & Chat AI Admin Panels",
"category": "feature",
"changes": [
"Admin panel: Globe management section — server location, rotation speed, arc cities, colours",
"Admin panel: Chat AI configuration — model selection, system prompt, greeting toggle",
"New API endpoints: /api/globe, /api/chat-config with auth-protected GET/POST",
"Interactive colour picker and slider controls for globe parameters",
"Arc cities table with add/remove functionality"
]
},
{
"version": "1.4.0",
"date": "2026-04-01",
"title": "Interactive 3D Globe",
"category": "feature",
"changes": [
"Added interactive 3D globe to homepage using globe.gl",
"Animated arcs connecting server location to cities worldwide",
"Hex polygon layer with customisable opacity and colour",
"Atmosphere glow effect with configurable altitude",
"Auto-rotation with adjustable speed"
]
},
{
"version": "1.3.0",
"date": "2026-03-31",
"title": "Blog & Transmissions System",
"category": "feature",
"changes": [
"Built blog system with markdown-to-HTML rendering",
"Blog index page with post cards, dates, and categories",
"Individual post pages with full content rendering",
"Transmissions section: SITREP, RADAR, DISPATCHES pages",
"Admin panel: blog post management with create/edit/delete"
]
},
{
"version": "1.2.0",
"date": "2026-03-30",
"title": "Admin Panel & Authentication",
"category": "feature",
"changes": [
"Built admin panel at /admin with session-based authentication",
"Dashboard with real-time server stats and process monitoring",
"Navigation management: add, edit, reorder, delete menu items",
"Blog post editor with live preview",
"Password-protected API endpoints with cookie auth"
]
},
{
"version": "1.1.0",
"date": "2026-03-29",
"title": "HQ & Section Pages",
"category": "feature",
"changes": [
"Created full site navigation structure with dropdown menus",
"HQ section: Telemetry, Logs, Profile, Briefing pages",
"DEPOT section: index, Propaganda, Recon, Exfil pages",
"ARMOURY section: Lab, Field Manuals, Deployments, Debrief pages",
"COMMS section: Open Channels, Encrypted Line, Backup Relay pages"
]
},
{
"version": "1.0.0",
"date": "2026-03-28",
"title": "Initial Launch",
"category": "release",
"changes": [
"Launched jaeswift.xyz with sci-fi military dashboard theme",
"Homepage with system stats, process monitor, clock, and typing effect",
"Dark theme with scanline overlay and monospace typography",
"Flask API backend for real-time server data",
"Responsive design across all screen sizes",
"Gitea repository setup at git.jaeswift.xyz"
]
}
]
}

229
css/changelog.css Normal file
View file

@ -0,0 +1,229 @@
/*
CHANGELOG Mission Update Log
*/
.changelog-container {
max-width: 900px;
margin: 0 auto;
padding: 2rem 1.5rem;
}
.changelog-header {
text-align: center;
margin-bottom: 2.5rem;
padding-bottom: 1.5rem;
border-bottom: 1px solid var(--border);
}
.changelog-title {
font-family: var(--font-display);
font-size: 1.8rem;
color: var(--text-primary);
letter-spacing: 3px;
margin-bottom: 0.5rem;
}
.changelog-subtitle {
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--text-secondary);
letter-spacing: 1px;
}
.changelog-stats {
display: flex;
justify-content: center;
gap: 2rem;
margin-top: 1rem;
}
.changelog-stat {
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--text-muted);
letter-spacing: 1px;
}
.changelog-stat-value {
color: var(--status-green);
font-weight: 600;
}
/* ─── Timeline ───────────────────────────────────────── */
.changelog-timeline {
position: relative;
padding-left: 2rem;
}
.changelog-timeline::before {
content: '';
position: absolute;
left: 0;
top: 0;
bottom: 0;
width: 2px;
background: linear-gradient(to bottom, var(--status-green), var(--border), transparent);
}
/* ─── Entry ──────────────────────────────────────────── */
.changelog-entry {
position: relative;
margin-bottom: 2rem;
transition: all 0.3s ease;
}
.changelog-entry::before {
content: '';
position: absolute;
left: -2rem;
top: 0.9rem;
width: 10px;
height: 10px;
border-radius: 50%;
background: var(--bg-primary);
border: 2px solid var(--status-green);
z-index: 1;
transition: all 0.3s ease;
}
.changelog-entry:hover::before {
background: var(--status-green);
box-shadow: 0 0 8px var(--status-green-glow);
}
.changelog-entry-card {
background: var(--bg-panel);
border: 1px solid var(--border);
padding: 1.2rem 1.5rem;
transition: all 0.3s ease;
}
.changelog-entry:hover .changelog-entry-card {
border-color: var(--status-green-dim);
background: var(--bg-panel-hover);
}
/* ─── Entry Header ───────────────────────────────────── */
.changelog-entry-header {
display: flex;
align-items: center;
gap: 1rem;
margin-bottom: 0.75rem;
flex-wrap: wrap;
}
.changelog-version {
font-family: var(--font-display);
font-size: 1rem;
color: var(--status-green);
letter-spacing: 1px;
font-weight: 700;
}
.changelog-badge {
font-family: var(--font-mono);
font-size: 0.7rem;
padding: 0.15rem 0.5rem;
letter-spacing: 1px;
text-transform: uppercase;
border: 1px solid;
}
.changelog-badge.feature {
color: var(--status-green);
border-color: var(--status-green-dim);
background: rgba(0, 204, 51, 0.08);
}
.changelog-badge.fix {
color: var(--warning);
border-color: var(--mil-red-dim);
background: rgba(201, 162, 39, 0.08);
}
.changelog-badge.release {
color: var(--accent);
border-color: var(--accent-dim);
background: rgba(208, 208, 208, 0.08);
}
.changelog-badge.security {
color: var(--danger);
border-color: rgba(255, 45, 45, 0.3);
background: rgba(255, 45, 45, 0.08);
}
.changelog-date {
font-family: var(--font-mono);
font-size: 0.8rem;
color: var(--text-muted);
letter-spacing: 1px;
margin-left: auto;
}
.changelog-entry-title {
font-family: var(--font-display);
font-size: 1.1rem;
color: var(--text-primary);
letter-spacing: 0.5px;
margin-bottom: 0.75rem;
}
/* ─── Change List ────────────────────────────────────── */
.changelog-changes {
list-style: none;
padding: 0;
margin: 0;
}
.changelog-changes li {
position: relative;
padding: 0.35rem 0 0.35rem 1.5rem;
font-family: var(--font-mono);
font-size: 0.85rem;
color: var(--text-secondary);
line-height: 1.5;
}
.changelog-changes li::before {
content: '▸';
position: absolute;
left: 0;
color: var(--status-green);
font-size: 0.9rem;
}
/* ─── Loading ────────────────────────────────────────── */
.changelog-loading {
text-align: center;
padding: 4rem 0;
font-family: var(--font-mono);
font-size: 0.9rem;
color: var(--text-muted);
letter-spacing: 2px;
}
/* ─── Responsive ─────────────────────────────────────── */
@media (max-width: 768px) {
.changelog-container {
padding: 1.5rem 1rem;
}
.changelog-timeline {
padding-left: 1.5rem;
}
.changelog-entry::before {
left: -1.5rem;
}
.changelog-entry-header {
flex-direction: column;
align-items: flex-start;
gap: 0.5rem;
}
.changelog-date {
margin-left: 0;
}
.changelog-stats {
flex-direction: column;
gap: 0.5rem;
}
}

56
hq/changelog.html Normal file
View file

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>JAESWIFT // CHANGELOG</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link href="https://fonts.googleapis.com/css2?family=Orbitron:wght@400;700;900&family=JetBrains+Mono:wght@300;400;500;700&display=swap" rel="stylesheet">
<link rel="stylesheet" href="/css/style.css">
<link rel="stylesheet" href="/css/section.css">
<link rel="stylesheet" href="/css/changelog.css">
</head>
<body>
<div class="scanline-overlay"></div>
<div class="grid-bg"></div>
<nav class="nav-main" id="navbar">
<div class="nav-container">
<a href="/" class="nav-logo">
<span class="logo-bracket">[</span> JAE <span class="logo-bracket">]</span>
</a>
<button class="nav-toggle" id="navToggle" aria-label="Menu">
<span></span><span></span><span></span>
</button>
<ul class="nav-menu" id="navMenu"></ul>
<div class="nav-status">
<span class="nav-clock" id="navClock"></span>
</div>
</div>
</nav>
<div class="breadcrumb">
<a href="/">HOME</a>
<span class="separator">/</span>
<a href="/hq">HQ</a>
<span class="separator">/</span>
<span class="current">CHANGELOG</span>
</div>
<section class="section-header" style="padding-top: calc(var(--nav-height) + 1.5rem);">
<div class="section-header-label">HQ // HEADQUARTERS</div>
<h1 class="section-header-title">CHANGELOG</h1>
<p class="section-header-sub">&gt; Complete mission update log. Every deployment, fix, and feature recorded.</p>
</section>
<section class="subpage-content">
<div class="changelog-container" id="changelogRoot">
<div class="changelog-loading">LOADING UPDATE LOG...</div>
</div>
</section>
<script src="/js/nav.js"></script>
<script src="/js/clock.js"></script>
<script src="/js/changelog.js"></script>
</body>
</html>

86
js/changelog.js Normal file
View file

@ -0,0 +1,86 @@
/*
CHANGELOG Mission Update Log Controller
*/
(function () {
'use strict';
const root = document.getElementById('changelogRoot');
if (!root) return;
function esc(s) {
const d = document.createElement('div');
d.textContent = s || '';
return d.innerHTML;
}
function formatDate(dateStr) {
const d = new Date(dateStr + 'T00:00:00');
const months = ['JAN', 'FEB', 'MAR', 'APR', 'MAY', 'JUN', 'JUL', 'AUG', 'SEP', 'OCT', 'NOV', 'DEC'];
return `${d.getDate().toString().padStart(2, '0')} ${months[d.getMonth()]} ${d.getFullYear()}`;
}
function render(data) {
const entries = data.entries || [];
const totalChanges = entries.reduce((sum, e) => sum + (e.changes ? e.changes.length : 0), 0);
const latestVersion = entries.length > 0 ? entries[0].version : '0.0.0';
let html = '';
// Header
html += `<div class="changelog-header">`;
html += `<div class="changelog-title">MISSION UPDATE LOG</div>`;
html += `<div class="changelog-subtitle">// SYSTEM CHANGELOG — ALL DEPLOYMENTS</div>`;
html += `<div class="changelog-stats">`;
html += `<span class="changelog-stat">CURRENT BUILD: <span class="changelog-stat-value">v${esc(latestVersion)}</span></span>`;
html += `<span class="changelog-stat">UPDATES: <span class="changelog-stat-value">${entries.length}</span></span>`;
html += `<span class="changelog-stat">TOTAL CHANGES: <span class="changelog-stat-value">${totalChanges}</span></span>`;
html += `</div></div>`;
// Timeline
html += `<div class="changelog-timeline">`;
for (const entry of entries) {
const badge = entry.category || 'update';
html += `<div class="changelog-entry">`;
html += `<div class="changelog-entry-card">`;
// Header row
html += `<div class="changelog-entry-header">`;
html += `<span class="changelog-version">v${esc(entry.version)}</span>`;
html += `<span class="changelog-badge ${esc(badge)}">${esc(badge)}</span>`;
html += `<span class="changelog-date">${formatDate(entry.date)}</span>`;
html += `</div>`;
// Title
html += `<div class="changelog-entry-title">${esc(entry.title)}</div>`;
// Changes
if (entry.changes && entry.changes.length > 0) {
html += `<ul class="changelog-changes">`;
for (const change of entry.changes) {
html += `<li>${esc(change)}</li>`;
}
html += `</ul>`;
}
html += `</div></div>`;
}
html += `</div>`;
root.innerHTML = html;
}
// Load data
root.innerHTML = '<div class="changelog-loading">LOADING UPDATE LOG...</div>';
fetch('/api/changelog')
.then(res => {
if (!res.ok) throw new Error('API ' + res.status);
return res.json();
})
.then(data => render(data))
.catch(err => {
root.innerHTML = '<div class="changelog-loading">⚠ FAILED TO LOAD UPDATE LOG</div>';
console.error('Changelog load error:', err);
});
})();