/* runtime.jsx — shared React runtime
   Exports to window:
     useI18n, I18nProvider
     useToasts, ToastStack
     Boot, RouteLoader, CommandBar, MatrixOverlay
     useClock, useUptime, useVisitorN
     useStageSequence
     PAGES, PAGE_LABEL, PAGE_FILE
*/
const { useState, useEffect, useRef, useCallback, useMemo, useContext, createContext } = React;

/* ============================================
   PAGE MAP
   ============================================ */
const PAGES = ['home', 'about', 'education', 'skills', 'experience', 'projects', 'contact'];
const PAGE_LABEL = {
  home: 'home', about: 'about', education: 'education',
  skills: 'skills', experience: 'experience', projects: 'projects', contact: 'contact',
};
const PAGE_FILE = {
  home: 'home.sh', about: 'about.md', education: 'education/',
  skills: 'skills.json', experience: 'experience.log',
  projects: 'projects/', contact: 'contact.sh',
};
const PAGE_PROMPT_PATH = {
  home: '~', about: '~/about', education: '~/education',
  skills: '~/skills', experience: '~/career',
  projects: '~/projects', contact: '~',
};

/* ============================================
   ROUTING (folder-based, no hash)
   - Production: /  → home, /about/ → about, etc.
   - Preview:    /projects/<id>/        → home
                 /projects/<id>/about/  → about
   ============================================ */
function getPathSegs() {
  const segs = window.location.pathname.split('/').filter(Boolean);
  if (segs[segs.length - 1] === 'index.html') segs.pop();
  return segs;
}
function pageFromPath() {
  const segs = getPathSegs();
  const last = segs[segs.length - 1] || 'home';
  return PAGES.includes(last) ? last : 'home';
}
function getBasePath() {
  const segs = getPathSegs();
  if (segs.length && PAGES.includes(segs[segs.length - 1])) segs.pop();
  return '/' + (segs.length ? segs.join('/') + '/' : '');
}
function pageToPath(page) {
  const base = getBasePath();
  return page === 'home' ? base : base + page + '/';
}

const NavContext = createContext(null);
function useNavigate() { return useContext(NavContext); }

