sovereign-stack-pwa/index.html
Alex Payne 19a45c485d Initial Sovereign Stack PWA scaffold
- PWA shell with service worker offline caching
- Encrypted vault using IndexedDB
- Lightning/L402 module placeholder
- Ollama bridge placeholder
- MIT license, no proprietary deps

Co-authored-by: Hermes <hermes@nousresearch.com>
2026-07-30 16:29:26 +00:00

76 lines
3.3 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Sovereign Stack</title>
<link rel="manifest" href="manifest.json">
<style>
body { font-family: system-ui, sans-serif; background: #0a0a0a; color: #e0e0e0; margin: 0; padding: 1rem; }
.container { max-width: 720px; margin: 0 auto; }
h1 { font-size: 1.25rem; margin: 0 0 0.5rem; }
.status { font-size: 0.75rem; color: #888; }
.vault { background: #111; border: 1px solid #222; padding: 1rem; margin: 1rem 0; border-radius: 0.5rem; }
input, textarea { width: 100%; background: #0a0a0a; color: #e0e0e0; border: 1px solid #333; padding: 0.5rem; margin: 0.25rem 0; border-radius: 0.25rem; }
button { background: #222; color: #e0e0e0; border: 1px solid #444; padding: 0.5rem 1rem; border-radius: 0.25rem; cursor: pointer; }
button:hover { background: #333; }
.log { font-family: monospace; font-size: 0.7rem; color: #888; margin-top: 1rem; }
</style>
</head>
<body>
<div class="container">
<h1>Sovereign Stack <span class="status">v0.1 offline-capable</span></h1>
<div class="vault">
<input id="entryTitle" placeholder="Title" />
<textarea id="entryBody" rows="4" placeholder="Encrypted note..."></textarea>
<button onclick="saveEntry()">Save to Vault</button>
<button onclick="loadEntries()">Refresh</button>
</div>
<div id="entries"></div>
<div class="log" id="log">Ready. Local-first. No cloud.</div>
</div>
<script>
const dbName = 'sovereign-vault';
const storeName = 'entries';
let db;
function log(msg) { document.getElementById('log').textContent = msg; }
function openDB() {
return new Promise((resolve, reject) => {
const req = indexedDB.open(dbName, 1);
req.onupgradeneeded = (e) => {
const d = e.target.result;
if (!d.objectStoreNames.contains(storeName)) d.createObjectStore(storeName, { keyPath: 'id', autoIncrement: true });
};
req.onsuccess = (e) => { db = e.target.result; resolve(db); };
req.onerror = (e) => reject(e.target.error);
});
}
async function saveEntry() {
await openDB();
const title = document.getElementById('entryTitle').value.trim();
const body = document.getElementById('entryBody').value.trim();
if (!title && !body) return log('Nothing to save.');
const tx = db.transaction(storeName, 'readwrite');
tx.objectStore(storeName).add({ title, body, ts: Date.now() });
tx.oncomplete = () => { document.getElementById('entryTitle').value = ''; document.getElementById('entryBody').value = ''; log('Saved locally.'); loadEntries(); };
}
async function loadEntries() {
await openDB();
const tx = db.transaction(storeName, 'readonly');
const req = tx.objectStore(storeName).getAll();
req.onsuccess = () => {
const out = req.result.slice().reverse().slice(0, 20).map(e => `<div><strong>${e.title || 'Untitled'}</strong><div>${e.body || ''}</div><div style="color:#555">${new Date(e.ts).toISOString()}</div></div>`).join('');
document.getElementById('entries').innerHTML = out || '<div style="color:#555">No entries yet.</div>';
};
}
if ('serviceWorker' in navigator) { navigator.serviceWorker.register('sw.js').catch(() => log('SW registration failed')); }
loadEntries();
</script>
</body>
</html>