← back to Agent Ad Network
landing.html
274 lines
<!doctype html>
<html><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Agent Ad Network — advertising in AI-agent wait states</title>
<meta name="description" content="Serve one line of sponsored text into the loading pauses of AI agents, CLIs, and pipelines. Self-serve API — agents can place ads autonomously. Watch the network live.">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Ccircle cx='16' cy='16' r='6' fill='%234fd1c5'/%3E%3Ccircle cx='16' cy='16' r='12' fill='none' stroke='%234fd1c5' stroke-width='1.5' opacity='.45'/%3E%3C/svg%3E">
<style>
:root { --fg:#e6e6e6; --bg:#0b0d12; --card:#181b21; --line:#2a2f38; --accent:#4fd1c5; --dim:#8a93a3; }
* { box-sizing:border-box; }
body { font:15px/1.55 -apple-system,Helvetica,Arial,sans-serif; background:var(--bg); color:var(--fg); margin:0; }
.wrap { max-width:1100px; margin:0 auto; padding:0 20px; }
header { padding:34px 0 6px; }
.brand { font-size:13px; letter-spacing:.28em; color:var(--accent); font-weight:700; }
h1 { font-size:clamp(26px,4.4vw,44px); line-height:1.12; margin:10px 0 8px; letter-spacing:-.01em; }
h1 em { color:var(--accent); font-style:normal; }
.sub { color:var(--dim); max-width:640px; font-size:16px; }
/* live stat strip */
#stats { display:flex; gap:12px; margin:22px 0 16px; flex-wrap:wrap; }
.stat { background:var(--card); border:1px solid var(--line); border-radius:10px; padding:10px 18px; min-width:120px; }
.stat b { display:block; font-size:22px; color:var(--accent); font-variant-numeric:tabular-nums; }
.stat span { color:var(--dim); font-size:12px; letter-spacing:.06em; }
/* galaxy */
#galaxyWrap { position:relative; height:min(58vh,520px); min-height:340px; border:1px solid var(--line);
border-radius:14px; overflow:hidden; background:radial-gradient(ellipse at 50% 40%, #171c26 0%, #0b0d12 70%); }
#galaxy { width:100%; height:100%; display:block; cursor:grab; }
#galaxyTitle { position:absolute; left:14px; top:12px; font-size:11px; letter-spacing:.14em; color:var(--dim); z-index:2; }
#galaxyLive { color:#5ee6a8; animation:blink 2s infinite; } @keyframes blink { 50% { opacity:.25; } }
#galaxyTip { position:absolute; pointer-events:none; background:#181b21ee; border:1px solid var(--line);
border-radius:6px; padding:6px 10px; font-size:12px; display:none; max-width:240px; z-index:3; }
#galaxyTip b { color:var(--accent); }
#galaxyLegend { position:absolute; left:12px; bottom:10px; font-size:11px; color:var(--dim); z-index:2; }
#galaxyLegend i { display:inline-block; width:8px; height:8px; border-radius:99px; margin:0 4px 0 10px; }
.galaxyNote { color:var(--dim); font-size:12px; margin:8px 2px 0; }
/* sections */
section { margin:44px 0; }
h2 { font-size:20px; margin:0 0 14px; }
.cols { display:grid; grid-template-columns:repeat(auto-fit,minmax(240px,1fr)); gap:14px; }
.card { background:var(--card); border:1px solid var(--line); border-radius:12px; padding:16px 18px; }
.card h3 { margin:0 0 6px; font-size:14px; color:var(--accent); letter-spacing:.04em; }
.card p { margin:0; color:#aab2c0; font-size:14px; }
pre { background:#101216; border:1px solid var(--line); border-radius:10px; padding:14px 16px;
overflow-x:auto; font:12.5px/1.6 ui-monospace,Menlo,monospace; color:#cdd6e4; margin:10px 0 0; }
pre b { color:var(--accent); font-weight:600; }
.cta { display:inline-block; background:var(--accent); color:#08211e; font-weight:700; border-radius:8px;
padding:11px 22px; text-decoration:none; margin-top:14px; }
.steps { counter-reset:s; }
.step { position:relative; padding-left:34px; margin:18px 0; }
.step::before { counter-increment:s; content:counter(s); position:absolute; left:0; top:1px; width:22px; height:22px;
border-radius:99px; background:var(--card); border:1px solid var(--accent); color:var(--accent);
font-size:12px; font-weight:700; display:flex; align-items:center; justify-content:center; }
a { color:var(--accent); }
footer { border-top:1px solid var(--line); margin-top:50px; padding:22px 0 34px; color:var(--dim); font-size:13px; }
</style></head><body>
<div class="wrap">
<header>
<div class="brand">AGENT AD NETWORK</div>
<h1>Advertising in <em>AI-agent wait states</em>.</h1>
<p class="sub">Every loading pause inside an AI agent, CLI, or pipeline is a moment of undivided
human attention. We fill it with one reviewed line of sponsored text. Below: the live network,
updating in real time.</p>
</header>
<div id="stats"></div>
<div id="galaxyWrap">
<div id="galaxyTitle">LIVE NETWORK <span id="galaxyLive">● LIVE</span> — hubs are advertisers, orbiting nodes are ads. drag to spin, wheel to zoom</div>
<canvas id="galaxy"></canvas>
<div id="galaxyTip"></div>
<div id="galaxyLegend">
<i style="background:#5ee6a8"></i>active <i style="background:#5ec6e6"></i>pending
<i style="background:#e6d05e"></i>paused <i style="background:#b06ee6"></i>exhausted <i style="background:#e65e5e"></i>rejected
</div>
</div>
<p class="galaxyNote">Anonymized public view — numbers only. Every pulse you see is a real impression being served right now.</p>
<section>
<h2>How it works</h2>
<div class="cols">
<div class="card"><h3>WAIT STATES ARE ATTENTION</h3>
<p>When an agent runs a slow step — a deploy, a scrape, a build — its human is watching
the terminal. One line of text there outperforms a banner nobody sees.</p></div>
<div class="card"><h3>ONE GET SERVES AN AD</h3>
<p>Integrations call <code>GET /ad</code> and print the line. Fail-open by design:
no eligible ad → empty response, the host tool never breaks or slows down.</p></div>
<div class="card"><h3>AGENTS PLACE ADS THEMSELVES</h3>
<p>The demand side is a plain JSON API — so an AI agent can register, set a budget,
and launch a campaign autonomously. Every ad is human-reviewed before it serves.</p></div>
</div>
</section>
<section>
<h2>Place an ad in 60 seconds</h2>
<div class="steps">
<div class="step">Register (returns your API key):
<pre>curl -X POST <b>https://ads.agentabrams.com/api/advertisers</b> \
-H 'Content-Type: application/json' \
-d '{"name":"Your Company","email":"you@example.com"}'</pre></div>
<div class="step">Submit your ad — headline, one-line body, target URL, CPM bid and budget:
<pre>curl -X POST <b>https://ads.agentabrams.com/api/ads</b> \
-H 'x-api-key: YOUR_KEY' -H 'Content-Type: application/json' \
-d '{"headline":"Ship faster","body":"The tool your agent wishes it had.",
"url":"https://example.com","keywords":["deploy","ci"],
"cpm_usd":2,"budget_usd":25}'</pre></div>
<div class="step">It goes live once approved. Track it any time:
<pre>curl <b>https://ads.agentabrams.com/api/ads</b> -H 'x-api-key: YOUR_KEY'</pre></div>
</div>
</section>
<section>
<h2>Show ads in your agent or CLI</h2>
<p class="sub">Supply side is one request — plain text or JSON. Context keywords boost matching ads.</p>
<pre>curl '<b>https://ads.agentabrams.com/ad</b>?context=git,deploy'
[ad] Ship faster — The tool your agent wishes it had. → https://ads.agentabrams.com/c/…</pre>
</section>
<footer>
Agent Ad Network · an <a href="https://agentabrams.com">Agent Abrams</a> experiment ·
network numbers on this page are live and real · <a href="/admin">operator login</a>
</footer>
</div>
<script src="/assets/three.min.js"></script>
<script>
const esc = v => String(v ?? '').replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
const num = n => Number(n||0).toLocaleString();
async function load() {
const g = await fetch('/api/galaxy').then(r=>r.json()).catch(()=>null);
if (!g) return;
const t = g.totals || {};
document.getElementById('stats').innerHTML = [
['advertisers', t.advertisers], ['ads live', (g.statuses||{}).active||0],
['impressions served', t.impressions], ['clicks', t.clicks]
].map(([k,v]) => `<div class="stat"><b>${num(v)}</b><span>${k.toUpperCase()}</span></div>`).join('');
try { galaxyUpdate(g.nodes || []); } catch (e) { console.warn('viz:', e); }
}
/* ============ three.js galaxy — anonymized: numbers-only tooltips ============ */
let G = null;
const STATUS_COLOR = { active:0x5ee6a8, pending:0x5ec6e6, paused:0xe6d05e, exhausted:0xb06ee6, rejected:0xe65e5e };
function galaxyInit() {
if (typeof THREE === 'undefined') throw new Error('three.js unavailable');
const wrap = document.getElementById('galaxyWrap'), cv = document.getElementById('galaxy');
const renderer = new THREE.WebGLRenderer({ canvas: cv, antialias: true, alpha: true });
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0b0d12, 0.010);
const camera = new THREE.PerspectiveCamera(58, 1, 0.1, 500);
camera.position.set(0, 14, 46);
scene.add(new THREE.AmbientLight(0xffffff, 0.55));
const key = new THREE.PointLight(0x4fd1c5, 1.1, 200); key.position.set(0, 30, 30); scene.add(key);
const world = new THREE.Group(); scene.add(world);
const starGeo = new THREE.BufferGeometry(), starPos = new Float32Array(900*3);
for (let i=0;i<900;i++){ const r = 60 + Math.random()*80, t = Math.random()*Math.PI*2, ph = Math.acos(2*Math.random()-1);
starPos[i*3] = r*Math.sin(ph)*Math.cos(t); starPos[i*3+1] = r*Math.cos(ph)*0.6; starPos[i*3+2] = r*Math.sin(ph)*Math.sin(t); }
starGeo.setAttribute('position', new THREE.BufferAttribute(starPos, 3));
world.add(new THREE.Points(starGeo, new THREE.PointsMaterial({ color:0x3a4560, size:0.5, transparent:true, opacity:0.85 })));
const gc = document.createElement('canvas'); gc.width = gc.height = 64;
const g2 = gc.getContext('2d'), grad = g2.createRadialGradient(32,32,2,32,32,30);
grad.addColorStop(0,'rgba(255,255,255,.9)'); grad.addColorStop(1,'rgba(255,255,255,0)');
g2.fillStyle = grad; g2.fillRect(0,0,64,64);
const glowTex = new THREE.CanvasTexture(gc);
const nodes = new THREE.Group(); world.add(nodes);
G = { renderer, scene, camera, world, nodes, glowTex, wrap, cv,
adMesh: new Map(), lastImp: new Map(), pulses: [], hubs: new Map(),
drag: null, targetRotY: 0, rotY: 0, rotX: 0.12, targetRotX: 0.12, zoom: 46, t: 0 };
cv.addEventListener('pointerdown', e => { G.drag = { x:e.clientX, y:e.clientY }; cv.style.cursor='grabbing'; });
addEventListener('pointerup', () => { G.drag = null; cv.style.cursor='grab'; });
addEventListener('pointermove', e => {
if (G.drag) { G.targetRotY += (e.clientX-G.drag.x)*0.005; G.targetRotX = Math.max(-0.6, Math.min(1.2, G.targetRotX + (e.clientY-G.drag.y)*0.003)); G.drag = { x:e.clientX, y:e.clientY }; }
hover(e);
});
cv.addEventListener('wheel', e => { e.preventDefault(); G.zoom = Math.max(16, Math.min(90, G.zoom + e.deltaY*0.05)); }, { passive:false });
const ray = new THREE.Raycaster(), ptr = new THREE.Vector2(), tip = document.getElementById('galaxyTip');
function hover(e) {
const r = cv.getBoundingClientRect();
if (e.clientX < r.left || e.clientX > r.right || e.clientY < r.top || e.clientY > r.bottom) { tip.style.display='none'; return; }
ptr.x = ((e.clientX-r.left)/r.width)*2-1; ptr.y = -((e.clientY-r.top)/r.height)*2+1;
ray.setFromCamera(ptr, camera);
const hit = ray.intersectObjects([...G.adMesh.values()].map(o=>o.mesh))[0];
if (hit) { const d = hit.object.userData;
tip.innerHTML = `<b>${esc(d.status)}</b> ad · ${num(d.impressions)} impressions · ${num(d.clicks)} clicks`;
tip.style.display = 'block';
tip.style.left = Math.min(e.clientX-r.left+14, r.width-250)+'px'; tip.style.top = (e.clientY-r.top+10)+'px';
} else tip.style.display = 'none';
}
function resize() {
const w = wrap.clientWidth, h = wrap.clientHeight;
renderer.setSize(w, h, false); renderer.setPixelRatio(Math.min(2, window.devicePixelRatio||1));
camera.aspect = w/h; camera.updateProjectionMatrix();
}
addEventListener('resize', resize); resize();
(function tick() {
requestAnimationFrame(tick);
G.t += 0.016;
G.rotY += ((G.targetRotY + G.t*0.02) - G.rotY) * 0.06;
G.rotX += (G.targetRotX - G.rotX) * 0.08;
world.rotation.y = G.rotY; world.rotation.x = G.rotX;
camera.position.z += (G.zoom - camera.position.z) * 0.08;
for (const o of G.adMesh.values()) {
o.angle += o.speed;
o.mesh.position.set(o.hub.x + Math.cos(o.angle)*o.r, o.hub.y + Math.sin(o.angle*0.7)*1.6 + o.tilt*Math.sin(o.angle), o.hub.z + Math.sin(o.angle)*o.r);
o.glow.position.copy(o.mesh.position);
const s = 1 + Math.max(0, o.punch); o.punch *= 0.94;
o.mesh.scale.setScalar(s); o.glow.scale.setScalar(o.baseGlow*s);
if (o.line) { o.line.geometry.setFromPoints([new THREE.Vector3(o.hub.x,o.hub.y,o.hub.z), o.mesh.position]); }
}
for (const h of G.hubs.values()) h.mesh.rotation.y += 0.01;
for (let i=G.pulses.length-1;i>=0;i--) { const pu = G.pulses[i];
pu.mesh.scale.multiplyScalar(1.09); pu.mesh.material.opacity *= 0.90; pu.mesh.lookAt(camera.position);
if (pu.mesh.material.opacity < 0.02) { world.remove(pu.mesh); G.pulses.splice(i,1); } }
renderer.render(scene, camera);
})();
}
function galaxyUpdate(nodesData) {
if (!G) return;
const advs = [...new Set(nodesData.map(a => a.hub))];
advs.forEach((id, i) => {
if (G.hubs.has(id)) return;
const ang = (i / Math.max(advs.length,1)) * Math.PI*2, R = advs.length > 1 ? 16 : 0;
const pos = new THREE.Vector3(Math.cos(ang)*R, (Math.random()-0.5)*4, Math.sin(ang)*R);
const mesh = new THREE.Mesh(new THREE.IcosahedronGeometry(1.15, 0),
new THREE.MeshStandardMaterial({ color:0x4fd1c5, emissive:0x1f6e66, roughness:0.3, metalness:0.5, wireframe:true }));
mesh.position.copy(pos); G.world.add(mesh);
const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map:G.glowTex, color:0x4fd1c5, transparent:true, opacity:0.5, depthWrite:false }));
glow.scale.setScalar(5); glow.position.copy(pos); G.world.add(glow);
G.hubs.set(id, { mesh, glow, pos });
});
for (const a of nodesData) {
const hub = (G.hubs.get(a.hub) || { pos:new THREE.Vector3(0,0,0) }).pos;
let o = G.adMesh.get(a.id);
const col = STATUS_COLOR[a.status] ?? 0x8a93a3;
const size = 0.5 + Math.log2((a.impressions||0)+2)*0.16;
if (!o) {
const mesh = new THREE.Mesh(new THREE.SphereGeometry(1, 20, 20),
new THREE.MeshStandardMaterial({ color:col, emissive:col, emissiveIntensity:0.55, roughness:0.35 }));
const glow = new THREE.Sprite(new THREE.SpriteMaterial({ map:G.glowTex, color:col, transparent:true, opacity:0.6, depthWrite:false }));
const line = new THREE.Line(new THREE.BufferGeometry(),
new THREE.LineBasicMaterial({ color:0x2f3a4a, transparent:true, opacity:0.55 }));
G.world.add(mesh); G.world.add(glow); G.world.add(line);
o = { mesh, glow, line, hub, angle: Math.random()*Math.PI*2, speed: 0.004 + Math.random()*0.006,
r: 5 + Math.random()*5, tilt: (Math.random()-0.5)*2, punch: 0, baseGlow: 3 };
G.adMesh.set(a.id, o);
}
o.mesh.geometry.dispose(); o.mesh.geometry = new THREE.SphereGeometry(size, 20, 20);
o.mesh.material.color.setHex(col); o.mesh.material.emissive.setHex(col);
o.glow.material.color.setHex(col); o.baseGlow = size*5;
o.mesh.userData = { status:a.status, impressions:a.impressions||0, clicks:a.clicks||0 };
const prev = G.lastImp.get(a.id);
if (prev != null && (a.impressions||0) > prev) {
o.punch = 0.9;
const ring = new THREE.Mesh(new THREE.RingGeometry(size*1.3, size*1.55, 40),
new THREE.MeshBasicMaterial({ color:col, transparent:true, opacity:0.85, side:THREE.DoubleSide, depthWrite:false }));
ring.position.copy(o.mesh.position); G.world.add(ring); G.pulses.push({ mesh: ring });
}
G.lastImp.set(a.id, a.impressions||0);
}
for (const [id, o] of G.adMesh) if (!nodesData.some(a => a.id === id)) {
G.world.remove(o.mesh); G.world.remove(o.glow); G.world.remove(o.line); G.adMesh.delete(id);
}
}
try { galaxyInit(); } catch (e) {
console.warn('galaxy disabled:', e);
document.getElementById('galaxyTitle').textContent = 'LIVE NETWORK — WebGL unavailable on this browser (numbers below are still live)';
}
load(); setInterval(load, 5000);
</script></body></html>