Selecting Aero starts a random track from the Frutiger Aero playlist (Bigblamboii's set on SoundCloud); every following track is drawn at random too, so the set shuffles itself. The player is the credit bar from the mock: a 44px glass circle with a note by default, expanding into pause + the track's title + "SoundCloud - artist" (both reported by the widget). - Only Aero ever touches SoundCloud: the embed and the widget API are created while the theme is on and the listener has not turned it off; light/dark request nothing (pinned by a suite check). - The off choice persists (freezmo.aero-music.v1); re-selecting Aero in Settings is fresh consent (applyTheme clears it). - Autoplay rides the theme-selection gesture; after a reload the circle comes up paused (browsers require a gesture) and one tap starts it. - Volume 45; the iframe is 1px and inert, only the audio engine; the credit area collapses back to the circle without stopping the music. - Geometry: clears the phone tab bar, lifts above the chat composer and the wizard bar, steps aside under immersive overlays and while the cookie notice shares the corner. 44px targets on phones. - Cookie policy gained the third-party-embed paragraph it was owed. tests/aero-music-test.mjs (16th suite, stubbed widget API, 21 checks). All suites green, contrast scan clean. Version 0.7.0, SW v92.
308 lines
16 KiB
TypeScript
308 lines
16 KiB
TypeScript
/**
|
|
* Privacy policy, terms of service and cookie policy — in both app languages.
|
|
*
|
|
* Written to describe what this app *actually* does (local-first storage, the
|
|
* key never leaving the browser, opt-in server copies), not a template. The
|
|
* pages that render this live in app/privacy, app/terms and app/cookies; the
|
|
* cookie banner links to the same text. Keep them in step with the code.
|
|
*/
|
|
|
|
export type LegalSection = {
|
|
heading: string;
|
|
paragraphs: string[];
|
|
list?: string[];
|
|
};
|
|
|
|
export type LegalDocument = {
|
|
title: string;
|
|
updated: string;
|
|
intro: string;
|
|
sections: LegalSection[];
|
|
footer: string;
|
|
};
|
|
|
|
const UPDATED = "October 2026";
|
|
|
|
const PRIVACY_EN: LegalDocument = {
|
|
title: "Privacy policy",
|
|
updated: UPDATED,
|
|
intro:
|
|
"Freezmo is a self-hosted study app with no accounts. This page describes exactly what happens to your data — nothing here is boilerplate.",
|
|
sections: [
|
|
{
|
|
heading: "Everything lives in your browser",
|
|
paragraphs: [
|
|
"Your decks, folders, homework, chats, study history and settings are stored in this browser (localStorage and IndexedDB). Nothing is uploaded unless you switch on one of the opt-ins below.",
|
|
"Clearing the site data deletes it. Settings → Export all data gives you a complete copy at any time.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Your API keys",
|
|
paragraphs: [
|
|
"Every model key — the Ollama one and any cloud provider keys you add under Settings → Providers — is kept in this browser only. A key is sent to this Freezmo server for the single model call you asked for, forwarded to that provider, and forgotten: never written to disk, never logged, never part of a backup, sync payload or share link.",
|
|
],
|
|
},
|
|
{
|
|
heading: "What the server stores — only if you opt in",
|
|
paragraphs: ["There is no other server-side data. Each of these is off until you turn it on:"],
|
|
list: [
|
|
"Sync — an encrypted blob the server cannot read (the key stays in your browser). Revocable from Settings → Delete synced data.",
|
|
"Reminders — the push endpoint your browser gave us, its keys and a timestamp. No personal data, no history.",
|
|
"Reported questions — the question text, the deck and activity ids and a timestamp. These are how bad AI cards get fixed; they are the one thing an operator can read.",
|
|
],
|
|
},
|
|
{
|
|
heading: "What the server does not store",
|
|
paragraphs: [
|
|
"No accounts, no email addresses, no analytics, no cookies, no tracking. The rate limiter keeps a client IP address in memory for a few minutes to stop floods and never writes it to disk.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Third parties",
|
|
paragraphs: [
|
|
"Model calls go to the Ollama endpoint this instance is configured for, or — when you pick a cloud model — to that provider (OpenAI, Anthropic, Google Gemini, DeepSeek, Groq, OpenRouter or your own endpoint) with the key from your browser. The provider's own privacy terms apply to those calls. Reminders travel through your browser's push service (Google, Mozilla or Apple). There is nothing else: no ads, no analytics, no embeds.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Your choices",
|
|
paragraphs: [
|
|
"Delete the local copy by clearing the site data (or Settings). Revoke the synced copy from Settings. Turn reminders off from Settings. If you want a reported question removed from the operator's file, ask the person running this instance — it is their file.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Plain-language description of how this app handles data — not legal advice.",
|
|
};
|
|
|
|
const PRIVACY_CS: LegalDocument = {
|
|
title: "Zásady ochrany soukromí",
|
|
updated: "říjen 2026",
|
|
intro:
|
|
"Freezmo je samoobslužná studijní aplikace bez účtů. Tahle stránka popisuje, co se přesně děje s tvými daty — nic z toho není šablona.",
|
|
sections: [
|
|
{
|
|
heading: "Všechno zůstává v tvém prohlížeči",
|
|
paragraphs: [
|
|
"Sady, složky, úkoly, chaty, historie učení i nastavení jsou uložené v tomto prohlížeči (localStorage a IndexedDB). Nic se neodesílá, dokud nezapneš některou z níže uvedených voleb.",
|
|
"Smazáním dat stránky je smažeš. Nastavení → Exportovat všechna data ti kdykoli dá úplnou kopii.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Tvoje API klíče",
|
|
paragraphs: [
|
|
"Každý klíč k modelu — ten ollamový i klíče cloudových poskytovatelů z Nastavení → Poskytovatelé — je uložený jen v tomto prohlížeči. Posílá se tomuto serveru Freezmo pro jeden požadovaný modelový dotaz, server ho předá danému poskytovateli a zapomene ho: nikdy se neukládá na disk, neloguje se a není součástí zálohy, synchronizace ani sdíleného odkazu.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Co server ukládá — jen když to zapneš",
|
|
paragraphs: ["Jiná data na serveru nejsou. Každá z těchhle věcí je vypnutá, dokud ji nezapneš:"],
|
|
list: [
|
|
"Synchronizace — zašifrovaný blob, který server nedokáže přečíst (klíč zůstává v prohlížeči). Zrušitelný v Nastavení → Smazat synchronizovaná data.",
|
|
"Připomínky — push endpoint, který ti dal prohlížeč, jeho klíče a časové razítko. Žádná osobní data, žádná historie.",
|
|
"Nahlášené otázky — text otázky, ID sady a aktivity a časové razítko. Díky nim se opravují špatné AI kartičky; je to jediná věc, kterou provozovatel může číst.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Co server neukládá",
|
|
paragraphs: [
|
|
"Žádné účty, e-maily, analytiku, cookies ani sledování. Omezovač rychlosti drží IP adresu klienta pár minut v paměti, aby zastavil záplavy požadavků, a nikdy ji nezapisuje na disk.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Třetí strany",
|
|
paragraphs: [
|
|
"Modelové dotazy jdou na Ollama endpoint, který je tato instance nastavená používat, nebo — když zvolíš cloudový model — danému poskytovateli (OpenAI, Anthropic, Google Gemini, DeepSeek, Groq, OpenRouter nebo vlastní endpoint) s klíčem z tvého prohlížeče; na tato volání platí podmínky daného poskytovatele. Připomínky putují přes push službu tvého prohlížeče (Google, Mozilla nebo Apple). Nic jiného tu není: žádné reklamy, analytika ani vložený obsah.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Tvoje volby",
|
|
paragraphs: [
|
|
"Lokální kopii smažeš smazáním dat stránky (nebo v Nastavení). Synchronizovanou kopii zrušíš v Nastavení. Připomínky vypneš v Nastavení. Pokud chceš, aby provozovatel odstranil nahlášenou otázku ze svého souboru, řekni mu to — ten soubor je jeho.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Popis v lidské řeči, jak aplikace nakládá s daty — není právní rada.",
|
|
};
|
|
|
|
const TERMS_EN: LegalDocument = {
|
|
title: "Terms of service",
|
|
updated: UPDATED,
|
|
intro:
|
|
"Freezmo is free software (GPL-3.0-only) that someone is hosting for you — usually a friend or yourself. These are the house rules for using that instance.",
|
|
sections: [
|
|
{
|
|
heading: "The software comes as-is",
|
|
paragraphs: [
|
|
"Freezmo is provided under the GNU General Public License v3 only, without warranty of any kind to the extent the law allows. AI output can be wrong: read it before you hand it in — that is what the note under every card is for.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Your key, your usage",
|
|
paragraphs: [
|
|
"You bring your own API keys. Limits, costs and account standing are between you and the provider (Ollama or a cloud one you added). Never paste a key you do not own, and keep your own key to yourself.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Fair use of the instance",
|
|
paragraphs: [
|
|
"Don't flood it, don't try to break it and don't use it to attack anything else. The operator can rate-limit, block or shut the instance down at any time, and the app already refuses obvious abuse.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Your content",
|
|
paragraphs: [
|
|
"Your notes, decks, homework and chats are yours. They stay in your browser, so the operator cannot read them — the only exception is a question you report on purpose (see the privacy policy).",
|
|
],
|
|
},
|
|
{
|
|
heading: "Availability and changes",
|
|
paragraphs: [
|
|
"This instance is a courtesy, with no uptime promise. The app and these terms may change; the current version always lives in the app, linked from Settings.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Liability",
|
|
paragraphs: [
|
|
"To the extent permitted by law, the operator and the authors are not liable for anything this app does or fails to do — including lost study time, lost data or a wrong answer in your homework.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Plain-language terms for a self-hosted app — not legal advice.",
|
|
};
|
|
|
|
const TERMS_CS: LegalDocument = {
|
|
title: "Podmínky použití",
|
|
updated: "říjen 2026",
|
|
intro:
|
|
"Freezmo je svobodný software (GPL-3.0-only), který ti někdo hostuje — většinou kamarád nebo ty sám. Tohle jsou pravidla pro používání téhle instance.",
|
|
sections: [
|
|
{
|
|
heading: "Software je tak, jak je",
|
|
paragraphs: [
|
|
"Freezmo se poskytuje pod licencí GNU General Public License v3 only, bez jakékoli záruky v rozsahu, který zákon dovoluje. AI výstup může být špatně: než ho odevzdáš, přečti si ho — od toho je poznámka pod každou kartičkou.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Tvůj klíč, tvoje spotřeba",
|
|
paragraphs: [
|
|
"Používáš vlastní API klíče. Limity, náklady a stav účtu jsou mezi tebou a daným poskytovatelem (Ollamou nebo cloudovým, kterého jsi přidal). Nikdy nevkládej klíč, který ti nepatří, a ten svůj nikomu nedávej.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Férové používání instance",
|
|
paragraphs: [
|
|
"Nezahlcuj ji, nezkoušej ji rozbít a nepoužívej ji k útoku na něco jiného. Provozovatel může kdykoli omezit rychlost, zablokovat přístup nebo instanci vypnout; aplikace už sama odmítá zjevné zneužití.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Tvůj obsah",
|
|
paragraphs: [
|
|
"Poznámky, sady, úkoly a chaty jsou tvoje. Zůstávají v tvém prohlížeči, takže je provozovatel nemůže číst — jediná výjimka je otázka, kterou sám nahlásíš (viz zásady soukromí).",
|
|
],
|
|
},
|
|
{
|
|
heading: "Dostupnost a změny",
|
|
paragraphs: [
|
|
"Instance je zdvořilost, bez slibu dostupnosti. Aplikace i tyhle podmínky se mohou měnit; aktuální verze je vždy v aplikaci, odkaz v Nastavení.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Odpovědnost",
|
|
paragraphs: [
|
|
"V rozsahu, který zákon dovoluje, provozovatel ani autoři neodpovídají za nic, co aplikace udělá nebo neudělá — včetně ztraceného času na učení, ztracených dat nebo špatné odpovědi v úkolu.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Podmínky v lidské řeči pro samoobslužnou aplikaci — nejsou právní rada.",
|
|
};
|
|
|
|
const COOKIES_EN: LegalDocument = {
|
|
title: "Cookie policy",
|
|
updated: UPDATED,
|
|
intro: "Short version: Freezmo sets no cookies at all. This page exists because the law asks for one, and because it is worth being explicit.",
|
|
sections: [
|
|
{
|
|
heading: "No cookies",
|
|
paragraphs: [
|
|
"The server sends no Set-Cookie header and the app never touches document.cookie. There are no pixels and no tag managers.",
|
|
],
|
|
},
|
|
{
|
|
heading: "The one third-party embed: Aero music",
|
|
paragraphs: [
|
|
"The Frutiger Aero theme can play its soundtrack from a SoundCloud playlist. That player is served by soundcloud.com, which may set its own cookies or storage there; Freezmo itself still sets none, and the embed only loads while the Aero theme is on and the player has not been turned off. Pause or close the player (or use another theme) and nothing from SoundCloud is requested.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Local storage instead",
|
|
paragraphs: [
|
|
"The app keeps your data in localStorage and IndexedDB — the same idea as a cookie, except it is never sent to the server automatically. It is strictly necessary for the app to work (it *is* the app's storage), which is why it does not need consent; the banner only tells you it happens.",
|
|
],
|
|
list: [
|
|
"Settings: language, theme, daily goal, haptics, chat notes preference.",
|
|
"Content: decks, folders, homework, chats, study history and schedule.",
|
|
"Bookkeeping: the sync id, the last-synced time, which questions you reported, whether you dismissed this banner.",
|
|
],
|
|
},
|
|
{
|
|
heading: "No tracking, no analytics, no ads",
|
|
paragraphs: ["Nothing measures you, and nothing is sold. The only counters the app keeps are the rate limits on the server, held in memory."],
|
|
},
|
|
{
|
|
heading: "Clearing it",
|
|
paragraphs: [
|
|
"Clear the site data in your browser to wipe the local copy, or use Settings → Export all data first if you want a backup. The opt-in server copies (sync, reminders, reports) are described in the privacy policy.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Plain-language description of what this app stores in your browser — not legal advice.",
|
|
};
|
|
|
|
const COOKIES_CS: LegalDocument = {
|
|
title: "Zásady používání cookies",
|
|
updated: "říjen 2026",
|
|
intro: "Krátce: Freezmo nepoužívá žádné cookies. Tahle stránka existuje proto, že to zákon vyžaduje — a protože je dobré to říct napřímo.",
|
|
sections: [
|
|
{
|
|
heading: "Žádné cookies",
|
|
paragraphs: [
|
|
"Server neposílá hlavičku Set-Cookie a aplikace se nikdy nedotkne document.cookie. Nejsou tu žádné pixely ani správci značek.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Jediný vložený prvek třetí strany: hudba v Aeru",
|
|
paragraphs: [
|
|
"Téma Frutiger Aero umí přehrát svůj soundtrack ze SoundCloud playlistu. Přehrávač posílá soundcloud.com, který si tam může nastavit vlastní cookies nebo úložiště; Freezmo samo pořád žádné nepoužívá a prvek se načte jen tehdy, když je zapnuté téma Aero a přehrávač nebyl vypnutý. Pozastav nebo zavři přehrávač (nebo přepni téma) a ze SoundCloudu se nestahuje nic.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Místo toho lokální úložiště",
|
|
paragraphs: [
|
|
"Aplikace drží tvá data v localStorage a IndexedDB — stejná myšlenka jako cookie, jen se nikdy neposílají serveru automaticky. Je to nezbytné pro fungování aplikace (je to *její* úložiště), a proto to nepotřebuje souhlas; banner ti to jen oznamuje.",
|
|
],
|
|
list: [
|
|
"Nastavení: jazyk, téma, denní cíl, haptika, preference poznámek pro chat.",
|
|
"Obsah: sady, složky, úkoly, chaty, historie a plán učení.",
|
|
"Evidence: sync ID, čas poslední synchronizace, které otázky jsi nahlásil, jestli jsi zavřel tenhle banner.",
|
|
],
|
|
},
|
|
{
|
|
heading: "Žádné sledování, analytika ani reklamy",
|
|
paragraphs: ["Nic tě neměří a nic se neprodává. Jediné počítadla jsou limity rychlosti na serveru, držené v paměti."],
|
|
},
|
|
{
|
|
heading: "Jak to smazat",
|
|
paragraphs: [
|
|
"Smaž data stránky v prohlížeči, nebo si nejdřív udělej kopii přes Nastavení → Exportovat všechna data. Volitelné kopie na serveru (synchronizace, připomínky, hlášení) popisují zásady soukromí.",
|
|
],
|
|
},
|
|
],
|
|
footer: "Popis v lidské řeči, co aplikace ukládá v prohlížeči — není právní rada.",
|
|
};
|
|
|
|
export type LegalSlug = "privacy" | "terms" | "cookies";
|
|
|
|
export const LEGAL: Record<LegalSlug, { en: LegalDocument; cs: LegalDocument }> = {
|
|
privacy: { en: PRIVACY_EN, cs: PRIVACY_CS },
|
|
terms: { en: TERMS_EN, cs: TERMS_CS },
|
|
cookies: { en: COOKIES_EN, cs: COOKIES_CS },
|
|
};
|