// Tellr · shared components

const { useState, useEffect, useRef } = React;

// ---------- Nav ----------
function Nav() {
  return (
    <nav className="nav">
      <div className="wrap nav-inner">
        <a href="#" className="logo">
          <span className="logo-mark" aria-hidden />
          <span>tellr</span>
          <span className="mono" style={{color:'var(--fg-dim)', fontSize:12, marginLeft:4}}>v0.9.2</span>
        </a>
        <div className="nav-links" style={{display:'flex'}}>
          <a href="#how">How it works</a>
          <a href="#checks">Checks</a>
          <a href="#alerts">Alerts</a>
          <a href="#ecosystem">Ecosystem</a>
          <a href="#pricing">Pricing</a>
          <a href="#oss">Open source</a>
          <a href="docs.html" className="mono" style={{fontSize:13}}>docs ↗</a>
        </div>
        <div className="nav-cta">
          <a href="status.html" className="status" title="live status">all systems nominal</a>
          <a className="btn btn-primary" href="#install">Install</a>
        </div>
      </div>
    </nav>
  );
}

// ---------- Hero ----------
function Hero() {
  return (
    <section style={{paddingTop: 88, paddingBottom: 88}}>
      <div className="wrap hero-grid">
        <div>
          <div className="tag" style={{marginBottom: 28}}>Self-hosted monitoring, v0.9.2</div>
          <h1 className="display">
            Stop watching<br/>dashboards.
          </h1>
          <p className="lede" style={{marginTop: 24}}>
            Tellr checks your services every few minutes and pings you in Slack, Telegram, or Discord
            only when something is actually wrong, with a written explanation and a suggested fix.
          </p>
          <div style={{display:'flex', gap:10, marginTop: 32, flexWrap:'wrap'}}>
            <a href="#install" className="btn btn-primary">
              <span className="mono">$</span> Install now
            </a>
            <a href="#how" className="btn btn-ghost">See how it works →</a>
          </div>
          <div style={{display:'flex', gap: 28, marginTop: 36, color:'var(--fg-dim)', fontSize:13}}>
            <span className="mono">one binary</span>
            <span className="mono">no agent</span>
            <span className="mono">no account</span>
          </div>
        </div>

        <div style={{display:'flex', flexDirection:'column', gap: 16}}>
          <HeroTerminal />
        </div>
      </div>
    </section>
  );
}

// ---------------- xterm.js powered terminal ----------------
// ANSI helpers
const A = {
  reset: '\x1b[0m',
  dim:   '\x1b[38;2;145;150;160m',
  muted: '\x1b[38;2;200;205;215m',
  fg:    '\x1b[38;2;240;242;246m',
  amber: '\x1b[38;2;220;165;90m',
  green: '\x1b[38;2;120;200;140m',
  red:   '\x1b[38;2;220;105;95m',
  yel:   '\x1b[38;2;225;200;110m',
  cyan:  '\x1b[38;2;140;200;220m',
  bold:  '\x1b[1m',
  clear: '\x1b[2J\x1b[H',
  home:  '\x1b[H',
};

const XTERM_THEME = {
  background: '#00000000',
  foreground: '#f0f2f6',
  cursor: '#dca55a',
  cursorAccent: '#1a1c22',
  black: '#1a1c22',
  brightBlack: '#70747f',
  red: '#dc6969',
  green: '#78c88c',
  yellow: '#e1c86e',
  blue: '#8cc8dc',
  magenta: '#b48ccc',
  cyan: '#8cc8dc',
  white: '#f0f2f6',
  brightWhite: '#ffffff',
  selectionBackground: '#dca55a55',
};

function createTerm() {
  return new window.Terminal({
    fontFamily: "'JetBrains Mono', ui-monospace, Menlo, monospace",
    fontSize: 13,
    lineHeight: 1.25,
    letterSpacing: 0,
    cursorBlink: true,
    cursorStyle: 'block',
    theme: XTERM_THEME,
    cols: 78,
    rows: 12,
    allowTransparency: true,
    scrollback: 200,
    disableStdin: true,
    convertEol: true,
  });
}

// Slowly write a string char-by-char to feel alive (~ms per char)
function typeInto(term, s, msPerChar = 0) {
  return new Promise(resolve => {
    if (msPerChar <= 0) { term.write(s); resolve(); return; }
    let i = 0;
    const tick = () => {
      if (i >= s.length) { resolve(); return; }
      term.write(s[i++]);
      setTimeout(tick, msPerChar);
    };
    tick();
  });
}
const wait = ms => new Promise(r => setTimeout(r, ms));

