// terminal-prod.jsx
// Production terminal landing for bromb.dev.
// One full-bleed page. Pre-rendered session at top, then a live prompt
// at the bottom that accepts commands. Wired to the Tweaks panel for
// accent, prompt glyph, chrome style, and preview mode.

const COMMANDS = [
  'help', 'whoami', 'ls', 'cat', 'notes', 'contact',
  'date', 'open', 'theme', 'clear', 'banner', 'sudo', 'irc',
];

const CAT_FILES = ['about', 'now', 'shader-tutorial', 'tinyroguelike', 'colophon'];
const THEMES    = ['peach', 'mint', 'cyan', 'magenta'];
const IRC_COMMANDS = ['/quit', '/exit', '/nick', '/me', '/who', '/help'];

// Argument candidates per command. `null`/missing → no arg completion.
function completionsFor(cmd) {
  switch (cmd) {
    case 'cat':   return CAT_FILES;
    case 'open':  return (COPY?.projects || []).map((p) => p.file.replace(/\.md$/, ''));
    case 'theme': return THEMES;
    default:      return null;
  }
}

function commonPrefix(strs) {
  if (!strs.length) return '';
  return strs.reduce((acc, s) => {
    let i = 0;
    while (i < acc.length && i < s.length && acc[i] === s[i]) i++;
    return acc.slice(0, i);
  });
}

// Tab-completion. Returns { value, candidates }:
// - value: the new input string (caller writes it back to state).
// - candidates: full match list to display when ambiguous; empty otherwise.
// Behaviour mirrors bash readline:
//   - unique match → expand + trailing space (commands only).
//   - multiple matches → expand to longest common prefix, list options.
//   - no match → input untouched.
function completeInput(raw) {
  const leading        = raw.match(/^\s*/)[0];
  const trimmed        = raw.trim();
  const hasTrailingSpc = /\s$/.test(raw) && trimmed.length > 0;
  const parts          = trimmed.length ? trimmed.split(/\s+/) : [];

  // Still on the command token.
  if (parts.length <= 1 && !hasTrailingSpc) {
    const token   = parts[0] || '';
    const matches = COMMANDS.filter((c) => c.startsWith(token));
    if (matches.length === 0) return { value: raw, candidates: [] };
    if (matches.length === 1) return { value: leading + matches[0] + ' ', candidates: [] };
    return { value: leading + commonPrefix(matches), candidates: matches };
  }

  // Completing an argument to a known command.
  const cmd  = parts[0];
  const opts = completionsFor(cmd);
  if (!opts) return { value: raw, candidates: [] };

  const argToken = hasTrailingSpc ? '' : parts[parts.length - 1];
  const argHead  = parts.slice(1, hasTrailingSpc ? parts.length : -1).join(' ');
  const prefix   = leading + cmd + ' ' + (argHead ? argHead + ' ' : '');
  const matches  = opts.filter((o) => o.startsWith(argToken));
  if (matches.length === 0) return { value: raw, candidates: [] };
  if (matches.length === 1) return { value: prefix + matches[0], candidates: [] };
  return { value: prefix + commonPrefix(matches), candidates: matches };
}

// Tab-completion for the IRC input — only completes the leading slash command.
// Plain chat text is left untouched so Tab inside a sentence doesn't disturb it.
function completeIrcInput(raw) {
  const leading        = raw.match(/^\s*/)[0];
  const trimmed        = raw.trim();
  if (!trimmed.startsWith('/')) return { value: raw, candidates: [] };
  const hasTrailingSpc = /\s$/.test(raw);
  const parts          = trimmed.split(/\s+/);
  if (parts.length > 1 || hasTrailingSpc) return { value: raw, candidates: [] };

  const matches = IRC_COMMANDS.filter((c) => c.startsWith(parts[0]));
  if (matches.length === 0) return { value: raw, candidates: [] };
  if (matches.length === 1) return { value: leading + matches[0] + ' ', candidates: [] };
  return { value: leading + commonPrefix(matches), candidates: matches };
}

// Static "initial transcript". Stays at the top of the terminal. The user's
// typed commands append below this.

const PROMPT_VARIANTS = {
  dollar: '$',
  chevron: '›',
  caret:   '❯',
  lambda:  'λ',
};

