Files
qrcode-generator/src/app/(app)/page.tsx
T
Florian Wilde b828f38ca5 Dark Mode: folgt dem System, mit Umschalter
Drei Zustaende in der Kopfzeile — System, Hell, Dunkel. Drei statt zwei, weil
ein reiner Ein/Aus-Schalter nach dem ersten Klick fuer immer eine feste Wahl
bedeutet; wer "System" waehlt, bekommt abends dunkel und morgens hell.

Umgesetzt ueber `light-dark()` im CSS, nicht ueber `dark:`-Klassen. Die
Oberflaeche benutzt an rund 440 Stellen feste Farbklassen; jede davon zu
verdoppeln waere eine riesige Fleissarbeit, und jede kuenftige Zeile muesste
daran denken. Stattdessen entscheiden die Farbvariablen selbst, welchen Wert
sie tragen — gesteuert ueber `color-scheme`. Deshalb genuegt fuer den
Umschalter ein Attribut am html-Element, und die Systemwahl funktioniert
sogar ohne JavaScript. Das Skript im Kopf setzt nur eine ausdrueckliche Wahl,
damit die helle Fassung nicht kurz aufblitzt.

Zwei Faelle liessen sich nicht ueber Variablen loesen: `bg-white` muss im
Dunkeln eine dunkle Flaeche sein, `text-white` dagegen weiss bleiben — beide
teilen sich aber dieselbe Variable. Fuer Flaechen gibt es deshalb jetzt
`bg-surface`. Ebenso brauchen Akzent-Links im Dunkeln eine hellere Stufe als
Akzent-Knoepfe: dafuer `text-accent-text` und `bg-accent-soft`.

Bewusst unveraendert bleiben die QR-Codes selbst — deren Farben legt die
Gestaltung fest.

Die dunklen Werte sind keine Spiegelung: Reines Schwarz unter weisser Schrift
flimmert. Alle Textkontraste sind gemessen und liegen ueber 4,5:1, der
niedrigste bei 5,9:1.
2026-08-16 09:09:10 +02:00

134 lines
5.1 KiB
TypeScript

import Link from "next/link";
import { Icon } from "@/components/icons";
import { Badge, Notice } from "@/components/ui";
import { requirePageActor } from "@/lib/auth/guard";
import { getVisibleQrTypes } from "@/lib/qr/availability";
// Welche Typen sichtbar sind, steht in der Datenbank und ist jederzeit
// änderbar — die Startseite darf deshalb nicht vorgerendert werden.
export const dynamic = "force-dynamic";
export default async function HomePage() {
await requirePageActor();
const types = await getVisibleQrTypes();
const usableCount = types.filter((entry) => entry.usable).length;
return (
<div className="mx-auto max-w-6xl px-4 py-10 sm:px-6 sm:py-14">
<div className="max-w-2xl">
<h1 className="text-3xl font-bold tracking-tight text-slate-900 sm:text-4xl">
Was möchtest du erstellen?
</h1>
<p className="mt-3 text-base text-slate-600">
Wähle einen Typ, gib deine Daten ein und lade den fertigen QR-Code als
PNG oder SVG herunter. Auf Wunsch als dynamischer Code, dessen Ziel du
später änderst, ohne den Code neu drucken zu müssen.
</p>
</div>
{usableCount === 0 ? (
<div className="mt-9 max-w-2xl">
<Notice tone="warning">
{types.length === 0
? "Zurzeit ist kein QR-Typ freigegeben."
: "Zurzeit ist kein QR-Typ zum Erstellen freigegeben — die unten gezeigten Typen folgen erst in einer späteren Version."}{" "}
Unter{" "}
<Link href="/verwaltung/typen" className="font-medium underline">
Verwaltung → QR-Typen
</Link>{" "}
lassen sich Typen wieder freigeben.
</Notice>
</div>
) : null}
{types.length === 0 ? null : (
<ul className="mt-9 grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-3">
{types.map(({ qrType, usable }) => {
const tile = (
<>
<span
className={
usable
? "flex size-11 shrink-0 items-center justify-center rounded-xl bg-accent-soft text-accent-text"
: "flex size-11 shrink-0 items-center justify-center rounded-xl bg-slate-100 text-slate-400"
}
>
<Icon name={qrType.icon} className="size-6" />
</span>
<span className="min-w-0 flex-1">
<span className="flex flex-wrap items-center gap-2">
<span
className={
usable
? "font-semibold text-slate-900"
: "font-semibold text-slate-500"
}
>
{qrType.title}
</span>
{/* Kein Abzeichen „Beliebt“ mehr: Es stand an jedem
nutzbaren Typ und sagte damit nichts. Hier steht nur
noch, was die Wahl beeinflusst — nämlich wenn ein Typ
zwingend eine Kurz-URL braucht und deshalb nicht ohne
diesen Server funktioniert. */}
{!usable ? (
<Badge tone="soon">Bald verfügbar</Badge>
) : !qrType.supportsStatic ? (
<Badge tone="neutral">nur dynamisch</Badge>
) : null}
</span>
<span
className={
usable
? "mt-1 block text-sm text-slate-600"
: "mt-1 block text-sm text-slate-400"
}
>
{qrType.description}
</span>
</span>
</>
);
return (
<li key={qrType.id}>
{usable ? (
<Link
href={`/erstellen/${qrType.id}`}
className="flex h-full items-start gap-4 rounded-2xl border border-slate-200 bg-surface p-4 shadow-sm transition-all hover:border-indigo-300 hover:shadow-md"
>
{tile}
</Link>
) : (
<div
aria-disabled="true"
title={`„${qrType.title}“ folgt in einer späteren Version`}
className="flex h-full cursor-not-allowed items-start gap-4 rounded-2xl border border-dashed border-slate-200 bg-slate-50/60 p-4"
>
{tile}
</div>
)}
</li>
);
})}
</ul>
)}
{usableCount > 0 && usableCount < types.length ? (
<p className="mt-8 text-sm text-slate-500">
Die übrigen Typen folgen in einer späteren Version. Welche Kacheln
hier erscheinen, legst du unter{" "}
<Link
href="/verwaltung/typen"
className="font-medium text-slate-600 underline"
>
Verwaltung → QR-Typen
</Link>{" "}
fest.
</p>
) : null}
</div>
);
}