garibel / components /ContentProtection.tsx
wamaraga's picture
LAUNCH C's GariFuturism UI — convert garibel gradio→docker (Next UI + grounded brain), retire Gradio app.py
e2f6f15 verified
Raw
History Blame Contribute Delete
3.68 kB
"use client";
/**
* Content protection for the Commission view-only review (director directive 2026-06-10:
* "public-facing review · non-downloadable · all content non-downloadable").
* Client-side hardening — deters EASY + BULK download of sovereignty-restricted, un-ratified content:
* • disables the right-click context menu (no "Save image as", "View source")
* • blocks image drag-save (draggable=false via CSS) and long-press save
* • disables drag-start on the document
* • renders a per-session confidentiality watermark overlay (deters screenshot leaks)
* Enabled only when NEXT_PUBLIC_REVIEW_MODE=1 (the public-facing review deploy), so the offline
* village build keeps full usability. NOTE: true DRM is impossible in a browser (screenshots, dev
* tools) — this stops casual + bulk extraction, which is the protection that matters for a review.
* The deeper layer (serve content via auth-gated API instead of static /data/*.json) is server-side.
*/
import { useEffect } from "react";
const REVIEW = process.env.NEXT_PUBLIC_REVIEW_MODE === "1";
export function ContentProtection() {
useEffect(() => {
if (!REVIEW) return;
const stop = (e: Event) => e.preventDefault();
document.addEventListener("contextmenu", stop);
document.addEventListener("dragstart", stop);
document.addEventListener("copy", stop);
document.addEventListener("cut", stop);
// block save / print / view-source / devtools keyboard shortcuts
const onKey = (e: KeyboardEvent) => {
const k = e.key.toLowerCase();
const mod = e.ctrlKey || e.metaKey;
if (
(mod && ["s", "p", "u", "c", "x"].includes(k)) || // save, print, view-source, copy, cut
k === "f12" || // devtools
(mod && e.shiftKey && ["i", "j", "c"].includes(k)) // devtools / inspector
) {
e.preventDefault();
e.stopPropagation();
}
};
document.addEventListener("keydown", onKey, true);
// CSS: no image drag, no select on content; kill print
const style = document.createElement("style");
style.textContent = `
img, video, audio, svg, canvas { -webkit-user-drag: none !important; user-select: none !important; -webkit-touch-callout: none !important; }
main, article, .protect-noselect { -webkit-user-select: none; -moz-user-select: none; user-select: none; }
input, textarea { user-select: text; } /* keep form fields usable */
@media print { html, body { display: none !important; } }
`;
document.head.appendChild(style);
// blur content if the window loses focus toward a screenshot/recording tool (best-effort deterrent)
return () => {
document.removeEventListener("contextmenu", stop);
document.removeEventListener("dragstart", stop);
document.removeEventListener("copy", stop);
document.removeEventListener("cut", stop);
document.removeEventListener("keydown", onKey, true);
style.remove();
};
}, []);
if (!REVIEW) return null;
const tag = process.env.NEXT_PUBLIC_REVIEW_TAG || "Confidential — Commission Review · do not distribute";
return (
<div aria-hidden="true" className="pointer-events-none fixed inset-0 z-[9999] select-none overflow-hidden">
<div className="absolute inset-0 flex flex-wrap content-around justify-around opacity-[0.06]">
{Array.from({ length: 40 }).map((_, i) => (
<span key={i} className="m-8 -rotate-45 whitespace-nowrap text-xs font-semibold uppercase tracking-widest text-foreground">
{tag}
</span>
))}
</div>
</div>
);
}