Initial commit — GCP Dot self-hosted clone
Some checks failed
Build & publish Docker images / Egg image (push) Has been cancelled
Build & publish Docker images / Server image (push) Has been cancelled

Server (FastAPI + SQLite) runs Stouffer Z network variance analysis.
Egg container uses os.urandom for hardware-entropy 200-bit trials.
Gitea Actions workflow auto-builds and publishes both Docker images.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Hexadual
2026-04-30 01:21:22 -05:00
commit ee2249c5f8
13 changed files with 1033 additions and 0 deletions

51
server/static/gcp.html Normal file
View File

@@ -0,0 +1,51 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html, body { width: 48px; height: 48px; overflow: hidden; background: transparent; }
.dot {
width: 40px;
height: 40px;
border-radius: 50%;
background: #00cc44;
box-shadow: 0 0 14px 4px rgba(0,204,68,.6);
position: absolute;
top: 4px; left: 4px;
transition: background 2.5s ease, box-shadow 2.5s ease;
animation: breathe 4s ease-in-out infinite;
}
@keyframes breathe {
0%,100% { transform: scale(1); }
50% { transform: scale(1.08); }
}
</style>
</head>
<body>
<div class="dot" id="dot" title="Global Consciousness Index"></div>
<script>
function palette(i) {
if (i > 95) return ['#0055ff','rgba(0,85,255,.6)'];
if (i > 90) return ['#00ccff','rgba(0,204,255,.6)'];
if (i > 40) return ['#00cc44','rgba(0,204,68,.6)'];
if (i > 10) return ['#ffdd00','rgba(255,221,0,.6)'];
if (i > 5) return ['#ff8800','rgba(255,136,0,.6)'];
return ['#ff2200','rgba(255,34,0,.6)'];
}
const dot = document.getElementById('dot');
async function update() {
try {
const r = await fetch('/api/status');
const d = await r.json();
const [color, glow] = palette(d.index);
dot.style.background = color;
dot.style.boxShadow = `0 0 14px 4px ${glow}`;
dot.title = `GCP Index: ${Math.round(d.index)}% (${d.n_eggs} eggs)`;
} catch {}
}
update();
setInterval(update, 60000);
</script>
</body>
</html>

434
server/static/index.html Normal file
View File

