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>
This commit is contained in:
@@ -0,0 +1,157 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { BRAND_OLIVE, BRAND_OLIVE_FILL, BRAND_OLIVE_MIX, BRAND_YELLOW } from './brand';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* brand.test — Kalibrierung des abgeleiteten Olivtons der vier
|
||||||
|
* achsenparallelen Kacheln (quick-260917-jdf).
|
||||||
|
*
|
||||||
|
* Rechnet sRGB <-> OKLab nach CSS Color 4 (Matrizen von Bjoern Ottosson, wie
|
||||||
|
* sie Browser fuer `color-mix(in oklab, ...)` verwenden) und bildet damit
|
||||||
|
* exakt nach, was `BRAND_OLIVE_FILL` als CSS-Ausdruck im Browser bewirkt.
|
||||||
|
* Dieselbe Rechnung, dieselben Matrixzahlen wie in der Referenzrechnung des
|
||||||
|
* Planers (`.planning/quick/260917-jdf-.../260917-jdf-oklab-kalibrierung.cjs`),
|
||||||
|
* hier als typisierte lokale Funktionen -- kein neues Modul unter src, die
|
||||||
|
* Rechnung wird ausschliesslich hier gebraucht.
|
||||||
|
*/
|
||||||
|
|
||||||
|
type Rgb = [number, number, number];
|
||||||
|
type Lab = [number, number, number];
|
||||||
|
|
||||||
|
function hexToRgb(hex: string): Rgb {
|
||||||
|
const h = hex.replace('#', '');
|
||||||
|
const [r, g, b] = [0, 2, 4].map((i) => Number.parseInt(h.slice(i, i + 2), 16) / 255);
|
||||||
|
return [r, g, b];
|
||||||
|
}
|
||||||
|
|
||||||
|
function rgbToHex(rgb: Rgb): string {
|
||||||
|
return `#${rgb
|
||||||
|
.map((v) => Math.round(Math.min(1, Math.max(0, v)) * 255).toString(16).padStart(2, '0'))
|
||||||
|
.join('')}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function srgbToLinear(c: number): number {
|
||||||
|
return c <= 0.04045 ? c / 12.92 : ((c + 0.055) / 1.055) ** 2.4;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linearToSrgb(c: number): number {
|
||||||
|
return c <= 0.0031308 ? 12.92 * c : 1.055 * c ** (1 / 2.4) - 0.055;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** sRGB (0..1 je Kanal) -> OKLab [L, a, b]. */
|
||||||
|
function rgbToOklab([r, g, b]: Rgb): Lab {
|
||||||
|
const R = srgbToLinear(r);
|
||||||
|
const G = srgbToLinear(g);
|
||||||
|
const B = srgbToLinear(b);
|
||||||
|
const l = Math.cbrt(0.4122214708 * R + 0.5363325363 * G + 0.0514459929 * B);
|
||||||
|
const m = Math.cbrt(0.2119034982 * R + 0.6806995451 * G + 0.1073969566 * B);
|
||||||
|
const s = Math.cbrt(0.0883024619 * R + 0.2817188376 * G + 0.6299787005 * B);
|
||||||
|
return [
|
||||||
|
0.2104542553 * l + 0.793617785 * m - 0.0040720468 * s,
|
||||||
|
1.9779984951 * l - 2.428592205 * m + 0.4505937099 * s,
|
||||||
|
0.0259040371 * l + 0.7827717662 * m - 0.808675766 * s,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** OKLab [L, a, b] -> sRGB (0..1 je Kanal, ungeclippt). */
|
||||||
|
function oklabToRgb([L, a, b]: Lab): Rgb {
|
||||||
|
const l = (L + 0.3963377774 * a + 0.2158037573 * b) ** 3;
|
||||||
|
const m = (L - 0.1055613458 * a - 0.0638541728 * b) ** 3;
|
||||||
|
const s = (L - 0.0894841775 * a - 1.291485548 * b) ** 3;
|
||||||
|
return [
|
||||||
|
linearToSrgb(4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s),
|
||||||
|
linearToSrgb(-1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s),
|
||||||
|
linearToSrgb(-0.0041960863 * l - 0.7034186147 * m + 1.707614701 * s),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
function lchToLab([L, C, h]: [number, number, number]): Lab {
|
||||||
|
const r = (h * Math.PI) / 180;
|
||||||
|
return [L, C * Math.cos(r), C * Math.sin(r)];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Entspricht `color-mix(in oklab, A share%, B)`. */
|
||||||
|
function mixInOklabLab(labA: Lab, share: number, hexB: string): string {
|
||||||
|
const labB = rgbToOklab(hexToRgb(hexB));
|
||||||
|
const w = share / 100;
|
||||||
|
const mixed: Lab = [
|
||||||
|
w * labA[0] + (1 - w) * labB[0],
|
||||||
|
w * labA[1] + (1 - w) * labB[1],
|
||||||
|
w * labA[2] + (1 - w) * labB[2],
|
||||||
|
];
|
||||||
|
return rgbToHex(oklabToRgb(mixed));
|
||||||
|
}
|
||||||
|
|
||||||
|
function mixInOklab(hexA: string, share: number, hexB: string): string {
|
||||||
|
return mixInOklabLab(rgbToOklab(hexToRgb(hexA)), share, hexB);
|
||||||
|
}
|
||||||
|
|
||||||
|
const rgb255 = (hex: string) => hexToRgb(hex).map((v) => Math.round(v * 255));
|
||||||
|
|
||||||
|
/** Groesste Kanalabweichung (0..255) zwischen zwei Hex-Farben. */
|
||||||
|
function maxChannelDiff(hexA: string, hexB: string): number {
|
||||||
|
const a = rgb255(hexA);
|
||||||
|
const b = rgb255(hexB);
|
||||||
|
return Math.max(...a.map((v, i) => Math.abs(v - b[i])));
|
||||||
|
}
|
||||||
|
|
||||||
|
describe('Markenfarben — abgeleiteter Olivton (BRAND_OLIVE_MIX)', () => {
|
||||||
|
it('mixInOklab(BRAND_YELLOW, BRAND_OLIVE_MIX) trifft BRAND_OLIVE auf hoechstens 2 Einheiten je Kanal (erwartet 0/0/0, #9c9440)', () => {
|
||||||
|
const result = mixInOklab(BRAND_YELLOW, BRAND_OLIVE_MIX.primaryShare, BRAND_OLIVE_MIX.mixWith);
|
||||||
|
expect(result).toBe('#9c9440');
|
||||||
|
expect(maxChannelDiff(result, BRAND_OLIVE)).toBeLessThanOrEqual(2);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('BRAND_OLIVE_FILL matcht das color-mix-Muster und traegt genau die Zahlen aus BRAND_OLIVE_MIX', () => {
|
||||||
|
const match = BRAND_OLIVE_FILL.match(
|
||||||
|
/^color-mix\(in oklab, var\(--primary, (#[0-9a-f]{6})\) (\d+)%, (#[0-9a-f]{6})\)$/,
|
||||||
|
);
|
||||||
|
expect(match).not.toBeNull();
|
||||||
|
const [, primaryFallback, share, mixWith] = match as RegExpMatchArray;
|
||||||
|
expect(primaryFallback).toBe(BRAND_YELLOW);
|
||||||
|
expect(Number(share)).toBe(BRAND_OLIVE_MIX.primaryShare);
|
||||||
|
expect(mixWith).toBe(BRAND_OLIVE_MIX.mixWith);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('das Mischgrau ist in OKLab neutral — der Farbton der Akzentfarbe bleibt erhalten', () => {
|
||||||
|
const [, a, b] = rgbToOklab(hexToRgb(BRAND_OLIVE_MIX.mixWith));
|
||||||
|
expect(Math.abs(a)).toBeLessThan(1e-4);
|
||||||
|
expect(Math.abs(b)).toBeLessThan(1e-4);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('CSS-Standardwert von --primary (oklch(0.91 0.19 102), globals.css) liegt hoechstens 4 Einheiten je Kanal neben BRAND_OLIVE (erwartet #9a903f)', () => {
|
||||||
|
// Deckt sich mit dem :root-Standard von `--primary` in globals.css Z. 66;
|
||||||
|
// das <verify> des Plans sichert, dass globals.css ihn noch enthaelt.
|
||||||
|
const cssDefaultPrimary = lchToLab([0.91, 0.19, 102]);
|
||||||
|
const result = mixInOklabLab(cssDefaultPrimary, BRAND_OLIVE_MIX.primaryShare, BRAND_OLIVE_MIX.mixWith);
|
||||||
|
expect(result).toBe('#9a903f');
|
||||||
|
expect(maxChannelDiff(result, BRAND_OLIVE)).toBeLessThanOrEqual(4);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('Nebenpruefung Helligkeit — OKLab-L des Ergebnisses gegen L der Quelle', () => {
|
||||||
|
it('#0057b8 wird dunkler (erwartet #284a7b)', () => {
|
||||||
|
const lab = rgbToOklab(hexToRgb('#0057b8'));
|
||||||
|
const result = mixInOklabLab(lab, BRAND_OLIVE_MIX.primaryShare, BRAND_OLIVE_MIX.mixWith);
|
||||||
|
expect(result).toBe('#284a7b');
|
||||||
|
const [resultL] = rgbToOklab(hexToRgb(result));
|
||||||
|
expect(resultL).toBeLessThan(lab[0]);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#ffffff wird dunkler und bleibt neutral (erwartet #9c9c9c)', () => {
|
||||||
|
const lab = rgbToOklab(hexToRgb('#ffffff'));
|
||||||
|
const result = mixInOklabLab(lab, BRAND_OLIVE_MIX.primaryShare, BRAND_OLIVE_MIX.mixWith);
|
||||||
|
expect(result).toBe('#9c9c9c');
|
||||||
|
const [resultL, resultA, resultB] = rgbToOklab(hexToRgb(result));
|
||||||
|
expect(resultL).toBeLessThan(lab[0]);
|
||||||
|
expect(Math.abs(resultA)).toBeLessThan(1e-3);
|
||||||
|
expect(Math.abs(resultB)).toBeLessThan(1e-3);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('#000000 wird HELLER statt dunkler — dokumentiertes Kippen bei sehr dunklen Akzentfarben (erwartet #0c0c0c)', () => {
|
||||||
|
const lab = rgbToOklab(hexToRgb('#000000'));
|
||||||
|
const result = mixInOklabLab(lab, BRAND_OLIVE_MIX.primaryShare, BRAND_OLIVE_MIX.mixWith);
|
||||||
|
expect(result).toBe('#0c0c0c');
|
||||||
|
const [resultL] = rgbToOklab(hexToRgb(result));
|
||||||
|
expect(resultL).toBeGreaterThan(lab[0]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -5,7 +5,9 @@
|
|||||||
* notiert wird. Jede Stelle der Oberflaeche, die eine Markenfarbe braucht
|
* notiert wird. Jede Stelle der Oberflaeche, die eine Markenfarbe braucht
|
||||||
* (die Bildmarken-Komponente, das Anmelde-Panel, der Vorgabewert der
|
* (die Bildmarken-Komponente, das Anmelde-Panel, der Vorgabewert der
|
||||||
* persoenlichen Akzentfarbe), importiert sie von hier. Eine spaetere
|
* persoenlichen Akzentfarbe), importiert sie von hier. Eine spaetere
|
||||||
* mandantenspezifische Gestaltung setzt an dieser Datei an.
|
* mandantenspezifische Gestaltung setzt an dieser Datei an. Auch die
|
||||||
|
* Ableitungsregel fuer den Olivton der vier achsenparallelen Kacheln lebt
|
||||||
|
* hier (BRAND_OLIVE_MIX, BRAND_OLIVE_FILL).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -16,8 +18,48 @@
|
|||||||
*/
|
*/
|
||||||
export const BRAND_YELLOW = '#ffed00';
|
export const BRAND_YELLOW = '#ffed00';
|
||||||
|
|
||||||
/** Olivton der vier achsenparallelen Kacheln. */
|
/**
|
||||||
|
* Olivton der vier achsenparallelen Kacheln bei Standard-Gelb. In der
|
||||||
|
* Bildmarke seit quick-260917-jdf Kalibrierziel und Rueckfall
|
||||||
|
* (Praesentationsattribut fuer Browser ohne `color-mix()`) -- die
|
||||||
|
* eigentliche Fuellung leitet BRAND_OLIVE_FILL aus der Akzentfarbe ab.
|
||||||
|
* `apps/web/src/app/icon.svg` (Favicon) und die daraus erzeugten
|
||||||
|
* Tauri-Icons verwenden den Wert weiterhin fest.
|
||||||
|
*/
|
||||||
export const BRAND_OLIVE = '#9c9440';
|
export const BRAND_OLIVE = '#9c9440';
|
||||||
|
|
||||||
/** Farbe der abgerundeten Grundplatte. */
|
/** Farbe der abgerundeten Grundplatte. */
|
||||||
export const BRAND_PLATE = '#1a1a1a';
|
export const BRAND_PLATE = '#1a1a1a';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mischanteil der Akzentfarbe (in Prozent) und neutrales Mischgrau fuer den
|
||||||
|
* abgeleiteten Olivton der vier achsenparallelen Kacheln (quick-260917-jdf).
|
||||||
|
*
|
||||||
|
* Gesucht war `color-mix(in oklab, #ffed00 P%, #GRAU)` = `#9c9440`. Rechnung
|
||||||
|
* sRGB -> OKLab -> sRGB nach CSS Color 4 (Referenz:
|
||||||
|
* `260917-jdf-oklab-kalibrierung.cjs` im Quick-Ordner, Nachweis in
|
||||||
|
* brand.test.ts). Ergebnis: 54 % / #363636 -> `#9c9440` exakt (Abweichung
|
||||||
|
* 0/0/0 je Kanal). OKLCH-Verhaeltnis Gelb -> Oliv: L 0.931 -> 0.656,
|
||||||
|
* C 0.197 -> 0.106, Farbton 104 Grad gleich.
|
||||||
|
*
|
||||||
|
* Warum `oklab` statt `oklch`: mit neutralem Grau ist das Ergebnis
|
||||||
|
* identisch (Farbton bleibt, Chroma und L skalieren linear), aber ohne
|
||||||
|
* Abhaengigkeit von der Sonderregel fuer den Farbton achromatischer Farben
|
||||||
|
* in polaren Raeumen.
|
||||||
|
*
|
||||||
|
* Verhalten fuer andere Akzentfarben: CSS-Standard `oklch(0.91 0.19 102)`
|
||||||
|
* (globals.css) -> `#9a903f` (-2/-4/-1), #ffffff -> neutrales Grau
|
||||||
|
* `#9c9c9c`, #0057b8 -> `#284a7b`. Akzentfarben dunkler als das Mischgrau
|
||||||
|
* (OKLab-L < 0.333, z. B. Schwarz -> `#0c0c0c`) werden heller statt dunkler
|
||||||
|
* -- bewusst hingenommen, kein Schutzmechanismus.
|
||||||
|
*/
|
||||||
|
export const BRAND_OLIVE_MIX = { primaryShare: 54, mixWith: '#363636' } as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CSS-Ausdruck fuer den Inline-`fill` der vier achsenparallelen Kacheln in
|
||||||
|
* tessera-logo.tsx -- die einzige Stelle, an der dieser Ausdruck gebildet
|
||||||
|
* wird. `--primary` setzt `applyAccentColor` (auth-store.ts); der
|
||||||
|
* CSS-Standardwert steht in globals.css; der `var()`-Rueckfall
|
||||||
|
* (BRAND_YELLOW) greift nur, wenn gar kein Stylesheet geladen ist.
|
||||||
|
*/
|
||||||
|
export const BRAND_OLIVE_FILL = `color-mix(in oklab, var(--primary, ${BRAND_YELLOW}) ${BRAND_OLIVE_MIX.primaryShare}%, ${BRAND_OLIVE_MIX.mixWith})`;
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { cleanup, render, screen } from '@testing-library/react';
|
import { cleanup, render, screen } from '@testing-library/react';
|
||||||
import { afterEach, describe, expect, it, vi } from 'vitest';
|
import { afterEach, describe, expect, it, vi } from 'vitest';
|
||||||
import { BRAND_OLIVE, BRAND_YELLOW } from './brand';
|
import { BRAND_OLIVE, BRAND_OLIVE_FILL, BRAND_YELLOW } from './brand';
|
||||||
import { TesseraLogo } from './tessera-logo';
|
import { TesseraLogo } from './tessera-logo';
|
||||||
import de from '@/messages/de.json';
|
import de from '@/messages/de.json';
|
||||||
import en from '@/messages/en.json';
|
import en from '@/messages/en.json';
|
||||||
@@ -94,7 +94,7 @@ describe('TesseraLogo', () => {
|
|||||||
expect(plate?.getAttribute('class')).not.toContain('dark:stroke-white/25');
|
expect(plate?.getAttribute('class')).not.toContain('dark:stroke-white/25');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('renders exactly five tiles; only the rotated one is filled from the accent token with the brand yellow as fallback', () => {
|
it('renders exactly five tiles; the rotated one takes the accent token, the other four the derived olive tone with the fixed olive as fallback', () => {
|
||||||
render(<TesseraLogo variant="mark" />);
|
render(<TesseraLogo variant="mark" />);
|
||||||
|
|
||||||
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
const mark = screen.getByRole('img', { name: 'Tessera Logo' });
|
||||||
@@ -111,10 +111,15 @@ describe('TesseraLogo', () => {
|
|||||||
const presentationYellowTiles = tiles.filter((tile) => tile.getAttribute('fill') === BRAND_YELLOW);
|
const presentationYellowTiles = tiles.filter((tile) => tile.getAttribute('fill') === BRAND_YELLOW);
|
||||||
expect(presentationYellowTiles).toHaveLength(0);
|
expect(presentationYellowTiles).toHaveLength(0);
|
||||||
|
|
||||||
const oliveTiles = tiles.filter((tile) => tile.getAttribute('fill') === BRAND_OLIVE);
|
// BRAND_OLIVE_FILL haengt selbst am --primary-Token — Zusicherung, dass
|
||||||
expect(oliveTiles).toHaveLength(4);
|
// die vier Kacheln wirklich daran haengen und nicht an einer festen Farbe.
|
||||||
for (const tile of oliveTiles) {
|
expect(BRAND_OLIVE_FILL).toContain('var(--primary');
|
||||||
expect(tile.getAttribute('style')).toBeNull();
|
|
||||||
|
const derivedOliveTiles = tiles.filter((tile) => tile.style.fill === BRAND_OLIVE_FILL);
|
||||||
|
expect(derivedOliveTiles).toHaveLength(4);
|
||||||
|
for (const tile of derivedOliveTiles) {
|
||||||
|
expect(tile.getAttribute('fill')).toBe(BRAND_OLIVE);
|
||||||
|
expect(tile.hasAttribute('transform')).toBe(false);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { useTranslations } from 'next-intl';
|
import { useTranslations } from 'next-intl';
|
||||||
import { BRAND_OLIVE, BRAND_PLATE, BRAND_YELLOW } from './brand';
|
import { BRAND_OLIVE, BRAND_OLIVE_FILL, BRAND_PLATE, BRAND_YELLOW } from './brand';
|
||||||
|
|
||||||
export type TesseraLogoVariant = 'mark' | 'horizontal';
|
export type TesseraLogoVariant = 'mark' | 'horizontal';
|
||||||
|
|
||||||
@@ -66,16 +66,39 @@ function LogoMark({
|
|||||||
: 'stroke-transparent'
|
: 'stroke-transparent'
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<g>
|
|
||||||
<rect x="16" y="16" width="12" height="12" rx="2.5" fill={BRAND_OLIVE} />
|
|
||||||
<rect x="30" y="16" width="12" height="12" rx="2.5" fill={BRAND_OLIVE} />
|
|
||||||
{/*
|
{/*
|
||||||
`var()` ist in SVG-Praesentationsattributen (fill="...") nicht
|
Alle fuenf Kacheln folgen `--primary`: die gedrehte Signalkachel
|
||||||
zuverlaessig, im Inline-Style schon — deshalb hier style statt fill.
|
direkt, die vier anderen als abgeleiteter dunklerer Ton
|
||||||
`--primary` wird von applyAccentColor() in auth-store.ts gesetzt;
|
(BRAND_OLIVE_FILL, Kalibrierung in brand.ts). `var()`/`color-mix()`
|
||||||
ohne angemeldeten Nutzer (z. B. Anmeldeseite) ist der Token nicht
|
sind in SVG-Praesentationsattributen nicht zuverlaessig, im
|
||||||
definiert und der Rueckfall in var() greift: BRAND_YELLOW.
|
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
|
<rect
|
||||||
x="45"
|
x="45"
|
||||||
y="15"
|
y="15"
|
||||||
@@ -85,8 +108,24 @@ function LogoMark({
|
|||||||
transform="rotate(12 51 21)"
|
transform="rotate(12 51 21)"
|
||||||
style={{ fill: `var(--primary, ${BRAND_YELLOW})` }}
|
style={{ fill: `var(--primary, ${BRAND_YELLOW})` }}
|
||||||
/>
|
/>
|
||||||
<rect x="30" y="30" width="12" height="12" rx="2.5" fill={BRAND_OLIVE} />
|
<rect
|
||||||
<rect x="30" y="44" width="12" height="12" rx="2.5" fill={BRAND_OLIVE} />
|
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>
|
</g>
|
||||||
</svg>
|
</svg>
|
||||||
);
|
);
|
||||||
@@ -98,8 +137,9 @@ function LogoMark({
|
|||||||
* mandantenspezifische Gestaltung setzt hier an, nicht an den einzelnen
|
* mandantenspezifische Gestaltung setzt hier an, nicht an den einzelnen
|
||||||
* Verwendungsstellen.
|
* Verwendungsstellen.
|
||||||
*
|
*
|
||||||
* Die gedrehte Signalkachel der Bildmarke folgt der persoenlichen
|
* Die gesamte Bildmarke folgt der persoenlichen Akzentfarbe (CSS-Token
|
||||||
* Akzentfarbe (CSS-Token `--primary`, siehe LogoMark).
|
* `--primary`, siehe LogoMark): die gedrehte Signalkachel direkt, die vier
|
||||||
|
* anderen als daraus abgeleiteter dunklerer Ton (siehe brand.ts).
|
||||||
*/
|
*/
|
||||||
export function TesseraLogo({
|
export function TesseraLogo({
|
||||||
variant = 'horizontal',
|
variant = 'horizontal',
|
||||||
|
|||||||
Reference in New Issue
Block a user