diff --git a/index.html b/index.html
index fdd8fa1..a9470e8 100644
--- a/index.html
+++ b/index.html
@@ -5,6 +5,7 @@
+
+
About
pyserve is a Python application orchestrator and HTTP server.
@@ -102,7 +104,7 @@
Version
-
Current version: 0.9.10
+
Current version: 0.9.10
Requirements
+
+
+
diff --git a/scripts/version-fetcher.js b/scripts/version-fetcher.js
new file mode 100644
index 0000000..a1b263f
--- /dev/null
+++ b/scripts/version-fetcher.js
@@ -0,0 +1,149 @@
+/**
+ * Version Fetcher - Automatically fetches and displays latest pyserveX version
+ * from Gitea releases page
+ */
+
+(function() {
+ 'use strict';
+ const RELEASES_URL = 'https://git.pyserve.org/Shifty/pyserveX/releases/latest';
+ const VERSION_ELEMENT_ID = 'current-version';
+ const CACHE_KEY = 'pyserve_version_cache';
+ const CACHE_DURATION = 3600000; // 1 hour
+ const FALLBACK_VERSION = 'offline';
+
+ async function fetchLatestVersion() {
+ try {
+ const response = await fetch(RELEASES_URL, {
+ method: 'GET',
+ mode: 'cors',
+ headers: {
+ 'Accept': 'text/html'
+ }
+ });
+
+ if (!response.ok) {
+ console.warn(`Failed to fetch: ${response.status}`);
+ return null;
+ }
+
+ const html = await response.text();
+
+ const patterns = [
+ /
PyServeX\s+v([\d.]+)/i,
+ /releases\/tag\/v([\d.]+)/,
+ /aria-label="PyServeX\s+v([\d.]+)/i,
+ /]*>.*?v([\d.]+).*?<\/h4>/i
+ ];
+
+ for (const pattern of patterns) {
+ const match = html.match(pattern);
+ if (match && match[1]) {
+ console.log(`✓ Version found: ${match[1]}`);
+ return match[1];
+ }
+ }
+
+ console.warn('Version not found in HTML');
+ return null;
+
+ } catch (error) {
+ console.warn('Fetch error:', error.message);
+ return null;
+ }
+ }
+
+ function getCachedVersion() {
+ try {
+ const cached = localStorage.getItem(CACHE_KEY);
+ if (!cached) return null;
+
+ const data = JSON.parse(cached);
+ const now = Date.now();
+
+ if (now - data.timestamp < CACHE_DURATION) {
+ return data.version;
+ }
+
+ localStorage.removeItem(CACHE_KEY);
+ return null;
+ } catch (error) {
+ console.error('Cache read error:', error);
+ return null;
+ }
+ }
+
+ function cacheVersion(version) {
+ try {
+ const data = {
+ version: version,
+ timestamp: Date.now()
+ };
+ localStorage.setItem(CACHE_KEY, JSON.stringify(data));
+ } catch (error) {
+ console.error('Cache write error:', error);
+ }
+ }
+
+ function updateVersionDisplay(version) {
+ const element = document.getElementById(VERSION_ELEMENT_ID);
+ if (!element) {
+ console.warn('Version element not found');
+ return;
+ }
+
+ const versionLink = document.createElement('a');
+ versionLink.href = 'https://git.pyserve.org/Shifty/pyserveX/releases/tag/v' + version;
+ versionLink.textContent = version;
+ versionLink.target = '_blank';
+ versionLink.rel = 'noopener noreferrer';
+ versionLink.className = 'version-link';
+
+ element.innerHTML = '';
+ element.appendChild(versionLink);
+
+ const badge = document.createElement('span');
+ badge.textContent = 'latest';
+ badge.className = 'version-badge';
+ badge.title = 'Fetched from Git repository';
+ element.appendChild(document.createTextNode(' '));
+ element.appendChild(badge);
+
+ element.style.opacity = '1';
+ }
+ function showLoadingState() {
+ const element = document.getElementById(VERSION_ELEMENT_ID);
+ if (element) {
+ element.style.opacity = '0.6';
+ }
+ }
+
+ async function init() {
+ showLoadingState();
+
+ const cachedVersion = getCachedVersion();
+ if (cachedVersion) {
+ updateVersionDisplay(cachedVersion);
+ }
+
+ const latestVersion = await fetchLatestVersion();
+ if (latestVersion) {
+ cacheVersion(latestVersion);
+ updateVersionDisplay(latestVersion);
+ } else if (!cachedVersion) {
+ const element = document.getElementById(VERSION_ELEMENT_ID);
+ if (element) {
+ element.textContent = FALLBACK_VERSION;
+ element.style.opacity = '1';
+ element.style.color = '#c9c9c9';
+ element.title = 'Version fetch failed (CORS or network issue)';
+ }
+ }
+ }
+
+ if (document.readyState === 'loading') {
+ document.addEventListener('DOMContentLoaded', init);
+ } else {
+ init();
+ }
+
+})();
diff --git a/style.css b/style.css
index 7e8fdd9..402e70a 100644
--- a/style.css
+++ b/style.css
@@ -13,7 +13,7 @@ body {
}
#container {
- max-width: 800px;
+ max-width: 1200px;
margin: 0 auto;
padding: 20px;
}
@@ -38,7 +38,7 @@ body {
}
#content {
- margin-bottom: 30px;
+ margin-bottom: 0;
}
h2 {
@@ -235,3 +235,208 @@ dd {
color: #666;
font-style: italic;
}
+
+/* Main wrapper for two-column layout */
+#main-wrapper {
+ display: flex;
+ gap: 30px;
+ align-items: flex-start;
+}
+
+#content {
+ flex: 1;
+ min-width: 0;
+}
+
+/* Sidebar */
+#sidebar {
+ width: 300px;
+ flex-shrink: 0;
+}
+
+.partner-block {
+ position: sticky;
+ top: 20px;
+ background: #0d0d0d;
+ border: 1px solid #333;
+ border-radius: 8px;
+ padding: 20px;
+}
+
+.partner-block h3 {
+ color: #3cb371;
+ font-size: 16px;
+ margin: 0 0 15px 0;
+ padding-bottom: 10px;
+ border-bottom: 2px solid #2e8b57;
+}
+
+.partner-card {
+ background: #1a1a1a;
+ border: 1px solid #2a2a2a;
+ border-radius: 6px;
+ padding: 15px;
+ margin-bottom: 15px;
+ transition: all 0.3s ease;
+}
+
+.partner-card:hover {
+ border-color: #3cb371;
+ transform: translateY(-2px);
+ box-shadow: 0 4px 12px rgba(60, 179, 113, 0.1);
+}
+
+.partner-icon {
+ font-size: 32px;
+ margin-bottom: 10px;
+}
+
+.partner-card h4 {
+ color: #e0e0e0;
+ font-size: 14px;
+ font-weight: bold;
+ margin: 0 0 8px 0;
+}
+
+.partner-card p {
+ color: #999;
+ font-size: 12px;
+ line-height: 1.6;
+ margin: 0 0 10px 0;
+}
+
+.partner-link {
+ display: inline-block;
+ color: #5fba7d;
+ font-size: 12px;
+ font-weight: bold;
+ text-decoration: none;
+ transition: color 0.2s ease;
+}
+
+.partner-link:hover {
+ color: #7ccd9a;
+}
+
+.community-block {
+ background: #1a2a1a;
+ border: 1px solid #2e4a2e;
+ border-radius: 6px;
+ padding: 15px;
+ margin-top: 15px;
+}
+
+.community-block h4 {
+ color: #3cb371;
+ font-size: 14px;
+ font-weight: bold;
+ margin: 0 0 10px 0;
+}
+
+.community-links {
+ list-style: none;
+ margin: 0;
+ padding: 0;
+}
+
+.community-links li {
+ padding: 5px 0;
+}
+
+.community-links li:before {
+ content: "► ";
+ color: #2e8b57;
+ font-size: 10px;
+ margin-right: 5px;
+}
+
+.community-links a {
+ color: #5fba7d;
+ font-size: 12px;
+ text-decoration: none;
+}
+
+.community-links a:hover {
+ color: #7ccd9a;
+ text-decoration: underline;
+}
+
+/* Responsive design */
+@media (max-width: 1024px) {
+ #container {
+ max-width: 900px;
+ }
+
+ #sidebar {
+ width: 250px;
+ }
+}
+
+@media (max-width: 768px) {
+ #main-wrapper {
+ flex-direction: column;
+ }
+
+ #sidebar {
+ width: 100%;
+ margin-top: 30px;
+ }
+
+ .partner-block {
+ position: static;
+ }
+
+ /* On mobile, show partner cards in a more compact grid */
+ .partner-card {
+ margin-bottom: 12px;
+ }
+}
+
+@media (max-width: 480px) {
+ #container {
+ padding: 15px;
+ }
+
+ .partner-block {
+ padding: 15px;
+ }
+
+ .partner-card {
+ padding: 12px;
+ }
+
+ .partner-icon {
+ font-size: 28px;
+ }
+}
+
+#current-version {
+ display: inline-flex;
+ align-items: center;
+ gap: 8px;
+}
+
+.version-link {
+ color: #5fba7d;
+ text-decoration: none;
+ font-weight: bold;
+ transition: color 0.2s ease;
+}
+
+.version-link:hover {
+ color: #7ccd9a;
+ text-decoration: underline;
+}
+
+.version-badge {
+ display: inline-block;
+ background: linear-gradient(135deg, #2e8b57 0%, #3cb371 100%);
+ color: #fff;
+ font-size: 10px;
+ font-weight: bold;
+ text-transform: uppercase;
+ padding: 2px 6px;
+ border-radius: 3px;
+ letter-spacing: 0.5px;
+ box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
+}