← back to Ken
Ken: incorporate the brass AB Trading Switch onto the dashboard front page
d6811edbd37b9d90ebd2d1b7559ecdff85cbf290 · 2026-08-03 12:23:36 -0700 · Steve Abrams
Serve /trading-switch-widget.js and inject it into the SPA index.html at serve-time (no React rebuild). Renders a compact brass bat-switch pinned bottom-right of the dashboard, wired to the same set_trading endpoint + guard source-of-truth; right-click opens the full /trading panel.
Files touched
M kalshi-dash/server.jsA kalshi-dash/trading-switch-widget.js
Diff
commit d6811edbd37b9d90ebd2d1b7559ecdff85cbf290
Author: Steve Abrams <steve@designerwallcoverings.com>
Date: Mon Aug 3 12:23:36 2026 -0700
Ken: incorporate the brass AB Trading Switch onto the dashboard front page
Serve /trading-switch-widget.js and inject it into the SPA index.html at serve-time (no React rebuild). Renders a compact brass bat-switch pinned bottom-right of the dashboard, wired to the same set_trading endpoint + guard source-of-truth; right-click opens the full /trading panel.
---
kalshi-dash/server.js | 25 +++++++++++-
kalshi-dash/trading-switch-widget.js | 74 ++++++++++++++++++++++++++++++++++++
2 files changed, 98 insertions(+), 1 deletion(-)
diff --git a/kalshi-dash/server.js b/kalshi-dash/server.js
index bec0c64..40cfefe 100644
--- a/kalshi-dash/server.js
+++ b/kalshi-dash/server.js
@@ -5035,6 +5035,18 @@ const server = http.createServer(async (req, res) => {
return;
}
+ // ── Front-page Trading-Switch widget (injected into the dashboard index.html below) ──
+ if (urlPath === '/trading-switch-widget.js') {
+ try {
+ const js = fs.readFileSync(path.join(__dirname, 'trading-switch-widget.js'), 'utf8');
+ res.writeHead(200, { 'Content-Type': 'application/javascript', 'Cache-Control': 'no-cache' });
+ res.end(js);
+ } catch (e) {
+ res.writeHead(404); res.end('// widget not found');
+ }
+ return;
+ }
+
// ── Serve Static Files / SPA Routing ──
let filePath = path.join(DIST, urlPath === '/' ? 'index.html' : urlPath);
@@ -5051,7 +5063,6 @@ const server = http.createServer(async (req, res) => {
}
try {
- const content = fs.readFileSync(filePath);
const ext = path.extname(filePath);
const contentType = MIME[ext] || 'application/octet-stream';
@@ -5060,6 +5071,18 @@ const server = http.createServer(async (req, res) => {
? 'no-cache, no-store, must-revalidate'
: 'public, max-age=31536000, immutable';
+ // Inject the front-page brass Trading-Switch widget into the dashboard HTML.
+ if (ext === '.html') {
+ let html = fs.readFileSync(filePath, 'utf8');
+ if (!html.includes('trading-switch-widget.js')) {
+ html = html.replace('</body>', ' <script src="/trading-switch-widget.js" defer></script>\n</body>');
+ }
+ res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': cacheHeader });
+ res.end(html);
+ return;
+ }
+
+ const content = fs.readFileSync(filePath);
res.writeHead(200, {
'Content-Type': contentType,
'Cache-Control': cacheHeader,
diff --git a/kalshi-dash/trading-switch-widget.js b/kalshi-dash/trading-switch-widget.js
new file mode 100644
index 0000000..ed8f7e6
--- /dev/null
+++ b/kalshi-dash/trading-switch-widget.js
@@ -0,0 +1,74 @@
+/* Ken front-page Trading Switch — a compact brass AB bat-switch pinned bottom-right of the
+ main dashboard. Self-contained, namespaced (ken-ts-*), no framework deps. Reads/sets the
+ same /api/trading/mode endpoints; the safe_mode guard treats this as the source of truth. */
+(function () {
+ if (window.__kenTradingSwitch) return; window.__kenTradingSwitch = true;
+
+ var css = `
+ .ken-ts{position:fixed;right:18px;bottom:18px;z-index:99999;font-family:Inter,system-ui,sans-serif;
+ display:flex;flex-direction:column;align-items:center;gap:7px}
+ .ken-ts-cap{font-family:'Space Grotesk',sans-serif;font-size:11px;letter-spacing:.14em;text-transform:uppercase;color:#8a97a8}
+ .ken-ts-state{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:13px;letter-spacing:.05em}
+ .ken-ts-state.on{color:#22c55e} .ken-ts-state.off{color:#8a97a8}
+ .ken-ts-sw{cursor:pointer;outline:none}
+ .ken-ts-sw[data-busy="true"]{opacity:.6;pointer-events:none}
+ .ken-ts-plate{position:relative;width:104px;height:150px;border-radius:12px;padding:11px 0;
+ display:flex;flex-direction:column;align-items:center;justify-content:space-between;
+ background:linear-gradient(150deg,#7a5c12 0%,#c9a53a 16%,#f6e79a 32%,#d9b64a 48%,#a8801f 64%,#e6cf78 82%,#7f6115 100%);
+ border:1px solid #5c460f;
+ box-shadow:inset 0 1px 3px rgba(255,255,255,.55),inset 0 -3px 7px rgba(0,0,0,.5),0 8px 18px rgba(0,0,0,.55)}
+ .ken-ts-scr{position:absolute;width:10px;height:10px;border-radius:50%;
+ background:radial-gradient(circle at 38% 32%,#f9efb8,#bf9a34 58%,#6f550f);box-shadow:inset 0 1px 1px rgba(255,255,255,.7),0 1px 1px rgba(0,0,0,.55)}
+ .ken-ts-scr.a{top:6px;left:6px}.ken-ts-scr.b{top:6px;right:6px}.ken-ts-scr.c{bottom:6px;left:6px}.ken-ts-scr.d{bottom:6px;right:6px}
+ .ken-ts-mono{font-family:'Space Grotesk',sans-serif;font-weight:800;font-size:12px;letter-spacing:.14em;color:#4f3d0d;text-shadow:0 1px 0 rgba(255,255,255,.55)}
+ .ken-ts-lbl{font-family:'Space Grotesk',sans-serif;font-weight:700;font-size:9px;letter-spacing:.2em;color:#5c4610;text-shadow:0 1px 0 rgba(255,255,255,.5);transition:color .2s,text-shadow .2s}
+ .ken-ts-sw[data-on="true"] .ken-ts-on{color:#1c7a34;text-shadow:0 0 7px rgba(34,197,94,.75)}
+ .ken-ts-sw[data-on="false"] .ken-ts-off{color:#8a1f1f}
+ .ken-ts-throw{position:relative;width:38px;height:74px;border-radius:20px;
+ background:linear-gradient(90deg,#4a380b,#7a5c12 32%,#2f240700 50%,#5a460f 70%,#332703);box-shadow:inset 0 3px 6px rgba(0,0,0,.75)}
+ .ken-ts-piv{position:absolute;left:50%;top:50%;width:28px;height:28px;border-radius:50%;transform:translate(-50%,-50%);
+ background:radial-gradient(circle at 40% 34%,#f6e79a,#bf9a34 56%,#7a5c12);box-shadow:inset 0 1px 2px rgba(255,255,255,.65),0 2px 3px rgba(0,0,0,.55)}
+ .ken-ts-lev{position:absolute;left:50%;bottom:50%;transform-origin:bottom center;z-index:2;
+ transform:translateX(-50%) rotate(178deg);transition:transform .26s cubic-bezier(.34,1.5,.5,1)}
+ .ken-ts-sw[data-on="true"] .ken-ts-lev{transform:translateX(-50%) rotate(0deg)}
+ .ken-ts-stem{width:13px;height:36px;border-radius:7px;margin:0 auto;
+ background:linear-gradient(90deg,#7a5c12,#e6cf78 44%,#f6e79a 52%,#a8801f 78%,#6f550f);box-shadow:inset 1px 0 1px rgba(255,255,255,.55),0 1px 3px rgba(0,0,0,.4)}
+ .ken-ts-ball{position:absolute;top:-10px;left:50%;transform:translateX(-50%);width:23px;height:23px;border-radius:50%;
+ background:radial-gradient(circle at 36% 30%,#f9efb8,#c19b33 56%,#7a5c12);box-shadow:inset 0 1px 2px rgba(255,255,255,.65),0 2px 4px rgba(0,0,0,.55);transition:background .2s,box-shadow .2s}
+ .ken-ts-sw[data-on="true"] .ken-ts-ball{background:radial-gradient(circle at 36% 30%,#d9ffe4,#22c55e 60%,#0f7a34);box-shadow:0 0 12px rgba(34,197,94,.85),inset 0 1px 2px rgba(255,255,255,.5)}
+ `;
+ var st = document.createElement('style'); st.textContent = css; document.head.appendChild(st);
+
+ var el = document.createElement('div'); el.className = 'ken-ts';
+ el.innerHTML =
+ '<div class="ken-ts-cap">Real-money trading</div>' +
+ '<div class="ken-ts-state off" id="kenTsState">…</div>' +
+ '<div class="ken-ts-sw" id="kenTsSw" data-on="false" role="switch" aria-checked="false" tabindex="0" title="Throw to arm / disarm real-money trading (opens full panel on right-click)">' +
+ '<div class="ken-ts-plate">' +
+ '<span class="ken-ts-scr a"></span><span class="ken-ts-scr b"></span><span class="ken-ts-scr c"></span><span class="ken-ts-scr d"></span>' +
+ '<div class="ken-ts-mono">AB</div>' +
+ '<div class="ken-ts-lbl ken-ts-on">ON</div>' +
+ '<div class="ken-ts-throw"><div class="ken-ts-piv"></div><div class="ken-ts-lev"><div class="ken-ts-ball"></div><div class="ken-ts-stem"></div></div></div>' +
+ '<div class="ken-ts-lbl ken-ts-off">OFF</div>' +
+ '</div>' +
+ '</div>';
+ document.body.appendChild(el);
+
+ var sw = el.querySelector('#kenTsSw'), lab = el.querySelector('#kenTsState');
+ var state = {};
+ function api(body){ return fetch('/api/trading/mode',{method:'POST',credentials:'same-origin',
+ headers:{'Content-Type':'application/json'},body:JSON.stringify(body)}).then(function(r){return r.json();}); }
+ function render(s){ var on = s.safe_mode === false; sw.dataset.on = on; sw.setAttribute('aria-checked', on);
+ lab.textContent = on ? 'ARMED' : 'SAFE'; lab.className = 'ken-ts-state ' + (on ? 'on' : 'off'); }
+ function refresh(){ api({action:'get'}).then(function(s){ state = s; render(s); }).catch(function(){}); }
+ function setOn(on){
+ if (on && !confirm('Arm REAL-MONEY trading?\n\nThe $50 live run will place real Kalshi orders once ≥2 winner-bots agree (capped $2/pos · $10/day). Toggle off any time.')) return;
+ sw.dataset.busy = 'true';
+ api({action:'set_trading', on:on}).then(function(){ sw.dataset.busy=''; refresh(); })
+ .catch(function(e){ sw.dataset.busy=''; alert('failed: '+e.message); });
+ }
+ sw.addEventListener('click', function(){ setOn(state.safe_mode !== false); });
+ sw.addEventListener('contextmenu', function(e){ e.preventDefault(); window.open('/trading','_blank'); });
+ sw.addEventListener('keydown', function(e){ if(e.key===' '||e.key==='Enter'){ e.preventDefault(); sw.click(); } });
+ refresh(); setInterval(refresh, 5000);
+})();
← ea9bacb Ken: style the /trading toggle as a large brass AB bat-switc
·
back to Ken
·
auto-save: 2026-08-03T12:23:59 (1 files) — kalshi-dash/ken-t 07bb411 →