// #/terminal — "Eithan": the CT 300 web terminal (ttyd → persistent tmux/claude), // same-origin under /terminal so it shares the Void's CF Access session AND lets // us reach the xterm instance for mobile copy/paste. import { el, mount } from '../dom.js'; const FS_KEY = 'void_term_fontsize'; export async function render(main) { // bigger default on touch screens; user-adjustable, remembered let fontSize = Number(localStorage.getItem(FS_KEY)) || (matchMedia('(pointer: coarse)').matches ? 17 : 14); const frame = el('iframe', { id: 'term-frame', class: 'term-frame', allow: 'clipboard-read; clipboard-write' }); const setSrc = () => { frame.src = `/terminal/?fontSize=${fontSize}`; }; setSrc(); // ttyd exposes its xterm as window.term; the same-origin proxy makes it reachable. const term = () => { try { return frame.contentWindow?.term ?? null; } catch { return null; } }; const note = el('span', { class: 'muted', style: { fontSize: '11px' } }, 'eithan @ ct300 · persistent tmux · swipe to scroll'); const flash = (msg) => { const old = note.textContent; note.textContent = msg; setTimeout(() => { note.textContent = old; }, 1600); }; const bump = (d) => { fontSize = Math.max(10, Math.min(24, fontSize + d)); localStorage.setItem(FS_KEY, String(fontSize)); setSrc(); // reload reattaches tmux; the session itself persists }; mount(main, el('div', { class: 'term-bar' }, el('span', { class: 'term-title' }, '◆ Eithan'), note, el('span', { style: { marginLeft: 'auto', display: 'flex', gap: '6px' } }, el('button', { class: 'ghost', title: 'smaller text', onclick: () => bump(-2) }, 'A−'), el('button', { class: 'ghost', title: 'larger text', onclick: () => bump(+2) }, 'A+'), el('button', { class: 'ghost', title: 'copy terminal selection', onclick: async () => { const sel = term()?.getSelection?.(); if (!sel) return flash('select text first (touch: long-press, then drag)'); try { await navigator.clipboard.writeText(sel); flash('copied ✓'); } catch { flash('clipboard needs the https domain'); } } }, '⧉ Copy'), el('button', { class: 'ghost', title: 'paste clipboard into terminal', onclick: async () => { const t = term(); if (!t) return flash('terminal not ready'); try { t.paste(await navigator.clipboard.readText()); } catch { flash('clipboard needs the https domain'); } } }, '⇩ Paste'), el('button', { class: 'ghost', title: 'jump to live output', onclick: () => { term()?.scrollToBottom?.(); frame.contentWindow?.focus(); } }, '↓ Live'), el('button', { class: 'ghost', title: 'reconnect', onclick: setSrc }, '⟳') ) ), frame ); }