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 (
+ {/* 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. */}
+