diff --git a/admin.html b/admin.html
index 538da88..52dbff7 100644
--- a/admin.html
+++ b/admin.html
@@ -84,6 +84,13 @@
+
+
+
@@ -1036,6 +1043,144 @@
+
+
+
+
+
🌍 GLOBE CONFIGURATION
+
+
+
◈ SERVER LOCATION
+
+
+
+
⚙ GLOBE SETTINGS
+
+
+
+
+
+
+
+
+
+
+
⟐ ARC CITIES
+
+
+
+
+
+
+
+ | CITY |
+ LAT |
+ LNG |
+ ACTIONS |
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
🤖 CHAT AI CONFIGURATION
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/api/app.py b/api/app.py
index 4c34b43..4b55731 100644
--- a/api/app.py
+++ b/api/app.py
@@ -737,6 +737,36 @@ def venice_chat():
except Exception as e:
return jsonify({'error': str(e)}), 500
+# ─── Globe Config ────────────────────────────────────
+@app.route('/api/globe')
+def get_globe():
+ return jsonify(load_json('globe.json'))
+
+@app.route('/api/globe', methods=['POST'])
+@require_auth
+def save_globe():
+ try:
+ d = request.get_json(force=True)
+ save_json('globe.json', d)
+ return jsonify(d)
+ except Exception as e:
+ return jsonify({'error': str(e)}), 500
+
+# ─── Chat AI Config ──────────────────────────────────
+@app.route('/api/chat-config')
+def get_chat_config():
+ return jsonify(load_json('chat_config.json'))
+
+@app.route('/api/chat-config', methods=['POST'])
+@require_auth
+def save_chat_config():
+ try:
+ d = request.get_json(force=True)
+ save_json('chat_config.json', d)
+ return jsonify(d)
+ except Exception as e:
+ return jsonify({'error': str(e)}), 500
+
# ─── Backups ─────────────────────────────────────────
@app.route('/api/backups/posts')
@require_auth
diff --git a/api/data/chat_config.json b/api/data/chat_config.json
new file mode 100644
index 0000000..41a1c0f
--- /dev/null
+++ b/api/data/chat_config.json
@@ -0,0 +1,8 @@
+{
+ "display_name": "JAE-AI",
+ "model": "venice-uncensored-1-2",
+ "system_prompt_summary": "AI assistant for jaeswift.xyz, knows all site sections and services",
+ "max_history": 20,
+ "auto_greeting": true,
+ "header_tag": "VENICE-UNCENSORED"
+}
diff --git a/api/data/globe.json b/api/data/globe.json
new file mode 100644
index 0000000..bd78fe2
--- /dev/null
+++ b/api/data/globe.json
@@ -0,0 +1,20 @@
+{
+ "server_lat": 53.4808,
+ "server_lng": -2.2426,
+ "server_label": "MANCHESTER",
+ "rotation_speed": 0.3,
+ "hex_polygon_opacity": 0.55,
+ "hex_polygon_color": "rgba(0, 220, 50, 0.55)",
+ "atmosphere_color": "#00cc33",
+ "atmosphere_altitude": 0.2,
+ "arc_cities": [
+ {"name": "New York", "lat": 40.7128, "lng": -74.006},
+ {"name": "Tokyo", "lat": 35.6762, "lng": 139.6503},
+ {"name": "Paris", "lat": 48.8566, "lng": 2.3522},
+ {"name": "Sydney", "lat": -33.8688, "lng": 151.2093},
+ {"name": "Berlin", "lat": 52.52, "lng": 13.405},
+ {"name": "Mumbai", "lat": 19.076, "lng": 72.8777},
+ {"name": "São Paulo", "lat": -23.5505, "lng": -46.6333},
+ {"name": "Seoul", "lat": 37.5665, "lng": 126.978}
+ ]
+}
diff --git a/js/admin.js b/js/admin.js
index 00cb8ce..18b8d6c 100644
--- a/js/admin.js
+++ b/js/admin.js
@@ -11,6 +11,8 @@ const AdminApp = {
servicesData: [],
navData: [],
linksData: [],
+ globeData: null,
+ chatAIData: null,
themeDefaults: {
accent: '#d0d0d0',
bg: '#111111',
@@ -119,6 +121,31 @@ const AdminApp = {
valFont.textContent = fontSlider.value;
});
}
+
+ // Globe slider sync
+ ['globeRotationSpeed', 'globeHexOpacity', 'globeAtmosphereAlt'].forEach(id => {
+ const slider = document.getElementById(id);
+ const valEl = document.getElementById(id + 'Val');
+ if (slider && valEl) {
+ slider.addEventListener('input', () => {
+ valEl.textContent = slider.value;
+ });
+ }
+ });
+
+ // Globe atmosphere colour sync
+ const globeAtmoPicker = document.getElementById('globeAtmosphereColor');
+ const globeAtmoHex = document.getElementById('globeAtmosphereColorHex');
+ if (globeAtmoPicker && globeAtmoHex) {
+ globeAtmoPicker.addEventListener('input', () => {
+ globeAtmoHex.value = globeAtmoPicker.value;
+ });
+ globeAtmoHex.addEventListener('input', () => {
+ if (/^#[0-9a-fA-F]{6}$/.test(globeAtmoHex.value)) {
+ globeAtmoPicker.value = globeAtmoHex.value;
+ }
+ });
+ }
},
async login(e) {
@@ -218,7 +245,9 @@ const AdminApp = {
'section-theme': 'Theme',
'section-seo': 'SEO',
'section-contact': 'Contact',
- 'section-backups': 'Backups'
+ 'section-backups': 'Backups',
+ 'section-globe': 'Globe',
+ 'section-chatai': 'Chat AI'
};
const topTitle = document.getElementById('topbarTitle') || document.querySelector('.topbar-title');
if (topTitle) topTitle.textContent = titleMap[name] || name.replace('section-', '').toUpperCase();
@@ -237,6 +266,8 @@ const AdminApp = {
case 'section-theme': this.loadTheme(); break;
case 'section-seo': this.loadSeo(); break;
case 'section-contact': this.loadContactSettings(); break;
+ case 'section-globe': this.loadGlobe(); break;
+ case 'section-chatai': this.loadChatAI(); break;
}
// Close mobile sidebar
@@ -1541,6 +1572,169 @@ const AdminApp = {
}
},
+ /* ─────────────────── GLOBE ─────────────────── */
+
+ async loadGlobe() {
+ try {
+ const res = await fetch(this.API + '/globe', { headers: this.authHeaders() });
+ if (!res.ok) throw new Error('Failed to load globe config');
+ const data = await res.json();
+ this.globeData = data;
+
+ // Server location
+ this.setVal('globeServerLat', data.server_lat);
+ this.setVal('globeServerLng', data.server_lng);
+ this.setVal('globeServerLabel', data.server_label);
+
+ // Globe settings
+ const rotSpeed = data.rotation_speed || 0.3;
+ this.setVal('globeRotationSpeed', rotSpeed);
+ const rotVal = document.getElementById('globeRotationSpeedVal');
+ if (rotVal) rotVal.textContent = rotSpeed;
+
+ const hexOp = data.hex_polygon_opacity || 0.55;
+ this.setVal('globeHexOpacity', hexOp);
+ const hexVal = document.getElementById('globeHexOpacityVal');
+ if (hexVal) hexVal.textContent = hexOp;
+
+ // Atmosphere
+ const atmoColor = data.atmosphere_color || '#00cc33';
+ this.setVal('globeAtmosphereColor', atmoColor);
+ this.setVal('globeAtmosphereColorHex', atmoColor);
+
+ const atmoAlt = data.atmosphere_altitude || 0.2;
+ this.setVal('globeAtmosphereAlt', atmoAlt);
+ const atmoVal = document.getElementById('globeAtmosphereAltVal');
+ if (atmoVal) atmoVal.textContent = atmoAlt;
+
+ // Arc cities table
+ this.renderArcCities(data.arc_cities || []);
+ } catch (err) {
+ console.error('Globe load error:', err);
+ this.notify('Failed to load globe config', 'error');
+ }
+ },
+
+ renderArcCities(cities) {
+ const tbody = document.getElementById('arcCitiesBody');
+ if (!tbody) return;
+ if (!cities || cities.length === 0) {
+ tbody.innerHTML = '| No arc cities configured |
';
+ return;
+ }
+ tbody.innerHTML = cities.map((c, i) => {
+ return `
+ | ${this.escapeHtml(c.name || '')} |
+ ${c.lat || 0} |
+ ${c.lng || 0} |
+ |
+
`;
+ }).join('');
+ },
+
+ addArcCity() {
+ const name = this.getVal('arcCityName');
+ const lat = parseFloat(this.getVal('arcCityLat'));
+ const lng = parseFloat(this.getVal('arcCityLng'));
+
+ if (!name || isNaN(lat) || isNaN(lng)) {
+ this.notify('City name, latitude and longitude are required', 'error');
+ return;
+ }
+
+ if (!this.globeData) this.globeData = {};
+ if (!this.globeData.arc_cities) this.globeData.arc_cities = [];
+
+ this.globeData.arc_cities.push({ name, lat, lng });
+ this.renderArcCities(this.globeData.arc_cities);
+
+ // Clear inputs
+ this.setVal('arcCityName', '');
+ this.setVal('arcCityLat', '');
+ this.setVal('arcCityLng', '');
+ this.notify('City added — save to persist', 'info');
+ },
+
+ removeArcCity(index) {
+ if (!this.globeData || !this.globeData.arc_cities) return;
+ if (!confirm('Remove this city?')) return;
+ this.globeData.arc_cities.splice(index, 1);
+ this.renderArcCities(this.globeData.arc_cities);
+ this.notify('City removed — save to persist', 'info');
+ },
+
+ async saveGlobe() {
+ const payload = {
+ server_lat: parseFloat(this.getVal('globeServerLat')) || 53.4808,
+ server_lng: parseFloat(this.getVal('globeServerLng')) || -2.2426,
+ server_label: this.getVal('globeServerLabel') || 'MANCHESTER',
+ rotation_speed: parseFloat(this.getVal('globeRotationSpeed')) || 0.3,
+ hex_polygon_opacity: parseFloat(this.getVal('globeHexOpacity')) || 0.55,
+ hex_polygon_color: (this.globeData && this.globeData.hex_polygon_color) || 'rgba(0, 220, 50, 0.55)',
+ atmosphere_color: this.getVal('globeAtmosphereColorHex') || this.getVal('globeAtmosphereColor') || '#00cc33',
+ atmosphere_altitude: parseFloat(this.getVal('globeAtmosphereAlt')) || 0.2,
+ arc_cities: (this.globeData && this.globeData.arc_cities) || []
+ };
+
+ try {
+ const res = await fetch(this.API + '/globe', {
+ method: 'POST',
+ headers: this.authHeaders(),
+ body: JSON.stringify(payload)
+ });
+ if (!res.ok) throw new Error('Save failed');
+ this.notify('Globe config saved');
+ } catch (err) {
+ console.error('Save globe error:', err);
+ this.notify('Failed to save globe config', 'error');
+ }
+ },
+
+ /* ─────────────────── CHAT AI ─────────────────── */
+
+ async loadChatAI() {
+ try {
+ const res = await fetch(this.API + '/chat-config', { headers: this.authHeaders() });
+ if (!res.ok) throw new Error('Failed to load chat config');
+ const data = await res.json();
+ this.chatAIData = data;
+
+ this.setVal('chatDisplayName', data.display_name);
+ this.setVal('chatModel', data.model);
+ this.setVal('chatHeaderTag', data.header_tag);
+ this.setVal('chatMaxHistory', data.max_history);
+ this.setChecked('chatAutoGreeting', data.auto_greeting !== false);
+ this.setVal('chatSystemPrompt', data.system_prompt_summary);
+ } catch (err) {
+ console.error('Chat AI load error:', err);
+ this.notify('Failed to load chat config', 'error');
+ }
+ },
+
+ async saveChatAI() {
+ const payload = {
+ display_name: this.getVal('chatDisplayName') || 'JAE-AI',
+ model: this.getVal('chatModel') || 'venice-uncensored-1-2',
+ header_tag: this.getVal('chatHeaderTag') || 'VENICE-UNCENSORED',
+ max_history: parseInt(this.getVal('chatMaxHistory')) || 20,
+ auto_greeting: this.getChecked('chatAutoGreeting'),
+ system_prompt_summary: this.getVal('chatSystemPrompt') || ''
+ };
+
+ try {
+ const res = await fetch(this.API + '/chat-config', {
+ method: 'POST',
+ headers: this.authHeaders(),
+ body: JSON.stringify(payload)
+ });
+ if (!res.ok) throw new Error('Save failed');
+ this.notify('Chat AI config saved');
+ } catch (err) {
+ console.error('Save chat AI error:', err);
+ this.notify('Failed to save chat config', 'error');
+ }
+ },
+
/* ─────────────────── UTILITIES ─────────────────── */
escapeHtml(str) {
diff --git a/js/globe.js b/js/globe.js
index 3a58219..d4ee1e5 100644
--- a/js/globe.js
+++ b/js/globe.js
@@ -29,7 +29,7 @@
.width(size)
.height(size)
.showAtmosphere(true)
- .atmosphereColor('#00cc33')
+ .atmosphereColor('#00ff44')
.atmosphereAltitude(0.2)
// Hex polygons for land masses
.hexPolygonsData([])
@@ -79,8 +79,8 @@
});
if (globeMesh && globeMesh.material) {
globeMesh.material.color.setHex(0x0a1a0a);
- globeMesh.material.emissive.setHex(0x006600);
- globeMesh.material.emissiveIntensity = 0.6;
+ globeMesh.material.emissive.setHex(0x008800);
+ globeMesh.material.emissiveIntensity = 0.8;
}
})
(container);
@@ -106,7 +106,10 @@
.hexPolygonResolution(3)
.hexPolygonMargin(0.4)
.hexPolygonColor(function () {
- return 'rgba(0, 204, 51, 0.45)';
+ return 'rgba(0, 220, 50, 0.55)';
+ })
+ .hexPolygonSideColor(function () {
+ return 'rgba(0, 255, 60, 0.6)';
})
.hexPolygonAltitude(0.005);
})
@@ -140,7 +143,7 @@
startLng: city.lng,
endLat: SERVER_LAT,
endLng: SERVER_LNG,
- color: ['rgba(0, 204, 51, 0.6)', 'rgba(0, 204, 51, 0.05)']
+ color: ['rgba(0, 255, 60, 0.7)', 'rgba(0, 255, 60, 0.08)']
});
}
return arcs;