Files
echolot/web/public/index.html
T
mrambossekandClaude Opus 5 6ddf013dfe License the project; add root README, website, and parked release CI
- Code: GPL-3.0-or-later (LICENSE, SPDX headers on all .kt/.aidl).
  Specs in docs/: CC-BY-4.0 (docs/LICENSE). Rationale in build-status.md;
  server decided GPL (not AGPL).
- Root README for the public repo.
- web/: Cloudflare Worker site for echo-lot.app. /apk resolves the newest
  APK from the Gitea latest-release API at request time (edge-cached 5 min),
  so tagging a release is the only publish step. /fdroid, /source, and a
  manual DOWNLOAD_URL fallback are wrangler vars.
- .gitea/workflows/release.yml: tag-driven (v*) signed semver APK builds for
  the future production app in echolot-app/. Parked; the prober is
  deliberately not CI-built.
- Fix UserService.kt: drop the explicit secondary constructor that
  conflicted with the implicit primary (never compiled before — first
  local build caught it). Prober now builds: :app:assembleDebug OK.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-07-30 09:15:35 +02:00

258 lines
12 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<!-- SPDX-FileCopyrightText: 2026 Echolot contributors
SPDX-License-Identifier: GPL-3.0-or-later -->
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Echolot — depth soundings for your local network</title>
<meta name="description" content="Free Android app for detecting and debugging local network issues: rogue DHCP, broken IPv6 RAs, MTU black holes, multicast loss, lying DNS. No root required.">
<meta property="og:title" content="Echolot">
<meta property="og:description" content="Depth soundings for your local network. F/OSS Android network diagnostics — no root required.">
<meta property="og:url" content="https://echo-lot.app/">
<link rel="icon" href="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'%3E%3Crect width='32' height='32' fill='%23071A29'/%3E%3Cg fill='none' stroke='%23FFB454' stroke-width='2'%3E%3Ccircle cx='16' cy='16' r='3' fill='%23FFB454' stroke='none'/%3E%3Cpath d='M16 6a10 10 0 0 1 10 10'/%3E%3Cpath d='M16 1a15 15 0 0 1 15 15' opacity='.5'/%3E%3C/g%3E%3C/svg%3E">
<style>
:root {
--depth-0: #0B2437; /* surface */
--depth-1: #092031; /* photic */
--depth-2: #071A29; /* mid */
--depth-3: #051320; /* floor */
--foam: #DCE9F1; /* primary text */
--slate: #8AA5B8; /* secondary text */
--grid: #16374E; /* hairlines, chart grid */
--ping: #FFB454; /* the one accent: sonar amber */
--ok: #7BC98F; /* verdict green, chips only */
--mono: "Cascadia Code", "SF Mono", Consolas, "Liberation Mono", Menlo, monospace;
--sans: "Segoe UI", system-ui, -apple-system, "Helvetica Neue", Arial, sans-serif;
}
* { box-sizing: border-box; margin: 0; }
html { scroll-behavior: smooth; }
body {
font-family: var(--sans);
color: var(--foam);
background: linear-gradient(var(--depth-0), var(--depth-1) 30%, var(--depth-2) 65%, var(--depth-3));
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
a { color: var(--ping); text-decoration-thickness: 1px; text-underline-offset: 3px; }
a:hover { text-decoration-thickness: 2px; }
:focus-visible { outline: 2px solid var(--ping); outline-offset: 3px; border-radius: 2px; }
.col { max-width: 46rem; margin: 0 auto; padding: 0 1.25rem; }
/* Depth ruler: fixed left margin scale, desktop only. Marks are set per-section
by scroll position purely decoratively — it is a ruler, not navigation. */
.ruler {
position: fixed; top: 0; bottom: 0; left: 0; width: 3.5rem;
border-right: 1px solid var(--grid);
font-family: var(--mono); font-size: .65rem; color: var(--slate);
display: none;
}
@media (min-width: 72rem) { .ruler { display: block; } }
.ruler span {
position: absolute; right: .5rem; transform: translateY(-50%);
}
.ruler span::after {
content: ""; position: absolute; right: -.55rem; top: 50%;
width: .35rem; height: 1px; background: var(--slate);
}
header.hero { padding: 4.5rem 0 3rem; }
.wordmark {
font-family: var(--mono); font-size: .8rem; letter-spacing: .35em;
text-transform: uppercase; color: var(--slate);
}
.wordmark b { color: var(--ping); font-weight: 600; }
h1 {
font-size: clamp(1.9rem, 5vw, 3rem);
font-weight: 650; letter-spacing: -.02em; line-height: 1.15;
margin: 1rem 0 .75rem; max-width: 30ch;
}
.hero p.lede { color: var(--slate); max-width: 52ch; font-size: 1.05rem; }
.hero p.lede strong { color: var(--foam); font-weight: 600; }
/* Echogram: the signature. A chart-recorder trace of ping RTTs; the sweep
line is the sounder, the profile is the "seabed" the echoes draw. */
figure.echogram {
margin: 2.5rem 0 0; border: 1px solid var(--grid); border-radius: 4px;
background:
repeating-linear-gradient(to right, transparent 0 39px, var(--grid) 39px 40px),
repeating-linear-gradient(to bottom, transparent 0 31px, var(--grid) 31px 32px),
var(--depth-3);
position: relative; overflow: hidden;
}
.echogram svg { display: block; width: 100%; height: auto; }
.echogram figcaption {
position: absolute; top: .5rem; left: .75rem;
font-family: var(--mono); font-size: .65rem; color: var(--slate);
}
.sweep {
position: absolute; top: 0; bottom: 0; width: 1px;
background: var(--ping); opacity: .8;
box-shadow: 0 0 8px var(--ping);
animation: sweep 7s linear infinite;
}
@keyframes sweep { from { left: 0; } to { left: 100%; } }
@media (prefers-reduced-motion: reduce) {
.sweep { animation: none; left: 62%; }
html { scroll-behavior: auto; }
}
section { padding: 3.5rem 0 0; }
.eyebrow {
font-family: var(--mono); font-size: .7rem; letter-spacing: .25em;
text-transform: uppercase; color: var(--ping);
}
h2 { font-size: 1.35rem; font-weight: 650; margin: .5rem 0 1rem; letter-spacing: -.01em; }
section > .col > p { color: var(--slate); max-width: 58ch; }
section p + p { margin-top: .8rem; }
section p strong { color: var(--foam); font-weight: 600; }
/* Sounding table: what it actually measures — real content, mono, no icons */
table.soundings {
width: 100%; border-collapse: collapse; margin-top: 1.5rem;
font-size: .9rem;
}
.soundings td {
padding: .55rem .75rem .55rem 0; border-top: 1px solid var(--grid);
vertical-align: top;
}
.soundings td:first-child {
font-family: var(--mono); font-size: .8rem; white-space: nowrap;
color: var(--foam); padding-right: 1.25rem;
}
.soundings td:last-child { color: var(--slate); }
.tiers { display: flex; gap: .5rem; flex-wrap: wrap; margin-top: 1.25rem; }
.tier {
font-family: var(--mono); font-size: .75rem;
border: 1px solid var(--grid); border-radius: 3px; padding: .35rem .6rem;
color: var(--slate);
}
.tier b { color: var(--ok); font-weight: 600; }
/* Install */
.buttons { display: flex; gap: .75rem; flex-wrap: wrap; margin: 1.5rem 0 1rem; }
.btn {
display: inline-block; padding: .7rem 1.3rem; border-radius: 4px;
font-weight: 600; text-decoration: none; font-size: .95rem;
}
.btn.primary { background: var(--ping); color: var(--depth-3); }
.btn.primary:hover { filter: brightness(1.08); }
.btn.ghost { border: 1px solid var(--grid); color: var(--foam); }
.btn.ghost:hover { border-color: var(--slate); }
.note {
font-size: .85rem; color: var(--slate);
border-left: 2px solid var(--ping); padding-left: .9rem; max-width: 52ch;
}
.checksum { font-family: var(--mono); font-size: .75rem; color: var(--slate); margin-top: 1rem; }
footer {
margin-top: 4.5rem; padding: 2rem 0 3rem;
border-top: 1px solid var(--grid);
font-size: .85rem; color: var(--slate);
}
footer .col { display: flex; gap: 1.5rem; flex-wrap: wrap; justify-content: space-between; }
</style>
</head>
<body>
<div class="ruler" aria-hidden="true">
<span style="top:6%">0 m</span>
<span style="top:28%">─ 20</span>
<span style="top:50%">─ 40</span>
<span style="top:72%">─ 60</span>
<span style="top:94%">─ 80</span>
</div>
<header class="hero">
<div class="col">
<p class="wordmark"><b></b> echo·lot <span aria-hidden="true">/ˈɛçolo:t/ — echo sounder</span></p>
<h1>Depth soundings for your local network.</h1>
<p class="lede">An echo sounder maps the seabed by timing returns. <strong>Echolot</strong> does the
same to your network: free Android diagnostics for the layer where things actually break —
<strong>no root required</strong>. Built for people who know what a neighbor table is.</p>
<figure class="echogram">
<figcaption>trace · icmp.ping4 · rtt ms ↓ / t →</figcaption>
<svg viewBox="0 0 720 190" role="img" aria-label="Chart-recorder style trace of ping round-trip times, drawn like a sonar seabed profile">
<!-- echo returns: the profile -->
<polyline fill="none" stroke="#FFB454" stroke-width="1.5" opacity=".9"
points="0,138 40,136 80,139 120,135 160,137 200,141 240,138 260,120 280,96 300,88 320,94 340,118 360,134 400,136 440,133 480,158 500,171 520,168 540,150 560,139 600,137 640,140 680,136 720,138"/>
<!-- second, fainter return (multipath) -->
<polyline fill="none" stroke="#FFB454" stroke-width="1" opacity=".25"
points="0,148 40,146 80,149 120,145 160,147 200,151 240,148 260,132 280,110 300,101 320,107 340,129 360,144 400,146 440,143 480,168 500,180 520,177 540,160 560,149 600,147 640,150 680,146 720,148"/>
<!-- dropped probes -->
<g fill="#8AA5B8" font-family="monospace" font-size="9">
<text x="497" y="30">×</text><text x="507" y="30">×</text>
<text x="288" y="30">▲ spike: wifi→cell handover</text>
</g>
</svg>
<div class="sweep" aria-hidden="true"></div>
</figure>
</div>
</header>
<section id="what">
<div class="col">
<p class="eyebrow">What it sounds out</p>
<h2>Signal bars lie. Timings don't.</h2>
<p>Most wifi apps show you signal strength and call it a diagnosis. The failures that ruin
home and office networks live deeper: a second DHCP server nobody admits to, IPv6 router
advertisements gone wrong, an MTU black hole that only eats large packets, multicast dying
quietly at the AP, a resolver answering differently than it should.</p>
<table class="soundings">
<tr><td>dhcp.rogue_detect</td><td>Who is answering DISCOVER — and is it more than one box?</td></tr>
<tr><td>ipv6.ra_audit</td><td>Router advertisements: lifetimes, flags, prefixes that shouldn't be there</td></tr>
<tr><td>mtu.blackhole</td><td>Path MTU probing — find the packet size where the network goes silent</td></tr>
<tr><td>local.mdns_discover</td><td>Does multicast actually survive your AP? What's announcing itself?</td></tr>
<tr><td>dns.canary</td><td>Compare answers against reference records — catch rewriting resolvers</td></tr>
<tr><td>trace.errqueue</td><td>Traceroute without root, straight from the kernel error queue</td></tr>
</table>
<p style="margin-top:1rem">Every run separates <strong>what was observed</strong> from
<strong>what it means</strong>, and exports as JSON — so the evidence survives the argument.</p>
</div>
</section>
<section id="tiers">
<div class="col">
<p class="eyebrow">Privilege tiers</p>
<h2>No root required. More access if you have it.</h2>
<p>Everything above runs as a plain app. Pair once over wireless ADB with
<a href="https://shizuku.rikka.app/">Shizuku</a> and Echolot additionally reads neighbor
tables, RA-installed routes, and DHCP client logs — still no root. Every result records
which tier produced it.</p>
<div class="tiers">
<span class="tier"><b>app</b> — no setup, the bulk of it</span>
<span class="tier"><b>shizuku</b> — wireless ADB pairing, in v1</span>
<span class="tier">root — future module</span>
</div>
</div>
</section>
<section id="install">
<div class="col">
<p class="eyebrow">Install</p>
<h2>Get Echolot</h2>
<div class="buttons">
<a class="btn primary" href="/apk">Download APK</a>
<a class="btn ghost" href="/fdroid">F-Droid</a>
</div>
<p class="note"><strong>Pre-release.</strong> The capability prober is running on real
hardware; the production app is under construction. These links go live with the first
release — until then they loop back here. No mailing list, no tracker: check back, or watch
the <a href="/source">repository</a>.</p>
<p class="checksum">releases will ship with sha256sums + a signing key you can pin</p>
</div>
</section>
<footer>
<div class="col">
<span>GPL-3.0-or-later · protocol &amp; schema docs CC-BY-4.0</span>
<span><a href="/source">source</a> · no analytics, no cookies, no third-party requests</span>
</div>
</footer>
</body>
</html>