Files
tessera-ctl/apps/web/src/components/brand/tessera-logo.tsx
T
schalli ecff144449 feat(brand): ganzes T der Bildmarke übernimmt die Akzentfarbe – Kacheln als abgeleiteter dunklerer Ton
- BRAND_OLIVE_MIX (54 %, #363636) und BRAND_OLIVE_FILL in brand.ts, Kalibrierung kommentiert
- vier achsenparallele Kacheln in tessera-logo.tsx: Inline-Style BRAND_OLIVE_FILL, festes Oliv-Attribut als Rückfall
- brand.test.ts (neu): rechnet Kalibrierung, Neutralität und Grenzfälle aus denselben Konstanten nach
- tessera-logo.test.tsx: Kacheltest auf Token-Fuellung (1x) und abgeleitete Fuellung (4x) umgestellt

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-17 14:18:58 +02:00

186 lines
5.4 KiB
TypeScript

'use client';
import { useTranslations } from 'next-intl';
import { BRAND_OLIVE, BRAND_OLIVE_FILL, BRAND_PLATE, BRAND_YELLOW } from './brand';
export type TesseraLogoVariant = 'mark' | 'horizontal';
export interface TesseraLogoProps {
/** Welche Bausteine gerendert werden. Vorgabe: `'horizontal'`. */
variant?: TesseraLogoVariant;
/** Kantenlaenge der Bildmarke in Pixeln. Vorgabe: 32. */
size?: number;
/**
* Optionaler Untertitel unter dem Schriftzug, rechts neben der Bildmarke —
* die Anordnung der gelieferten Datei `tessera-logo-schriftzug.svg`. Ohne
* Angabe steht rechts nur der Name. Nur fuer `variant="horizontal"`.
*/
tagline?: string;
/**
* Schaltet die halbtransparente helle Kontur der Grundplatte im dunklen
* Erscheinungsbild ein. Auf Flaechen, die unabhaengig vom
* Erscheinungsbild immer hell sind (etwa das gelbe Anmelde-Panel), muss
* dies auf `false` gesetzt werden. Vorgabe: `true`.
*/
plateOutline?: boolean;
/** Zusaetzliche Klassen fuer das aeusserste Element der gewaehlten Variante. */
className?: string;
/** Zusaetzliche Klassen fuer den Schriftzug (nur `horizontal`). */
wordmarkClassName?: string;
/** Zusaetzliche Klassen fuer den Untertitel (nur zusammen mit `tagline`). */
taglineClassName?: string;
}
function LogoMark({
size,
plateOutline,
className,
ariaLabel,
}: {
size: number;
plateOutline: boolean;
className?: string;
ariaLabel: string;
}) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 72 72"
width={size}
height={size}
role="img"
aria-label={ariaLabel}
className={className}
>
<rect
x="4"
y="4"
width="64"
height="64"
rx="14"
fill={BRAND_PLATE}
strokeWidth={2}
className={
plateOutline
? 'stroke-transparent dark:stroke-white/25'
: 'stroke-transparent'
}
/>
{/*
Alle fuenf Kacheln folgen `--primary`: die gedrehte Signalkachel
direkt, die vier anderen als abgeleiteter dunklerer Ton
(BRAND_OLIVE_FILL, Kalibrierung in brand.ts). `var()`/`color-mix()`
sind in SVG-Praesentationsattributen nicht zuverlaessig, im
Inline-Style schon -- deshalb tragen alle fuenf Kacheln ihre
Akzentfarben-Fuellung als Inline-Style. Der feste Olivton bleibt an
den vier Kacheln zusaetzlich als Praesentationsattribut stehen: ein
Browser ohne `color-mix()`-Unterstuetzung verwirft die
Inline-Deklaration, und ohne dieses Attribut waere die Kachel
schwarz (SVG-Standardfuellung). `--primary` wird von
applyAccentColor() in auth-store.ts gesetzt und hat in globals.css
immer einen Standardwert (oklch(0.91 0.19 102), Markengelb).
*/}
<g>
<rect
x="16"
y="16"
width="12"
height="12"
rx="2.5"
fill={BRAND_OLIVE}
style={{ fill: BRAND_OLIVE_FILL }}
/>
<rect
x="30"
y="16"
width="12"
height="12"
rx="2.5"
fill={BRAND_OLIVE}
style={{ fill: BRAND_OLIVE_FILL }}
/>
<rect
x="45"
y="15"
width="12"
height="12"
rx="2.5"
transform="rotate(12 51 21)"
style={{ fill: `var(--primary, ${BRAND_YELLOW})` }}
/>
<rect
x="30"
y="30"
width="12"
height="12"
rx="2.5"
fill={BRAND_OLIVE}
style={{ fill: BRAND_OLIVE_FILL }}
/>
<rect
x="30"
y="44"
width="12"
height="12"
rx="2.5"
fill={BRAND_OLIVE}
style={{ fill: BRAND_OLIVE_FILL }}
/>
</g>
</svg>
);
}
/**
* Zentrale Bildmarken-Komponente von Tessera. Buendelt die Bildmarke, den
* Schriftzug und deren Anordnung an einer einzigen Stelle -- eine spaetere
* mandantenspezifische Gestaltung setzt hier an, nicht an den einzelnen
* Verwendungsstellen.
*
* Die gesamte Bildmarke folgt der persoenlichen Akzentfarbe (CSS-Token
* `--primary`, siehe LogoMark): die gedrehte Signalkachel direkt, die vier
* anderen als daraus abgeleiteter dunklerer Ton (siehe brand.ts).
*/
export function TesseraLogo({
variant = 'horizontal',
size = 32,
tagline,
plateOutline = true,
className,
wordmarkClassName,
taglineClassName,
}: TesseraLogoProps) {
const t = useTranslations('common');
const ariaLabel = t('brand.logoAlt');
if (variant === 'mark') {
return (
<LogoMark
size={size}
plateOutline={plateOutline}
className={className}
ariaLabel={ariaLabel}
/>
);
}
// Bildmarke links, Schriftzug rechts daneben. Traegt die Komponente einen
// Untertitel, steht er unter dem Namen — dieselbe Anordnung wie in der
// gelieferten Datei `tessera-logo-schriftzug.svg`.
return (
<span className={`inline-flex items-center gap-3 ${className ?? ''}`}>
<LogoMark size={size} plateOutline={plateOutline} ariaLabel={ariaLabel} />
<span className="inline-flex flex-col text-left">
<span className={`font-bold tracking-tight leading-none ${wordmarkClassName ?? ''}`}>
{t('appName')}
</span>
{tagline ? (
<span className={`mt-1 leading-snug ${taglineClassName ?? ''}`}>
{tagline}
</span>
) : null}
</span>
</span>
);
}