A1 — ID-class bug family (v0.6.1 persona split left network-NodeId comparisons
where posting IDs live). ~14 sites fixed with multi-persona semantics ("author is
one of MY posting identities", not == default):
- revoke_post_access / revoke_circle_access (revocation was broken for every post)
- group key create/add-member/rotate (add-member distribution now works)
- circle profile set/delete/get, encrypted-attachment CEK unwrap
- receipt/comment slot authorship, replication cycle, blob eviction own-tier
- orphaned GroupKeyRequest/Response (0xA1/0xA2) deleted (no send side existed)
A2 — Manifest privacy: AuthorManifest.author_addresses removed from struct AND
signature digest; CdnManifest tree-era fields dropped. Startup migrations re-sign
own manifests, purge unverifiable foreign copies, strip persona fields from legacy
network-id profile rows. Posting identities are now location-anonymous.
A3 — Discovery & first contact:
- Open-slot comments: derivable slot V_x (blake3 from author + slot_binder_nonce)
so strangers pass the CDN comment-verification gate; OpenSlotDecl in FoF gating
- Greetings: HPKE-style sealed GreetingBody (return_path + fresh reply_pubkey),
throwaway outer ID, size-bucketed; messaging-first (Reply/Dismiss, no vouch)
- Registry: frozen canonical registry post + REGISTRY_POST_ID, signed registration
comments (self-certifying deletes), newest-wins per persona, --publish-registry
- Comment TTL: expires_at_ms inside signed digest v2 (context bump), rand 30-365d
ordinary / fixed 30d registrations, expiry sweep on the eviction cycle
- Single accept_incoming_comment() gate wired into all three ingest sites, closing
a pre-existing hole where both pull paths stored comments with no verification
- UI: consent panel (visible/listed/greetings, active choice at first publish),
registry search in Discover, sealed "Say hi" compose, greetings inbox
Security fixes from review: forged-tombstone injection, delete-by-sender-trust,
remote SetPolicy poisoning, stored XSS (escapeHtml quotes, 7 pre-existing sites),
greeting off-switch now revokes prior bios' slots.
No PoW (rejected: inverted cost). Session-relay gating untouched.
201 core tests pass; CLI + desktop build; 3-node integration green.
DEPLOY GATE: wire-incompatible with v0.7.3 — ALPN bump to itsgoin/4 lands in
Iteration B. Do not build/deploy/anchor-swap before that.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LGiPD2cF75mnvneSCjdDC5
372 lines
21 KiB
HTML
372 lines
21 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover">
|
|
<title>ItsGoin</title>
|
|
<link rel="stylesheet" href="style.css">
|
|
</head>
|
|
<body>
|
|
<!-- First-run: set display name (optional — can be left blank) -->
|
|
<div id="setup-overlay" class="overlay hidden">
|
|
<div class="overlay-box">
|
|
<h2>Welcome to ItsGoin</h2>
|
|
<p>Pick a display name if you want one — or leave blank to stay anonymous.</p>
|
|
<input id="setup-name" type="text" placeholder="Display name (optional)" maxlength="50" autofocus />
|
|
<!-- v0.8 (A3): active choice at first profile publish — pre-checked,
|
|
visible, opt-out BEFORE anything ships (never silently defaulted). -->
|
|
<label class="checkbox-label" style="display:block;text-align:left;font-size:0.8rem;margin:0.5rem 0 0.75rem;cursor:pointer">
|
|
<input type="checkbox" id="setup-greetings-check" checked />
|
|
Accept greetings — people who find you can send a sealed hello. Only you can read them. Turn off any time in Profiles.
|
|
</label>
|
|
<button id="setup-btn" class="btn btn-primary">Continue</button>
|
|
</div>
|
|
</div>
|
|
|
|
<header>
|
|
<div id="header-row">
|
|
<h1>ItsGoin</h1>
|
|
<div id="status-ticker"></div>
|
|
<div id="net-indicator">
|
|
<span id="net-dot"></span>
|
|
<span id="net-labels"></span>
|
|
</div>
|
|
<button id="close-app-btn" title="Close app (stops connections to save battery)" aria-label="Close app"><svg viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M18.36 6.64a9 9 0 1 1-12.73 0"/><line x1="12" y1="2" x2="12" y2="12"/></svg></button>
|
|
</div>
|
|
<nav id="tabs">
|
|
<button class="tab" data-tab="feed"><span class="tab-icon">📰</span><span class="tab-label">Feed</span></button>
|
|
<button class="tab" data-tab="myposts"><span class="tab-icon">✍</span><span class="tab-label">My Posts</span></button>
|
|
<button class="tab" data-tab="people"><span class="tab-icon">👥</span><span class="tab-label">People</span></button>
|
|
<button class="tab" data-tab="messages"><span class="tab-icon">💬</span><span class="tab-label">Messages</span></button>
|
|
<button class="tab" data-tab="settings"><span class="tab-icon">⚙</span><span class="tab-label">Settings</span></button>
|
|
</nav>
|
|
</header>
|
|
|
|
<main>
|
|
<!-- Welcome (shown on startup) -->
|
|
<section id="view-welcome" class="view active">
|
|
<div style="text-align:center;padding:2rem 1rem">
|
|
<h2 style="color:#7fdbca;margin-bottom:0.25rem">Welcome back!</h2>
|
|
<p style="color:#e0e0e0;font-size:1.1rem;margin-bottom:0.5rem">How's it goin?</p>
|
|
<!-- Release upgrade banner: populated by loadUpgradeBanner() when a
|
|
newer version is announced on the user's selected update channel. -->
|
|
<div id="upgrade-banner" class="hidden" style="max-width:360px;margin:0 auto 1rem;padding:0.8rem 1rem;border:1px solid #7fdbca;border-radius:8px;background:#0f2a26;text-align:left;font-size:0.9rem">
|
|
<div id="upgrade-banner-title" style="color:#7fdbca;font-weight:600;margin-bottom:0.3rem"></div>
|
|
<div id="upgrade-banner-body" style="color:#c0d0c0;font-size:0.82rem;margin-bottom:0.6rem"></div>
|
|
<button id="upgrade-banner-btn" style="padding:0.5rem 1rem;border:none;border-radius:6px;background:#7fdbca;color:#0a1a18;font-weight:600;cursor:pointer;width:100%">Download upgrade</button>
|
|
</div>
|
|
<p style="color:#666;font-size:0.8rem;margin-bottom:1.5rem">Connecting and getting updates usually takes a couple minutes.<br>New things we've found so far:</p>
|
|
<div id="welcome-counts" style="display:flex;flex-wrap:wrap;gap:1rem;justify-content:center;color:#888;font-size:0.85rem">
|
|
<div><span id="welcome-connections" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Connections</div>
|
|
<div><span id="welcome-posts" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>New Posts</div>
|
|
<div><span id="welcome-messages" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Messages</div>
|
|
<div><span id="welcome-reacts" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Reacts</div>
|
|
<div><span id="welcome-comments" style="font-size:1.4rem;font-weight:700;color:#5b8def;display:block">-</span>Comments</div>
|
|
</div>
|
|
<div style="margin-top:2rem;max-width:280px;margin-left:auto;margin-right:auto">
|
|
<div style="background:#1a1a2e;border-radius:6px;height:6px;overflow:hidden;margin-bottom:0.75rem">
|
|
<div id="welcome-ready-bar" style="height:100%;background:#7fdbca;width:0%;transition:width 0.5s ease;border-radius:6px"></div>
|
|
</div>
|
|
<button id="welcome-ready-btn" disabled style="width:100%;padding:0.75rem 1.5rem;border:1px solid #333;border-radius:8px;background:#1a1a2e;color:#666;font-size:0.95rem;cursor:not-allowed;opacity:0.5;transition:all 0.3s ease">Loading...</button>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Feed tab -->
|
|
<section id="view-feed" class="view">
|
|
<div id="persona-filter-row" class="hidden" style="display:flex;gap:0.4rem;flex-wrap:wrap;padding:0.3rem 0.5rem;border-bottom:1px solid #222;overflow-x:auto"></div>
|
|
<div id="feed-list"></div>
|
|
</section>
|
|
|
|
<!-- My Posts tab -->
|
|
<section id="view-myposts" class="view">
|
|
<div class="section-card" style="text-align:center;margin-bottom:0.5rem">
|
|
<button id="profile-lightbox-btn" class="btn btn-ghost btn-sm">Profiles</button>
|
|
<button id="circles-toggle" class="btn btn-ghost btn-sm section-toggle">Manage Circles</button>
|
|
<button id="redundancy-lightbox-btn" class="btn btn-ghost btn-sm">Redundancy</button>
|
|
<div id="circles-body" class="hidden">
|
|
<div class="input-row" style="margin-top:0.5rem">
|
|
<input id="circle-name-input" placeholder="New circle name" />
|
|
<button id="create-circle-btn" class="btn btn-primary">Create</button>
|
|
</div>
|
|
<div id="circles-list"></div>
|
|
</div>
|
|
</div>
|
|
<div id="compose">
|
|
<textarea id="post-content" placeholder="How's it goin?"></textarea>
|
|
<div id="attachment-preview"></div>
|
|
<input type="file" id="file-input" multiple class="hidden" />
|
|
<div style="text-align:center;margin:0.3rem 0"><button id="attach-btn" class="btn btn-ghost btn-sm" title="Attach images or videos">Attach</button></div>
|
|
<div id="compose-footer">
|
|
<div class="compose-left">
|
|
<span class="hint">Ctrl+Enter to post</span>
|
|
<div id="visibility-row">
|
|
<select id="persona-select" title="Post as" class="hidden"></select>
|
|
<select id="visibility-select">
|
|
<option value="fof_closed" selected>Extended Friends (FoF)</option>
|
|
<option value="friends">Friends</option>
|
|
<option value="public">Public</option>
|
|
<option value="circle">Circle</option>
|
|
</select>
|
|
<select id="circle-select" class="hidden"></select>
|
|
<select id="comment-perm-select" title="Comment permission">
|
|
<option value="public">Comments: All</option>
|
|
<option value="followers_only">Comments: Followers</option>
|
|
<option value="friends_of_friends">Comments: Friends of Friends</option>
|
|
<option value="none">Comments: Off</option>
|
|
</select>
|
|
<select id="react-perm-select" title="React permission">
|
|
<option value="both">Reacts: All</option>
|
|
<option value="public">Reacts: Public</option>
|
|
<option value="private">Reacts: Private</option>
|
|
<option value="none">Reacts: Off</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
<div class="compose-right">
|
|
<span id="char-count">0/500</span>
|
|
<button id="post-btn" class="btn btn-primary">Post</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="my-posts-list"></div>
|
|
</section>
|
|
|
|
<!-- People tab -->
|
|
<section id="view-people" class="view">
|
|
<div class="section-card">
|
|
<div style="display:flex;align-items:center;justify-content:space-between;gap:0.5rem;margin-bottom:0.4rem">
|
|
<h3 style="margin:0">Following</h3>
|
|
<button id="follows-refresh-btn" class="btn btn-ghost btn-sm hidden" style="background:#1f3f38;color:#7fdbca;border:1px solid #7fdbca">See new activity</button>
|
|
</div>
|
|
<p class="empty-hint" style="margin:0 0 0.5rem;font-size:0.75rem">Sorted by last post. Tap a name to see their bio.</p>
|
|
<div id="follows-list"></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<button id="discover-toggle" class="btn btn-ghost btn-sm section-toggle">Hide Discover</button>
|
|
<div id="discover-body">
|
|
<!-- v0.8 (A3): registry search — on-demand chain pull, local query -->
|
|
<div class="input-row" style="margin-bottom:0.5rem">
|
|
<input id="registry-search-input" placeholder="Search the registry: name or keywords" />
|
|
<button id="registry-search-btn" class="btn btn-primary">Search</button>
|
|
</div>
|
|
<div id="registry-results"></div>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem">Named profiles on the network you haven't followed or ignored.</p>
|
|
<div id="discover-list"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-card" style="display:flex;gap:0.5rem;flex-wrap:wrap">
|
|
<button id="share-details-btn" class="btn btn-ghost btn-sm">Share my details</button>
|
|
<button id="connect-toggle" class="btn btn-ghost btn-sm">Add peer manually</button>
|
|
<div id="connect-body" class="hidden">
|
|
<div class="input-row" style="margin-top:0.5rem">
|
|
<input id="connect-input" placeholder="paste connect string: nodeid@ip:port" />
|
|
<button id="connect-btn" class="btn btn-primary">Connect</button>
|
|
</div>
|
|
<div id="connect-status"></div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Messages tab -->
|
|
<section id="view-messages" class="view">
|
|
<div id="dm-compose" class="section-card">
|
|
<h3>New Conversation</h3>
|
|
<div class="dm-compose-row">
|
|
<select id="dm-recipient-select">
|
|
<option value="">(select recipient)</option>
|
|
</select>
|
|
</div>
|
|
<textarea id="dm-content" placeholder="Write a private message..." rows="2"></textarea>
|
|
<div class="dm-compose-footer">
|
|
<span class="hint">Encrypted end-to-end</span>
|
|
<button id="dm-send-btn" class="btn btn-primary">Send</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="conversations-list"></div>
|
|
|
|
<div class="section-card" id="message-requests-section">
|
|
<h3>Message Requests</h3>
|
|
<div id="message-requests-list"></div>
|
|
</div>
|
|
|
|
<!-- v0.8 (A3): greetings inbox — [Reply] and [Dismiss] ONLY.
|
|
Vouching is People-tab relationship management, never a
|
|
Messages action (round 9). -->
|
|
<div class="section-card" id="greetings-section">
|
|
<h3>Greetings</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.75rem">Sealed hellos from people outside your circles. Replies go back sealed — only the sender can read them.</p>
|
|
<div id="greetings-list"></div>
|
|
</div>
|
|
</section>
|
|
|
|
<!-- Settings tab -->
|
|
<section id="view-settings" class="view">
|
|
<!-- Hidden profile fields (used by JS, edited via Profiles lightbox) -->
|
|
<div class="hidden">
|
|
<input id="profile-name" type="text" maxlength="50" />
|
|
<textarea id="profile-bio" maxlength="200"></textarea>
|
|
<input type="checkbox" id="public-visible-check" checked />
|
|
<button id="save-profile-btn"></button>
|
|
<button id="circle-profiles-toggle"></button>
|
|
<div id="circle-profiles-body" class="hidden"><div id="circle-profiles-list"></div></div>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:center">
|
|
<h3 style="margin-bottom:0.25rem">Ignored</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem">Peers whose posts and profiles are hidden from Feed, Messages, and Discover. Local-only; nothing on the wire says you've ignored them.</p>
|
|
<div id="ignored-list" style="text-align:left"></div>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:center">
|
|
<h3 style="margin-bottom:0.25rem">Vouches</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem">Vouches you've given let those friends read your Friend-of-Friend posts. Vouches you've received unlock the posts of those who vouched for you. Revoking rotates your vouch key — the revoked friend keeps access to your existing posts, but not future ones.</p>
|
|
<div style="display:flex;gap:1rem;text-align:left">
|
|
<div style="flex:1;min-width:0">
|
|
<h4 style="margin:0 0 0.4rem;font-size:0.9rem">Given</h4>
|
|
<div id="vouches-given-list"></div>
|
|
</div>
|
|
<div style="flex:1;min-width:0">
|
|
<h4 style="margin:0 0 0.4rem;font-size:0.9rem">Received</h4>
|
|
<div id="vouches-received-list"></div>
|
|
</div>
|
|
</div>
|
|
<div style="margin-top:0.75rem;padding-top:0.75rem;border-top:1px solid var(--border)">
|
|
<p class="empty-hint" style="margin-bottom:0.4rem">Rotate your vouch key (V_me) to issue a fresh key to all your current vouchees. Old posts remain readable by anyone who held the old key — use "Cascade revoke" afterward to actively cut off comment access to your old posts.</p>
|
|
<button id="rotate-v-me-btn" class="btn btn-ghost btn-sm">Rotate my vouch key</button>
|
|
<span id="rotate-v-me-status" class="empty-hint" style="margin-left:0.5rem;font-size:0.8rem"></span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:center">
|
|
<h3 style="margin-bottom:0.25rem">Updates</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem">Network-wide release announcements are signed by the bootstrap anchor and arrive via the CDN. Choose which channel to follow.</p>
|
|
<div id="update-channel-row" style="display:flex;gap:1rem;justify-content:center;margin-bottom:0.5rem">
|
|
<label style="cursor:pointer"><input type="radio" name="update-channel" value="stable" id="channel-stable" checked> Stable</label>
|
|
<label style="cursor:pointer"><input type="radio" name="update-channel" value="beta" id="channel-beta"> Beta</label>
|
|
</div>
|
|
<div id="update-status" class="empty-hint" style="font-size:0.8rem"></div>
|
|
<button id="check-updates-btn" class="btn btn-ghost btn-sm" style="margin-top:0.5rem">Check now</button>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:left">
|
|
<h3 style="margin-bottom:0.4rem;text-align:center">Your data on this device</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.78rem;line-height:1.5">
|
|
<strong style="color:#7fdbca">Personas</strong> are who you are to peers — the keys you post and message with. Most people only need one. To move your account to a new device, you <em>export your personas</em> from this device and <em>import them</em> on the new one.
|
|
<br><br>
|
|
<strong style="color:#888">Device Address</strong> below is this device's own network endpoint — usually not what you want to move. Leave it alone unless you know why you're touching it.
|
|
</p>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:center">
|
|
<h3 style="margin-bottom:0.25rem">Personas</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem">Separate posting identities on this device. Peers see each persona as a distinct author.</p>
|
|
<div id="personas-list" style="margin-bottom:0.5rem;text-align:left"></div>
|
|
<button id="create-persona-btn" class="btn btn-ghost btn-sm">New Persona</button>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:center">
|
|
<h3 style="margin-bottom:0.4rem">Move to another device</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.78rem">
|
|
Export creates a ZIP containing your personas (and optionally posts/follows). Import on the other device's Settings > Move to another device.
|
|
</p>
|
|
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap">
|
|
<button id="export-btn" class="btn btn-primary btn-sm">Export personas</button>
|
|
<button id="import-btn" class="btn btn-ghost btn-sm">Import from another device</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-card" style="text-align:center">
|
|
<h3 style="margin-bottom:0.4rem;font-size:0.85rem;color:#888">Device Address (advanced)</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.72rem">
|
|
This device's network endpoint — the QUIC address peers use to reach you. Changing this rotates the device's network identifier but does NOT change your posting identity (personas). Rarely useful.
|
|
</p>
|
|
<div id="identities-list" style="margin-bottom:0.5rem"></div>
|
|
<div style="display:flex;gap:0.5rem;justify-content:center;flex-wrap:wrap">
|
|
<button id="create-identity-btn" class="btn btn-ghost btn-sm">New Device Address</button>
|
|
<button id="import-identity-btn" class="btn btn-ghost btn-sm">Import Address Key</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<button id="notifications-btn" class="btn btn-ghost btn-full">Notifications</button>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<h3 style="margin-bottom:0.4rem;font-size:0.85rem;color:#888">Session Relay (off by default)</h3>
|
|
<p class="empty-hint" style="margin-bottom:0.5rem;font-size:0.72rem">
|
|
When two peers can't connect directly through their networks, a third peer can pipe their traffic through itself. This burns the relay peer's bandwidth on someone else's connection. Off by default. Enable only if you're OK both <em>using</em> other peers as relays and <em>serving</em> as a relay for others.
|
|
</p>
|
|
<label style="display:flex;align-items:center;gap:0.5rem;cursor:pointer">
|
|
<input type="checkbox" id="session-relay-toggle">
|
|
<span>Enable session relay</span>
|
|
</label>
|
|
</div>
|
|
|
|
<!-- Hidden: node-info, anchors, diagnostics btn moved elsewhere -->
|
|
<div class="hidden">
|
|
<button id="anchors-toggle"></button>
|
|
<div id="anchors-body"><div id="known-anchors-list"></div><div id="anchors-list"></div></div>
|
|
<button id="diagnostics-btn"></button>
|
|
<div id="node-info"><span id="node-id"></span></div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<h3>Text Size</h3>
|
|
<div id="text-size-btns" style="display:flex;gap:0.4rem;margin-top:0.3rem">
|
|
<button class="notif-opt text-size-opt" data-size="xsmall">XS</button>
|
|
<button class="notif-opt text-size-opt" data-size="small">S</button>
|
|
<button class="notif-opt text-size-opt active" data-size="normal">M</button>
|
|
<button class="notif-opt text-size-opt" data-size="large">L</button>
|
|
<button class="notif-opt text-size-opt" data-size="xlarge">XL</button>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="section-card">
|
|
<h3>Cache Storage</h3>
|
|
<div id="cache-stats-display" class="empty-hint">Loading...</div>
|
|
<label for="cache-size-select" style="margin-top:0.5rem;display:block">Cache Size Limit</label>
|
|
<select id="cache-size-select" style="margin-top:0.25rem;width:100%">
|
|
<option value="268435456">256 MB</option>
|
|
<option value="536870912">512 MB</option>
|
|
<option value="1073741824" selected>1 GB (default)</option>
|
|
<option value="2147483648">2 GB</option>
|
|
<option value="5368709120">5 GB</option>
|
|
<option value="10737418240">10 GB</option>
|
|
<option value="0">Unlimited</option>
|
|
</select>
|
|
</div>
|
|
|
|
<!-- Redundancy moved to My Posts lightbox -->
|
|
<div class="hidden"><div id="redundancy-panel"></div></div>
|
|
|
|
<!-- Sync moved to diagnostics popover -->
|
|
<div class="hidden"><button id="sync-btn"></button></div>
|
|
|
|
<div class="section-card">
|
|
<h3>Danger Zone</h3>
|
|
<p class="empty-hint">Delete all local data. Device address key preserved.</p>
|
|
<button id="reset-data-btn" class="btn btn-danger btn-full">Reset All Data</button>
|
|
</div>
|
|
</section>
|
|
</main>
|
|
|
|
<!-- Generic popover -->
|
|
<div id="popover-overlay" class="overlay hidden">
|
|
<div class="overlay-box overlay-wide">
|
|
<div class="overlay-header">
|
|
<h3 id="popover-title"></h3>
|
|
<button class="overlay-close" id="popover-close-btn">×</button>
|
|
</div>
|
|
<div class="overlay-body" id="popover-body"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="toast" class="toast hidden"></div>
|
|
<script src="app.js"></script>
|
|
</body>
|
|
</html>
|