← back to Ken

kalshi-dash/trading-switch-widget.js

75 lines

/* 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);
})();