(function(){ if(window._toolbar) return; window._toolbar=true; // ── Sethmux toolbar — Google Workspace dark vibe, sethmux orange accent ── // Tokens (kept local, not :root, so we don't pollute the host page) // bg #202124 toolbar surface // surface #303134 button face // border #3c4043 hairlines // text #e8eaed primary // text-2 #9aa0a6 secondary / icons at rest // accent #D35400 sethmux orange (replaces Google blue) // accent-bg #3a2a1a tinted hover/selected wash // ok #81c995 save success var css=document.createElement('style'); css.textContent = [ "#mb{", "display:none;position:fixed;bottom:0;left:0;right:0;", "background:#202124;", "border-top:1px solid #3c4043;", "padding:6px 8px 7px;", "gap:0;flex-direction:column;align-items:stretch;", "z-index:99999;", "font-family:'Roboto','Helvetica Neue',Arial,sans-serif;", "-webkit-font-smoothing:antialiased;", "box-shadow:0 -1px 0 rgba(0,0,0,.4),0 -8px 24px rgba(0,0,0,.35);", "}", "#mb .row{", "display:flex;gap:4px;justify-content:center;align-items:center;", "width:100%;", "}", "#mb .row + .row{margin-top:4px}", "#mb button{", "background:#303134;", "color:#e8eaed;", "border:1px solid #3c4043;", "border-radius:4px;", "padding:0 10px;height:32px;min-width:40px;", "font:500 12px/1 'Roboto','Helvetica Neue',Arial,sans-serif;", "letter-spacing:.1px;", "cursor:pointer;", "touch-action:manipulation;", "-webkit-tap-highlight-color:transparent;", "user-select:none;", "display:inline-flex;align-items:center;justify-content:center;", "transition:background .15s ease,border-color .15s ease,color .15s ease;", "}", "#mb button:hover{background:#3a2a1a;border-color:#3c4043;color:#fff}", "#mb button:active{background:#D35400;border-color:#D35400;color:#0a0a0a}", // Mono labels for chord/arrow keys so they read as terminal input "#mb button.mono{", "font-family:'Roboto Mono','SF Mono',ui-monospace,Menlo,Consolas,monospace;", "font-weight:400;color:#9aa0a6;", "}", "#mb button.mono:hover{color:#e8eaed}", // Accent (orange) — used for primary/important actions at rest "#mb button.hi{", "color:#f0a36b;border-color:#5a3a22;background:#2a1f15;", "}", "#mb button.hi:hover{background:#3a2a1a;color:#ffb37a;border-color:#7a4a2a}", // Toggled-on state (Sel active, etc.) — filled accent "#mb button.on{", "background:#D35400;border-color:#D35400;color:#0a0a0a;", "}", "#mb button.on:hover{background:#e26416;border-color:#e26416;color:#0a0a0a}", // Success (Save) — Google green at rest, fills on confirm "#mb button.grn{color:#81c995;border-color:#3c4043;background:#303134}", "#mb button.grn:hover{background:#1f2a22;color:#a8e0b8;border-color:#3a5a44}", "#mb button.grn.on{background:#1e8e3e;border-color:#1e8e3e;color:#0a0a0a}", // Vertical hairline divider between groups "#mb .sep{", "width:1px;height:20px;background:#3c4043;margin:0 4px;flex-shrink:0;", "}", // ── Compose bar (mobile autocorrect workaround) ── "#mb .compose{", "display:none;width:100%;gap:4px;align-items:center;margin-top:4px;", "}", "#mb.composing .compose{display:flex}", "#mb.composing #typebtn{", "background:#D35400;border-color:#D35400;color:#0a0a0a;", "}", "#mb-compose{", "flex:1;min-width:0;height:36px;", "padding:0 10px;", "background:#303134;color:#e8eaed;", "border:1px solid #3c4043;border-radius:4px;", "font:400 14px/1 'Roboto Mono','SF Mono',ui-monospace,Menlo,Consolas,monospace;", "outline:none;", "-webkit-appearance:none;appearance:none;", "caret-color:#D35400;", "}", "#mb-compose:focus{border-color:#D35400}", "#mb-compose::placeholder{color:#5f6368}", "#mb .send{", "height:36px;min-width:54px;padding:0 12px;", "background:#D35400;border:1px solid #D35400;color:#0a0a0a;", "border-radius:4px;font:500 12px/1 'Roboto',sans-serif;cursor:pointer;", "}", "#mb .send:disabled{background:#303134;border-color:#3c4043;color:#5f6368;cursor:default}", "#mb .send.nl{background:#303134;border-color:#3c4043;color:#9aa0a6;min-width:38px;padding:0 8px}", "#mb .send.nl:hover{color:#e8eaed;background:#3a2a1a}", // Selection mode visual — dim the terminal slightly so the text-select layer reads "body.selmode .xterm-screen{", "pointer-events:none!important;", "user-select:text!important;-webkit-user-select:text!important;", "}", "body.selmode .xterm{filter:brightness(.92)}", "@media(max-width:900px){#mb{display:flex}}", // Tighter on very narrow phones "@media(max-width:380px){", "#mb button{padding:0 7px;min-width:34px;height:30px;font-size:11.5px}", "#mb .sep{margin:0 2px}", "}", ].join(""); document.head.appendChild(css); var bar=document.createElement('div'); bar.id='mb'; bar.innerHTML = '
' + '' + '' + '' + '
' + '' + '' + '' + '
' + '' + '' + '' + '' + '
' + '
' + '' + '' + '' + '' + '
' + '' + '' + '' + '' + '
' + '' + '
' + '
' + '' + '' + '' + '
'; document.body.appendChild(bar); // ── terminal I/O ───────────────────────────────────────── function send(k){ if(document.body.classList.contains('selmode')) toggleSel(); var t=window.term; if(!t) return; if(t._core && t._core.coreService && t._core.coreService.triggerDataEvent){ t._core.coreService.triggerDataEvent(k); } else if(t._core && t._core._onData){ t._core._onData.fire(k); } else if(t.input){ t.input(k); } t.focus(); } function toggleSel(){ var b=document.getElementById('selbtn'); document.body.classList.toggle('selmode'); if(document.body.classList.contains('selmode')){ b.classList.add('on');b.textContent='Done'; } else { b.classList.remove('on');b.textContent='Sel'; window.getSelection().removeAllRanges(); if(window.term) window.term.focus(); } } function doPaste(){ if(!navigator.clipboard||!navigator.clipboard.readText){ alert('Clipboard access not available (needs HTTPS)'); return; } navigator.clipboard.readText().then(function(text){ if(text) send(text); }).catch(function(e){ alert('Clipboard read failed: '+e.message); }); } function doSave(){ send('\x01S'); var btn=document.querySelector('[data-save]'); btn.classList.add('on'); btn.textContent='\u2713 Saved'; setTimeout(function(){btn.classList.remove('on');btn.textContent='Save';},1500); } // ── Compose bar (mobile autocorrect workaround) ── // xterm.js reads keys from a hidden textarea via per-keystroke events; // Gboard autocorrect/swipe replaces .value in bulk so those chars never // reach stdin. The compose bar gives autocorrect a real to chew // on, then sends the assembled string to the terminal in one shot. var ci=document.getElementById('mb-compose'); function toggleType(){ var on=bar.classList.toggle('composing'); if(on){ // Defer focus so the keyboard opens reliably on iOS/Android setTimeout(function(){ ci.focus(); },0); relayout(); } else { ci.blur(); if(window.term) window.term.focus(); relayout(); } } function flushCompose(appendNewline){ var v=ci.value; ci.value=''; if(v) send(v); if(appendNewline) send('\r'); ci.focus(); } ci.addEventListener('keydown',function(e){ if(e.key==='Enter'){ e.preventDefault(); flushCompose(true); } }); bar.addEventListener('click',function(e){ var btn=e.target.closest('button'); if(!btn) return; if(btn.dataset.sel) return toggleSel(); if(btn.dataset.paste) return doPaste(); if(btn.dataset.save) return doSave(); if(btn.dataset.type) return toggleType(); if(btn.dataset.send) return flushCompose(true); if(btn.dataset.nl) return flushCompose(true); // explicit newline button if(btn.dataset.k) { // Key pressed while composing: flush typed text first so order is preserved, // but DON'T close the compose bar — user is mid-thought. if(bar.classList.contains('composing') && ci.value){ var v=ci.value; ci.value=''; send(v); } send(btn.dataset.k); if(bar.classList.contains('composing')) ci.focus(); } }); // Keep terminal focused when tapping outside the toolbar document.addEventListener('click', function(e){ if(!e.target.closest('#mb') && window.term) window.term.focus(); }); // Tab key shouldn't escape into toolbar focus bar.querySelectorAll('button').forEach(function(b){ b.tabIndex = -1; }); // Disable mobile autocomplete on xterm's hidden textarea function disableMobileAutocomplete(){ var ta=document.querySelector('.xterm-helper-textarea'); if(!ta || ta.dataset.acOff) return; ta.setAttribute('autocomplete','off'); ta.setAttribute('autocorrect','off'); ta.setAttribute('autocapitalize','off'); ta.setAttribute('spellcheck','false'); ta.dataset.acOff='1'; } // Resize terminal to leave room for the toolbar on mobile. // Height is dynamic: 2 rows when collapsed, 3 when compose bar is open. function relayout(){ var el=document.querySelector('.xterm'); if(!el || window.innerWidth>900) return; var h=bar.offsetHeight || 92; el.style.height='calc(100vh - '+(h+4)+'px)'; if(window.term && window.term.fit) window.term.fit(); } var obs=new MutationObserver(function(){ disableMobileAutocomplete(); relayout(); }); obs.observe(document.body,{childList:true,subtree:true}); window.addEventListener('resize',relayout); })();