feat(ui): Dunkelmodus fuer Shop und Admin mit Umschalter

Rollen-Farben statt reiner Markenfarben, damit hell und dunkel aus einem
Satz Klassen entstehen:
- heading (Ueberschriften, Links, Rahmen): hell Waldgruen, dunkel Creme
- surface (abgesetzte Flaeche): hell Creme, dunkel angehobenes Dunkelgruen
- forest bleibt die dunkle Markenflaeche (Hero, Baender, Knoepfe,
  Admin-Leiste), cream der helle Text darauf
- honey-deep als fester Honigton fuer Flaechen; honey-dark (Text) wird
  im Dunkelmodus heller und bleibt lesbar
Codemod ueber src: text-/border-forest -> *-heading, bg-cream ->
bg-surface, bg-honey-dark -> bg-honey-deep. Im hellen Modus haben die
Rollen exakt die bisherigen Werte - das Erscheinungsbild bleibt gleich.

Umschalter (components/theme-toggle.tsx) Automatisch -> Hell -> Dunkel in
Kopfzeile, Mobilmenue und Admin-Leiste. Die Wahl liegt im Cookie "theme"
(nur light/dark, geprueft in lib/theme.ts); der Server setzt data-theme
schon beim Ausliefern, nichts blitzt hell auf. "Automatisch" folgt der
Systemeinstellung. Gewaehlter und automatischer Dunkelblock in
globals.css werden per Test auf Gleichheit geprueft.

Logo, Biene und Select-Pfeil nutzen die Rollenfarben. In der Kopfzeile
steht "Honig entdecken" zwischen 1280 und 1535 px nicht mehr doppelt
neben "Shop" (sonst stiess die Navigation an), der Slogan erscheint erst
ab 1680 px.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-09-26 06:12:44 +02:00
1 parent b77516b9f5
commit 6950657c91
95 files changed
+514 -272

No files matched your search

