Add Datenschutzerklärung, two blog posts, and a /uses colophon
- datenschutz.astro: DSGVO privacy policy (server logfiles, Hetzner as Auftragsverarbeiter per Art. 28, cookieless Umami, self-hosted fonts, data-subject rights, supervisory-authority complaint). - consts.ts: extract shared LEGAL entity data; Impressum now reads from it. - blog: "Why my calendar app has no internet permission" and "Open standards as a constraint, not a checkbox". - uses.astro: colophon of the site stack, Floret apps, and self-hosted infra. - Footer: add Uses + Datenschutz links alongside Impressum. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,10 @@ const year = new Date().getFullYear();
|
|||||||
|
|
||||||
<footer class="site-footer">
|
<footer class="site-footer">
|
||||||
<div class="site-footer__inner">
|
<div class="site-footer__inner">
|
||||||
<p>© {year} {SITE.author} · <a href="/impressum">Impressum</a></p>
|
<p>
|
||||||
|
© {year} {SITE.author} · <a href="/uses">Uses</a> ·
|
||||||
|
<a href="/impressum">Impressum</a> · <a href="/datenschutz">Datenschutz</a>
|
||||||
|
</p>
|
||||||
{
|
{
|
||||||
SOCIALS.length > 0 && (
|
SOCIALS.length > 0 && (
|
||||||
<nav aria-label="Social" class="socials">
|
<nav aria-label="Social" class="socials">
|
||||||
|
|||||||
@@ -8,6 +8,18 @@ export const SITE = {
|
|||||||
locale: 'en_US',
|
locale: 'en_US',
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
// Legal entity details — single source of truth for the Impressum and
|
||||||
|
// Datenschutzerklärung. Registered Einzelunternehmen (Kleinunternehmer per
|
||||||
|
// § 19 UStG; no VAT ID, not in the Handelsregister). `phone` renders only if set.
|
||||||
|
export const LEGAL = {
|
||||||
|
business: 'IT-Dienstleister | Jean-Luc Makiola', // full Gewerbe name (§ 5 DDG)
|
||||||
|
person: SITE.author, // natural person responsible — § 18 Abs. 2 MStV
|
||||||
|
street: 'Mahlerstraße 10',
|
||||||
|
city: '14772 Brandenburg an der Havel',
|
||||||
|
email: 'mail@jeanlucmakiola.de',
|
||||||
|
phone: '',
|
||||||
|
} as const;
|
||||||
|
|
||||||
// Social / external links shown in the footer. `icon` is an Iconify name.
|
// Social / external links shown in the footer. `icon` is an Iconify name.
|
||||||
export const SOCIALS: { label: string; href: string; icon: string }[] = [
|
export const SOCIALS: { label: string; href: string; icon: string }[] = [
|
||||||
{ label: 'Gitea', href: 'https://gitea.jeanlucmakiola.de/makiolaj', icon: 'simple-icons:gitea' },
|
{ label: 'Gitea', href: 'https://gitea.jeanlucmakiola.de/makiolaj', icon: 'simple-icons:gitea' },
|
||||||
|
|||||||
67
src/content/blog/no-internet-permission.md
Normal file
67
src/content/blog/no-internet-permission.md
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
---
|
||||||
|
title: Why my calendar app has no internet permission
|
||||||
|
description: >-
|
||||||
|
Calendula can't talk to the network — and that's the whole design. A look at
|
||||||
|
building on Android's CalendarContract instead of reinventing a sync stack.
|
||||||
|
pubDate: 2026-06-28
|
||||||
|
tags: [android, architecture, calendula]
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
Open Calendula's manifest and you'll notice something missing: there is no
|
||||||
|
`android.permission.INTERNET`. The app physically *cannot* reach the network.
|
||||||
|
For a calendar — a category of app practically synonymous with cloud accounts —
|
||||||
|
that sounds like a missing feature. It's the opposite. It's the design.
|
||||||
|
|
||||||
|
## The usual shape of a calendar app
|
||||||
|
|
||||||
|
Most calendar apps own their data. They sign you into an account, pull events
|
||||||
|
down over the provider's API, cache them in a private database, and reconcile
|
||||||
|
changes with their own sync engine. That sync stack is the hard part: conflict
|
||||||
|
resolution, recurring-event expansion, time zones, retries, token refresh. It's
|
||||||
|
also the part that locks you in — your events live in *their* schema, reachable
|
||||||
|
only through *their* app.
|
||||||
|
|
||||||
|
## The other option Android already gives you
|
||||||
|
|
||||||
|
Android ships a system calendar database, exposed through
|
||||||
|
[`CalendarContract`](https://developer.android.com/reference/android/provider/CalendarContract).
|
||||||
|
Anything synced to your device lands there: a CalDAV account via
|
||||||
|
[DAVx5](https://www.davx5.com/), your Google calendar, a local on-device
|
||||||
|
calendar, a read-only WebCal subscription. They all show up through the same
|
||||||
|
content provider, with the same columns.
|
||||||
|
|
||||||
|
Calendula is a pure front-end over that provider. It reads events through
|
||||||
|
`CalendarContract`, and when you create or edit something, it writes straight
|
||||||
|
back. Whatever sync adapter put the calendar on your device picks the change up
|
||||||
|
and pushes it out. There is **no own database and no reinvented sync stack** —
|
||||||
|
so there is nothing for the app to phone home about.
|
||||||
|
|
||||||
|
## What you get for free
|
||||||
|
|
||||||
|
Dropping the network permission isn't a sacrifice; it's what falls out of the
|
||||||
|
architecture:
|
||||||
|
|
||||||
|
- **Your data stays yours, and stays portable.** Events live in the platform's
|
||||||
|
store and in your CalDAV account — not in a schema only Calendula understands.
|
||||||
|
- **Privacy is structural, not a promise.** Zero telemetry and zero analytics
|
||||||
|
are easy to claim. *No internet permission* is enforced by the OS: even if I
|
||||||
|
wanted to exfiltrate your schedule, the app couldn't.
|
||||||
|
- **Reminders still work** — Calendula delivers them itself as notifications,
|
||||||
|
because Android delegates reminder delivery to the installed calendar app.
|
||||||
|
- **Any account "just appears."** Add a new CalDAV account in DAVx5 and it
|
||||||
|
surfaces in Calendula with no integration work, because the integration point
|
||||||
|
is the OS, not a vendor API.
|
||||||
|
|
||||||
|
## The trade-off, stated honestly
|
||||||
|
|
||||||
|
A front-end can only be as good as the provider beneath it. Calendula doesn't
|
||||||
|
add its own server-side features, and it relies on a sync adapter like DAVx5
|
||||||
|
being installed to actually move bytes. That's a deliberate line: I'd rather put
|
||||||
|
a thoughtful Material 3 Expressive interface on an open protocol than own a sync
|
||||||
|
stack I'd inevitably get subtly wrong.
|
||||||
|
|
||||||
|
The same idea drives the rest of the [Floret family](/work) — Agendula is the
|
||||||
|
exact same bet, made on the OpenTasks provider instead of the calendar one.
|
||||||
|
Different content, identical philosophy: build the part that's worth building,
|
||||||
|
and let open standards carry the rest.
|
||||||
56
src/content/blog/open-standards.md
Normal file
56
src/content/blog/open-standards.md
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
---
|
||||||
|
title: Open standards as a constraint, not a checkbox
|
||||||
|
description: >-
|
||||||
|
CalDAV, iCalendar, OpenTasks, DecSync — why I treat open standards as a hard
|
||||||
|
boundary for what the Floret apps are allowed to do.
|
||||||
|
pubDate: 2026-06-27
|
||||||
|
tags: [open-standards, caldav, android]
|
||||||
|
draft: false
|
||||||
|
---
|
||||||
|
|
||||||
|
"Supports open standards" usually means a feature in a list — an export button,
|
||||||
|
an import dialog, an optional CalDAV setting buried three screens deep. For the
|
||||||
|
[Floret apps](/work) it's the other way round: open standards are the boundary,
|
||||||
|
and everything that would step outside them is simply out of scope.
|
||||||
|
|
||||||
|
## The lane
|
||||||
|
|
||||||
|
The standards are deliberately boring and well-proven:
|
||||||
|
|
||||||
|
- **[CalDAV](https://datatracker.ietf.org/doc/html/rfc4791)** and
|
||||||
|
**[iCalendar](https://datatracker.ietf.org/doc/html/rfc5545)** for calendar
|
||||||
|
events — the protocol and the data format the rest of the ecosystem already
|
||||||
|
speaks.
|
||||||
|
- **[OpenTasks](https://github.com/dmfs/opentasks)' `TaskContract` provider**
|
||||||
|
for tasks, which stores CalDAV VTODOs on the device.
|
||||||
|
- **[DecSync](https://github.com/39aldo39/DecSync)** for peer-to-peer sync
|
||||||
|
without a server in the middle.
|
||||||
|
|
||||||
|
Calendula reads and writes events through Android's `CalendarContract`; Agendula
|
||||||
|
does the same over the OpenTasks provider. Neither app owns a database. The sync
|
||||||
|
adapter you already trust — DAVx5, SmoothSync, DecSync — moves the bytes.
|
||||||
|
|
||||||
|
## Why make it a hard boundary
|
||||||
|
|
||||||
|
Treating the standard as a constraint changes which decisions are even on the
|
||||||
|
table. A proprietary task service with a slick API is permanently off the
|
||||||
|
roadmap — not because it's bad, but because integrating it would mean owning a
|
||||||
|
sync stack and tying your data to one vendor's schema. The moment an app starts
|
||||||
|
reconciling its own copy of your data against someone's cloud, the simplicity
|
||||||
|
that made it trustworthy is gone.
|
||||||
|
|
||||||
|
The constraint also keeps the apps **reproducible**. There are no secret API
|
||||||
|
keys to embed, no SDKs that pull in closed dependencies, nothing that would stop
|
||||||
|
an app from building clean for [F-Droid](https://f-droid.org/). What goes in is
|
||||||
|
exactly what you can read in the source.
|
||||||
|
|
||||||
|
## The cost, and why it's worth paying
|
||||||
|
|
||||||
|
Living inside the standard means some things are genuinely harder. Provider APIs
|
||||||
|
have rough edges; recurrence rules and time zones in iCalendar are a deep well;
|
||||||
|
and you inherit whatever the underlying sync adapter does or doesn't support.
|
||||||
|
You don't get to paper over those gaps with a server you control.
|
||||||
|
|
||||||
|
That's the right trade. An app built on an open standard is one you can leave
|
||||||
|
without losing anything — your events and tasks were never hostage to it in the
|
||||||
|
first place. The interface is mine to get right; the data was always yours.
|
||||||
138
src/pages/datenschutz.astro
Normal file
138
src/pages/datenschutz.astro
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
import { SITE, LEGAL } from '../consts';
|
||||||
|
|
||||||
|
// Contact data is shared with the Impressum via the LEGAL object in consts.ts.
|
||||||
|
// Hosting provider (Auftragsverarbeiter): Hetzner Online GmbH — an AVV per
|
||||||
|
// Art. 28 DSGVO must be concluded (Hetzner provides one in the customer panel).
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout
|
||||||
|
title="Datenschutzerklärung"
|
||||||
|
description={`Datenschutzerklärung nach DSGVO für ${SITE.url}.`}
|
||||||
|
width="narrow"
|
||||||
|
>
|
||||||
|
<article class="prose">
|
||||||
|
<h1 class="page-title">Datenschutzerklärung</h1>
|
||||||
|
|
||||||
|
<h2>1. Verantwortlicher</h2>
|
||||||
|
<p>
|
||||||
|
Verantwortlicher im Sinne der Datenschutz-Grundverordnung (DSGVO) ist:
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
{LEGAL.business}<br />
|
||||||
|
{LEGAL.street}<br />
|
||||||
|
{LEGAL.city}<br />
|
||||||
|
E-Mail: <a href={`mailto:${LEGAL.email}`}>{LEGAL.email}</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>2. Grundsätzliches</h2>
|
||||||
|
<p>
|
||||||
|
Der Schutz Ihrer personenbezogenen Daten ist mir wichtig. Diese Website ist
|
||||||
|
bewusst datensparsam aufgebaut: Es werden keine Cookies gesetzt, keine
|
||||||
|
Inhalte von Drittanbietern (z. B. Google Fonts, CDNs oder Werbenetze)
|
||||||
|
nachgeladen und keine geräteübergreifenden Profile gebildet. Schriftarten
|
||||||
|
werden vom eigenen Server ausgeliefert, sodass beim Seitenaufruf keine
|
||||||
|
Verbindung zu Dritten entsteht.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>3. Hosting und Server-Logfiles</h2>
|
||||||
|
<p>
|
||||||
|
Diese Website wird auf selbst verwalteter Infrastruktur betrieben, die bei
|
||||||
|
der <strong>Hetzner Online GmbH</strong>, Industriestr. 25,
|
||||||
|
91710 Gunzenhausen, Deutschland, angemietet ist. Hetzner verarbeitet
|
||||||
|
die nachfolgend genannten Daten als Auftragsverarbeiter auf Grundlage eines
|
||||||
|
Vertrags zur Auftragsverarbeitung gemäß Art. 28 DSGVO. Die Server
|
||||||
|
stehen in Rechenzentren innerhalb der Europäischen Union (Deutschland bzw.
|
||||||
|
Finnland); eine Datenübermittlung in ein Drittland findet nicht statt.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Beim Aufruf der Seiten erhebt und speichert der Server automatisch
|
||||||
|
Informationen in sogenannten Server-Logfiles, die Ihr Browser übermittelt:
|
||||||
|
</p>
|
||||||
|
<ul>
|
||||||
|
<li>IP-Adresse des anfragenden Geräts,</li>
|
||||||
|
<li>Datum und Uhrzeit des Zugriffs,</li>
|
||||||
|
<li>Name und URL der abgerufenen Datei,</li>
|
||||||
|
<li>übertragene Datenmenge sowie Meldung über erfolgreichen Abruf,</li>
|
||||||
|
<li>verwendeter Browsertyp und dessen Version,</li>
|
||||||
|
<li>Betriebssystem sowie die zuvor besuchte Seite (Referrer), sofern übermittelt.</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Diese Verarbeitung erfolgt zum Zweck der technischen Bereitstellung,
|
||||||
|
Stabilität und Sicherheit der Website. Rechtsgrundlage ist das berechtigte
|
||||||
|
Interesse an einem fehlerfreien und sicheren Betrieb gemäß
|
||||||
|
Art. 6 Abs. 1 lit. f DSGVO. Die Logfiles werden
|
||||||
|
nicht mit anderen Datenquellen zusammengeführt und nach kurzer Zeit
|
||||||
|
gelöscht, soweit sie nicht zur Aufklärung eines konkreten
|
||||||
|
Missbrauchsfalls benötigt werden.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>4. Webanalyse mit Umami</h2>
|
||||||
|
<p>
|
||||||
|
Zur Reichweitenmessung nutze ich <strong>Umami</strong>, eine
|
||||||
|
datenschutzfreundliche Analyse-Software, die ich selbst hoste. Umami
|
||||||
|
arbeitet <strong>ohne Cookies</strong> und ohne über mehrere Websites
|
||||||
|
hinweg wiedererkennbare Kennungen. Erhoben werden ausschließlich
|
||||||
|
aggregierte, anonymisierte Nutzungsdaten – etwa aufgerufene Seiten,
|
||||||
|
ungefähre Herkunft (Land), Gerätetyp und Verweisquelle. Es werden keine
|
||||||
|
personenbezogenen Profile gebildet und keine Daten an Dritte weitergegeben.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Rechtsgrundlage ist das berechtigte Interesse an einer datensparsamen
|
||||||
|
Auswertung der Websitenutzung gemäß
|
||||||
|
Art. 6 Abs. 1 lit. f DSGVO. Da keine Cookies oder
|
||||||
|
vergleichbaren Technologien zum Einsatz kommen und keine
|
||||||
|
personenbeziehbaren Daten gespeichert werden, ist hierfür keine
|
||||||
|
Einwilligung erforderlich.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>5. Kontaktaufnahme</h2>
|
||||||
|
<p>
|
||||||
|
Wenn Sie mich per E-Mail kontaktieren, werden die von Ihnen mitgeteilten
|
||||||
|
Daten (Ihre E-Mail-Adresse sowie der Inhalt Ihrer Nachricht) zur
|
||||||
|
Bearbeitung Ihrer Anfrage verarbeitet und gespeichert. Rechtsgrundlage ist
|
||||||
|
Art. 6 Abs. 1 lit. f DSGVO (Beantwortung Ihrer
|
||||||
|
Anfrage) bzw. Art. 6 Abs. 1 lit. b DSGVO, sofern
|
||||||
|
die Anfrage auf den Abschluss eines Vertrags gerichtet ist. Die Daten
|
||||||
|
werden gelöscht, sobald sie für die Bearbeitung nicht mehr erforderlich
|
||||||
|
sind und keine gesetzlichen Aufbewahrungspflichten entgegenstehen.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>6. SSL-/TLS-Verschlüsselung</h2>
|
||||||
|
<p>
|
||||||
|
Diese Seite nutzt aus Sicherheitsgründen eine SSL-/TLS-Verschlüsselung. Eine
|
||||||
|
verschlüsselte Verbindung erkennen Sie am Schloss-Symbol in der
|
||||||
|
Adresszeile Ihres Browsers und am Präfix „https://“.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>7. Ihre Rechte</h2>
|
||||||
|
<p>Ihnen stehen nach der DSGVO gegenüber dem Verantwortlichen folgende Rechte zu:</p>
|
||||||
|
<ul>
|
||||||
|
<li>Auskunft über die zu Ihrer Person gespeicherten Daten (Art. 15 DSGVO),</li>
|
||||||
|
<li>Berichtigung unrichtiger Daten (Art. 16 DSGVO),</li>
|
||||||
|
<li>Löschung Ihrer Daten (Art. 17 DSGVO),</li>
|
||||||
|
<li>Einschränkung der Verarbeitung (Art. 18 DSGVO),</li>
|
||||||
|
<li>Datenübertragbarkeit (Art. 20 DSGVO),</li>
|
||||||
|
<li>
|
||||||
|
Widerspruch gegen die Verarbeitung, soweit diese auf
|
||||||
|
Art. 6 Abs. 1 lit. f DSGVO beruht
|
||||||
|
(Art. 21 DSGVO).
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p>
|
||||||
|
Zur Ausübung Ihrer Rechte genügt eine formlose Nachricht an die oben
|
||||||
|
genannte E-Mail-Adresse.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>8. Beschwerderecht bei der Aufsichtsbehörde</h2>
|
||||||
|
<p>
|
||||||
|
Unbeschadet eines anderweitigen verwaltungsrechtlichen oder gerichtlichen
|
||||||
|
Rechtsbehelfs steht Ihnen das Recht auf Beschwerde bei einer
|
||||||
|
Datenschutz-Aufsichtsbehörde zu, insbesondere in dem Mitgliedstaat Ihres
|
||||||
|
Aufenthaltsorts, Ihres Arbeitsplatzes oder des Orts des mutmaßlichen
|
||||||
|
Verstoßes, wenn Sie der Ansicht sind, dass die Verarbeitung Ihrer Daten
|
||||||
|
gegen die DSGVO verstößt (Art. 77 DSGVO).
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</BaseLayout>
|
||||||
@@ -1,20 +1,6 @@
|
|||||||
---
|
---
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import { SITE } from '../consts';
|
import { SITE, LEGAL as IMPRESSUM } from '../consts';
|
||||||
|
|
||||||
// ─── Legal data ──────────────────────────────────────────────────────────────
|
|
||||||
// REPLACE the «…» placeholders with real, legally accurate values before going
|
|
||||||
// live. A German Impressum (§ 5 DDG) requires a summonable postal address —
|
|
||||||
// a P.O. box / Postfach is NOT sufficient. Leave `phone` empty to omit it.
|
|
||||||
const IMPRESSUM = {
|
|
||||||
business: 'IT-Dienstleister | Jean-Luc Makiola', // full Gewerbe name (§ 5 DDG)
|
|
||||||
person: SITE.author, // natural person responsible — 'Jean-Luc Makiola' (§ 18 MStV)
|
|
||||||
street: 'Mahlerstraße 10',
|
|
||||||
city: '14772 Brandenburg an der Havel',
|
|
||||||
email: 'mail@jeanlucmakiola.de',
|
|
||||||
phone: '', // e.g. '+49 ...' — rendered only if set
|
|
||||||
};
|
|
||||||
// ─────────────────────────────────────────────────────────────────────────────
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout
|
<BaseLayout
|
||||||
|
|||||||
75
src/pages/uses.astro
Normal file
75
src/pages/uses.astro
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
---
|
||||||
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
|
|
||||||
|
// A /uses colophon — the stack behind this site and the Floret apps.
|
||||||
|
// Add a "Desk / hardware" section here if you'd like to list your machine,
|
||||||
|
// editor, and peripherals — left out for now to avoid guessing.
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout
|
||||||
|
title="Uses"
|
||||||
|
description="The stack behind this site, the Floret apps, and the infrastructure they run on."
|
||||||
|
width="narrow"
|
||||||
|
>
|
||||||
|
<article class="prose">
|
||||||
|
<h1 class="page-title">Uses</h1>
|
||||||
|
<p>
|
||||||
|
A running colophon of the tools and stack behind this site, the
|
||||||
|
<a href="/work">Floret apps</a>, and the infrastructure they run on. The
|
||||||
|
throughline: open, self-hostable standards, and as little reliance on
|
||||||
|
third-party services as I can manage.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<h2>This website</h2>
|
||||||
|
<ul>
|
||||||
|
<li>
|
||||||
|
Built with <a href="https://astro.build" rel="noopener">Astro</a> — a
|
||||||
|
static site, no client-side framework, content authored as Markdown in
|
||||||
|
the repo.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Typefaces are <a href="https://rsms.me/inter/" rel="noopener">Inter</a>
|
||||||
|
and <a href="https://www.jetbrains.com/lp/mono/" rel="noopener">JetBrains
|
||||||
|
Mono</a>, self-hosted via Fontsource — no Google Fonts CDN, so no
|
||||||
|
third-party request on page load.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
Analytics is self-hosted <a href="https://umami.is" rel="noopener">Umami</a>:
|
||||||
|
cookieless, no cross-site tracking, no personal profiles. See the
|
||||||
|
<a href="/datenschutz">Datenschutzerklärung</a> for what that means.
|
||||||
|
</li>
|
||||||
|
<li>Icons from <a href="https://iconify.design" rel="noopener">Iconify</a> (Material Design Icons + Simple Icons).</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>The Floret apps</h2>
|
||||||
|
<ul>
|
||||||
|
<li><strong>Kotlin</strong> and <strong>Jetpack Compose</strong>, designed in <strong>Material 3 Expressive</strong>.</li>
|
||||||
|
<li>
|
||||||
|
No reinvented sync stack — each app is a front-end over a platform
|
||||||
|
provider (<code>CalendarContract</code>, the OpenTasks
|
||||||
|
<code>TaskContract</code>) and open standards like CalDAV, iCalendar, and
|
||||||
|
DecSync.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
A shared design system, <a href="/work/floret-kit">floret-kit</a>, wired
|
||||||
|
in as a git submodule via a Gradle composite build — no published
|
||||||
|
artifacts, so every app stays reproducible.
|
||||||
|
</li>
|
||||||
|
<li>Released on <a href="https://f-droid.org" rel="noopener">F-Droid</a>, MIT-licensed, zero telemetry.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<h2>Self-hosted infrastructure</h2>
|
||||||
|
<p>Code, translations, builds, and this site all run on infrastructure I host myself.</p>
|
||||||
|
<ul>
|
||||||
|
<li><a href="https://gitea.jeanlucmakiola.de/makiolaj" rel="noopener">Gitea</a> for source hosting and as the home for smaller experiments.</li>
|
||||||
|
<li><a href="https://weblate.org" rel="noopener">Weblate</a> for community translations of the apps.</li>
|
||||||
|
<li><a href="https://coolify.io" rel="noopener">Coolify</a> to build and deploy this site.</li>
|
||||||
|
<li>Umami for the privacy-respecting analytics above.</li>
|
||||||
|
</ul>
|
||||||
|
|
||||||
|
<p class="muted">
|
||||||
|
This list grows as the stack does. Spotted something you'd ask about?
|
||||||
|
<a href="mailto:mail@jeanlucmakiola.de">Get in touch</a>.
|
||||||
|
</p>
|
||||||
|
</article>
|
||||||
|
</BaseLayout>
|
||||||
Reference in New Issue
Block a user