////
// version: df700ff

var __defProp = Object.defineProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, {
      get: all[name],
      enumerable: true,
      configurable: true,
      set: (newValue) => all[name] = () => newValue
    });
};

// src/js/completion.ts
var exports_completion = {};
__export(exports_completion, {
  initCompletion: () => initCompletion
});

// src/js/dom.ts
var exports_dom = {};
__export(exports_dom, {
  scrollback: () => scrollback,
  content: () => content2,
  cmdLine: () => cmdLine,
  cmdInput: () => cmdInput,
  $$: () => $$,
  $: () => $
});
var content2 = $("content");
var cmdLine = $("command-line");
var cmdInput = $("command-textbox");
var scrollback = $("scrollback");
function $(id) {
  return document.getElementById(id);
}
var $$ = (tag, innerHTML = "") => {
  let name = tag;
  let classList = [];
  if (tag.startsWith("."))
    tag = "div" + tag;
  if (tag.includes("."))
    [name, ...classList] = tag.split(".").map((x) => x.trim());
  const el = document.createElement(name);
  if (innerHTML)
    el.innerHTML = innerHTML;
  if (classList.length)
    el.classList.add(...classList);
  return el;
};

// src/js/commands.ts
var exports_commands = {};
__export(exports_commands, {
  mode: () => mode,
  commands: () => commands,
  cacheCommands: () => cacheCommands
});

// src/js/shell.ts
var exports_shell = {};
__export(exports_shell, {
  runCommand: () => runCommand
});

// src/js/scrollback.ts
var exports_scrollback = {};
__export(exports_scrollback, {
  setStatus: () => setStatus,
  replaceOutput: () => replaceOutput,
  latestId: () => latestId,
  insert: () => insert,
  initScrollback: () => initScrollback,
  handleOutput: () => handleOutput,
  appendOutput: () => appendOutput,
  addOutput: () => addOutput,
  addInput: () => addInput,
  addErrorMessage: () => addErrorMessage
});

// src/shared/utils.ts
function randomId() {
  return Math.random().toString(36).slice(7);
}
async function importUrl(url, cacheTag) {
  url += url.includes("?") ? "&" : "?";
  if (cacheTag)
    url += "t=" + cacheTag;
  else
    url += "t=" + (nodeEnv() === "production" ? gitSHA() : Date.now());
  console.log("-> import", url);
  return import(url);
}
function nodeEnv() {
  if (typeof process !== "undefined" && process.env?.NODE_ENV)
    return "development";
  if (typeof globalThis !== "undefined" && globalThis.NODE_ENV)
    return globalThis.NODE_ENV;
  return;
}
function gitSHA() {
  return globalThis.GIT_SHA;
}

// src/js/scrollback.ts
var statusColors = {
  waiting: "yellow",
  streaming: "purple",
  ok: "green",
  error: "red"
};
function initScrollback() {
  window.addEventListener("click", handleInputClick);
}
function insert(node) {
  scrollback.append(node);
}
function latestId() {
  const nodes = document.querySelectorAll("[data-id]");
  if (nodes.length)
    return nodes[nodes.length - 1].dataset.id;
}
function addInput(id, input, status) {
  const parent = $$("li.input");
  const statusSpan = $$(`span.status.${statusColors[status || "waiting"]}`, "•");
  const content3 = $$("span.content", input);
  parent.append(statusSpan, content3);
  parent.dataset.id = id;
  insert(parent);
  scrollback.scrollTop = scrollback.scrollHeight - scrollback.clientHeight;
}
function setStatus(id, status) {
  const statusEl = document.querySelector(`[data-id="${id}"].input .status`);
  if (!statusEl)
    return;
  statusEl.classList.remove(...Object.values(statusColors));
  statusEl.classList.add(statusColors[status]);
}
function addOutput(id, output2) {
  const item = $$("li");
  item.classList.add("output");
  item.dataset.id = id || randomId();
  const [format, content3] = processOutput(output2);
  if (format === "html")
    item.innerHTML = content3;
  else
    item.textContent = content3;
  const input = document.querySelector(`[data-id="${id}"].input`);
  if (input instanceof HTMLLIElement) {
    input.parentNode.insertBefore(item, input.nextSibling);
  } else {
    insert(item);
  }
}
function addErrorMessage(message) {
  addOutput("", { html: `<span class="red">${message}</span>` });
}
function appendOutput(id, output2) {
  const item = document.querySelector(`[data-id="${id}"].output`);
  if (!item) {
    console.error(`output id ${id} not found`);
    return;
  }
  const [format, content3] = processOutput(output2);
  if (format === "html")
    item.innerHTML += content3;
  else
    item.textContent += content3;
}
function replaceOutput(id, output2) {
  const item = document.querySelector(`[data-id="${id}"].output`);
  if (!item) {
    console.error(`output id ${id} not found`);
    return;
  }
  const [format, content3] = processOutput(output2);
  if (format === "html")
    item.innerHTML = content3;
  else
    item.textContent = content3;
}
function processOutput(output) {
  let content = "";
  let html = false;
  if (typeof output === "string") {
    content = output;
  } else if (Array.isArray(output)) {
    content = output.join(" ");
  } else if (typeof output === "object" && "html" in output) {
    html = true;
    content = output.html;
    if (output.script)
      eval(output.script);
  } else if (typeof output === "object" && "text" in output) {
    content = output.text;
    if (output.script)
      eval(output.script);
  } else if (typeof output === "object" && "script" in output) {
    eval(output.script);
  } else {
    content = JSON.stringify(output);
  }
  return [html ? "html" : "text", content];
}
function handleInputClick(e) {
  const target = e.target;
  if (!(target instanceof HTMLElement))
    return;
  if (target.matches(".input .content")) {
    cmdInput.value = target.textContent;
  }
}
function handleOutput(msg) {
  const result = msg.data;
  const id = "id" in msg ? msg.id || "" : "";
  setStatus(id, result.status);
  addOutput(id, result.output);
}