function Prompt({ glyph, children, inline = false }) {
  return (
    <div className="bt-prompt" style={inline ? { padding: 0 } : null}>
      <span className="bt-prompt__user">bromb</span>
      <span className="bt-prompt__at">@</span>
      <span className="bt-prompt__host">dev</span>
      <span className="bt-prompt__colon">:</span>
      <span className="bt-prompt__path">~</span>
      <span className="bt-prompt__glyph">{glyph}</span>
      <span className="bt-prompt__cmd">{children}</span>
    </div>
  );
}

// ── Output renderers ─────────────────────────────────────────
function Out({ children, className = '' }) {
  return <pre className={`bt-out ${className}`}>{children}</pre>;
}

function HelpOutput() {
  return (
    <div className="bt-help">
      <div className="bt-help__head">commands</div>
      <table className="bt-help__table">
        <tbody>
          <tr><td>help</td><td>show this list</td></tr>
          <tr><td>whoami</td><td>about bromb</td></tr>
          <tr><td>ls</td><td>list projects</td></tr>
          <tr><td>cat <span className="dim">&lt;file&gt;</span></td><td><span className="dim">files:</span> about, now, shader-tutorial, tinyroguelike, colophon</td></tr>
          <tr><td>notes</td><td>recent notes</td></tr>
          <tr><td>contact</td><td>github, email, rss</td></tr>
          <tr><td>open <span className="dim">&lt;name&gt;</span></td><td>open a project / link in a new tab</td></tr>
          <tr><td>theme <span className="dim">&lt;color&gt;</span></td><td>peach · mint · cyan · magenta</td></tr>
          <tr><td>date</td><td>current time</td></tr>
          <tr><td>banner</td><td>print the wordmark</td></tr>
          <tr><td>irc</td><td>join <span className="dim">#lobby</span> — chat with other visitors</td></tr>
          <tr><td>clear</td><td>clear the screen</td></tr>
        </tbody>
      </table>
      <div className="bt-help__hint">↑/↓ recall · tab complete · click anywhere to focus the prompt</div>
    </div>
  );
}

// useIsCompact — true when viewport ≤ 720px. Subscribes to changes so the
// row swaps behavior live when you resize across the breakpoint.
function useIsCompact() {
  const [compact, setCompact] = React.useState(() =>
    typeof window !== 'undefined' &&
    window.matchMedia('(max-width: 720px)').matches
  );
  React.useEffect(() => {
    const mq = window.matchMedia('(max-width: 720px)');
    const onChange = (e) => setCompact(e.matches);
    mq.addEventListener('change', onChange);
    return () => mq.removeEventListener('change', onChange);
  }, []);
  return compact;
}

