<!-- RUBY-BACKUP · part 87 -->
lid's heart
     -------------------------------------------------------------------------- */
  function buildHeart(){
    var st = document.createElement('div');
    st.id = 'solid-heart';
    st.style.cssText = 'position:fixed;left:-9999px;top:0;width:300px;'
      + 'background:rgba(10,8,4,0.98);border:2px solid #d8a920;border-radius:12px;'
      + 'padding:12px;z-index:9999;display:none;';
    st.innerHTML = '<div style="color:#ffd700;text-align:center;letter-spacing:4px;'
      + 'margin-bottom:8px;">SOLID\'S HEART</div>';
    var inp = document.createElement('input');
    inp.placeholder = 'punch in a code...';
    inp.style.cssText = 'width:100%;background:#0a0805;border:1px solid #d8a920;'
      + 'border-radius:8px;color:#ffd700;padding:10px;margin-bottom:6px;'
      + 'font-family:Georgia,serif;box-sizing:border-box;font-size:16px;';
    var snd = document.createElement('button');
    snd.textContent = 'send secretly';
    snd.style.cssText = 'width:100%;background:#d8a920;color:#111;border:none;'
      + 'border-radius:8px;padding:10px;font-weight:bold;cursor:pointer;'
      + 'font-family:Georgia,serif;font-size:16px;';
    snd.onclick = function(){
      var c = inp.value.trim();
      if(c){ code(c); inp.value=''; }
    };
    inp.addEventListener('keydown', function(e){ if(e.key==='Enter') snd.onclick(); });
    st.appendChild(inp); st.appendChild(snd);
    document.body.appendChild(st);
    window.openHeart = function(){
      st.style.left='50%'; st.style.top='50%';
      st.style.transform='translate(-50%,-50%)'; st.style.display='block';
      inp.focus();
    };
    window.closeHeart = function(){ st.style.display='none'; st.style.left='-9999px'; };
    window.solidSecretCode = function(c){ if(c) code(c); };

    /* os codes */
    window.__osCodes = {};
    allFns.forEach(function(it,i){ window.__osCodes['os'+(i+1)] = it.fn; });
    if(window.registerHook){
      window.registerHook('oscodes', function(){
        var prev = window.runLine;
        window.runLine = function(line){
          try{
            var tl = String(line||'').trim().toLowerCase();
            if(window.__osCodes[tl]){ window.__osCodes[tl](); return; }
            if(tl==='osnext'){ window.cycleOS(); return; }
            if(tl==='osreset'){ cycleIdx=-1; return; }
          }catch(e){}
          return prev.apply(this, arguments);
        };
        window.runLine.__oscodesHooked = true;
      });
    }
  }

  /* --------------------------------------------------------------------------
     REAL JOYSTICKS — the toggles are gone. Two joysticks rule.
     Left: move (walk/fly). Right: camera (drag to look).
     -------------------------------------------------------------------------- */
  (function setupJoysticks(){
    /* LEFT — move joystick. The existing #joy, made permanent and always visible. */
    var joy = document.getElementById('joy');
    if(joy){
      joy.style.setProperty('display', 'block', 'important');
      joy.style.setProperty('position', 'fixed', 'important');
      joy.style.setProperty('left', '18px', 'important');
      joy.style.setProperty('bottom', '120px', 'important');
      joy.style.setProperty('z-index', '999', 'important');
      /* Keep it visible — the game tries to hide it */
      setInterval(function(){
        if(joy.style.display !== 'block') joy.style.setProperty('display', 'block', 'important');
      }, 1000);
      /* Label: Body */
      var bodyLbl = document.createElement('div');
      bodyLbl.textContent = 'Body';
      bodyLbl.style.cssText = 'position:fixed;left:18px;bottom:224px;width:100px;'
        + 'text-align:center;font-size:11px;font-weight:bold;color:#d8a920;'
        + 'font-family:Georgia,serif;z-index:999;pointer-events:none;letter-spacing:1px;';
      document.body.appendChild(bodyLbl);
    }
    /* RIGHT — camera joystick. Drag to rotate the view. */
    var camJoy = document.createElement('div');
    camJoy.id = 'camjoy';
    var knob = document.createElement('div');
    knob.id = 'camjoyknob';
    camJoy.appendChild(knob);
    camJoy.style.cssText = 'position:fixed;right:120px;bottom:130px;width:100px;height:100px;'
      + 'border-radius:50%;border:2px solid #8a6a1c;background:rgba(20,14,6,0.4);'
      + 'z-index:999;touch-action:none;';
    knob.style.cssText = 'position:absolute;left:50%;top:50%;width:44px;height:44px;'
      + 'margin:-22px 0 0 -22px;border-radius:50%;'
      + 'background:radial-gradient(circle at 30% 30%, #ffd700, #8a6a1c);'
      + 'box-shadow:0 0 8px rgba(255,215,0,0.5);';
    var dragging = false, lastX = 0, lastY = 0;
    camJoy.addEventListener('touchstart', function(e){
      e.stopPropagation();
      dragging = true;
      var t = e.changedTouches[0];
      lastX = t.clientX; lastY = t.clientY;
    }, {passive:true});
    camJoy.addEventListener('touchmove', function(e){
      e.stopPropagation(); e.preventDefault();
      if(!dragging) return;
      var t = e.changedTouches[0];
     