// src/js/websocket.ts
var exports_websocket = {};
__export(exports_websocket, {
  startConnection: () => startConnection,
  send: () => send,
  close: () => close
});

// src/js/session.ts
var exports_session = {};
__export(exports_session, {
  sessionStore: () => sessionStore,
  sessionId: () => sessionId,
  initSession: () => initSession,
  handleSessionUpdate: () => handleSessionUpdate,
  handleSessionStart: () => handleSessionStart
});

// src/js/event.ts
var exports_event = {};
__export(exports_event, {
  once: () => once,
  on: () => on,
  off: () => off,
  fire: () => fire
});
function fire(eventName, detail) {
  window.dispatchEvent(new CustomEvent(eventName, { detail }));
}
function on(eventName, handler) {
  const listener = handler;
  window.addEventListener(eventName, listener);
  return () => window.removeEventListener(eventName, listener);
}
function once(eventName, handler) {
  const listener = handler;
  window.addEventListener(eventName, listener, { once: true });
  return () => window.removeEventListener(eventName, listener);
}
function off(eventName, handler) {
  window.removeEventListener(eventName, handler);
}

// src/js/session.ts
var sessionId = randomId();
var sessionStore = new Map;
function initSession() {}
function handleSessionStart(msg) {
  for (const key of Object.keys(msg.data))
    sessionStore.set(key, msg.data[key] || "");
  fire("session:update", msg.data);
  mode(msg.data.mode);
}
function handleSessionUpdate(msg) {
  for (const key of Object.keys(msg.data))
    sessionStore.set(key, msg.data[key] || "");
  fire("session:update", msg.data);
}

// src/js/dispatch.ts
var exports_dispatch = {};
__export(exports_dispatch, {
  dispatchMessage: () => dispatchMessage
});

// src/js/webapp.ts
var exports_webapp = {};
__export(exports_webapp, {
  currentAppUrl: () => currentAppUrl,
  cacheApps: () => cacheApps,
  apps: () => apps
});
var apps = [];
function cacheApps(a) {
  apps.length = 0;
  apps.unshift(...a);
  apps.sort();
  window.dispatchEvent(new CustomEvent("apps:change"));
}
function currentAppUrl() {
  const project = sessionStore.get("project") || "root";
  if (!apps.includes(project))
    return;
  const hostname = sessionStore.get("hostname") || "localhost";
  const s = hostname.startsWith("localhost") ? "" : "s";
  return `http${s}://${project}.${hostname}`;
}

// src/js/stream.ts
var exports_stream = {};
__export(exports_stream, {
  handleStreamStart: () => handleStreamStart,
  handleStreamReplace: () => handleStreamReplace,
  handleStreamEnd: () => handleStreamEnd,
  handleStreamAppend: () => handleStreamAppend
});
function handleStreamStart(msg) {
  const id = msg.id;
  const status = document.querySelector(`[data-id="${id}"].input .status`);
  if (!status)
    return;
  addOutput(id, msg.data);
  status.classList.remove("yellow");
  status.classList.add("purple");
}
function handleStreamAppend(msg) {
  appendOutput(msg.id, msg.data);
}
function handleStreamReplace(msg) {
  replaceOutput(msg.id, msg.data);
}
function handleStreamEnd(_msg) {}

// src/js/game.ts
var exports_game = {};
__export(exports_game, {
  handleGameStart: () => handleGameStart
});

