diff --git a/docker/frontend/index.html b/docker/frontend/index.html index dda9df8..eae7565 100644 --- a/docker/frontend/index.html +++ b/docker/frontend/index.html @@ -8,6 +8,29 @@ RecipesCam — web + + + + + + + + + + + + + + + + + + + +
diff --git a/docker/frontend/public/assets/og-cover.png b/docker/frontend/public/assets/og-cover.png new file mode 100644 index 0000000..fce6b65 Binary files /dev/null and b/docker/frontend/public/assets/og-cover.png differ diff --git a/docker/frontend/src/Landing.tsx b/docker/frontend/src/Landing.tsx index 17ee4fb..f7cb0d1 100644 --- a/docker/frontend/src/Landing.tsx +++ b/docker/frontend/src/Landing.tsx @@ -237,6 +237,37 @@ export function Landing() { const toastDownload: Txt = { en: 'Download links land with the public release — the web studio is live now.', vi: 'Link tải sẽ có khi phát hành chính thức — web studio đã chạy được ngay.' }; const toastStore: Txt = { en: 'Store listing arrives with the public release — use the web studio in the meantime.', vi: 'Cửa hàng ứng dụng sẽ mở khi phát hành chính thức — trong lúc chờ, hãy dùng web studio.' }; + // Sharing. The URL is the page's own origin, so the links point at whatever + // host is serving this build (the crawler meta in index.html does the same + // with relative URLs). Each network opens in a new tab with the URL already + // filled in; the last control uses the OS share sheet when there is one and + // copies the link when there is not. + const shareUrl = `${window.location.origin}/`; + const shareText = c({ + en: 'RecipesCam — Kodak and Fuji film colour on your own photos, on the phone and in the browser.', + vi: 'RecipesCam — màu phim Kodak và Fuji trên ảnh của bạn, trên điện thoại và trên trình duyệt.', + }); + const shareLinks: { id: string; label: string; href: string }[] = [ + { id: 'facebook', label: 'FACEBOOK', href: `https://www.facebook.com/sharer/sharer.php?u=${encodeURIComponent(shareUrl)}` }, + { id: 'x', label: 'X', href: `https://twitter.com/intent/tweet?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareText)}` }, + { id: 'linkedin', label: 'LINKEDIN', href: `https://www.linkedin.com/sharing/share-offsite/?url=${encodeURIComponent(shareUrl)}` }, + { id: 'telegram', label: 'TELEGRAM', href: `https://t.me/share/url?url=${encodeURIComponent(shareUrl)}&text=${encodeURIComponent(shareText)}` }, + ]; + const share = async () => { + if (navigator.share) { + // Dismissing the sheet is not a failure — say nothing. + await navigator.share({ title: 'RecipesCam', text: shareText, url: shareUrl }).catch(() => undefined); + return; + } + try { + await navigator.clipboard.writeText(shareUrl); + showToast(c({ en: 'Link copied to the clipboard.', vi: 'Đã sao chép liên kết.' })); + } catch { + // No clipboard (an insecure origin): hand the URL over in the toast. + showToast(c({ en: `Copy this link: ${shareUrl}`, vi: `Sao chép liên kết này: ${shareUrl}` })); + } + }; + return (
{/* 1 — fixed glass navigation */} @@ -252,6 +283,14 @@ export function Landing() { {nav.map((entry) => {c(entry.label)})}
+ {/* The browser studio is live today, so it leads the row and wears + the same primary pill as the store CTA. */} + + {c({ en: 'RecipesCam web studio', vi: 'Studio web RecipesCam' })} + + {/* Theme + language, the same two switches the workspace TopBar has. Theme opens a popover: light/dark plus the font group, which retypes the whole page. */} @@ -348,14 +387,6 @@ export function Landing() { {t('nav.login')} )} - {/* Between the account slot and the store CTA: the browser studio is - live today, so it gets its own way in from the top bar. */} - - {c({ en: 'RecipesCam web studio', vi: 'Studio web RecipesCam' })} - - @@ -381,6 +412,7 @@ export function Landing() { {c({ en: 'Open the Web Studio', vi: 'Mở Web Studio' })} + {/* Share targets: the four networks take the URL as a query param, + the last control uses the OS sheet (phones) or the clipboard. */} +
+ {c({ en: 'SHARE THE PAGE', vi: 'CHIA SẺ TRANG' })} + {shareLinks.map((s) => ( + + {s.label} + + ))} + +
diff --git a/docker/frontend/src/styles/landing.css b/docker/frontend/src/styles/landing.css index 99a89e4..68f85ff 100644 --- a/docker/frontend/src/styles/landing.css +++ b/docker/frontend/src/styles/landing.css @@ -424,6 +424,9 @@ html { scroll-behavior: smooth; scroll-padding-top: 78px; } .lp-store { display: flex; flex-direction: column; align-items: flex-start; line-height: 1.25; } .lp-store small { font-family: var(--lp-mono); font-size: 9.5px; color: var(--lp-muted); } .lp-store b { font-size: 15px; } +/* Share row under the store badges: the label reads as a mono caption. */ +.lp-share { display: flex; align-items: center; justify-content: center; gap: 10px; flex-wrap: wrap; margin-top: 26px; } +.lp-share-label { font-family: var(--lp-mono); font-size: 10px; letter-spacing: 0.12em; color: var(--lp-muted); margin-right: 4px; } .lp-footer { border-top: 1px solid var(--lp-line); padding: 30px 0 40px; } .lp-footer .lp-wrap { display: flex; flex-wrap: wrap; gap: 14px 26px; align-items: center; font-size: 12.5px; color: var(--lp-muted); } .lp-footer a:hover { color: var(--lp-text); }