CSP: Inline-Styles entfernt, Policy vom Build erzeugen lassen
Ein CSP-Header im Reverse-Proxy hat die Seite still zerlegt: Astro liefert seine Skripte als inline type=module aus, "script-src 'self'" blockiert die. Im Browser blieb data-reveal leer und das Mobilmenue reagierte nicht, ohne dass eine Fehlermeldung sichtbar war. Astro kennt die Hashes seiner eigenen Bundles, deshalb setzt jetzt der Build die Policy per <meta> (security.csp in astro.config.mjs). Damit die Policy auch fuer Styles streng bleiben kann, sind die letzten drei style-Attribute raus: sobald eine CSP Hashes fuer Styles enthaelt, ignorieren Browser 'unsafe-inline', inline gesetzte Farben waeren also ohnehin verloren. - Laserstrahlen im Hero: .beam-1 bis .beam-5 im <style>-Block - Genre-Akzentfarben: .accent-<name> statt berechnetem style-Attribut Geprueft im Browser hinter Caddy: Skripte laufen, Laser-Winkel und Akzentfarben stimmen, keine Verstoesse in der Konsole. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -16,6 +16,26 @@ export default defineConfig({
|
||||
|
||||
integrations: [sitemap()],
|
||||
|
||||
// Content-Security-Policy. Astro kennt seine eigenen gebuendelten Skripte und
|
||||
// Styles und traegt deren SHA-256-Hashes automatisch nach - deshalb wird die
|
||||
// Policy hier gesetzt und nicht im Reverse-Proxy: der weiss von den Hashes
|
||||
// nichts und wuerde die Inline-Module blockieren.
|
||||
security: {
|
||||
csp: {
|
||||
directives: [
|
||||
"default-src 'self'",
|
||||
"img-src 'self' data:",
|
||||
"font-src 'self'",
|
||||
"connect-src 'self'",
|
||||
"form-action 'self'",
|
||||
"base-uri 'self'",
|
||||
"object-src 'none'",
|
||||
],
|
||||
scriptDirective: { resources: ["'self'"] },
|
||||
styleDirective: { resources: ["'self'"] },
|
||||
},
|
||||
},
|
||||
|
||||
image: {
|
||||
// Bilder werden beim Build zu AVIF/WebP konvertiert.
|
||||
responsiveStyles: true,
|
||||
|
||||
+18
-12
@@ -3,13 +3,9 @@ import { Image } from 'astro:assets';
|
||||
import banner from '../assets/hero-banner.jpg';
|
||||
import { ABOUT, GENRES, CONTACT } from '../data/site';
|
||||
|
||||
const accentVar: Record<(typeof GENRES)[number]['accent'], string> = {
|
||||
magenta: 'var(--color-laser-magenta)',
|
||||
cyan: 'var(--color-laser-cyan)',
|
||||
gold: 'var(--color-gold-400)',
|
||||
violet: 'var(--color-laser-violet)',
|
||||
lime: 'var(--color-laser-lime)',
|
||||
};
|
||||
// Die Akzentfarbe kommt ueber die Klasse accent-<name> aus dem <style>-Block
|
||||
// unten. Bewusst kein style-Attribut: sobald die CSP Hashes fuer Styles setzt,
|
||||
// ignorieren Browser 'unsafe-inline', und inline gesetzte Farben waeren weg.
|
||||
---
|
||||
|
||||
<section id="about" class="relative overflow-hidden py-24 sm:py-32">
|
||||
@@ -84,18 +80,16 @@ const accentVar: Record<(typeof GENRES)[number]['accent'], string> = {
|
||||
{
|
||||
GENRES.map((genre, i) => (
|
||||
<article
|
||||
class="group relative overflow-hidden rounded-2xl border border-white/10 bg-ink-900/50 p-6 transition duration-300 hover:-translate-y-1 hover:border-white/20 hover:bg-ink-850/70"
|
||||
class={`accent-${genre.accent} group relative overflow-hidden rounded-2xl border border-white/10 bg-ink-900/50 p-6 transition duration-300 hover:-translate-y-1 hover:border-white/20 hover:bg-ink-850/70`}
|
||||
data-reveal
|
||||
data-reveal-delay={i * 90}
|
||||
>
|
||||
<span
|
||||
class="absolute inset-x-0 top-0 h-px opacity-60 transition-opacity duration-300 group-hover:opacity-100"
|
||||
style={`background:linear-gradient(to right, transparent, ${accentVar[genre.accent]}, transparent);`}
|
||||
class="accent-line absolute inset-x-0 top-0 h-px opacity-60 transition-opacity duration-300 group-hover:opacity-100"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<span
|
||||
class="absolute -top-16 -right-16 h-40 w-40 rounded-full opacity-0 blur-3xl transition-opacity duration-500 group-hover:opacity-30"
|
||||
style={`background:${accentVar[genre.accent]};`}
|
||||
class="accent-glow absolute -top-16 -right-16 h-40 w-40 rounded-full opacity-0 blur-3xl transition-opacity duration-500 group-hover:opacity-30"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
<h3 class="font-display text-xl text-white">{genre.name}</h3>
|
||||
@@ -106,3 +100,15 @@ const accentVar: Record<(typeof GENRES)[number]['accent'], string> = {
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Akzentfarbe pro Genre. Der Name muss zu `accent` in src/data/site.ts passen. */
|
||||
.accent-magenta { --accent: var(--color-laser-magenta); }
|
||||
.accent-cyan { --accent: var(--color-laser-cyan); }
|
||||
.accent-gold { --accent: var(--color-gold-400); }
|
||||
.accent-violet { --accent: var(--color-laser-violet); }
|
||||
.accent-lime { --accent: var(--color-laser-lime); }
|
||||
|
||||
.accent-line { background: linear-gradient(to right, transparent, var(--accent), transparent); }
|
||||
.accent-glow { background: var(--accent); }
|
||||
</style>
|
||||
|
||||
+22
-13
@@ -4,14 +4,11 @@ import club from '../assets/hero-club.jpg';
|
||||
import nizz from '../assets/nizz-cutout.png';
|
||||
import { CONTACT, GENRES } from '../data/site';
|
||||
|
||||
// Laserstrahlen: Position in %, Start-/Endwinkel, Farbe, Startverzoegerung
|
||||
const beams = [
|
||||
{ x: 18, from: '-26deg', to: '-13deg', color: 'var(--color-laser-cyan)', delay: '0s' },
|
||||
{ x: 32, from: '-12deg', to: '-3deg', color: 'var(--color-laser-magenta)', delay: '-2.6s' },
|
||||
{ x: 47, from: '9deg', to: '19deg', color: 'var(--color-gold-400)', delay: '-5.1s' },
|
||||
{ x: 61, from: '22deg', to: '11deg', color: 'var(--color-laser-violet)', delay: '-1.3s' },
|
||||
{ x: 74, from: '31deg', to: '20deg', color: 'var(--color-laser-cyan)', delay: '-6.7s' },
|
||||
];
|
||||
// Fuenf Laserstrahlen. Position, Winkel, Farbe und Verzoegerung stehen im
|
||||
// <style>-Block unten als .beam-1 bis .beam-5 - bewusst nicht als
|
||||
// style-Attribut, sonst braeuchte die Content-Security-Policy
|
||||
// 'unsafe-inline' fuer Styles.
|
||||
const beams = [1, 2, 3, 4, 5];
|
||||
|
||||
const genreLine = GENRES.map((g) => g.name).join(' • ');
|
||||
---
|
||||
@@ -33,11 +30,8 @@ const genreLine = GENRES.map((g) => g.name).join(' • ');
|
||||
{/* Ebene 2: animierte Laser */}
|
||||
<div class="absolute inset-x-0 top-0 -z-20 h-[125%] overflow-hidden" aria-hidden="true">
|
||||
{
|
||||
beams.map((b) => (
|
||||
<span
|
||||
class="absolute top-[-12%] h-full w-px origin-top animate-sweep blur-[1.5px] mix-blend-screen"
|
||||
style={`left:${b.x}%;--beam-from:${b.from};--beam-to:${b.to};animation-delay:${b.delay};background:linear-gradient(to bottom, ${b.color} 0%, ${b.color} 35%, transparent 100%);`}
|
||||
/>
|
||||
beams.map((n) => (
|
||||
<span class={`beam-${n} absolute top-[-12%] h-full w-px origin-top animate-sweep blur-[1.5px] mix-blend-screen`} />
|
||||
))
|
||||
}
|
||||
</div>
|
||||
@@ -156,3 +150,18 @@ const genreLine = GENRES.map((g) => g.name).join(' • ');
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<style>
|
||||
/* Je Strahl: Position, Winkelbereich der Animation, Farbe, Versatz.
|
||||
Die Keyframes "sweep" stehen in global.css. */
|
||||
.beam-1 { left: 18%; --beam-from: -26deg; --beam-to: -13deg; animation-delay: 0s;
|
||||
background: linear-gradient(to bottom, var(--color-laser-cyan) 0%, var(--color-laser-cyan) 35%, transparent 100%); }
|
||||
.beam-2 { left: 32%; --beam-from: -12deg; --beam-to: -3deg; animation-delay: -2.6s;
|
||||
background: linear-gradient(to bottom, var(--color-laser-magenta) 0%, var(--color-laser-magenta) 35%, transparent 100%); }
|
||||
.beam-3 { left: 47%; --beam-from: 9deg; --beam-to: 19deg; animation-delay: -5.1s;
|
||||
background: linear-gradient(to bottom, var(--color-gold-400) 0%, var(--color-gold-400) 35%, transparent 100%); }
|
||||
.beam-4 { left: 61%; --beam-from: 22deg; --beam-to: 11deg; animation-delay: -1.3s;
|
||||
background: linear-gradient(to bottom, var(--color-laser-violet) 0%, var(--color-laser-violet) 35%, transparent 100%); }
|
||||
.beam-5 { left: 74%; --beam-from: 31deg; --beam-to: 20deg; animation-delay: -6.7s;
|
||||
background: linear-gradient(to bottom, var(--color-laser-cyan) 0%, var(--color-laser-cyan) 35%, transparent 100%); }
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user