// ProjectRow — one ls-style row + its own floating preview tooltip.
// Desktop (≥721px): the whole row is the link; hover toggles the preview.
// Compact (≤720px): the row toggles the preview on tap; the "open" pill is
// the actual link (touch has no hover, and we want the preview reachable).
function ProjectRow({ project, previewMode }) {
  const [hovering, setHovering] = React.useState(false);
  const isCompact = useIsCompact();

  const isHoverMode = previewMode === 'hover';
  const previewActive =
    previewMode === 'always' ? true :
    previewMode === 'off'    ? false :
    hovering;
  const showPreview = previewActive;

  const isExternal = project.href.startsWith('http');
  const linkProps = {
    href: project.href,
    target: isExternal ? '_blank' : undefined,
    rel: isExternal ? 'noopener noreferrer' : undefined,
  };
  const blockInternal = (e) => { if (!isExternal) e.preventDefault(); };

  const hoverHandlers = (isHoverMode && !isCompact) ? {
    onMouseEnter: () => setHovering(true),
    onMouseLeave: () => setHovering(false),
    onFocus:      () => setHovering(true),
    onBlur:       () => setHovering(false),
  } : {};

  const useTapToggle = isCompact && isHoverMode;

  const rowInner = (
    <>
      <span className="bt-ls__perm">-rw-r--r--</span>
      <span className="bt-ls__owner">bromb</span>
      <span className="bt-ls__size">{project.size}</span>
      <span className="bt-ls__date">{project.date}</span>
      <span className="bt-ls__nameWrap">
        <span className="bt-ls__name">{project.file}</span>
        <span className={`bt-ls__status bt-ls__status--${project.status}`}>
          {project.status === 'live' ? '● live' : '◐ wip'}
        </span>
        {useTapToggle ? (
          <a
            className="bt-ls__action"
            {...linkProps}
            onClick={(e) => { e.stopPropagation(); blockInternal(e); }}
          >
            <span className="bt-ls__actionLabel">open</span>
            <span className="bt-ls__actionArrow">→</span>
          </a>
        ) : (
          <span className="bt-ls__action">
            <span className="bt-ls__actionLabel">open</span>
            <span className="bt-ls__actionArrow">→</span>
          </span>
        )}
      </span>
      {useTapToggle && (
        <span className="bt-ls__toggle" aria-hidden="true">[{showPreview ? '−' : '+'}]</span>
      )}
    </>
  );

  return (
    <div className="bt-ls__rowWrap">
      {useTapToggle ? (
        <div
          className="bt-ls__row"
          role="button"
          tabIndex={0}
          aria-expanded={showPreview}
          onClick={() => setHovering((v) => !v)}
          onKeyDown={(e) => {
            if (e.key === 'Enter' || e.key === ' ') {
              e.preventDefault();
              setHovering((v) => !v);
            }
          }}
        >
          {rowInner}
        </div>
      ) : (
        <a
          {...linkProps}
          className="bt-ls__row"
          onClick={blockInternal}
          {...hoverHandlers}
        >
          {rowInner}
        </a>
      )}

      {(() => {
        const floatInner = (
          <>
            <div className="bt-float__pointer" />
            <div className="bt-float__card">
              <div className="bt-float__frame">
                {previewActive
                  ? <ShaderPreview active={previewActive} variant={project.shader} />
                  : <ShaderStill />}
                <div className="bt-float__badge">
                  {previewActive ? '● running · webgl' : '○ paused'}
                </div>
              </div>
              <div className="bt-float__meta">
                <div className="bt-float__title">{project.title}</div>
                <div className="bt-float__sub">{project.subtitle}</div>
                <p className="bt-float__body">{project.summary}</p>
                <div className="bt-float__metaRow">
                  {project.meta.map((m, i) => (
                    <span key={i}>{i > 0 && <span className="dim"> · </span>}{m}</span>
                  ))}
                </div>
              </div>
            </div>
          </>
        );
        // On mobile, the expanded card itself is a link to the project — the
        // user has already "previewed", a second tap commits to navigation.
        return useTapToggle ? (
          <a
            className="bt-float bt-float--link"
            {...linkProps}
            data-open={showPreview}
            aria-hidden={!showPreview}
            tabIndex={showPreview ? 0 : -1}
            onClick={blockInternal}
          >
            {floatInner}
          </a>
        ) : (
          <div className="bt-float" data-open={showPreview} aria-hidden={!showPreview}>
            {floatInner}
          </div>
        );
      })()}
    </div>
  );
}

function LsOutput({ previewMode }) {
  const count = COPY.projects.length;
  return (
    <div className="bt-ls">
      <div className="bt-ls__head">
        total {count} · {count} project{count !== 1 ? 's' : ''} · more soon
      </div>
      <div className="bt-ls__list">
        {COPY.projects.map((p) => (
          <ProjectRow key={p.file} project={p} previewMode={previewMode} />
        ))}
      </div>
    </div>
  );
}

// ProjectsBlock — wrapper for the `ls` output. Kept as a separate component
// because runCommand('ls') from the live prompt also renders it, and we want
// the same hover-tooltip behavior in both places.
function ProjectsBlock({ tweaks }) {
  return <LsOutput previewMode={tweaks.preview} />;
}