// src/shared/game.ts
class GameContext {
  ctx;
  constructor(ctx) {
    this.ctx = ctx;
  }
  width = 960;
  height = 540;
  clear(color) {
    if (color)
      this.rectfill(0, 0, this.width, this.height, color);
    else
      this.ctx.clearRect(0, 0, this.ctx.canvas.width, this.ctx.canvas.height);
  }
  text(msg, x, y, color = "black", size = 16, font = "C64ProMono") {
    const c = this.ctx;
    c.save();
    c.fillStyle = color;
    c.font = `${size}px ${font}`;
    c.textBaseline = "top";
    c.fillText(msg, x, y);
    c.restore();
  }
  centerText(msg, color = "black", size = 16, font = "C64ProMono") {
    const c = this.ctx;
    c.save();
    c.fillStyle = color;
    c.font = `${size}px ${font}`;
    c.textBaseline = "middle";
    c.textAlign = "center";
    c.fillText(msg, this.width / 2, this.height / 2);
    c.restore();
  }
  centerTextX(msg, y, color = "black", size = 16, font = "C64ProMono") {
    const c = this.ctx;
    c.save();
    c.fillStyle = color;
    c.font = `${size}px ${font}`;
    c.textBaseline = "middle";
    c.textAlign = "center";
    c.fillText(msg, this.width / 2, y);
    c.restore();
  }
  centerTextY(msg, x, color = "black", size = 16, font = "C64ProMono") {
    const c = this.ctx;
    c.save();
    c.fillStyle = color;
    c.font = `${size}px ${font}`;
    c.textBaseline = "middle";
    c.textAlign = "center";
    c.fillText(msg, x, this.height / 2);
    c.restore();
  }
  circ(x, y, r, color = "black") {
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.strokeStyle = color;
    c.arc(x, y, r, 0, Math.PI * 2);
    c.stroke();
    c.restore();
  }
  circfill(x, y, r, color = "black") {
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.fillStyle = color;
    c.arc(x, y, r, 0, Math.PI * 2);
    c.fill();
    c.restore();
  }
  line(x0, y0, x1, y1, color = "black") {
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.strokeStyle = color;
    c.moveTo(x0, y0);
    c.lineTo(x1, y1);
    c.stroke();
    c.restore();
  }
  oval(x0, y0, x1, y1, color = "black") {
    const c = this.ctx;
    const w = x1 - x0;
    const h = y1 - y0;
    c.save();
    c.beginPath();
    c.strokeStyle = color;
    c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
    c.stroke();
    c.restore();
  }
  ovalfill(x0, y0, x1, y1, color = "black") {
    const c = this.ctx;
    const w = x1 - x0;
    const h = y1 - y0;
    c.save();
    c.beginPath();
    c.fillStyle = color;
    c.ellipse(x0 + w / 2, y0 + h / 2, Math.abs(w) / 2, Math.abs(h) / 2, 0, 0, Math.PI * 2);
    c.fill();
    c.restore();
  }
  rect(x0, y0, x1, y1, color = "black") {
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.strokeStyle = color;
    c.rect(x0, y0, x1 - x0, y1 - y0);
    c.stroke();
    c.restore();
  }
  rectfill(x0, y0, x1, y1, color = "black") {
    const c = this.ctx;
    c.save();
    this.ctx.fillStyle = color;
    this.ctx.fillRect(x0, y0, x1 - x0, y1 - y0);
    c.restore();
  }
  rrect(x, y, w, h, r, color = "black") {
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.strokeStyle = color;
    this.roundRectPath(x, y, w, h, r);
    c.stroke();
    c.restore();
  }
  rrectfill(x, y, w, h, r, color = "black") {
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.fillStyle = color;
    this.roundRectPath(x, y, w, h, r);
    c.fill();
    c.restore();
  }
  trianglefill(x0, y0, x1, y1, x2, y2, color = "black") {
    return this.polygonfill([
      [x0, y0],
      [x1, y1],
      [x2, y2]
    ], color);
  }
  polygonfill(points, color = "black") {
    if (points.length < 3)
      return;
    const c = this.ctx;
    c.save();
    c.beginPath();
    c.fillStyle = color;
    c.moveTo(points[0][0], points[0][1]);
    for (let i = 1;i < points.length; i++) {
      c.lineTo(points[i][0], points[i][1]);
    }
    c.closePath();
    c.fill();
    c.restore();
  }
  roundRectPath(x, y, w, h, r) {
    const c = this.ctx;
    c.moveTo(x + r, y);
    c.lineTo(x + w - r, y);
    c.quadraticCurveTo(x + w, y, x + w, y + r);
    c.lineTo(x + w, y + h - r);
    c.quadraticCurveTo(x + w, y + h, x + w - r, y + h);
    c.lineTo(x + r, y + h);
    c.quadraticCurveTo(x, y + h, x, y + h - r);
    c.lineTo(x, y + r);
    c.quadraticCurveTo(x, y, x + r, y);
  }
}

// src/js/focus.ts
var exports_focus = {};
__export(exports_focus, {
  initFocus: () => initFocus,
  focusInput: () => focusInput,
  focusHandler: () => focusHandler
});
function initFocus() {
  window.addEventListener("click", focusHandler);
  focusInput();
}
function focusInput() {
  cmdInput.focus();
}
function focusHandler(e) {
  const target = e.target;
  if (!(target instanceof HTMLElement)) {
    focusInput();
    return;
  }
  if (["INPUT", "TEXTAREA", "CANVAS", "A"].includes(target.tagName))
    return false;
  const selection = window.getSelection() || "";
  if (selection.toString() === "")
    focusInput();
  e.preventDefault();
}

// src/js/game.ts
var FPS = 30;
var HEIGHT = 540;
var WIDTH = 960;
var oldMode = "cinema";
var running = false;
var canvas;
var pressedStack = new Set;
var pressed = {
  key: "",
  shift: false,
  ctrl: false,
  meta: false,
  pressed: pressedStack,
  prevPressed: new Set,
  justPressed: new Set,
  justReleased: new Set
};
async function handleGameStart(msg) {
  const msgId = msg.id;
  const name = msg.data;
  let game;
  try {
    game = await importUrl(`/source/${name}?session=${sessionId}}`);
  } catch (err) {
    setStatus(msgId, "error");
    addOutput(msgId, `Error: ${err.message ? err.message : err}`);
    return;
  }
  if (document.body.dataset.mode === "tall") {
    mode();
    oldMode = "tall";
  }
  canvas = createCanvas();
  canvas.focus();
  setStatus(msgId, "ok");
  window.addEventListener("keydown", handleKeydown);
  window.addEventListener("keyup", handleKeyup);
  window.addEventListener("resize", resizeCanvas);
  resizeCanvas();
  gameLoop(new GameContext(canvas.getContext("2d")), game);
}
function createCanvas() {
  const canvas2 = $$("canvas.game.active");
  canvas2.id = randomId();
  canvas2.height = HEIGHT;
  canvas2.width = WIDTH;
  canvas2.tabIndex = 0;
  const main = document.querySelector("main");
  main?.classList.add("game");
  main?.parentNode?.insertBefore(canvas2, main);
  return canvas2;
}
function handleKeydown(e) {
  e.preventDefault();
  if (e.key === "Escape" || e.ctrlKey && e.key === "c") {
    endGame();
  } else {
    pressedStack.add(e.key);
    pressed.key = e.key;
    pressed.ctrl = e.ctrlKey;
    pressed.shift = e.shiftKey;
    pressed.meta = e.metaKey;
  }
}
function handleKeyup(e) {
  pressedStack.delete(e.key);
  if (pressedStack.size === 0) {
    pressed.key = "";
    pressed.ctrl = false;
    pressed.shift = false;
    pressed.meta = false;
  }
}
function updateInputState() {
  pressed.justPressed = new Set([...pressed.pressed].filter((k) => !pressed.prevPressed.has(k)));
  pressed.justReleased = new Set([...pressed.prevPressed].filter((k) => !pressed.pressed.has(k)));
  pressed.prevPressed = new Set(pressed.pressed);
}
function resizeCanvas() {
  const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540);
  canvas.width = 960 * scale;
  canvas.height = 540 * scale;
  const ctx = canvas.getContext("2d");
  ctx.setTransform(scale, 0, 0, scale, 0, 0);
}
function gameLoop(ctx, game) {
  running = true;
  let last = 0;
  if (game.init)
    game.init();
  function loop(ts) {
    if (!running)
      return;
    const delta = ts - last;
    if (delta >= 1000 / FPS) {
      updateInputState();
      if (game.update)
        game.update(delta, pressed);
      if (game.draw)
        game.draw(ctx);
      last = ts;
    }
    requestAnimationFrame(loop);
  }
  requestAnimationFrame(loop);
}
function endGame() {
  running = false;
  window.removeEventListener("keydown", handleKeydown);
  window.removeEventListener("keyup", handleKeyup);
  window.removeEventListener("resize", resizeCanvas);
  if (oldMode === "tall")
    mode();
  const main = document.querySelector("main");
  main?.classList.remove("game");
  canvas.classList.remove("active");
  canvas.style.height = HEIGHT / 2 + "px";
  canvas.style.width = WIDTH / 2 + "px";
  const output2 = $$("li.output");
  output2.append(canvas);
  insert(output2);
  focusInput();
}

