ecff144449
- 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>
186 lines
5.4 KiB
TypeScript
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>
|
|
);
|
|
}
|