+16 -16
View File
@@ -303,7 +303,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
{/* Begruessung statt nackter Ueberschrift: der Tag beginnt hier. */}
<div className="mb-10">
<p className="text-sm text-ink/60">{todayLine}</p>
<h1 className="mt-1 text-4xl md:text-5xl leading-tight text-forest">
<h1 className="mt-1 text-4xl md:text-5xl leading-tight text-heading">
{t.adminDashboardPage.greeting(dayPart, admin?.firstName ?? "")}
</h1>
</div>
@@ -396,23 +396,23 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
{/* Was jetzt ansteht — direkt anklickbar. Wartet nichts, gibt es eine
kleine Belohnung statt einer leeren Liste. */}
<section className="border border-ink/10 bg-background p-6 lg:col-span-2">
<h2 className="text-lg text-forest">{t.adminDashboardPage.toDoHeading}</h2>
<h2 className="text-lg text-heading">{t.adminDashboardPage.toDoHeading}</h2>
{toDo.length > 0 ? (
<ul className="mt-4 divide-y divide-ink/10">
{toDo.map((item) => (
<li key={item.href}>
<Link
href={item.href}
className="group flex items-center gap-3 py-3 text-sm text-ink/80 transition-colors hover:text-forest"
className="group flex items-center gap-3 py-3 text-sm text-ink/80 transition-colors hover:text-heading"
>
<span className="flex h-7 min-w-7 items-center justify-center rounded-full bg-honey-dark px-2 text-xs font-semibold text-cream tabular-nums">
<span className="flex h-7 min-w-7 items-center justify-center rounded-full bg-honey-deep px-2 text-xs font-semibold text-cream tabular-nums">
{item.count}
</span>
<span className="flex-1">{item.label}</span>
<svg
aria-hidden="true"
viewBox="0 0 24 24"
className="h-4 w-4 text-ink/40 transition-[color,transform] duration-300 group-hover:translate-x-1 group-hover:text-forest"
className="h-4 w-4 text-ink/40 transition-[color,transform] duration-300 group-hover:translate-x-1 group-hover:text-heading"
fill="none"
stroke="currentColor"
strokeWidth="1.6"
@@ -430,7 +430,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
<path d="M7.5 13.2l3 3 6-6.4" stroke="var(--color-cream)" strokeWidth="1.8" fill="none" strokeLinecap="round" strokeLinejoin="round" />
</svg>
<div>
<p className="font-serif text-xl text-forest">{t.adminDashboardPage.allDoneTitle}</p>
<p className="font-serif text-xl text-heading">{t.adminDashboardPage.allDoneTitle}</p>
<p className="mt-1 text-sm text-ink/65">{t.adminDashboardPage.allDoneText}</p>
</div>
</div>
@@ -448,11 +448,11 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
key={stat.key}
className="group border border-ink/10 bg-background p-5 transition-[border-color,transform] duration-300 hover:-translate-y-0.5 hover:border-honey/50"
>
<span className="flex h-9 w-9 items-center justify-center border border-ink/10 text-forest transition-colors group-hover:border-honey/50 group-hover:text-honey-dark">
<span className="flex h-9 w-9 items-center justify-center border border-ink/10 text-heading transition-colors group-hover:border-honey/50 group-hover:text-honey-dark">
<Icon />
</span>
<p className="mt-4 text-xs text-ink/60">{statLabels[stat.key]}</p>
<p className="mt-1 text-2xl font-semibold text-forest">{stat.value}</p>
<p className="mt-1 text-2xl font-semibold text-heading">{stat.value}</p>
</div>
);
})}
@@ -460,7 +460,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
<div className="mt-10 grid lg:grid-cols-2 gap-6">
<div className="bg-background border border-ink/10 p-6 transition-colors hover:border-ink/20">
<h2 className="text-lg text-forest mb-4">{t.adminDashboardPage.topProductsHeading}</h2>
<h2 className="text-lg text-heading mb-4">{t.adminDashboardPage.topProductsHeading}</h2>
{topProducts.length === 0 ? (
<p className="text-sm text-ink/65">{t.adminDashboardPage.noSalesYet}</p>
) : (
@@ -469,13 +469,13 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
<li key={product.name} className="flex items-center justify-between gap-4">
<span className="flex items-center gap-3 text-ink/80">
<span className="relative flex h-6 w-6 flex-shrink-0 items-center justify-center text-[10px] font-semibold text-cream">
<HexMark className={`absolute inset-0 h-6 w-6 ${i === 0 ? "text-[var(--color-chart-honey)]" : "text-forest/70"}`} />
<HexMark className={`absolute inset-0 h-6 w-6 ${i === 0 ? "text-[var(--color-chart-honey)]" : "text-heading/70"}`} />
<span className="relative">{i + 1}</span>
</span>
{product.name}{" "}
<span className="text-ink/65">{t.adminDashboardPage.unitsSuffix(product.quantity)}</span>
</span>
<span className="text-forest whitespace-nowrap">{formatPrice(product.revenueCents)}</span>
<span className="text-heading whitespace-nowrap">{formatPrice(product.revenueCents)}</span>
</li>
))}
</ul>
@@ -483,7 +483,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
</div>
<div className="bg-background border border-ink/10 p-6 transition-colors hover:border-ink/20">
<h2 className="text-lg text-forest mb-4">
<h2 className="text-lg text-heading mb-4">
{t.adminDashboardPage.revenueByCategoryHeading}
</h2>
{revenueByCategory.length === 0 ? (
@@ -496,7 +496,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
<span className="text-ink/70">
{t.common.categories[category as keyof typeof t.common.categories] ?? category}
</span>
<span className="text-forest">{formatPrice(totals.revenueCents)}</span>
<span className="text-heading">{formatPrice(totals.revenueCents)}</span>
</div>
{/* Meter: Spur ist eine hellere Stufe desselben Tons. */}
<div className="h-2 overflow-hidden rounded-full bg-[color-mix(in_oklab,var(--color-chart-honey)_15%,transparent)]">
@@ -514,7 +514,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
<div className="mt-6 grid lg:grid-cols-2 gap-6">
<div className="bg-background border border-ink/10 p-6 transition-colors hover:border-ink/20">
<h2 className="text-lg text-forest mb-4">
<h2 className="text-lg text-heading mb-4">
{t.adminDashboardPage.revenueTrendHeading(REVENUE_TREND_DAYS)}
</h2>
{/* Balken nach dataviz-Skill: duenn, 4px gerundete Kappe an der
@@ -553,7 +553,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
</div>
<div className="bg-background border border-ink/10 p-6 transition-colors hover:border-ink/20">
<h2 className="text-lg text-forest mb-4">{t.adminDashboardPage.topVouchersHeading}</h2>
<h2 className="text-lg text-heading mb-4">{t.adminDashboardPage.topVouchersHeading}</h2>
{topDiscountCodes.length === 0 ? (
<p className="text-sm text-ink/65">{t.adminDashboardPage.noVouchersYet}</p>
) : (
@@ -566,7 +566,7 @@ export default async function AdminDashboardPage(props: PageProps<"/admin">) {
({code.type === "PERCENT" ? `${code.value} %` : formatPrice(code.value)})
</span>
</span>
<span className="text-forest whitespace-nowrap">
<span className="text-heading whitespace-nowrap">
{t.adminDashboardPage.usedCountLabel(code.usedCount, code.maxUses)}
</span>
</li>