// src/js/dispatch.ts
async function dispatchMessage(msg) {
  switch (msg.type) {
    case "output":
      handleOutput(msg);
      break;
    case "commands":
      cacheCommands(msg.data);
      break;
    case "apps":
      cacheApps(msg.data);
      break;
    case "error":
      console.error(msg.data);
      break;
    case "stream:start":
      handleStreamStart(msg);
      break;
    case "stream:end":
      handleStreamEnd(msg);
      break;
    case "stream:append":
      handleStreamAppend(msg);
      break;
    case "stream:replace":
      handleStreamReplace(msg);
      break;
    case "game:start":
      await handleGameStart(msg);
      break;
    case "session:start":
      handleSessionStart(msg);
      break;
    case "session:update":
      handleSessionUpdate(msg);
      break;
    default:
      console.error("unknown message type", msg);
  }
}

// src/js/websocket.ts
var MAX_RETRIES = 5;
var retries = 0;
var connected = false;
var msgQueue = [];
var ws = null;
function startConnection() {
  const url = new URL(`/ws?session=${sessionId}`, location.href);
  url.protocol = url.protocol.replace("http", "ws");
  ws = new WebSocket(url);
  ws.onmessage = receive;
  ws.onclose = retryConnection;
  ws.onerror = () => ws?.close();
  ws.onopen = () => {
    connected = true;
    msgQueue.forEach((msg) => send(msg));
    msgQueue.length = 0;
  };
}
function send(msg) {
  if (!connected) {
    msgQueue.push(msg);
    startConnection();
    return;
  }
  if (!msg.session)
    msg.session = sessionId;
  ws?.readyState === 1 && ws.send(JSON.stringify(msg));
  console.log("-> send", msg);
}
async function receive(e) {
  const data = JSON.parse(e.data);
  console.log("<- receive", data);
  await dispatchMessage(data);
}
function close() {
  ws?.close(1000, "bye");
}
function retryConnection() {
  connected = false;
  if (retries >= MAX_RETRIES) {
    addErrorMessage(`Failed to reconnect ${retries} times. Server is down.`);
    if (ws)
      ws.onclose = () => {};
    return;
  }
  retries++;
  addErrorMessage(`Connection lost. Retrying...`);
  setTimeout(startConnection, 2000);
}

// src/js/history.ts
var exports_history = {};
__export(exports_history, {
  resetHistory: () => resetHistory,
  initHistory: () => initHistory,
  addToHistory: () => addToHistory
});
var history = ["one", "two", "three"];
var idx = -1;
var savedInput = "";
function initHistory() {
  cmdInput.addEventListener("keydown", navigateHistory);
}
function addToHistory(input) {
  if (history.length === 0 || history[0] === input)
    return;
  history.unshift(input);
  resetHistory();
}
function resetHistory() {
  idx = -1;
  savedInput = "";
}
function navigateHistory(e) {
  if (cmdLine.dataset.extended)
    return;
  if (e.key === "ArrowUp" || e.ctrlKey && e.key === "p") {
    e.preventDefault();
    if (idx >= history.length - 1)
      return;
    if (idx === -1)
      savedInput = cmdInput.value;
    cmdInput.value = history[++idx] || "";
    if (idx >= history.length)
      idx = history.length - 1;
  } else if (e.key === "ArrowDown" || e.ctrlKey && e.key === "n") {
    e.preventDefault();
    if (idx <= 0) {
      cmdInput.value = savedInput;
      idx = -1;
      return;
    }
    cmdInput.value = history[--idx] || "";
    if (idx < -1)
      idx = -1;
  } else if (idx !== -1) {
    resetHistory();
  }
}

// src/js/shell.ts
async function runCommand(input, showInput = true) {
  if (!input.trim())
    return;
  if (input.includes(";")) {
    input.split(";").forEach(async (cmd2) => await runCommand(cmd2.trim()));
    return;
  }
  const id = randomId();
  addToHistory(input);
  if (showInput)
    addInput(id, input);
  const [cmd = "", ...args] = input.split(" ");
  if (commands[cmd]?.type === "browser") {
    const mod = await importUrl(`/source/${cmd}`);
    if (!mod.default) {
      addOutput(id, `no default export in ${cmd}`);
      setStatus(id, "error");
      return;
    }
    const result = mod.default(...args);
    if (result)
      addOutput(id, result);
    setStatus(id, "ok");
  } else {
    send({ id, type: "input", data: input });
  }
}

