diff --git a/docker/frontend/src/Landing.tsx b/docker/frontend/src/Landing.tsx index 13fd4bb..6aec5fa 100644 --- a/docker/frontend/src/Landing.tsx +++ b/docker/frontend/src/Landing.tsx @@ -18,6 +18,53 @@ type Txt = { en: string; vi: string }; const SAMPLE = (n: number) => `/assets/samples/s${n}.jpg`; +// No page can really stop a save — the bytes are already on the machine. What it +// can do is refuse to hand over more pixels than it draws: the photo is decoded, +// redrawn at the size of its box times the screen's pixel ratio (2x at most) and +// that smaller bitmap is what the tag carries, so a saved copy is a screen-sized +// one and not the uploader's 4000px original. The save gestures are turned off +// on top of it. `data-full` keeps the original address for tests and debugging. +function Shot({ src, alt, filter }: { src: string; alt: string; filter?: string }) { + const tag = useRef(null); + const [shown, setShown] = useState(src); + useEffect(() => { + setShown(src); + let alive = true; + const source = new Image(); + source.onload = () => { + const box = tag.current?.clientWidth ?? 0; + const scale = Math.min(2, Math.max(1, window.devicePixelRatio || 1)); + const width = Math.min(source.naturalWidth, Math.round(box * scale)); + if (!alive || box === 0 || width >= source.naturalWidth) return; + const canvas = document.createElement('canvas'); + canvas.width = width; + canvas.height = Math.max(1, Math.round((source.naturalHeight * width) / source.naturalWidth)); + const ctx = canvas.getContext('2d'); + if (!ctx) return; + ctx.drawImage(source, 0, 0, canvas.width, canvas.height); + setShown(canvas.toDataURL('image/jpeg', 0.85)); + }; + source.src = src; + return () => { + alive = false; + }; + }, [src]); + return ( + {alt} e.preventDefault()} + onContextMenu={(e) => e.preventDefault()} + /> + ); +} + interface Look { tag: string; title: Txt; @@ -517,7 +564,7 @@ export function Landing() {
- {c(look.title)} +
@@ -551,7 +598,7 @@ export function Landing() { ))}
- {`${preset.name} +
f/1.8 SS 1/500s @@ -588,7 +635,11 @@ export function Landing() {
- {c({ +
{c({ en: 'CUSTOM RECIPE', vi: 'CÔNG THỨC RIÊNG' })}TEMP {temp > 0 ? `+${temp}` : temp}SAT {sat}%CON {con}%
@@ -647,7 +698,11 @@ export function Landing() {
- {c({ +
diff --git a/docker/frontend/src/styles/landing.css b/docker/frontend/src/styles/landing.css index b051ed8..7b87ef3 100644 --- a/docker/frontend/src/styles/landing.css +++ b/docker/frontend/src/styles/landing.css @@ -293,6 +293,9 @@ html { scroll-behavior: smooth; scroll-padding-top: 116px; } } .lp-shot { position: relative; aspect-ratio: 4 / 5; border-radius: 5px; overflow: hidden; background: #000; } .lp-shot img { width: 100%; height: 100%; object-fit: cover; display: block; } +/* Contributed photos, whatever the section: the page draws them but does not + invite a save — no drag image, no long-press callout, no text selection. */ +.lp-photo { -webkit-user-drag: none; user-select: none; -webkit-touch-callout: none; } .lp-grain { position: absolute; inset: 0;