Die Oberflaeche auf das Manner-CD umstellen
Farben, Schrift, Logo und Symbole folgen jetzt dem CD Manual 2022. An der Logik aendert sich nichts: keine Migration, keine Abfrage, keine Berechtigung. Die zwei Markenfarben stehen in §1.1 als #F69686 (Rosa) und #164194 (Blau). Welche davon was traegt, ist nicht gewaehlt, sondern nachgerechnet: Weiss auf Rosa kommt auf 2.18:1 und faellt damit auch fuer Grossschrift durch, Blau auf Rosa auf 4.34:1 und reicht fuer das Logo, nicht fuer Text. Schwarz auf Rosa sind 7.47:1, Weiss auf Blau 9.47:1. Rosa ist deshalb Flaeche, Blau ist Interaktion — zwei Skalen, brand-* und accent-*, statt einer geteilten. Von den 26 Stellen mit brand-50/100/200 sind nur sieben auf accent gewandert. Der Rest meint einen Zustand und keinen Markenton: die Zeile unter dem Zeiger, der gewaehlte Listeneintrag, der aktive Menuepunkt. Gegen die warme Seitenflaeche ist ein kuehler Ton dort deutlicher, und Blau heisst in dieser Oberflaeche ab jetzt "reagiert auf dich". Nach accent gingen die Faelle ohne eigene Bedeutung — "Geplant", "Offen", der neutrale Protokoll-Chip — und die offene Planstelle im Organigramm, die vorher ein blasses Blau war und damit wie eine schwaechere Person aussah. Die Funktionsfarben bleiben, was sie sind. Das Manual regelt die Identitaet, nicht die Rueckmeldung: Rot heisst Fehler, weil die Benutzerin das mitbringt. `info` bleibt bewusst tuerkis — Blau saehe ab jetzt bedienbar aus, und gemessen kaeme ein blaues info dem violetten Chip auf dE 6.8 nahe, also nicht unterscheidbar. Violett ist dabei nachgezogen: gegen den Fehler-Chip stand es bei dE 8.0, "Austritt" und "Befoerderung" waren im Vorbeigehen dieselbe blasse Flaeche. Jetzt dE 15.0. Die Schrift ist Barlow. Nachgezaehlt ist das Manual zu 95 % in DIN gesetzt (Regular 79 %, Bold 16 %); Helvetica Neue steht nur in den Visitenkarten und im Claim. DIN laesst sich nicht ausliefern — eine Drucklizenz deckt keinen Webfont —, und die freien Nachbauten der DIN 1451 sind Schilderschriften, bei 14 px in einer langen Tabelle schlechter lesbar als das, was sie ersetzen. Die Variable heisst --font-din und nicht --font-barlow: liegt eines Tages eine Web-Lizenz vor, ist der Wechsel diese eine Deklaration. Nebenbei zwei Dinge repariert, die vorher schon falsch waren. Die Umrandung von Eingabefeldern, Knoepfen und Suchfeldern lag bei 1.30:1 und damit unter den 3:1, die WCAG 1.4.11 fuer Bedienelemente verlangt; border-strong bringt 3.56:1. Und das mitgelieferte favicon.ico liess sich gar nicht bauen: eingebettet waren 24-Bit-RGB-PNG, waehrend der Kopf 32 bpp behauptete. Alle Rastersymbole liegen jetzt als RGBA vor, und ihr Blau ist auf den CD-Wert gezogen — samt der kantengeglaetteten Raender, indem je Pixel der Blauanteil bestimmt und neu gemischt wurde. Das Logo ist das Markenlogo (§4.1), nicht das Unternehmenslogo, das §3.2 fuer eine Anwendung mit der AG als Absender vorsaehe — es liegt nicht vor. Der Freiraum X/3 steckt im Bauteil selbst und nicht in den Aufrufstellen, sonst haengt seine Einhaltung daran, dass jede einzelne daran denkt. docs/farbschema.html zeigt Token, Kontraste und Bauteile nebeneinander und laesst sich ohne Server oeffnen. Nicht im Browser gesehen: Anmeldung laeuft ueber das Firmenkonto und die Datenbank ist von hier nicht erreichbar. Lint, Typen, Schemaabgleich, 524 Tests und der Build sind sauber. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
import { EntraSignInButton } from "@/components/auth/EntraSignInButton";
|
||||
import { PasswortFormular } from "@/components/auth/PasswortFormular";
|
||||
import { AppWortmarke } from "@/components/brand/Logo";
|
||||
import { logout, signInWithEntra } from "@/actions/auth";
|
||||
|
||||
// The query string is attacker-controlled, so the login page renders a message
|
||||
@@ -56,8 +57,8 @@ export default async function LoginPage({ searchParams }: LoginPageProps) {
|
||||
|
||||
<main className="flex items-center justify-center px-6 py-12 lg:py-6">
|
||||
<div className="w-full max-w-sm">
|
||||
<div className="lg:hidden">
|
||||
<Wordmark className="text-ink" />
|
||||
<div className="-ml-2 lg:hidden">
|
||||
<AppWortmarke hoehe={26} textKlasse="text-ink" />
|
||||
</div>
|
||||
|
||||
<h2 className="mt-8 text-2xl font-extrabold tracking-tight text-ink lg:mt-0">Anmelden</h2>
|
||||
@@ -130,8 +131,10 @@ function BrandPanel() {
|
||||
}}
|
||||
/>
|
||||
|
||||
<div className="relative">
|
||||
<Wordmark className="text-white" />
|
||||
{/* -ml-4 gleicht den Freiraum des Logos aus, damit die Wortmarke
|
||||
buendig mit dem Text darunter beginnt und nicht eingerueckt wirkt. */}
|
||||
<div className="relative -ml-4">
|
||||
<AppWortmarke hoehe={36} textKlasse="text-white" />
|
||||
</div>
|
||||
|
||||
<div className="relative max-w-md">
|
||||
@@ -149,19 +152,6 @@ function BrandPanel() {
|
||||
);
|
||||
}
|
||||
|
||||
function Wordmark({ className = "" }: { className?: string }) {
|
||||
return (
|
||||
<div className={`flex items-center gap-2.5 ${className}`}>
|
||||
{/* Vier Quadrate wie ein Organigramm-Ausschnitt: eine Wurzel, darunter
|
||||
drei Einheiten. */}
|
||||
<svg viewBox="0 0 24 24" className="h-7 w-7" aria-hidden="true">
|
||||
<rect x="9" y="2" width="6" height="6" rx="1.5" fill="currentColor" />
|
||||
<rect x="1" y="16" width="6" height="6" rx="1.5" fill="currentColor" opacity="0.55" />
|
||||
<rect x="9" y="16" width="6" height="6" rx="1.5" fill="currentColor" opacity="0.75" />
|
||||
<rect x="17" y="16" width="6" height="6" rx="1.5" fill="currentColor" opacity="0.55" />
|
||||
<path d="M12 8v4M4 16v-4h16v4" stroke="currentColor" strokeWidth="1.5" fill="none" opacity="0.5" />
|
||||
</svg>
|
||||
<span className="text-lg font-extrabold tracking-tight">Alpenwerk HR</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
// Die vier Quadrate, die hier als Platzhalter-Bildmarke standen, sind
|
||||
// entfallen: an ihrer Stelle steht jetzt das Manner-Logo
|
||||
// (components/brand/Logo.tsx).
|
||||
|
||||
BIN
app/apple-icon.png
Normal file
BIN
app/apple-icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.0 KiB |
BIN
app/favicon.ico
BIN
app/favicon.ico
Binary file not shown.
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 5.4 KiB |
@@ -6,6 +6,12 @@ import { useEffect } from "react";
|
||||
// (app)/error.tsx is not mounted yet. It replaces <html>, so it cannot use
|
||||
// the app's fonts, Tailwind layer or shared components — hence the inline
|
||||
// styles. Kept deliberately plain; anything clever here can fail too.
|
||||
//
|
||||
// Die Farben sind dieselben wie in app/globals.css, nur von Hand: surface,
|
||||
// border, ink, ink-body, ink-muted und brand-500. Sie stehen hier doppelt,
|
||||
// weil diese Seite gerade dann erscheint, wenn das Stylesheet nicht
|
||||
// getragen hat — eine Variable waere hier genau das, worauf man sich nicht
|
||||
// verlassen kann.
|
||||
export default function GlobalError({ error, reset }: { error: Error & { digest?: string }; reset: () => void }) {
|
||||
useEffect(() => {
|
||||
console.error("Global error:", error);
|
||||
@@ -13,19 +19,19 @@ export default function GlobalError({ error, reset }: { error: Error & { digest?
|
||||
|
||||
return (
|
||||
<html lang="de-AT">
|
||||
<body style={{ margin: 0, backgroundColor: "#f9f1f5", fontFamily: "system-ui, sans-serif", color: "#2d1c26" }}>
|
||||
<div style={{ maxWidth: 480, margin: "15vh auto", padding: 24, background: "#fff", border: "1px solid #eedde6", borderRadius: 8 }}>
|
||||
<body style={{ margin: 0, backgroundColor: "#fdf6f4", fontFamily: "system-ui, sans-serif", color: "#231f20" }}>
|
||||
<div style={{ maxWidth: 480, margin: "15vh auto", padding: 24, background: "#fff", border: "1px solid #f0dbd4", borderRadius: 8 }}>
|
||||
<h1 style={{ fontSize: 18, margin: "0 0 8px" }}>Die Anwendung konnte nicht geladen werden</h1>
|
||||
<p style={{ fontSize: 14, lineHeight: 1.5, color: "#503b47", margin: "0 0 16px" }}>
|
||||
<p style={{ fontSize: 14, lineHeight: 1.5, color: "#4b4446", margin: "0 0 16px" }}>
|
||||
Es ist ein unerwarteter Fehler aufgetreten. Ihre Daten sind davon nicht betroffen.
|
||||
</p>
|
||||
{error.digest && (
|
||||
<p style={{ fontSize: 12, fontFamily: "monospace", color: "#7a636f", margin: "0 0 16px" }}>Fehlerkennung: {error.digest}</p>
|
||||
<p style={{ fontSize: 12, fontFamily: "monospace", color: "#756c6e", margin: "0 0 16px" }}>Fehlerkennung: {error.digest}</p>
|
||||
)}
|
||||
<button
|
||||
onClick={reset}
|
||||
style={{
|
||||
background: "#d6046e",
|
||||
background: "#164194",
|
||||
color: "#fff",
|
||||
border: "none",
|
||||
borderRadius: 4,
|
||||
|
||||
@@ -1,23 +1,74 @@
|
||||
@import "tailwindcss";
|
||||
|
||||
/* Design tokens from spec §7 (ported from tailwind.config.ts for Tailwind v4) */
|
||||
/* ═══ Farben nach dem Manner CD Manual 2022 ═══════════════════════════
|
||||
§1.1 kennt genau zwei Markenfarben:
|
||||
|
||||
Manner Rosa #F69686 CMYK 0/50/40/0 sRGB 246 150 134
|
||||
Manner Blau #164194 CMYK 100/80/0/0 sRGB 22 65 148 (Reflex Blue)
|
||||
|
||||
Beide behalten ihren Wert unveraendert; die Abstufungen daneben sind
|
||||
Aufhellungen und Abdunklungen davon, weil eine Oberflaeche Zwischentoene
|
||||
fuer Hover, Chips und Flaechen braucht und das Manual nur fuer den Druck
|
||||
geschrieben ist.
|
||||
|
||||
── Warum Blau die Interaktion traegt und Rosa die Flaeche ──
|
||||
Nachgerechnet nach WCAG 2.1:
|
||||
|
||||
Weiss auf Rosa 2.18:1 faellt durch, auch fuer Grossschrift
|
||||
Schwarz auf Rosa 7.47:1 AAA
|
||||
Weiss auf Blau 9.47:1 AAA
|
||||
Blau auf Weiss 9.47:1 AAA
|
||||
|
||||
Rosa kann also keine Schrift tragen — weder weisse noch, bei 4.34:1,
|
||||
blaue Fliesschrift. Es ist eine Flaeche, auf der Dunkles steht. Blau
|
||||
traegt in beide Richtungen und wird damit zur Farbe von Knopf, Link und
|
||||
Fokusring. Das ist keine Auslegung des Manuals, sondern das, was die
|
||||
beiden Farben physikalisch hergeben. */
|
||||
@theme {
|
||||
--color-brand-50: #fdf3f8;
|
||||
--color-brand-100: #fdeaf3;
|
||||
--color-brand-200: #f6cfe2;
|
||||
--color-brand-500: #d6046e;
|
||||
--color-brand-600: #b0035a;
|
||||
--color-brand-700: #a30354;
|
||||
/* ── Blau: alles, was man anfassen kann ── */
|
||||
--color-brand-50: #eef3fb;
|
||||
--color-brand-100: #dae4f5;
|
||||
--color-brand-200: #afc3e6;
|
||||
--color-brand-500: #164194; /* Manner Blau, CD §1.1 */
|
||||
--color-brand-600: #11326f;
|
||||
--color-brand-700: #0d2857;
|
||||
|
||||
--color-surface: #f9f1f5;
|
||||
/* ── Rosa: alles, was die Marke traegt ──
|
||||
accent-500 ist der Markenwert und bleibt Flaeche. Die Schrift darauf
|
||||
ist ink (7.47:1), nie Weiss. */
|
||||
--color-accent-50: #fef5f2;
|
||||
--color-accent-100: #fde7e1;
|
||||
--color-accent-200: #facec2;
|
||||
--color-accent-500: #f69686; /* Manner Rosa, CD §1.1 */
|
||||
--color-accent-600: #d9604b;
|
||||
--color-accent-700: #a8402e;
|
||||
|
||||
--color-border: #eedde6;
|
||||
--color-border-subtle: #f7e8ef;
|
||||
--color-surface: #fdf6f4;
|
||||
|
||||
--color-ink: #2d1c26;
|
||||
--color-ink-body: #503b47;
|
||||
--color-ink-muted: #7a636f;
|
||||
--color-border: #f0dbd4;
|
||||
--color-border-subtle: #f8ece8;
|
||||
/* Fuer die Umrandung von Eingabefeldern. Der dekorative Kartenrahmen
|
||||
darueber darf blass sein; die Grenze eines Bedienelements muss nach
|
||||
WCAG 1.4.11 3:1 erreichen, und das tut dieser Ton mit 3.56:1 gegen
|
||||
Weiss und 3.34:1 gegen die Seitenflaeche. */
|
||||
--color-border-strong: #a67f70;
|
||||
|
||||
/* Das Schwarz aus dem Manual selbst (#231F20), nicht reines Schwarz. */
|
||||
--color-ink: #231f20;
|
||||
--color-ink-body: #4b4446;
|
||||
--color-ink-muted: #756c6e;
|
||||
|
||||
/* ── Funktionsfarben ──
|
||||
Sie bleiben, was sie sind. Das Manual regelt die Identitaet, nicht die
|
||||
Rueckmeldung: Rot heisst Fehler, weil die Benutzerin das mitbringt,
|
||||
nicht weil es jemand entworfen hat. In einem System, in dem ein Knopf
|
||||
einen Austritt erfasst, waere eine markenkonforme Fehlerfarbe kein
|
||||
Stil, sondern ein Mangel.
|
||||
|
||||
`info` bleibt bewusst tuerkis statt blau zu werden: Blau heisst ab
|
||||
jetzt „hier kann man klicken", und ein Hinweischip in derselben Farbe
|
||||
saehe bedienbar aus. Gemessen kommt dazu, dass ein blaues info dem
|
||||
violetten Chip zu nahe kaeme (dE 6.8 — nicht unterscheidbar). */
|
||||
--color-success-bg: #dff6dd;
|
||||
--color-success-text: #0e700e;
|
||||
|
||||
@@ -31,8 +82,11 @@
|
||||
--color-info-bg: #d7f0f0;
|
||||
--color-info-text: #00666d;
|
||||
|
||||
--color-purple-bg: #f0ecf7;
|
||||
--color-purple-text: #5c2e91;
|
||||
/* Nachgezogen: gegen den alten Wert #f0ecf7 stand der Fehler-Chip bei
|
||||
dE 8.0 — „Austritt" und „Befoerderung" waren im Vorbeigehen dieselbe
|
||||
blasse Flaeche. Jetzt dE 15.0 bei 7.9:1 Kontrast. */
|
||||
--color-purple-bg: #e4d9f5;
|
||||
--color-purple-text: #54277f;
|
||||
|
||||
/* A bare `--radius` in Tailwind v4 collapses the whole scale onto one
|
||||
value — `rounded` and `rounded-lg` both came out at 8px, so a chip, an
|
||||
@@ -46,12 +100,17 @@
|
||||
|
||||
/* Warm, brand-tinted rather than neutral grey: a pure black shadow over
|
||||
the pink surface reads as dirt. Kept shallow — this is a data-dense
|
||||
admin UI, the depth only has to separate a card from the page. */
|
||||
--shadow-card: 0 1px 2px rgb(107 33 71 / 0.04), 0 1px 3px rgb(107 33 71 / 0.06);
|
||||
--shadow-card-hover: 0 2px 4px rgb(107 33 71 / 0.06), 0 4px 12px rgb(107 33 71 / 0.08);
|
||||
--shadow-overlay: 0 8px 32px rgb(45 28 38 / 0.16);
|
||||
admin UI, the depth only has to separate a card from the page.
|
||||
Der Grundton folgt dem Rosa der Flaeche, nicht mehr dem alten Magenta. */
|
||||
--shadow-card: 0 1px 2px rgb(122 60 45 / 0.05), 0 1px 3px rgb(122 60 45 / 0.07);
|
||||
--shadow-card-hover: 0 2px 4px rgb(122 60 45 / 0.07), 0 4px 12px rgb(122 60 45 / 0.09);
|
||||
--shadow-overlay: 0 8px 32px rgb(35 31 32 / 0.18);
|
||||
|
||||
--font-sans: var(--font-nunito), system-ui, sans-serif;
|
||||
/* Siehe app/layout.tsx: DIN waere die Schrift des Manuals, ist aber nicht
|
||||
als Webfont lizenziert; geladen wird Barlow als naechstliegender freier
|
||||
Ersatz. Die Ersatzkette bleibt neutral — faellt der Webfont aus, soll
|
||||
keine gerundete Systemschrift einspringen. */
|
||||
--font-sans: var(--font-din), "Segoe UI", system-ui, sans-serif;
|
||||
}
|
||||
|
||||
body {
|
||||
|
||||
315
app/icon.svg
Normal file
315
app/icon.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 73 KiB |
@@ -1,11 +1,33 @@
|
||||
import type { Metadata, Viewport } from "next";
|
||||
import { Nunito } from "next/font/google";
|
||||
import { Barlow } from "next/font/google";
|
||||
import { ToastProvider } from "@/components/ui/Toast";
|
||||
import "./globals.css";
|
||||
|
||||
const nunito = Nunito({
|
||||
variable: "--font-nunito",
|
||||
subsets: ["latin"],
|
||||
// ═══ Die Schrift ═══
|
||||
//
|
||||
// Das CD Manual ist selbst in DIN gesetzt: nachgezaehlt tragen DIN-Regular
|
||||
// 79 % und DIN-Bold 16 % des Textes, zusammen 95 %. Helvetica Neue taucht nur
|
||||
// in den Visitenkarten-Mustern und im Claim auf.
|
||||
//
|
||||
// DIN laesst sich hier trotzdem nicht einsetzen: FF DIN und DIN Next sind
|
||||
// lizenzpflichtig, und eine Druck-Lizenz deckt die Auslieferung als Webfont
|
||||
// nicht ab — das ist eine eigene, nach Domain oder Abrufen bemessene Lizenz.
|
||||
// Die freien Nachbauten der DIN 1451 sind Schilderschriften: enge Punzen,
|
||||
// wenige Schnitte, und bei 14 px in einer Tabelle mit achthundert Zeilen
|
||||
// schlechter lesbar als das, was sie ersetzen sollen.
|
||||
//
|
||||
// Barlow ist die naechstliegende freie Schrift mit UI-Tauglichkeit: dieselbe
|
||||
// Grotesk-Familie, schmale Proportionen, geringer Strichkontrast, gerade
|
||||
// Abschluesse — und neun Schnitte mit vollem Latin-Extended, also allen
|
||||
// Umlauten.
|
||||
//
|
||||
// Die Variable heisst nach der Absicht und nicht nach dem Ersatz: liegt
|
||||
// eines Tages eine Web-Lizenz fuer DIN vor, ist der Wechsel diese eine
|
||||
// Deklaration und sonst nichts.
|
||||
const din = Barlow({
|
||||
variable: "--font-din",
|
||||
// latin-ext wegen der Umlaute und des scharfen S, die in Namen vorkommen.
|
||||
subsets: ["latin", "latin-ext"],
|
||||
weight: ["400", "600", "700", "800"],
|
||||
});
|
||||
|
||||
@@ -15,6 +37,11 @@ export const metadata: Metadata = {
|
||||
// Added to the home screen on iOS this opens without Safari's chrome and
|
||||
// keeps the app's own name rather than the page title.
|
||||
appleWebApp: { capable: true, title: "Alpenwerk HR", statusBarStyle: "default" },
|
||||
// app/favicon.ico, app/icon.svg und app/apple-icon.png findet Next von
|
||||
// selbst; die Datei-Konventionen brauchen keinen Eintrag hier. Der Verweis
|
||||
// auf das Manifest dagegen schon — app/manifest.webmanifest wird sonst
|
||||
// zwar ausgeliefert, aber von keiner Seite genannt.
|
||||
manifest: "/manifest.webmanifest",
|
||||
};
|
||||
|
||||
export const viewport: Viewport = {
|
||||
@@ -23,7 +50,9 @@ export const viewport: Viewport = {
|
||||
// The layout extends under the notch and home indicator; every edge that
|
||||
// matters pads itself back out with env(safe-area-inset-*).
|
||||
viewportFit: "cover",
|
||||
themeColor: "#ffffff",
|
||||
// Manner Rosa (CD §1.1): die Farbe, in der mobile Browser ihre eigene
|
||||
// Leiste ueber der Seite einfaerben. Weiss war hier nur die Vorgabe.
|
||||
themeColor: "#F69686",
|
||||
};
|
||||
|
||||
export default function RootLayout({
|
||||
@@ -32,7 +61,7 @@ export default function RootLayout({
|
||||
children: React.ReactNode;
|
||||
}>) {
|
||||
return (
|
||||
<html lang="de-AT" className={`${nunito.variable} h-full antialiased`}>
|
||||
<html lang="de-AT" className={`${din.variable} h-full antialiased`}>
|
||||
<body className="flex min-h-dvh flex-col font-sans">
|
||||
<ToastProvider>{children}</ToastProvider>
|
||||
</body>
|
||||
|
||||
29
app/manifest.webmanifest
Normal file
29
app/manifest.webmanifest
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"name": "Alpenwerk HR",
|
||||
"short_name": "Alpenwerk HR",
|
||||
"description": "Interne HR-Stammdatenverwaltung",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/brand/icon-192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/brand/icon-512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "any"
|
||||
},
|
||||
{
|
||||
"src": "/brand/icon-512-maskable.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png",
|
||||
"purpose": "maskable"
|
||||
}
|
||||
],
|
||||
"theme_color": "#F69686",
|
||||
"background_color": "#FDF6F4",
|
||||
"display": "standalone",
|
||||
"start_url": "/"
|
||||
}
|
||||
Reference in New Issue
Block a user