function CatOutput({ file }) {
  if (file === 'about') {
    return (
      <Out>
{`# about.md

i'm bromb (/brɒm/)
${COPY.about.body}

→ cat now      what i'm working on
→ ls           projects
→ contact      get in touch`}
      </Out>
    );
  }
  if (file === 'now') {
    return (
      <Out>
{`# now.md · updated ${COPY.now.updated}

${COPY.now.bullets.map((b) => '- ' + b).join('\n')}`}
      </Out>
    );
  }
  if (file === 'shader-tutorial' || file === 'tinyroguelike') {
    const p = COPY.projects.find((x) => x.file.startsWith(file)) || COPY.projects[0];
    return (
      <Out>
{`# ${p.file}
## ${p.subtitle}

${p.summary}

${p.meta.join('  ·  ')}

→ open ${p.file.replace(/\.md$/, '')}`}
      </Out>
    );
  }
  if (file === 'colophon') {
    return (
      <Out>
{`# colophon

${COPY.colophon}`}
      </Out>
    );
  }
  return (
    <Out className="bt-err">
{`cat: ${file || ''}: No such file or directory
try: cat about · cat now · cat shader-tutorial · cat tinyroguelike · cat colophon`}
    </Out>
  );
}

function NotesOutput() {
  return (
    <div className="bt-notes">
      <div className="bt-notes__head">recent notes · {COPY.notes.length} shown of {COPY.notes.length + 6}</div>
      <ul>
        {COPY.notes.map((n, i) => (
          <li key={i} className="bt-notes__row">
            <span className="bt-notes__date">{n.date}</span>
            <span className="bt-notes__min">{String(n.minutes).padStart(2, ' ')}min</span>
            <a className="bt-notes__title" href="#" onClick={(e) => e.preventDefault()}>{n.title}</a>
          </li>
        ))}
      </ul>
      <div className="bt-notes__more">→ archive  for the rest</div>
    </div>
  );
}

function ContactOutput() {
  return (
    <div className="bt-contact">
      {COPY.contact.map((c) => (
        <div key={c.label} className="bt-contact__row">
          <span className="bt-contact__label">{c.label.padEnd(8, ' ')}</span>
          <span className="dim">→</span>
          <a href={c.href} onClick={(e) => e.preventDefault()}>{c.value}</a>
        </div>
      ))}
    </div>
  );
}

function BannerOutput() {
  return (
    <pre className="bt-banner">{String.raw`
  ██████╗ ██████╗  ██████╗ ███╗   ███╗██████╗
  ██╔══██╗██╔══██╗██╔═══██╗████╗ ████║██╔══██╗
  ██████╔╝██████╔╝██║   ██║██╔████╔██║██████╔╝
  ██╔══██╗██╔══██╗██║   ██║██║╚██╔╝██║██╔══██╗
  ██████╔╝██║  ██║╚██████╔╝██║ ╚═╝ ██║██████╔╝
  ╚═════╝ ╚═╝  ╚═╝ ╚═════╝ ╚═╝     ╚═╝╚═════╝
                                  v0.4.1
`}</pre>
  );
}

// ── shell engine ─────────────────────────────────────────────
function runCommand(raw, { setTweak, setLines, clear }) {
  const trimmed = raw.trim();
  if (!trimmed) return null;
  const [cmd, ...args] = trimmed.split(/\s+/);
  const a = args[0];

  switch (cmd) {
    case 'help':    return { kind: 'help' };
    case 'whoami':  return { kind: 'cat', file: 'about' };
    case 'ls':      return { kind: 'ls' };
    case 'la':      return { kind: 'ls' };
    case 'cat':     return { kind: 'cat', file: a };
    case 'notes':   return { kind: 'notes' };
    case 'contact': return { kind: 'contact' };
    case 'date':    return { kind: 'text', text: new Date().toString() };
    case 'banner':  return { kind: 'banner' };
    case 'clear':   clear(); return null;
    case 'theme': {
      if (!a) return { kind: 'err', text: 'theme: missing color. try: theme peach|mint|cyan|magenta' };
      const ok = ['peach', 'mint', 'cyan', 'magenta'].includes(a);
      if (!ok)  return { kind: 'err', text: `theme: '${a}' unknown. try: peach · mint · cyan · magenta` };
      setTweak('accent', a);
      return { kind: 'text', text: `theme: → ${a}` };
    }
    case 'open': {
      const name = a || 'shader-tutorial';
      return { kind: 'text', text: `open: ${name} (link would open in a new tab in production)` };
    }
    case 'sudo':    return { kind: 'err', text: `sudo: permission denied (you are not in the sudoers file. this incident will be reported.)` };
    case 'irc':     return { kind: 'irc-enter' };
    case 'rm':      return { kind: 'err', text: `rm: nice try` };
    case 'vim':     return { kind: 'err', text: `vim: this is a read-only shell. try :q` };
    case ':q':      return { kind: 'text', text: 'phew.' };
    case 'exit':    return { kind: 'text', text: 'you can close the tab.' };
    default:        return { kind: 'err', text: `${cmd}: command not found. type "help".` };
  }
}

