[object Object]

← 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

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 →