// src/js/commands.ts
var commands = {};
function cacheCommands(cmds) {
  for (const key in commands)
    delete commands[key];
  Object.assign(commands, cmds);
}
async function mode(mode2) {
  await runCommand(mode2 ? `mode ${mode2}` : "mode", false);
}

// src/js/completion.ts
function initCompletion() {
  cmdInput.addEventListener("keydown", handleCompletion);
}
function handleCompletion(e) {
  if (e.key !== "Tab")
    return;
  e.preventDefault();
  const input = cmdInput.value;
  for (const command of Object.keys(commands)) {
    if (command.startsWith(input)) {
      cmdInput.value = command + " ";
      return;
    }
  }
}

// src/js/cursor.ts
var exports_cursor = {};
__export(exports_cursor, {
  initCursor: () => initCursor
});
var cursor = "Û";
var cmdCursor;
var enabled = true;
function initCursor() {
  cmdCursor = $("command-cursor");
  cmdInput.addEventListener("keydown", showCursor);
  document.addEventListener("focus", cursorEnablerHandler, true);
  showCursor();
}
function showCursor(e = {}) {
  if (!enabled) {
    cmdCursor.value = "";
    return;
  }
  if (e.key === "Enter" && !e.shiftKey) {
    cmdCursor.value = cursor;
    return;
  }
  requestAnimationFrame(() => cmdCursor.value = buildBlankCursorLine() + cursor);
}
function cursorEnablerHandler(e) {
  if (!e.target)
    return;
  const target = e.target;
  enabled = target.id === "command-textbox";
  showCursor();
}
function buildBlankCursorLine() {
  let line = "";
  for (const char of cmdInput.value.slice(0, cmdInput.selectionEnd)) {
    line += char === `
` ? char : " ";
  }
  return line;
}

// src/js/drop.ts
var exports_drop = {};
__export(exports_drop, {
  initDrop: () => initDrop
});
function initDrop() {
  ["dragenter", "dragover", "dragleave", "drop"].forEach((eventName) => {
    document.body.addEventListener(eventName, preventDefaults, false);
  });
  document.body.addEventListener("drop", handleDrop);
}
function preventDefaults(e) {
  e.preventDefault();
  e.stopPropagation();
}
function handleDrop(e) {
  const fileInput = document.querySelector("input[type=file]");
  const files = e.dataTransfer?.files ?? [];
  if (files.length > 0) {
    const dt = new DataTransfer;
    Array.from(files).forEach((f) => dt.items.add(f));
    fileInput.files = dt.files;
    fileInput.dispatchEvent(new Event("change", { bubbles: true }));
  }
}

