From 21725b35ff42fb52b5b3aaf4bf99b4db0a44aa6f Mon Sep 17 00:00:00 2001
From: thePR0M3TH3AN <53631862+PR0M3TH3AN@users.noreply.github.com>
Date: Mon, 1 Jun 2026 10:34:48 -0400
Subject: [PATCH] External embed: code generator dialog in preview pane
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Per docs/EXTERNAL_EMBED_FEATURE.md §13 / §18 Phase 6 — the embed-
code generator now ships alongside Phase 1's viewer so authors
can grab iframe snippets directly from the community preview.
UX:
- New "Embed ↗" button in the preview pane's action row, between
the share-link button and Use/Fork. Hidden for private rows
(embed refuses them anyway); shown for locked-premium rows so
designs can be embedded as watermarked-preview teasers pre-
purchase.
- Click opens #embedCodeModal: a single-screen dialog with
identifier selector, size preset, options checkboxes, live
iframe preview, and the generated iframe HTML in a copy-ready
textarea + Copy URL / Copy iframe code buttons.
Controls:
- Identifier: naddr (latest version, auto-updates) | nevent
(snapshot, frozen to this exact event). naddr is the default
but falls back to nevent when the row has no d-tag.
- Size: responsive 16:9 (recommended) | square 320 / 480 | fixed
640×360 / 800×450 / 960×540 | custom WxH.
- Options: controls / author chip / "Open in Artstr" link /
credits row — each toggled via a query param (controls=0,
attr=0, open=0, credits=1).
The live-preview pane uses a pink/yellow checker background so
transparency in the embedded design shows through visually
(verifies that the iframe is actually transparent before the
user pastes the snippet on their own site).
Copy buttons use navigator.clipboard.writeText with a graceful
fallback toast on permission denial. The URL extracted from the
generated iframe's src=, so URL/iframe stay in sync.
---
src/index.html | 228 +++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 228 insertions(+)
diff --git a/src/index.html b/src/index.html
index 8cb5d54..87db273 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4640,6 +4640,56 @@
+
+
+
+
Embed this design
+
Paste the snippet below into any website to render this design read-only. Linked sub-designs, animations, and deck navigation all work inside the iframe.
+
+
+
+
+
+
+
+
+
+ ×
+
+
+
+
+
+
+
+
+
+
+
+
Live preview
+
+
+
Iframe code
+
+
+
+
+
+
+
+
+
@@ -21624,6 +21674,166 @@
_paintEmbed(root, payload, event, route, decoded);
}
+ // -------- Embed code generator (preview-pane dialog) -------------
+ // Opens from the "Embed ↗" button on the design preview pane.
+ // Builds an iframe snippet from a row + user-picked options;
+ // shows a live preview of what the snippet will render.
+
+ function _embedSizeFor(preset, customW, customH) {
+ switch (preset) {
+ case 'square-320': return { w: 320, h: 320, responsive: false };
+ case 'square-480': return { w: 480, h: 480, responsive: false };
+ case 'fixed-640-360': return { w: 640, h: 360, responsive: false };
+ case 'fixed-800-450': return { w: 800, h: 450, responsive: false };
+ case 'fixed-960-540': return { w: 960, h: 540, responsive: false };
+ case 'custom': return { w: Math.max(50, customW | 0) || 800,
+ h: Math.max(50, customH | 0) || 450,
+ responsive: false };
+ case 'responsive-16-9':
+ default: return { w: 800, h: 450, responsive: true };
+ }
+ }
+
+ function _buildEmbedIdFromRow(row, idType) {
+ if (!row?.e) return '';
+ const topRelays = splitRelayList(state.community?.relays || '').slice(0, 2);
+ if (idType === 'nevent') {
+ return encodeNevent({
+ eventId: row.e.id,
+ author: row.e.pubkey || '',
+ kind: row.e.kind || 30078,
+ relays: topRelays,
+ });
+ }
+ // naddr: replaceable-event coord; requires a d-tag.
+ const dTag = (row.e.tags || []).find(t => t[0] === 'd' && t[1])?.[1] || '';
+ if (!dTag) return '';
+ return encodeNaddr({
+ kind: row.e.kind || 30078,
+ pubkey: row.e.pubkey || '',
+ dTag,
+ relays: topRelays,
+ });
+ }
+
+ function _buildEmbedUrlString(id, opts) {
+ const params = new URLSearchParams();
+ if (!opts.controls) params.set('controls', '0');
+ if (!opts.attr) params.set('attr', '0');
+ if (!opts.open) params.set('open', '0');
+ if (opts.credits) params.set('credits', '1');
+ const qs = params.toString();
+ return `${location.origin}/embed/${id}${qs ? '?' + qs : ''}`;
+ }
+
+ function refreshEmbedDialog() {
+ const modal = $('embedCodeModal');
+ const row = modal?._row;
+ if (!row) return;
+ const idType = $('embedIdType').value;
+ const sizePreset = $('embedSizePreset').value;
+ const isCustom = sizePreset === 'custom';
+ for (const id of ['embedCustomW', 'embedCustomX', 'embedCustomH']) {
+ const el = $(id); if (el) el.style.display = isCustom ? '' : 'none';
+ }
+ const id = _buildEmbedIdFromRow(row, idType);
+ const codeEl = $('embedIframeCode');
+ const previewArea = $('embedPreviewArea');
+ if (!id) {
+ if (codeEl) codeEl.value = '// This design has no d-tag — switch identifier to Snapshot (nevent).';
+ if (previewArea) previewArea.innerHTML = '
This design has no addressable d-tag. Use Snapshot (nevent) instead.