// ── IRC mode ─────────────────────────────────────────────────
// Mounts an active chat session. Owns its own input and irc client; when
// /quit (or /exit) is typed, calls onExit() which flips the parent back to
// shell mode and tears this component down — the irc hook cleanup closes
// the socket. Lives inline above the parent's status bar.
function IrcSession({ url, promptGlyph, onExit, scrollRef, isCompact }) {
  const irc = useIrc({ url, channel: '#lobby' });
  const [input, setInput] = React.useState('');
  const inputRef = React.useRef(null);

  React.useEffect(() => {
    if (isCompact) return;
    if (inputRef.current) inputRef.current.focus({ preventScroll: true });
  }, [isCompact]);

  // auto-scroll on new chat messages, mirroring the shell's lines effect.
  const prevCount = React.useRef(irc.messages.length);
  React.useEffect(() => {
    if (irc.messages.length > prevCount.current) {
      requestAnimationFrame(() => {
        if (scrollRef.current) {
          scrollRef.current.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
        }
      });
    }
    prevCount.current = irc.messages.length;
  }, [irc.messages.length, scrollRef]);

  function handle(trimmed) {
    if (trimmed === '/quit' || trimmed === '/exit') {
      irc.disconnect();
      onExit();
      return;
    }
    if (trimmed.startsWith('/nick ')) {
      const to = trimmed.slice(6).trim();
      if (!to) { irc.pushLocal('usage: /nick <name>', 'warn'); return; }
      irc.setNick(to);
      return;
    }
    if (trimmed.startsWith('/me ')) {
      const text = trimmed.slice(4).trim();
      if (!text) return;
      irc.sendAction(text);
      return;
    }
    if (trimmed === '/who') {
      const list = irc.presence.length ? irc.presence.join(', ') : '—';
      irc.pushLocal(`online (${irc.presence.length}): ${list}`);
      return;
    }
    if (trimmed === '/help' || trimmed === '/?') {
      irc.pushLocal('commands: /nick <name> · /me <action> · /who · /help · /quit');
      return;
    }
    if (trimmed.startsWith('/')) {
      irc.pushLocal(`unknown command: ${trimmed.split(' ')[0]} — try /help`, 'warn');
      return;
    }
    irc.send(trimmed);
  }

  function submit(e) {
    e.preventDefault();
    const raw = input;
    setInput('');
    const trimmed = raw.trim();
    if (!trimmed) return;
    handle(trimmed);
  }

  function onKey(e) {
    if (e.key !== 'Tab') return;
    e.preventDefault();
    const { value, candidates } = completeIrcInput(input);
    if (value !== input) setInput(value);
    if (candidates.length > 1) irc.pushLocal(candidates.join('  '));
  }

  return (
    <>
      <IrcView irc={irc} />
      <form className="bt-livePrompt bt-livePrompt--irc" onSubmit={submit}>
        <span className="bt-prompt__user">{irc.nick}</span>
        <span className="bt-prompt__at">@</span>
        <span className="bt-prompt__host">bromb</span>
        <span className="bt-prompt__colon">:</span>
        <span className="bt-prompt__path">{irc.channel}</span>
        <span className="bt-prompt__glyph">{promptGlyph}</span>
        <div className="bt-inputWrap" data-value={input}>
          <input
            ref={inputRef}
            className="bt-input"
            value={input}
            size={1}
            onChange={(e) => setInput(e.target.value)}
            onKeyDown={onKey}
            spellCheck={false}
            autoComplete="off"
            autoCapitalize="off"
            placeholder="say something — /help for commands"
          />
        </div>
        <span className="bt-caret">▌</span>
      </form>
    </>
  );
}

