mirror of
https://github.com/PR0M3TH3AN/VoxVera.git
synced 2026-10-27 09:40:40 +00:00
Shared voxvera.org links now render a real image card, not just a text summary. Adds site/og-card.jpg (1200x630, ~90KB, derived from the "say something… voxvera.org" custom art) and wires og:image + twitter:image + image dimensions/alt on the home, board, and safety pages, bumping twitter:card to summary_large_image. Per-flyer previews remain out of scope (fragment URLs are invisible to servers; this stays a purely static site). Test asserts all three pages declare the og:image and that the asset is served as JPEG. Full suite 378 passing. Co-Authored-By: Claude Opus 4.8 <[email protected]>
152 lines
8.6 KiB
HTML
152 lines
8.6 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
<title>VoxVera — Bulletin Board</title>
|
|
<!-- Root-absolute asset paths: with Vercel cleanUrls + trailingSlash this
|
|
page is served at /board/, so relative paths would resolve to
|
|
/board/... and 404. -->
|
|
<link rel="icon" type="image/svg+xml" href="/favicon.svg">
|
|
<link rel="stylesheet" href="/nostr-client.css">
|
|
<meta name="description" content="Recent VoxVera flyers published to the Nostr relays, filtered by your web of trust.">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:site_name" content="VoxVera">
|
|
<meta property="og:title" content="VoxVera — Bulletin Board">
|
|
<meta property="og:description" content="Recent VoxVera flyers published to the Nostr relays, filtered by your web of trust.">
|
|
<meta property="og:url" content="https://voxvera.org/board">
|
|
<meta property="og:image" content="https://voxvera.org/og-card.jpg">
|
|
<meta property="og:image:width" content="1200">
|
|
<meta property="og:image:height" content="630">
|
|
<meta property="og:image:alt" content="VoxVera — say something. voxvera.org">
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
<meta name="twitter:title" content="VoxVera — Bulletin Board">
|
|
<meta name="twitter:description" content="Recent VoxVera flyers published to the Nostr relays, filtered by your web of trust.">
|
|
<meta name="twitter:image" content="https://voxvera.org/og-card.jpg">
|
|
</head>
|
|
<body class="board-page">
|
|
<header class="topbar">
|
|
<div>
|
|
<h1 id="board-title">Bulletin Board</h1>
|
|
<p id="board-subtitle">Most recent flyers published to the relays.</p>
|
|
</div>
|
|
<div class="topbar-controls">
|
|
<a id="board-back" class="board-back" href="/">Back to VoxVera</a>
|
|
<span id="board-identity" class="board-identity" hidden>
|
|
<span id="board-identity-npub" class="board-npub"></span>
|
|
<button id="board-disconnect" type="button" class="board-disconnect">Disconnect</button>
|
|
</span>
|
|
<select id="board-lang" aria-label="Language"></select>
|
|
</div>
|
|
</header>
|
|
<main class="board-main">
|
|
<!-- Login gate: the board is visible only to a connected Nostr identity.
|
|
The data itself is public on the relays, so this is not access
|
|
control — it supplies the viewer's identity so a later web-of-trust
|
|
filter can show them a trustworthy board (see docs/roadmap.md). -->
|
|
<section id="board-gate" class="board-gate">
|
|
<h2 id="board-gate-title">Connect to view the board</h2>
|
|
<p id="board-gate-body">Connect a Nostr identity to view the bulletin board.</p>
|
|
<div class="board-connect-methods">
|
|
<button id="board-connect" type="button" class="board-connect">Browser extension (NIP-07)</button>
|
|
<button id="board-connect-nip46-toggle" type="button" class="board-connect-alt">Remote signer (NIP-46)</button>
|
|
<button id="board-connect-nsec-toggle" type="button" class="board-connect-alt">Use a private key (nsec)</button>
|
|
<button id="board-generate" type="button" class="board-connect-alt">Create a new key</button>
|
|
</div>
|
|
<!-- NIP-46 remote signer (bunker): the local key is ephemeral and
|
|
in-memory; the user's secret stays in their signer app. Establishes
|
|
the viewer pubkey and keeps the connection so reports/deletes can be
|
|
signed remotely this session. -->
|
|
<div id="board-nip46-row" class="board-nsec-row" hidden>
|
|
<input id="board-nip46-input" type="text" autocomplete="off" spellcheck="false" placeholder="bunker://…">
|
|
<button id="board-nip46-submit" type="button" class="board-connect">Connect</button>
|
|
<p id="board-nip46-note" class="board-key-note"></p>
|
|
<button id="board-nip46-scan" type="button" class="board-connect-alt">Scan with your signer app</button>
|
|
<!-- nostrconnect:// QR: this client generates the link; the signer app
|
|
scans it and connects back. The local key is ephemeral. -->
|
|
<div id="board-nip46-qr-wrap" hidden>
|
|
<div id="board-nip46-qr" class="nip46-qr"></div>
|
|
<input id="board-nip46-uri" readonly>
|
|
<button id="board-nip46-uri-copy" type="button" class="board-connect-alt">Copy link</button>
|
|
<p id="board-nip46-scan-status" class="board-key-note" role="status"></p>
|
|
</div>
|
|
</div>
|
|
<!-- nsec import: the key is decoded in-page to derive the pubkey, then
|
|
discarded. Only the pubkey is persisted (board never signs). -->
|
|
<div id="board-nsec-row" class="board-nsec-row" hidden>
|
|
<input id="board-nsec-input" type="password" autocomplete="off" spellcheck="false" placeholder="nsec1…">
|
|
<button id="board-nsec-submit" type="button" class="board-connect">Connect</button>
|
|
<p id="board-nsec-note" class="board-key-note"></p>
|
|
</div>
|
|
<!-- Generated key reveal: shown once so the user can save the secret. -->
|
|
<div id="board-generated" class="board-generated" hidden>
|
|
<p id="board-gen-warning" class="board-key-warning"></p>
|
|
<label class="board-key-field">npub
|
|
<input id="board-gen-npub" readonly>
|
|
</label>
|
|
<label class="board-key-field">nsec
|
|
<span class="board-key-secret">
|
|
<input id="board-gen-nsec" type="password" readonly>
|
|
<button id="board-gen-copy" type="button" class="board-disconnect">Copy</button>
|
|
</span>
|
|
</label>
|
|
<button id="board-gen-continue" type="button" class="board-connect">Continue</button>
|
|
</div>
|
|
<p id="board-gate-error" class="board-gate-error" role="alert" hidden></p>
|
|
</section>
|
|
<div id="board-content" hidden>
|
|
<div class="board-filter">
|
|
<span id="board-filter-note" class="board-filter-note"></span>
|
|
<span id="board-blocked-note" class="board-blocked-note" hidden>
|
|
<span id="board-blocked-count">0</span> · <button id="board-unblock-all" type="button" class="board-action">Clear blocked</button>
|
|
</span>
|
|
<label class="board-show-all">
|
|
<input type="checkbox" id="board-mine-only">
|
|
<span id="board-mine-only-label">My flyers</span>
|
|
</label>
|
|
<label class="board-show-all">
|
|
<input type="checkbox" id="board-show-all">
|
|
<span id="board-show-all-label">Show all flyers</span>
|
|
</label>
|
|
</div>
|
|
<p id="board-status">Loading…</p>
|
|
<table class="board-table" id="board-table">
|
|
<thead><tr id="board-head"></tr></thead>
|
|
<tbody id="board-rows"></tbody>
|
|
</table>
|
|
</div>
|
|
</main>
|
|
|
|
<p class="board-link"><a id="board-safety" href="/safety.html">Safety & privacy</a></p>
|
|
|
|
<div id="board-delete-modal" class="modal-overlay" hidden>
|
|
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="board-delete-title">
|
|
<h2 id="board-delete-title">Delete this flyer?</h2>
|
|
<p id="board-delete-message">This requests deletion from the relays and hides it in VoxVera. Deletion can't be guaranteed across every relay.</p>
|
|
<div class="modal-actions">
|
|
<button id="board-delete-cancel" type="button" class="board-action">Cancel</button>
|
|
<button id="board-delete-confirm" type="button" class="board-action danger">Delete</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="board-report-modal" class="modal-overlay" hidden>
|
|
<div class="modal" role="dialog" aria-modal="true" aria-labelledby="board-report-title">
|
|
<h2 id="board-report-title">Report this flyer?</h2>
|
|
<p id="board-report-message">This publishes a public report to the relays and hides the flyer for you. Reports can't be undone.</p>
|
|
<select id="board-report-category" class="board-report-category" aria-label="Report category"></select>
|
|
<input id="board-report-reason" type="text" autocomplete="off" spellcheck="false" placeholder="Reason (optional)" maxlength="280">
|
|
<div class="modal-actions">
|
|
<button id="board-report-cancel" type="button" class="board-action">Cancel</button>
|
|
<button id="board-report-confirm" type="button" class="board-action danger">Submit report</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script src="/locales.js"></script>
|
|
<script src="/vendor/nostr-tools/nostr.bundle.js"></script>
|
|
<script src="/vendor/qrcode-generator/qrcode.js"></script>
|
|
<script src="/board.js"></script>
|
|
</body>
|
|
</html>
|