feat: changelog page with timeline UI, API endpoint, auto-loaded data
This commit is contained in:
parent
a7b9f3225a
commit
ec50baa369
5 changed files with 516 additions and 0 deletions
11
api/app.py
11
api/app.py
|
|
@ -896,5 +896,16 @@ def contraband_search():
|
|||
|
||||
|
||||
# ─── 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__':
|
||||
app.run(host='0.0.0.0', port=5000, debug=False)
|
||||
|
|
|
|||
134
api/data/changelog.json
Normal file
134
api/data/changelog.json
Normal 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
229
css/changelog.css
Normal 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
56
hq/changelog.html
Normal 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">> 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
86
js/changelog.js
Normal 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);
|
||||
});
|
||||
})();
|
||||
Loading…
Add table
Reference in a new issue