feat(web): Modul-Identitaet mit App-Kacheln und deutschen Kategorienamen

Jedes Modul bekommt Farbe und Symbol (tender-radar Ocker, dkv-fleet
Petrol, cert-manager Violett, domaincheck Blau, proxmox Rotorange,
unbekannte stabil per Hash). Kategorien in Alltagssprache; dazu die
Texte fuer die neue Anmeldeseite.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-25 14:32:37 +02:00
parent 3c62a1a29c
commit 7192be223a
5 changed files with 189 additions and 4 deletions
+53
View File
@@ -0,0 +1,53 @@
/**
* Modul-Identitaet (Design „Mosaik“): jedes Modul bekommt eine feste Farbe
* und ein Symbol — wie die App-Kacheln bei Microsoft 365. Die Kachel wird in
* der Seitenleiste (20 px), auf den Marktplatz-Karten (40 px) und im Kopf der
* Modulseiten (36 px) gezeigt, immer mit weissem Symbol.
*
* Kontrast weisses Symbol gegen Kachel (WCAG, gemessen): tender-radar 3,8:1
* (Ockerton gegenueber dem Entwurf oklch(0.68 0.14 70) = 2,96:1 abgedunkelt),
* dkv-fleet 4,5:1, cert-manager 5,1:1, domaincheck 4,3:1, proxmox 4,6:1,
* Reserve gruen 4,1:1, Reserve pink 4,3:1 — alle ueber den geforderten 3:1
* fuer grafische Elemente.
*/
export type ModuleIconId =
| 'radar'
| 'fuel'
| 'certificate'
| 'globe'
| 'server'
| 'tile';
export interface ModuleIdentity {
/** Kachelfarbe als OKLCH-Farbwert. */
color: string;
/** Symbol auf der Kachel. */
icon: ModuleIconId;
}
const KNOWN: Record<string, ModuleIdentity> = {
'tender-radar': { color: 'oklch(0.62 0.135 68)', icon: 'radar' },
'dkv-fleet': { color: 'oklch(0.56 0.09 195)', icon: 'fuel' },
'cert-manager': { color: 'oklch(0.55 0.14 292)', icon: 'certificate' },
domaincheck: { color: 'oklch(0.58 0.14 250)', icon: 'globe' },
proxmox: { color: 'oklch(0.58 0.16 35)', icon: 'server' },
};
/** Reservefarben fuer unbekannte Module (stabil per Kennungs-Hash). */
const RESERVE = ['oklch(0.58 0.12 150)', 'oklch(0.60 0.15 350)'] as const;
function stableHash(value: string): number {
let hash = 0;
for (let i = 0; i < value.length; i++) {
hash = (hash * 31 + value.charCodeAt(i)) >>> 0;
}
return hash;
}
/** Farbe und Symbol eines Moduls; unbekannte Kennungen bekommen stabil eine Reservefarbe. */
export function getModuleIdentity(slug: string): ModuleIdentity {
const known = KNOWN[slug];
if (known) return known;
return { color: RESERVE[stableHash(slug) % RESERVE.length], icon: 'tile' };
}
+17
View File
@@ -0,0 +1,17 @@
'use client';
import { useTranslations } from 'next-intl';
import { useCallback } from 'react';
/**
* Liefert den Anzeigenamen einer Modulkategorie in Alltagssprache
* (`domain-tools` → „Domains“). Unbekannte Kategorien behalten ihre Kennung.
*/
export function useCategoryLabel(): (slug: string) => string {
const t = useTranslations('moduleCategories');
return useCallback(
// `t.has` fehlt in einfachen Test-Attrappen von next-intl — dann Kennung.
(slug: string) => (typeof t.has === 'function' && t.has(slug) ? t(slug) : slug),
[t],
);
}