diff --git a/src/index.html b/src/index.html index 1fa3768..cadaf52 100644 --- a/src/index.html +++ b/src/index.html @@ -578,38 +578,86 @@ /* Embed viewer (docs/EXTERNAL_EMBED_FEATURE.md). When embed mode is on, hide every existing top-level element and show only the - dedicated #embedRoot the embed boot path creates. */ + dedicated #embedRoot the embed boot path creates. + + Frame is fully transparent โ€” controls float as individual + pill chips over the bottom edge so the embed blends with any + host theme (light, dark, branded). Each chip carries its own + semi-opaque dark background + blur so it stays readable + regardless of what's behind. */ body.embed-mode { margin: 0; padding: 0; overflow: hidden; background: transparent; } body.embed-mode > :not(#embedRoot) { display: none !important; } #embedRoot { position: fixed; inset: 0; - display: flex; flex-direction: column; background: transparent; font: 13px/1.45 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; color: #111827; } #embedRoot .embedStage { - flex: 1 1 auto; - position: relative; + position: absolute; inset: 0; overflow: hidden; display: flex; align-items: center; justify-content: center; + background: transparent; } #embedRoot .embedFooter { - flex: 0 0 auto; - padding: 6px 10px; - display: flex; gap: 12px; - align-items: center; justify-content: space-between; + position: absolute; + left: 0; right: 0; bottom: 0; + padding: 8px 10px; + display: flex; gap: 8px; + align-items: flex-end; justify-content: space-between; + pointer-events: none; font-size: 0.74rem; - background: rgba(255,255,255,0.78); - backdrop-filter: blur(6px); - border-top: 1px solid rgba(0,0,0,0.08); } #embedRoot .embedFooter[data-hidden="true"] { display: none; } - #embedRoot .embedFooter a { color: #1e40af; text-decoration: none; } - #embedRoot .embedFooter a:hover { text-decoration: underline; } + #embedRoot .embedFooter > * { pointer-events: auto; } + #embedRoot .embedFooter .embedSide { + display: flex; flex-wrap: wrap; gap: 6px; + align-items: center; + max-width: 100%; + } + /* Pill chip: floats over any host theme. */ + #embedRoot .embedChip { + display: inline-flex; align-items: center; gap: 4px; + background: rgba(17,24,39,0.7); + color: #ffffff; + border: 1px solid rgba(255,255,255,0.14); + border-radius: 999px; + padding: 3px 10px; + text-decoration: none; + font-size: 0.7rem; + line-height: 1.25; + white-space: nowrap; + backdrop-filter: blur(8px) saturate(120%); + -webkit-backdrop-filter: blur(8px) saturate(120%); + cursor: pointer; + box-shadow: 0 1px 3px rgba(0,0,0,0.18); + max-width: 200px; + overflow: hidden; + text-overflow: ellipsis; + } + #embedRoot a.embedChip, #embedRoot button.embedChip { color: #ffffff; } + #embedRoot .embedChip:hover { background: rgba(17,24,39,0.88); } + #embedRoot .embedChip[disabled] { opacity: 0.5; cursor: default; } + #embedRoot .embedChipUnlock { color: #fbbf24; border-color: rgba(251,191,36,0.45); } + #embedRoot .embedChipUnlock:hover { background: rgba(0,0,0,0.85); } + #embedRoot .embedChipCounter { + background: rgba(17,24,39,0.55); + cursor: default; + font-variant-numeric: tabular-nums; + } + #embedRoot .embedChipBadge { + background: rgba(0,0,0,0.6); + color: #fbbf24; + cursor: default; + letter-spacing: 0.04em; + font-weight: 600; + } #embedRoot .embedStatus { display: flex; flex-direction: column; align-items: center; gap: 6px; padding: 14px 16px; text-align: center; color: #4b5563; font-size: 0.85rem; + background: rgba(255,255,255,0.86); + border-radius: 8px; + max-width: 80%; } #embedRoot .embedStatus.error { color: #92400e; } @@ -21334,31 +21382,26 @@ _setEmbedStatus(root, 'error', 'Design preview unavailable.'); return; } - // Footer: author chip + Open in Artstr link, both togglable - // via query params per docs/EXTERNAL_EMBED_FEATURE.md ยง6.3. + // Footer: chip-style controls float over the bottom edge of + // the stage with no background band โ€” so the embed blends + // into any host theme. Each chip carries its own dark blur + // for cross-theme legibility. footer.innerHTML = ''; const params = route.params || new URLSearchParams(); const showAttr = params.get('attr') !== '0'; const showOpen = params.get('open') !== '0'; const showControls = params.get('controls') !== '0'; const left = document.createElement('div'); + left.className = 'embedSide'; const right = document.createElement('div'); - left.style.cssText = 'display:flex;align-items:center;gap:6px;min-width:0'; - right.style.cssText = 'display:flex;align-items:center;gap:8px'; + right.className = 'embedSide'; + right.style.justifyContent = 'flex-end'; if (showAttr && event?.pubkey) { - const profile = _feedCache.profiles?.[event.pubkey] || state.community?.profiles?.[event.pubkey] || null; - const name = profile?.display_name || profile?.name || shortPubkey(event.pubkey); - const a = document.createElement('a'); - a.textContent = '@' + name; - a.title = profile?.nip05 || encodeNpub(event.pubkey) || event.pubkey; - a.href = `${location.origin}/u/${encodeNpub(event.pubkey) || event.pubkey}`; - a.target = '_top'; - a.rel = 'noopener'; - a.style.cssText = 'color:#1e40af;text-decoration:none;font-family:monospace;font-size:0.78rem;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;max-width:200px'; - left.appendChild(a); + left.appendChild(_makeAuthorChip(event.pubkey)); } if (showOpen) { const open = document.createElement('a'); + open.className = 'embedChip'; open.textContent = 'Open in Artstr โ†—'; open.href = `${location.origin}/#/share/${route.id}`; open.target = '_top'; @@ -21370,6 +21413,22 @@ footer.dataset.hidden = (!showControls || (!showAttr && !showOpen)) ? 'true' : 'false'; } + // Author chip used by both the public and premium-locked + // paint paths. Resolves to display name when cached; falls + // back to a short pubkey otherwise. + function _makeAuthorChip(pubkey) { + const profile = _feedCache.profiles?.[pubkey] || state.community?.profiles?.[pubkey] || null; + const name = profile?.display_name || profile?.name || shortPubkey(pubkey); + const a = document.createElement('a'); + a.className = 'embedChip'; + a.textContent = '@' + name; + a.title = profile?.nip05 || encodeNpub(pubkey) || pubkey; + a.href = `${location.origin}/u/${encodeNpub(pubkey) || pubkey}`; + a.target = '_top'; + a.rel = 'noopener'; + return a; + } + // Paint a premium-locked embed: watermarked preview (single // image OR paginated deck slides) + a "๐Ÿ”’ Open in Artstr to // unlock" footer CTA. The iframe never initiates a zap; visitors @@ -21387,31 +21446,27 @@ const totalDeck = isDeck ? (preview.totalSlides || slides.length) : 0; let idx = 0; const wrap = document.createElement('div'); - wrap.style.cssText = 'position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;background:#0b0b0c;overflow:hidden'; + wrap.style.cssText = 'position:relative;width:100%;height:100%;display:flex;align-items:center;justify-content:center;overflow:hidden'; const img = document.createElement('img'); img.alt = 'Premium Artstr preview'; img.style.cssText = 'max-width:100%;max-height:100%;object-fit:contain;display:block'; wrap.appendChild(img); - // Soft lock badge โ€” sits in the top-right of the stage so - // it's always visible but doesn't fight the artwork. + // Lock badge โ€” sits top-right of the stage, same chip system + // as the footer so the visual language stays uniform. const badge = document.createElement('div'); - badge.style.cssText = 'position:absolute;top:8px;right:8px;background:rgba(0,0,0,0.55);color:#fbbf24;padding:3px 7px;border-radius:4px;font-size:0.72rem;font-weight:600;letter-spacing:0.04em;display:flex;align-items:center;gap:4px;pointer-events:none'; + badge.className = 'embedChip embedChipBadge'; + badge.style.cssText = 'position:absolute;top:8px;right:8px'; badge.textContent = '๐Ÿ”’ PREMIUM'; wrap.appendChild(badge); stage.appendChild(wrap); - // Deck pager: only when more than one slide is available. let counter = null; - let prev = null; - let next = null; const paint = () => { const s = slides[idx] || slides[0]; img.src = s?.dataUrl || ''; if (counter) { - if (isDeck && totalDeck > slides.length) { - counter.textContent = `Slide ${idx + 1} of ${slides.length} ยท ${totalDeck} in deck`; - } else { - counter.textContent = `Slide ${idx + 1} of ${slides.length}`; - } + counter.textContent = (isDeck && totalDeck > slides.length) + ? `${idx + 1} / ${slides.length} ยท ${totalDeck} in deck` + : `${idx + 1} / ${slides.length}`; } }; paint(); @@ -21420,46 +21475,37 @@ const showOpen = params.get('open') !== '0'; const showAttr = params.get('attr') !== '0'; const left = document.createElement('div'); - left.style.cssText = 'display:flex;align-items:center;gap:8px;min-width:0'; + left.className = 'embedSide'; const right = document.createElement('div'); - right.style.cssText = 'display:flex;align-items:center;gap:8px'; + right.className = 'embedSide'; + right.style.justifyContent = 'flex-end'; if (isDeck && slides.length > 1 && showControls) { - prev = document.createElement('button'); + const prev = document.createElement('button'); prev.type = 'button'; + prev.className = 'embedChip'; prev.textContent = 'โ—‚'; prev.title = 'Previous slide'; - next = document.createElement('button'); + const next = document.createElement('button'); next.type = 'button'; + next.className = 'embedChip'; next.textContent = 'โ–ธ'; next.title = 'Next slide'; counter = document.createElement('span'); - counter.style.cssText = 'color:#374151;font-variant-numeric:tabular-nums'; - for (const b of [prev, next]) { - b.style.cssText = 'background:#fef3c7;color:#1f2937;border:1px solid #fcd34d;border-radius:3px;padding:1px 7px;cursor:pointer;font-size:0.85rem'; - } + counter.className = 'embedChip embedChipCounter'; prev.addEventListener('click', () => { idx = (idx - 1 + slides.length) % slides.length; paint(); }); next.addEventListener('click', () => { idx = (idx + 1) % slides.length; paint(); }); left.append(prev, counter, next); } if (showAttr && event?.pubkey) { - const profile = _feedCache.profiles?.[event.pubkey] || state.community?.profiles?.[event.pubkey] || null; - const name = profile?.display_name || profile?.name || shortPubkey(event.pubkey); - const a = document.createElement('a'); - a.textContent = '@' + name; - a.href = `${location.origin}/u/${encodeNpub(event.pubkey) || event.pubkey}`; - a.target = '_top'; - a.rel = 'noopener'; - a.style.cssText = 'color:#1e40af;text-decoration:none;font-family:monospace;font-size:0.78rem'; - if (left.firstChild) left.appendChild(document.createTextNode(' ยท ')); - left.appendChild(a); + left.appendChild(_makeAuthorChip(event.pubkey)); } if (showOpen) { const open = document.createElement('a'); + open.className = 'embedChip embedChipUnlock'; open.textContent = '๐Ÿ”’ Open in Artstr to unlock โ†—'; open.href = `${location.origin}/#/share/${route.id}`; open.target = '_top'; open.rel = 'noopener'; - open.style.cssText = 'color:#92400e;font-weight:600'; right.appendChild(open); } footer.appendChild(left);