176 lines
5.5 KiB
TypeScript
176 lines
5.5 KiB
TypeScript
import React, { useEffect, useMemo, useState } from "react";
|
|
import { createRoot } from "react-dom/client";
|
|
import "./privacy.css";
|
|
|
|
type Locale = "en" | "fr";
|
|
|
|
type CopyBlock = {
|
|
title: string;
|
|
tagline: string;
|
|
backLabel: string;
|
|
effective: string;
|
|
summaryTitle: string;
|
|
summaryText: string;
|
|
sections: { title: string; items: string[] }[];
|
|
footer: string;
|
|
contactFooter: string;
|
|
};
|
|
|
|
const copy: Record<Locale, CopyBlock> = {
|
|
en: {
|
|
title: "Privacy Policy",
|
|
tagline: "A tiny policy for a tabletop bank.",
|
|
backLabel: "Back to NegoCity",
|
|
effective: "Effective February 3, 2026",
|
|
summaryTitle: "Quick summary",
|
|
summaryText:
|
|
"We do not sell personal data. We do not run ads or analytics. We only use the game info needed to run a session.",
|
|
sections: [
|
|
{
|
|
title: "What we do not do",
|
|
items: [
|
|
"We do not sell, trade, or rent personal data.",
|
|
"We do not sell your soul (or anyone else's).",
|
|
"We do not run ads or third-party analytics.",
|
|
"We do not create user accounts.",
|
|
],
|
|
},
|
|
{
|
|
title: "What we do use to run the game",
|
|
items: [
|
|
"Session code, player names, balances, transactions, and chat messages you enter.",
|
|
"Optional device notification token, only if you enable push notifications.",
|
|
"Electricity.",
|
|
],
|
|
},
|
|
{
|
|
title: "Where data lives",
|
|
items: [
|
|
"Game data lives in memory while the session is active and disappears when it ends.",
|
|
"Some data can be stored locally on your device (such as the last session and autosave snapshots).",
|
|
"In your head, hopefully.",
|
|
],
|
|
},
|
|
],
|
|
footer: "This policy applies to the Negopoly Companion app and companion web experience.",
|
|
contactFooter: "For more info, contact us at",
|
|
},
|
|
fr: {
|
|
title: "Politique de confidentialite",
|
|
tagline: "Une politique simple pour une banque de jeu de societe.",
|
|
backLabel: "Retour a NegoCity",
|
|
effective: "En vigueur le 3 fevrier 2026",
|
|
summaryTitle: "Resume rapide",
|
|
summaryText:
|
|
"Nous ne vendons pas de donnees personnelles. Nous n'utilisons ni publicites ni analytics. Nous utilisons uniquement les infos de jeu necessaires pour la session.",
|
|
sections: [
|
|
{
|
|
title: "Ce que nous ne faisons pas",
|
|
items: [
|
|
"Nous ne vendons, n'echangeons, ni ne louons vos donnees personnelles.",
|
|
"Nous ne vendons pas votre ame (ni celle de qui que ce soit).",
|
|
"Nous n'affichons pas de publicites et n'utilisons pas d'analytics tiers.",
|
|
"Nous ne creons pas de comptes utilisateurs.",
|
|
],
|
|
},
|
|
{
|
|
title: "Ce que nous utilisons pour faire tourner la partie",
|
|
items: [
|
|
"Code de session, noms des joueurs, soldes, transactions et messages de chat que vous saisissez.",
|
|
"Jeton de notification, uniquement si vous activez les notifications push.",
|
|
"De l'electricite.",
|
|
],
|
|
},
|
|
{
|
|
title: "Ou les donnees sont stockees",
|
|
items: [
|
|
"Les donnees de jeu restent en memoire pendant la session et disparaissent a la fin.",
|
|
"Certaines donnees peuvent etre stockees localement sur votre appareil (par exemple la derniere session et les sauvegardes automatiques).",
|
|
"Dans votre tete, esperons-le.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Cette politique s'applique a l'application Negopoly Companion et a l'experience web associee.",
|
|
contactFooter: "Pour plus d'informations, contactez-nous a",
|
|
},
|
|
};
|
|
|
|
function getDefaultLocale(): Locale {
|
|
if (typeof navigator === "undefined") return "en";
|
|
return navigator.language?.toLowerCase().startsWith("fr") ? "fr" : "en";
|
|
}
|
|
|
|
function Privacy() {
|
|
const [locale, setLocale] = useState<Locale>(getDefaultLocale());
|
|
const content = useMemo(() => copy[locale], [locale]);
|
|
|
|
useEffect(() => {
|
|
document.documentElement.lang = locale;
|
|
}, [locale]);
|
|
|
|
return (
|
|
<div className="privacy">
|
|
<header className="hero reveal" style={{ "--delay": "0.05s" } as React.CSSProperties}>
|
|
<div className="hero__top">
|
|
<a className="back-link" href="/">
|
|
{content.backLabel}
|
|
</a>
|
|
<div className="lang-toggle" role="tablist" aria-label="Language">
|
|
<button
|
|
type="button"
|
|
className={locale === "en" ? "active" : ""}
|
|
onClick={() => setLocale("en")}
|
|
aria-pressed={locale === "en"}
|
|
>
|
|
English
|
|
</button>
|
|
<button
|
|
type="button"
|
|
className={locale === "fr" ? "active" : ""}
|
|
onClick={() => setLocale("fr")}
|
|
aria-pressed={locale === "fr"}
|
|
>
|
|
Francais
|
|
</button>
|
|
</div>
|
|
</div>
|
|
<h1>{content.title}</h1>
|
|
<p>{content.tagline}</p>
|
|
<div className="hero__meta">{content.effective}</div>
|
|
</header>
|
|
|
|
<section className="summary reveal" style={{ "--delay": "0.12s" } as React.CSSProperties}>
|
|
<div className="summary__label">{content.summaryTitle}</div>
|
|
<div className="summary__text">{content.summaryText}</div>
|
|
</section>
|
|
|
|
<section className="policy">
|
|
{content.sections.map((section, index) => (
|
|
<article
|
|
key={section.title}
|
|
className="policy__card reveal"
|
|
style={{ "--delay": `${0.18 + index * 0.08}s` } as React.CSSProperties}
|
|
>
|
|
<h2>{section.title}</h2>
|
|
<ul>
|
|
{section.items.map((item) => (
|
|
<li key={item}>{item}</li>
|
|
))}
|
|
</ul>
|
|
</article>
|
|
))}
|
|
</section>
|
|
|
|
<footer className="footer reveal" style={{ "--delay": "0.42s" } as React.CSSProperties}>
|
|
<p>{content.footer}</p>
|
|
<p>
|
|
{content.contactFooter}{" "}
|
|
<a href="mailto:privacy@negopoly.fr">privacy@negopoly.fr</a>
|
|
</p>
|
|
</footer>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
const root = createRoot(document.getElementById("root")!);
|
|
root.render(<Privacy />);
|