Simplify header logo colors

codex/redisenio-dominio-musical
dev 4 weeks ago
parent b390dcf6c0
commit 3a75eb768d

@ -41,17 +41,25 @@ test.describe('Navegación', () => {
await expect(ultimo).toHaveAttribute('aria-label', /tema/); await expect(ultimo).toHaveAttribute('aria-label', /tema/);
}); });
test('el logo conserva el mismo metal al cambiar al tema claro', async ({ page }) => { test('el logo de cabecera usa blanco plano en oscuro y azul plano en claro', async ({ page }) => {
await page.goto('/'); await page.goto('/');
const logo = page.locator('header .nombre-marca'); const marca = page.locator('header .marca');
const oroOscuro = await logo.evaluate((elemento) => getComputedStyle(elemento).backgroundImage); const nombre = page.locator('header .nombre-marca');
const simbolo = page.locator('header [data-logo-senza-paura]');
await expect(marca).toHaveCSS('color', 'rgb(255, 255, 255)');
await expect(nombre).toHaveCSS('background-image', 'none');
await expect(simbolo).toHaveCSS('fill', 'rgb(255, 255, 255)');
await page.getByRole('button', { name: 'Cambiar al tema claro' }).click(); await page.getByRole('button', { name: 'Cambiar al tema claro' }).click();
await expect(page.locator('html')).toHaveAttribute('data-tema', 'claro'); await expect(page.locator('html')).toHaveAttribute('data-tema', 'claro');
const oroClaro = await logo.evaluate((elemento) => getComputedStyle(elemento).backgroundImage); const azul = await page
expect(oroClaro).toBe(oroOscuro); .locator('body')
.evaluate((elemento) => getComputedStyle(elemento).color);
await expect(marca).toHaveCSS('color', azul);
await expect(nombre).toHaveCSS('background-image', 'none');
await expect(simbolo).toHaveCSS('fill', azul);
}); });
test('el símbolo de marca aparece en la cabecera y en el pie', async ({ page }) => { test('el símbolo de marca aparece en la cabecera y en el pie', async ({ page }) => {

@ -188,7 +188,7 @@ test.describe('Respuesta al puntero', () => {
}); });
}); });
test.describe('Contraste del dorado', () => { test.describe('Contraste y color de marca', () => {
// El dorado hace dos trabajos y no puede ser el mismo valor en los dos: de // El dorado hace dos trabajos y no puede ser el mismo valor en los dos: de
// relleno tiene que ser claro para que encima se lea el marino, y escrito // relleno tiene que ser claro para que encima se lea el marino, y escrito
// tiene que ser hondo para leerse sobre el fondo. Esto lo fija. // tiene que ser hondo para leerse sobre el fondo. Esto lo fija.
@ -278,58 +278,25 @@ test.describe('Contraste del dorado', () => {
await expect(page.locator('html')).toHaveAttribute('data-tema', 'claro'); await expect(page.locator('html')).toHaveAttribute('data-tema', 'claro');
}); });
/* /* El identificador de cabecera es monocromo y no depende de degradados. */
* El símbolo ya es un SVG vectorial autocontenido: el disco negro y el oro
* viajan dentro del propio recurso. Por eso el tema no debe teñirlo. Además
* de comprobar que siempre carga el mismo SVG, se mide cada parada del oro
* contra el disco sobre el que realmente se dibuja.
*/
for (const modo of ['oscuro', 'claro'] as const) { for (const modo of ['oscuro', 'claro'] as const) {
test(`el logo conserva su oro y contraste en tema ${modo}`, async ({ page }) => { test(`el logo plano conserva el contraste en tema ${modo}`, async ({ page }) => {
await page.goto('/'); await page.goto('/');
await page.evaluate((m) => { await page.evaluate((m) => {
document.documentElement.dataset.tema = m; document.documentElement.dataset.tema = m;
}, modo); }, modo);
const peor = await page.evaluate(async () => { const resultado = await page.evaluate(() => {
const logo = document.querySelector<HTMLImageElement>('header [data-logo-senza-paura]')!; const logo = document.querySelector<SVGElement>('header [data-logo-senza-paura]')!;
const respuesta = await fetch(logo.src); const fondo = document.querySelector<HTMLElement>('header')!;
const documento = new DOMParser().parseFromString(await respuesta.text(), 'image/svg+xml'); const color = getComputedStyle(logo).color;
const colores = [...documento.querySelectorAll('linearGradient stop')].map((stop) => const base = getComputedStyle(fondo).backgroundColor;
stop.getAttribute('stop-color') return { color, base, etiqueta: logo.tagName, relleno: getComputedStyle(logo).fill };
);
const fondo = documento.querySelector('circle')?.getAttribute('fill');
const pincel = document.createElement('canvas').getContext('2d')!;
const aRgb = (c: string | null) => {
if (!c) return [0, 0, 0];
pincel.fillStyle = c;
pincel.fillRect(0, 0, 1, 1);
const d = pincel.getImageData(0, 0, 1, 1).data;
return [d[0], d[1], d[2]];
};
const lum = (rgb: number[]) => {
const f = (v: number) => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : ((v + 0.055) / 1.055) ** 2.4;
};
return 0.2126 * f(rgb[0]) + 0.7152 * f(rgb[1]) + 0.0722 * f(rgb[2]);
};
const contraste = (c: string | null) => {
const [a, b] = [lum(aRgb(c)), lum(aRgb(fondo))].sort((m, n) => n - m);
return (a + 0.05) / (b + 0.05);
};
return {
recurso: new URL(logo.src).pathname,
cuantas: colores.length,
minimo: Math.min(...colores.map(contraste))
};
}); });
expect(peor.recurso).toMatch(/senza-paura-logo.*\.svg$/); expect(resultado.etiqueta).toBe('svg');
expect(peor.cuantas).toBeGreaterThanOrEqual(3); expect(resultado.relleno).toBe(resultado.color);
expect(peor.minimo).toBeGreaterThan(MINIMO); expect(resultado.color).not.toBe(resultado.base);
}); });
} }

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.7 KiB

After

Width:  |  Height:  |  Size: 8.7 KiB

@ -88,7 +88,7 @@
<header class="cabecera" class:desplazada bind:this={cabecera}> <header class="cabecera" class:desplazada bind:this={cabecera}>
<div class="container fila"> <div class="container fila">
<a class="marca" href={resolve('/')}> <a class="marca" href={resolve('/')}>
<LogoSenzaPaura lado={38} /> <LogoSenzaPaura lado={38} plano />
<span class="nombre-marca">{site.nombre}</span> <span class="nombre-marca">{site.nombre}</span>
</a> </a>
@ -258,10 +258,14 @@
font-weight: 600; font-weight: 600;
line-height: 1.15; line-height: 1.15;
letter-spacing: 0.01em; letter-spacing: 0.01em;
color: var(--c-marca-oro); color: #fff;
text-decoration: none; text-decoration: none;
} }
:global(:root[data-tema='claro']) .marca {
color: var(--c-text);
}
/* /*
* La caja tipográfica sí queda centrada con `align-items`, pero Newsreader * La caja tipográfica sí queda centrada con `align-items`, pero Newsreader
* concentra más tinta por encima de la línea base. Este pequeño ajuste * concentra más tinta por encima de la línea base. Este pequeño ajuste
@ -273,41 +277,6 @@
transform: translateY(0.07em); transform: translateY(0.07em);
} }
/*
* El mismo oro de los botones, aquí recortado sobre las letras.
*
* Va dentro de un `@supports` y el color plano se queda fuera a propósito:
* `color: transparent` sin el recorte dejaría el nombre invisible, que es
* mucho peor que dejarlo dorado y liso. Es el mismo cuidado que tiene el
* botón, que guarda un color de reserva por si el degradado no llega a
* pintar.
*
* Las cinco paradas, el ángulo y el alto del fondo son los suyos, para que
* sea el mismo metal y no un dorado parecido. Y como en el botón, al pasar
* el puntero la banda de luz recorre las letras en vez de aclararlas.
*/
@supports (background-clip: text) or (-webkit-background-clip: text) {
.nombre-marca {
background-image: var(--oro-escrito);
background-size: 100% 240%;
background-position: 0 22%;
-webkit-background-clip: text;
background-clip: text;
color: transparent;
transition: background-position 0.6s var(--sal);
}
.marca:hover .nombre-marca {
background-position: 0 78%;
}
}
@media (prefers-reduced-motion: reduce) {
.nombre-marca {
transition: none;
}
}
.nav .principal { .nav .principal {
display: flex; display: flex;
gap: var(--sp-5); gap: var(--sp-5);

@ -5,9 +5,11 @@
interface Props { interface Props {
lado?: number; lado?: number;
/** Usa solo el trazo del símbolo y hereda un único color del contexto. */
plano?: boolean;
} }
let { lado = 36 }: Props = $props(); let { lado = 36, plano = false }: Props = $props();
const reproduciendo = $derived(reproductor.sonando); const reproduciendo = $derived(reproductor.sonando);
</script> </script>
@ -16,18 +18,33 @@
animación está asignada incluso en pausa para conservar el ángulo alcanzado; animación está asignada incluso en pausa para conservar el ángulo alcanzado;
solo cambia `animation-play-state`, de modo que nunca salta al origen. solo cambia `animation-play-state`, de modo que nunca salta al origen.
--> -->
<img {#if plano}
class="logo" <svg
class:reproduciendo class="logo logo--plano"
src={logo} class:reproduciendo
alt="" viewBox="0 0 350 350"
aria-hidden="true" aria-hidden="true"
draggable="false" width={lado}
width={lado} height={lado}
height={lado} style="--lado: {lado}px"
style="--lado: {lado}px" data-logo-senza-paura
data-logo-senza-paura >
/> <use href={`${logo}#simbolo`} />
</svg>
{:else}
<img
class="logo"
class:reproduciendo
src={logo}
alt=""
aria-hidden="true"
draggable="false"
width={lado}
height={lado}
style="--lado: {lado}px"
data-logo-senza-paura
/>
{/if}
<style> <style>
.logo { .logo {
@ -46,6 +63,10 @@
animation-play-state: running; animation-play-state: running;
} }
.logo--plano {
fill: currentColor;
}
@keyframes giro-logo { @keyframes giro-logo {
to { to {
transform: rotate(1turn); transform: rotate(1turn);

Loading…
Cancel
Save

Powered by TurnKey Linux.