// src/js/editor.ts
var exports_editor = {};
__export(exports_editor, {
  initEditor: () => initEditor
});
var INDENT_SIZE = 2;
function initEditor() {
  document.addEventListener("input", handleAdjustHeight);
  focusTextareaOnCreation();
}
function handleAdjustHeight(e) {
  const target = e.target;
  if (target?.matches(".editor"))
    adjustHeight(target);
}
function adjustHeight(editor) {
  editor.style.height = "auto";
  editor.style.height = editor.scrollHeight + "px";
}
function focusTextareaOnCreation() {
  const observer = new MutationObserver((mutations) => {
    for (const m of mutations)
      for (const node of Array.from(m.addedNodes))
        if (node instanceof HTMLElement && node.childNodes[0] instanceof HTMLElement && node.childNodes[0].matches("textarea")) {
          const editor = node.childNodes[0];
          editor.focus();
          editor.addEventListener("keydown", keydownHandler);
          return;
        }
  });
  observer.observe(scrollback, { childList: true });
}
function keydownHandler(e) {
  const editor = e.target;
  if (e.key === "Tab") {
    e.preventDefault();
    if (e.shiftKey)
      removeTab(editor);
    else
      insertTab(editor);
  } else if (e.ctrlKey && e.key === "c") {
    focusInput();
  } else if (e.ctrlKey && e.key === "s" || e.ctrlKey && e.key === "Enter") {
    e.preventDefault();
    send({
      id: editor.dataset.path || "/tmp.txt",
      type: "save-file",
      data: editor.value
    });
  } else if (e.key === "{") {
    if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, "{", "}");
      e.preventDefault();
    } else {
      setTimeout(() => insertAfterCaret(editor, "}"), 0);
    }
  } else if (e.key === "}" && isNextChar(editor, "}")) {
    moveOneRight(editor);
    e.preventDefault();
  } else if (e.key === "[") {
    if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, "[", "]");
      e.preventDefault();
    } else {
      setTimeout(() => insertAfterCaret(editor, "]"), 0);
    }
  } else if (e.key === "]" && isNextChar(editor, "]")) {
    moveOneRight(editor);
    e.preventDefault();
  } else if (e.key === "(") {
    if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, "(", ")");
      e.preventDefault();
    } else {
      setTimeout(() => insertAfterCaret(editor, ")"), 0);
    }
  } else if (e.key === ")" && isNextChar(editor, ")")) {
    moveOneRight(editor);
    e.preventDefault();
  } else if (e.key === '"') {
    if (isNextChar(editor, '"')) {
      moveOneRight(editor);
      e.preventDefault();
    } else if (editor.selectionStart !== editor.selectionEnd) {
      insertAroundSelection(editor, '"', '"');
      e.preventDefault();
    } else {
      setTimeout(() => insertAfterCaret(editor, '"'), 0);
    }
  } else if (e.key === "Enter") {
    indentNewlineForBraces(e, editor);
  }
}
function moveOneRight(editor) {
  const pos = editor.selectionStart;
  editor.selectionStart = editor.selectionEnd = pos + 1;
}
function insertTab(editor) {
  const start = editor.selectionStart;
  const end = editor.selectionEnd;
  const lineStart = editor.value.lastIndexOf(`
`, start - 1) + 1;
  editor.value = editor.value.slice(0, lineStart) + "  " + editor.value.slice(lineStart);
  editor.selectionStart = start + INDENT_SIZE;
  editor.selectionEnd = end + INDENT_SIZE;
}
function removeTab(editor) {
  const start = editor.selectionStart;
  const end = editor.selectionEnd;
  const lineStart = editor.value.lastIndexOf(`
`, start - 1) + 1;
  if (editor.value.slice(lineStart, lineStart + 2) === "  ") {
    editor.value = editor.value.slice(0, lineStart) + editor.value.slice(lineStart + 2);
    editor.selectionStart = start - INDENT_SIZE;
    editor.selectionEnd = end - INDENT_SIZE;
  }
}
function insertAfterCaret(editor, char) {
  const pos = editor.selectionStart;
  editor.value = editor.value.slice(0, pos) + char + editor.value.slice(pos);
  editor.selectionStart = editor.selectionEnd = pos;
}
function isNextChar(editor, char) {
  return editor.value[editor.selectionStart] === char;
}
function indentNewlineForBraces(e, editor) {
  e.preventDefault();
  if (isBetween(editor, "{", "}") || isBetween(editor, "[", "]")) {
    setTimeout(() => insertMoreIndentedNewline(editor), 0);
  } else {
    setTimeout(() => insertIndentedNewline(editor), 0);
  }
}
function isBetween(editor, start, end) {
  const pos = editor.selectionStart;
  const val = editor.value;
  if (pos <= 0 || pos >= val.length)
    return false;
  return val[pos - 1] === start && val[pos] === end;
}
function insertIndentedNewline(editor) {
  const pos = editor.selectionStart;
  const before = editor.value.slice(0, pos);
  const prevLineStart = before.lastIndexOf(`
`, pos - 1) + 1;
  const prevLine = before.slice(prevLineStart);
  let leading = 0;
  while (prevLine[leading] === " ")
    leading++;
  const indent = " ".repeat(leading);
  const insert2 = `
` + indent;
  editor.value = editor.value.slice(0, pos) + insert2 + editor.value.slice(pos);
  const newPos = pos + insert2.length;
  editor.selectionStart = editor.selectionEnd = newPos;
  adjustHeight(editor);
}
function insertAroundSelection(editor, before, after) {
  const start = editor.selectionStart;
  const end = editor.selectionEnd;
  editor.value = editor.value.slice(0, start) + before + editor.value.slice(start, end) + after + editor.value.slice(end);
}
function insertMoreIndentedNewline(editor) {
  const pos = editor.selectionStart;
  const before = editor.value.slice(0, pos);
  const prevLineStart = before.lastIndexOf(`
`, pos - 1) + 1;
  const prevLine = before.slice(prevLineStart);
  let leading = 0;
  while (prevLine[leading] === " ")
    leading++;
  const oldIndent = " ".repeat(leading);
  const newIndent = " ".repeat(leading + INDENT_SIZE);
  const insert2 = `
` + newIndent + `
` + oldIndent;
  editor.value = editor.value.slice(0, pos) + insert2 + editor.value.slice(pos);
  const newPos = pos + insert2.length;
  editor.selectionStart = editor.selectionEnd = newPos - 1 - oldIndent.length;
  adjustHeight(editor);
}

// src/js/form.ts
var exports_form = {};
__export(exports_form, {
  submitHandler: () => submitHandler,
  initForm: () => initForm
});
function initForm() {
  document.addEventListener("submit", submitHandler);
}
var submitHandler = async (e) => {
  e.preventDefault();
  const form = e.target;
  if (!(form instanceof HTMLFormElement))
    return;
  const li = form.closest(".output");
  if (!(li instanceof HTMLLIElement))
    return;
  const id = li.dataset.id;
  if (!id)
    return;
  let output2;
  let error = false;
  try {
    const fd = new FormData(form);
    const data = await fetch("/cmd" + new URL(form.action).pathname, {
      method: "POST",
      headers: { "X-Session": sessionId },
      body: fd
    }).then((r) => r.json());
    output2 = data.output;
    error = data.status === "error";
  } catch (e2) {
    output2 = e2.message || e2.toString();
    error = true;
  }
  if (error)
    setStatus(id, "error");
  replaceOutput(id, output2);
  focusInput();
};

// src/js/gamepad.ts
var exports_gamepad = {};
__export(exports_gamepad, {
  initGamepad: () => initGamepad
});
var BUTTONS_TO_KEYS = {
  0: "z",
  1: "x",
  2: "c",
  3: "v",
  12: "ArrowUp",
  13: "ArrowDown",
  14: "ArrowLeft",
  15: "ArrowRight",
  9: "Enter",
  8: "Escape"
};
var activePads = new Set;
var prevState = {};
function initGamepad() {
  window.addEventListener("gamepadconnected", (e) => {
    console.log("Gamepad connected:", e.gamepad);
    activePads.add(e.gamepad.index);
    requestAnimationFrame(handleGamepad);
  });
  window.addEventListener("gamepaddisconnected", (e) => {
    console.log("Gamepad disconnected:", e.gamepad);
    activePads.delete(e.gamepad.index);
  });
}
function handleGamepad() {
  const pads = navigator.getGamepads();
  for (const gp of pads) {
    if (!gp)
      continue;
    if (!prevState[gp.index])
      prevState[gp.index] = gp.buttons.map(() => false);
    gp.buttons.forEach((btn, i) => {
      const was = prevState[gp.index][i];
      const is = btn.pressed;
      if (was !== is) {
        const key = BUTTONS_TO_KEYS[i];
        if (key) {
          const type = is ? "keydown" : "keyup";
          window.dispatchEvent(new KeyboardEvent(type, { code: key, key }));
        }
        prevState[gp.index][i] = is;
      }
    });
  }
  requestAnimationFrame(handleGamepad);
}

