Skip to content

Commit d89343e

Browse files
committed
privacy statement
1 parent e98dcbe commit d89343e

4 files changed

Lines changed: 145 additions & 1 deletion

File tree

src/App.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { LevelOverview } from "./components/LevelOverview";
66
import { LevelSummaryScreen } from "./components/LevelSummaryScreen";
77
import { MemorizationScreen } from "./components/MemorizationScreen";
88
import { MobileMenuDialog } from "./components/MobileMenuDialog";
9+
import { PrivacyDialog } from "./components/PrivacyDialog";
910
import { ProblemResultScreen } from "./components/ProblemResultScreen";
1011
import { ReconstructionScreen } from "./components/ReconstructionScreen";
1112
import { SettingsDialog } from "./components/SettingsDialog";
@@ -228,6 +229,7 @@ export default function App() {
228229
const [summarySaved, setSummarySaved] = useState(false);
229230
const [pendingExitRoute, setPendingExitRoute] = useState<Route | null>(null);
230231
const [settingsOpen, setSettingsOpen] = useState(false);
232+
const [privacyOpen, setPrivacyOpen] = useState(false);
231233
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
232234
const sessionRef = useRef<TrainingSession | null>(null);
233235
const appStateRef = useRef<AppState>(appState);
@@ -774,6 +776,12 @@ export default function App() {
774776
) : null}
775777
</main>
776778