function Link({ to, children, className, ...rest }) {
  const navigate = useNavigate();
  function onClick(e) {
    if (e.button !== 0 || e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
    e.preventDefault();
    navigate(to);
  }
  return (
    <a href={pageToPath(to)} className={className} onClick={onClick} {...rest}>
      {children}
    </a>
  );
}

/* ============================================
   I18N
   ============================================ */
const I18nContext = createContext(null);
function I18nProvider({ children }) {
  const [lang, setLangState] = useState(() => localStorage.getItem('portfolio.lang') || 'es');
  const setLang = useCallback((l) => {
    if (l !== 'es' && l !== 'en') return;
    setLangState(l);
    localStorage.setItem('portfolio.lang', l);
  }, []);
  const t = useCallback((key) => {
    const dict = window.I18N?.[lang] || {};
    return dict[key] ?? window.I18N?.es?.[key] ?? key;
  }, [lang]);
  useEffect(() => { document.documentElement.lang = lang; }, [lang]);
  const value = useMemo(() => ({ lang, t, setLang }), [lang, t, setLang]);
  return <I18nContext.Provider value={value}>{children}</I18nContext.Provider>;
}
function useI18n() { return useContext(I18nContext); }

/* ============================================
   CLOCK + UPTIME + VISITOR
   ============================================ */
function useClock() {
  const [time, setTime] = useState(() => formatTime(new Date()));
  useEffect(() => {
    const id = setInterval(() => setTime(formatTime(new Date())), 1000);
    return () => clearInterval(id);
  }, []);
  return time;
}
function formatTime(d) {
  return [d.getHours(), d.getMinutes(), d.getSeconds()].map(x => String(x).padStart(2, '0')).join(':');
}

function useUptime() {
  // Reset on every page load — measures time spent on this view session.
  const startRef = useRef(Date.now());
  const [val, setVal] = useState('0m 00s');
  useEffect(() => {
    function tick() {
      const s = Math.floor((Date.now() - startRef.current) / 1000);
      const m = Math.floor(s / 60);
      setVal(`${m}m ${String(s % 60).padStart(2, '0')}s`);
    }
    tick();
    const id = setInterval(tick, 1000);
    return () => clearInterval(id);
  }, []);
  return val;
}

function useVisitorN() {
  // Global cross-visitor counter — calls your own backend endpoint.
  // Each browser may increment exactly once (localStorage flag).
  // Returning browsers do a GET to read the current count without incrementing.
  // Expected response shape: { count: <number> }
  const COUNTER_ENDPOINT = '/api/counter';
  const COUNTED_KEY = 'portfolio.visits.counted'; // permanent per-browser flag

  const cached = +sessionStorage.getItem('portfolio.visits.global.session') || 0;
  const [n, setN] = useState(cached);

  useEffect(() => {
    if (cached) return; // already loaded this tab session
    let cancelled = false;
    const alreadyCounted = localStorage.getItem(COUNTED_KEY) === '1';

    async function load() {
      try {
        const ctrl = new AbortController();
        setTimeout(() => ctrl.abort(), 3500);
        const method = alreadyCounted ? 'GET' : 'POST';
        const r = await fetch(COUNTER_ENDPOINT, { method, signal: ctrl.signal });
        if (!r.ok) throw new Error('http ' + r.status);
        const d = await r.json();
        const v = d?.count ?? d?.value ?? null;
        if (!cancelled && Number.isFinite(v)) {
          if (!alreadyCounted) localStorage.setItem(COUNTED_KEY, '1');
          setN(v);
          sessionStorage.setItem('portfolio.visits.global.session', v);
          localStorage.setItem('portfolio.visits.global', v);
          return;
        }
        throw new Error('bad response');
      } catch (e) {
        // fallback: last known global count from cache
        const last = +localStorage.getItem('portfolio.visits.global') || 0;
        if (!cancelled && last > 0) {
          setN(last);
          sessionStorage.setItem('portfolio.visits.global.session', last);
        } else if (!cancelled) {
          // no cache yet — use a local per-browser tally so something shows
          let local = +localStorage.getItem('portfolio.visits') || 0;
          if (!alreadyCounted) {
            local += 1;
            localStorage.setItem('portfolio.visits', local);
            localStorage.setItem(COUNTED_KEY, '1');
          }
          setN(local || 1);
          sessionStorage.setItem('portfolio.visits.global.session', local || 1);
        }
      }
    }
    load();
    return () => { cancelled = true; };
  }, []);

  return n;
}

/* ============================================
   TOAST SYSTEM
   ============================================ */
const ToastContext = createContext(null);
function useToasts() { return useContext(ToastContext); }

function ToastProvider({ children }) {
  const [toasts, setToasts] = useState([]);
  const push = useCallback((html, kind = '', ms = 4200) => {
    const id = Math.random().toString(36).slice(2);
    setToasts(prev => [...prev, { id, html, kind, fading: false }]);
    setTimeout(() => {
      setToasts(prev => prev.map(t => t.id === id ? { ...t, fading: true } : t));
      setTimeout(() => setToasts(prev => prev.filter(t => t.id !== id)), 300);
    }, ms);
  }, []);
  const clear = useCallback(() => setToasts([]), []);
  const value = useMemo(() => ({ push, clear }), [push, clear]);
  return (
    <ToastContext.Provider value={value}>
      {children}
      <ToastStack toasts={toasts} />
    </ToastContext.Provider>
  );
}

function ToastStack({ toasts }) {
  return (
    <div className="toast-stack">
      {toasts.map(t => (
        <div key={t.id} className={`toast ${t.kind} ${t.fading ? 'fade-out' : ''}`} dangerouslySetInnerHTML={{ __html: t.html }} />
      ))}
    </div>
  );
}

/* ============================================
   BOOT OVERLAY
   ============================================ */
function Boot() {
  const [show, setShow] = useState(() => !sessionStorage.getItem('booted'));
  const [gone, setGone] = useState(false);
  useEffect(() => {
    if (!show) return;
    sessionStorage.setItem('booted', '1');
    const lines = document.querySelectorAll('.boot .b-line');
    lines.forEach((l, i) => { l.style.animationDelay = `${i * 80}ms`; });
    const t1 = setTimeout(() => setGone(true), Math.max(1400, lines.length * 80 + 600));
    const t2 = setTimeout(() => setShow(false), Math.max(1400, lines.length * 80 + 600) + 500);
    return () => { clearTimeout(t1); clearTimeout(t2); };
  }, [show]);
  if (!show) return null;
  return (
    <div className={`boot ${gone ? 'gone' : ''}`}>
      <div className="b-line b-info">[ portfolio.os v1.0.0 / kernel: david-5.18.2 ]</div>
      <div className="b-line b-dim">BIOS POST ........................................... [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-dim">mounting /home/david ................................ [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-dim">loading bash 5.2 / zsh-5.9 .......................... [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-dim">checking projects ................................... [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-dim">scanning skills ..................................... [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-dim">connecting github.com/DavidFB-creator ............... [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-dim">starting portfolio.service .......................... [<span className="b-ok">OK</span>]</div>
      <div className="b-line b-warn">tip: type 'help' in the command bar at the bottom.</div>
      <div className="b-line b-info">&gt; login as david_<span className="caret"></span></div>
    </div>
  );
}

/* ============================================
   ROUTE LOADER SPLASH
   ============================================ */
function RouteLoader({ target }) {
  if (!target) return null;
  const file = PAGE_FILE[target] || target;
  return (
    <div className="route-load on" key={target}>
      <div className="rl-card" role="status" aria-live="polite">
        <div className="rl-prompt">
          <span className="u">david</span><span className="d">@</span><span className="h">fontanet</span><span className="d">:~ $ </span><span className="cmd">cd ~/{target}</span>
        </div>
        <div className="rl-status">
          <span className="badge">LOAD</span><span>mounting <span className="target">{file}</span> ...</span>
        </div>
        <div className="rl-bar"><i></i></div>
        <div className="rl-meta"><span>route: /{target}</span><span className="ok">[ OK ]</span></div>
      </div>
    </div>
  );
}

/* ============================================
   MATRIX OVERLAY
   ============================================ */
function MatrixOverlay({ on, onExit }) {
  const canvasRef = useRef(null);
  useEffect(() => {
    if (!on) return;
    const canvas = canvasRef.current;
    if (!canvas) return;
    const ctx = canvas.getContext('2d');
    function size() { canvas.width = innerWidth; canvas.height = innerHeight; }
    size();
    window.addEventListener('resize', size);
    const chars = 'アァイィウヴエカキクケコサシスセソタチツテトナニヌネノハヒフヘホマミムメモヤヨラリルレロワヲン0123456789{}[]<>=#$&*+';
    const fontSize = 16;
    const cols = Math.floor(innerWidth / fontSize);
    const drops = Array.from({ length: cols }, () => Math.random() * -50);
    let raf;
    function draw() {
      ctx.fillStyle = 'rgba(5, 8, 13, 0.08)';
      ctx.fillRect(0, 0, canvas.width, canvas.height);
      ctx.font = fontSize + 'px JetBrains Mono, monospace';
      for (let i = 0; i < drops.length; i++) {
        const c = chars[Math.floor(Math.random() * chars.length)];
        const x = i * fontSize;
        const y = drops[i] * fontSize;
        ctx.fillStyle = y < fontSize * 2 ? '#e6edf7' : '#00d4ff';
        ctx.fillText(c, x, y);
        if (y > canvas.height && Math.random() > 0.975) drops[i] = 0;
        drops[i] += 0.6 + Math.random() * 0.4;
      }
      raf = requestAnimationFrame(draw);
    }
    draw();
    function onKey(e) { if (e.key === 'Escape') onExit(); }
    document.addEventListener('keydown', onKey);
    return () => {
      cancelAnimationFrame(raf);
      window.removeEventListener('resize', size);
      document.removeEventListener('keydown', onKey);
    };
  }, [on, onExit]);
  return (
    <>
      <canvas ref={canvasRef} className={`matrix-canvas ${on ? 'on' : ''}`}></canvas>
      {on && <div className="matrix-exit-hint">press Esc to exit</div>}
    </>
  );
}

/* ============================================
   TYPEWRITER + STAGE SEQUENCE
   ============================================ */
function typewriter(el, opts = {}) {
  const speed = opts.speed ?? 16;
  const jitter = opts.jitter ?? 10;
  const walker = document.createTreeWalker(el, NodeFilter.SHOW_TEXT, {
    acceptNode(n) {
      const p = n.parentElement;
      if (!p || p.closest('script, style')) return NodeFilter.FILTER_REJECT;
      return NodeFilter.FILTER_ACCEPT;
    }
  });
  const nodes = [];
  let n;
  while ((n = walker.nextNode())) {
    nodes.push({ node: n, full: n.textContent });
    n.textContent = '';
  }
  el.classList.add('tw-active');
  let aborted = false;
  function abort() {
    if (aborted) return;
    aborted = true;
    nodes.forEach(({ node, full }) => { node.textContent = full; });
    el.classList.remove('tw-active');
    el.classList.add('tw-done');
  }
  const promise = new Promise(resolve => {
    let i = 0, ci = 0;
    function tick() {
      if (aborted) { resolve(); return; }
      if (i >= nodes.length) {
        el.classList.remove('tw-active');
        el.classList.add('tw-done');
        resolve();
        return;
      }
      const { node, full } = nodes[i];
      if (ci < full.length) {
        node.textContent += full[ci];
        const ch = full[ci];
        ci++;
        let d = speed + (Math.random() * jitter - jitter / 2);
        if (ch === ' ') d *= 0.55;
        setTimeout(tick, Math.max(5, d));
      } else {
        i++; ci = 0;
        setTimeout(tick, speed);
      }
    }
    tick();
  });
  return { promise, abort };
}

function useStageSequence(ref, key) {
  useEffect(() => {
    const wrapper = ref.current;
    if (!wrapper) return;
    if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;

    // The ref wraps the page component; the actual stage is .content-inner
    const stage = wrapper.querySelector('.content-inner') || wrapper;
    stage.classList.add('tw-stage');
    const children = [...stage.children];
    children.forEach(c => c.classList.add('tw-hidden'));

    let aborted = false;
    const aborts = [];

    function revealAll() {
      stage.querySelectorAll('.tw-hidden').forEach(el => {
        el.classList.remove('tw-hidden');
        el.classList.add('tw-shown');
      });
      aborts.forEach(fn => fn());
    }
    function abortSeq() {
      if (aborted) return;
      aborted = true;
      revealAll();
    }
    function onSkip(e) {
      if (e.type === 'keydown') {
        const ae = document.activeElement;
        if (ae && ['INPUT', 'TEXTAREA'].includes(ae.tagName)) return;
        if (['Control', 'Meta', 'Shift', 'Alt'].includes(e.key)) return;
      }
      abortSeq();
    }
    document.addEventListener('keydown', onSkip, true);
    stage.addEventListener('click', onSkip);

    (async () => {
      await new Promise(r => setTimeout(r, 60));
      for (const el of children) {
        if (aborted) break;
        el.classList.remove('tw-hidden');
        if (el.classList.contains('prompt-line')) {
          const tw = typewriter(el, { speed: 14, jitter: 8 });
          aborts.push(tw.abort);
          await tw.promise;
          await new Promise(r => setTimeout(r, 110));
        } else if (el.classList.contains('comment')) {
          const tw = typewriter(el, { speed: 12, jitter: 6 });
          aborts.push(tw.abort);
          await tw.promise;
          await new Promise(r => setTimeout(r, 140));
        } else if (el.classList.contains('ascii-name')) {
          el.classList.add('tw-shown', 'tw-scan');
          await new Promise(r => setTimeout(r, 700));
        } else if (el.tagName === 'H1') {
          el.classList.add('tw-shown');
          await new Promise(r => setTimeout(r, 180));
        } else {
          el.classList.add('tw-shown');
          await new Promise(r => setTimeout(r, 70));
        }
      }
    })();

    return () => {
      abortSeq();
      document.removeEventListener('keydown', onSkip, true);
      stage.removeEventListener('click', onSkip);
    };
  }, [key]);
}

/* ============================================
   COMMAND BAR
   ============================================ */
const HELP_CMDS = [
  ['help', 'esta lista / show this list'],
  ['ls', 'listar páginas disponibles'],
  ['cd &lt;page&gt;', 'navegar — p.ej. cd about'],
  ['whoami', '¿quién eres?'],
  ['lang es|en', 'cambiar idioma'],
  ['clear', 'limpiar toasts'],
  ['matrix · hire · sudo · vim · coffee', '...try them'],
];

function CommandBar({ pathLabel, onNavigate, onMatrix }) {
  const { t, lang, setLang } = useI18n();
  const toasts = useToasts();
  const inputRef = useRef(null);
  const histRef = useRef(JSON.parse(localStorage.getItem('cmd.history') || '[]'));
  const idxRef = useRef(histRef.current.length);
  const [value, setValue] = useState('');
  // tab-completion state
  const [suggestions, setSuggestions] = useState([]); // array of strings
  const cycleRef = useRef(0);
  const cyclingRef = useRef(false); // true while user is Tab-ing through matches
  const cycleModeRef = useRef('cd'); // 'cd' | 'cmd'

  function run(raw) {
    const cmd = raw.trim();
    if (!cmd) return;
    const lc = cmd.toLowerCase();
    const parts = lc.split(/\s+/);
    const [head, ...rest] = parts;

    if (head === 'cd' || PAGES.includes(head) || head === 'exp' || head === 'proj' || head === 'edu' || head === 'work' || head === 'stack') {
      const aliasMap = { exp: 'experience', work: 'experience', proj: 'projects', edu: 'education', stack: 'skills' };
      const target = head === 'cd' ? rest[0] : (aliasMap[head] || head);
      if (target === '..' || target === '/' || target === '~') { onNavigate('home'); return; }
      if (PAGES.includes(target)) { onNavigate(target); return; }
      const mapped = aliasMap[target];
      if (mapped) { onNavigate(mapped); return; }
      toasts.push(`<span class="muted">cd: ${target || ''}: </span><span class="error">no such directory</span>`, 'error');
      return;
    }
    if (head === 'ls') {
      const list = PAGES;
      toasts.push(`<span class="muted">$ ls</span><br>${list.map(x => `<span class="neon">${x}/</span>`).join('  ')}`);
      return;
    }
    if (head === 'help') {
      const rows = HELP_CMDS.map(([k, v]) =>
        `<div class="help-row"><span class="help-k">${k}</span><span class="help-v">${v}</span></div>`
      ).join('');
      toasts.push(`<div class="label">${t('ee.help.title')}</div><div class="help-grid">${rows}</div>`, '', 8000);
      return;
    }
    if (head === 'whoami') {
      const n = +sessionStorage.getItem('portfolio.visits.global.session') || +localStorage.getItem('portfolio.visits.global') || 1;
      toasts.push(`<span class="neon">${t('ee.whoami')}${n}</span>`);
      return;
    }
    if (head === 'date') {
      toasts.push(`<span class="muted">${t('ee.date')}</span> <span class="bright">${new Date().toString()}</span>`);
      return;
    }
    if (head === 'uname') {
      toasts.push(`<span class="bright">${t('ee.uname')}</span>`);
      return;
    }
    if (head === 'fortune') {
      const arr = t('ee.fortune') || [];
      const pick = arr[Math.floor(Math.random() * arr.length)];
      toasts.push(`<span class="magenta">~</span> ${pick}`);
      return;
    }
    if (head === 'lang') {
      const target = rest[0];
      if (target === 'es' || target === 'en') { setLang(target); toasts.push(`<span class="success">lang → ${target}</span>`, 'success', 1800); return; }
      toasts.push(`<span class="muted">usage: lang es|en</span>`);
      return;
    }
    if (head === 'sudo') {
      toasts.push(`<span class="warn">[sudo]</span> ${t('ee.sudo')}`, 'warn');
      return;
    }
    if (lc.startsWith('rm -rf')) {
      const lines = ['rm: removing /bin...', 'rm: removing /etc...', 'rm: removing /home/david...', 'rm: PANIC: filesystem corrupted'];
      lines.forEach((l, i) => setTimeout(() => toasts.push(`<span class="error">${l}</span>`, 'error', 1400), i * 320));
      setTimeout(() => toasts.push(`<span class="success">${t('ee.rm')}</span>`, 'success', 4200), lines.length * 320 + 200);
      return;
    }
    if (head === 'vim' || head === 'vi' || head === 'nvim') {
      toasts.push(`<span class="warn">${t('ee.vim')}</span>`, 'warn');
      return;
    }
    if (head === 'coffee') {
      toasts.push(`${t('ee.coffee')}`, 'warn');
      return;
    }
    if (head === 'hire' || head === 'hireme' || head === 'hire-me') {
      toasts.push(`<span class="success">${t('ee.hire')}</span>`, 'success', 5200);
      setTimeout(() => onNavigate('contact'), 1800);
      return;
    }
    if (head === 'matrix') {
      toasts.push(`<span class="neon">${t('ee.matrix')}</span>`, '', 2000);
      setTimeout(onMatrix, 600);
      return;
    }
    if (head === 'clear' || head === 'cls') { toasts.clear(); return; }
    if (head === 'theme' || head === 'glitch') {
      document.documentElement.classList.add('glitch');
      setTimeout(() => document.documentElement.classList.remove('glitch'), 1800);
      toasts.push(`<span class="neon">// reticulating splines...</span>`);
      return;
    }
    if (head === 'echo') {
      toasts.push(`<span class="bright">${rest.join(' ') || ''}</span>`);
      return;
    }
    if (head === 'exit' || head === 'logout') {
      toasts.push(`<span class="muted">nice try — there is no exit. <span class="dim">(this is the web)</span></span>`);
      return;
    }
    toasts.push(`<span class="error">${cmd}: ${t('ee.unknown')}</span>`, 'error');
  }

  function onKeyDown(e) {
    if (e.key === 'Enter') {
      const v = value.trim();
      if (!v) return;
      const h = histRef.current;
      h.push(v);
      if (h.length > 50) h.shift();
      localStorage.setItem('cmd.history', JSON.stringify(h));
      idxRef.current = h.length;
      run(v);
      setValue('');
      setSuggestions([]);
    } else if (e.key === 'ArrowUp') {
      const h = histRef.current;
      if (idxRef.current > 0) { idxRef.current--; setValue(h[idxRef.current] || ''); }
      setSuggestions([]);
      e.preventDefault();
    } else if (e.key === 'ArrowDown') {
      const h = histRef.current;
      if (idxRef.current < h.length - 1) { idxRef.current++; setValue(h[idxRef.current] || ''); }
      else { idxRef.current = h.length; setValue(''); }
      setSuggestions([]);
      e.preventDefault();
    } else if (e.key === 'Tab') {
      e.preventDefault();
      const raw = value;

      // Already cycling? Just advance and re-fill.
      if (cyclingRef.current && suggestions.length) {
        cycleRef.current = (cycleRef.current + 1) % suggestions.length;
        const pick = suggestions[cycleRef.current];
        setValue(cycleModeRef.current === 'cd' ? 'cd ' + pick : pick + ' ');
        setSuggestions([...suggestions]); // force re-render of highlight
        return;
      }

      // Start a new completion.
      const cdMatch = raw.match(/^cd(\s+(.*))?$/i);
      if (cdMatch) {
        const stub = (cdMatch[2] || '').toLowerCase();
        const matches = PAGES.filter(p => p.startsWith(stub));
        if (matches.length === 0) { setSuggestions([]); cyclingRef.current = false; return; }
        if (matches.length === 1) {
          setValue('cd ' + matches[0] + ' ');
          setSuggestions([]); cyclingRef.current = false;
          return;
        }
        cycleModeRef.current = 'cd';
        cycleRef.current = 0;
        cyclingRef.current = true;
        setSuggestions(matches);
        setValue('cd ' + matches[0]);
        return;
      }

      const all = ['help', 'cd', 'ls', 'whoami', 'date', 'uname', 'fortune', 'lang', 'matrix', 'hire', 'sudo', 'vim', 'coffee', 'clear', 'theme', 'echo'];
      const stub = raw.toLowerCase();
      const matches = all.filter(k => k.startsWith(stub));
      if (matches.length === 1) { setValue(matches[0] + ' '); setSuggestions([]); cyclingRef.current = false; }
      else if (matches.length > 1) {
        cycleModeRef.current = 'cmd';
        cycleRef.current = 0;
        cyclingRef.current = true;
        setSuggestions(matches);
        setValue(matches[0] + ' ');
      }
    } else if (e.key === 'Escape') {
      setSuggestions([]);
      cyclingRef.current = false;
    } else {
      // any other key breaks the cycle (next Tab will recompute fresh)
      cyclingRef.current = false;
    }
  }

  function onChange(e) {
    setValue(e.target.value);
    if (suggestions.length) setSuggestions([]);
    cyclingRef.current = false;
  }

  // Ctrl+K global focus
  useEffect(() => {
    function onKey(e) {
      if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'k') {
        e.preventDefault();
        inputRef.current?.focus();
      }
    }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, []);

  return (
    <div className="cmd-input-wrap">
      {suggestions.length > 0 && (
        <div className="cmd-suggestions">
          {suggestions.map((s, i) => (
            <span key={s} className={i === cycleRef.current ? 'sug active' : 'sug'}>
              {s}<span className="dim">/</span>
            </span>
          ))}
        </div>
      )}
      <span className="prompt-mini">{pathLabel} $</span>
      <input
        ref={inputRef}
        id="cmd"
        className="cmd-input"
        autoComplete="off"
        spellCheck="false"
        value={value}
        placeholder={t('status.placeholder')}
        onChange={onChange}
        onKeyDown={onKeyDown}
      />
    </div>
  );
}

/* ============================================
   KONAMI HOOK
   ============================================ */
function useKonami(onTrigger) {
  useEffect(() => {
    const seq = ['ArrowUp', 'ArrowUp', 'ArrowDown', 'ArrowDown', 'ArrowLeft', 'ArrowRight', 'ArrowLeft', 'ArrowRight', 'b', 'a'];
    let pos = 0;
    function onKey(e) {
      const ae = document.activeElement;
      if (ae && ['INPUT', 'TEXTAREA'].includes(ae.tagName)) return;
      if (e.key.toLowerCase() === seq[pos].toLowerCase()) {
        pos++;
        if (pos === seq.length) { pos = 0; onTrigger(); }
      } else pos = 0;
    }
    document.addEventListener('keydown', onKey);
    return () => document.removeEventListener('keydown', onKey);
  }, [onTrigger]);
}

/* exports */
Object.assign(window, {
  useI18n, I18nProvider,
  useToasts, ToastProvider,
  Boot, RouteLoader, CommandBar, MatrixOverlay,
  useClock, useUptime, useVisitorN,
  useStageSequence, useKonami,
  Link, NavContext, useNavigate,
  pageFromPath, pageToPath, getBasePath,
  PAGES, PAGE_LABEL, PAGE_FILE, PAGE_PROMPT_PATH,
});
