Ajout de la privacy policy (obligatoire pour App Store) et ajout de screenshots pour l'App Store
BIN
ScreenShots/iPad Air 13-inch/Chat.png
Normal file
|
After Width: | Height: | Size: 171 KiB |
BIN
ScreenShots/iPad Air 13-inch/Home.png
Normal file
|
After Width: | Height: | Size: 188 KiB |
BIN
ScreenShots/iPad Air 13-inch/Lobby.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
ScreenShots/iPad Air 13-inch/Start.png
Normal file
|
After Width: | Height: | Size: 271 KiB |
BIN
ScreenShots/iPad Air 13-inch/Transfers.png
Normal file
|
After Width: | Height: | Size: 239 KiB |
BIN
ScreenShots/iPhone 17 Pro Max/Chat.png
Normal file
|
After Width: | Height: | Size: 163 KiB |
BIN
ScreenShots/iPhone 17 Pro Max/Home.png
Normal file
|
After Width: | Height: | Size: 187 KiB |
BIN
ScreenShots/iPhone 17 Pro Max/Lobby.png
Normal file
|
After Width: | Height: | Size: 152 KiB |
BIN
ScreenShots/iPhone 17 Pro Max/Start.png
Normal file
|
After Width: | Height: | Size: 328 KiB |
BIN
ScreenShots/iPhone 17 Pro Max/Transfers.png
Normal file
|
After Width: | Height: | Size: 256 KiB |
8
bun.lock
|
|
@ -5,6 +5,8 @@
|
||||||
"": {
|
"": {
|
||||||
"name": "créditmabligopapp",
|
"name": "créditmabligopapp",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/react": "^19.2.10",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|
@ -26,6 +28,10 @@
|
||||||
|
|
||||||
"@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="],
|
"@types/qrcode": ["@types/qrcode@1.5.6", "", { "dependencies": { "@types/node": "*" } }, "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw=="],
|
||||||
|
|
||||||
|
"@types/react": ["@types/react@19.2.10", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw=="],
|
||||||
|
|
||||||
|
"@types/react-dom": ["@types/react-dom@19.2.3", "", { "peerDependencies": { "@types/react": "^19.2.0" } }, "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ=="],
|
||||||
|
|
||||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||||
|
|
||||||
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
"ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||||
|
|
@ -42,6 +48,8 @@
|
||||||
|
|
||||||
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
"cookie": ["cookie@1.1.1", "", {}, "sha512-ei8Aos7ja0weRpFzJnEA9UHJ/7XQmqglbRwnf2ATjcB9Wq874VKH9kfjjirM6UhU2/E5fFYadylyhFldcqSidQ=="],
|
||||||
|
|
||||||
|
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
||||||
|
|
||||||
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
"decamelize": ["decamelize@1.2.0", "", {}, "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA=="],
|
||||||
|
|
||||||
"dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
"dijkstrajs": ["dijkstrajs@1.0.3", "", {}, "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA=="],
|
||||||
|
|
|
||||||
250
front/privacy.css
Normal file
|
|
@ -0,0 +1,250 @@
|
||||||
|
@import url("https://fonts.googleapis.com/css2?family=Fraunces:wght@400;600;700&family=IBM+Plex+Sans:wght@300;400;600&display=swap");
|
||||||
|
|
||||||
|
:root {
|
||||||
|
color-scheme: light;
|
||||||
|
--ink: #1d1b16;
|
||||||
|
--ink-soft: #5b574e;
|
||||||
|
--ink-faint: rgba(29, 27, 22, 0.55);
|
||||||
|
--paper: #f7f1e6;
|
||||||
|
--paper-strong: #fffaf0;
|
||||||
|
--paper-alt: #efe5d4;
|
||||||
|
--accent: #0f766e;
|
||||||
|
--accent-dark: #0f4c43;
|
||||||
|
--accent-soft: rgba(15, 118, 110, 0.12);
|
||||||
|
--border: rgba(29, 27, 22, 0.12);
|
||||||
|
--shadow: rgba(20, 18, 14, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
min-height: 100vh;
|
||||||
|
color: var(--ink);
|
||||||
|
font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at top right, rgba(15, 118, 110, 0.12), transparent 45%),
|
||||||
|
radial-gradient(circle at 20% 20%, rgba(15, 118, 110, 0.08), transparent 40%),
|
||||||
|
linear-gradient(180deg, var(--paper) 0%, var(--paper-alt) 60%, var(--paper) 100%);
|
||||||
|
position: relative;
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::before {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
background-image:
|
||||||
|
linear-gradient(rgba(29, 27, 22, 0.04) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(29, 27, 22, 0.04) 1px, transparent 1px);
|
||||||
|
background-size: 36px 36px;
|
||||||
|
mix-blend-mode: multiply;
|
||||||
|
opacity: 0.45;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body::after {
|
||||||
|
content: "";
|
||||||
|
position: fixed;
|
||||||
|
inset: -20% 0 0 0;
|
||||||
|
background: radial-gradient(circle at 50% 0%, rgba(0, 0, 0, 0.12), transparent 60%);
|
||||||
|
opacity: 0.18;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-privacy #root {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.privacy {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 3.5rem 1.5rem 4rem;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 2rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero {
|
||||||
|
background: var(--paper-strong);
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
border-radius: 28px;
|
||||||
|
padding: 2.5rem 2.2rem;
|
||||||
|
box-shadow: 0 25px 60px var(--shadow);
|
||||||
|
display: grid;
|
||||||
|
gap: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
right: 1.6rem;
|
||||||
|
top: 1.8rem;
|
||||||
|
width: 92px;
|
||||||
|
height: 92px;
|
||||||
|
border-radius: 50%;
|
||||||
|
border: 2px dashed var(--accent-dark);
|
||||||
|
opacity: 0.35;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__top {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.back-link {
|
||||||
|
text-decoration: none;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--accent-dark);
|
||||||
|
font-weight: 600;
|
||||||
|
padding-bottom: 2px;
|
||||||
|
border-bottom: 1px solid rgba(15, 118, 110, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero h1 {
|
||||||
|
font-family: "Fraunces", "Times New Roman", serif;
|
||||||
|
font-size: clamp(2.4rem, 3.2vw + 1rem, 3.6rem);
|
||||||
|
margin: 0;
|
||||||
|
letter-spacing: -0.02em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero p {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.1rem;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
max-width: 32rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero__meta {
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-toggle {
|
||||||
|
background: var(--paper-alt);
|
||||||
|
border-radius: 999px;
|
||||||
|
padding: 0.25rem;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
display: inline-flex;
|
||||||
|
gap: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-toggle button {
|
||||||
|
border: none;
|
||||||
|
background: transparent;
|
||||||
|
padding: 0.4rem 0.9rem;
|
||||||
|
border-radius: 999px;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.lang-toggle button.active {
|
||||||
|
background: var(--accent);
|
||||||
|
color: #fff;
|
||||||
|
box-shadow: 0 10px 24px rgba(15, 118, 110, 0.25);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
display: grid;
|
||||||
|
gap: 0.6rem;
|
||||||
|
background: rgba(255, 255, 255, 0.65);
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 1.4rem 1.6rem;
|
||||||
|
box-shadow: 0 18px 45px rgba(20, 18, 14, 0.08);
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary__label {
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 0.72rem;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
color: var(--ink-faint);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary__text {
|
||||||
|
font-size: 1.05rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--ink);
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy {
|
||||||
|
display: grid;
|
||||||
|
gap: 1.5rem;
|
||||||
|
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy__card {
|
||||||
|
background: var(--paper-strong);
|
||||||
|
border-radius: 20px;
|
||||||
|
border: 1px solid var(--border);
|
||||||
|
padding: 1.5rem;
|
||||||
|
box-shadow: 0 18px 40px rgba(20, 18, 14, 0.08);
|
||||||
|
display: grid;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy__card h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-family: "Fraunces", serif;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.policy__card ul {
|
||||||
|
margin: 0;
|
||||||
|
padding-left: 1.1rem;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
line-height: 1.5;
|
||||||
|
display: grid;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.footer {
|
||||||
|
font-size: 0.95rem;
|
||||||
|
color: var(--ink-soft);
|
||||||
|
border-top: 1px solid var(--border);
|
||||||
|
padding-top: 1.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.reveal {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(18px);
|
||||||
|
animation: reveal 0.7s ease forwards;
|
||||||
|
animation-delay: var(--delay, 0s);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes reveal {
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 700px) {
|
||||||
|
.hero {
|
||||||
|
padding: 2rem 1.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hero::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.summary {
|
||||||
|
padding: 1.2rem 1.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
16
front/privacy.html
Normal file
|
|
@ -0,0 +1,16 @@
|
||||||
|
<!doctype html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta
|
||||||
|
name="viewport"
|
||||||
|
content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
|
||||||
|
/>
|
||||||
|
<title>Negopoly | Privacy Policy</title>
|
||||||
|
<link rel="stylesheet" href="./privacy.css" />
|
||||||
|
</head>
|
||||||
|
<body class="page-privacy">
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="./privacy.tsx"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
169
front/privacy.tsx
Normal file
|
|
@ -0,0 +1,169 @@
|
||||||
|
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;
|
||||||
|
};
|
||||||
|
|
||||||
|
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.",
|
||||||
|
},
|
||||||
|
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.",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
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}>
|
||||||
|
{content.footer}
|
||||||
|
</footer>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const root = createRoot(document.getElementById("root")!);
|
||||||
|
root.render(<Privacy />);
|
||||||
2
index.ts
|
|
@ -1,5 +1,6 @@
|
||||||
import home from "./front/index.html";
|
import home from "./front/index.html";
|
||||||
import play from "./front/play.html";
|
import play from "./front/play.html";
|
||||||
|
import privacy from "./front/privacy.html";
|
||||||
import { apiRoutes } from "./server/api";
|
import { apiRoutes } from "./server/api";
|
||||||
import { handleSocketMessage, registerSocket, unregisterSocket } from "./server/websocket";
|
import { handleSocketMessage, registerSocket, unregisterSocket } from "./server/websocket";
|
||||||
|
|
||||||
|
|
@ -15,6 +16,7 @@ const server = Bun.serve({
|
||||||
headers: { "Content-Type": "application/json" },
|
headers: { "Content-Type": "application/json" },
|
||||||
}),
|
}),
|
||||||
"/": home,
|
"/": home,
|
||||||
|
"/privacy": privacy,
|
||||||
"/play": play,
|
"/play": play,
|
||||||
"/play/:sessionId": play,
|
"/play/:sessionId": play,
|
||||||
"/play/:sessionId/lobby": play,
|
"/play/:sessionId/lobby": play,
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,8 @@
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@types/react": "^19.2.10",
|
||||||
|
"@types/react-dom": "^19.2.3",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
"react": "^19.0.0",
|
"react": "^19.0.0",
|
||||||
"react-dom": "^19.0.0",
|
"react-dom": "^19.0.0",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
// Environment setup & latest features
|
// Environment setup & latest features
|
||||||
"lib": ["ESNext"],
|
"lib": ["ESNext", "DOM"],
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
"module": "Preserve",
|
"module": "Preserve",
|
||||||
"moduleDetection": "force",
|
"moduleDetection": "force",
|
||||||
|
|
|
||||||