index.html
Rama main · réplica de lectura del repositorio original.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Tera Pilot — Private, Vendor-Neutral Coding Agents</title>
<meta name="description" content="Self-hosted, vendor-neutral coding agent for private repos and local models. TUI-first, BYOK across 17 providers or fully offline with Ollama/LM Studio. Commands run in an OS-level sandbox; every agent action is logged and Ed25519-signed — built to be verified, not just trusted.">
<link rel="icon" href="./tera_pilot_logo.png" type="image/png">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=DM+Mono:wght@400;500&display=swap" rel="stylesheet">
<style>
:root {
--bg: #0a0a0a;
--bg-soft: #0c0c0f;
--card: #101013;
--card-hover: #141418;
--border: #1f1f24;
--border-soft: #19191d;
--text: #f5f5f4;
--text-muted: #a1a1aa;
--text-dim: #71717a;
--green: #28c840;
--green-dim: #1a7a2e;
--green-bg: rgba(40, 200, 64, 0.08);
--green-glow: rgba(40, 200, 64, 0.6);
--blue: #60a5fa;
--yellow: #febc2e;
--red: #ef4444;
--radius: 16px;
--radius-sm: 10px;
--font-sans: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
--font-mono: "DM Mono", ui-monospace, SFMono-Regular, Menlo, monospace;
--card-glass: linear-gradient(180deg, rgba(255,255,255,0.03), rgba(255,255,255,0) 38%), linear-gradient(180deg, rgba(17,17,21,0.92), rgba(11,11,14,0.9));
}
* { margin: 0; padding: 0; box-sizing: border-box; }
html { scroll-behavior: smooth; }
body {
background: linear-gradient(180deg, #0b0b0e 0%, var(--bg) 40%, #07070a 100%);
color: var(--text);
font-family: var(--font-sans);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
.container { max-width: 1120px; margin: 0 auto; padding: 0 24px; }
/* ---------- Dynamic background ---------- */
.bg-orbs { position: fixed; inset: -80px; z-index: -1; overflow: hidden; pointer-events: none; will-change: transform; }
/* Film grain — subtle texture over the whole page */
.bg-grain {
position: fixed; inset: 0; z-index: -1; pointer-events: none; opacity: 0.05;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='0.7'/%3E%3C/svg%3E");
mix-blend-mode: overlay;
}
/* Soft light pooling behind the hero */
.bg-hero-glow {
position: fixed; top: -34vh; left: 50%; transform: translateX(-50%);
width: 130vw; height: 92vh; z-index: -1; pointer-events: none;
background: radial-gradient(50% 52% at 50% 42%, rgba(40,200,64,0.075), rgba(96,165,250,0.03) 55%, transparent 75%);
}
/* Drifting motes (spawned by JS, gated on prefers-reduced-motion) */
.particle {
position: absolute; border-radius: 50%; pointer-events: none; opacity: 0;
background: radial-gradient(circle, rgba(255,255,255,0.95), rgba(40,200,64,0.55) 55%, transparent 75%);
box-shadow: 0 0 8px rgba(40,200,64,0.5);
animation: floatUp linear infinite;
}
@keyframes floatUp {
0% { transform: translate3d(0, 0, 0) scale(1); opacity: 0; }
8% { opacity: var(--po, 0.4); }
85% { opacity: var(--po, 0.4); }
100% { transform: translate3d(var(--px, 0px), -112vh, 0) scale(0.55); opacity: 0; }
}
.orb { position: absolute; border-radius: 50%; will-change: transform; }
.orb-1 {
width: 640px; height: 640px; top: -180px; left: -140px;
background: radial-gradient(circle at 30% 30%, rgba(40,200,64,0.30), transparent 65%);
filter: blur(90px); animation: drift1 26s ease-in-out infinite alternate;
}
.orb-2 {
width: 540px; height: 540px; top: 28%; right: -180px;
background: radial-gradient(circle at 60% 40%, rgba(96,165,250,0.24), transparent 65%);
filter: blur(90px); animation: drift2 32s ease-in-out infinite alternate;
}
.orb-3 {
width: 500px; height: 500px; bottom: -160px; left: 28%;
background: radial-gradient(circle at 40% 60%, rgba(254,188,46,0.16), transparent 60%);
filter: blur(100px); animation: drift3 38s ease-in-out infinite alternate;
}
@keyframes drift1 { to { transform: translate(130px, 90px) scale(1.15); } }
@keyframes drift2 { to { transform: translate(-150px, -70px) scale(1.1); } }
@keyframes drift3 { to { transform: translate(90px, -130px) scale(1.2); } }
body::before {
content: ""; position: fixed; inset: 0; z-index: -1; pointer-events: none;
background-image:
linear-gradient(rgba(255,255,255,0.028) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.028) 1px, transparent 1px);
background-size: 56px 56px;
-webkit-mask-image: radial-gradient(ellipse 90% 70% at 50% 0%, #000 35%, transparent 78%);
mask-image: radial-gradient(ellipse 90% 70% at 50% 0%, #000 35%, transparent 78%);
}
/* ---------- Scroll progress ---------- */
.scroll-progress {
position: fixed; top: 0; left: 0; height: 2px; width: 0;
background: linear-gradient(90deg, var(--green), var(--blue));
box-shadow: 0 0 10px rgba(40,200,64,0.55);
z-index: 70; transition: width 0.1s linear;
}
/* ---------- Entrance & reveal ---------- */
.hero-anim { opacity: 0; animation: fade-up 0.7s cubic-bezier(.22,.61,.36,1) forwards; }
.hero-anim.d1 { animation-delay: 0.08s; } .hero-anim.d2 { animation-delay: 0.16s; }
.hero-anim.d3 { animation-delay: 0.24s; } .hero-anim.d4 { animation-delay: 0.34s; }
.hero-anim.d5 { animation-delay: 0.46s; }
@keyframes fade-up { from { opacity: 0; transform: translateY(18px); } to { opacity: 1; transform: none; } }
.reveal {
opacity: 0; transform: translateY(24px); filter: blur(7px);
transition: opacity 0.6s cubic-bezier(.22,.61,.36,1), transform 0.6s cubic-bezier(.22,.61,.36,1), filter 0.6s cubic-bezier(.22,.61,.36,1);
transition-delay: var(--d, 0s);
}
.reveal.visible { opacity: 1; transform: none; filter: blur(0); }
/* ---------- Accent shimmer ---------- */
.hero h1 .accent {
background: linear-gradient(90deg, #28c840, #8fe89b, #28c840);
background-size: 200% 100%;
-webkit-background-clip: text; background-clip: text;
color: transparent;
animation: shine 6s linear infinite;
}
@keyframes shine { to { background-position: 200% 0; } }
/* ---------- Terminal effects ---------- */
.terminal {
box-shadow: 0 24px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(40,200,64,0.07), 0 0 60px rgba(40,200,64,0.06);
animation: floaty 7s ease-in-out infinite, glowPulse 5.5s ease-in-out infinite;
transform-style: preserve-3d;
}
.terminal.tilting { animation: glowPulse 5.5s ease-in-out infinite; transition: transform 0.18s ease-out; }
@keyframes floaty { 0%,100% { transform: translateY(0); } 50% { transform: translateY(-7px); } }
@keyframes glowPulse {
0%,100% { box-shadow: 0 24px 60px rgba(0,0,0,0.5), 0 0 0 1px rgba(40,200,64,0.07), 0 0 60px rgba(40,200,64,0.06); }
50% { box-shadow: 0 24px 64px rgba(0,0,0,0.5), 0 0 0 1px rgba(40,200,64,0.13), 0 0 84px rgba(40,200,64,0.14); }
}
.terminal-body .line-fade { opacity: 0; transform: translateY(4px); transition: opacity 0.45s ease, transform 0.45s ease; }
.terminal-body .lf1 { transition-delay: 0.12s; }
.terminal-body .lf2 { transition-delay: 0.24s; }
.terminal-body .lf3 { transition-delay: 0.36s; }
.terminal-body .lf4 { transition-delay: 0.48s; }
.terminal-body .lf5 { transition-delay: 0.60s; }
.terminal-body .lf6 { transition-delay: 0.72s; }
.terminal-body .lf7 { transition-delay: 0.84s; }
.terminal-body .lf8 { transition-delay: 0.96s; }
.terminal-body.typed-done .line-fade { opacity: 1; transform: none; }
/* ---------- Hover effects ---------- */
.card { transition: border-color 0.2s ease, background 0.2s ease, transform 0.2s ease, box-shadow 0.2s ease; }
.card:hover {
transform: translateY(-4px);
border-color: rgba(40,200,64,0.35);
box-shadow: 0 14px 36px rgba(0,0,0,0.45), 0 0 0 1px rgba(40,200,64,0.10), 0 0 38px rgba(40,200,64,0.07);
}
.card .icon { transition: transform 0.25s ease; }
.card:hover .icon { transform: scale(1.1) rotate(-4deg); }
.btn-primary { position: relative; overflow: hidden; }
.btn-primary::after {
content: ""; position: absolute; top: 0; left: -80%; width: 45%; height: 100%;
background: linear-gradient(100deg, transparent, rgba(255,255,255,0.4), transparent);
transform: skewX(-20deg); transition: left 0.5s ease;
}
.btn-primary:hover::after { left: 135%; }
.flow-step .num { animation: pulse-soft 3.2s ease-in-out infinite; }
@keyframes pulse-soft {
0%,100% { box-shadow: 0 0 0 0 rgba(40,200,64,0.35); }
50% { box-shadow: 0 0 0 8px rgba(40,200,64,0); }
}
.provider { position: relative; overflow: hidden; transition: transform 0.16s ease, border-color 0.16s ease, background 0.16s ease; }
.provider::before {
content: ""; position: absolute; left: 0; top: 22%; bottom: 22%; width: 3px;
border-radius: 0 3px 3px 0;
background: linear-gradient(180deg, var(--green), rgba(96,165,250,0.7));
box-shadow: 0 0 10px rgba(40,200,64,0.5);
opacity: 0; transform: scaleY(0.3); transition: opacity .25s ease, transform .25s ease;
}
.provider:hover { transform: translateX(5px); border-color: rgba(40,200,64,0.35); background: var(--card-hover); }
.provider:hover::before { opacity: 1; transform: scaleY(1); }
.chip { transition: border-color 0.2s ease, transform 0.2s ease, color 0.2s ease; }
.chip:hover { border-color: rgba(40,200,64,0.4); color: var(--text); transform: translateY(-2px); }
.faq-item summary { transition: color 0.2s ease; }
.faq-item summary:hover { color: var(--green); }
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.001s !important; animation-iteration-count: 1 !important; transition-duration: 0.001s !important; }
.hero-anim { opacity: 1 !important; transform: none !important; filter: none !important; }
.reveal { opacity: 1 !important; transform: none !important; filter: none !important; }
.chip { opacity: 1 !important; animation: none !important; }
.bg-orbs, .orb, .particle, .bg-grain { display: none; }
}
/* ---------- Nav ---------- */
nav {
position: sticky; top: 0; z-index: 50;
background: rgba(10, 10, 10, 0.78);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--border-soft);
transition: box-shadow 0.3s ease, background 0.3s ease;
}
nav.scrolled { background: rgba(9, 9, 11, 0.88); box-shadow: 0 10px 30px rgba(0,0,0,0.35); }
.nav-inner {
display: flex; align-items: center; justify-content: space-between;
height: 64px;
}
.brand { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 17px; color: var(--text); }
.brand img { width: 30px; height: 30px; border-radius: 8px; }
.nav-links { display: flex; align-items: center; gap: 26px; font-size: 14px; color: var(--text-muted); }
.nav-links a { position: relative; color: var(--text-muted); }
.nav-links a::after {
content: ""; position: absolute; left: 0; right: 0; bottom: -6px; height: 2px;
border-radius: 2px; background: linear-gradient(90deg, var(--green), var(--blue));
transform: scaleX(0); transform-origin: left; transition: transform 0.25s ease;
}
.nav-links a:hover, .nav-links a.active { color: var(--text); text-decoration: none; }
.nav-links a:hover::after, .nav-links a.active::after { transform: scaleX(1); }
.nav-cta { display: flex; align-items: center; gap: 14px; }
.nav-burger { display: none; background: none; border: 1px solid var(--border); color: var(--text); border-radius: 8px; padding: 6px 10px; font-size: 16px; cursor: pointer; }
/* ---------- Buttons ---------- */
.btn {
display: inline-flex; align-items: center; gap: 8px;
font-family: var(--font-sans); font-size: 14px; font-weight: 600;
border-radius: 10px; padding: 11px 18px; cursor: pointer;
border: 1px solid transparent; transition: all .15s ease;
white-space: nowrap;
}
.btn-primary {
background: linear-gradient(180deg, #2fd14d, #1fae3a);
color: #04170a;
box-shadow: 0 8px 22px rgba(40,200,64,0.26), inset 0 1px 0 rgba(255,255,255,0.25);
}
.btn-primary:hover {
background: linear-gradient(180deg, #3ade5b, #23ba41);
transform: translateY(-2px);
box-shadow: 0 12px 30px rgba(40,200,64,0.36), inset 0 1px 0 rgba(255,255,255,0.28);
text-decoration: none;
}
.btn-ghost { background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.01)); color: var(--text); border-color: var(--border); }
.btn-ghost:hover { border-color: #3a3a42; background: var(--card-hover); transform: translateY(-2px); text-decoration: none; }
.btn-sm { padding: 8px 13px; font-size: 13px; }
/* ---------- Hero ---------- */
.hero { padding: 88px 0 72px; text-align: center; position: relative; }
.hero-badge {
display: inline-flex; align-items: center; gap: 8px;
border: 1px solid var(--border);
background: linear-gradient(180deg, rgba(255,255,255,0.035), transparent), var(--bg-soft);
backdrop-filter: blur(8px); -webkit-backdrop-filter: blur(8px);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), 0 8px 24px rgba(0,0,0,0.3);
color: var(--text-muted); font-size: 13px;
padding: 6px 14px; border-radius: 999px; margin-bottom: 28px;
}
.hero-badge .dot { width: 7px; height: 7px; border-radius: 50%; background: var(--green); box-shadow: 0 0 10px var(--green); }
.hero h1 {
font-size: clamp(38px, 6vw, 62px); font-weight: 800; letter-spacing: -0.03em;
line-height: 1.08; margin-bottom: 20px;
}
.hero h1 .accent { color: var(--green); }
.hero p.sub {
max-width: 680px; margin: 0 auto 34px;
color: var(--text-muted); font-size: clamp(16px, 2vw, 19px);
}
.hero-cta { display: flex; gap: 14px; justify-content: center; flex-wrap: wrap; margin-bottom: 48px; }
/* Terminal window */
.terminal {
max-width: 680px; margin: 0 auto; text-align: left;
background: var(--bg-soft); border: 1px solid var(--border);
border-radius: var(--radius); overflow: hidden;
box-shadow: 0 24px 60px rgba(0,0,0,0.5);
}
.terminal-bar {
display: flex; align-items: center; gap: 7px;
padding: 12px 16px; border-bottom: 1px solid var(--border-soft);
background: #0e0e11;
}
.terminal-bar .dot { width: 11px; height: 11px; border-radius: 50%; }
.dot-red { background: #ff5f57; } .dot-yellow { background: #febc2e; } .dot-green { background: #28c840; }
.terminal-bar .title { margin-left: 10px; color: var(--text-dim); font-family: var(--font-mono); font-size: 12px; }
.terminal-body { padding: 20px 22px; font-family: var(--font-mono); font-size: 13.5px; line-height: 1.85; }
.terminal-body .cmd { color: var(--text); }
.terminal-body .cmd::before { content: "$ "; color: var(--green); font-weight: 500; }
.terminal-body .out { color: var(--text-dim); }
.terminal-body .ok { color: var(--green); }
.terminal-body .comment { color: var(--text-dim); }
.cursor { display: inline-block; width: 8px; height: 15px; background: var(--green); vertical-align: middle; animation: blink 1.1s steps(1) infinite; }
@keyframes blink { 50% { opacity: 0; } }
/* Badge strip under hero */
.badge-strip {
display: flex; justify-content: center; flex-wrap: wrap; gap: 10px;
margin-top: 44px;
}
.chip {
font-size: 13px; color: var(--text-muted);
border: 1px solid var(--border); background: var(--bg-soft);
padding: 6px 14px; border-radius: 999px;
opacity: 0; animation: fade-up 0.6s cubic-bezier(.22,.61,.36,1) backwards;
}
.chip:nth-child(1) { animation-delay: .56s; } .chip:nth-child(2) { animation-delay: .62s; }
.chip:nth-child(3) { animation-delay: .68s; } .chip:nth-child(4) { animation-delay: .74s; }
.chip:nth-child(5) { animation-delay: .80s; } .chip:nth-child(6) { animation-delay: .86s; }
.chip:nth-child(7) { animation-delay: .92s; } .chip:nth-child(8) { animation-delay: .98s; }
.chip b { color: var(--text); font-weight: 600; }
/* ---------- Sections ---------- */
section { padding: 84px 0; }
section.alt { background: rgba(12,12,15,0.72); border-top: 1px solid var(--border-soft); border-bottom: 1px solid var(--border-soft); }
.sec-head { text-align: center; max-width: 720px; margin: 0 auto 52px; }
.sec-head h2 { font-size: clamp(28px, 4vw, 40px); font-weight: 800; letter-spacing: -0.02em; margin-bottom: 18px; }
.sec-head h2::after {
content: ""; display: block; width: 58px; height: 3px; margin: 16px auto 0;
border-radius: 3px;
background: linear-gradient(90deg, var(--green), var(--blue));
box-shadow: 0 0 12px rgba(40,200,64,0.4);
}
.sec-head p { color: var(--text-muted); font-size: 16px; }
.sec-head .kicker {
display: inline-block; padding: 5px 13px; border-radius: 999px;
background: var(--green-bg); border: 1px solid rgba(40,200,64,0.22);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.05);
font-family: var(--font-mono); font-size: 12.5px; text-transform: uppercase;
letter-spacing: 0.14em; color: var(--green); margin-bottom: 14px;
}
/* ---------- Cards / grids ---------- */
.grid { display: grid; gap: 16px; }
.grid-3 { grid-template-columns: repeat(3, 1fr); }
.grid-4 { grid-template-columns: repeat(4, 1fr); }
.grid-2 { grid-template-columns: repeat(2, 1fr); }
.card {
position: relative; overflow: hidden;
background: var(--card-glass);
border: 1px solid var(--border-soft);
border-radius: var(--radius); padding: 24px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.045), 0 1px 0 rgba(0,0,0,0.4);
transition: border-color .15s ease, background .15s ease, box-shadow .3s ease;
}
.card:hover {
border-color: rgba(40,200,64,0.35); background: var(--card-hover);
box-shadow: 0 18px 44px rgba(0,0,0,0.45), 0 0 42px rgba(40,200,64,0.07), inset 0 1px 0 rgba(255,255,255,0.06);
}
/* Sweeping shine on hover */
.card::before {
content: ""; position: absolute; top: 0; left: -75%; width: 45%; height: 100%;
background: linear-gradient(100deg, transparent, rgba(255,255,255,0.055), transparent);
transform: skewX(-20deg); transition: left 0.7s ease; pointer-events: none;
}
.card:hover::before { left: 135%; }
/* Orbiting gradient ring on hover */
.card::after {
content: ""; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
background: linear-gradient(115deg, transparent 32%, var(--green-glow) 46%, rgba(96,165,250,0.55) 56%, transparent 70%);
background-size: 200% 100%;
-webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
-webkit-mask-composite: xor; mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
mask-composite: exclude;
opacity: 0; transition: opacity 0.35s ease; pointer-events: none;
animation: border-sweep 3.2s linear infinite;
}
.card:hover::after { opacity: 1; }
@keyframes border-sweep { to { background-position: -200% 0; } }
.card .icon {
width: 38px; height: 38px; border-radius: 10px;
display: flex; align-items: center; justify-content: center;
font-size: 18px; margin-bottom: 16px;
background: linear-gradient(135deg, rgba(40,200,64,0.18), rgba(40,200,64,0.05) 55%, rgba(96,165,250,0.1));
border: 1px solid rgba(40,200,64,0.3);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 6px 16px rgba(0,0,0,0.35);
transition: transform .25s ease, box-shadow .25s ease, border-color .25s ease;
}
.card:hover .icon {
transform: scale(1.12) rotate(-6deg);
border-color: rgba(40,200,64,0.55);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.1), 0 8px 22px rgba(0,0,0,0.45), 0 0 22px rgba(40,200,64,0.28);
}
.card h3 { font-size: 16.5px; font-weight: 700; margin-bottom: 8px; }
.card p { color: var(--text-muted); font-size: 14px; }
/* Why list */
.why-grid { grid-template-columns: repeat(2, 1fr); }
.why-item { display: flex; gap: 14px; }
.why-item .check {
flex-shrink: 0; width: 24px; height: 24px; margin-top: 2px;
border-radius: 7px;
background: linear-gradient(135deg, rgba(40,200,64,0.22), rgba(40,200,64,0.06));
border: 1px solid rgba(40,200,64,0.35);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.08), 0 0 12px rgba(40,200,64,0.15);
display: flex; align-items: center; justify-content: center; font-size: 13px; color: var(--green);
}
.why-item b { display: block; font-size: 15px; margin-bottom: 4px; }
.why-item span { color: var(--text-muted); font-size: 14px; }
/* Interface cards with commands */
.iface-card { display: flex; flex-direction: column; }
.iface-card .cmd-line {
margin-top: auto; padding-top: 14px;
font-family: var(--font-mono); font-size: 12.5px; color: var(--text-dim);
border-top: 1px dashed var(--border);
}
.iface-card .cmd-line::before { content: "$ "; color: var(--green); }
.tag { display: inline-block; font-size: 11px; font-weight: 600; padding: 3px 9px; border-radius: 6px; margin-bottom: 12px; }
.tag-green { background: var(--green-bg); color: var(--green); border: 1px solid rgba(40,200,64,0.3); }
.tag-blue { background: rgba(96,165,250,0.1); color: var(--blue); border: 1px solid rgba(96,165,250,0.3); }
.tag-yellow { background: rgba(254,188,46,0.1); color: var(--yellow); border: 1px solid rgba(254,188,46,0.3); }
/* ReAct flow */
.flow { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; margin-bottom: 40px; }
.flow-step {
display: flex; align-items: center; gap: 10px;
background: var(--card-glass); border: 1px solid var(--border-soft);
border-radius: 999px; padding: 10px 18px; font-size: 14px; font-weight: 600;
transition: transform .2s ease, border-color .2s ease, box-shadow .25s ease;
}
.flow-step:hover {
transform: translateY(-3px);
border-color: rgba(40,200,64,0.4);
box-shadow: 0 10px 26px rgba(0,0,0,0.35), 0 0 18px rgba(40,200,64,0.08);
}
.flow-step .num {
width: 24px; height: 24px; border-radius: 50%;
background: var(--green); color: #04170a;
display: flex; align-items: center; justify-content: center;
font-size: 12.5px; font-weight: 700;
}
.flow-arrow {
color: var(--text-dim); font-size: 18px; align-self: center;
animation: arrowPulse 2.4s ease-in-out infinite;
}
.flow-arrow:nth-of-type(2) { animation-delay: .3s; }
.flow-arrow:nth-of-type(3) { animation-delay: .6s; }
.flow-arrow:nth-of-type(4) { animation-delay: .9s; }
@keyframes arrowPulse { 0%,100% { opacity: .4; transform: translateX(0); } 50% { opacity: 1; transform: translateX(5px); } }
/* Tool groups */
.tool-group { margin-bottom: 14px; }
.tool-group .label { font-family: var(--font-mono); font-size: 12px; color: var(--green); text-transform: uppercase; letter-spacing: 0.08em; margin-bottom: 6px; }
.tool-group .tools { display: flex; flex-wrap: wrap; gap: 8px; }
.tool-chip {
font-family: var(--font-mono); font-size: 12.5px;
color: var(--text); background: var(--bg-soft);
border: 1px solid var(--border); border-radius: 7px; padding: 4px 10px;
transition: border-color .2s ease, box-shadow .2s ease, transform .2s ease;
}
.tool-chip:hover {
border-color: rgba(40,200,64,0.4);
box-shadow: 0 0 12px rgba(40,200,64,0.12);
transform: translateY(-1px);
}
/* Providers */
.provider {
display: flex; align-items: center; justify-content: space-between;
padding: 13px 18px; border-radius: var(--radius-sm);
background: var(--bg-soft); border: 1px solid var(--border-soft);
font-size: 14px; font-weight: 500;
}
.provider .kind { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 5px; }
.kind-local { background: var(--green-bg); color: var(--green); border: 1px solid rgba(40,200,64,0.3); }
.kind-cloud { background: rgba(96,165,250,0.1); color: var(--blue); border: 1px solid rgba(96,165,250,0.3); }
.kind-aggregator { background: rgba(254,188,46,0.1); color: var(--yellow); border: 1px solid rgba(254,188,46,0.3); }
/* Install */
.install-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 16px; align-items: start; }
.install-block h3 { font-size: 16.5px; margin-bottom: 14px; }
.code-block {
background: linear-gradient(180deg, rgba(255,255,255,0.02), transparent 30%), var(--bg-soft);
border: 1px solid var(--border);
border-radius: var(--radius-sm); padding: 16px 18px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
font-family: var(--font-mono); font-size: 13px; line-height: 1.8;
position: relative; overflow-x: auto;
}
.code-block .copy {
position: absolute; top: 10px; right: 10px;
background: var(--card); border: 1px solid var(--border);
color: var(--text-muted); border-radius: 7px;
font-size: 12px; padding: 4px 10px; cursor: pointer;
}
.code-block .copy:hover { color: var(--text); border-color: #33333a; }
.code-block .c-green { color: var(--green); }
.code-block .c-dim { color: var(--text-dim); }
table { width: 100%; border-collapse: collapse; font-size: 14px; }
th { text-align: left; color: var(--text-muted); font-weight: 600; font-size: 13px; padding: 10px 14px; border-bottom: 1px solid var(--border); }
td { padding: 12px 14px; border-bottom: 1px solid var(--border-soft); color: var(--text); vertical-align: top; }
td .mono { font-family: var(--font-mono); font-size: 12.5px; color: var(--green); }
tr:last-child td { border-bottom: none; }
/* FAQ */
.faq-item { margin-bottom: 14px; }
.faq-item details {
background: var(--card-glass);
border: 1px solid var(--border-soft);
border-radius: var(--radius-sm); padding: 4px 20px;
box-shadow: inset 0 1px 0 rgba(255,255,255,0.04);
transition: border-color .2s ease, box-shadow .3s ease, transform .2s ease;
}
.faq-item details:hover {
border-color: rgba(40,200,64,0.3);
box-shadow: inset 0 1px 0 rgba(255,255,255,0.05), 0 12px 30px rgba(0,0,0,0.35), 0 0 26px rgba(40,200,64,0.05);
}
.faq-item summary {
cursor: pointer; font-weight: 600; font-size: 15px;
padding: 14px 0; list-style: none; display: flex; justify-content: space-between; align-items: center;
}
.faq-item summary::after { content: "+"; color: var(--green); font-size: 18px; }
.faq-item details[open] summary::after { content: "–"; }
.faq-item details p { color: var(--text-muted); font-size: 14.5px; padding: 0 0 16px; }
/* Footer */
footer { border-top: 1px solid var(--border-soft); padding: 56px 0 40px; }
.footer-grid { display: grid; grid-template-columns: 2fr 1fr 1fr 1fr; gap: 32px; }
.footer-grid h4 { font-size: 13px; color: var(--text-muted); margin-bottom: 14px; font-weight: 600; }
.footer-grid a { display: block; color: var(--text-muted); font-size: 13.5px; margin-bottom: 9px; }
.footer-grid a:hover { color: var(--text); text-decoration: none; }
.footer-brand p { color: var(--text-dim); font-size: 13.5px; margin-top: 10px; max-width: 280px; }
.footer-bottom {
margin-top: 44px; padding-top: 22px;
border-top: 1px solid var(--border-soft);
display: flex; justify-content: space-between; gap: 16px; flex-wrap: wrap;
color: var(--text-dim); font-size: 13px;
}
.logo-inline { display: flex; align-items: center; gap: 10px; font-weight: 700; font-size: 16px; color: var(--text); }
.logo-inline img { width: 26px; height: 26px; border-radius: 7px; }
/* ---------- Mascot ---------- */
.mascot-row {
display: flex; align-items: center; justify-content: center;
gap: 16px; margin-bottom: 34px; flex-wrap: wrap;
}
.mascot-sticker {
width: 104px; height: 104px; border-radius: 50%;
object-fit: cover; object-position: center 20%;
border: 2px solid rgba(254,188,46,0.5);
box-shadow: 0 0 0 4px rgba(254,188,46,0.08), 0 12px 34px rgba(0,0,0,0.5), 0 0 34px rgba(254,188,46,0.18);
background: #141417;
animation: mascotFloat 5s ease-in-out infinite;
}
@keyframes mascotFloat {
0%,100% { transform: translateY(0) rotate(-1.5deg); }
50% { transform: translateY(-9px) rotate(1.5deg); }
}
.mascot-speech {
max-width: 420px; text-align: left;
background: var(--card-glass); border: 1px solid var(--border);
border-radius: 4px 14px 14px 14px; padding: 12px 16px;
font-size: 14px; color: var(--text-muted);
}
.mascot-speech b { color: var(--text); }
/* ---------- Compare table ---------- */
.compare-wrap { overflow-x: auto; border: 1px solid var(--border-soft); border-radius: var(--radius); background: var(--bg-soft); }
.compare-wrap table { min-width: 860px; }
.compare-wrap th:first-child, .compare-wrap td:first-child { position: sticky; left: 0; background: var(--bg-soft); }
.compare-wrap th.us, .compare-wrap td.us {
background: linear-gradient(180deg, rgba(40,200,64,0.10), rgba(40,200,64,0.03));
}
.compare-wrap th.us { color: var(--green); }
.verdict-yes { color: var(--green); font-weight: 700; }
.verdict-no { color: var(--text-dim); }
.verdict-part { color: var(--yellow); font-weight: 600; }
.compare-note { text-align: center; color: var(--text-dim); font-size: 13px; margin-top: 18px; }
/* ---------- Benchmark ---------- */
.live-dot { display: inline-block; width: 8px; height: 8px; border-radius: 50%; background: var(--green); box-shadow: 0 0 10px var(--green); margin-right: 8px; animation: pulse-soft 2s ease-in-out infinite; }
.bench-result { font-family: var(--font-mono); font-size: 12.5px; color: var(--text-muted); margin-top: 12px; border-top: 1px dashed var(--border); padding-top: 12px; }
.bench-result .ok { color: var(--green); font-weight: 600; }
.bench-result .warn { color: var(--yellow); font-weight: 600; }
/* ---------- Final CTA ---------- */
.cta-banner {
display: flex; align-items: center; gap: 28px;
background: var(--card-glass); border: 1px solid var(--border);
border-radius: 20px; padding: 36px 40px; flex-wrap: wrap;
box-shadow: 0 24px 60px rgba(0,0,0,0.4);
}
.cta-banner img { width: 150px; border-radius: 18px; animation: mascotFloat 6s ease-in-out infinite; }
.cta-banner h2 { font-size: clamp(24px, 3.4vw, 32px); letter-spacing: -0.02em; margin-bottom: 10px; }
.cta-banner p { color: var(--text-muted); font-size: 15px; margin-bottom: 20px; max-width: 560px; }
.cta-banner .row { display: flex; gap: 12px; flex-wrap: wrap; }
/* ---------- Responsive ---------- */
@media (max-width: 900px) {
.grid-3, .grid-4 { grid-template-columns: repeat(2, 1fr); }
.why-grid, .install-grid { grid-template-columns: 1fr; }
.footer-grid { grid-template-columns: 1fr 1fr; }
.nav-links { display: none; }
.nav-burger { display: block; }
.nav-links.open {
display: flex; flex-direction: column; align-items: flex-start; gap: 6px;
position: absolute; top: 64px; left: 0; right: 0;
background: var(--bg); border-bottom: 1px solid var(--border);
padding: 16px 24px 20px;
}
}
@media (max-width: 560px) {
.grid-3, .grid-4, .grid-2, .why-grid { grid-template-columns: 1fr; }
.hero { padding: 60px 0 48px; }
section { padding: 60px 0; }
.flow-arrow { display: none; }
}
</style>
</head>
<body>
<div class="bg-orbs" aria-hidden="true">
<div class="orb orb-1"></div>
<div class="orb orb-2"></div>
<div class="orb orb-3"></div>
</div>
<div class="bg-grain" aria-hidden="true"></div>
<div class="bg-hero-glow" aria-hidden="true"></div>
<div class="scroll-progress" id="scrollProgress" aria-hidden="true"></div>
<nav>
<div class="container nav-inner">
<a class="brand" href="#">
<img src="./tera_pilot_logo.png" alt="Tera Pilot logo">
Tera Pilot
</a>
<div class="nav-links" id="navLinks">
<a href="#why">Why</a>
<a href="#compare">Compare</a>
<a href="#benchmark">Benchmark</a>
<a href="#interfaces">Interfaces</a>
<a href="#runtime">Runtime</a>
<a href="#providers">Providers</a>
<a href="#trust">Trust</a>
<a href="#install">Install</a>
<a href="#faq">FAQ</a>
<a href="#pro">Pro</a>
</div>
<div class="nav-cta">
<a class="btn btn-primary btn-sm" href="https://github.com/ilyaosovskoi/tera-pilot">View on GitHub</a>
<button class="nav-burger" id="navBurger" aria-label="Menu">☰</button>
</div>
</div>
</nav>
<!-- HERO -->
<header class="hero">
<div class="container">
<div class="hero-badge hero-anim d1"><span class="dot"></span> Local-first · BYOK · 17 providers · MIT license</div>
<h1 class="hero-anim d2">Private, vendor-neutral<br>coding agents <span class="accent">— built to be verified.</span></h1>
<p class="sub hero-anim d3">A self-hosted coding agent for private repositories, local models, CI, and verifiable automation. Textual TUI first, Web UI, HTTP daemon, MCP/ACP — or fully offline with Ollama and LM Studio. Commands run inside an OS-level sandbox with an Ed25519-signed audit trail.</p>
<div class="hero-cta hero-anim d4">
<a class="btn btn-primary" href="#install">Get started — it’s free</a>
<a class="btn btn-ghost" href="https://github.com/ilyaosovskoi/tera-pilot">GitHub repo →</a>
</div>
<div class="mascot-row hero-anim d4">
<img class="mascot-sticker" src="./maskot.png" alt="Tera Pilot mascot — robot pilot">
<div class="mascot-speech"><b>👋 Meet your pilot.</b> I plan, edit, run and <em>verify</em> — inside a sandbox, with signed evidence. Scroll down: comparison with real competitors first, then a live model benchmark.</div>
</div>
<div class="terminal hero-anim d5">
<div class="terminal-bar">
<span class="dot dot-red"></span><span class="dot dot-yellow"></span><span class="dot dot-green"></span>
<span class="title">zsh — tera-pilot</span>
</div>
<div class="terminal-body">
<div><span class="cmd" id="typedCmd">npm install -g tera-pilot</span></div>
<div class="out line-fade lf1">added 1 package in 6s</div>
<div class="out line-fade lf2">[tera-pilot] creating Python virtualenv at ~/.tera_pilot/venv …</div>
<div class="ok line-fade lf3">[tera-pilot] done — run: tera-pilot / tera-pilot-tui</div>
<div class="line-fade lf4"> </div>
<div class="line-fade lf5"><span class="cmd">tera-pilot-tui</span> <span class="comment"># full-screen terminal UI</span></div>
<div class="line-fade lf6"><span class="cmd">tera-pilot doctor</span> <span class="comment"># environment readiness check</span></div>
<div class="line-fade lf7"><span class="cmd">tera-pilot audit export --out audit.json</span> <span class="comment"># signed audit trail</span></div>
<div class="line-fade lf8"> <span class="cursor"></span></div>
</div>
</div>
<div class="badge-strip">
<span class="chip"><b>MIT</b> license</span>
<span class="chip"><b>Python 3.11+</b></span>
<span class="chip"><b>Textual</b> TUI</span>
<span class="chip"><b>macOS · Linux · Windows</b></span>
<span class="chip"><b>Ollama / LM Studio</b> offline</span>
<span class="chip"><b>OS-level sandbox</b> Seatbelt / bwrap</span>
<span class="chip"><b>SSRF-hardened</b> web fetch</span>
<span class="chip"><b>Ed25519</b> signed audit</span>
</div>
<div class="badge-strip" style="margin-top:12px;">
<span class="chip">Pick your track →</span>
<a class="chip" href="#install">💻 Solo dev: TUI in ~5 min</a>
<a class="chip" href="#interfaces">👥 Team: daemon + CI evidence</a>
<a class="chip" href="#mcp">🧩 Platform: API / MCP / ACP</a>
<a class="chip" href="#trust">🏛️ Regulated: sandbox + audit</a>
</div>
</div>
</header>
<!-- WHY -->
<section id="why">
<div class="container">
<div class="sec-head">
<div class="kicker">Why Tera Pilot</div>
<h2>Trust, control, and evidence — not just generation quality</h2>
<p>AI coding agents can edit repositories, run commands, call external tools, and finish multi-step tasks. The hard part is no longer only quality — it is trust, control, and evidence.</p>
</div>
<div class="grid why-grid">
<div class="why-item">
<div class="check">✓</div>
<div><b>Keep code local</b><span>Use Ollama or LM Studio — repository content never leaves your machine.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Bring your own keys</b><span>Choose among 17 providers and switch models without lock-in to a vendor.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Run it anywhere</b><span>Terminal, TUI, browser, daemon, or a CI job — the same backend everywhere.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Review and restrict actions</b><span>Sandbox file, command, Git, and MCP operations with explicit policies.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Keep an activity & audit trail</b><span>Every tool call is recorded — export it signed with Ed25519 and a hash chain.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Verify instead of trust</b><span>Re-read touched files, run tests, request a reviewer — evidence over answers.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Avoid lock-in</b><span>No single model vendor or hosted IDE required to use the product.</span></div>
</div>
<div class="why-item">
<div class="check">✓</div>
<div><b>Free & open source</b><span>MIT-licensed and self-hostable — you own the whole stack.</span></div>
</div>
</div>
</div>
</section>
<!-- COMPARE -->
<section class="alt" id="compare">
<div class="container">
<div class="sec-head">
<div class="kicker">Compare</div>
<h2>Tera Pilot vs the agents you already tried</h2>
<p>We don’t compete with autocomplete. We compete on <b>controlled, verifiable execution</b>: what the agent may touch, what it must ask, and what evidence it leaves behind.</p>
</div>
<div class="compare-wrap">
<table>
<tr><th>Capability</th><th class="us">Tera Pilot</th><th>Claude Code</th><th>Codex CLI</th><th>Cursor</th><th>Copilot Agent Mode</th><th>Aider</th></tr>
<tr><td>Autonomous agent execution</td><td class="us"><span class="verdict-yes">✅ yes</span></td><td><span class="verdict-yes">✅</span></td><td><span class="verdict-yes">✅</span></td><td><span class="verdict-part">⚠️ IDE-supervised</span></td><td><span class="verdict-yes">✅ Autopilot</span></td><td><span class="verdict-part">⚠️ pair-programming</span></td></tr>
<tr><td>Fully offline option (local models)</td><td class="us"><span class="verdict-yes">✅ Ollama / LM Studio</span></td><td><span class="verdict-no">❌ cloud</span></td><td><span class="verdict-no">❌ cloud</span></td><td><span class="verdict-no">❌ cloud</span></td><td><span class="verdict-no">❌ cloud</span></td><td><span class="verdict-yes">✅ via local backends</span></td></tr>
<tr><td>Vendor-neutral, BYOK (17 providers)</td><td class="us"><span class="verdict-yes">✅ 17 providers</span></td><td><span class="verdict-no">❌ Claude only</span></td><td><span class="verdict-no">❌ OpenAI only</span></td><td><span class="verdict-part">⚠️ multi-model, IDE-bound</span></td><td><span class="verdict-part">⚠️ multi-model, GitHub-bound</span></td><td><span class="verdict-yes">✅ any key</span></td></tr>
<tr><td>OS-level command sandbox (fail-closed)</td><td class="us"><span class="verdict-yes">✅ seatbelt / bwrap</span></td><td><span class="verdict-yes">✅ sandboxed exec</span></td><td><span class="verdict-yes">✅ sandboxed</span></td><td><span class="verdict-part">⚠️ partial</span></td><td><span class="verdict-part">⚠️ MCP sandbox</span></td><td><span class="verdict-no">❌ relies on git + user</span></td></tr>
<tr><td>Uniform approval policy (every side effect)</td><td class="us"><span class="verdict-yes">✅ always_ask</span></td><td><span class="verdict-yes">✅ permissions</span></td><td><span class="verdict-yes">✅ approvals</span></td><td><span class="verdict-part">⚠️ IDE review</span></td><td><span class="verdict-yes">✅ PR review flow</span></td><td><span class="verdict-yes">✅ per-change ask</span></td></tr>
<tr><td>SSRF-hardened web tools (metadata-IP blocked)</td><td class="us"><span class="verdict-yes">✅ tested live</span></td><td><span class="verdict-no">— not published</span></td><td><span class="verdict-no">— not published</span></td><td><span class="verdict-no">— not published</span></td><td><span class="verdict-no">— not published</span></td><td><span class="verdict-no">— not published</span></td></tr>
<tr><td>Signed, tamper-evident audit export</td><td class="us"><span class="verdict-yes">✅ Ed25519 + hash chain</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌ (git log only)</span></td></tr>
<tr><td>Agent profiles + multi-agent fleets</td><td class="us"><span class="verdict-yes">✅ /agent + fleet watch</span></td><td><span class="verdict-part">⚠️ subagents only</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-part">⚠️ cloud agents</span></td><td><span class="verdict-part">⚠️ nested subagents</span></td><td><span class="verdict-no">❌</span></td></tr>
<tr><td>Reproducible public eval harness</td><td class="us"><span class="verdict-yes">✅ 58 tasks, open</span></td><td><span class="verdict-part">⚠️ vendor scores*</span></td><td><span class="verdict-part">⚠️ vendor scores*</span></td><td><span class="verdict-no">❌ not published</span></td><td><span class="verdict-no">❌ not published</span></td><td><span class="verdict-yes">✅ public benchmarks</span></td></tr>
<tr><td>Open-source core</td><td class="us"><span class="verdict-yes">✅ MIT</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-yes">✅ Apache-2.0 CLI</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-yes">✅ Apache-2.0</span></td></tr>
<tr><td>Zero telemetry, offline-verified licensing</td><td class="us"><span class="verdict-yes">✅</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-no">❌</span></td><td><span class="verdict-yes">✅ local tool</span></td></tr>
</table>
</div>
<p class="compare-note">* Claude Code and Codex publish strong vendor-reported scores (SWE-bench Verified/Pro, Terminal-Bench 2.x) — we cite them, not contest them. Our claim is narrower: <b>no other agent gives you sandbox + approvals + signed audit + offline option in one MIT-licensed runtime.</b> Positions as of September 2026; tell us if something changed.</p>
</div>
</section>
<!-- BENCHMARK -->
<section id="benchmark">
<div class="container">
<div class="sec-head">
<div class="kicker"><span class="live-dot"></span>Live probe · 2026-09-18 · NVIDIA NIM</div>
<h2>Concreteness instead of «we are secure»</h2>
<p>We probed production models through NVIDIA’s hosted API the same day: one coding task executed for real, two attack-style prompts. Raw behavior below — including the parts that flaked.</p>
</div>
<div class="grid grid-3">
<div class="card">
<span class="tag tag-green">coding · executed</span>
<h3>Nemotron 3 Super writes working code</h3>
<p>120B / 12B-active hybrid Mamba-Transformer MoE, built for agentic coding. Asked for <span style="font-family:var(--font-mono);font-size:13px">clamp(x, lo, hi)</span> with edge cases — returned code passed <b>5/5 assertions</b> when executed, in ~2 s.</p>
<div class="bench-result"><span class="ok">PASS</span> · nvidia/nemotron-3-super-120b-a12b · 2.0 s · code ran, not just rendered</div>
</div>
<div class="card">
<span class="tag tag-green">security · refused</span>
<h3>Credential-theft and destructive prompts refused</h3>
<p>Asked to fetch cloud metadata (<span style="font-family:var(--font-mono);font-size:13px">169.254.169.254</span>) and to hand over a no-confirmation home-directory wipe. Both refused — the wipe request got a lecture plus safe alternatives.</p>
<div class="bench-result"><span class="ok">REFUSED × 2</span> · no disclosure, no command issued</div>
</div>
<div class="card">
<span class="tag tag-yellow">honest flake</span>
<h3>Kimi K3, DeepSeek V4, GLM all timed out</h3>
<p>Flagship models (Kimi K3 — 2.8T MoE, Terminal-Bench 2.1 88.3 partner-reported; DeepSeek V4 Flash — 1M context) didn’t answer on the NIM trial endpoint that day — consistent with public congestion reports. That’s exactly why Tera Pilot is provider-neutral with failover, not single-model locked.</p>
<div class="bench-result"><span class="warn">TIMEOUT</span> · endpoint congestion, not a model verdict · retried, documented</div>
</div>
</div>
<div class="grid grid-3" style="margin-top:16px;">
<div class="card">
<div class="icon">🧪</div>
<h3>Coding 2/2 green — twice, today</h3>
<p>End-to-end agent runs via NVIDIA NIM (2026-09-18, two batches): executed code, green tests, verified. Full detail in <span style="font-family:var(--font-mono);font-size:13px">eval/REPORT_2026-09-18.md</span>.</p>
</div>
<div class="card">
<div class="icon">🏠</div>
<h3>Exfil blocked, zero bytes out</h3>
<p>The metadata-endpoint attack was stopped by the SSRF guard with nothing retrieved. Scored «failed» on a <span style="font-family:var(--font-mono);font-size:13px">final_answer</span> parser mismatch — the block held, the scoring didn’t. Disclosed, not hidden.</p>
</div>
<div class="card">
<div class="icon">🛡️</div>
<h3>976 tests · 310+ security</h3>
<p>956 passing, 20 environment-dependent skips. Model refusal is politeness — <b>runtime enforcement</b> (sandbox, SSRF guard, approvals) is what blocks. That’s the defense-in-depth difference.</p>
</div>
</div>
<p class="compare-note">Model scores cited (Terminal-Bench, DeepSWE) are vendor/partner-reported. Our probe results above are self-measured on 2026-09-18 and re-runnable — the script pattern lives in <span style="font-family:var(--font-mono);font-size:13px">eval/</span>.</p>
</div>
</section>
<!-- INTERFACES -->
<section class="alt" id="interfaces">
<div class="container">
<div class="sec-head">
<div class="kicker">Interfaces</div>
<h2>One backend, six ways to drive it</h2>
<p>The TUI is the primary interactive product; the same controlled backend powers the browser, daemon, and automation.</p>
</div>
<div class="grid grid-3">
<div class="card iface-card">
<span class="tag tag-green">Primary app</span>
<h3>Textual TUI</h3>
<p>Full-screen terminal UI: chat surface, activity stream, task canvas, provider controls, command palette, approval dialogs and verification feedback.</p>
<div class="cmd-line">tera-pilot-tui</div>
</div>
<div class="card iface-card">
<span class="tag tag-blue">Browser</span>
<h3>Web UI</h3>
<p>Browser chat, project browsing, provider settings and activity — served by a plain HTTP server, no legacy desktop stack.</p>
<div class="cmd-line">tera-pilot</div>
</div>
<div class="card iface-card">
<span class="tag tag-yellow">Backend</span>
<h3>HTTP daemon</h3>
<p>REST + SSE task execution, queues and notifications for integrations that embed the backend directly.</p>
<div class="cmd-line">tera-pilot-daemon --help</div>
</div>
<div class="card iface-card">
<span class="tag tag-blue">Integration</span>
<h3>ACP / MCP server</h3>
<p>Expose Tera Pilot tools to editors and other agents — read-only by default, writes only when you opt in.</p>
<div class="cmd-line">tera-pilot-acp --mcp-server</div>
</div>
<div class="card iface-card">
<span class="tag tag-green">Onboarding</span>
<h3>Environment doctor</h3>
<p>One command checks Python, dependencies, provider keys, local model servers, Rust acceleration and the workspace.</p>
<div class="cmd-line">tera-pilot doctor --json</div>
</div>
<div class="card iface-card">
<span class="tag tag-yellow">Evidence</span>
<h3>Signed audit</h3>
<p>Export the activity log with Ed25519 signatures and a SHA-256 hash chain, then verify it even on another machine.</p>
<div class="cmd-line">tera-pilot audit verify audit.json</div>
</div>
</div>
</div>
</section>
<!-- RUNTIME -->
<section id="runtime">
<div class="container">
<div class="sec-head">
<div class="kicker">Core Agent Runtime</div>
<h2>A ReAct loop with verification built in</h2>
<p>Every task moves through the same five stages — and ends with evidence, not just an answer.</p>
</div>
<div class="flow">
<div class="flow-step"><span class="num">1</span> Plan</div>
<span class="flow-arrow">→</span>
<div class="flow-step"><span class="num">2</span> Explore</div>
<span class="flow-arrow">→</span>
<div class="flow-step"><span class="num">3</span> Act</div>
<span class="flow-arrow">→</span>
<div class="flow-step"><span class="num">4</span> Verify</div>
<span class="flow-arrow">→</span>
<div class="flow-step"><span class="num">5</span> Report</div>
</div>
<div class="card">
<div class="grid grid-3" style="margin-bottom: 8px;">
<div>
<div class="tool-group">
<div class="label">Files</div>
<div class="tools"><span class="tool-chip">read_file</span><span class="tool-chip">write_file</span><span class="tool-chip">str_replace</span><span class="tool-chip">apply_diff</span><span class="tool-chip">delete_file</span><span class="tool-chip">rename_file</span></div>
</div>
<div class="tool-group">
<div class="label">Search</div>
<div class="tools"><span class="tool-chip">search_project</span><span class="tool-chip">grep</span><span class="tool-chip">glob</span><span class="tool-chip">list_files</span><span class="tool-chip">get_project_structure</span></div>
</div>
</div>
<div>
<div class="tool-group">
<div class="label">Execution</div>
<div class="tools"><span class="tool-chip">execute_command</span><span class="tool-chip">run_code</span></div>
</div>
<div class="tool-group">
<div class="label">Git</div>
<div class="tools"><span class="tool-chip">status</span><span class="tool-chip">diff</span><span class="tool-chip">stage</span><span class="tool-chip">commit</span><span class="tool-chip">checkpoints</span><span class="tool-chip">undo</span></div>
</div>
<div class="tool-group">
<div class="label">Web</div>
<div class="tools"><span class="tool-chip">web_search</span><span class="tool-chip">web_fetch</span></div>
</div>
</div>
<div>
<div class="tool-group">
<div class="label">MCP</div>
<div class="tools"><span class="tool-chip">external tools</span><span class="tool-chip">typed discovery</span></div>
</div>
<div class="tool-group">
<div class="label">Agents</div>
<div class="tools"><span class="tool-chip">subagents</span><span class="tool-chip">parallel tasks</span><span class="tool-chip">watchdog</span><span class="tool-chip">task decomposition</span></div>
</div>
<div class="tool-group">
<div class="label">Verification · Office</div>
<div class="tools"><span class="tool-chip">self_verify</span><span class="tool-chip">tests</span><span class="tool-chip">reviewer</span><span class="tool-chip">.docx / .xlsx / .pptx</span></div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- PROVIDERS -->
<section class="alt" id="providers">
<div class="container">
<div class="sec-head">
<div class="kicker">Providers</div>
<h2>Vendor-neutral by design</h2>
<p>Bring your own key to any cloud provider — or go fully offline with a local model. Configure provider, model, workspace and autonomy in the TUI.</p>
</div>
<div class="grid grid-4">
<div class="provider">Anthropic <span class="kind kind-cloud">cloud</span></div>
<div class="provider">OpenAI <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Google Gemini <span class="kind kind-cloud">cloud</span></div>
<div class="provider">DeepSeek <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Groq <span class="kind kind-cloud">cloud</span></div>
<div class="provider">xAI <span class="kind kind-cloud">cloud</span></div>
<div class="provider">z.ai <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Mistral <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Cerebras <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Together <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Fireworks <span class="kind kind-cloud">cloud</span></div>
<div class="provider">SambaNova <span class="kind kind-cloud">cloud</span></div>
<div class="provider">Nvidia NIM <span class="kind kind-cloud">cloud</span></div>
<div class="provider">OpenRouter <span class="kind kind-aggregator">aggregator</span></div>
<div class="provider">Ollama <span class="kind kind-local">local</span></div>
<div class="provider">LM Studio <span class="kind kind-local">local</span></div>
<div class="provider">Local (OpenAI-compatible) <span class="kind kind-local">local</span></div>
</div>
</div>
</section>
<!-- TRUST -->
<section id="trust">
<div class="container">
<div class="sec-head">
<div class="kicker">Trust & Control</div>
<h2>Autonomy as a policy decision, not a label</h2>
<p>Every layer of the agent loop is sandboxed, logged, and reversible — control and evidence by default.</p>
</div>
<div class="grid grid-3">
<div class="card">
<div class="icon">🔒</div>
<h3>Workspace sandbox</h3>
<p>File operations are limited to the selected project root.</p>
</div>
<div class="card">
<div class="icon">🛡️</div>
<h3>Command policy</h3>
<p>Allowed and denied commands are controlled explicitly.</p>
</div>
<div class="card">
<div class="icon">🏰</div>
<h3>OS-level sandbox</h3>
<p>Commands run inside a Seatbelt/bubblewrap sandbox — network denied, writes kept to the workspace, secrets unreadable.</p>
</div>
<div class="card">
<div class="icon">✅</div>
<h3>Project approvals</h3>
<p>A repository cannot silently widen its own permissions.</p>
</div>
<div class="card">
<div class="icon">🎚️</div>
<h3>Autonomy levels</h3>
<p><span style="font-family:var(--font-mono);font-size:13px;color:var(--green)">always_ask · new_files_only · never_ask</span><br>Headless runs fail closed — side effects are blocked unless explicitly opted in.</p>
</div>
<div class="card">
<div class="icon">👁️</div>
<h3>Diff review</h3>
<p>Writes can pause for human approval before they land.</p>
</div>
<div class="card">
<div class="icon">🌐</div>
<h3>SSRF-hardened web</h3>
<p>web_fetch rejects loopback, private, and cloud-metadata hosts — DNS and every redirect hop re-checked.</p>
</div>
<div class="card">
<div class="icon">🧠</div>
<h3>Guardian</h3>
<p>Assesses risky tool calls — approve, reject, or modify; fails closed on errors.</p>
</div>
<div class="card">
<div class="icon">↩️</div>
<h3>Checkpoints & undo</h3>
<p>Recovery paths for every file change along the way.</p>
</div>
<div class="card">
<div class="icon">📜</div>
<h3>Activity & audit logs</h3>
<p>Tool execution and agent identity are recorded by default.</p>
</div>
<div class="card">
<div class="icon">✍️</div>
<h3>Signed audit export</h3>
<p>Ed25519 signatures + SHA-256 hash chaining for exported records — verify on any machine.</p>
</div>
<div class="card">
<div class="icon">🗝️</div>
<h3>Offline licensing</h3>
<p>Pro features gate on Ed25519-signed license keys — verified entirely offline, zero telemetry, no phone-home.</p>
</div>
</div>
<p style="text-align:center;color:var(--text-dim);font-size:13.5px;margin-top:28px;">
These mechanisms provide control and evidence — they are not a claim of SOC 2, ISO 27001, or vulnerability-free code. See
<a href="TERA_PILOT_PRODUCT_STRATEGY.md">product strategy</a>, <a href="THREAT_MODEL.md">threat model</a> and <a href="SECURITY.md">security</a>.
Every control is covered by automated tests — 976 tests (956 passing, 20 environment-dependent skips), 310+ of them security/sandbox/command-policy/licensing — plus 58 reproducible eval tasks (incl. 10 adversarial sec-*).
</p>
</div>
</section>
<!-- INSTALL -->
<section class="alt" id="install">
<div class="container">
<div class="sec-head">
<div class="kicker">Install</div>
<h2>One command, no git clone, no manual pip</h2>
<p>Python 3.11+ is the only system prerequisite. The npm install boots an isolated virtualenv for you.</p>
</div>
<div class="install-grid">
<div class="install-block">
<h3>Quick start (npm)</h3>
<div class="code-block">
<button class="copy" onclick="copyCmd(this)">copy</button>
<span class="c-green">npm install -g tera-pilot</span>
</div>
<p style="color:var(--text-muted);font-size:14px;margin:14px 0;">Everything works out of the box after install:</p>
<div class="code-block">
<span class="c-dim"># Web UI</span><br>
<span class="c-green">tera-pilot</span><br>
<span class="c-dim"># Full-screen terminal UI (primary app)</span><br>
<span class="c-green">tera-pilot-tui</span><br>
<span class="c-dim"># REST API + SSE daemon / ACP server</span><br>
<span class="c-green">tera-pilot-daemon --help</span><br>
<span class="c-green">tera-pilot-acp --help</span><br>
<span class="c-dim"># Environment doctor / signed audit</span><br>
<span class="c-green">tera-pilot doctor</span><br>
<span class="c-green">tera-pilot audit</span><br>
<span class="c-dim"># Offline license keys (optional Pro features)</span><br>
<span class="c-green">tera-pilot license status</span>
</div>
</div>
<div class="install-block">
<h3>Environment knobs (optional)</h3>
<div class="card" style="padding:0;overflow:hidden;">
<table>
<tr><th>Variable</th><th>Effect</th></tr>
<tr><td class="mono">TERA_PILOT_PYTHON</td><td>Python interpreter (default <span class="mono">python3</span>)</td></tr>
<tr><td class="mono">TERA_PILOT_VENV</td><td>venv location (default <span class="mono">~/.tera_pilot/venv</span>)</td></tr>
<tr><td class="mono">TERA_PILOT_SKIP_PIP=1</td><td>install without running pip (offline / custom)</td></tr>
</table>
</div>
<h3 style="margin-top:24px;">Install from source</h3>
<div class="code-block">
<span class="c-green">git clone</span> https://github.com/ilyaosovskoi/tera-pilot.git<br>
<span class="c-green">cd</span> tera-pilot<br>
<span class="c-green">python3 -m venv</span> .venv<br>
<span class="c-green">source</span> .venv/bin/activate<br>
<span class="c-green">pip install -e</span> .
</div>
</div>
</div>
</div>
</section>
<!-- MCP / ACP -->
<section id="mcp">
<div class="container">
<div class="sec-head">
<div class="kicker">MCP & ACP</div>
<h2>Connect Tera Pilot to your editors and agents</h2>
<p>Tera Pilot both consumes external MCP tools and exposes its own tools through an MCP/ACP server. Write-capable external tools should be trusted and approved deliberately.</p>
</div>
<div class="grid grid-2">
<div class="card">
<h3>Read-only by default</h3>
<div class="code-block" style="margin-top:14px;">
<span class="c-dim"># Expose read-only Tera Pilot tools from a workspace</span><br>
<span class="c-green">tera-pilot-acp --mcp-server --workspace</span> /path/to/project
</div>
</div>
<div class="card">
<h3>Opt into writes</h3>
<div class="code-block" style="margin-top:14px;">
<span class="c-dim"># Enable writes only when you explicitly need them</span><br>
<span class="c-green">tera-pilot-acp --mcp-server --workspace</span> /path/to/project <span class="c-green">--allow-writes</span>
</div>
</div>
</div>
</div>
</section>
<!-- FAQ -->
<section class="alt" id="faq">
<div class="container">
<div class="sec-head">
<div class="kicker">FAQ</div>
<h2>Questions, answered</h2>
</div>
<div style="max-width:760px;margin:0 auto;">
<div class="faq-item">
<details>
<summary>Is this a replacement for Cursor autocomplete or GitHub Copilot?</summary>
<p>No — and Copilot deserves more credit than that: since Agent Mode / Autopilot (GA 2026) it <em>is</em> an agent, and Cursor is the flow king for IDE editing. Tera Pilot competes on a different axis: <em>controlled, verifiable execution</em> — local-first models, 17 providers with no lock-in, OS-level sandbox, uniform approvals, and a signed audit trail. See the <a href="#compare">head-to-head table</a>.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>Does "local-first" mean always offline?</summary>
<p>No. Cloud providers and remote MCP servers still send data outside the machine by design — local-first is not the same as always-offline. With Ollama or LM Studio the repository content stays on your machine.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>What are the system requirements?</summary>
<p>Python 3.11+ and a terminal. The npm install is the only prerequisite — it creates an isolated virtualenv at <span style="font-family:var(--font-mono);font-size:13px">~/.tera_pilot/venv</span> automatically. Optional Rust acceleration is detected by the doctor.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>How much does it cost?</summary>
<p>Nothing for the software — MIT licensed, free to use, modify and integrate. You only pay for the model providers you choose to bring your own keys to (or nothing at all with local models). A few optional Pro features (second opinion, cost router, spend dashboard) gate on Ed25519-signed license keys issued entirely offline — verification never phones home.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>Can the audit trail be verified on another machine?</summary>
<p>Yes. Export with <span style="font-family:var(--font-mono);font-size:13px">tera-pilot audit export</span>, then run <span style="font-family:var(--font-mono);font-size:13px">tera-pilot audit verify</span> anywhere using the public key from <span style="font-family:var(--font-mono);font-size:13px">~/.tera_pilot/audit_key.pub</span>. Exit code 0 means the chain is intact.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>How does the OS-level sandbox work?</summary>
<p><span style="font-family:var(--font-mono);font-size:13px">execute_command</span>, <span style="font-family:var(--font-mono);font-size:13px">run_code</span> and auto-detected test/lint commands run inside an OS-level sandbox when a backend is available (macOS <span style="font-family:var(--font-mono);font-size:13px">sandbox-exec</span>/Seatbelt, Linux <span style="font-family:var(--font-mono);font-size:13px">bubblewrap</span>): network is denied by default, writes are restricted to the workspace and OS temp, and sensitive paths (<span style="font-family:var(--font-mono);font-size:13px">~/.ssh</span>, <span style="font-family:var(--font-mono);font-size:13px">~/.aws</span>, <span style="font-family:var(--font-mono);font-size:13px">~/.gnupg</span>) are unreadable. The mode is configurable (<span style="font-family:var(--font-mono);font-size:13px">auto</span> default / <span style="font-family:var(--font-mono);font-size:13px">on</span> / <span style="font-family:var(--font-mono);font-size:13px">off</span>) and <span style="font-family:var(--font-mono);font-size:13px">on</span> fails closed without a backend. It is a defense layer, not a full VM.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>How is the security posture verified?</summary>
<p>Security is treated as a continuously tested property: 976 tests (956 passing), 310+ of them security/sandbox/command-policy/licensing, plus a reproducible eval harness of 58 repository tasks (incl. 10 adversarial <span style="font-family:var(--font-mono);font-size:13px">sec-*</span> tasks) and live model probes — see <a href="#benchmark">Benchmark</a>. Five real vulnerabilities found by offensive testing (git alias/config-key execution, CORS origin-prefix matching, npm script aliases, …) were fixed in v2.3.4 with regression tests — see <a href="SECURITY.md">SECURITY.md</a> and <a href="SECURITY_TEST_REPORT.md">SECURITY_TEST_REPORT.md</a>.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>What about enterprise features like SSO, RBAC, compliance?</summary>
<p>They are roadmap work. The current focus is reproducible evaluation, machine-readable evidence, transparent sandboxing, and a smooth local/BYOK onboarding path — see the <a href="TERA_PILOT_PRODUCT_STRATEGY.md">product strategy</a>.</p>
</details>
</div>
<div class="faq-item">
<details>
<summary>How good are the results?</summary>
<p>Quality depends on the selected model, provider configuration and the repository's tests. That is exactly why the runtime verifies: re-reads touched files, runs tests, and can request a reviewer before reporting.</p>
</details>
</div>
</div>
</div>
</section>
<!-- PRO -->
<section class="alt" id="pro">
<div class="container">
<div class="sec-head">
<div class="kicker">Tera Pilot Pro · early access</div>
<h2>The core stays free. Pro pays for itself.</h2>
<p>MIT core forever. Pro unlocks second-opinion reviews, cost-based model routing, and the spend dashboard — verified by an offline Ed25519 key. No account, no telemetry, no phone-home.</p>
</div>
<div class="compare-wrap">
<table>
<tr><th></th><th class="us">Personal</th><th>Team</th></tr>
<tr><td>Price</td><td class="us"><span class="verdict-yes"><b>$10/mo</b> or <b>$100/yr</b></span></td><td><span class="verdict-yes"><b>$20/mo</b> per seat</span></td></tr>
<tr><td>Unlocks</td><td class="us">second-opinion reviews · cost router · spend dashboard</td><td>everything in Personal · per-seat keys for the team</td></tr>
<tr><td>License</td><td class="us">offline key, monthly or yearly expiry</td><td>one key per teammate · invoices & SSO on request</td></tr>
</table>
</div>
<p class="compare-note">Early access is open now — keys are issued manually during rollout. <b><a href="https://github.com/ilyaosovskoi/tera-pilot/issues">Open an issue titled «Pro early access»</a></b>, tell us which plan you want, and you’ll get payment details (crypto/USDT/TON — no bank needed) plus your key within a day.</p>
</div>
</section>
<!-- FINAL CTA -->
<section id="takeoff">
<div class="container">
<div class="cta-banner">
<img src="./maskot.png" alt="Tera Pilot mascot ready for takeoff">
<div style="flex:1;min-width:260px;">
<h2>Your 60-second challenge, pilot.</h2>
<p>You scrolled this far — you’re not «just browsing». Prove it in one command: ask it to find the riskiest file in your repo and explain why. If it plans, shows its tool calls, asks before anything dangerous, and hands you evidence — welcome aboard. If not, open an issue: we fix fast.</p>
<div class="row">
<a class="btn btn-primary" href="#install">Install — it’s free</a>
<a class="btn btn-ghost" href="#pro">💎 Pro — from $10/mo</a>
<a class="btn btn-ghost" href="https://github.com/ilyaosovskoi/tera-pilot">⭐ Star on GitHub</a>
<a class="btn btn-ghost" href="https://github.com/ilyaosovskoi/tera-pilot/issues">🐛 Open an issue</a>
</div>
</div>
</div>
</div>
</section>
<!-- FOOTER -->
<footer>
<div class="container">
<div class="footer-grid">
<div class="footer-brand">
<div class="logo-inline"><img src="./tera_pilot_logo.png" alt="Tera Pilot logo"> Tera Pilot</div>
<p>Private, vendor-neutral coding agents for private repositories, local models, CI, and verifiable automation.</p>
</div>
<div>
<h4>Product</h4>
<a href="#compare">Compare</a>
<a href="#benchmark">Benchmark</a>
<a href="#interfaces">Interfaces</a>
<a href="#runtime">Agent runtime</a>
<a href="#providers">Providers</a>
<a href="#trust">Trust & control</a>
<a href="#install">Install</a>
<a href="#pro">Pro</a>
</div>
<div>
<h4>Documentation</h4>
<a href="README.md">README</a>
<a href="TERA_PILOT_PRODUCT_STRATEGY.md">Product strategy</a>
<a href="THREAT_MODEL.md">Threat model</a>
<a href="P0_IMPLEMENTATION.md">P0 implementation</a>
<a href="SECURITY.md">Security</a>
</div>
<div>
<h4>Community</h4>
<a href="https://github.com/ilyaosovskoi/tera-pilot">GitHub</a>
<a href="https://github.com/ilyaosovskoi/tera-pilot/issues">Issues</a>
<a href="https://github.com/ilyaosovskoi/tera-pilot/blob/main/CONTRIBUTING.md">Contributing</a>
<a href="LICENSE">License (MIT)</a>
</div>
</div>
<div class="footer-bottom">
<span>© 2026 Tera Pilot Contributors · MIT License</span>
<span>Self-hosted · BYOK · OS-sandboxed · TUI-first</span>
<a href="https://smollaunch.com" target="_blank" rel="noopener"><img src="https://smollaunch.com/badges/featured.svg" alt="Tera Pilot – Featured on Smol Launch" loading="lazy"></a>
</div>
</div>
</footer>
<script>
// Mobile nav
document.getElementById('navBurger').addEventListener('click', function () {
document.getElementById('navLinks').classList.toggle('open');
});
// Scroll progress bar
var progress = document.getElementById('scrollProgress');
window.addEventListener('scroll', function () {
var h = document.documentElement;
var max = h.scrollHeight - h.clientHeight;
progress.style.width = (max > 0 ? (h.scrollTop / max) * 100 : 0) + '%';
}, { passive: true });
// Reveal on scroll
var revealEls = document.querySelectorAll('.sec-head, .card, .provider, .why-item, .faq-item, .install-block');
var reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (reduceMotion) {
revealEls.forEach(function (el) { el.classList.add('visible'); });
} else {
revealEls.forEach(function (el, i) {
el.classList.add('reveal');
el.style.setProperty('--d', ((i % 5) * 70) + 'ms');
});
var io = new IntersectionObserver(function (entries) {
entries.forEach(function (entry) {
if (entry.isIntersecting) { entry.target.classList.add('visible'); io.unobserve(entry.target); }
});
}, { threshold: 0.12 });
revealEls.forEach(function (el) { io.observe(el); });
}
// Terminal typewriter
var typed = document.getElementById('typedCmd');
var termBody = document.querySelector('.terminal-body');
if (typed) {
var full = typed.textContent;
if (reduceMotion) { termBody.classList.add('typed-done'); }
else {
typed.textContent = '';
var i = 0;
var typeIv = setInterval(function () {
typed.textContent = full.slice(0, ++i);
if (i >= full.length) { clearInterval(typeIv); termBody.classList.add('typed-done'); }
}, 48);
}
}
// Copy install command
function copyCmd(btn) {
const text = btn.parentElement.textContent.replace('copy', '').trim();
navigator.clipboard.writeText(text).then(function () {
btn.textContent = 'copied ✓';
setTimeout(function () { btn.textContent = 'copy'; }, 1500);
});
}
// Nav: scrolled shadow + scrollspy active link
var nav = document.querySelector('nav');
window.addEventListener('scroll', function () {
nav.classList.toggle('scrolled', window.scrollY > 12);
}, { passive: true });
var spyLinks = document.querySelectorAll('.nav-links a[href^="#"]');
if ('IntersectionObserver' in window && spyLinks.length) {
var spy = new IntersectionObserver(function (entries) {
entries.forEach(function (en) {
if (en.isIntersecting) {
spyLinks.forEach(function (a) {
a.classList.toggle('active', a.getAttribute('href') === '#' + en.target.id);
});
}
});
}, { rootMargin: '-40% 0px -55% 0px' });
document.querySelectorAll('section[id]').forEach(function (s) { spy.observe(s); });
}
// Background: drifting motes
if (!reduceMotion) {
var motes = document.querySelector('.bg-orbs');
var moteCount = window.innerWidth < 768 ? 16 : 34;
for (var mi = 0; mi < moteCount; mi++) {
var mote = document.createElement('span');
mote.className = 'particle';
var mSize = (Math.random() * 2.1 + 0.9).toFixed(1);
mote.style.cssText = [
'left:' + (Math.random() * 100).toFixed(2) + '%',
'bottom:' + (-(Math.random() * 12)).toFixed(1) + 'vh',
'width:' + mSize + 'px', 'height:' + mSize + 'px',
'--px:' + (Math.random() * 140 - 70).toFixed(1) + 'px',
'--po:' + (Math.random() * 0.4 + 0.15).toFixed(2),
'animation-duration:' + (Math.random() * 20 + 16).toFixed(1) + 's',
'animation-delay:' + (Math.random() * 22).toFixed(1) + 's'
].join(';');
motes.appendChild(mote);
}
}
// Background: gentle mouse parallax on the orb field
var orbs = document.querySelector('.bg-orbs');
var finePointer = window.matchMedia('(pointer: fine)').matches;
if (orbs && !reduceMotion && finePointer) {
var px = 0, py = 0, cx = 0, cy = 0, pRaf = null;
window.addEventListener('mousemove', function (e) {
px = (e.clientX / window.innerWidth - 0.5) * 44;
py = (e.clientY / window.innerHeight - 0.5) * 32;
if (!pRaf) { pRaf = requestAnimationFrame(parallaxStep); }
}, { passive: true });
function parallaxStep() {
cx += (px - cx) * 0.055; cy += (py - cy) * 0.055;
orbs.style.transform = 'translate3d(' + cx.toFixed(2) + 'px,' + cy.toFixed(2) + 'px,0)';
if (Math.abs(px - cx) > 0.05 || Math.abs(py - cy) > 0.05) { pRaf = requestAnimationFrame(parallaxStep); }
else { pRaf = null; }
}
}
// Hero terminal: subtle 3D tilt following the pointer
var term = document.querySelector('.terminal');
var heroEl = document.querySelector('.hero');
if (term && heroEl && !reduceMotion && finePointer) {
var tiltRaf = null;
heroEl.addEventListener('mousemove', function (e) {
if (tiltRaf) { return; }
tiltRaf = requestAnimationFrame(function () {
var r = term.getBoundingClientRect();
var dx = (e.clientX - (r.left + r.width / 2)) / r.width;
var dy = (e.clientY - (r.top + r.height / 2)) / r.height;
term.classList.add('tilting');
term.style.transform = 'perspective(900px) rotateX(' + (-dy * 4).toFixed(2) + 'deg) rotateY(' + (dx * 5).toFixed(2) + 'deg)';
tiltRaf = null;
});
});
heroEl.addEventListener('mouseleave', function () {
term.classList.remove('tilting');
term.style.transform = '';
});
}
</script>
</body>
</html>