@@ -0,0 +1,434 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Global Consciousness Project — Live Dot</title>
<style>
:root {
--dot-color: #00cc44;
--glow: rgba(0,204,68,.35);
}
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
body {
background: #080812;
color: #b0b0c8;
font-family: 'Segoe UI', system-ui, sans-serif;
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
padding: 2.5rem 1rem 4rem;
gap: 0;
}
/* ── header ── */
h1 {
font-size: 1rem;
font-weight: 300;
letter-spacing: .25em;
text-transform: uppercase;
color: #505070;
margin-bottom: .4rem;
}
.subtitle {
font-size: .75rem;
letter-spacing: .12em;
color: #303050;
text-transform: uppercase;
}
/* ── dot ── */
.dot-wrap {
position: relative;
width: 220px;
height: 220px;
margin: 2.5rem 0 1.5rem;
flex-shrink: 0;
}
.dot {
width: 160px;
height: 160px;
border-radius: 50%;
background: var(--dot-color);
box-shadow:
0 0 50px 18px var(--glow),
0 0 110px 40px var(--glow);
position: absolute;
inset: 0;
margin: auto;
transition: background 2.5s ease, box-shadow 2.5s ease;
animation: breathe 4s ease-in-out infinite;
}
@keyframes breathe {
0%,100% { transform: scale(1); }
50% { transform: scale(1.04); }
}
/* ── status label ── */
.status-label {
font-size: 1rem;
color: #d0d0e8;
text-align: center;
min-height: 1.6em;
transition: color 2s ease;
}
.status-label strong { color: #fff; }
/* ── stats row ── */
.stats {
display: flex;
gap: 3rem;
margin: 2rem 0 1rem;
}
.stat { text-align: center; }
.stat .val {
display: block;
font-size: 1.8rem;
font-weight: 200;
color: #9090b8;
line-height: 1;
}
.stat .lbl {
display: block;
font-size: .65rem;
letter-spacing: .12em;
text-transform: uppercase;
color: #383858;
margin-top: .3rem;
}
/* ── history chart ── */
#chart {
display: block;
width: min(480px, 96vw);
height: 72px;
margin: .5rem 0 2rem;
}
/* ── legend ── */
.card {
width: min(480px, 96vw);
background: #0e0e1c;
border: 1px solid #18182a;
border-radius: 12px;
padding: 1.4rem 1.6rem;
margin-bottom: 1rem;
}
.card-title {
font-size: .65rem;
letter-spacing: .14em;
text-transform: uppercase;
color: #303050;
margin-bottom: 1rem;
}
.legend-row {
display: flex;
align-items: center;
gap: .75rem;
padding: .35rem 0;
font-size: .82rem;
color: #7070a0;
border-bottom: 1px solid #131325;
}
.legend-row:last-child { border-bottom: none; }
.ldot {
width: 11px;
height: 11px;
border-radius: 50%;
flex-shrink: 0;
}
.legend-range {
margin-left: auto;
font-size: .7rem;
color: #383858;
white-space: nowrap;
}
/* ── embed box ── */
.embed-code {
background: #080812;
border: 1px solid #1a1a2e;
border-radius: 6px;
padding: .7rem 1rem;
font-family: monospace;
font-size: .75rem;
color: #6060a0;
word-break: break-all;
cursor: pointer;
transition: border-color .2s;
}
.embed-code:hover { border-color: #404068; }
.copy-hint {
font-size: .65rem;
color: #282840;
margin-top: .4rem;
}
/* ── footer ── */
footer {
margin-top: 2rem;
font-size: .75rem;
color: #282840;
text-align: center;
line-height: 1.8;
}
footer a { color: #383858; }
/* ── tick ── */
#tick {
font-size: .65rem;
color: #282840;
margin-top: 1.5rem;
letter-spacing: .05em;
}
/* ── copied toast ── */
#toast {
position: fixed;
bottom: 2rem;
left: 50%;
transform: translateX(-50%) translateY(4rem);
background: #1c1c30;
color: #8080c0;
padding: .5rem 1.2rem;
border-radius: 99px;
font-size: .8rem;
transition: transform .3s ease;
pointer-events: none;
}
#toast.show { transform: translateX(-50%) translateY(0); }
</style>
</head>
<body>
<h1>Global Consciousness Project</h1>
<p class="subtitle">Real-time network variance</p>
<div class="dot-wrap">
<div class="dot" id="dot"></div>
</div>
<p class="status-label" id="status-label">Connecting…</p>
<div class="stats">
<div class="stat">
<span class="val" id="s-index"></span>
<span class="lbl">Index</span>
</div>
<div class="stat">
<span class="val" id="s-eggs"></span>
<span class="lbl">Active Eggs</span>
</div>
<div class="stat">
<span class="val" id="s-age"></span>
<span class="lbl">Updated</span>
</div>
</div>
<canvas id="chart"></canvas>
<!-- legend -->
<div class="card">
<p class="card-title">What the color means</p>
<div class="legend-row">
<div class="ldot" style="background:#0055ff;box-shadow:0 0 6px #0055ff"></div>
Deeply coherent — significantly small network variance
<span class="legend-range">&gt; 95%</span>
</div>
<div class="legend-row">
<div class="ldot" style="background:#00ccff;box-shadow:0 0 6px #00ccff"></div>
Small variance — probably coherence
<span class="legend-range">90 95%</span>
</div>
<div class="legend-row">
<div class="ldot" style="background:#00cc44;box-shadow:0 0 6px #00cc44"></div>
Normal — expected random behavior
<span class="legend-range">40 90%</span>
</div>
<div class="legend-row">
<div class="ldot" style="background:#ffdd00;box-shadow:0 0 6px #ffdd00"></div>
Slightly elevated variance — probably chance
<span class="legend-range">10 40%</span>
</div>
<div class="legend-row">
<div class="ldot" style="background:#ff8800;box-shadow:0 0 6px #ff8800"></div>
Strongly elevated variance — possible shared focus
<span class="legend-range">5 10%</span>
</div>
<div class="legend-row">
<div class="ldot" style="background:#ff2200;box-shadow:0 0 6px #ff2200"></div>
Significantly large variance — broadly shared emotion
<span class="legend-range">&lt; 5%</span>
</div>
</div>
<!-- embed -->
<div class="card">
<p class="card-title">Embed on your site</p>
<div class="embed-code" id="embed-code" onclick="copyEmbed()" title="Click to copy"></div>
<p class="copy-hint">Click to copy · The dot updates every 60 seconds</p>
</div>
<footer>
<p>Methodology based on the <a href="https://global-mind.org" target="_blank" rel="noopener">Global Consciousness Project</a> by Roger Nelson.</p>
<p>Contribute an egg: <code>docker run -e SERVER_URL=<span class="origin-span"></span> yourname/gcp-egg</code></p>
</footer>
<p id="tick"></p>
<div id="toast">Copied!</div>
<script>
// ── color mapping ──────────────────────────────────────────────────────
function palette(index) {
if (index > 95) return ['#0055ff', 'rgba(0,85,255,.35)'];
if (index > 90) return ['#00ccff', 'rgba(0,204,255,.35)'];
if (index > 40) return ['#00cc44', 'rgba(0,204,68,.35)'];
if (index > 10) return ['#ffdd00', 'rgba(255,221,0,.35)'];
if (index > 5) return ['#ff8800', 'rgba(255,136,0,.35)'];
return ['#ff2200', 'rgba(255,34,0,.35)'];
}
function label(index, n_eggs) {
if (!n_eggs) return 'No eggs connected — waiting for data';
if (index > 95) return '<strong>Deeply coherent</strong> — significantly small network variance';
if (index > 90) return '<strong>Slightly coherent</strong> — probably chance fluctuation';
if (index > 40) return '<strong>Normal</strong> — expected random behavior';
if (index > 10) return '<strong>Slightly elevated</strong> variance — probably chance';
if (index > 5) return '<strong>Strongly elevated</strong> variance — possible shared focus';
return '<strong>Significantly large</strong> variance — broadly shared emotion';
}
// ── dot ───────────────────────────────────────────────────────────────
const dotEl = document.getElementById('dot');
function applyColor(index) {
const [color, glow] = palette(index);
dotEl.style.background = color;
dotEl.style.boxShadow = `0 0 50px 18px ${glow}, 0 0 110px 40px ${glow}`;
}
// ── chart ─────────────────────────────────────────────────────────────
const canvas = document.getElementById('chart');
const ctx = canvas.getContext('2d');
function drawChart(history) {
const W = canvas.offsetWidth || 480;
const H = 72;
canvas.width = W * devicePixelRatio;
canvas.height = H * devicePixelRatio;
canvas.style.width = W + 'px';
canvas.style.height = H + 'px';
ctx.scale(devicePixelRatio, devicePixelRatio);
if (!history.length) return;
const n = history.length;
const step = W / Math.max(n - 1, 1);
const pad = 4;
ctx.clearRect(0, 0, W, H);
// Draw subtle grid lines at 5%, 40%, 90%, 95%
[5, 40, 90, 95].forEach(pct => {
const y = H - pad - ((pct / 100) * (H - 2 * pad));
ctx.beginPath();
ctx.strokeStyle = '#14142a';
ctx.lineWidth = 1;
ctx.setLineDash([4, 4]);
ctx.moveTo(0, y);
ctx.lineTo(W, y);
ctx.stroke();
ctx.setLineDash([]);
});
// Draw polyline
history.forEach((pt, i) => {
const x = i * step;
const y = H - pad - (pt.index / 100) * (H - 2 * pad);
const [color] = palette(pt.index);
if (i === 0) {
ctx.beginPath();
ctx.moveTo(x, y);
} else {
const prev = history[i - 1];
const px = (i - 1) * step;
const py = H - pad - (prev.index / 100) * (H - 2 * pad);
ctx.lineTo(x, y);
ctx.strokeStyle = color + 'aa';
ctx.lineWidth = 1.5;
ctx.stroke();
ctx.beginPath();
ctx.moveTo(x, y);
}
// dot
ctx.beginPath();
ctx.arc(x, y, 2, 0, Math.PI * 2);
ctx.fillStyle = color;
ctx.fill();
});
}
// ── fetch & render ─────────────────────────────────────────────────────
async function fetchStatus() {
const r = await fetch('/api/status');
return r.json();
}
async function fetchHistory() {
const r = await fetch('/api/history?limit=60');
return r.json();
}
function ageStr(ts) {
if (!ts) return '—';
const d = Math.floor(Date.now() / 1000) - ts;
if (d < 60) return d + 's';
if (d < 3600) return Math.floor(d / 60) + 'm';
return Math.floor(d / 3600) + 'h';
}
async function update() {
try {
const [status, history] = await Promise.all([fetchStatus(), fetchHistory()]);
applyColor(status.index);
document.getElementById('status-label').innerHTML = label(status.index, status.n_eggs);
document.getElementById('s-index').textContent = Math.round(status.index) + '%';
document.getElementById('s-eggs').textContent = status.n_eggs;
document.getElementById('s-age').textContent = ageStr(status.timestamp);
drawChart(history.slice().reverse());
} catch (e) {
document.getElementById('status-label').textContent = 'Connection error — retrying…';
}
}
// ── embed code ────────────────────────────────────────────────────────
const origin = window.location.origin;
document.querySelectorAll('.origin-span').forEach(el => el.textContent = origin);
document.getElementById('embed-code').textContent =
`<iframe src="${origin}/gcp.html" height="48" width="48" scrolling="no" frameborder="0"></iframe>`;
// ── copy toast ────────────────────────────────────────────────────────
function copyEmbed() {
const text = document.getElementById('embed-code').textContent;
navigator.clipboard?.writeText(text).catch(() => {});
const toast = document.getElementById('toast');
toast.classList.add('show');
setTimeout(() => toast.classList.remove('show'), 1800);
}
// ── countdown ─────────────────────────────────────────────────────────
let countdown = 0;
setInterval(() => {
if (countdown <= 0) {
update();
countdown = 60;
}
document.getElementById('tick').textContent = `Next update in ${countdown}s`;
countdown--;
}, 1000);
</script>
</body>
</html>