From 200d0841c5b85cf904c28ef71392d689fe2b8eb3 Mon Sep 17 00:00:00 2001 From: Markus Neusinger <2921697+MarkusNeusinger@users.noreply.github.com> Date: Mon, 12 Jan 2026 23:02:36 +0100 Subject: [PATCH 1/5] feat: add legal page and link in footer - Implement LegalPage component with legal notice and privacy policy - Add link to legal page in footer - Update router to include legal route --- app/src/components/Footer.tsx | 13 ++ app/src/hooks/useUrlSync.ts | 2 +- app/src/pages/LegalPage.tsx | 292 ++++++++++++++++++++++++++++++++++ app/src/router.tsx | 2 + 4 files changed, 308 insertions(+), 1 deletion(-) create mode 100644 app/src/pages/LegalPage.tsx diff --git a/app/src/components/Footer.tsx b/app/src/components/Footer.tsx index 1125c90a30..05c193b974 100644 --- a/app/src/components/Footer.tsx +++ b/app/src/components/Footer.tsx @@ -1,5 +1,6 @@ import Box from '@mui/material/Box'; import Link from '@mui/material/Link'; +import { Link as RouterLink } from 'react-router-dom'; import { GITHUB_URL } from '../constants'; interface FooterProps { @@ -63,6 +64,18 @@ export function Footer({ onTrackEvent, selectedSpec, selectedLibrary }: FooterPr > stats + · + + legal + ); diff --git a/app/src/hooks/useUrlSync.ts b/app/src/hooks/useUrlSync.ts index 119e8700e5..95fd52f2b5 100644 --- a/app/src/hooks/useUrlSync.ts +++ b/app/src/hooks/useUrlSync.ts @@ -6,7 +6,7 @@ import { useEffect } from 'react'; -import type { FilterCategory, ActiveFilters } from '../types'; +import type { ActiveFilters } from '../types'; import { FILTER_CATEGORIES } from '../types'; /** diff --git a/app/src/pages/LegalPage.tsx b/app/src/pages/LegalPage.tsx new file mode 100644 index 0000000000..437a5ba448 --- /dev/null +++ b/app/src/pages/LegalPage.tsx @@ -0,0 +1,292 @@ +import { useEffect } from 'react'; +import { Helmet } from 'react-helmet-async'; +import Box from '@mui/material/Box'; +import Typography from '@mui/material/Typography'; +import Link from '@mui/material/Link'; +import Paper from '@mui/material/Paper'; +import Table from '@mui/material/Table'; +import TableBody from '@mui/material/TableBody'; +import TableCell from '@mui/material/TableCell'; +import TableRow from '@mui/material/TableRow'; + +import { useAnalytics } from '../hooks'; +import { Breadcrumb, Footer } from '../components'; +import { GITHUB_URL } from '../constants'; + +export function LegalPage() { + const { trackPageview, trackEvent } = useAnalytics(); + + useEffect(() => { + trackPageview('/legal'); + }, [trackPageview]); + + const headingStyle = { + fontFamily: '"MonoLisa", monospace', + fontWeight: 600, + fontSize: '1rem', + color: '#1f2937', + mb: 2, + }; + + const subheadingStyle = { + fontFamily: '"MonoLisa", monospace', + fontWeight: 600, + fontSize: '1.1rem', + color: '#374151', + mt: 3, + mb: 1, + }; + + const textStyle = { + fontFamily: '"MonoLisa", monospace', + fontSize: '0.9rem', + color: '#4b5563', + lineHeight: 1.8, + mb: 2, + }; + + const tableStyle = { + '& .MuiTableCell-root': { + fontFamily: '"MonoLisa", monospace', + fontSize: '0.85rem', + color: '#4b5563', + borderBottom: '1px solid #f3f4f6', + py: 1.5, + px: 2, + }, + '& .MuiTableCell-root:first-of-type': { + fontWeight: 500, + color: '#374151', + width: '40%', + }, + }; + + return ( + <> + + legal | pyplots.ai + + + + + + + + + {/* Legal Notice */} + + + Legal Notice + + + + Operator +
+ Markus Neusinger +
+ Visp, Switzerland +
+ + + Contact +
+ Email:{' '} + + admin@pyplots.ai + +
+ + + Disclaimer +
+ This is a personal portfolio project, not a commercial service. The content is provided "as is" + without warranty of any kind. Code examples are for educational purposes and should be reviewed before use + in production environments. +
+
+ + {/* Privacy Policy */} + + + Privacy Policy + + + Data Controller + Markus Neusinger (see Legal Notice above) + + What We Collect + + Anonymized Analytics: We use Plausible Analytics, a privacy-focused analytics tool. It + collects no personal data, uses no cookies, and does not track you across websites. All data is aggregated + and anonymous. + + + Server Logs: Temporary technical logs (anonymized IP addresses) are retained for up to 30 + days for security and debugging purposes. + + + What We Do NOT Collect + + • No user accounts or personal profiles +
+ • No personal data (names, emails, etc.) +
+ • No cookies (except technically necessary session cookies) +
No AI training: Your interactions are not used to train AI models +
+ + + Analytics: Filter selections and search terms are tracked anonymously via Plausible + Analytics for usage statistics. This data is aggregated and cannot be linked to individual users. + + + + Contributors: If you suggest a plot type via GitHub, your GitHub username may be credited + in the specification metadata. This is public information from your GitHub profile. + + + Hosting & Third Parties + All services are hosted in the EU (Netherlands, europe-west4): + + + + Hosting + Google Cloud Run (Netherlands) + + + Database + Google Cloud SQL (Netherlands) + + + Storage + Google Cloud Storage (Netherlands) + + + Analytics + Plausible Analytics (EU, proxied) + + +
+ + Your Rights + + Under GDPR and Swiss DSG, you have the right to access, rectification, erasure, and data portability. Since + we do not store personal data, there is typically nothing to delete or export. For questions, contact{' '} + + admin@pyplots.ai + + . + +
+ + {/* Transparency */} + + + Transparency + + + + This project is open source and committed to full transparency about how it works and what it costs. + + + Technology Stack + + + + Typography + + + MonoLisa + {' '} + by Marcus Sterz + + + + Frontend + React 19, Vite, MUI 7, TypeScript + + + Backend + Python 3.13, FastAPI, SQLAlchemy + + + Database + PostgreSQL + + + Hosting + Google Cloud Run (Netherlands) + + + Storage + Google Cloud Storage + + + Analytics + Plausible (privacy-friendly, no cookies) + + + AI + Anthropic Claude via Claude Max (code generation & review) + + +
+ + Source Code + + The entire codebase is publicly available under the MIT License: +
+ + github.com/MarkusNeusinger/pyplots + +
+ + Monthly Costs (approximate) + + + + Cloud Run + ~$15 + + + Cloud SQL + ~$40 + + + Cloud Storage + ~$5 + + + Plausible Analytics + ~$9 + + + Domain (.ai) + ~$8 + + + Claude Max + ~$100 (shared) + + + + Total + + + ~$177/month + + + +
+ + Claude Max subscription is shared across projects. +
+ All costs are currently covered privately. Last updated: January 2026. +
+
+
+ +