// ── main component ───────────────────────────────────────────
function TerminalLanding({ tweaks, setTweak }) {
  const [input, setInput]       = React.useState('');
  const [lines, setLines]       = React.useState([]); // dynamic entries from user
  const [history, setHistory]   = React.useState([]);
  const [hIdx, setHIdx]         = React.useState(-1);
  const [mode, setMode]         = React.useState('shell'); // 'shell' | 'irc'
  const inputRef = React.useRef(null);
  const scrollRef = React.useRef(null);
  const isCompact = useIsCompact();

  const promptGlyph = PROMPT_VARIANTS[tweaks.prompt] || '$';
  const chrome      = tweaks.chrome || 'mac';

  // focus prompt on any click in the terminal body. preventScroll avoids the
  // browser pulling the input into view (which is what caused initial autoscroll
  // to bottom on load). Disabled on mobile so tapping the page doesn't pop
  // the soft keyboard — the shell is display-only there.
  function focusPrompt() {
    if (inputRef.current) inputRef.current.focus({ preventScroll: true });
  }

  React.useEffect(() => {
    if (isCompact) return;
    if (mode !== 'shell') return;
    if (inputRef.current) inputRef.current.focus({ preventScroll: true });
  }, [isCompact, mode]);

  // scroll to the bottom whenever the user adds a NEW line (skip initial mount
  // so the page opens at the top showing the welcome/projects).
  const prevLines = React.useRef(lines.length);
  React.useEffect(() => {
    if (lines.length > prevLines.current) {
      requestAnimationFrame(() => {
        if (scrollRef.current) {
          scrollRef.current.scrollTo({ top: scrollRef.current.scrollHeight, behavior: 'smooth' });
        }
      });
    }
    prevLines.current = lines.length;
  }, [lines.length]);

  function submit(e) {
    e.preventDefault();
    const raw = input;
    const result = runCommand(raw, {
      setTweak,
      setLines,
      clear: () => setLines([]),
    });
    if (result || raw.trim()) {
      setLines((prev) => [...prev, { cmd: raw, result }]);
    }
    if (result && result.kind === 'irc-enter') setMode('irc');
    if (raw.trim()) {
      setHistory((h) => [...h, raw]);
      setHIdx(-1);
    }
    setInput('');
  }

  function onKey(e) {
    if (e.key === 'ArrowUp') {
      e.preventDefault();
      if (!history.length) return;
      const next = hIdx < 0 ? history.length - 1 : Math.max(0, hIdx - 1);
      setHIdx(next); setInput(history[next] || '');
    } else if (e.key === 'ArrowDown') {
      e.preventDefault();
      if (hIdx < 0) return;
      const next = hIdx + 1;
      if (next >= history.length) { setHIdx(-1); setInput(''); }
      else { setHIdx(next); setInput(history[next]); }
    } else if (e.key === 'Tab') {
      e.preventDefault();
      const { value, candidates } = completeInput(input);
      if (value !== input) setInput(value);
      if (candidates.length > 1) {
        setLines((prev) => [...prev, { cmd: null, result: { kind: 'completions', items: candidates } }]);
      }
    } else if (e.key === 'l' && (e.ctrlKey || e.metaKey)) {
      e.preventDefault();
      setLines([]);
    }
  }

  function renderResult(r) {
    if (!r) return null;
    switch (r.kind) {
      case 'help':    return <HelpOutput />;
      case 'ls':      return <ProjectsBlock tweaks={tweaks} />;
      case 'cat':     return <CatOutput file={r.file} />;
      case 'notes':   return <NotesOutput />;
      case 'contact': return <ContactOutput />;
      case 'banner':  return <BannerOutput />;
      case 'text':    return <Out>{r.text}</Out>;
      case 'err':     return <Out className="bt-err">{r.text}</Out>;
      case 'irc-enter': return <Out>connecting to #lobby — type /help for commands, /quit to leave</Out>;
      case 'completions': return <Out className="dim">{r.items.join('  ')}</Out>;
      default:        return null;
    }
  }

  return (
    <div className={`bt-page bt-page--${chrome}`} data-screen-label="bromb.dev terminal">
      <div className={`bt-window bt-window--${chrome}`}>
        {chrome !== 'none' && (
          <div className={`bt-chrome bt-chrome--${chrome}`}>
            {chrome === 'mac' && (
              <>
                <div className="bt-dots">
                  <span /><span /><span />
                </div>
                <div className="bt-chrome__title">
                  bromb@dev — zsh — bromb.dev — 96×42
                </div>
                <div className="bt-chrome__right">⌘T  ⌘N</div>
              </>
            )}
            {chrome === 'linux' && (
              <>
                <div className="bt-chrome__title bt-chrome__title--left">
                  bromb@dev:~ — /bin/zsh
                </div>
                <div className="bt-chrome__right bt-chrome__right--linux">
                  <span>—</span><span>▢</span><span>×</span>
                </div>
              </>
            )}
            {chrome === 'minimal' && (
              <div className="bt-chrome__title bt-chrome__title--left">
                <span className="bt-chrome__dot ok">●</span> bromb.dev — read-only public shell
              </div>
            )}
          </div>
        )}

        <div className="bt-scroll" ref={scrollRef} onClick={isCompact ? undefined : focusPrompt}>
          <div className="bt-body">
            {/* ── boot banner ── */}
            <div className="bt-line dim">Last login: Mon May 18 19:42:11 on ttys003</div>
            <div className="bt-line dim">
              Welcome to <span style={{ color: 'var(--accent)' }}>bromb.dev</span> — public read-only shell.
              {!isCompact && <> Type <code>help</code> for commands, or just scroll.</>}
            </div>
            <div className="bt-sp" />

            {/* ── pre-rendered session ── */}
            <Prompt glyph={promptGlyph}>ls -lah projects/</Prompt>
            <ProjectsBlock tweaks={tweaks} />

            <Prompt glyph={promptGlyph}>contact --all</Prompt>
            <ContactOutput />

            {/* ── user session (dynamic) ── */}
            {lines.map((line, i) => (
              <React.Fragment key={i}>
                {line.cmd != null && <Prompt glyph={promptGlyph}>{line.cmd}</Prompt>}
                {renderResult(line.result)}
              </React.Fragment>
            ))}

            {/* ── live prompt ──
                Shell mode is desktop-only (no input on mobile). IRC mode shows
                the input on mobile too — chat is the one feature where typing
                on a phone makes sense. */}
            {mode === 'irc' ? (
              <IrcSession
                url={typeof window !== 'undefined' ? window.IRC_URL : null}
                promptGlyph={promptGlyph}
                scrollRef={scrollRef}
                isCompact={isCompact}
                onExit={() => {
                  setMode('shell');
                  setLines((prev) => [...prev, { cmd: '', result: { kind: 'text', text: 'disconnected from #lobby.' } }]);
                }}
              />
            ) : (!isCompact && (
              <form className="bt-livePrompt" onSubmit={submit}>
                <span className="bt-prompt__user">bromb</span>
                <span className="bt-prompt__at">@</span>
                <span className="bt-prompt__host">dev</span>
                <span className="bt-prompt__colon">:</span>
                <span className="bt-prompt__path">~</span>
                <span className="bt-prompt__glyph">{promptGlyph}</span>
                <div className="bt-inputWrap" data-value={input}>
                  <input
                    ref={inputRef}
                    className="bt-input"
                    value={input}
                    size={1}
                    onChange={(e) => setInput(e.target.value)}
                    onKeyDown={onKey}
                    spellCheck={false}
                    autoComplete="off"
                    autoCapitalize="off"
                  />
                </div>
                <span className="bt-caret">▌</span>
              </form>
            ))}

            {/* ── status bar ── */}
            <div className="bt-statusbar">
              <span><span className="bt-statusdot ok">●</span> online</span>
              <span className="dim">·</span>
              <span>type <code>help</code></span>
              <span className="dim">·</span>
              <span>↑/↓ history</span>
              <span className="dim">·</span>
              <span>tab to complete</span>
              <span className="bt-statusspacer" />
              <span>{COPY.colophon}</span>
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

window.TerminalLanding = TerminalLanding;
