/* codle.js — Codle AI Platform client-side engine Handles: particles, toast, command palette, flow visualizer, complexity chart, keyboard shortcuts, export utilities */ "use strict"; // ── Namespace ──────────────────────────────────────────────────────────────── window.Codle = window.Codle || {}; // ── Particle background ────────────────────────────────────────────────────── Codle.Particles = (function () { let canvas, ctx, particles = [], raf; const CFG = { count: 55, speed: 0.28, size: [1, 2.2], color: "99,102,241", connDist: 130, opacity: 0.55 }; function init() { canvas = document.getElementById("codle-particles"); if (!canvas) return; ctx = canvas.getContext("2d"); resize(); spawn(); loop(); window.addEventListener("resize", resize); } function resize() { canvas.width = window.innerWidth; canvas.height = window.innerHeight; } function spawn() { particles = []; for (let i = 0; i < CFG.count; i++) { particles.push({ x: Math.random() * canvas.width, y: Math.random() * canvas.height, vx: (Math.random() - 0.5) * CFG.speed, vy: (Math.random() - 0.5) * CFG.speed, r: CFG.size[0] + Math.random() * (CFG.size[1] - CFG.size[0]), o: 0.2 + Math.random() * CFG.opacity, }); } } function loop() { ctx.clearRect(0, 0, canvas.width, canvas.height); particles.forEach(p => { p.x += p.vx; p.y += p.vy; if (p.x < 0 || p.x > canvas.width) p.vx *= -1; if (p.y < 0 || p.y > canvas.height) p.vy *= -1; ctx.beginPath(); ctx.arc(p.x, p.y, p.r, 0, Math.PI * 2); ctx.fillStyle = `rgba(${CFG.color},${p.o})`; ctx.fill(); }); for (let i = 0; i < particles.length; i++) { for (let j = i + 1; j < particles.length; j++) { const dx = particles[i].x - particles[j].x; const dy = particles[i].y - particles[j].y; const d = Math.sqrt(dx * dx + dy * dy); if (d < CFG.connDist) { ctx.beginPath(); ctx.moveTo(particles[i].x, particles[i].y); ctx.lineTo(particles[j].x, particles[j].y); ctx.strokeStyle = `rgba(${CFG.color},${0.12 * (1 - d / CFG.connDist)})`; ctx.lineWidth = 0.6; ctx.stroke(); } } } raf = requestAnimationFrame(loop); } return { init }; })(); // ── Toast notifications ────────────────────────────────────────────────────── Codle.Toast = (function () { function show(msg, type = "info", duration = 3200) { let container = document.getElementById("codle-toast-container"); if (!container) { container = document.createElement("div"); container.id = "codle-toast-container"; document.body.appendChild(container); } const icons = { success: "✓", error: "✕", info: "ℹ" }; const el = document.createElement("div"); el.className = `codle-toast toast-${type}`; el.innerHTML = `${icons[type] || "ℹ"}${msg}`; container.appendChild(el); setTimeout(() => { el.style.animation = "toast-out 0.3s ease forwards"; setTimeout(() => el.remove(), 310); }, duration); } return { show, success: m => show(m, "success"), error: m => show(m, "error"), info: m => show(m, "info") }; })(); // ── Command palette ────────────────────────────────────────────────────────── Codle.CommandPalette = (function () { const CMDS = [ { icon: "🚀", label: "Analyze Code", shortcut: "Ctrl+Enter", action: () => document.querySelector(".btn-primary")?.click() }, { icon: "🗑", label: "Clear Editor", shortcut: "Ctrl+K", action: () => document.querySelector(".btn-clear")?.click() }, { icon: "📋", label: "Copy Report", shortcut: "Ctrl+Shift+C", action: () => Codle.Export.copyReport() }, { icon: "📥", label: "Download Markdown", shortcut: "Ctrl+S", action: () => document.querySelector(".btn-download-md")?.click() }, { icon: "📊", label: "Download JSON", shortcut: "", action: () => document.querySelector(".btn-download-json")?.click() }, { icon: "🔄", label: "Go to Translator tab", shortcut: "", action: () => Codle.UI.switchTab(1) }, { icon: "📈", label: "Go to Complexity tab", shortcut: "", action: () => Codle.UI.switchTab(2) }, { icon: "🌊", label: "Go to Flow tab", shortcut: "", action: () => Codle.UI.switchTab(3) }, ]; let overlay, input, listEl, filtered = [...CMDS], selected = 0; function build() { if (document.getElementById("codle-cmd-overlay")) return; overlay = document.createElement("div"); overlay.id = "codle-cmd-overlay"; overlay.innerHTML = `
`; document.body.appendChild(overlay); input = overlay.querySelector("#codle-cmd-input"); listEl = overlay.querySelector("#codle-cmd-list"); overlay.addEventListener("click", e => { if (e.target === overlay) close(); }); input.addEventListener("input", () => filter(input.value)); input.addEventListener("keydown", onKey); render(); } function filter(q) { filtered = q ? CMDS.filter(c => c.label.toLowerCase().includes(q.toLowerCase())) : [...CMDS]; selected = 0; render(); } function render() { listEl.innerHTML = filtered.map((c, i) => `
${c.icon} ${c.label} ${c.shortcut ? `${c.shortcut}` : ""}
`).join(""); listEl.querySelectorAll(".cmd-item").forEach(el => { el.addEventListener("click", () => { exec(+el.dataset.idx); }); }); } function onKey(e) { if (e.key === "ArrowDown") { selected = Math.min(selected + 1, filtered.length - 1); render(); e.preventDefault(); } else if (e.key === "ArrowUp") { selected = Math.max(selected - 1, 0); render(); e.preventDefault(); } else if (e.key === "Enter") { exec(selected); } else if (e.key === "Escape") { close(); } } function exec(idx) { if (filtered[idx]) { close(); filtered[idx].action(); } } function open() { build(); overlay.classList.add("open"); input.value = ""; filter(""); setTimeout(() => input.focus(), 50); } function close() { overlay && overlay.classList.remove("open"); } function toggle() { overlay && overlay.classList.contains("open") ? close() : open(); } return { open, close, toggle }; })(); // ── Complexity Chart (Canvas 2D) ───────────────────────────────────────────── Codle.ComplexityChart = (function () { const CURVES = { "O(1)": { fn: n => 1, color: "#34d399", label: "O(1)" }, "O(log n)": { fn: n => Math.log2(n + 1), color: "#22d3ee", label: "O(log n)" }, "O(n)": { fn: n => n, color: "#818cf8", label: "O(n)" }, "O(n log n)": { fn: n => n * Math.log2(n + 1), color: "#a78bfa", label: "O(n log n)" }, "O(n²)": { fn: n => n * n, color: "#fbbf24", label: "O(n²)" }, "O(2^n)": { fn: n => Math.pow(2, n), color: "#f87171", label: "O(2^n)" }, }; function draw(canvasEl, highlight) { const W = canvasEl.width = canvasEl.offsetWidth || 600; const H = canvasEl.height = canvasEl.offsetHeight || 220; const ctx = canvasEl.getContext("2d"); const pad = { t: 20, r: 20, b: 40, l: 52 }; const cw = W - pad.l - pad.r; const ch = H - pad.t - pad.b; const N = 40; ctx.clearRect(0, 0, W, H); // Background ctx.fillStyle = "rgba(6,8,16,0.9)"; ctx.fillRect(0, 0, W, H); // Axis grid ctx.strokeStyle = "rgba(30,41,59,0.8)"; ctx.lineWidth = 1; for (let i = 0; i <= 4; i++) { const y = pad.t + (ch * i) / 4; ctx.beginPath(); ctx.moveTo(pad.l, y); ctx.lineTo(pad.l + cw, y); ctx.stroke(); } for (let i = 0; i <= 5; i++) { const x = pad.l + (cw * i) / 5; ctx.beginPath(); ctx.moveTo(x, pad.t); ctx.lineTo(x, pad.t + ch); ctx.stroke(); } // Axis labels ctx.fillStyle = "rgba(148,163,184,0.7)"; ctx.font = "11px Inter, sans-serif"; ctx.fillText("n →", pad.l + cw - 8, pad.t + ch + 32); ctx.save(); ctx.translate(14, pad.t + ch / 2); ctx.rotate(-Math.PI / 2); ctx.fillText("ops", -12, 0); ctx.restore(); // Compute max visible value (exclude O(2^n) for scaling) const maxVal = Object.values(CURVES) .filter(c => c.label !== "O(2^n)") .reduce((m, c) => Math.max(m, c.fn(N)), 0) * 1.1; const toX = n => pad.l + (n / N) * cw; const toY = v => pad.t + ch - Math.min((v / maxVal) * ch, ch); Object.entries(CURVES).forEach(([key, c]) => { const isHighlighted = highlight && key === highlight; ctx.beginPath(); ctx.strokeStyle = isHighlighted ? c.color : c.color + "66"; ctx.lineWidth = isHighlighted ? 2.5 : 1.2; ctx.shadowColor = isHighlighted ? c.color : "transparent"; ctx.shadowBlur = isHighlighted ? 10 : 0; for (let n = 0; n <= N; n++) { const x = toX(n); const y = toY(c.fn(n)); n === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); } ctx.stroke(); ctx.shadowBlur = 0; // Label at end of curve const endY = toY(c.fn(N)); if (endY > pad.t && endY < pad.t + ch) { ctx.fillStyle = isHighlighted ? c.color : c.color + "99"; ctx.font = isHighlighted ? "bold 11px Fira Code, monospace" : "10px Fira Code, monospace"; ctx.fillText(c.label, pad.l + cw + 4, Math.max(pad.t + 10, Math.min(endY + 4, pad.t + ch))); } }); } function animateDraw(canvasEl, highlight) { let frame = 0, total = 40; const loop = () => { frame++; if (frame <= total) { drawPartial(canvasEl, highlight, frame / total); requestAnimationFrame(loop); } else { draw(canvasEl, highlight); } }; loop(); } function drawPartial(canvasEl, highlight, progress) { const W = canvasEl.width = canvasEl.offsetWidth || 600; const H = canvasEl.height = canvasEl.offsetHeight || 220; const ctx = canvasEl.getContext("2d"); const pad = { t: 20, r: 20, b: 40, l: 52 }; const cw = W - pad.l - pad.r, ch = H - pad.t - pad.b, N = 40; ctx.clearRect(0, 0, W, H); ctx.fillStyle = "rgba(6,8,16,0.9)"; ctx.fillRect(0, 0, W, H); const maxVal = Object.values(CURVES).filter(c => c.label !== "O(2^n)").reduce((m, c) => Math.max(m, c.fn(N)), 0) * 1.1; const toX = n => pad.l + (n / N) * cw; const toY = v => pad.t + ch - Math.min((v / maxVal) * ch, ch); const drawN = Math.floor(N * progress); Object.entries(CURVES).forEach(([key, c]) => { ctx.beginPath(); ctx.strokeStyle = key === highlight ? c.color : c.color + "55"; ctx.lineWidth = key === highlight ? 2.5 : 1.2; for (let n = 0; n <= drawN; n++) { const x = toX(n), y = toY(c.fn(n)); n === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y); } ctx.stroke(); }); } return { draw, animateDraw }; })(); // ── Execution Flow Visualizer (Canvas 2D) ──────────────────────────────────── Codle.FlowViz = (function () { const NODE_COLORS = { start: { bg: "rgba(52,211,153,0.18)", border: "#34d399", text: "#6ee7b7" }, "return": { bg: "rgba(248,113,113,0.18)", border: "#f87171", text: "#fca5a5" }, condition: { bg: "rgba(251,191,36,0.18)", border: "#fbbf24", text: "#fde68a" }, loop: { bg: "rgba(167,139,250,0.18)", border: "#a78bfa", text: "#c4b5fd" }, function: { bg: "rgba(99,102,241,0.18)", border: "#6366f1", text: "#a5b4fc" }, call: { bg: "rgba(34,211,238,0.18)", border: "#22d3ee", text: "#67e8f9" }, assignment: { bg: "rgba(30,41,59,0.6)", border: "#475569", text: "#94a3b8" }, default: { bg: "rgba(30,41,59,0.6)", border: "#334155", text: "#94a3b8" }, }; let state = { data: null, canvas: null, ctx: null, playing: false, step: 0, speed: 800, raf: null, view: "flow" }; function loadData(data, canvasEl) { state.data = data; state.canvas = canvasEl; state.ctx = canvasEl.getContext("2d"); state.step = 0; state.playing = false; render(); } function layout(nodes) { const cols = Math.ceil(Math.sqrt(nodes.length)); const padX = 160, padY = 110, startX = 120, startY = 80; return nodes.map((n, i) => ({ ...n, x: startX + (i % cols) * padX, y: startY + Math.floor(i / cols) * padY, w: 130, h: 54, })); } function render() { if (!state.canvas || !state.data) return; const W = state.canvas.width = state.canvas.offsetWidth || 700; const H = state.canvas.height = state.canvas.offsetHeight || 420; const ctx = state.ctx; ctx.clearRect(0, 0, W, H); // Dark bg with subtle grid ctx.fillStyle = "rgba(6,8,16,0.97)"; ctx.fillRect(0, 0, W, H); ctx.strokeStyle = "rgba(30,41,59,0.4)"; ctx.lineWidth = 1; for (let x = 0; x < W; x += 40) { ctx.beginPath(); ctx.moveTo(x,0); ctx.lineTo(x,H); ctx.stroke(); } for (let y = 0; y < H; y += 40) { ctx.beginPath(); ctx.moveTo(0,y); ctx.lineTo(W,y); ctx.stroke(); } if (!state.data.nodes || !state.data.nodes.length) { ctx.fillStyle = "rgba(148,163,184,0.5)"; ctx.font = "14px Inter, sans-serif"; ctx.textAlign = "center"; ctx.fillText("No flow data. Run analysis first.", W/2, H/2); return; } const laid = layout(state.data.nodes); // Draw edges (state.data.edges || []).forEach(edge => { const from = laid.find(n => n.id === edge.from); const to = laid.find(n => n.id === edge.to); if (!from || !to) return; const sx = from.x + from.w/2, sy = from.y + from.h; const ex = to.x + to.w/2, ey = to.y; const isActive = state.step > 0 && (from.id <= state.step && to.id <= state.step + 1); ctx.beginPath(); ctx.moveTo(sx, sy); ctx.bezierCurveTo(sx, sy + 30, ex, ey - 30, ex, ey); ctx.strokeStyle = isActive ? "rgba(99,102,241,0.85)" : "rgba(51,65,85,0.6)"; ctx.lineWidth = isActive ? 2 : 1.2; ctx.shadowColor = isActive ? "#6366f1" : "transparent"; ctx.shadowBlur = isActive ? 8 : 0; ctx.stroke(); ctx.shadowBlur = 0; // Arrowhead const angle = Math.atan2(ey - (sy+30), ex - (sx+30)); ctx.beginPath(); ctx.moveTo(ex, ey); ctx.lineTo(ex - 9*Math.cos(angle-0.4), ey - 9*Math.sin(angle-0.4)); ctx.lineTo(ex - 9*Math.cos(angle+0.4), ey - 9*Math.sin(angle+0.4)); ctx.closePath(); ctx.fillStyle = isActive ? "rgba(99,102,241,0.85)" : "rgba(51,65,85,0.6)"; ctx.fill(); // Edge label if (edge.label) { ctx.fillStyle = "rgba(148,163,184,0.7)"; ctx.font = "10px Inter, sans-serif"; ctx.textAlign = "center"; ctx.fillText(edge.label, (sx+ex)/2, (sy+ey)/2); } }); // Draw nodes laid.forEach(n => { const col = NODE_COLORS[n.type] || NODE_COLORS.default; const active = state.step > 0 && n.id === state.step; const visited = state.step > 0 && n.id < state.step; // Shadow glow on active if (active) { ctx.shadowColor = col.border; ctx.shadowBlur = 18; } // Card bg ctx.fillStyle = active ? col.bg.replace("0.18","0.38") : col.bg; roundRect(ctx, n.x, n.y, n.w, n.h, 10); ctx.fill(); // Card border ctx.strokeStyle = active ? col.border : (visited ? col.border + "77" : "rgba(30,41,59,0.8)"); ctx.lineWidth = active ? 2 : 1; roundRect(ctx, n.x, n.y, n.w, n.h, 10); ctx.stroke(); ctx.shadowBlur = 0; // Label ctx.fillStyle = active ? col.text : (visited ? col.text + "cc" : "rgba(148,163,184,0.7)"); ctx.font = active ? "bold 12px Inter, sans-serif" : "12px Inter, sans-serif"; ctx.textAlign = "center"; ctx.fillText(truncate(n.label, 18), n.x + n.w/2, n.y + n.h/2 - 6); // Detail ctx.fillStyle = "rgba(148,163,184,0.55)"; ctx.font = "10px Inter, sans-serif"; ctx.fillText(truncate(n.detail, 22), n.x + n.w/2, n.y + n.h/2 + 9); // Line number badge ctx.fillStyle = "rgba(99,102,241,0.5)"; ctx.font = "9px Fira Code, monospace"; ctx.fillText("L" + n.line, n.x + n.w - 18, n.y + 12); }); // Title if (state.data.title) { ctx.fillStyle = "rgba(165,180,252,0.7)"; ctx.font = "12px Inter, sans-serif"; ctx.textAlign = "left"; ctx.fillText("⟶ " + state.data.title, 14, 20); } } function roundRect(ctx, x, y, w, h, r) { ctx.beginPath(); ctx.moveTo(x + r, y); ctx.lineTo(x + w - r, y); ctx.quadraticCurveTo(x + w, y, x + w, y + r); ctx.lineTo(x + w, y + h - r); ctx.quadraticCurveTo(x + w, y + h, x + w - r, y + h); ctx.lineTo(x + r, y + h); ctx.quadraticCurveTo(x, y + h, x, y + h - r); ctx.lineTo(x, y + r); ctx.quadraticCurveTo(x, y, x + r, y); ctx.closePath(); } function truncate(s, len) { return s && s.length > len ? s.slice(0, len) + "…" : (s || ""); } function play() { if (!state.data) return; state.playing = true; const total = state.data.nodes.length; function tick() { if (!state.playing) return; state.step = (state.step % (total + 1)) + 1; render(); if (state.step <= total) state.raf = setTimeout(tick, state.speed); else { state.playing = false; state.step = 0; render(); } } tick(); } function pause() { state.playing = false; clearTimeout(state.raf); } function replay() { pause(); state.step = 0; render(); setTimeout(play, 200); } function setSpeed(v) { state.speed = Math.max(200, 1600 - v * 14); } function stepFwd() { if (!state.data) return; state.step = Math.min(state.step + 1, state.data.nodes.length); render(); } function stepBack() { if (!state.data) return; state.step = Math.max(state.step - 1, 0); render(); } return { loadData, render, play, pause, replay, setSpeed, stepFwd, stepBack }; })(); // ── Export utilities ───────────────────────────────────────────────────────── Codle.Export = (function () { function getOutputText() { const el = document.querySelector(".output-panel, .prose"); return el ? el.innerText : ""; } function copyReport() { const text = getOutputText(); if (!text || text.includes("Results will appear here")) { Codle.Toast.info("No report to copy yet."); return; } navigator.clipboard.writeText(text) .then(() => Codle.Toast.success("Report copied to clipboard!")) .catch(() => Codle.Toast.error("Clipboard access denied.")); } function downloadMarkdown(text, filename) { if (!text || text.includes("Results will appear here")) { Codle.Toast.info("No report to export yet."); return; } const blob = new Blob([text], { type: "text/markdown;charset=utf-8" }); triggerDownload(blob, filename || "codle_report.md"); Codle.Toast.success("Markdown report downloaded!"); } function downloadJSON(data, filename) { const blob = new Blob([JSON.stringify(data, null, 2)], { type: "application/json" }); triggerDownload(blob, filename || "codle_report.json"); Codle.Toast.success("JSON report downloaded!"); } function screenshotReport() { Codle.Toast.info("Use browser Print (Ctrl+P) → Save as PDF for a full-page screenshot."); } function triggerDownload(blob, name) { const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = name; a.click(); setTimeout(() => URL.revokeObjectURL(a.href), 1000); } return { copyReport, downloadMarkdown, downloadJSON, screenshotReport, getOutputText }; })(); // ── UI helpers ─────────────────────────────────────────────────────────────── Codle.UI = (function () { function switchTab(idx) { const tabs = document.querySelectorAll(".tab-nav button"); if (tabs[idx]) tabs[idx].click(); } function showSkeleton(containerId) { const el = document.getElementById(containerId); if (!el) return; el.innerHTML = `
`; } function hideSkeleton(containerId) { const el = document.getElementById(containerId); if (el) el.innerHTML = ""; } return { switchTab, showSkeleton, hideSkeleton }; })(); // ── Keyboard shortcuts ─────────────────────────────────────────────────────── Codle.Shortcuts = (function () { function init() { document.addEventListener("keydown", e => { const ctrl = e.ctrlKey || e.metaKey; // Ctrl+Enter → Analyze if (ctrl && e.key === "Enter") { e.preventDefault(); document.querySelector(".btn-primary")?.click(); Codle.Toast.info("Analyzing…"); } // Ctrl+K → Clear if (ctrl && e.key === "k") { e.preventDefault(); document.querySelector(".btn-clear")?.click(); } // Ctrl+Shift+P or Ctrl+/ → Command palette if ((ctrl && e.shiftKey && e.key === "P") || (ctrl && e.key === "/")) { e.preventDefault(); Codle.CommandPalette.toggle(); } // Ctrl+S → download markdown if (ctrl && e.key === "s") { e.preventDefault(); document.querySelector(".btn-download-md")?.click(); } // Ctrl+Shift+C → copy report if (ctrl && e.shiftKey && e.key === "C") { e.preventDefault(); Codle.Export.copyReport(); } // Escape → close command palette if (e.key === "Escape") { Codle.CommandPalette.close(); } }); } return { init }; })(); // ── Boot ───────────────────────────────────────────────────────────────────── Codle.boot = function () { Codle.Particles.init(); Codle.Shortcuts.init(); // Removed startup toast — cleaner UX }; // Boot when DOM is ready if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", Codle.boot); } else { setTimeout(Codle.boot, 100); }