← back to Ventura Bus 3d
switch port to 9783, add ecosystem.config.js, bake in dark/light toggle (sun/moon header button, anti-flash inline script, CSS-var inversion, localStorage persist)
4f29a8f6562c578aea037ce3401b97ee432f745f · 2026-05-10 00:57:20 -0700 · Steve
Files touched
A ecosystem.config.jsM public/index.htmlM src/server.js
Diff
commit 4f29a8f6562c578aea037ce3401b97ee432f745f
Author: Steve <steve@designerwallcoverings.com>
Date: Sun May 10 00:57:20 2026 -0700
switch port to 9783, add ecosystem.config.js, bake in dark/light toggle (sun/moon header button, anti-flash inline script, CSS-var inversion, localStorage persist)
---
ecosystem.config.js | 18 ++++++++++++++++++
public/index.html | 55 +++++++++++++++++++++++++++++++++++++++++++++--------
src/server.js | 2 +-
3 files changed, 66 insertions(+), 9 deletions(-)
diff --git a/ecosystem.config.js b/ecosystem.config.js
new file mode 100644
index 0000000..38f3aea
--- /dev/null
+++ b/ecosystem.config.js
@@ -0,0 +1,18 @@
+module.exports = {
+ apps: [
+ {
+ name: 'ventura-bus-3d',
+ script: 'src/server.js',
+ cwd: __dirname,
+ instances: 1,
+ exec_mode: 'fork',
+ autorestart: true,
+ watch: false,
+ max_memory_restart: '512M',
+ env: {
+ NODE_ENV: 'production',
+ PORT: 9783
+ }
+ }
+ ]
+};
diff --git a/public/index.html b/public/index.html
index db0dac1..6191048 100644
--- a/public/index.html
+++ b/public/index.html
@@ -4,18 +4,34 @@
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Ventura Bus 3D</title>
+ <script>
+ /* anti-flash: apply saved theme before paint */
+ (function () {
+ try {
+ var t = localStorage.getItem('vb3d-theme');
+ if (!t) t = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
+ document.documentElement.setAttribute('data-theme', t);
+ } catch (e) { document.documentElement.setAttribute('data-theme', 'dark'); }
+ })();
+ </script>
<style>
- :root { --bg: #0b0d12; --fg: #e7ecf2; --accent: #ffb000; --panel: rgba(10,12,18,0.62); --line: rgba(255,255,255,0.12); }
- html, body { margin: 0; padding: 0; height: 100%; background: var(--bg); color: var(--fg); font: 14px/1.4 system-ui, sans-serif; }
+ :root[data-theme="dark"] { --bg: #0b0d12; --fg: #e7ecf2; --accent: #ffb000; --panel: rgba(10,12,18,0.62); --line: rgba(255,255,255,0.12); --btn-bg: rgba(255,255,255,0.08); --active-fg: #1a1100; }
+ :root[data-theme="light"] { --bg: #f4f6fb; --fg: #14181f; --accent: #c46b00; --panel: rgba(255,255,255,0.78); --line: rgba(20,24,31,0.14); --btn-bg: rgba(20,24,31,0.06); --active-fg: #fffaf0; }
+ html, body { margin: 0; padding: 0; height: 100%; background: var(--bg); color: var(--fg); font: 14px/1.4 system-ui, sans-serif; transition: background 200ms ease, color 200ms ease; }
#app { position: fixed; inset: 0; }
.panel { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px); }
#hud { position: fixed; top: 12px; left: 12px; padding: 10px 14px; }
#hud h1 { margin: 0 0 4px; font-size: 14px; font-weight: 600; letter-spacing: 0.02em; }
#hud small { opacity: 0.7; }
#stops { position: fixed; bottom: 12px; left: 12px; right: 12px; display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
- #stops button { background: rgba(255,255,255,0.08); color: var(--fg); border: 1px solid var(--line); padding: 6px 10px; border-radius: 999px; cursor: pointer; }
+ #stops button { background: var(--btn-bg); color: var(--fg); border: 1px solid var(--line); padding: 6px 10px; border-radius: 999px; cursor: pointer; }
#stops button:hover { border-color: var(--accent); color: var(--accent); }
- #stops button.active { background: var(--accent); color: #1a1100; border-color: var(--accent); }
+ #stops button.active { background: var(--accent); color: var(--active-fg); border-color: var(--accent); }
+
+ /* theme toggle (sun/moon) lives in #hud */
+ #theme-toggle { margin-left: 10px; width: 28px; height: 28px; border-radius: 50%; border: 1px solid var(--line); background: var(--btn-bg); color: var(--fg); cursor: pointer; font-size: 14px; line-height: 1; vertical-align: middle; transition: border-color 150ms ease, color 150ms ease; }
+ #theme-toggle:hover { border-color: var(--accent); color: var(--accent); }
+ #hud .row { display: flex; align-items: center; gap: 8px; }
/* Route legend (top-right) */
#legend { position: fixed; top: 12px; right: 12px; padding: 12px 14px; min-width: 220px; max-width: 260px; }
@@ -36,19 +52,42 @@
#tod input[type=range] { width: 100%; accent-color: var(--accent); }
#tod .ticks { display: flex; justify-content: space-between; font-size: 10px; opacity: 0.55; margin-top: 2px; }
#tod .presets { display: flex; gap: 6px; margin-top: 8px; flex-wrap: wrap; }
- #tod .presets button { flex: 1 1 auto; background: rgba(255,255,255,0.06); color: var(--fg); border: 1px solid var(--line); padding: 4px 8px; border-radius: 6px; cursor: pointer; font-size: 11px; }
+ #tod .presets button { flex: 1 1 auto; background: var(--btn-bg); color: var(--fg); border: 1px solid var(--line); padding: 4px 8px; border-radius: 6px; cursor: pointer; font-size: 11px; }
#tod .presets button:hover { border-color: var(--accent); color: var(--accent); }
- #tod .presets button.active { background: var(--accent); color: #1a1100; border-color: var(--accent); }
+ #tod .presets button.active { background: var(--accent); color: var(--active-fg); border-color: var(--accent); }
#tod .phase { margin-top: 6px; font-size: 11px; opacity: 0.7; display: flex; justify-content: space-between; }
</style>
</head>
<body>
<div id="app"></div>
<div id="hud" class="panel">
- <h1>Ventura Bus 3D</h1>
- <small>Drag to orbit · scroll to zoom</small>
+ <div class="row">
+ <div>
+ <h1>Ventura Bus 3D</h1>
+ <small>Drag to orbit · scroll to zoom</small>
+ </div>
+ <button id="theme-toggle" type="button" title="Toggle light / dark" aria-label="Toggle theme">☾</button>
+ </div>
</div>
+ <script>
+ (function () {
+ var btn = document.getElementById('theme-toggle');
+ var root = document.documentElement;
+ function paint() {
+ btn.textContent = root.getAttribute('data-theme') === 'light' ? '☀' : '☾';
+ }
+ paint();
+ btn.addEventListener('click', function () {
+ var next = root.getAttribute('data-theme') === 'light' ? 'dark' : 'light';
+ root.setAttribute('data-theme', next);
+ try { localStorage.setItem('vb3d-theme', next); } catch (e) {}
+ paint();
+ window.dispatchEvent(new CustomEvent('vb3d:theme', { detail: next }));
+ });
+ })();
+ </script>
+
<aside id="legend" class="panel" aria-label="Route legend">
<h2>Route</h2>
<div class="route-name" id="legend-route">—</div>
diff --git a/src/server.js b/src/server.js
index 283cad4..54a4eec 100644
--- a/src/server.js
+++ b/src/server.js
@@ -4,7 +4,7 @@ const compression = require('compression');
const morgan = require('morgan');
const app = express();
-const PORT = process.env.PORT || 9871;
+const PORT = process.env.PORT || 9783;
const PUBLIC_DIR = path.join(__dirname, '..', 'public');
app.use(compression());
← 5505ad6 add route legend (top-right) and time-of-day overlay (bottom
·
back to Ventura Bus 3d
·
task #3: base Three.js scene — curved corridor, mountains, d b343de6 →