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.
226 lines
7.1 KiB
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>
|