Skip to content

Commit c65bdc8

Browse files
committed
Add ProofCard Web page and stabilize proof hash
1 parent 0db9c16 commit c65bdc8

3 files changed

Lines changed: 395 additions & 1 deletion

File tree

docs/proof/README.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Proof Web Page
2+
3+
Static web proof page for the canonical FIRE scenario.
4+
5+
- URL target (GitHub Pages): `https://egonex-code.github.io/ecp-protocol/proof`
6+
- Source file: `docs/proof/index.html`
7+
- Canonical values: `CAP 669`, `JSON 270`, `ECP UET 8`
8+
- Canonical proof hash (must match CLI): `8a5340fce836`
9+
10+
## Validation checklist
11+
12+
1. Run CLI:
13+
- `cd samples/ProofCard`
14+
- `dotnet run -- --show-payload`
15+
2. Confirm:
16+
- `CAP XML payload (669 bytes)`
17+
- `JSON payload (270 bytes)`
18+
- `ECP UET payload (8 bytes)`
19+
- `Proof hash: 8a5340fce836`
20+
3. Open `docs/proof/index.html` and verify same values/hashes shown.
21+
22+
## Deployment note
23+
24+
For the expected URL path (`/proof`) with GitHub Pages, the repository Pages source should be set to:
25+
26+
- Branch: `main`
27+
- Folder: `/docs`