function HeroTerminal() {
  const TABS = ['install', 'watch', 'status'];
  const termsRef = useRef({});        // { install: Terminal, watch, status }
  const nodeRefs = { install: useRef(null), watch: useRef(null), status: useRef(null) };
  const [active, setActive] = useState('install');
  const [running, setRunning] = useState(null); // which tab is currently typing
  const cancelledRef = useRef(false);

  // Initialize terminals once
  useEffect(() => {
    if (!window.Terminal) return;
    for (const t of TABS) {
      if (!termsRef.current[t] && nodeRefs[t].current) {
        const term = createTerm();
        term.open(nodeRefs[t].current);
        // initial prompt on each
        term.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
        termsRef.current[t] = term;
      }
    }
    return () => {
      for (const t of TABS) {
        if (termsRef.current[t]) { termsRef.current[t].dispose(); termsRef.current[t] = null; }
      }
    };
  }, []);

  // Orchestrate the scene loop
  useEffect(() => {
    cancelledRef.current = false;
    const inst = termsRef.current;
    const ok = () => !cancelledRef.current && inst.install && inst.watch && inst.status;

    const clearAll = () => {
      for (const t of TABS) {
        if (inst[t]) { inst[t].clear(); inst[t].reset(); }
      }
    };

    async function sceneInstall() {
      setActive('install'); setRunning('install');
      const t = inst.install;
      t.clear(); t.reset();
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      await typeInto(t, 'curl -fsSL install.tellr.dev | bash', 38);
      if (!ok()) return; await wait(650);
      t.writeln('');
      t.writeln(`${A.dim}  → detecting system: linux/amd64, systemd${A.reset}`); await wait(560);
      if (!ok()) return;
      t.writeln(`${A.dim}  → downloading tellr 0.9.2 (8.4 MB)${A.reset}`); await wait(620);
      if (!ok()) return;
      // progress bar
      const bar = (p) => {
        const n = 24; const f = Math.round(n * p);
        return `  ${A.amber}${'█'.repeat(f)}${A.dim}${'░'.repeat(n - f)}${A.reset} ${Math.round(p*100)}%`;
      };
      for (let p = 0; p <= 1.0001; p += 0.1) {
        if (!ok()) return;
        t.write(`\r${bar(p)}`); await wait(130);
      }
      t.writeln('');
      t.writeln(`${A.dim}  → writing /etc/tellr/config.yml${A.reset}`); await wait(520);
      if (!ok()) return;
      t.writeln(`${A.green}✓${A.reset} ${A.muted}installed to /usr/local/bin/tellr${A.reset}`);
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      setRunning(null);
    }

    async function sceneWatch() {
      setActive('watch'); setRunning('watch');
      const t = inst.watch;
      t.clear(); t.reset();
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      await typeInto(t, 'tellr watch api.prod db.prod workers', 42);
      if (!ok()) return; await wait(550);
      t.writeln('');
      t.writeln(`${A.dim}  → probing api.prod  … ${A.green}200 OK${A.dim} · 84ms${A.reset}`); await wait(620);
      if (!ok()) return;
      t.writeln(`${A.dim}  → probing db.prod   … ${A.green}connected${A.dim} · 12ms${A.reset}`); await wait(620);
      if (!ok()) return;
      t.writeln(`${A.dim}  → probing workers   … ${A.yel}queue depth 3${A.reset}`); await wait(620);
      if (!ok()) return;
      t.writeln(`${A.green}✓${A.reset} ${A.muted}registered 3 targets, 11 checks · slack${A.reset} ${A.amber}#ops-alerts${A.reset}`);
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      setRunning(null);
    }

    // TUI dashboard in status tab. Redraws frames in place.
    async function sceneStatusTui(durationMs = 5200) {
      setActive('status'); setRunning('status');
      const t = inst.status;
      t.clear(); t.reset();
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ tellr status --live\r\n`);
      const start = Date.now();
      let tick = 0;

      // Fixed inner width between the │ borders (visible cols)
      const W = 52;
      const stripAnsi = s => s.replace(/\u001b\[[0-9;]*m/g, '');
      const padTo = (s, n) => {
        const visible = stripAnsi(s).length;
        return s + ' '.repeat(Math.max(0, n - visible));
      };
      const bar = (ch, n) => ch.repeat(n);
      const rowTop = (label) => {
        const left = '─ ' + label + ' ';
        const pad = W - left.length; // dashes after label, purely ascii
        return `${A.muted}┌${left}${bar('─', Math.max(0, pad))}┐${A.reset}`;
      };
      const rowMid = (label) => {
        const left = '─ ' + label + ' ';
        const pad = W - left.length;
        return `${A.muted}├${left}${bar('─', Math.max(0, pad))}┤${A.reset}`;
      };
      const rowSep = () => `${A.muted}├${bar('─', W)}┤${A.reset}`;
      const rowBot = (hint) => {
        const right = ' ' + hint + ' ';
        const pad = W - right.length;
        return `${A.muted}└${bar('─', Math.max(0, pad))}${right}┘${A.reset}`;
      };
      const row = (content) => `${A.muted}│${A.reset} ${padTo(content, W - 2)} ${A.muted}│${A.reset}`;

      const frame = () => {
        const barFor = (seed) => {
          const v = (Math.sin((tick + seed) * 0.7) * 0.5 + 0.5);
          const len = 10 + Math.floor(v * 16);
          return `${A.amber}${'█'.repeat(len)}${A.dim}${'░'.repeat(26 - len)}${A.reset}`;
        };
        const p95 = 84 + Math.floor(Math.cos(tick * 0.3) * 9);
        const rps = 120 + Math.floor(Math.sin(tick * 0.4) * 14);
        const mins = 22 + (tick % 38);
        const queue = 3 + (tick % 4);

        // Target rows: name(8) + 2 space + bar(26) + 2 space + status
        const targetRow = (name, barStr, status) =>
          row(`${name.padEnd(8)}  ${barStr}  ${status}`);

        let out = A.home;
        out += rowTop('tellr · 3 targets · 11 checks') + '\r\n';
        out += targetRow('api.prod', barFor(0), `${A.green}ok ${String(p95).padStart(3)}ms${A.reset}`) + '\r\n';
        out += targetRow('db.prod',  barFor(3), `${A.green}ok 43%${A.reset}    `) + '\r\n';
        out += targetRow('workers',  barFor(7), `${A.green}ok q${queue}${A.reset}      `) + '\r\n';
        out += rowSep() + '\r\n';
        out += row(`uptime 3d 14h ${String(mins).padStart(2,'0')}m   ${String(rps).padStart(3)} rps   silent 3d 14h`) + '\r\n';

        // events sparkline row — exactly 36 bars
        let spark = '';
        for (let i = 0; i < 36; i++) {
          const hot = i >= 30 && tick % 2 === 0;
          spark += hot ? `${A.red}▮${A.reset}` : `${A.dim}·${A.reset}`;
        }
        out += row(`events ${spark}`) + '\r\n';
        out += rowBot('q to quit');
        t.write(out);
      };
      t.write('\r\n');
      while (ok() && Date.now() - start < durationMs) {
        frame();
        tick++;
        await wait(520);
      }
      setRunning(null);
    }

    async function sceneAlertInWatch() {
      setActive('watch'); setRunning('watch');
      const t = inst.watch;
      await wait(400);
      t.writeln(`${A.dim}[02:14:07]  api.prod  POST /checkout … ${A.red}500${A.reset}`); await wait(450);
      if (!ok()) return;
      t.writeln(`${A.dim}[02:14:22]  api.prod  POST /checkout … ${A.red}500${A.reset}`); await wait(450);
      if (!ok()) return;
      t.writeln(`${A.dim}[02:14:41]  api.prod  POST /checkout … ${A.red}500${A.reset}`); await wait(600);
      if (!ok()) return;
      t.writeln(`${A.red}✗${A.reset} ${A.muted}error rate on api.prod: ${A.red}14×${A.muted} baseline over 4 min${A.reset}`); await wait(750);
      if (!ok()) return;
      t.writeln(`${A.dim}  → composing alert (gpt-4o · your key)…${A.reset}`); await wait(1100);
      if (!ok()) return;
      t.writeln(`${A.amber}→${A.reset} ${A.muted}sent to${A.reset} ${A.amber}slack #ops-alerts${A.reset} ${A.dim}· 127 tok · $0.0008${A.reset}`);
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      setRunning(null);
    }

    async function sceneStatusFinal() {
      setActive('status'); setRunning('status');
      const t = inst.status;
      t.write(`\r\n${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      await typeInto(t, 'tellr status', 42);
      if (!ok()) return; await wait(500);
      t.writeln('');
      t.writeln(`${A.dim}  api.prod  ${A.green}ok${A.dim}  rolled back to a3f20b · 12 min ago${A.reset}`); await wait(580);
      if (!ok()) return;
      t.writeln(`${A.dim}  db.prod   ${A.green}ok${A.dim}  disk 43%${A.reset}`); await wait(580);
      if (!ok()) return;
      t.writeln(`${A.dim}  workers   ${A.green}ok${A.dim}  queue depth 2${A.reset}`); await wait(580);
      if (!ok()) return;
      t.writeln(`${A.green}✓${A.reset} ${A.muted}all checks passing. silent for 3d 14h.${A.reset}`);
      t.write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
      setRunning(null);
    }

    async function run() {
      // wait for xterm to mount
      let tries = 0;
      while (!ok() && tries < 40) { await wait(60); tries++; }
      while (ok()) {
        await sceneInstall();           if (!ok()) return;
        await wait(1300);
        await sceneWatch();             if (!ok()) return;
        await wait(950);
        await sceneStatusTui();         if (!ok()) return;
        await wait(700);
        await sceneAlertInWatch();      if (!ok()) return;
        await wait(1100);
        await sceneStatusFinal();       if (!ok()) return;
        await wait(2600);
        clearAll();
        for (const tabName of TABS) {
          termsRef.current[tabName].write(`${A.dim}tellr@prod${A.reset}:${A.cyan}~${A.reset}$ `);
        }
      }
    }
    run();
    return () => { cancelledRef.current = true; };
  }, []);

  return (
    <div className="card rise">
      <div className="term-head" style={{padding: 0, borderBottom:'1px solid var(--line-soft)', background:'var(--bg-3)'}}>
        <div style={{display:'flex', alignItems:'center', gap:8, padding:'10px 14px'}}>
          <span className="term-dot" /><span className="term-dot" /><span className="term-dot" />
        </div>
        <div style={{display:'flex', alignItems:'stretch', flex:1}}>
          {TABS.map(t => {
            const isActive = active === t;
            const isRunning = running === t;
            return (
              <div key={t}
                onClick={() => setActive(t)}
                style={{
                  padding: '10px 14px',
                  fontSize: 12,
                  fontFamily: 'var(--font-mono)',
                  color: isActive ? 'var(--fg)' : 'var(--fg-dim)',
                  background: isActive ? 'var(--bg-2)' : 'transparent',
                  borderLeft: '1px solid var(--line-soft)',
                  borderBottom: isActive ? '1px solid var(--bg-2)' : '1px solid var(--line-soft)',
                  marginBottom: isActive ? -1 : 0,
                  display:'flex', alignItems:'center', gap:8,
                  cursor: 'pointer',
                }}>
                {isRunning
                  ? <span style={{width:6, height:6, borderRadius:'50%', background:'var(--accent)'}} />
                  : <span style={{width:6, height:6, borderRadius:'50%', background: isActive ? 'var(--fg-dim)' : 'transparent'}} />
                }
                {t}
              </div>
            );
          })}
        </div>
        <div style={{padding:'10px 14px', fontSize:11, color:'var(--fg-dim)', fontFamily:'var(--font-mono)', borderLeft:'1px solid var(--line-soft)'}}>
        </div>
      </div>
      <div style={{position:'relative', background:'var(--bg-2)', padding: '14px 16px', minHeight: 260}}>
        {TABS.map(t => (
          <div key={t}
            ref={nodeRefs[t]}
            className="xterm-host"
            style={{
              position: active === t ? 'relative' : 'absolute',
              inset: active === t ? 'auto' : '14px 16px',
              opacity: active === t ? 1 : 0,
              transition: 'opacity 420ms ease',
              pointerEvents: active === t ? 'auto' : 'none',
              width: active === t ? '100%' : 'calc(100% - 32px)',
            }}
          />
        ))}
      </div>
    </div>
  );
}

function HeroAlertPeek() {
  return (
    <div className="alert" style={{animation:'rise .6s .3s ease-out both'}}>
      <div className="alert-head">
        <div className="platform">
          <span className="platform-dot" style={{background:'var(--purple)'}} />
          <span>slack · #ops-alerts</span>
        </div>
        <span>just now</span>
      </div>
      <div className="alert-body">
        <div className="msg">
          <div className="alert-bar" />
          <div style={{flex:1}}>
            <div className="msg-head">
              <span className="msg-name">tellr</span>
              <span className="msg-badge">app</span>
              <span className="msg-time">2:14 AM</span>
            </div>
            <div style={{fontWeight:600, marginBottom: 6}}>
              <span style={{color:'var(--red)'}}>●</span> api.prod · error rate up 14× in 4 min
            </div>
            <div style={{color:'var(--fg-muted)', fontSize:13.5, lineHeight:1.6}}>
              500s started climbing on <span className="code-inline">POST /checkout</span> at 2:10 AM.
              Likely cause: the deploy <span className="code-inline">a3f21c</span> that shipped 6 min ago
              changed the Stripe webhook handler. Try rolling back or checking <span className="code-inline">stripe_client.rb:142</span>.
            </div>
          </div>
        </div>
      </div>
    </div>
  );
}

// ---------- How it works ----------
function HowItWorks() {
  return (
    <section id="how">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">01</span><span>how it works</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth: '18ch', marginBottom: 40}}>
          Three commands. That's the whole product.
        </h2>

        <div className="how-grid">
          <div className="how-step">
            <div className="how-head">
              <span className="how-num">01</span>
              <span className="how-title">Install</span>
            </div>
            <p className="how-body">One curl. A single static binary. No daemon to babysit, no agent to update, no account to make.</p>
            <div className="how-art how-art--term">
              <div className="how-art-head">
                <span className="term-dot" /><span className="term-dot" /><span className="term-dot" />
                <span className="mono" style={{marginLeft:8, color:'var(--fg-dim)', fontSize:11}}>~/infra</span>
              </div>
              <div className="how-art-body mono">
                <div><span className="c-dim">$</span> curl -fsSL install.tellr.dev | bash</div>
                <div className="c-dim">&nbsp;&nbsp;→ tellr 0.9.2 · linux/amd64 · 8.4 MB</div>
                <div><span className="c-green">✓</span> <span className="c-muted">installed in 3.2s</span></div>
                <div><span className="c-dim">$</span><span className="cursor" /></div>
              </div>
            </div>
          </div>

          <div className="how-step">
            <div className="how-head">
              <span className="how-num">02</span>
              <span className="how-title">Point it at your services</span>
            </div>
            <p className="how-body">A short YAML or one <span className="code-inline">tellr watch</span> command. Sensible defaults per service: HTTP, TCP, disk, memory.</p>
            <div className="how-art how-art--yaml">
              <div className="how-art-head">
                <span className="mono" style={{color:'var(--fg-dim)', fontSize:11}}>tellr.yml</span>
                <span className="mono" style={{marginLeft:'auto', color:'var(--fg-dim)', fontSize:11}}>11 checks</span>
              </div>
              <div className="how-art-body mono">
                <div><span className="c-purple">targets</span>:</div>
                <div>&nbsp;&nbsp;- <span className="c-accent">api.prod</span></div>
                <div>&nbsp;&nbsp;&nbsp;&nbsp;<span className="c-muted">http</span>: https://api.tellr.dev/healthz</div>
                <div>&nbsp;&nbsp;&nbsp;&nbsp;<span className="c-muted">every</span>: 30s</div>
                <div>&nbsp;&nbsp;- <span className="c-accent">db.prod</span></div>
                <div>&nbsp;&nbsp;&nbsp;&nbsp;<span className="c-muted">disk</span>: <span className="c-yellow">80%</span></div>
              </div>
            </div>
          </div>

          <div className="how-step">
            <div className="how-head">
              <span className="how-num">03</span>
              <span className="how-title">Get pinged when something breaks</span>
            </div>
            <p className="how-body">Slack, Telegram, or Discord. A plain-language explanation, a suspected cause, a suggested fix. Silent when nothing's wrong.</p>
            <div className="how-art how-art--msg">
              <div className="how-art-head" style={{display:'flex', alignItems:'center', gap:8}}>
                <span className="platform-dot" style={{background:'var(--purple)'}} />
                <span className="mono" style={{color:'var(--fg-dim)', fontSize:11}}>slack · #ops-alerts</span>
              </div>
              <div className="how-art-body" style={{display:'flex', gap:10, padding:'12px 14px'}}>
                <div style={{width:3, background:'var(--accent)', borderRadius:2, alignSelf:'stretch'}} />
                <div style={{flex:1, minWidth:0}}>
                  <div style={{fontSize:12, color:'var(--fg-dim)', fontFamily:'var(--font-mono)', marginBottom:4}}>tellr · 2:14 AM</div>
                  <div style={{fontSize:13.5, fontWeight:600, letterSpacing:'-0.01em', marginBottom:4}}>
                    <span style={{color:'var(--red)'}}>●</span> api.prod · error rate up 14×
                  </div>
                  <div style={{fontSize:12.5, color:'var(--fg-muted)', lineHeight:1.5}}>
                    Likely the deploy <span className="code-inline" style={{fontSize:11}}>a3f21c</span> 6 min ago. Try rolling back.
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- Checks ----------
function Checks() {
  const items = [
    {
      k: 'HTTP', title: 'HTTP probes',
      body: 'GET, POST, or a full request recipe. Status codes, body match, latency budgets.',
      yaml: `- api.prod
    http: https://api.tellr.dev/healthz
    expect: 200
    body_contains: "ok"
    budget: 400ms
    every: 30s`,
      log: [
        { t: 'dim', s: '[14:02:11] api.prod · GET /healthz … ' },
        { t: 'green', s: '200 · 84ms · ok' },
        { t: 'dim', s: '\n[14:02:41] api.prod · GET /healthz … ' },
        { t: 'green', s: '200 · 79ms · ok' },
        { t: 'dim', s: '\n[14:03:11] api.prod · GET /healthz … ' },
        { t: 'green', s: '200 · 91ms · ok' },
      ],
    },
    {
      k: 'ERR', title: 'Error-rate anomalies',
      body: 'Watches log streams or Sentry for sudden spikes relative to the last seven days.',
      yaml: `- workers
    errors:
      source: sentry
      project: worker-prod
      baseline: 7d
      threshold: 3x`,
      log: [
        { t: 'dim', s: '[14:02:00] workers · error rate · ' },
        { t: 'green', s: '0.02% · within baseline' },
        { t: 'dim', s: '\n[14:02:30] workers · error rate · ' },
        { t: 'green', s: '0.03% · within baseline' },
        { t: 'dim', s: '\n[14:03:00] workers · error rate · ' },
        { t: 'red', s: '0.41% · 14× baseline ✗' },
      ],
    },
    {
      k: 'SYS', title: 'Disk & memory',
      body: 'Per-host thresholds with a trend view. Warns early when disks fill linearly.',
      yaml: `- db.prod
    disk:
      path: /var/lib/postgres
      warn: 80%
      crit: 90%
      forecast: 48h`,
      log: [
        { t: 'dim', s: '[14:00] db.prod · /var/lib/postgres · ' },
        { t: 'green', s: '43% · ok' },
        { t: 'dim', s: '\n[24h ago] · ' },
        { t: 'dim', s: '39% · +4%/24h' },
        { t: 'dim', s: '\nforecast · ' },
        { t: 'yellow', s: 'reaches 80% in ~36h · warn' },
      ],
    },
    {
      k: 'DPL', title: 'Deploy-triggered watching',
      body: 'Tighter checks for 20 minutes after a deploy. Catches regressions while the PR is still open.',
      yaml: `- workers
    deploy_watch:
      source: github
      repo: tellr/monorepo
      window: 20m
      strict: true`,
      log: [
        { t: 'dim', s: '[14:36] deploy ' },
        { t: 'accent', s: '7e0d1b' },
        { t: 'dim', s: ' detected · watching 20m' },
        { t: 'dim', s: '\n[14:47] email.send p95 · 420ms → ' },
        { t: 'red', s: '3.1s ✗' },
        { t: 'dim', s: '\n→ pinged ' },
        { t: 'accent', s: '#alerts' },
        { t: 'dim', s: ' (post-deploy regression)' },
      ],
    },
    {
      k: 'LLM', title: 'Custom LLM checks',
      body: 'Give it a prompt and a data source. "Is the signup funnel still converting like yesterday?"',
      yaml: `- signup_funnel
    llm:
      prompt: "Is conversion within 10% of
        yesterday for the same hour?"
      data: select * from events_daily
      every: 1h`,
      log: [
        { t: 'dim', s: '[15:00] running check · gpt-4o · your key' },
        { t: 'dim', s: '\nconversion today · ' },
        { t: 'green', s: '3.8%' },
        { t: 'dim', s: '  yesterday · 3.6%' },
        { t: 'dim', s: '\nmodel verdict · ' },
        { t: 'green', s: 'within 10% · ok' },
        { t: 'dim', s: '\ncost · $0.0012 · 380 tok' },
      ],
    },
    {
      k: 'JOB', title: 'Cron & queue health',
      body: 'Heartbeats for scheduled jobs. Alerts when queues back up or a run is skipped.',
      yaml: `- nightly_backup
    cron: "0 3 * * *"
    timeout: 15m
    queue:
      name: default
      max_depth: 500`,
      log: [
        { t: 'dim', s: '[03:00] nightly_backup · started' },
        { t: 'dim', s: '\n[03:08] nightly_backup · ' },
        { t: 'green', s: 'done · 8m 12s · ok' },
        { t: 'dim', s: '\nqueue default · depth ' },
        { t: 'green', s: '2 · ok' },
      ],
    },
  ];

  const [sel, setSel] = useState(0);
  const active = items[sel];

  const colorOf = t => ({
    dim: 'var(--fg-dim)', green: 'var(--green)', red: 'var(--red)',
    yellow: 'var(--yellow)', accent: 'var(--accent)', muted: 'var(--fg-muted)',
  }[t] || 'var(--fg)');

  return (
    <section id="checks">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">02</span><span>what it watches</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth:'22ch', marginBottom:10}}>
          Six check types. Not sixty.
        </h2>
        <p className="lede" style={{marginBottom: 40}}>
          Enough to cover the 90% of things that actually go wrong on a small stack.
          Click one to see what it looks like running.
        </p>
        <div className="checks">
          {items.map((it, i) => (
            <div
              className={'check' + (i === sel ? ' sel' : '')}
              key={it.k}
              onClick={() => setSel(i)}
              role="button"
              tabIndex={0}
            >
              <div className="check-ico mono">{it.k}</div>
              <h4>{it.title}</h4>
              <p>{it.body}</p>
            </div>
          ))}
        </div>

        <div className="check-demo">
          <div className="check-demo-pane">
            <h5>
              <span>config · tellr.yml</span>
            </h5>
            <pre>{active.yaml}</pre>
          </div>
          <div className="check-demo-pane">
            <h5>
              <span>live output · {active.k.toLowerCase()}</span>
              <span className="check-run mono">running</span>
            </h5>
            <pre>
              {active.log.map((l, i) => (
                <span key={i} style={{color: colorOf(l.t)}}>{l.s}</span>
              ))}
            </pre>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- Alerts ----------
function Alerts() {
  return (
    <section id="alerts">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">03</span><span>sample alerts</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth: '22ch', marginBottom: 10}}>
          What an alert actually looks like.
        </h2>
        <p className="lede" style={{marginBottom: 40}}>
          Three real pings from three real outages. No metric soup, just a sentence that tells you what broke, why it probably broke, and where to look.
        </p>

        <div className="alerts">
          <AlertCard
            platform="slack" channel="#ops-alerts" color="var(--purple)" time="2 min ago"
            name="tellr" badge="app" at="2:14 AM"
            title={<><span style={{color:'var(--red)'}}>●</span>&nbsp; api.prod · error rate up 14× in 4 min</>}
            body={<>
              500s started climbing on <span className="code-inline">POST /checkout</span> at 2:10 AM.
              The deploy <span className="code-inline">a3f21c</span> 6 min ago changed the Stripe webhook handler.
              Likely fix: roll back, or check <span className="code-inline">stripe_client.rb:142</span>. The signature verify looks off.
            </>}
            extra={<Sparkline hotAt={7} />}
          />
          <AlertCard
            platform="telegram" channel="@tellr_bot → you" color="var(--blue)" time="11 min ago"
            name="tellr" badge="bot" at="3:02 AM"
            title={<><span style={{color:'var(--yellow)'}}>●</span>&nbsp; db.prod · disk 87%, full in ~36h</>}
            body={<>
              <span className="code-inline">/var/lib/postgres</span> grew 4.1 GB in the last 24h. Steady, not a spike.
              The <span className="code-inline">events</span> table is up 2.3 GB; retention policy still set to 90 days.
              Either bump the disk or drop old events before Friday.
            </>}
            extra={<div className="mono" style={{marginTop:10, fontSize:12, color:'var(--fg-dim)'}}>
              43% ████████████░░░░░░░░░░ 87%
            </div>}
          />
          <AlertCard
            platform="discord" channel="#alerts" color="var(--red)" time="just now"
            name="tellr" badge="bot" at="14:47"
            title={<><span style={{color:'var(--red)'}}>●</span>&nbsp; post-deploy regression on workers</>}
            body={<>
              Deploy <span className="code-inline">7e0d1b</span> shipped 11 min ago. Since then,
              <span className="code-inline"> email.send</span> job p95 went from 420ms → 3.1s and retries are up 8×.
              The diff touched <span className="code-inline">lib/mailer.rb</span>. Probably the new attachment path. Worth a look.
            </>}
            extra={<div style={{marginTop:12, display:'flex', gap:10, fontSize:12, color:'var(--fg-muted)', fontFamily:'var(--font-mono)'}}>
              <span>p95: <span style={{color:'var(--red)'}}>3.1s</span></span>
              <span>retries: <span style={{color:'var(--red)'}}>+812%</span></span>
              <span>queue: <span style={{color:'var(--yellow)'}}>1,402</span></span>
            </div>}
          />
        </div>

        <div style={{marginTop: 28, color:'var(--fg-muted)', fontSize: 13.5, fontFamily:'var(--font-mono)', display:'flex', gap: 20, flexWrap:'wrap'}}>
          <span>└─ LLM-composed message, using your own API key</span>
        </div>
      </div>
    </section>
  );
}

function AlertCard({platform, channel, color, time, name, badge, at, title, body, extra}) {
  return (
    <div className="alert">
      <div className="alert-head">
        <div className="platform">
          <span className="platform-dot" style={{background: color}} />
          <span>{platform} · {channel}</span>
        </div>
        <span>{time}</span>
      </div>
      <div className="alert-body">
        <div className="msg">
          <div className="alert-bar" />
          <div style={{flex:1, minWidth:0}}>
            <div className="msg-head">
              <span className="msg-name">{name}</span>
              <span className="msg-badge">{badge}</span>
              <span className="msg-time">{at}</span>
            </div>
            <div style={{fontWeight:600, marginBottom: 8, letterSpacing:'-0.01em'}}>{title}</div>
            <div style={{color:'var(--fg-muted)', fontSize:13.5, lineHeight:1.6}}>{body}</div>
            {extra}
          </div>
        </div>
      </div>
    </div>
  );
}

function Sparkline({hotAt = 6}) {
  const heights = [5, 7, 6, 8, 7, 6, 7, 22, 26, 24, 20, 18];
  return (
    <div className="sparkline">
      {heights.map((h, i) => (
        <i key={i} className={i >= hotAt ? 'hot' : ''} style={{height: h}} />
      ))}
    </div>
  );
}

// ---------- Comparison ----------
function Comparison() {
  const rows = [
    ['Pricing model', 'flat, $39/env', 'per host + usage', 'free (ops time)', 'per monitor'],
    ['Setup time', 'one command', 'agent + config', 'build it yourself', 'a few forms'],
    ['Alert writing', 'LLM-composed', 'you write them', 'you write them', 'you write them'],
    ['Self-host option', 'yes, same software', 'no', 'yes, it is', 'no'],
    ['Good for 50+ services', 'no, use Datadog', 'yes', 'yes, with work', 'no'],
    ['Full APM / traces', 'no', 'yes', 'yes, configurable', 'no'],
  ];
  return (
    <section id="compare">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">04</span><span>why not X</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth: '22ch', marginBottom: 10}}>
          Honest about what this is and isn't.
        </h2>
        <p className="lede" style={{marginBottom: 40}}>
          Tellr is not trying to replace Datadog. It's for the team that doesn't have monitoring yet, or has a Grafana nobody looks at.
        </p>

        <div className="compare">
          <div className="compare-row head">
            <div>&nbsp;</div>
            <div className="us">tellr</div>
            <div>Datadog</div>
            <div>Self-host Grafana</div>
          </div>
          {rows.map((r, i) => (
            <div className="compare-row" key={i}>
              <div className="label">{r[0]}</div>
              <div style={{color:'var(--accent)'}}>{r[1]}</div>
              <div style={{color:'var(--fg-muted)'}}>{r[2]}</div>
              <div style={{color:'var(--fg-muted)'}}>{r[3]}</div>
            </div>
          ))}
        </div>

        <div className="honest">
          <div className="yes">
            <h4>Tellr is for you if</h4>
            <ul>
              <li>You run 3–20 services on one or two VPSes</li>
              <li>You have no monitoring, or one nobody checks</li>
              <li>You want to be told what broke, not shown a graph</li>
              <li>You'd rather pay one flat fee than read a pricing PDF</li>
            </ul>
          </div>
          <div className="no">
            <h4>Tellr is not for you if</h4>
            <ul>
              <li>You run 50+ microservices with real on-call rotations</li>
              <li>You need distributed tracing or full APM</li>
              <li>You need SOC 2-audited hosted monitoring today</li>
              <li>You want a dashboard you can stare at all day</li>
            </ul>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- Pricing ----------
function Pricing() {
  return (
    <section id="pricing">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">05</span><span>pricing</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth:'22ch', marginBottom: 10}}>One price. No seat math.</h2>
        <p className="lede" style={{marginBottom: 40}}>
          Unlimited services, unlimited checks, unlimited people looking at alerts. Priced per environment because that's the only thing worth counting.
        </p>

        <div className="pricing">
          <div className="price feat">
            <div className="price-name">hosted</div>
            <div className="price-amt">$39<span className="per">/ month per environment</span></div>
            <ul>
              <li>Unlimited services & checks</li>
              <li>Unlimited teammates</li>
              <li>Slack, Telegram, Discord, email</li>
              <li>LLM alerts (you bring the key)</li>
              <li>30-day alert history</li>
              <li>Email support, one business day</li>
            </ul>
            <a className="btn btn-primary" href="#install" style={{marginTop: 4, alignSelf:'flex-start'}}>
              <span className="mono">$</span> Install the hosted client
            </a>
          </div>
          <div className="price">
            <div className="price-name">self-hosted</div>
            <div className="price-amt">$0<span className="per">/ forever</span></div>
            <ul>
              <li>The same software. Literally.</li>
              <li>Runs as a single binary or Docker</li>
              <li>Source on GitHub, Apache 2.0</li>
              <li>Bring your own LLM key</li>
              <li>Community support on GitHub</li>
              <li>No phone-home, no telemetry</li>
            </ul>
            <a className="btn btn-ghost" href="#oss" style={{marginTop: 4, alignSelf:'flex-start'}}>Read the docs →</a>
          </div>
        </div>

        <div style={{marginTop: 24, color:'var(--fg-dim)', fontSize: 13, fontFamily:'var(--font-mono)'}}>
          // environment = one logical stack. most teams have two: production and staging.
        </div>
      </div>
    </section>
  );
}

// ---------- Ecosystem ----------
function Ecosystem() {
  return (
    <section id="ecosystem">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">06</span><span>ecosystem</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth: '22ch', marginBottom: 10}}>
          Plays well with the stack you already grep.
        </h2>
        <p style={{color:'var(--fg-muted)', fontSize: 16, maxWidth: '60ch', marginBottom: 36, lineHeight: 1.6}}>
          No lock-in, no proprietary agent, no "Tellr Cloud". If the tool already exists and is
          standard, we speak to it. If it doesn't, we shell out.
        </p>

        <div className="eco-hero">
          <div className="eco-hero-copy">
            <span className="eco-badge">mcp server · native</span>
            <h3>Your infra, as a conversation.</h3>
            <p>
              Tellr ships an <b style={{color:'var(--fg)'}}>MCP server</b> that exposes every check, alert, and log snippet as tools.
              Point Claude, Cursor, Zed, or any MCP-speaking client at <code className="mono" style={{color:'var(--accent)'}}>tellr mcp</code> and
              ask, in plain English, what's on fire. It answers with the receipts.
            </p>
            <p style={{fontSize: 13.5}}>
              <span style={{color:'var(--fg-dim)', fontFamily:'var(--font-mono)', fontSize: 12}}>tools exposed:</span>{' '}
              <code className="mono" style={{background:'oklch(0.96 0.004 250 / 0.06)', padding:'1px 6px', borderRadius:4, fontSize:12}}>list_checks</code>{' '}
              <code className="mono" style={{background:'oklch(0.96 0.004 250 / 0.06)', padding:'1px 6px', borderRadius:4, fontSize:12}}>get_alert</code>{' '}
              <code className="mono" style={{background:'oklch(0.96 0.004 250 / 0.06)', padding:'1px 6px', borderRadius:4, fontSize:12}}>silence</code>{' '}
              <code className="mono" style={{background:'oklch(0.96 0.004 250 / 0.06)', padding:'1px 6px', borderRadius:4, fontSize:12}}>run_check</code>{' '}
              <code className="mono" style={{background:'oklch(0.96 0.004 250 / 0.06)', padding:'1px 6px', borderRadius:4, fontSize:12}}>tail_logs</code>
            </p>
            <ul className="eco-clients">
              <li><b>Claude Desktop</b></li>
              <li><b>Cursor</b></li>
              <li><b>Zed</b></li>
              <li><b>Goose</b></li>
              <li>any mcp 0.3+ client</li>
            </ul>
          </div>
          <div className="eco-demo">
            <div className="eco-msg">
              <span className="eco-who">you</span>
              <div className="eco-body">is anything weird about api.prod in the last hour?</div>
            </div>
            <div className="eco-msg">
              <span className="eco-who amber">claude</span>
              <div className="eco-body">
                <span className="dim">calling</span> <span className="amber">tellr.list_checks(target=api.prod, since=1h)</span>
                <div className="eco-tool">
                  3 checks. 1 <span style={{color:'var(--accent)'}}>warn</span>: <b style={{color:'var(--fg)'}}>p95</b> 180ms → 410ms at 14:22. 2 <span style={{color:'var(--green)',filter:'hue-rotate(0deg)'}}>ok</span>.
                </div>
                <span className="dim" style={{marginTop: 8, display:'block'}}>calling</span> <span className="amber">tellr.tail_logs(target=api.prod, grep="slow")</span>
                <div className="eco-tool">
                  14 matches. All on <b style={{color:'var(--fg)'}}>POST /v1/embed</b>. Upstream: <b style={{color:'var(--fg)'}}>openai</b>.
                </div>
              </div>
            </div>
            <div className="eco-msg">
              <span className="eco-who amber">claude</span>
              <div className="eco-body">
                Yes: OpenAI embeddings latency has doubled since 14:22. Not your code.
                Want me to <span className="amber">silence</span> the p95 check for 30m?
              </div>
            </div>
          </div>
        </div>

        <div className="eco-grid">
          <div className="eco-tile">
            <div className="eco-tile-head"><span>opentelemetry</span><span className="pill">otlp</span></div>
            <h4>OTLP ingest, as a check type.</h4>
            <p>Point OTel collectors at Tellr. Any metric becomes a check with a threshold, a floor, or an LLM judge.</p>
            <code><span className="c"># tellr.yaml</span>{'\n'}<span className="k">type</span>: otlp{'\n'}<span className="k">metric</span>: <span className="s">http.server.duration</span>{'\n'}<span className="k">p95_over</span>: <span className="s">500ms</span></code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>terraform</span><span className="pill">provider</span></div>
            <h4>Checks in version control, not dashboards.</h4>
            <p>The <code className="mono" style={{background:'none',border:0,padding:0,display:'inline'}}>tellr/tellr</code> provider maps every check, route, and silence to HCL.</p>
            <code><span className="k">resource</span> <span className="s">"tellr_check"</span> <span className="s">"db"</span> {'{'}{'\n'}  name = <span className="s">"db.prod"</span>{'\n'}  type = <span className="s">"postgres"</span>{'\n'}{'}'}</code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>github actions</span><span className="pill">hook</span></div>
            <h4>Silenced during your own rollouts.</h4>
            <p>The <code className="mono" style={{background:'none',border:0,padding:0,display:'inline'}}>tellr-silence</code> action pauses affected checks for the duration of your deploy. No more "it was just the deploy" alerts.</p>
            <code><span className="k">- uses</span>: tellr/silence@v1{'\n'}  <span className="k">with</span>:{'\n'}    target: <span className="s">api.prod</span>{'\n'}    for: <span className="s">5m</span></code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>prometheus</span><span className="pill">remote_write</span></div>
            <h4>Scrape or be scraped.</h4>
            <p>Ingest existing Prom endpoints as checks, or push Tellr's own metrics to your Mimir/Thanos over remote_write.</p>
            <code><span className="k">exporters</span>:{'\n'}  - <span className="k">url</span>: <span className="s">https://prom/api/v1/write</span></code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>grafana</span><span className="pill">dashboard</span></div>
            <h4>A dashboard you'll never need.</h4>
            <p>Pre-built Grafana JSON for latency, success rate, and silence history, in case anyone asks to see a graph.</p>
            <code><span className="c">$</span> grafana-cli plugins install tellr-ds</code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>nix / brew / oci</span><span className="pill">install</span></div>
            <h4>Install like a normal human.</h4>
            <p>Flake, Homebrew tap, Docker image, .deb, .rpm, scoop, static binary. No curl-pipe-sudo required.</p>
            <code><span className="c">#</span> pick one{'\n'}brew install tellr/tap/tellr{'\n'}nix run github:tellr/tellr{'\n'}docker run ghcr.io/tellr/tellr</code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>cosign · slsa</span><span className="pill">provenance</span></div>
            <h4>Signed, sbom'd, reproducible.</h4>
            <p>Every release is cosign-signed with a SLSA-3 provenance attestation. Verify before you run.</p>
            <code><span className="c">$</span> cosign verify --certificate-identity-regexp \{'\n'}   <span className="s">"tellr/tellr@refs/tags/.*"</span> tellr:0.9.2</code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>openapi · sdk</span><span className="pill">generated</span></div>
            <h4>Every endpoint, every language.</h4>
            <p>Hand-written OpenAPI 3.1 spec. Generated SDKs for Go, TS, Python, Rust, Ruby. PRs welcome for your weird one.</p>
            <code><span className="c">$</span> tellr openapi &gt; spec.yaml{'\n'}<span className="c">$</span> openapi-gen -i spec.yaml -l rust</code>
          </div>
          <div className="eco-tile">
            <div className="eco-tile-head"><span>pagerduty · opsgenie</span><span className="pill">bridge</span></div>
            <h4>For the on-call rotation you inherited.</h4>
            <p>Route <code className="mono" style={{background:'none',border:0,padding:0,display:'inline'}}>severity=error</code> to PagerDuty or Opsgenie. Keep everything else in Slack where it belongs.</p>
            <code><span className="k">routes</span>:{'\n'}  - <span className="k">match</span>: severity=error{'\n'}    <span className="k">to</span>: <span className="s">pagerduty://prod</span></code>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- OSS + BYOK ----------
function OSS() {
  return (
    <section id="oss">
      <div className="wrap">
        <div className="eyebrow">
          <span className="num">07</span><span>open source &amp; byok</span><span className="rule" />
        </div>
        <h2 className="h2" style={{maxWidth: '22ch', marginBottom: 40}}>
          The code is yours. The LLM bill is yours.
        </h2>
        <div className="oss">
          <div className="oss-card">
            <h3>Open source, Apache 2.0</h3>
            <p>
              The whole thing is on GitHub: agent, checks, alert composer, web UI. Read it, fork it, audit it before you install it.
              If you don't trust a binary that pings your servers every three minutes, you shouldn't. We wouldn't either.
            </p>
            <a className="oss-link" href="https://github.com/tellr/tellr">
              github.com/tellr/tellr →
            </a>
          </div>
          <div className="oss-card">
            <h3>Bring your own LLM key</h3>
            <p>
              Alert summaries are written by a model of your pick: OpenAI, Anthropic, or a local Ollama. You plug in the key.
              The cost is transparent (fractions of a cent per alert) and it never goes through us. If you'd rather turn it off, set
              <span className="code-inline">&nbsp;llm: off&nbsp;</span>and get plain alerts.
            </p>
            <a className="oss-link" href="docs.html#llm">
              docs / bring-your-own-key →
            </a>
          </div>
        </div>
      </div>
    </section>
  );
}

// ---------- Footer ----------
function Footer() {
  return (
    <footer>
      <div className="wrap">
        <h2 className="foot-wordmark" aria-label="tellr">
          <span className="dot" aria-hidden />
          <span>tellr</span>
          <span className="caret">_</span>
        </h2>
        <div className="foot-cols">
          <div className="foot-pitch">
            <p>A monitor that shuts up when everything is fine, and writes you a paragraph when it isn't.</p>
            <a href="status.html" className="status" title="live status">all systems nominal · updated just now →</a>
            <div className="sig" style={{marginTop: 22}}>
              <span><b>built by one person in cairo</b></span>
              <span>who got tired of pretending to check dashboards</span>
            </div>
          </div>
          <div>
            <h5>Product</h5>
            <a href="#how">How it works</a>
            <a href="#checks">Checks</a>
            <a href="#alerts">Alerts</a>
            <a href="#ecosystem">Ecosystem</a>
            <a href="#pricing">Pricing</a>
          </div>
          <div>
            <h5>Developers</h5>
            <a href="docs.html">Docs</a>
            <a href="https://github.com/tellr/tellr">GitHub</a>
            <a href="changelog.html">Changelog</a>
            <a href="status.html">Status</a>
          </div>
          <div>
            <h5>Contact</h5>
            <a href="mailto:hey@tellr.dev">hey@tellr.dev</a>
            <a href="mailto:security@tellr.dev">security@tellr.dev</a>
            <a href="https://github.com/tellr/tellr/issues">File an issue</a>
          </div>
        </div>
        <div className="foot-meta">
          <span>© 2026 tellr · apache-2.0</span>
          <span>build a3f21c · <a href="changelog.html" style={{color:'var(--accent)'}}>changelog</a></span>
        </div>
      </div>
    </footer>
  );
}

Object.assign(window, { Nav, Hero, HowItWorks, Checks, Alerts, Comparison, Pricing, Ecosystem, OSS, Footer });
