Add legal consent and reliable external redirects

This commit is contained in:
hz
2026-07-20 02:16:20 -05:00
parent 2615ab4773
commit dae3a31b71
25 changed files with 756 additions and 14 deletions
+7
View File
@@ -37,11 +37,18 @@ RUN mkdir -p /build \
&& cp /src/assets/fonts/Inter-Variable.ttf /build/medmind-inter.ttf \
&& cp /src/web/donation-complete.html /build/donation-complete.html \
&& cp /src/web/donation-canceled.html /build/donation-canceled.html \
&& cp /src/web/terms.html /src/web/privacy.html /src/web/legal-page.css /src/web/legal-page.js /src/web/external-redirect.html /build/ \
&& mkdir -p /build/legal \
&& cp /src/legal/legal_documents.json /build/legal/legal_documents.json \
&& test -s /build/index.html \
&& test -s /build/ranaze-loader-icon.png \
&& test -s /build/medmind-inter.ttf \
&& test -s /build/donation-complete.html \
&& test -s /build/donation-canceled.html \
&& test -s /build/terms.html \
&& test -s /build/privacy.html \
&& test -s /build/external-redirect.html \
&& test -s /build/legal/legal_documents.json \
&& test -s /build/index.wasm \
&& test -s /build/index.pck
+31
View File
@@ -0,0 +1,31 @@
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Opening secure service | RanaZe</title><style>body{font:16px system-ui;background:#081324;color:#f8fafc;display:grid;place-items:center;min-height:100vh;margin:0}.card{max-width:34rem;padding:2rem;border:1px solid #33415d;border-radius:8px}a{color:#38bdf8}</style></head>
<body><main class="card"><strong id="title">Opening secure service...</strong><p id="message">This window will continue automatically.</p><p><a id="continue" hidden>Continue</a></p></main>
<script>
(() => {
const parameters = new URLSearchParams(location.search);
const key = parameters.get("key") || "";
const provider = parameters.get("provider") || "external service";
const allowedKey = /^ranaze_[a-z0-9_]{1,80}$/i.test(key);
const title = document.getElementById("title");
const message = document.getElementById("message");
const link = document.getElementById("continue");
title.textContent = `Opening ${provider}...`;
message.textContent = `This window will continue to ${provider} automatically.`;
const redirect = () => {
if (!allowedKey) return;
const url = localStorage.getItem(key);
if (!url || !url.startsWith("https://")) return;
link.href = url;
link.hidden = false;
link.textContent = `Continue to ${provider}`;
localStorage.removeItem(key);
location.replace(url);
};
addEventListener("storage", (event) => { if (event.key === key) redirect(); });
addEventListener("focus", redirect);
setInterval(redirect, 500);
redirect();
})();
</script></body></html>
+19
View File
@@ -0,0 +1,19 @@
:root { color-scheme: light dark; font-family: Inter, system-ui, sans-serif; }
* { box-sizing: border-box; }
body { margin: 0; background: #f6f8fc; color: #172033; line-height: 1.65; }
main { width: min(880px, calc(100% - 32px)); margin: 32px auto 72px; }
header { border-bottom: 1px solid #cbd5e1; margin-bottom: 28px; padding-bottom: 22px; }
.brand { color: #2563eb; font-weight: 700; text-decoration: none; }
h1 { font-size: clamp(2rem, 5vw, 3rem); line-height: 1.1; margin: 18px 0 8px; }
.meta, .summary { color: #526078; }
section { margin: 30px 0; }
h2 { font-size: 1.25rem; line-height: 1.35; }
a { color: #2563eb; }
.error { color: #dc2626; font-weight: 600; }
@media (prefers-color-scheme: dark) {
body { background: #081324; color: #f8fafc; }
header { border-color: #33415d; }
.meta, .summary { color: #a9b6cc; }
a, .brand { color: #38bdf8; }
.error { color: #fb7185; }
}
+27
View File
@@ -0,0 +1,27 @@
(() => {
const key = document.body.dataset.document;
const target = document.getElementById("legal-document");
const escapeHtml = (value) => String(value).replace(/[&<>"']/g, (character) => ({
"&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;"
})[character]);
fetch("/legal/legal_documents.json", { cache: "no-store" })
.then((response) => {
if (!response.ok) throw new Error(`status ${response.status}`);
return response.json();
})
.then((documents) => {
const documentData = documents[key];
if (!documentData) throw new Error("document not found");
document.title = `${documentData.title} | RanaZe`;
const sections = documentData.sections.map((section) => `
<section><h2>${escapeHtml(section.heading)}</h2>
${section.paragraphs.map((paragraph) => `<p>${escapeHtml(paragraph)}</p>`).join("")}</section>`).join("");
target.innerHTML = `
<header><a class="brand" href="/">RanaZe: Step 3 Trainer</a>
<h1>${escapeHtml(documentData.title)}</h1>
<div class="meta">Effective ${escapeHtml(documentData.effectiveDate)} | Version ${escapeHtml(documentData.version)}</div>
<p class="summary">${escapeHtml(documentData.summary)}</p></header>${sections}`;
})
.catch(() => { target.innerHTML = '<p class="error">This document could not be loaded. Contact [email protected].</p>'; });
})();
+6
View File
@@ -41,6 +41,12 @@ server {
try_files $uri =404;
}
location ~ ^/(terms\.html|privacy\.html|legal-page\.(css|js)|legal/legal_documents\.json|external-redirect\.html)$ {
add_header Cache-Control "no-cache, no-store, must-revalidate" always;
add_header X-Content-Type-Options "nosniff" always;
try_files $uri =404;
}
# Godot exports reuse these filenames across releases. Revalidate them so a
# newly deployed index never boots an older cached project package.
location ~ ^/(index\.(js|pck|wasm)|ranaze-loader-icon\.png|medmind-inter\.ttf)$ {
+4
View File
@@ -0,0 +1,4 @@
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Privacy Policy | RanaZe</title><link rel="stylesheet" href="/legal-page.css"></head>
<body data-document="privacy"><main id="legal-document"><p>Loading Privacy Policy...</p></main><script src="/legal-page.js"></script></body></html>
+4
View File
@@ -0,0 +1,4 @@
<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>Terms of Service | RanaZe</title><link rel="stylesheet" href="/legal-page.css"></head>
<body data-document="terms"><main id="legal-document"><p>Loading Terms of Service...</p></main><script src="/legal-page.js"></script></body></html>