feat(t5): neue Farbwerte, Dunkel für alle, Farb-Codemod (T5a A4)

- globals.css: Hell, Dunkel und Druck mit den Spec-Werten aus 4.2,
  Fokusring = --primary-ink, Verlaufs-Token entfernt.
- Codemod scripts/codemod-t5-colors.mjs: text-primary/-warning auf -ink,
  Vordergrund nur auf voller Fläche, Auswahlrahmen dunkel; --check-Modus
  und Spec halten src sauber.
- Kontrast-Wächter misst die echte globals.css (VALUES_SWITCHED), alle 21
  Zeilen aus 4.4 in allen vier Sätzen; keine dark:-Klassen mehr in src.
- Dunkel ist ohne Entwickler-Schalter wählbar, Standard bleibt Hell.
- Review-Nacharbeit: Auswahl-Umrisse und aktueller Schritt in Vordergrund,
  deaktivierte Felder bg-muted, Avatar ohne Blend, Honig-Check-Rahmen
  über --input.

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick.RosenloecherandClaude Opus 5.5 committed 2026-10-16 17:23:21 +02:00
1 parent 6edae3efec
commit d8238855fc
91 files changed
+673 -391

No files matched your search

@@ -126,8 +126,8 @@ export function WeatherCard({
<div
className={`flex items-center gap-2 rounded-lg border p-2.5 text-sm ${
todaySuitability
? "border-primary/30 bg-primary/5 text-primary"
: "border-warning/40 bg-warning/10 text-warning-foreground"
? "border-primary/30 bg-primary/5 text-primary-ink"
: "border-warning/40 bg-warning/10 text-warning-ink"
}`}
>
{todaySuitability ? (
@@ -155,7 +155,7 @@ export function WeatherCard({
{day.tempMinC !== null ? `${Math.round(day.tempMinC)}°` : "–"}
</span>
{day.precipitationMm !== null && day.precipitationMm > 0 ? (
<span className="text-xs text-primary">
<span className="text-xs text-primary-ink">
{day.precipitationMm.toFixed(0)} mm
</span>
) : null}
@@ -26,7 +26,7 @@ const PRINT_DONE = "Etiketten-PDF heruntergeladen.";
const PROOF_DONE = "Probedruck (MUSTER) heruntergeladen. Er ist nicht zum Verkauf gedacht.";
const CHECK_FALLBACK = "Die Pflichtangaben konnten nicht geprüft werden.";
const LINK_CLASS =
"inline-flex min-h-11 items-center rounded-md text-sm font-medium text-primary underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-ring";
"inline-flex min-h-11 items-center rounded-md text-sm font-medium text-primary-ink underline underline-offset-4 focus-visible:outline-2 focus-visible:outline-ring";
type FixTarget = "master" | "product" | "designer";
@@ -301,7 +301,7 @@ export function PrintLabelsCard({
<ul className="flex flex-col gap-1">
{warnings.map((warning, index) => (
<li key={`${warning.code}-${index}`} className="flex items-start gap-2 text-sm">
<TriangleAlert aria-hidden className="mt-0.5 size-4 shrink-0 text-warning" />
<TriangleAlert aria-hidden className="mt-0.5 size-4 shrink-0 text-warning-ink" />
<span>
<span className="font-medium">Hinweis:</span> {warning.title}
</span>
@@ -452,14 +452,14 @@ export default function NewBottlingBatchPage() {
/>
{withdrawalCheckFailed ? (
<p role="alert" className="flex items-start gap-2 rounded-lg bg-warning/15 p-3 text-sm">
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning" aria-hidden />
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning-ink" aria-hidden />
Wartezeit konnte nicht geprüft werden
</p>
) : null}
{withdrawalHints.length > 0 ? (
<div className="flex flex-col gap-1.5 rounded-lg bg-warning/15 p-3 text-sm">
<p className="flex items-center gap-2 font-medium">
<AlertTriangle className="size-4 shrink-0 text-warning" aria-hidden />
<AlertTriangle className="size-4 shrink-0 text-warning-ink" aria-hidden />
Honig aus einer Wartezeit
</p>
<ul className="flex flex-col gap-1">
@@ -28,7 +28,7 @@ export function BottomNav({ user }: { user: Pick<AuthUser, "permissions"> }) {
aria-current={isActive ? "page" : undefined}
className={cn(
"flex flex-1 flex-col items-center justify-center gap-1 text-xs",
isActive ? "text-primary" : "text-muted-foreground",
isActive ? "text-primary-ink" : "text-muted-foreground",
)}
>
<Icon className="size-5" aria-hidden="true" />
@@ -355,7 +355,7 @@ export default function CalendarPage() {
className={cn(
"flex min-h-24 flex-col gap-1 border-r border-b p-1.5 text-left last:border-r-0",
isCurrentMonth ? "bg-card" : "bg-muted/40",
isSelected && "outline-2 -outline-offset-2 outline-primary",
isSelected && "outline-2 -outline-offset-2 outline-foreground",
)}
>
<span
@@ -209,7 +209,7 @@ function VarroaTrendChart({
<div className="flex flex-col gap-2">
<svg
viewBox={`0 0 ${CHART_WIDTH} ${CHART_HEIGHT}`}
className="w-full text-primary"
className="w-full text-primary-ink"
role="img"
aria-labelledby={titleId}
aria-describedby={descId}
@@ -293,7 +293,7 @@ function ChartLegend({ hasRed, hasYellow }: { hasRed: boolean; hasYellow: boolea
return (
<ul aria-label="Legende" className="flex flex-wrap gap-x-4 gap-y-1 text-xs text-muted-foreground">
<li className="flex items-center gap-1.5">
<span className="text-primary">
<span className="text-primary-ink">
<LegendSwatch width={2} />
</span>
<span>Milbenfall pro Tag</span>
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createColony } from "@/lib/colonies/api";
import { COLONY_TYPES, COLONY_TYPE_LABELS, MAX_OCCUPIED_FRAMES } from "@/lib/colonies/labels";
import { fetchApiaries } from "@/lib/apiaries/api";
@@ -33,7 +33,7 @@ function Step({ number, title, titleId, state, children }: StepProps) {
return (
<li
aria-current={state === "current" ? "step" : undefined}
className="flex flex-col gap-1 rounded-lg border border-border/60 p-3 aria-[current=step]:border-primary aria-[current=step]:bg-primary/10"
className="flex flex-col gap-1 rounded-lg border border-border/60 p-3 aria-[current=step]:border-foreground aria-[current=step]:bg-primary/10"
>
<span className="flex flex-wrap items-center justify-between gap-2">
<span
@@ -136,7 +136,7 @@ function CreateNoticeForm({ onDone }: { onDone: () => void }) {
{NOTICE_KINDS.map((entry) => (
<label
key={entry}
className="flex min-h-11 cursor-pointer items-start gap-3 rounded-lg border border-border px-3 py-2 has-[:checked]:border-primary has-[:checked]:bg-primary/10"
className="flex min-h-11 cursor-pointer items-start gap-3 rounded-lg border border-border px-3 py-2 has-[:checked]:border-foreground has-[:checked]:bg-primary/10"
>
<input
type="radio"
@@ -17,7 +17,7 @@ const STATE_ICON: Record<ReadinessItemState, LucideIcon> = {
};
const FIX_LINK_CLASS =
"inline-flex min-h-11 shrink-0 items-center gap-1 rounded-lg px-2 font-medium text-primary underline-offset-4 outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50";
"inline-flex min-h-11 shrink-0 items-center gap-1 rounded-lg px-2 font-medium text-primary-ink underline-offset-4 outline-none hover:underline focus-visible:ring-3 focus-visible:ring-ring/50";
function FixLink({ itemKey, subject, context }: { itemKey: string; subject: ReadinessSubject | null; context: string }) {
const { href, label, available } = fixLinkFor(itemKey, subject);
@@ -2,7 +2,6 @@
import { useEffect, useMemo, useState } from "react";
import { useQuery } from "@tanstack/react-query";
import { NoComplianceAccess } from "@/components/compliance/no-compliance-access";
import { DetailHeader } from "@/components/detail-header";
import { StandNote } from "@/components/offline/stand-note";
@@ -1,7 +1,6 @@
"use client";
import { FormEvent, useEffect, useState } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { NoComplianceAccess } from "@/components/compliance/no-compliance-access";
import { DetailHeader, DetailQueryFallback } from "@/components/detail-header";
@@ -219,7 +219,7 @@ export default function DashboardPage() {
</span>
{href ? (
<Link
className="inline-flex min-h-11 shrink-0 items-center rounded-lg px-2 font-medium text-primary underline-offset-4 hover:underline"
className="inline-flex min-h-11 shrink-0 items-center rounded-lg px-2 font-medium text-primary-ink underline-offset-4 hover:underline"
href={href}
>
Ansehen
@@ -247,7 +247,7 @@ export default function DashboardPage() {
className="flex min-h-11 items-center gap-3 rounded-xl px-3 py-2 transition-colors hover:bg-muted focus-visible:outline-2 focus-visible:outline-ring"
>
<Icon
className={`size-5 shrink-0 ${tile.attention ? "text-warning" : "text-primary"}`}
className={`size-5 shrink-0 ${tile.attention ? "text-warning-ink" : "text-primary-ink"}`}
aria-hidden
/>
<span className="font-heading text-lg font-semibold tabular-nums">
@@ -1,7 +1,6 @@
"use client";
import { useParams } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { fetchEquipment, updateEquipment } from "@/lib/equipment/api";
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createEquipment } from "@/lib/equipment/api";
import { useFormDraft } from "@/lib/forms/use-form-draft";
import { errorMessage } from "@/lib/forms/error-message";
@@ -2,7 +2,6 @@
import Link from "next/link";
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { fetchExtraction } from "@/lib/extractions/api";
import { Button } from "@/components/ui/button";
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { fetchHarvests } from "@/lib/harvests/api";
import {
createExtraction,
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createFeeding } from "@/lib/feedings/api";
import { createFeedingsBulk } from "@/lib/bulk/api";
import {
@@ -2,7 +2,6 @@
import Link from "next/link";
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { fetchHarvest } from "@/lib/harvests/api";
import { Button } from "@/components/ui/button";
@@ -284,7 +284,7 @@ export default function NewHarvestPage() {
</Field>
{withdrawalQuery.isError ? (
<p role="alert" className="flex items-start gap-2 rounded-lg bg-warning/15 p-3 text-sm">
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning" aria-hidden />
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning-ink" aria-hidden />
Wartezeiten konnten nicht geprüft werden. Vor der Ernte die Behandlungen der
Völker selbst ansehen.
</p>
@@ -114,7 +114,7 @@ function MovementList({ movements }: { movements: HoneyBatchMovement[] }) {
className={
movement.quantityGrams < 0
? "font-semibold tabular-nums text-destructive"
: "font-semibold tabular-nums text-primary"
: "font-semibold tabular-nums text-primary-ink"
}
>
{formatSignedKg(gramsToKg(movement.quantityGrams))}
@@ -98,7 +98,7 @@ function HiveOpsList({ ops, base, catalog, onChange, onUndone, onEmptied }: Hive
</div>
{blocked?.index === index ? (
<p role="alert" className="flex items-start gap-2 text-sm">
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning" aria-hidden />
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning-ink" aria-hidden />
{blocked.reason} Mach zuerst die späteren Änderungen rückgängig.
</p>
) : null}
@@ -734,7 +734,7 @@ function InspectionCaptureForm({
{noQueenSigns ? (
<p className="flex items-start gap-2 rounded-lg bg-warning/15 p-3 text-sm">
<AlertTriangle
className="mt-0.5 size-4 shrink-0 text-warning"
className="mt-0.5 size-4 shrink-0 text-warning-ink"
aria-hidden
/>
Weder Königin noch Stifte gesehen. Nach dem Speichern wird
@@ -894,7 +894,7 @@ function InspectionCaptureForm({
/>
{values.feedLegacyText !== null ? (
<p role="status" className="flex items-start gap-2 text-sm">
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning" aria-hidden />
<AlertTriangle className="mt-0.5 size-4 shrink-0 text-warning-ink" aria-hidden />
{feedLegacyHint(values.feedLegacyText)}
</p>
) : null}
@@ -3,7 +3,6 @@
import type { ReactNode } from "react";
import Link from "next/link";
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { useAuth } from "@/lib/auth/auth-context";
import { buildBook } from "@/lib/medicines/book";
@@ -37,7 +37,7 @@ export function MoreSheet({ user }: { user: Pick<AuthUser, "permissions"> }) {
<SheetTrigger
className={cn(
"flex flex-1 flex-col items-center justify-center gap-1 text-xs",
isMoreActive ? "text-primary" : "text-muted-foreground",
isMoreActive ? "text-primary-ink" : "text-muted-foreground",
)}
>
<Menu className="size-5" aria-hidden="true" />
@@ -4,7 +4,6 @@ import { Suspense } from "react";
import Link from "next/link";
import { useSearchParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { useAuth, useCurrentUserId } from "@/lib/auth/auth-context";
import {
OFFLINE_PACKS_QUERY_KEY,
@@ -2,7 +2,6 @@
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import {
@@ -2,7 +2,6 @@
import { FormEvent, useRef, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createProductionBatch } from "@/lib/production-batches/api";
@@ -2,7 +2,6 @@
import Link from "next/link";
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { fetchProduct } from "@/lib/products/api";
import { fetchProductionBatches } from "@/lib/production-batches/api";
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createProduct } from "@/lib/products/api";
import { useFormDraft } from "@/lib/forms/use-form-draft";
import { errorMessage } from "@/lib/forms/error-message";
@@ -1,4 +1,4 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { mockMedia, resetDocument } from "@/lib/appearance/test-helpers";
@@ -30,21 +30,20 @@ describe("AppearanceSection", () => {
expect(screen.getByText("Gilt nur für dieses Gerät.")).toBeInTheDocument();
});
it("hides Dunkel and Wie Gerät without the dev flag", () => {
it("starts on Hell and offers Dunkel and Wie Gerät without a flag", () => {
render(<AppearanceSection />);
expect(screen.getByRole("radio", { name: /^Hell/ })).toBeInTheDocument();
expect(screen.queryByRole("radio", { name: /^Dunkel/ })).not.toBeInTheDocument();
expect(screen.queryByRole("radio", { name: /^Wie Gerät/ })).not.toBeInTheDocument();
expect(screen.getByRole("radio", { name: /^Hell/ })).toBeChecked();
expect(screen.getByRole("radio", { name: /^Dunkel/ })).not.toBeChecked();
expect(screen.getByRole("radio", { name: /^Wie Gerät/ })).not.toBeChecked();
});
it("offers Dunkel and Wie Gerät with the dev flag", async () => {
vi.stubEnv("NEXT_PUBLIC_APPEARANCE_DARK", "1");
it("dark is selectable without a flag", async () => {
render(<AppearanceSection />);
await userEvent.click(screen.getByRole("radio", { name: /^Dunkel/ }));
expect(document.documentElement).toHaveClass("dark");
expect(screen.getByRole("radio", { name: /^Wie Gerät/ })).toBeInTheDocument();
expect(window.localStorage.getItem("apicore.theme")).toBe("dark");
});
it("changes the motion level to Dezent and applies it", async () => {
@@ -3,12 +3,7 @@
import { useId } from "react";
import { ChoiceGroup, type ChoiceOption } from "@/components/form/choice-group";
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
import {
isDarkEnabled,
useAppearance,
type ContrastChoice,
type ThemeChoice,
} from "@/lib/appearance/appearance";
import { useAppearance, type ContrastChoice, type ThemeChoice } from "@/lib/appearance/appearance";
import { MOTION_LEVELS, type MotionLevel } from "@/lib/motion/motion-preference";
const OPTION_CLASS = "h-auto justify-start px-3 py-2 text-left";
@@ -38,8 +33,6 @@ const MOTION_OPTIONS: readonly ChoiceOption<MotionLevel>[] = MOTION_LEVELS.map((
export function AppearanceSection() {
const { theme, contrast, motion, setTheme, setContrast, setMotion } = useAppearance();
const ids = { theme: useId(), contrast: useId(), motion: useId(), motionHint: useId() };
// Dark has unreadable old values until task A4; the dev flag shows it for work on A4.
const themeOptions = isDarkEnabled() ? THEME_OPTIONS : THEME_OPTIONS.slice(0, 1);
return (
<Card>
@@ -56,7 +49,7 @@ export function AppearanceSection() {
Farbschema
</p>
<ChoiceGroup
options={themeOptions}
options={THEME_OPTIONS}
value={theme}
onChange={setTheme}
labelledBy={ids.theme}
@@ -1,7 +1,6 @@
"use client";
import { ChangeEvent, FormEvent, useRef, useState } from "react";
import { useMutation } from "@tanstack/react-query";
import { toast } from "sonner";
import { useAuth } from "@/lib/auth/auth-context";
@@ -2,7 +2,6 @@
import { useParams } from "next/navigation";
import { useQuery } from "@tanstack/react-query";
import { fetchQueen } from "@/lib/queens/api";
import { QUEEN_STATUS_LABELS } from "@/lib/queens/labels";
import { QrCodeDialog } from "@/components/qr-code-dialog";
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createQueen, fetchQueens } from "@/lib/queens/api";
import { useFormDraft } from "@/lib/forms/use-form-draft";
import { errorMessage } from "@/lib/forms/error-message";
@@ -2,7 +2,6 @@
import Link from "next/link";
import { useParams, useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import {
@@ -2,7 +2,6 @@
import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createRawMaterialLot } from "@/lib/raw-materials/api";
@@ -53,8 +53,8 @@ export function ApiKeyAllowlist({ apiKey }: { apiKey: ApiKey }) {
Nur von: <span className="font-mono">{apiKey.allowedIps.join(", ")}</span>
</span>
) : (
<span className="flex items-center gap-1 text-warning-foreground">
<ShieldAlert className="size-3.5 text-warning" aria-hidden />
<span className="flex items-center gap-1 text-warning-ink">
<ShieldAlert className="size-3.5 text-warning-ink" aria-hidden />
Von jeder Adresse nutzbar – besser auf den Server von Wabenhain beschränken.
</span>
)}
@@ -155,7 +155,7 @@ export function ApiKeysTab() {
className="flex flex-col gap-3 rounded-2xl border border-primary/40 bg-primary/5 p-4"
>
<div className="flex items-center gap-2 font-medium">
<ShieldCheck className="size-5 text-primary" aria-hidden />
<ShieldCheck className="size-5 text-primary-ink" aria-hidden />
Schlüssel „{created.name}“ angelegt
</div>
<p className="text-sm">
@@ -2,7 +2,6 @@
import { Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { PageHeader } from "@/components/page-header";
import { TabSwitcher } from "@/components/tab-switcher";
import { WebhooksTab } from "./webhooks-tab";
@@ -133,7 +133,7 @@ function ElementBox({
<div
className={cn(
"absolute cursor-move touch-none select-none",
selected && "outline outline-2 outline-primary",
selected && "outline outline-2 outline-foreground",
)}
style={{
left: box.left,
@@ -146,7 +146,7 @@ function ElementBox({
{renderContent(element, scale)}
{selected ? (
<div
className="absolute right-0 bottom-0 size-3 translate-x-1/2 translate-y-1/2 cursor-nwse-resize rounded-full border-2 border-primary bg-background"
className="absolute right-0 bottom-0 size-3 translate-x-1/2 translate-y-1/2 cursor-nwse-resize rounded-full border-2 border-foreground bg-background"
onPointerDown={(event) => {
event.stopPropagation();
onStartResize(event);
@@ -1,6 +1,5 @@
"use client";
import { createLabelTemplate } from "@/lib/label-templates/api";
import { FormBackLink } from "@/components/form/form-back-link";
import { PageHeader } from "@/components/page-header";
@@ -2,7 +2,6 @@
import { Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { PageHeader } from "@/components/page-header";
import { TabSwitcher } from "@/components/tab-switcher";
import { UsersTab } from "./users-tab";
@@ -2,7 +2,6 @@
import { useSyncExternalStore } from "react";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { ConfirmDialog } from "@/components/confirm-dialog";
import { useAuth } from "@/lib/auth/auth-context";
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter } from "next/navigation";
import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { createTask } from "@/lib/tasks/api";
import { TASK_PRIORITIES, TASK_PRIORITY_LABELS } from "@/lib/tasks/labels";
import { fetchUsers } from "@/lib/users/api";
@@ -4,7 +4,6 @@ import { FormEvent, useState } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { useQuery, useQueryClient } from "@tanstack/react-query";
import { toast } from "sonner";
import { saveErrorText } from "@/lib/honey-batches/errors";
import { useAuth } from "@/lib/auth/auth-context";
import { fetchColonies } from "@/lib/colonies/api";
+124 -130
View File
@@ -102,55 +102,52 @@
}
:root {
--background: oklch(0.955 0.017 80);
--foreground: oklch(0.22 0.028 55);
--background: oklch(0.968 0.014 85);
--foreground: oklch(0.21 0.03 55);
--card: oklch(0.995 0.004 90);
--card-foreground: oklch(0.22 0.028 55);
--card-foreground: oklch(0.21 0.03 55);
--popover: oklch(0.995 0.004 90);
--popover-foreground: oklch(0.22 0.028 55);
--primary: oklch(0.6 0.15 48);
--primary-foreground: oklch(0.99 0.02 85);
--popover-foreground: oklch(0.21 0.03 55);
--primary: oklch(0.8 0.145 78);
--primary-foreground: oklch(0.21 0.035 55);
--secondary: oklch(0.44 0.07 145);
--secondary-foreground: oklch(0.98 0.015 100);
--muted: oklch(0.9 0.02 82);
--muted-foreground: oklch(0.48 0.032 65);
--accent: oklch(0.93 0.032 74);
--accent-foreground: oklch(0.26 0.045 50);
--destructive: oklch(0.5 0.16 30);
--border: oklch(0.85 0.024 78);
--input: oklch(0.85 0.024 78);
--ring: oklch(0.6 0.15 48);
--warning: oklch(0.68 0.14 75);
--warning-foreground: oklch(0.24 0.04 60);
--success: oklch(0.46 0.06 145);
--muted: oklch(0.925 0.018 82);
--muted-foreground: oklch(0.44 0.03 60);
--accent: oklch(0.935 0.03 78);
--accent-foreground: oklch(0.25 0.045 50);
--destructive: oklch(0.5 0.17 28);
--border: oklch(0.86 0.022 78);
--input: oklch(0.6 0.035 65);
--ring: oklch(0.47 0.115 55);
--warning: oklch(0.79 0.15 80);
--warning-foreground: oklch(0.23 0.04 60);
--success: oklch(0.46 0.08 145);
--success-foreground: oklch(0.98 0.015 100);
--condition-excellent: oklch(0.58 0.13 145);
--condition-excellent-foreground: oklch(0.16 0.04 145);
--condition-good: oklch(0.64 0.13 118);
--condition-good-foreground: oklch(0.26 0.05 118);
--condition-average: oklch(0.7 0.14 90);
--condition-average-foreground: oklch(0.28 0.05 90);
--condition-weak: oklch(0.68 0.15 55);
--condition-weak-foreground: oklch(0.26 0.05 55);
--condition-critical: oklch(0.64 0.16 30);
--condition-excellent: oklch(0.62 0.13 150);
--condition-excellent-foreground: oklch(0.15 0.04 150);
--condition-good: oklch(0.645 0.13 125);
--condition-good-foreground: oklch(0.17 0.04 125);
--condition-average: oklch(0.665 0.125 88);
--condition-average-foreground: oklch(0.19 0.04 90);
--condition-weak: oklch(0.665 0.14 58);
--condition-weak-foreground: oklch(0.18 0.04 55);
--condition-critical: oklch(0.62 0.17 30);
--condition-critical-foreground: oklch(0.15 0.04 30);
--chart-1: oklch(0.6 0.135 50);
--chart-2: oklch(0.46 0.06 145);
--chart-3: oklch(0.68 0.14 75);
--chart-4: oklch(0.5 0.16 30);
--chart-5: oklch(0.55 0.05 250);
--chart-1: oklch(0.56 0.13 55);
--chart-2: oklch(0.46 0.08 145);
--chart-3: oklch(0.6 0.12 85);
--chart-4: oklch(0.5 0.17 28);
--chart-5: oklch(0.52 0.08 250);
--radius: 0.75rem;
--sidebar: oklch(0.995 0.004 90);
--sidebar-foreground: oklch(0.22 0.028 55);
--sidebar-primary: oklch(0.6 0.15 48);
--sidebar-primary-foreground: oklch(0.99 0.02 85);
--sidebar-accent: oklch(0.93 0.032 74);
--sidebar-accent-foreground: oklch(0.26 0.045 50);
--sidebar-border: oklch(0.85 0.024 78);
--sidebar-ring: oklch(0.6 0.15 48);
--gradient-honey: linear-gradient(135deg, oklch(0.74 0.15 62) 0%, oklch(0.56 0.16 38) 100%);
--gradient-sage: linear-gradient(135deg, oklch(0.64 0.09 150) 0%, oklch(0.4 0.075 145) 100%);
--gradient-sky: linear-gradient(135deg, oklch(0.68 0.1 235) 0%, oklch(0.5 0.1 255) 100%);
--sidebar-foreground: oklch(0.21 0.03 55);
--sidebar-primary: oklch(0.8 0.145 78);
--sidebar-primary-foreground: oklch(0.21 0.035 55);
--sidebar-accent: oklch(0.935 0.03 78);
--sidebar-accent-foreground: oklch(0.25 0.045 50);
--sidebar-border: oklch(0.86 0.022 78);
--sidebar-ring: oklch(0.47 0.115 55);
--shadow-elevated: 0 1px 2px -1px oklch(0.3 0.05 50 / 8%), 0 8px 24px -8px oklch(0.3 0.05 50 / 16%);
--shadow-soft: 0 1px 2px -1px oklch(0.3 0.05 50 / 6%), 0 4px 12px -4px oklch(0.3 0.05 50 / 10%);
/* Hive "screen": always dark, in light and dark mode (spec 9.3). */
@@ -160,7 +157,7 @@
--holo-fill: oklch(0.78 0.14 220 / 10%);
--holo-label-bg: oklch(0.26 0.04 250);
--holo-label-fg: oklch(0.95 0.02 230);
/* T5 tokens, end values (spec 4.2); the old values above switch in task A4. */
/* T5 tokens (spec 4.2). */
--primary-ink: oklch(0.47 0.115 55);
--warning-ink: oklch(0.5 0.11 65);
--destructive-foreground: oklch(0.985 0.005 30);
@@ -188,54 +185,51 @@
}
.dark {
--background: oklch(0.2 0.018 55);
--foreground: oklch(0.94 0.02 80);
--card: oklch(0.25 0.02 55);
--card-foreground: oklch(0.94 0.02 80);
--popover: oklch(0.25 0.02 55);
--popover-foreground: oklch(0.94 0.02 80);
--primary: oklch(0.72 0.13 55);
--primary-foreground: oklch(0.2 0.02 55);
--secondary: oklch(0.62 0.075 145);
--secondary-foreground: oklch(0.15 0.02 145);
--muted: oklch(0.29 0.018 55);
--muted-foreground: oklch(0.7 0.025 75);
--accent: oklch(0.32 0.03 60);
--accent-foreground: oklch(0.94 0.02 80);
--destructive: oklch(0.62 0.16 30);
--border: oklch(1 0 0 / 12%);
--input: oklch(1 0 0 / 16%);
--ring: oklch(0.72 0.13 55);
--warning: oklch(0.74 0.13 75);
--background: oklch(0.19 0.016 55);
--foreground: oklch(0.95 0.018 80);
--card: oklch(0.235 0.018 55);
--card-foreground: oklch(0.95 0.018 80);
--popover: oklch(0.235 0.018 55);
--popover-foreground: oklch(0.95 0.018 80);
--primary: oklch(0.8 0.14 78);
--primary-foreground: oklch(0.19 0.03 55);
--secondary: oklch(0.66 0.08 145);
--secondary-foreground: oklch(0.16 0.02 145);
--muted: oklch(0.28 0.018 55);
--muted-foreground: oklch(0.76 0.025 75);
--accent: oklch(0.31 0.03 60);
--accent-foreground: oklch(0.95 0.018 80);
--destructive: oklch(0.7 0.15 28);
--border: oklch(0.34 0.02 60);
--input: oklch(0.56 0.025 70);
--ring: oklch(0.82 0.13 80);
--warning: oklch(0.8 0.14 80);
--warning-foreground: oklch(0.2 0.03 60);
--success: oklch(0.62 0.075 145);
--success-foreground: oklch(0.15 0.02 145);
--condition-excellent: oklch(0.66 0.11 145);
--condition-excellent-foreground: oklch(0.2 0.04 145);
--condition-good: oklch(0.72 0.12 118);
--condition-good-foreground: oklch(0.22 0.04 118);
--condition-average: oklch(0.76 0.13 90);
--condition-average-foreground: oklch(0.24 0.04 90);
--condition-weak: oklch(0.74 0.13 55);
--condition-weak-foreground: oklch(0.22 0.04 55);
--condition-critical: oklch(0.62 0.16 30);
--condition-critical-foreground: oklch(0.2 0.04 30);
--chart-1: oklch(0.72 0.13 55);
--chart-2: oklch(0.62 0.075 145);
--chart-3: oklch(0.74 0.13 75);
--chart-4: oklch(0.62 0.16 30);
--chart-5: oklch(0.65 0.06 250);
--sidebar: oklch(0.22 0.02 55);
--sidebar-foreground: oklch(0.94 0.02 80);
--sidebar-primary: oklch(0.72 0.13 55);
--sidebar-primary-foreground: oklch(0.2 0.02 55);
--sidebar-accent: oklch(0.32 0.03 60);
--sidebar-accent-foreground: oklch(0.94 0.02 80);
--sidebar-border: oklch(1 0 0 / 12%);
--sidebar-ring: oklch(0.72 0.13 55);
--gradient-honey: linear-gradient(135deg, oklch(0.62 0.14 55) 0%, oklch(0.38 0.13 40) 100%);
--gradient-sage: linear-gradient(135deg, oklch(0.5 0.08 148) 0%, oklch(0.3 0.06 145) 100%);
--gradient-sky: linear-gradient(135deg, oklch(0.55 0.09 235) 0%, oklch(0.34 0.09 255) 100%);
--success: oklch(0.7 0.09 145);
--success-foreground: oklch(0.16 0.02 145);
--condition-excellent: oklch(0.68 0.12 150);
--condition-excellent-foreground: oklch(0.17 0.04 150);
--condition-good: oklch(0.72 0.12 125);
--condition-good-foreground: oklch(0.18 0.04 125);
--condition-average: oklch(0.77 0.13 90);
--condition-average-foreground: oklch(0.2 0.04 90);
--condition-weak: oklch(0.74 0.13 60);
--condition-weak-foreground: oklch(0.19 0.04 55);
--condition-critical: oklch(0.68 0.16 28);
--condition-critical-foreground: oklch(0.16 0.04 30);
--chart-1: oklch(0.78 0.13 70);
--chart-2: oklch(0.7 0.09 145);
--chart-3: oklch(0.82 0.12 90);
--chart-4: oklch(0.7 0.15 28);
--chart-5: oklch(0.72 0.08 250);
--sidebar: oklch(0.235 0.018 55);
--sidebar-foreground: oklch(0.95 0.018 80);
--sidebar-primary: oklch(0.8 0.14 78);
--sidebar-primary-foreground: oklch(0.19 0.03 55);
--sidebar-accent: oklch(0.31 0.03 60);
--sidebar-accent-foreground: oklch(0.95 0.018 80);
--sidebar-border: oklch(0.34 0.02 60);
--sidebar-ring: oklch(0.82 0.13 80);
--shadow-elevated: 0 1px 2px -1px oklch(0 0 0 / 30%), 0 8px 24px -8px oklch(0 0 0 / 45%);
--shadow-soft: 0 1px 2px -1px oklch(0 0 0 / 25%), 0 4px 12px -4px oklch(0 0 0 / 30%);
/* Hive "screen": always dark, in light and dark mode (spec 9.3). */
@@ -245,7 +239,7 @@
--holo-fill: oklch(0.78 0.14 220 / 10%);
--holo-label-bg: oklch(0.26 0.04 250);
--holo-label-fg: oklch(0.95 0.02 230);
/* T5 tokens, end values (spec 4.2); the old values above switch in task A4. */
/* T5 tokens (spec 4.2). */
--primary-ink: oklch(0.82 0.13 80);
--warning-ink: oklch(0.82 0.13 82);
--destructive-foreground: oklch(0.17 0.03 30);
@@ -364,51 +358,51 @@
.dark,
:root[data-contrast="sun"],
.dark[data-contrast="sun"] {
--background: oklch(0.955 0.017 80);
--foreground: oklch(0.22 0.028 55);
--background: oklch(0.968 0.014 85);
--foreground: oklch(0.21 0.03 55);
--card: oklch(0.995 0.004 90);
--card-foreground: oklch(0.22 0.028 55);
--card-foreground: oklch(0.21 0.03 55);
--popover: oklch(0.995 0.004 90);
--popover-foreground: oklch(0.22 0.028 55);
--primary: oklch(0.6 0.15 48);
--primary-foreground: oklch(0.99 0.02 85);
--popover-foreground: oklch(0.21 0.03 55);
--primary: oklch(0.8 0.145 78);
--primary-foreground: oklch(0.21 0.035 55);
--secondary: oklch(0.44 0.07 145);
--secondary-foreground: oklch(0.98 0.015 100);
--muted: oklch(0.9 0.02 82);
--muted-foreground: oklch(0.48 0.032 65);
--accent: oklch(0.93 0.032 74);
--accent-foreground: oklch(0.26 0.045 50);
--destructive: oklch(0.5 0.16 30);
--border: oklch(0.85 0.024 78);
--input: oklch(0.85 0.024 78);
--ring: oklch(0.6 0.15 48);
--warning: oklch(0.68 0.14 75);
--warning-foreground: oklch(0.24 0.04 60);
--success: oklch(0.46 0.06 145);
--muted: oklch(0.925 0.018 82);
--muted-foreground: oklch(0.44 0.03 60);
--accent: oklch(0.935 0.03 78);
--accent-foreground: oklch(0.25 0.045 50);
--destructive: oklch(0.5 0.17 28);
--border: oklch(0.86 0.022 78);
--input: oklch(0.6 0.035 65);
--ring: oklch(0.47 0.115 55);
--warning: oklch(0.79 0.15 80);
--warning-foreground: oklch(0.23 0.04 60);
--success: oklch(0.46 0.08 145);
--success-foreground: oklch(0.98 0.015 100);
--condition-excellent: oklch(0.58 0.13 145);
--condition-excellent-foreground: oklch(0.16 0.04 145);
--condition-good: oklch(0.64 0.13 118);
--condition-good-foreground: oklch(0.26 0.05 118);
--condition-average: oklch(0.7 0.14 90);
--condition-average-foreground: oklch(0.28 0.05 90);
--condition-weak: oklch(0.68 0.15 55);
--condition-weak-foreground: oklch(0.26 0.05 55);
--condition-critical: oklch(0.64 0.16 30);
--condition-excellent: oklch(0.62 0.13 150);
--condition-excellent-foreground: oklch(0.15 0.04 150);
--condition-good: oklch(0.645 0.13 125);
--condition-good-foreground: oklch(0.17 0.04 125);
--condition-average: oklch(0.665 0.125 88);
--condition-average-foreground: oklch(0.19 0.04 90);
--condition-weak: oklch(0.665 0.14 58);
--condition-weak-foreground: oklch(0.18 0.04 55);
--condition-critical: oklch(0.62 0.17 30);
--condition-critical-foreground: oklch(0.15 0.04 30);
--chart-1: oklch(0.6 0.135 50);
--chart-2: oklch(0.46 0.06 145);
--chart-3: oklch(0.68 0.14 75);
--chart-4: oklch(0.5 0.16 30);
--chart-5: oklch(0.55 0.05 250);
--chart-1: oklch(0.56 0.13 55);
--chart-2: oklch(0.46 0.08 145);
--chart-3: oklch(0.6 0.12 85);
--chart-4: oklch(0.5 0.17 28);
--chart-5: oklch(0.52 0.08 250);
--sidebar: oklch(0.995 0.004 90);
--sidebar-foreground: oklch(0.22 0.028 55);
--sidebar-primary: oklch(0.6 0.15 48);
--sidebar-primary-foreground: oklch(0.99 0.02 85);
--sidebar-accent: oklch(0.93 0.032 74);
--sidebar-accent-foreground: oklch(0.26 0.045 50);
--sidebar-border: oklch(0.85 0.024 78);
--sidebar-ring: oklch(0.6 0.15 48);
--sidebar-foreground: oklch(0.21 0.03 55);
--sidebar-primary: oklch(0.8 0.145 78);
--sidebar-primary-foreground: oklch(0.21 0.035 55);
--sidebar-accent: oklch(0.935 0.03 78);
--sidebar-accent-foreground: oklch(0.25 0.045 50);
--sidebar-border: oklch(0.86 0.022 78);
--sidebar-ring: oklch(0.47 0.115 55);
--shadow-elevated: 0 1px 2px -1px oklch(0.3 0.05 50 / 8%), 0 8px 24px -8px oklch(0.3 0.05 50 / 16%);
--shadow-soft: 0 1px 2px -1px oklch(0.3 0.05 50 / 6%), 0 4px 12px -4px oklch(0.3 0.05 50 / 10%);
--holo-bg: oklch(0.18 0.03 250);
@@ -515,7 +509,7 @@
}
input,
textarea {
caret-color: var(--primary);
caret-color: var(--primary-ink);
}
}
@@ -587,7 +581,7 @@
position: absolute;
inset: 0.375rem;
border-radius: 0.6rem;
border: 2px solid color-mix(in oklch, var(--primary) 65%, var(--border));
border: 2px solid var(--input);
background: var(--card);
transition:
border-color 150ms ease-out,
@@ -62,7 +62,7 @@ interface InfoRowProps {
function InfoRow({ icon: Icon, label, value }: InfoRowProps) {
return (
<div className="flex items-start gap-2.5">
<Icon className="mt-0.5 size-4 shrink-0 text-primary" />
<Icon className="mt-0.5 size-4 shrink-0 text-primary-ink" />
<div>
<dt className="text-muted-foreground">{label}</dt>
<dd className="font-medium">{value}</dd>
@@ -80,7 +80,7 @@ interface StatProps {
function Stat({ icon: Icon, label, value }: StatProps) {
return (
<div className="flex flex-col items-center gap-1 rounded-xl bg-accent/40 px-3 py-3 text-center">
<Icon className="size-4 text-primary" />
<Icon className="size-4 text-primary-ink" />
<span className="text-sm font-semibold">{value}</span>
<span className="text-[0.7rem] text-muted-foreground">{label}</span>
</div>
@@ -137,7 +137,7 @@ export default function PublicBatchPage() {
{batch.productName ?? "Honig"}
</h1>
{batch.honeyType ? (
<span className="inline-flex items-center gap-1 rounded-full bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-primary">
<span className="inline-flex items-center gap-1 rounded-full bg-primary/10 px-2.5 py-0.5 text-xs font-medium text-primary-ink">
<Sparkles className="size-3" />
{batch.honeyType}
</span>
@@ -197,7 +197,7 @@ export default function PublicBatchPage() {
href={link.url}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1.5 rounded-full bg-primary/10 px-3 py-1.5 text-sm font-medium text-primary transition-colors hover:bg-primary/20"
className="flex items-center gap-1.5 rounded-full bg-primary/10 px-3 py-1.5 text-sm font-medium text-primary-ink transition-colors hover:bg-primary/20"
>
<Icon className="size-4" />
{link.label}