148 lines
4.6 KiB
TypeScript
148 lines
4.6 KiB
TypeScript
"use client";
|
||
|
||
import { useEffect, useSyncExternalStore } from "react";
|
||
import { useChromeless } from "@/components/layout/route-visibility";
|
||
|
||
/** 对齐 jha-1043 eightPop.js:sessionStorage + 关闭/退出均离开站点 */
|
||
const SESSION_KEY = "ageConfirmed";
|
||
const EXIT_URL = "about:blank";
|
||
|
||
let confirmed: boolean | null = null;
|
||
const listeners = new Set<() => void>();
|
||
|
||
function emit() {
|
||
listeners.forEach((listener) => listener());
|
||
}
|
||
|
||
function readConfirmed() {
|
||
if (confirmed === null) {
|
||
try {
|
||
confirmed = window.sessionStorage.getItem(SESSION_KEY) === "1";
|
||
} catch {
|
||
confirmed = false;
|
||
}
|
||
}
|
||
return confirmed;
|
||
}
|
||
|
||
function confirmAge() {
|
||
confirmed = true;
|
||
try {
|
||
window.sessionStorage.setItem(SESSION_KEY, "1");
|
||
} catch {
|
||
// sessionStorage 不可用时仍允许本页确认并关闭
|
||
}
|
||
emit();
|
||
}
|
||
|
||
export function openAgeGateDialog() {
|
||
confirmed = false;
|
||
try {
|
||
window.sessionStorage.removeItem(SESSION_KEY);
|
||
} catch {}
|
||
emit();
|
||
}
|
||
|
||
export function subscribeAgeGate(listener: () => void) {
|
||
listeners.add(listener);
|
||
return () => listeners.delete(listener);
|
||
}
|
||
|
||
export const getAgeGateOpen = () => !readConfirmed();
|
||
const getServerSnapshot = () => false;
|
||
|
||
function exitSite() {
|
||
try {
|
||
window.location.replace(EXIT_URL);
|
||
} catch {
|
||
window.location.href = EXIT_URL;
|
||
}
|
||
}
|
||
|
||
export function AgeGateDialogHost() {
|
||
const chromeless = useChromeless();
|
||
const open = useSyncExternalStore(subscribeAgeGate, getAgeGateOpen, getServerSnapshot) && !chromeless;
|
||
|
||
useEffect(() => {
|
||
if (!open) return;
|
||
const { overflow } = document.body.style;
|
||
document.body.style.overflow = "hidden";
|
||
return () => {
|
||
document.body.style.overflow = overflow;
|
||
};
|
||
}, [open]);
|
||
|
||
if (!open) return null;
|
||
|
||
return (
|
||
<div data-component="age-gate-dialog" className="fixed inset-0 z-[60] flex flex-col items-center overflow-y-auto bg-black/70 px-4 py-10">
|
||
<div className="my-auto flex flex-col items-center gap-9 lg:gap-6">
|
||
<section
|
||
role="dialog"
|
||
aria-modal="true"
|
||
aria-labelledby="age-gate-title"
|
||
aria-describedby="age-gate-desc"
|
||
className="flex w-[300px] flex-col items-center gap-[18px] rounded-[20px] bg-[#0B0914] px-5 py-6 outline outline-1 outline-white/20 lg:w-[440px] lg:gap-9 lg:rounded-[40px] lg:p-9"
|
||
>
|
||
<AdultIcon />
|
||
<div className="flex flex-col gap-[18px] text-white/90 lg:gap-6">
|
||
<h2
|
||
id="age-gate-title"
|
||
className="text-center text-lg/6 font-semibold lg:text-2xl/8 lg:font-medium"
|
||
>
|
||
这是个成人网站
|
||
</h2>
|
||
<p id="age-gate-desc" className="text-justify text-sm/5 lg:text-lg/6">
|
||
本网站包含有年龄限制的内容,包括裸体和露骨色情素材的内容。
|
||
点击继续即表示您确认您已年满 18 岁,或在您访问本网站时所在的司法管辖区已是成年人
|
||
<span className="hidden lg:inline">。</span>
|
||
</p>
|
||
</div>
|
||
<div className="flex w-full flex-col gap-3">
|
||
<button
|
||
type="button"
|
||
onClick={confirmAge}
|
||
className="cursor-pointer rounded-xl bg-[#E9019C] py-[13px] text-base/[22px] font-semibold text-white transition hover:bg-[#F21AAA]"
|
||
>
|
||
我已年满18岁-进入
|
||
</button>
|
||
<button
|
||
type="button"
|
||
onClick={exitSite}
|
||
className="cursor-pointer rounded-xl bg-white/10 py-[13px] text-base/[22px] text-white/90 transition hover:bg-white/15"
|
||
>
|
||
我未满18岁-退出
|
||
</button>
|
||
</div>
|
||
</section>
|
||
<button type="button" aria-label="退出" onClick={exitSite} className="cursor-pointer size-9">
|
||
<svg viewBox="0 0 44 44" className="size-full" aria-hidden="true">
|
||
<circle cx="22" cy="22" r="21" fill="none" stroke="white" strokeWidth="2" />
|
||
<path d="M16 16l12 12M28 16L16 28" stroke="white" strokeWidth="2" strokeLinecap="round" />
|
||
</svg>
|
||
</button>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
function AdultIcon() {
|
||
return (
|
||
<svg viewBox="0 0 78 78" className="size-[78px] shrink-0 text-[#E9019C]" aria-hidden="true">
|
||
<circle cx="39" cy="39" r="35" fill="none" stroke="currentColor" strokeWidth="5" />
|
||
<text
|
||
x="39"
|
||
y="40"
|
||
fill="currentColor"
|
||
fontSize="26"
|
||
fontWeight="800"
|
||
textAnchor="middle"
|
||
dominantBaseline="central"
|
||
fontFamily="Arial, Helvetica, sans-serif"
|
||
>
|
||
18+
|
||
</text>
|
||
</svg>
|
||
);
|
||
}
|