// DevPulse Landing Page Controller
// Uses Lucide icons (loaded via CDN in HTML)
interface SimState {
activeTab: string;
prStatus: "open" | "approved" | "changes_requested" | "merged";
repoTab: string;
}
const state: SimState = {
activeTab: "pr",
prStatus: "open",
repoTab: "overview",
};
// All 21 commands data
const commands = [
{
name: "/repo",
desc: "Repository intelligence, health score, dependencies, growth",
icon: "book-open",
color: "gh-blue",
},
{
name: "/pr",
desc: "PR details, CI status, approve, request changes, merge",
icon: "git-pull-request",
color: "gh-green",
},
{
name: "/search",
desc: "Search code, issues, PRs, repos, commits with qualifiers",
icon: "search",
color: "gh-purple",
},
{
name: "/code",
desc: "View files, git blame, commit-to-PR resolution",
icon: "code",
color: "gh-blue",
},
{
name: "/investigate",
desc: "Full lifecycle: issue → commit → PR → review → merge → release",
icon: "git-merge",
color: "gh-purple",
},
{
name: "/activity",
desc: "Commit histograms, churn analysis, personal telemetry",
icon: "bar-chart-2",
color: "gh-green",
},
{
name: "/trending",
desc: "Trending repos across TypeScript, Rust, AI/ML, Go, Python",
icon: "trending-up",
color: "gh-amber",
},
{
name: "/watch",
desc: "Subscribe channels to releases, PRs, issues, security alerts",
icon: "bell",
color: "gh-blue",
},
{
name: "/actions",
desc: "CI/CD workflow trees, run history, rerun/cancel controls",
icon: "play-circle",
color: "gh-green",
},
{
name: "/security",
desc: "Dependabot alerts, CVE lookup, real-time secret scanner",
icon: "shield-check",
color: "gh-coral",
},
{
name: "/release",
desc: "Latest releases, tag comparison, auto changelog generator",
icon: "tag",
color: "gh-purple",
},
{
name: "/team",
desc: "Team velocity, cycle times, review load balancing",
icon: "users",
color: "gh-blue",
},
{
name: "/home",
desc: "Morning cockpit: active PRs, reviews, alerts, trending",
icon: "home",
color: "gh-green",
},
{
name: "/connect",
desc: "AES-256-GCM encrypted PAT storage for write actions",
icon: "link",
color: "gh-blue",
},
{
name: "/why",
desc: "Trace file/line → commit → PR → originating issue",
icon: "help-circle",
color: "gh-purple",
},
{
name: "/ask",
desc: "Query engine: stale PRs, bottlenecks, frequent files",
icon: "message-circle",
color: "gh-green",
},
{
name: "/tools",
desc: "JSON formatter, base64, JWT inspector, hash, timestamps",
icon: "wrench",
color: "gh-amber",
},
{
name: "/settings",
desc: "Server config: repos, channels, access controls",
icon: "settings",
color: "gh-blue",
},
{
name: "/help",
desc: "Interactive command directory with examples",
icon: "info",
color: "gh-green",
},
{
name: "/ai",
desc: "AI code explanations, PR summaries, bug fix suggestions",
icon: "brain",
color: "gh-coral",
},
{
name: "/code-review",
desc: "Automated PR review: security, performance, style",
icon: "eye",
color: "gh-purple",
},
];
// Initialize everything
document.addEventListener("DOMContentLoaded", () => {
// Initialize Lucide icons
if (typeof lucide !== "undefined") {
lucide.createIcons();
}
// Build commands grid
buildCommandsGrid();
// Initialize simulator
initSimulator();
// Initialize navbar
initNavbar();
// Initialize mobile menu
initMobileMenu();
// Initialize copy buttons
initCopyButtons();
// Initialize scroll effects
initScrollEffects();
});
// Build the 21 commands grid
function buildCommandsGrid() {
const grid = document.getElementById("commands-grid");
if (!grid) return;
grid.innerHTML = commands
.map(
(cmd) => `
`,
)
.join("");
// Re-initialize icons for new elements
if (typeof lucide !== "undefined") {
lucide.createIcons();
}
}
// Simulator
function initSimulator() {
renderSimulator();
attachSimulatorEvents();
}
function renderSimulator() {
const container = document.getElementById("simulator-body");
if (!container) return;
const templates: Record string> = {
pr: () => renderPREmbed(),
repo: () => renderRepoEmbed(),
actions: () => renderActionsEmbed(),
investigate: () => renderInvestigateEmbed(),
code: () => renderCodeEmbed(),
activity: () => renderActivityEmbed(),
};
const renderer = templates[state.activeTab];
if (renderer) {
container.style.opacity = "0";
container.style.transform = "translateY(8px)";
container.style.transition = "opacity 0.15s ease, transform 0.15s ease";
requestAnimationFrame(() => {
container.innerHTML = renderer();
requestAnimationFrame(() => {
container.style.opacity = "1";
container.style.transform = "translateY(0)";
});
});
}
}
function renderPREmbed() {
const statusMap = {
open: { text: "Ready to merge", color: "gh-green", icon: "check-circle" },
approved: { text: "Approved by you", color: "gh-green", icon: "check-circle" },
changes_requested: { text: "Changes Requested", color: "gh-coral", icon: "alert-triangle" },
merged: { text: "Merged into main", color: "gh-purple", icon: "git-merge" },
};
const status = statusMap[state.prStatus];
return `
DevPulse
Bot
Today at 10:42 AM
/pr 142 repo:vercel/next.js
vercel/next.js • Pull Request #142
feat(router): optimize parallel route cache hydration
${status.text}
@timneutkens • 14 files • +482 -193
Reviews
${state.prStatus === "approved" ? "3/2" : "2/2"} (Required met)
${
state.prStatus === "merged"
? ``
: ``
}
`;
}
function renderRepoEmbed() {
return `
DevPulse
Bot
Today at 10:45 AM
/repo name:vercel/next.js
vercel/next.js
The React Framework for the Web. Health Score: 96/100
Stars & Forks
124,510 • 26,840
`;
}
function renderActionsEmbed() {
return `
DevPulse
Bot
Today at 10:48 AM
/actions repo:vercel/next.js branch:canary
canary • Run #4,892 Passed
├── Unit Tests (Node 20 & 22) Passing (1m 42s)
├── Turbopack Integration E2E Passing (4m 15s)
├── Edge Runtime Compatibility Passing (52s)
└── Security Scanning & Audit Passing (38s)
`;
}
function renderInvestigateEmbed() {
return `
DevPulse
Bot
Today at 10:50 AM
/investigate pr:142 repo:vercel/next.js
Full Development Trace
1. Issue #892 created by @alex (Sep 1)
2. Branch feature/route-cache (Sep 2)
3. Commit 8f3b12a by @timneutkens
4. PR #142 opened (Sep 2)
5. CI: 12 checks passed (Sep 2)
6. Approved by @sokra & @kdy1 (Sep 3)
7. Merged into canary (Sep 3)
8. Shipped in v15.2.0-canary.18 (Sep 4)
`;
}
function renderCodeEmbed() {
return `
DevPulse
Bot
Today at 10:52 AM
/code blame file:packages/next/src/server/route-matcher.ts lines:40-46
route-matcher.ts (Lines 40-46)
40 | export function matchRoute(url: string, routes: Route[]): Route | null {
41 | // [e71ab82 @timneutkens 3d ago] Fast-path lookup
42 | const exact = exactMap.get(url);
43 | if (exact) return exact;
44 | // [8f3b12a @sokra 1w ago] Fallback evaluator
45 | return evaluateRegexRoutes(url, routes);
46 | }
Commit 8f3b12a originated from PR #138 merged Aug 28.
`;
}
function renderActivityEmbed() {
return `
DevPulse
Bot
Today at 10:55 AM
/activity me days:7
Personal Activity Profile (@swadhin)
48 commits • 6 PRs authored • 14 PRs reviewed
Mon ██████████████ 14
Tue ██████████ 10
Wed ██████ 6
Thu ████████████ 12
Fri ████ 4
Sat █ 1
Sun █ 1
Churn Ratio
+2,140 / -840 (2.5x)
`;
}
function attachSimulatorEvents() {
// Tab switching
const tabs = document.querySelectorAll(".sim-tab-btn");
for (const tab of tabs) {
tab.addEventListener("click", () => {
for (const t of tabs) t.classList.remove("active");
tab.classList.add("active");
const target = tab.getAttribute("data-tab");
if (target) {
state.activeTab = target;
renderSimulator();
}
});
}
// PR simulator buttons
document.addEventListener("click", (e) => {
const target = e.target as HTMLElement;
const btn = target.closest("button");
if (!btn) return;
if (btn.id === "sim-btn-approve") {
state.prStatus = "approved";
renderSimulator();
} else if (btn.id === "sim-btn-changes") {
state.prStatus = "changes_requested";
renderSimulator();
} else if (btn.id === "sim-btn-merge") {
btn.innerHTML = ` Merging...`;
if (typeof lucide !== "undefined") lucide.createIcons();
setTimeout(() => {
state.prStatus = "merged";
renderSimulator();
}, 800);
} else if (btn.id === "sim-btn-rerun") {
const original = btn.innerHTML;
btn.innerHTML = ` Dispatching...`;
if (typeof lucide !== "undefined") lucide.createIcons();
setTimeout(() => {
btn.innerHTML = ` Triggered!`;
if (typeof lucide !== "undefined") lucide.createIcons();
setTimeout(() => {
btn.innerHTML = original;
if (typeof lucide !== "undefined") lucide.createIcons();
}, 2000);
}, 1000);
} else if (btn.id === "sim-btn-copy-trace") {
const text = "Issue #892 → PR #142 → v15.2.0-canary.18 (Commit 8f3b12a)";
navigator.clipboard.writeText(text).then(() => {
const original = btn.innerHTML;
btn.innerHTML = ` Copied!`;
if (typeof lucide !== "undefined") lucide.createIcons();
setTimeout(() => {
btn.innerHTML = original;
if (typeof lucide !== "undefined") lucide.createIcons();
}, 2000);
});
}
});
}
// Navbar scroll effect
function initNavbar() {
const navbar = document.getElementById("navbar");
if (!navbar) return;
let ticking = false;
const onScroll = () => {
if (!ticking) {
requestAnimationFrame(() => {
if (window.scrollY > 10) {
navbar.classList.add("navbar-scrolled");
} else {
navbar.classList.remove("navbar-scrolled");
}
ticking = false;
});
ticking = true;
}
};
window.addEventListener("scroll", onScroll, { passive: true });
}
// Mobile menu toggle
function initMobileMenu() {
const btn = document.getElementById("mobile-menu-btn");
const menu = document.getElementById("mobile-menu");
if (btn && menu) {
btn.addEventListener("click", () => {
menu.classList.toggle("hidden");
});
// Close on link click
const links = menu.querySelectorAll("a");
for (const link of links) {
link.addEventListener("click", () => {
menu.classList.add("hidden");
});
}
}
}
// Copy buttons
function initCopyButtons() {
const copyAllBtn = document.getElementById("copy-all-btn");
if (copyAllBtn) {
copyAllBtn.addEventListener("click", () => {
const commands = `git clone https://github.com/husteno-o/git-discord-bot.git\ncd git-discord-bot\nbun install\ncp .env.example .env\nbun run start`;
navigator.clipboard.writeText(commands).then(() => {
const original = copyAllBtn.innerHTML;
copyAllBtn.innerHTML = ` Copied!`;
if (typeof lucide !== "undefined") lucide.createIcons();
setTimeout(() => {
copyAllBtn.innerHTML = original;
if (typeof lucide !== "undefined") lucide.createIcons();
}, 2000);
});
});
}
}
// Scroll effects
function initScrollEffects() {
// Intersection observer for reveal animations
const reveals = document.querySelectorAll(".reveal");
if (reveals.length > 0) {
const observer = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add("visible");
observer.unobserve(entry.target);
}
}
},
{ threshold: 0.1, rootMargin: "0px 0px -40px 0px" },
);
for (const el of reveals) observer.observe(el);
}
}