diff --git a/README.md b/README.md index 56f40a7..5eb7e2a 100644 --- a/README.md +++ b/README.md @@ -663,6 +663,17 @@ videos: ## La cabecera y las anclas +La portada **empieza en el borde de la ventana** y la cabecera flota encima: +por eso es translúcida con desenfoque, para apoyarse sobre la imagen en vez de +tapar una franja. Se consigue tirando de la portada hacia arriba con un margen +negativo del alto de la cabecera, que cada lámina devuelve en relleno para que +su contenido no se mueva. + +Ese alto **lo mide la cabecera** y lo publica en `--altura-cabecera`, igual que +hace la barra del reproductor con el suyo. Estaba escrito a mano en 4,75rem +cuando la cabecera mide 68px, así que todo lo que dependía de él —el salto a un +ancla, las barras laterales pegajosas— quedaba desplazado. + La cabecera **acompaña al desplazamiento** (`position: sticky`) y deja ver lo que pasa por debajo. La transparencia va **con desenfoque, no sola**: sobre la portada, un fondo solo translúcido dejaría el contraste del menú a merced de la @@ -706,6 +717,18 @@ Tres cosas que estaban copiadas en varios sitios y ahora se escriben una vez: un enlace dentro de otro deja una zona donde el clic hace una cosa distinta según el píxel—, así que es una clase y no un componente. +## Cantos y radios + +Dos valores y no más, para que nada se vea de otra familia: +`--radius-boton` (6px) para lo que se pulsa o se escribe —botones, campos, +pastillas—, y `--radius-tarjeta` (8px) para las superficies —tarjetas, paneles, +portadas—. Antes había tres radios sueltos y una píldora de 999px repetida en +quince archivos. + +Los botones de **icono suelto** siguen siendo redondos: el de reproducir sobre +una portada, el del tema, los puntos del carrusel. Un icono en un círculo tiene +sentido; un botón con texto en forma de cápsula es otra cosa. + ## Tarjetas Las tres clases —estilo, álbum y canción— comparten `.tarjeta` en `app.css` y diff --git a/despliegue/senzapaura.nginx.conf b/despliegue/senzapaura.nginx.conf index 35ead3b..411d06b 100644 --- a/despliegue/senzapaura.nginx.conf +++ b/despliegue/senzapaura.nginx.conf @@ -1,35 +1,28 @@ # Nginx por delante del servidor de Node. # -# Copiar a /etc/nginx/sites-available/senzapaura, sustituir DOMINIO, y luego: +# sudo cp senzapaura.nginx.conf /etc/nginx/sites-available/senzapaura +# sudo ln -sf /etc/nginx/sites-available/senzapaura /etc/nginx/sites-enabled/ +# sudo rm -f /etc/nginx/sites-enabled/default +# sudo nginx -t && sudo systemctl reload nginx # -# sudo ln -s /etc/nginx/sites-available/senzapaura /etc/nginx/sites-enabled/ -# sudo nginx -t # comprueba la sintaxis antes de recargar -# sudo systemctl reload nginx +# Ese `rm` del sitio por defecto importa: mientras esté, es él quien contesta a +# quien entra por IP, y se ve la página de bienvenida de Nginx en lugar de la +# web. # -# El certificado lo pone certbot, que reescribe este archivo al ejecutarse: -# -# sudo certbot --nginx -d DOMINIO -d www.DOMINIO -# -# Sin HTTPS no se puede publicar: aquí se escriben correos y se paga. - -# Todo lo que llegue sin cifrar se manda a la versión segura. Certbot añade -# este bloque por su cuenta, pero se deja escrito para que no dependa de ello. -server { - listen 80; - listen [::]:80; - server_name DOMINIO www.DOMINIO; - return 301 https://$host$request_uri; -} +# Tal como está, sirve por IP y sin cifrar, que es lo que se puede hacer en una +# red local sin dominio. Al final del archivo está lo que hay que cambiar +# cuando lo haya. server { - listen 443 ssl; - listen [::]:443 ssl; - http2 on; - server_name DOMINIO www.DOMINIO; + # `default_server` es lo que hace que este sitio conteste a quien entra por + # la IP o por un nombre que no coincida con ninguno. Sin esto, Nginx elige + # el sitio `default` y enseña su bienvenida. + listen 80 default_server; + listen [::]:80 default_server; - # certbot rellena estas dos. - # ssl_certificate /etc/letsencrypt/live/DOMINIO/fullchain.pem; - # ssl_certificate_key /etc/letsencrypt/live/DOMINIO/privkey.pem; + # `_` no casa con ningún nombre real: es la forma de decir «cualquiera». + # Cuando haya dominio, aquí va el dominio. + server_name _; # Los mp3 completos pesan varios megas; los fragmentos, cientos de kilos. client_max_body_size 20m; @@ -46,7 +39,7 @@ server { # # Todo lo de `_app/immutable` lleva el hash del contenido en el nombre: si # cambia el archivo, cambia la URL. Por eso se puede cachear para siempre y - # servirlo desde nginx sin molestar a Node, que es lo que hace que abrir la + # servirlo desde Nginx sin molestar a Node, que es lo que hace que abrir la # web no despierte al proceso para devolver un CSS. # ----------------------------------------------------------------------- location /_app/immutable/ { @@ -57,9 +50,9 @@ server { try_files $uri =404; } - # Imágenes, fuentes, vídeo y los fragmentos de muestra. No llevan huella, así - # que se cachean un día: lo justo para no pedirlos en cada visita sin que un - # cambio tarde una semana en verse. + # Imágenes, fuentes, vídeo, los fragmentos de muestra y el panel. No llevan + # huella, así que se cachean un día: lo justo para no pedirlos en cada + # visita sin que un cambio tarde una semana en verse. location ~* ^/(images|background|video|muestras|admin)/ { root /var/www/senzapaura/build/client; access_log off; @@ -73,7 +66,7 @@ server { # # Incluidas /descargas/… —que comprueban la compra antes de entregar el # archivo— y el webhook de Stripe. Ojo: las descargas NO se pueden servir - # desde nginx; el audio completo vive fuera de `build/` justamente para que + # desde Nginx; el audio completo vive fuera de `build/` justamente para que # no haya forma de pedirlo por URL sin haberlo comprado. # ----------------------------------------------------------------------- location / { @@ -93,7 +86,26 @@ server { # Un mp3 completo tarda en salir por una conexión lenta. proxy_read_timeout 300s; # Sin buffer: la descarga empieza a fluir en cuanto Node la manda, en - # vez de esperar a que nginx tenga el archivo entero. + # vez de esperar a que Nginx tenga el archivo entero. proxy_buffering off; } } + +# --------------------------------------------------------------------------- +# Cuando haya dominio +# +# 1. Cambiar `server_name _;` por `server_name DOMINIO www.DOMINIO;` y quitar +# `default_server` de las dos líneas `listen` de arriba. +# 2. Poner el certificado: +# +# sudo apt install -y certbot python3-certbot-nginx +# sudo certbot --nginx -d DOMINIO -d www.DOMINIO +# +# Certbot añade solo el bloque de 443 y la redirección desde el 80. +# 3. Cambiar `ORIGIN` en /var/www/senzapaura/.env a `https://DOMINIO` y +# reiniciar el servicio. Sin eso, SvelteKit rechaza todos los formularios +# con un 403 por comprobación de origen, y parece que la web está rota. +# +# Sin HTTPS no se puede publicar de cara al exterior: aquí se escriben correos +# y se paga. +# --------------------------------------------------------------------------- diff --git a/src/app.css b/src/app.css index 45732e7..2c28535 100644 --- a/src/app.css +++ b/src/app.css @@ -94,9 +94,18 @@ var(--c-accent-sombra) 100% ); - --radius: 4px; - /* Radio propio: 4px en una tarjeta grande se lee como un recorte, no como una pieza. */ - --radius-tarjeta: 14px; + /* + * Dos radios y no más, para que nada se vea de otra familia: + * + * - `--radius-boton` para lo que se pulsa o se escribe: botones, campos, + * pastillas. + * - `--radius-tarjeta` para las superficies: tarjetas, paneles, portadas. + * + * Un canto muy redondo tira a aplicación de móvil, y esto es una web de + * letras: la esquina corta acompaña mejor a una serif. + */ + --radius-boton: 6px; + --radius-tarjeta: 8px; /* * Curvas del movimiento. `--sal` sale rápido y frena largo, que es como se @@ -319,7 +328,7 @@ button { gap: var(--sp-2); padding: var(--sp-3) var(--sp-5); border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); /* * `background-color` y no el atajo `background`: el atajo reinicia también * `background-image`, así que una variante con degradado —el botón dorado— @@ -406,7 +415,7 @@ button { display: inline-block; padding: 0.2em 0.7em; border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); font-size: var(--fs-xs); color: var(--c-text-muted); text-decoration: none; diff --git a/src/lib/components/BotonCarrito.svelte b/src/lib/components/BotonCarrito.svelte index d7d8a7e..9f60272 100644 --- a/src/lib/components/BotonCarrito.svelte +++ b/src/lib/components/BotonCarrito.svelte @@ -57,7 +57,7 @@ .boton { padding: var(--sp-1) var(--sp-3); border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); background: transparent; font-size: var(--fs-sm); cursor: pointer; diff --git a/src/lib/components/BotonFavorito.svelte b/src/lib/components/BotonFavorito.svelte index 79704c3..81b9982 100644 --- a/src/lib/components/BotonFavorito.svelte +++ b/src/lib/components/BotonFavorito.svelte @@ -81,7 +81,7 @@ gap: var(--sp-2); padding: var(--sp-1) var(--sp-3); border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); background: transparent; color: var(--c-text-muted); font: inherit; diff --git a/src/lib/components/BotonReproducir.svelte b/src/lib/components/BotonReproducir.svelte index bb31acc..094bbb7 100644 --- a/src/lib/components/BotonReproducir.svelte +++ b/src/lib/components/BotonReproducir.svelte @@ -109,6 +109,8 @@ } .solido { + /* Con texto ya no es un icono en un círculo, sino un botón. */ + border-radius: var(--radius-boton); padding: var(--sp-2) var(--sp-4); background-color: var(--c-accent); background-image: var(--oro); diff --git a/src/lib/components/Cabecera.svelte b/src/lib/components/Cabecera.svelte index 0c3e3eb..c6f3083 100644 --- a/src/lib/components/Cabecera.svelte +++ b/src/lib/components/Cabecera.svelte @@ -7,6 +7,34 @@ import { navegacion, site } from '$lib/site'; let menuAbierto = $state(false); + let cabecera = $state(null); + + /** + * La cabecera publica su altura real en `--altura-cabecera`. + * + * Ese valor lo usan tres cosas: el desplazamiento a un ancla, las barras + * laterales pegajosas y la portada, que se mete por debajo de la cabecera y + * necesita saber cuánto para compensarlo. Estaba escrito a mano en + * `app.css` y no coincidía —4,75rem contra los 70px reales—, así que todo + * lo que dependía de él quedaba desplazado. + * + * Se mide en vez de calcularse porque el menú móvil la hace crecer al + * desplegarse. + */ + $effect(() => { + if (!cabecera) return; + + const observador = new ResizeObserver(([entrada]) => { + const alto = entrada.borderBoxSize?.[0]?.blockSize ?? entrada.contentRect.height; + document.documentElement.style.setProperty('--altura-cabecera', `${Math.ceil(alto)}px`); + }); + observador.observe(cabecera); + + return () => { + observador.disconnect(); + document.documentElement.style.removeProperty('--altura-cabecera'); + }; + }); /** Una sección está activa también en sus páginas hijas. */ function activa(href: string): boolean { @@ -23,7 +51,7 @@ }); -
+
{site.nombre} @@ -184,7 +212,7 @@ min-width: 1.4em; height: 1.4em; padding-inline: 0.35em; - border-radius: 999px; + border-radius: var(--radius-boton); background: var(--c-accent); color: var(--c-accent-text); font-size: var(--fs-xs); @@ -196,7 +224,7 @@ display: none; padding: var(--sp-2); border: 1px solid var(--c-border); - border-radius: var(--radius); + border-radius: var(--radius-boton); background: transparent; cursor: pointer; } diff --git a/src/lib/components/EnlacesPlataformas.svelte b/src/lib/components/EnlacesPlataformas.svelte index 8762254..ecbff61 100644 --- a/src/lib/components/EnlacesPlataformas.svelte +++ b/src/lib/components/EnlacesPlataformas.svelte @@ -44,7 +44,7 @@ gap: var(--sp-2); padding: var(--sp-1) var(--sp-3); border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); font-size: var(--fs-sm); text-decoration: none; } diff --git a/src/lib/components/Heroe.svelte b/src/lib/components/Heroe.svelte index f60415e..c4b116f 100644 --- a/src/lib/components/Heroe.svelte +++ b/src/lib/components/Heroe.svelte @@ -120,7 +120,8 @@ display: flex; align-items: center; min-height: min(88vh, 46rem); - padding-block: var(--sp-8); + /* El alto de la cabecera, que flota encima, se devuelve aquí. */ + padding-block: calc(var(--sp-8) + var(--altura-cabecera)) var(--sp-8); overflow: hidden; isolation: isolate; } diff --git a/src/lib/components/LaminaCta.svelte b/src/lib/components/LaminaCta.svelte index 7f8ff83..0ec5272 100644 --- a/src/lib/components/LaminaCta.svelte +++ b/src/lib/components/LaminaCta.svelte @@ -43,7 +43,8 @@ display: flex; align-items: center; min-height: min(88vh, 46rem); - padding-block: var(--sp-8); + /* El alto de la cabecera, que flota encima, se devuelve aquí. */ + padding-block: calc(var(--sp-8) + var(--altura-cabecera)) var(--sp-8); overflow: hidden; isolation: isolate; } diff --git a/src/lib/components/ListaCanciones.svelte b/src/lib/components/ListaCanciones.svelte index 3246f7d..d312b22 100644 --- a/src/lib/components/ListaCanciones.svelte +++ b/src/lib/components/ListaCanciones.svelte @@ -125,7 +125,7 @@ .marca { border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); padding: 0 0.4em; margin-right: var(--sp-1); } diff --git a/src/lib/components/Valoracion.svelte b/src/lib/components/Valoracion.svelte index 29504ab..37ed63b 100644 --- a/src/lib/components/Valoracion.svelte +++ b/src/lib/components/Valoracion.svelte @@ -173,7 +173,7 @@ .estrellas label:has(input:focus-visible) { outline: 2px solid var(--c-accent); outline-offset: 2px; - border-radius: var(--radius); + border-radius: var(--radius-boton); } .retirar { diff --git a/src/lib/components/VideoYoutube.svelte b/src/lib/components/VideoYoutube.svelte index 14348c1..98d7afc 100644 --- a/src/lib/components/VideoYoutube.svelte +++ b/src/lib/components/VideoYoutube.svelte @@ -65,7 +65,7 @@ aspect-ratio: 16 / 9; overflow: hidden; border: 1px solid var(--c-border); - border-radius: var(--radius); + border-radius: var(--radius-tarjeta); background: var(--c-bg-sunken); } diff --git a/src/lib/components/player/BarraReproductor.svelte b/src/lib/components/player/BarraReproductor.svelte index 3463097..8786526 100644 --- a/src/lib/components/player/BarraReproductor.svelte +++ b/src/lib/components/player/BarraReproductor.svelte @@ -258,7 +258,7 @@ flex: none; padding: 0 0.4em; border: 1px solid var(--c-border); - border-radius: 999px; + border-radius: var(--radius-boton); font-size: 0.65rem; text-transform: uppercase; letter-spacing: 0.06em; diff --git a/src/lib/components/player/ControlVolumen.svelte b/src/lib/components/player/ControlVolumen.svelte index 8f8548a..2383ee2 100644 --- a/src/lib/components/player/ControlVolumen.svelte +++ b/src/lib/components/player/ControlVolumen.svelte @@ -43,7 +43,7 @@ display: inline-flex; padding: var(--sp-2); border: none; - border-radius: var(--radius); + border-radius: var(--radius-boton); background: transparent; cursor: pointer; } diff --git a/src/lib/components/player/PanelCola.svelte b/src/lib/components/player/PanelCola.svelte index ead567c..4c396e9 100644 --- a/src/lib/components/player/PanelCola.svelte +++ b/src/lib/components/player/PanelCola.svelte @@ -64,7 +64,7 @@ background: var(--c-bg-raised); border: 1px solid var(--c-border); border-bottom: 0; - border-radius: var(--radius) 0 0 0; + border-radius: var(--radius-tarjeta) 0 0 0; } .cabecera { @@ -88,7 +88,7 @@ border: none; background: transparent; cursor: pointer; - border-radius: var(--radius); + border-radius: var(--radius-boton); } .cerrar:hover { @@ -115,7 +115,7 @@ width: 100%; padding: var(--sp-2); border: none; - border-radius: var(--radius); + border-radius: var(--radius-boton); background: transparent; text-align: left; cursor: pointer; diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 23d876d..b0da955 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -64,7 +64,7 @@ padding: var(--sp-2) var(--sp-4); background: var(--c-accent); color: var(--c-accent-text); - border-radius: var(--radius); + border-radius: var(--radius-boton); transition: top 0.15s ease; } diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 2402962..b419a8e 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -30,41 +30,52 @@ - - {#snippet lamina(i)} - {#if i === 0} - - {:else if i === 1} - - {#snippet acciones()} - Entrar al taller - - Empezar por el principio - - {/snippet} - - {:else} - - {#snippet acciones()} - Elegir canciones - Ver el carrito - {/snippet} - - {/if} - {/snippet} - + +
+ + {#snippet lamina(i)} + {#if i === 0} + + {:else if i === 1} + + {#snippet acciones()} + Entrar al taller + + Empezar por el principio + + {/snippet} + + {:else} + + {#snippet acciones()} + Elegir canciones + Ver el carrito + {/snippet} + + {/if} + {/snippet} + +
{#if principal}