← back to Wallco Ai
wallco.ai · /design/:id Share button — Web Share API + clipboard fallback
680a09e2a9033833bcf42ab29908aa1805f0c598 · 2026-05-12 06:53:30 -0700 · SteveStudio2
Drops a "↗ Share" pill into the existing .cta-group row, sibling to
"Request Free Sample" + "Back to Designs". On click:
1. Tries navigator.share() — mobile/iOS/Android native share sheet pops
with title + body text + canonical URL. Best UX, lets the user pick
Messages/Mail/AirDrop/IG/etc.
2. Falls through to navigator.clipboard.writeText() if Web Share is
unavailable or the user cancelled. Button label flashes "Link copied"
for 1.8s then reverts.
3. Last-resort window.prompt() if clipboard API is blocked (older browsers,
non-https). Always succeeds at giving the user the URL.
Pairs with the OG/Twitter cards from tick 27 — when the shared URL hydrates
in iMessage/Slack/etc., recipients see the og:image:product preview card.
Reversible: 1 button + 30 lines JS + 15 lines CSS. No new deps.
Files touched
M public/css/site.cssM server.js
Diff
commit 680a09e2a9033833bcf42ab29908aa1805f0c598
Author: SteveStudio2 <stevestudio2@SteveStacStudio.lan>
Date: Tue May 12 06:53:30 2026 -0700
wallco.ai · /design/:id Share button — Web Share API + clipboard fallback
Drops a "↗ Share" pill into the existing .cta-group row, sibling to
"Request Free Sample" + "Back to Designs". On click:
1. Tries navigator.share() — mobile/iOS/Android native share sheet pops
with title + body text + canonical URL. Best UX, lets the user pick
Messages/Mail/AirDrop/IG/etc.
2. Falls through to navigator.clipboard.writeText() if Web Share is
unavailable or the user cancelled. Button label flashes "Link copied"
for 1.8s then reverts.
3. Last-resort window.prompt() if clipboard API is blocked (older browsers,
non-https). Always succeeds at giving the user the URL.
Pairs with the OG/Twitter cards from tick 27 — when the shared URL hydrates
in iMessage/Slack/etc., recipients see the og:image:product preview card.
Reversible: 1 button + 30 lines JS + 15 lines CSS. No new deps.
---
public/css/site.css | 17 +++++++++++++++++
server.js | 40 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 57 insertions(+)
diff --git a/public/css/site.css b/public/css/site.css
index 85a9787..5dd857d 100644
--- a/public/css/site.css
+++ b/public/css/site.css
@@ -557,6 +557,23 @@ main { padding-top: 73px; }
.spec-val { font-size: 13px; color: var(--ink); }
.cta-group { display: flex; gap: 12px; flex-wrap: wrap; margin-bottom: 24px; }
.cta-sample {}
+.cta-share {
+ display: inline-flex;
+ align-items: center;
+ gap: 7px;
+ font-family: var(--sans);
+ cursor: pointer;
+ transition: background-color 160ms ease, color 160ms ease;
+}
+.cta-share .cta-share-icon {
+ font-size: 14px;
+ line-height: 1;
+ transform: translateY(-1px);
+}
+.cta-share:focus-visible {
+ outline: 2px solid var(--ink);
+ outline-offset: 2px;
+}
.provenance-note {
font-size: 13px;
color: var(--ink-faint);
diff --git a/server.js b/server.js
index ab48b0f..fbd7609 100644
--- a/server.js
+++ b/server.js
@@ -3066,8 +3066,48 @@ ${htmlHeader('/designs')}
<div class="cta-group">
<a id="cta-sample-link" href="/samples?design=${design.id}" class="btn-primary cta-sample">Request Free Sample</a>
<a href="/designs" class="btn-outline">Back to Designs</a>
+ <button id="cta-share" type="button" class="btn-outline cta-share"
+ data-share-title="${design.title} — wallco.ai"
+ data-share-text="Found this in the wallco.ai catalog — ${design.title}"
+ data-share-url="https://wallco.ai/design/${design.id}"
+ aria-label="Share this design">
+ <span class="cta-share-icon" aria-hidden="true">↗</span>
+ <span class="cta-share-label">Share</span>
+ </button>
</div>
<script>
+ (function(){
+ var btn = document.getElementById('cta-share');
+ if (!btn) return;
+ var labelEl = btn.querySelector('.cta-share-label');
+ var origLabel = labelEl.textContent;
+ var resetTimer = null;
+ function flash(msg){
+ if (resetTimer) clearTimeout(resetTimer);
+ labelEl.textContent = msg;
+ resetTimer = setTimeout(function(){ labelEl.textContent = origLabel; }, 1800);
+ }
+ btn.addEventListener('click', async function(){
+ var url = btn.dataset.shareUrl;
+ var title = btn.dataset.shareTitle;
+ var text = btn.dataset.shareText;
+ // Prefer native Web Share API when available (mobile + supporting desktop browsers).
+ if (navigator.share) {
+ try { await navigator.share({ title: title, text: text, url: url }); return; }
+ catch (e) { /* user cancelled or unsupported — fall through to clipboard */ }
+ }
+ // Clipboard fallback.
+ try {
+ await navigator.clipboard.writeText(url);
+ flash('Link copied');
+ } catch (e) {
+ // Last resort: prompt() so the user can copy manually.
+ try { window.prompt('Copy this link:', url); } catch (_) {}
+ }
+ });
+ })();
+ </script>
+ <script>
(function(){
function syncCtaWidth(){
var saved = JSON.parse(localStorage.getItem('wallco.material') || 'null');
← f4354f9 spoonflower upload — fix hydration race + add --batch CSV mo
·
back to Wallco Ai
·
spoonflower — add scripts/spoonflower_kept_to_csv.py to make 8aa0c7c →