779+
<footer className="app-footer">
780+
<button type="button" className="footer-link" onClick={() => setPrivacyOpen(true)}>
781+
Privacy / Datenschutz
782+
</button>
783+
</footer>
784+
777785
<SettingsDialog
778786
open={settingsOpen}
779787
theme={settings.theme}
@@ -799,7 +807,9 @@ export default function App() {
799807
onHome={backHome}
800808
onChooseLevel={chooseSelectedLevel}
801809
onOpenSettings={() => setSettingsOpen(true)}
810+
onOpenPrivacy={() => setPrivacyOpen(true)}
802811
/>
812+
<PrivacyDialog open={privacyOpen} onClose={() => setPrivacyOpen(false)} />
803813
<ConfirmDialog
804814
open={pendingExitRoute !== null}
805815
title="Leave training?"

src/components/MobileMenuDialog.tsx

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { FaCog, FaHome, FaLayerGroup, FaTimes } from "react-icons/fa";
1+
import { FaCog, FaHome, FaLayerGroup, FaShieldAlt, FaTimes } from "react-icons/fa";
22
import { useModalDismiss } from "../hooks/useModalDismiss";
33

44
type MobileMenuDialogProps = {
@@ -8,6 +8,7 @@ type MobileMenuDialogProps = {
88
onHome: () => void;
99
onChooseLevel: () => void;
1010
onOpenSettings: () => void;
11+
onOpenPrivacy: () => void;
1112
};
1213

1314
export function MobileMenuDialog({
@@ -17,6 +18,7 @@ export function MobileMenuDialog({
1718
onHome,
1819
onChooseLevel,
1920
onOpenSettings,
21+
onOpenPrivacy,
2022
}: MobileMenuDialogProps) {
2123
const { handleBackdropClick } = useModalDismiss(open, onClose);
2224

@@ -39,6 +41,11 @@ export function MobileMenuDialog({
3941
onOpenSettings();
4042
}
4143

44+
function handlePrivacy(): void {
45+
onClose();
46+
onOpenPrivacy();
47+
}
48+
4249
return (
4350
<div className="modal-backdrop" role="presentation" onMouseDown={handleBackdropClick}>
4451
<section
@@ -71,6 +78,10 @@ export function MobileMenuDialog({
7178
<FaCog aria-hidden="true" />
7279
Settings
7380
</button>
81+
<button type="button" className="btn-with-icon" onClick={handlePrivacy}>
82+
<FaShieldAlt aria-hidden="true" />
83+
Privacy / Datenschutz
84+
</button>
7485
</div>
7586
</section>
7687
</div>

src/components/PrivacyDialog.tsx

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import { FaTimes } from "react-icons/fa";
2+
import { useModalDismiss } from "../hooks/useModalDismiss";
3+
4+
type PrivacyDialogProps = {
5+
open: boolean;
6+
onClose: () => void;
7+
};
8+
9+
export function PrivacyDialog({ open, onClose }: PrivacyDialogProps) {
10+
const { handleBackdropClick } = useModalDismiss(open, onClose);
11+
12+
if (!open) {
13+
return null;
14+
}
15+
16+
return (
17+
<div className="modal-backdrop" role="presentation" onMouseDown={handleBackdropClick}>
18+
<section
19+
className="modal-dialog privacy-dialog"
20+
role="dialog"
21+
aria-modal="true"
22+
aria-labelledby="privacy-dialog-title"
23+
>
24+
<button
25+
type="button"
26+
className="modal-close"
27+
onClick={onClose}
28+
aria-label="Close privacy notice"
29+
>
30+
<FaTimes aria-hidden="true" />
31+
</button>
32+
33+
<h2 id="privacy-dialog-title">Privacy / Datenschutz</h2>
34+
35+
<div className="privacy-content">
36+
<p>
37+
This app is a static GitHub Pages site. It has no account system, no backend,
38+
no advertising, and no analytics.
39+
</p>
40+
41+
<h3>Local data</h3>
42+
<p>
43+
Training progress and settings are stored only in your browser via
44+
localStorage. They are not sent to this app&apos;s developer.
45+
</p>
46+
47+
<h3>Hosting</h3>
48+
<p>
49+
The site is hosted by GitHub Pages. When you open it, GitHub may process
50+
technical data such as IP address, browser information, request time, and
51+
requested files to deliver and secure the site.
52+
</p>
53+
54+
<h3>Contact</h3>
55+
<p>
56+
Responsible: Leif Battermann. Contact is available through the{" "}
57+
<a
58+
href="https://github.com/battermann/go-visualization-trainer"
59+
target="_blank"
60+
rel="noreferrer"
61+
>
62+
GitHub repository
63+
</a>
64+
.
65+
</p>
66+
</div>
67+
68+
<div className="button-row modal-actions">
69+
<button type="button" className="primary" onClick={onClose}>
70+
Done
71+
</button>
72+
</div>
73+
</section>
74+
</div>
75+
);
76+
}

src/styles.css

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,27 @@ body {
7979
display: block;
8080
}
8181

82+
.app-footer {
83+
display: flex;
84+
justify-content: center;
85+
margin-top: 18px;
86+
}
87+
88+
.footer-link {
89+
padding: 4px 6px;
90+
border-color: transparent;
91+
background: transparent;
92+
color: #9faabd;
93+
font-size: 0.9rem;
94+
}
95+
96+
.footer-link:not(:disabled):hover {
97+
border-color: transparent;
98+
background: transparent;
99+
color: #dcecff;
100+
text-decoration: underline;
101+
}
102+
82103
.panel {
83104
background:
84105
radial-gradient(circle at 92% 0%, rgba(224, 176, 109, 0.08), transparent 26%),
@@ -921,6 +942,25 @@ a:hover {
921942
width: min(100%, 560px);
922943
}
923944

945+
.privacy-dialog {
946+
width: min(100%, 560px);
947+
}
948+
949+
.privacy-content {
950+
display: grid;
951+
gap: 10px;
952+
}
953+
954+
.privacy-content h3 {
955+
margin: 6px 0 0;
956+
color: #f3f6fb;
957+
font-size: 0.96rem;
958+
}
959+
960+
.privacy-content p {
961+
margin: 0;
962+
}
963+
924964
.settings-section {
925965
padding: 14px 0;
926966
border-top: 1px solid #2c3644;
@@ -1190,6 +1230,7 @@ a:hover {
11901230
:root.theme-light .home-section-note,
11911231
:root.theme-light .source-credit,
11921232
:root.theme-light .empty-state,
1233+
:root.theme-light .footer-link,
11931234
:root.theme-light .timer,
11941235
:root.theme-light .color-toggle button,
11951236
:root.theme-light .board-wrap h3,
@@ -1217,6 +1258,7 @@ a:hover {
12171258
:root.theme-light .screen-facts dt,
12181259
:root.theme-light .progress-label,
12191260
:root.theme-light .recommendation-band span,
1261+
:root.theme-light .privacy-content h3,
12201262
:root.theme-light .settings-section h3 {
12211263
color: #586578;
12221264
}
@@ -1226,6 +1268,7 @@ a:hover {
12261268
:root.theme-light .recommendation-band strong,
12271269
:root.theme-light .timer strong,
12281270
:root.theme-light .score-callout strong,
1271+
:root.theme-light .privacy-dialog h2,
12291272
:root.theme-light .settings-section h3 {
12301273
color: #20242a;
12311274
}
@@ -1340,6 +1383,10 @@ a:hover {
13401383
color: #174f82;
13411384
}
13421385

1386+
:root.theme-light .footer-link:not(:disabled):hover {
1387+
color: #244e77;
1388+
}
1389+
13431390
:root.board-style-original .go-board {
13441391
background: #d7a864;
13451392
border: none;

0 commit comments

Comments
 (0)