// src/js/hyperlink.ts
var exports_hyperlink = {};
__export(exports_hyperlink, {
  initHyperlink: () => initHyperlink
});

// src/js/browser.ts
var exports_browser = {};
__export(exports_browser, {
  openBrowser: () => openBrowser,
  isBrowsing: () => isBrowsing
});
var HEIGHT2 = 540;
var WIDTH2 = 960;
var controls = $("browser-controls");
var address = $("browser-address");
var iframe;
var realUrl = "";
var showInput = true;
function isBrowsing() {
  return document.querySelector("iframe.browser.active") !== null;
}
function openBrowser(url, openedVia = "click") {
  showInput = openedVia === "click";
  iframe = $$("iframe.browser.active");
  iframe.src = url;
  iframe.sandbox.add("allow-scripts", "allow-same-origin", "allow-forms");
  iframe.height = String(HEIGHT2);
  iframe.width = String(WIDTH2);
  iframe.tabIndex = 0;
  window.addEventListener("message", handleAppMessage);
  window.addEventListener("keydown", handleBrowserKeydown);
  controls.addEventListener("click", handleClick);
  iframe.addEventListener("load", handlePageLoad);
  controls.style.display = "";
  const main = document.querySelector("#content");
  main?.prepend(iframe);
  setAddress(url);
}
function closeBrowser() {
  window.removeEventListener("keydown", handleBrowserKeydown);
  window.removeEventListener("message", handleAppMessage);
  controls.removeEventListener("click", handleClick);
  iframe.removeEventListener("load", handlePageLoad);
  const id = randomId();
  if (showInput)
    addInput(id, "browse " + realUrl, "ok");
  iframe.style.transformOrigin = "top left";
  iframe.style.transform = "scale(0.5)";
  iframe.style.pointerEvents = "none";
  iframe.tabIndex = -1;
  iframe.classList.remove("fullscreen", "active");
  scrollback.append(iframe);
  controls.style.display = "none";
  focusInput();
}
function handleAppMessage(event) {
  const origin = event.origin;
  if (!origin.includes("localhost") && !origin.match(/\.local$/)) {
    return;
  }
  const { type, data } = event.data;
  switch (type) {
    case "NAV_READY":
      break;
    case "URL_CHANGED":
      setAddress(data.url);
      break;
    case "TITLE_CHANGED":
      console.log("Page title:", data.title);
      break;
    case "NAV_BLOCKED":
      showNavigationError(data.url, data.reason);
      break;
    case "KEYDOWN":
      const keyEvent = new KeyboardEvent("keydown", {
        key: data.key,
        ctrlKey: data.ctrlKey,
        shiftKey: data.shiftKey,
        altKey: data.altKey,
        metaKey: data.metaKey,
        bubbles: true
      });
      window.dispatchEvent(keyEvent);
      break;
  }
}
function handleBrowserKeydown(e) {
  if (e.key === "Escape" || e.ctrlKey && e.key === "c") {
    e.preventDefault();
    closeBrowser();
  }
}
function handleClick(e) {
  const target = e.target;
  if (!(target instanceof HTMLElement))
    return;
  switch (target.id) {
    case "back-button":
      navigateBack();
      break;
    case "forward-button":
      navigateForward();
      break;
    case "stop-button":
      stopLoading();
      break;
    case "reload-button":
      reloadBrowser();
      break;
    case "fullscreen-button":
      fullscreenBrowser();
      break;
    case "close-button":
      e.stopImmediatePropagation();
      closeBrowser();
      break;
    default:
      return;
  }
  e.preventDefault();
}
function handlePageLoad() {}
function setAddress(url) {
  realUrl = url;
  address.textContent = url.replace(/https?:\/\//, "");
}
function navigateBack() {
  sendNavCommand("back");
}
function navigateForward() {
  sendNavCommand("forward");
}
function reloadBrowser() {
  iframe.src = realUrl;
}
function stopLoading() {
  sendNavCommand("stop");
}
function fullscreenBrowser() {
  controls.style.display = "none";
  iframe.classList.add("fullscreen");
  document.body.append(iframe);
}
function sendNavCommand(action) {
  if (!iframe.contentWindow)
    return;
  iframe.contentWindow.postMessage({
    type: "NAV_COMMAND",
    action
  }, "*");
}
function showNavigationError(url, reason) {
  alert(`NAVIGATION BLOCKED

${url}

${reason}`);
}

// src/js/hyperlink.ts
function initHyperlink() {
  window.addEventListener("click", handleClick2);
}
async function handleClick2(e) {
  const target = e.target;
  if (!(target instanceof HTMLElement))
    return;
  const a = target.closest("a");
  if (!a)
    return;
  const href = a.getAttribute("href");
  if (!href)
    return;
  if (href.startsWith("#") && href.length > 1) {
    e.preventDefault();
    await runCommand(href.slice(1));
    focusInput();
  } else if (!isBrowsing() && !e.metaKey) {
    e.preventDefault();
    openBrowser(href);
  }
}

// src/js/input.ts
var exports_input = {};
__export(exports_input, {
  initInput: () => initInput
});
function initInput() {
  cmdInput.addEventListener("keydown", inputHandler);
  cmdInput.addEventListener("paste", pasteHandler);
}
async function inputHandler(event) {
  const target = event.target;
  if (target?.id !== cmdInput.id)
    return;
  if (event.key === "Escape" || event.ctrlKey && event.key === "c") {
    clearInput();
    resetHistory();
  } else if (event.key === "Tab") {
    event.preventDefault();
  } else if (event.shiftKey && event.key === "Enter") {
    cmdInput.rows += 1;
    cmdLine.dataset.extended = "true";
  } else if (event.key === "Enter") {
    event.preventDefault();
    await runCommand(cmdInput.value);
    clearInput();
  }
  setTimeout(() => {
    if (cmdLine.dataset.extended && !cmdInput.value.includes(`
`)) {
      cmdInput.rows = 1;
      cmdInput.style.height = "auto";
      delete cmdLine.dataset.extended;
    }
  }, 0);
}
function pasteHandler(event) {
  const text = event.clipboardData?.getData("text") || "";
  if (!text.includes(`
`)) {
    delete cmdLine.dataset.extended;
    return;
  }
  setTimeout(() => {
    cmdInput.style.height = "auto";
    cmdInput.style.height = cmdInput.scrollHeight + "px";
    cmdLine.dataset.extended = "true";
  }, 0);
}
function clearInput() {
  cmdInput.value = "";
  cmdInput.rows = 1;
  cmdInput.style.height = "auto";
  delete cmdLine.dataset.extended;
}

// src/js/resize.ts
var exports_resize = {};
__export(exports_resize, {
  resize: () => resize,
  initResize: () => initResize
});
var content3 = document.getElementById("content");
function initResize() {
  window.addEventListener("resize", resize);
  resize();
}
function resize() {
  if (document.body.dataset.mode === "tall") {
    resizeTall();
  } else {
    resizeCinema();
  }
}
function resizeTall() {
  const scale = Math.min(1, window.innerWidth / 960);
  content3.style.transformOrigin = "top center";
  content3.style.transform = `scaleX(${scale})`;
}
function resizeCinema() {
  const scale = Math.min(window.innerWidth / 960, window.innerHeight / 540);
  content3.style.transformOrigin = "center center";
  content3.style.transform = `scale(${scale})`;
}

// src/js/statusbar.ts
var exports_statusbar = {};
__export(exports_statusbar, {
  status: () => status,
  projectWww: () => projectWww,
  projectName: () => projectName,
  projectCwd: () => projectCwd,
  initStatusbar: () => initStatusbar
});
var projectName = $("project-name");
var projectCwd = $("project-cwd");
var projectWww = $("project-www");
var statusbar = $("statusbar");
var statusbarMsg = $("statusbar-msg");
var STATUS_MSG_LENGTH = 3000;
var timer;
function initStatusbar() {
  registerEvents();
}
function registerEvents() {
  on("apps:change", (e) => updateWww(sessionStore.get("project") || "root"));
  on("session:update", (e) => {
    const ev = e;
    const data = ev.detail;
    if (data.project)
      updateProjectName(data.project);
    if (data.cwd)
      updateCwd(data.cwd);
  });
}
function status(msg) {
  showStatusMsg();
  statusbarMsg.textContent = msg;
  if (timer)
    clearTimeout(timer);
  timer = setTimeout(hideStatusMsg, STATUS_MSG_LENGTH);
}
function showStatusMsg() {
  statusbar.classList.add("showing-msg");
}
function hideStatusMsg() {
  statusbar.className = "";
}
function updateProjectName(project) {
  projectName.textContent = project;
  updateWww(project);
  updateCwd("/");
}
function updateCwd(cwd) {
  cwd = displayProjectPath(cwd);
  projectCwd.textContent = cwd;
}
function updateWww(project) {
  if (!apps.includes(project) || project === "root") {
    projectWww.style.display = "none";
    return;
  }
  projectWww.style.display = "";
  const hostname = sessionStore.get("hostname") || "localhost";
  projectWww.href = `http://${project}.${hostname}`;
}
function displayProjectPath(path) {
  let prefix = sessionStore.get("NOSE_DIR") || "";
  prefix += "/" + sessionStore.get("project");
  return path.replace(prefix, "") || "/";
}

// src/js/vram.ts
var exports_vram = {};
__export(exports_vram, {
  startVramCounter: () => startVramCounter
});
var vramCounter = $("vram-size");
var startVramCounter = () => {
  const timer2 = setInterval(() => {
    const count = parseInt(vramCounter.textContent) + 1;
    let val = count + "KB";
    if (count < 10)
      val = "0" + val;
    vramCounter.textContent = val;
    if (count >= 64) {
      vramCounter.textContent += " OK";
      clearInterval(timer2);
    }
  }, 15);
};

// src/js/main.tmp.ts
initCompletion();
initCursor();
initDrop();
initFocus();
initForm();
initEditor();
initGamepad();
initHistory();
initHyperlink();
initInput();
initResize();
initScrollback();
initSession();
initStatusbar();
startConnection();
startVramCounter();
Object.assign(window, {
  __pluto_modules: {
    browser: exports_browser,
    commands: exports_commands,
    completion: exports_completion,
    cursor: exports_cursor,
    dispatch: exports_dispatch,
    dom: exports_dom,
    drop: exports_drop,
    editor: exports_editor,
    event: exports_event,
    focus: exports_focus,
    form: exports_form,
    game: exports_game,
    gamepad: exports_gamepad,
    history: exports_history,
    hyperlink: exports_hyperlink,
    input: exports_input,
    resize: exports_resize,
    scrollback: exports_scrollback,
    session: exports_session,
    shell: exports_shell,
    statusbar: exports_statusbar,
    stream: exports_stream,
    vram: exports_vram,
    webapp: exports_webapp,
    websocket: exports_websocket
  }
});