docs/proof/index.html

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1">
6+
<title>ECP Proof - 669 vs 8</title>
7+
<meta name="description" content="Canonical ECP comparison: CAP XML 669 bytes, JSON 270 bytes, ECP UET 8 bytes.">
8+
<style>
9+
:root {
10+
--bg: #0a1020;
11+
--bg-soft: #141d35;
12+
--text: #e9eeff;
13+
--muted: #a8b2d5;
14+
--cap: #ff6b6b;
15+
--json: #ffd166;
16+
--ecp: #31e08b;
17+
--accent: #67a2ff;
18+
--border: rgba(255, 255, 255, 0.14);
19+
}
20+
21+
* {
22+
box-sizing: border-box;
23+
}
24+
25+
body {
26+
margin: 0;
27+
min-height: 100vh;
28+
font-family: ui-sans-serif, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif;
29+
color: var(--text);
30+
background:
31+
radial-gradient(circle at 12% 18%, rgba(103, 162, 255, 0.22), transparent 40%),
32+
radial-gradient(circle at 85% 10%, rgba(49, 224, 139, 0.12), transparent 35%),
33+
linear-gradient(160deg, #050a18 0%, #0a1020 40%, #121f3f 100%);
34+
display: grid;
35+
place-items: center;
36+
padding: 24px;
37+
}
38+
39+
.card {
40+
width: min(980px, 100%);
41+
background: linear-gradient(160deg, rgba(20, 29, 53, 0.95), rgba(8, 13, 28, 0.96));
42+
border: 1px solid var(--border);
43+
border-radius: 20px;
44+
box-shadow: 0 24px 70px rgba(0, 0, 0, 0.45);
45+
overflow: hidden;
46+
}
47+
48+
.inner {
49+
padding: 28px;
50+
}
51+
52+
.eyebrow {
53+
color: var(--accent);
54+
letter-spacing: 0.08em;
55+
text-transform: uppercase;
56+
font-size: 0.78rem;
57+
margin-bottom: 12px;
58+
font-weight: 700;
59+
}
60+
61+
h1 {
62+
margin: 0 0 8px;
63+
line-height: 1.16;
64+
font-size: clamp(1.55rem, 3vw, 2.3rem);
65+
letter-spacing: -0.02em;
66+
}
67+
68+
.sub {
69+
margin: 0 0 22px;
70+
color: var(--muted);
71+
font-size: 1rem;
72+
}
73+
74+
.metrics {
75+
display: grid;
76+
gap: 14px;
77+
margin-bottom: 18px;
78+
}
79+
80+
.metric {
81+
border: 1px solid var(--border);
82+
border-radius: 12px;
83+
padding: 12px;
84+
background: rgba(255, 255, 255, 0.02);
85+
}
86+
87+
.metric-head {
88+
display: flex;
89+
align-items: baseline;
90+
justify-content: space-between;
91+
gap: 12px;
92+
margin-bottom: 8px;
93+
}
94+
95+
.label {
96+
font-size: 0.94rem;
97+
color: #d5defc;
98+
font-weight: 600;
99+
}
100+
101+
.value {
102+
font-size: 1.36rem;
103+
font-weight: 800;
104+
letter-spacing: -0.02em;
105+
font-variant-numeric: tabular-nums;
106+
}
107+
108+
.bar-wrap {
109+
height: 12px;
110+
border-radius: 999px;
111+
background: rgba(255, 255, 255, 0.08);
112+
overflow: hidden;
113+
}
114+
115+
.bar {
116+
width: 0;
117+
height: 100%;
118+
border-radius: 999px;
119+
transition: width 220ms linear;
120+
}
121+
122+
.bar.cap { background: linear-gradient(90deg, #ff7a7a, #ff4f7b); }
123+
.bar.json { background: linear-gradient(90deg, #ffd166, #f9b234); }
124+
.bar.ecp { background: linear-gradient(90deg, #39e49e, #20be79); }
125+
126+
.impact {
127+
margin: 18px 0 22px;
128+
padding: 16px;
129+
border: 1px solid var(--border);
130+
border-radius: 12px;
131+
background: rgba(49, 224, 139, 0.07);
132+
}
133+
134+
.impact strong {
135+
font-size: 1.22rem;
136+
}
137+
138+
.meta {
139+
display: grid;
140+
gap: 8px;
141+
margin-bottom: 18px;
142+
font-size: 0.94rem;
143+
color: var(--muted);
144+
}
145+
146+
.meta code {
147+
color: #f0f5ff;
148+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
149+
font-size: 0.92rem;
150+
}
151+
152+
.actions {
153+
display: flex;
154+
flex-wrap: wrap;
155+
gap: 12px;
156+
margin-bottom: 16px;
157+
}
158+
159+
button,
160+
.link-btn {
161+
border: 0;
162+
border-radius: 10px;
163+
padding: 12px 16px;
164+
font-weight: 700;
165+
font-size: 0.96rem;
166+
cursor: pointer;
167+
text-decoration: none;
168+
display: inline-flex;
169+
align-items: center;
170+
justify-content: center;
171+
}
172+
173+
button {
174+
background: #4f7fff;
175+
color: #fff;
176+
}
177+
178+
button:hover {
179+
filter: brightness(1.05);
180+
}
181+
182+
.link-btn {
183+
background: rgba(255, 255, 255, 0.08);
184+
border: 1px solid var(--border);
185+
color: var(--text);
186+
}
187+
188+
.copy-status {
189+
min-height: 1.2em;
190+
color: var(--muted);
191+
font-size: 0.9rem;
192+
}
193+
194+
.note {
195+
margin-top: 8px;
196+
color: var(--muted);
197+
font-size: 0.88rem;
198+
line-height: 1.45;
199+
border-top: 1px dashed var(--border);
200+
padding-top: 12px;
201+
}
202+
203+
.cli {
204+
margin-top: 12px;
205+
border: 1px solid var(--border);
206+
border-radius: 10px;
207+
padding: 12px;
208+
background: rgba(255, 255, 255, 0.02);
209+
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
210+
color: #c8d4fb;
211+
font-size: 0.88rem;
212+
white-space: pre-wrap;
213+
}
214+
215+
@media (max-width: 640px) {
216+
.inner {
217+
padding: 20px;
218+
}
219+
}
220+
</style>
221+
</head>
222+
<body>
223+
<main class="card">
224+
<div class="inner">
225+
<div class="eyebrow">ECP Proof Card - Canonical FIRE Scenario</div>
226+
<h1>Why does it take 669 bytes to tell a computer there is a fire?</h1>
227+
<p class="sub">ECP does the same core machine alert in 8 bytes.</p>
228+
229+
<section class="metrics" aria-label="Byte comparison">
230+
<article class="metric" data-key="cap" data-value="669">
231+
<div class="metric-head">
232+
<span class="label">CAP XML</span>
233+
<span class="value" id="cap-value">0 bytes</span>
234+
</div>
235+
<div class="bar-wrap"><div class="bar cap" id="cap-bar"></div></div>
236+
</article>
237+
238+
<article class="metric" data-key="json" data-value="270">
239+
<div class="metric-head">
240+
<span class="label">JSON</span>
241+
<span class="value" id="json-value">0 bytes</span>
242+
</div>
243+
<div class="bar-wrap"><div class="bar json" id="json-bar"></div></div>
244+
</article>
245+
246+
<article class="metric" data-key="ecp" data-value="8">
247+
<div class="metric-head">
248+
<span class="label">ECP UET</span>
249+
<span class="value" id="ecp-value">0 bytes</span>
250+
</div>
251+
<div class="bar-wrap"><div class="bar ecp" id="ecp-bar"></div></div>
252+
</article>
253+
</section>
254+
255+
<section class="impact" aria-live="polite">
256+
<strong id="impact-text">98.8% less data</strong>
257+
<div>Same canonical FIRE vector. Visualized in seconds.</div>
258+
</section>
259+
260+
<section class="meta">
261+
<div>Run ID: <code id="run-id">#pending</code></div>
262+
<div>Proof hash (CLI canonical): <code id="proof-hash">8a5340fce836</code></div>
263+
</section>
264+
265+
<section class="actions">
266+
<button id="copy-btn" type="button">Copy result</button>
267+
<a class="link-btn" href="https://github.com/Egonex-Code/ecp-protocol/tree/main/samples/ProofCard" target="_blank" rel="noopener noreferrer">Run it yourself (CLI)</a>
268+
</section>
269+
<div class="copy-status" id="copy-status"></div>
270+
271+
<p class="note">
272+
These values are live-measured in the CLI ProofCard tool. This page shows the canonical FIRE scenario for instant sharing.
273+
Clone the repository to verify independently, including the raw payloads via <code>-- --show-payload</code>.
274+
</p>
275+
276+
<div class="cli">git clone https://github.com/Egonex-Code/ecp-protocol
277+
cd ecp-protocol/samples/ProofCard
278+
dotnet run</div>
279+
</div>
280+
</main>
281+
282+
<script>
283+
(function () {
284+
const CANONICAL = {
285+
cap: 669,
286+
json: 270,
287+
ecp: 8,
288+
proofHash: "8a5340fce836"
289+
};
290+
291+
const max = CANONICAL.cap;
292+
const reduction = ((1 - (CANONICAL.ecp / CANONICAL.cap)) * 100).toFixed(1);
293+
const runId = buildTimestampRunId();
294+
295+
document.getElementById("run-id").textContent = runId;
296+
document.getElementById("proof-hash").textContent = CANONICAL.proofHash;
297+
document.getElementById("impact-text").textContent = reduction + "% less data";
298+
299+
animateMetric("cap", CANONICAL.cap, max, 0);
300+
animateMetric("json", CANONICAL.json, max, 280);
301+
animateMetric("ecp", CANONICAL.ecp, max, 560);
302+
303+
const copyBtn = document.getElementById("copy-btn");
304+
const copyStatus = document.getElementById("copy-status");
305+
306+
copyBtn.addEventListener("click", async function () {
307+
const shareText = [
308+
"I ran ECP Proof " + runId + ":",
309+
"CAP XML " + CANONICAL.cap + "B | JSON " + CANONICAL.json + "B | ECP " + CANONICAL.ecp + "B.",
310+
reduction + "% less data vs CAP. Proof " + CANONICAL.proofHash + ".",
311+
"https://egonex-code.github.io/ecp-protocol/proof",
312+
"#ECP #OpenSource #DotNet"
313+
].join("\n");
314+
315+
try {
316+
await navigator.clipboard.writeText(shareText);
317+
copyStatus.textContent = "Copied. Paste it where you want to share.";
318+
} catch (error) {
319+
copyStatus.textContent = "Copy failed. You can still select and copy manually.";
320+
}
321+
});
322+
323+
function animateMetric(key, targetValue, maxValue, delayMs) {
324+
const valueEl = document.getElementById(key + "-value");
325+
const barEl = document.getElementById(key + "-bar");
326+
const durationMs = 1300;
327+
const startAt = performance.now() + delayMs;
328+
329+
requestAnimationFrame(function step(now) {
330+
if (now < startAt) {
331+
requestAnimationFrame(step);
332+
return;
333+
}
334+
335+
const progress = Math.min((now - startAt) / durationMs, 1);
336+
const eased = 1 - Math.pow(1 - progress, 3);
337+
const current = Math.round(targetValue * eased);
338+
const widthPct = (current / maxValue) * 100;
339+
340+
valueEl.textContent = current + " bytes";
341+
barEl.style.width = widthPct + "%";
342+
343+
if (progress < 1) {
344+
requestAnimationFrame(step);
345+
} else {
346+
valueEl.textContent = targetValue + " bytes";
347+
barEl.style.width = ((targetValue / maxValue) * 100) + "%";
348+
}
349+
});
350+
}
351+
352+
function buildTimestampRunId() {
353+
const now = new Date();
354+
const yyyy = String(now.getUTCFullYear());
355+
const mm = String(now.getUTCMonth() + 1).padStart(2, "0");
356+
const dd = String(now.getUTCDate()).padStart(2, "0");
357+
const hh = String(now.getUTCHours()).padStart(2, "0");
358+
const mi = String(now.getUTCMinutes()).padStart(2, "0");
359+
const ss = String(now.getUTCSeconds()).padStart(2, "0");
360+
return "#web-" + yyyy + mm + dd + "-" + hh + mi + ss;
361+
}
362+
})();
363+
</script>
364+
</body>
365+
</html>

0 commit comments

Comments
 (0)