You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/src/lib/components/FondoDePortada.svelte

226 lines
7.1 KiB

<script lang="ts">
/**
* El fondo a sangre de una lámina de la portada: la imagen y el velo que la
* atenúa lo justo para que se lea lo que va encima.
*
* Vive aparte porque lo usan la cabecera del disco y las láminas de llamada,
* y estaba copiado en las dos. Ya habían empezado a divergir —el degradado
* era distinto por descuido, no por decisión—, que es exactamente lo que
* pasa con el marcado duplicado.
*
* El clip de vídeo sigue siendo la reserva para el tema que todavía no tiene
* imagen. Nunca un incrustado de terceros: en la primera pantalla del sitio
* un iframe pesa más que todo lo demás junto y deja cookies antes de que
* nadie haya pedido ver nada. Y no se arranca con `autoplay` sino desde
* código, para poder respetar `prefers-reduced-motion`.
*/
import OndasWebgl from './OndasWebgl.svelte';
import { heroe } from '$lib/site';
import { tema } from '$lib/tema.svelte';
interface Props {
/**
* Cuánto tapa el velo:
*
* - `lateral` — fuerte a la izquierda, donde está el texto, y casi nada a
* la derecha, que es donde se ve el tocadiscos.
* - `cerrado` — parejo y más cubriente. En una lámina sin nada que enseñar
* a la derecha, lo único que importa es que se lea lo que se propone.
* - `foto` — para una imagen real y clara. El texto del tema oscuro es
* blanco, y sobre un mármol iluminado no se lee: aquí el velo no atenúa,
* oscurece.
*/
velo?: 'lateral' | 'cerrado' | 'foto';
/**
* Imagen propia de la lámina. Sin esto se usa el fondo de marca, que
* cambia con el tema; una foto no cambia, así que quien la pasa se
* responsabiliza también de que el velo la deje legible.
*/
imagen?: { src: string; srcset: string; ancho: number; alto: number };
/**
* Solo la primera lámina lo pide con prioridad alta: es lo que se ve al
* llegar. Pedir tres fondos con prioridad alta es no priorizar nada.
*/
prioritario?: boolean;
/**
* Si se monta el clip de reserva donde no hay imagen para el tema en curso.
*
* Solo lo pide la cabecera. Las láminas de llamada no: comparten fondo con
* ella, y con esto activado el tema claro acababa reproduciendo tres copias
* del mismo vídeo en la portada, una por lámina. Sin clip se quedan con el
* color de fondo, que para una llamada a la acción sobra.
*/
clip?: boolean;
/**
* Si encima del fondo van las ondas animadas.
*
* Solo lo pide la cabecera: es la primera pantalla y donde ese movimiento
* dice algo. Repetirlo en las tres láminas serían tres shaders a pantalla
* completa peleándose por la tarjeta gráfica para pintar lo mismo.
*
* Las ondas también resuelven el tema claro, que no tiene imagen propia:
* el shader las pinta en azules pálidos y el clip de reserva dejó de hacer
* falta. Donde no pueden correr —sin WebGL o con movimiento reducido— el
* tema claro se queda con el color de fondo a secas, hasta que exista una
* imagen suya.
*/
ondas?: boolean;
}
let {
velo = 'lateral',
prioritario = false,
clip = false,
ondas = false,
imagen
}: Props = $props();
const fondo = $derived(imagen ?? heroe.fondo[tema.actual]);
/*
* Donde hay imagen no se monta el clip, así que nunca conviven los dos en la
* página. Lo que sí ocurre es que el servidor pinta el tema oscuro —el de
* marca, y el que no puede saber de antemano—, de modo que quien tenga el
* claro se descarga la imagen oscura una vez antes de cambiarla por el clip.
* Se acaba cuando el claro tenga su propia imagen.
*/
const fuente = $derived(fondo || !clip ? undefined : heroe.video[tema.actual]);
const poster = $derived(heroe.poster[tema.actual]);
let video = $state<HTMLVideoElement | null>(null);
$effect(() => {
// Se lee la fuente para que el efecto vuelva a correr al cambiar de tema:
// un `src` nuevo deja el vídeo parado hasta que se le vuelve a dar.
void fuente;
if (!video) return;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) return;
// `play()` rechaza la promesa si el navegador decide no reproducir; el
// fotograma de póster ya cubre ese caso.
void video.play().catch(() => {});
});
</script>
<div class="fondo">
{#if fondo}
<!--
`alt` vacío y no una descripción: es decoración. Nombrarlo obligaría a un
lector de pantalla a leer algo que no aporta nada a quien llega.
-->
<img
src={fondo.src}
srcset={fondo.srcset}
sizes="100vw"
width={fondo.ancho}
height={fondo.alto}
alt=""
fetchpriority={prioritario ? 'high' : 'auto'}
loading={prioritario ? 'eager' : 'lazy'}
/>
{:else if fuente}
<video
bind:this={video}
src={fuente}
{poster}
muted
loop
playsinline
preload="metadata"
aria-hidden="true"
tabindex="-1"
></video>
{/if}
<!--
Las ondas van encima de la imagen, no en su lugar: la imagen se pinta al
instante y el shader entra por encima cuando ha arrancado. Si no arranca
—sin WebGL, con movimiento reducido o si el programa no compila— debajo
queda el fondo de siempre y nadie ve un hueco.
-->
{#if ondas}
<OndasWebgl />
{/if}
<span class="velo" class:cerrado={velo === 'cerrado'} class:foto={velo === 'foto'}></span>
</div>
<style>
.fondo {
position: absolute;
inset: 0;
z-index: -1;
background: var(--c-bg-sunken);
}
/*
* `cover` recorta lo que sobre en vez de deformar: la imagen es una textura
* sin motivo central, así que da igual por dónde se corte, y así llena
* cualquier proporción de pantalla sin estirarse.
*/
.fondo img,
.fondo video {
width: 100%;
height: 100%;
object-fit: cover;
}
/*
* El velo va lo justo: el fondo es material de marca y taparlo entero sería
* no haberlo puesto.
*/
.velo {
position: absolute;
inset: 0;
background:
linear-gradient(
to right,
color-mix(in oklab, var(--c-bg) 80%, transparent) 0%,
color-mix(in oklab, var(--c-bg) 40%, transparent) 45%,
transparent 100%
),
linear-gradient(to top, var(--c-bg) 0%, transparent 28%);
}
/*
* En vertical el disco se coloca arriba y el texto debajo, así que el velo
* deja de tener izquierda y derecha: pasa a cerrar de abajo hacia arriba,
* que es como quedan repartidos.
*/
@media (max-width: 62rem) {
.velo {
background: linear-gradient(
to top,
var(--c-bg) 10%,
color-mix(in oklab, var(--c-bg) 65%, transparent) 100%
);
}
}
/*
* Sobre una foto clara hace falta oscurecer de verdad, no atenuar: el texto
* es blanco. Se carga la izquierda, donde va, y se deja respirar la derecha
* para que la imagen siga contándose.
*/
.velo.foto {
background:
linear-gradient(
to right,
color-mix(in oklab, var(--c-bg) 94%, transparent) 0%,
color-mix(in oklab, var(--c-bg) 80%, transparent) 45%,
color-mix(in oklab, var(--c-bg) 35%, transparent) 100%
),
linear-gradient(to top, var(--c-bg) 0%, transparent 45%);
}
.velo.cerrado {
background:
linear-gradient(
to right,
color-mix(in oklab, var(--c-bg) 92%, transparent) 0%,
color-mix(in oklab, var(--c-bg) 70%, transparent) 55%,
color-mix(in oklab, var(--c-bg) 30%, transparent) 100%
),
linear-gradient(to top, var(--c-bg) 0%, transparent 35%);
}
</style>

Powered by TurnKey Linux.