Banner-Hintergründe CSP-fest: <img> statt Inline-Variable
Beim ersten Deploy blieben die Slides des Banner-Sliders schwarz. Ursache war kein fehlendes Bild, sondern die CSP: public/.htaccess setzt default-src 'self' ohne style-src, der Browser verwirft damit jedes style-Attribut — und die Komponente lieferte Bild und Akzentfarbe genau so (style="--banner-bg: url(…); --banner-accent: #6cbbbc"). Lokal war das unsichtbar, weil php -S keine .htaccess liest. Der Hintergrund ist jetzt ein <img class="ad-banner__bg"> über die bestehende img-Komponente (CSP erlaubt img-src) und bringt srcset mit: das Handy lädt die 640er statt der 1600er, die erste Slide lädt eager mit fetchpriority. Der dunkle Leseverlauf liegt auf ::before statt im background-image. Die Akzentfarbe kann als beliebiger Hex-Wert nicht ohne Inline-Style in die Seite. Sie steht deshalb als Token in tokens.css (wo Farben laut CLAUDE.md hingehören), components.css bildet den Namen darauf ab, und banners.json nennt nur noch "accent": "breadcrumb". bin/preflight.php bricht ab, wenn unter app/ wieder ein style-Attribut auftaucht — der Fehler wäre sonst erneut erst in Produktion sichtbar. Beim Bauen dieser Wache zweimal selbst hineingetappt: ein Muster mit [^>]* bricht an jedem schließenden PHP-Tag ab, und die Zeichenfolge dieses Tags beendet den PHP-Modus auch in einem //-Kommentar (preflight gab ab dort seinen eigenen Quelltext aus, php -l meldete nichts). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NzeVVgMCrzCDJAKeLEdKr7
This commit is contained in:
13
CLAUDE.md
13
CLAUDE.md
@@ -119,7 +119,18 @@ Funktion, sie ist die einzige Stelle, an der aus JSON HTML entsteht.
|
|||||||
Quelle für Termine-Badges und Hero-Beschriftung) und `upcoming_highlights(3)` (die nächsten
|
Quelle für Termine-Badges und Hero-Beschriftung) und `upcoming_highlights(3)` (die nächsten
|
||||||
Termine für den Startseiten-Hero, s. Hero-Regel).
|
Termine für den Startseiten-Hero, s. Hero-Regel).
|
||||||
- **CSS:** 6 Dateien als `<link>` in fester Reihenfolge: tokens → reset → base → layout → components →
|
- **CSS:** 6 Dateien als `<link>` in fester Reihenfolge: tokens → reset → base → layout → components →
|
||||||
utilities. Kein `@import`, kein Inline-Style. Komponenten-Styles in `components.css` mit
|
utilities. Kein `@import`, **kein Inline-Style — die CSP erzwingt das, und zwar nur in
|
||||||
|
Produktion.** `default-src 'self'` ohne `style-src` heißt: der Browser verwirft jedes
|
||||||
|
`style`-Attribut und jeden `<style>`-Block stillschweigend. Lokal fällt das **nie** auf,
|
||||||
|
weil `php -S` keine `.htaccess` liest. Genau so blieben die Banner-Slides beim ersten
|
||||||
|
Deploy schwarz (31.07.2026): die Komponente setzte `style="--banner-bg: url(…)"`.
|
||||||
|
**Die zwei Auswege, wenn ein Wert aus `data/*.json` ins Aussehen muss:**
|
||||||
|
ein **Bild** wird ein `<img>` (die CSP erlaubt `img-src 'self' data:`, und `srcset`
|
||||||
|
gibt es gratis dazu), eine **Farbe** wird ein Token in `tokens.css` plus eine Klasse in
|
||||||
|
`components.css`, und die JSON nennt nur den Namen (`"accent": "breadcrumb"`).
|
||||||
|
`bin/preflight.php` bricht mit Fehler ab, wenn irgendwo unter `app/` ein `style`-Attribut
|
||||||
|
auftaucht. Per JavaScript gesetzte Styles (`element.style.…`) sind nicht betroffen.
|
||||||
|
Komponenten-Styles in `components.css` mit
|
||||||
Banner-Kommentaren (`/* === hero === */`).
|
Banner-Kommentaren (`/* === hero === */`).
|
||||||
**Kontext-Regeln setzen Custom Properties, kein Layout.** Wenn eine Komponente in einem Kontext
|
**Kontext-Regeln setzen Custom Properties, kein Layout.** Wenn eine Komponente in einem Kontext
|
||||||
anders aussehen soll, gehört auf `.kontext .komponente` nur die Variable (`--crest-size`,
|
anders aussehen soll, gehört auf `.kontext .komponente` nur die Variable (`--crest-size`,
|
||||||
|
|||||||
@@ -6,11 +6,23 @@ declare(strict_types=1);
|
|||||||
* Werbe-/Sponsoren-Banner-Slider. Daten: data/banners.json (Single Source of Truth).
|
* Werbe-/Sponsoren-Banner-Slider. Daten: data/banners.json (Single Source of Truth).
|
||||||
* Layout je Slide: 2/3 Titel + Text + CTA, 1/3 Firmenlogo.
|
* Layout je Slide: 2/3 Titel + Text + CTA, 1/3 Firmenlogo.
|
||||||
* Pro Slide optional: "variant" (CSS-Modifier, z. B. "house" für Eigenwerbung),
|
* Pro Slide optional: "variant" (CSS-Modifier, z. B. "house" für Eigenwerbung),
|
||||||
* "tag" (Badge-Text, Default "Anzeige"), "background" (Bild), "cta.internal"
|
* "tag" (Badge-Text, Default "Anzeige"), "cta.internal"
|
||||||
* (interner Link ohne target=_blank/rel=sponsored, für eigene Seiten/Anker),
|
* (interner Link ohne target=_blank/rel=sponsored, für eigene Seiten/Anker),
|
||||||
* "cta_secondary" (zweiter Button als Outline), "logo_light" (helle Logo-Platte
|
* "cta_secondary" (zweiter Button als Outline), "logo_light" (helle Logo-Platte
|
||||||
* für dunkle Firmenlogos), "accent" (CSS-Farbe; überschreibt das Vereins-Rot
|
* für dunkle Firmenlogos).
|
||||||
* der Buttons für diese Slide, z. B. Sponsor-CI).
|
*
|
||||||
|
* "background" ist ein Varianten-Set {base, widths} und wird als <img> gerendert,
|
||||||
|
* NICHT als CSS-Hintergrund. Grund: die CSP (default-src 'self', kein style-src)
|
||||||
|
* verwirft jedes style-Attribut, eine Inline-Variable --banner-bg käme also nie an.
|
||||||
|
* Das fiel lokal nie auf, weil `php -S` keine .htaccess und damit keine CSP kennt —
|
||||||
|
* erst auf echtem Apache blieben die Slides schwarz (31.07.2026). Als <img> ist es
|
||||||
|
* ohnehin besser: srcset lädt am Handy die 640er statt der 1600er. Ein String bleibt
|
||||||
|
* als Einzeldatei zulässig (Bestandsschutz, kein srcset).
|
||||||
|
*
|
||||||
|
* "accent" ist ein NAME, keine Farbe ("breadcrumb") — die Farbe steht als Token in
|
||||||
|
* tokens.css, die Klasse ad-banner__slide--accent-<name> in components.css. Ein
|
||||||
|
* beliebiger Hex-Wert aus der JSON käme nur per Inline-Style in die Seite, und den
|
||||||
|
* verwirft die CSP. Neuer Sponsor mit eigener CI = ein Token plus eine Klasse.
|
||||||
* Auto-Rotation + Fortschritts-Indikatoren übernimmt der generische Antrieb
|
* Auto-Rotation + Fortschritts-Indikatoren übernimmt der generische Antrieb
|
||||||
* assets/js/carousel.js (pausiert bei Hover/Fokus, respektiert prefers-reduced-motion).
|
* assets/js/carousel.js (pausiert bei Hover/Fokus, respektiert prefers-reduced-motion).
|
||||||
* Ohne JS: nur der erste Banner ist sichtbar (Graceful Degradation).
|
* Ohne JS: nur der erste Banner ist sichtbar (Graceful Degradation).
|
||||||
@@ -34,25 +46,30 @@ $total = count($banners);
|
|||||||
$variant = $banner['variant'] ?? null;
|
$variant = $banner['variant'] ?? null;
|
||||||
$accent = $banner['accent'] ?? null;
|
$accent = $banner['accent'] ?? null;
|
||||||
|
|
||||||
|
// Slug-Filter für beides: der Wert landet in einem Klassennamen, nie ungeprüft.
|
||||||
|
$slug = static fn (string $v): string => (string) preg_replace('/[^a-z0-9-]+/', '', strtolower($v));
|
||||||
|
|
||||||
$classes = 'ad-banner__slide';
|
$classes = 'ad-banner__slide';
|
||||||
if ($bg) {
|
if ($bg) {
|
||||||
$classes .= ' ad-banner__slide--bg';
|
$classes .= ' ad-banner__slide--bg';
|
||||||
}
|
}
|
||||||
if ($accent) {
|
if ($accent) {
|
||||||
$classes .= ' ad-banner__slide--accent';
|
$classes .= ' ad-banner__slide--accent ad-banner__slide--accent-' . $slug((string) $accent);
|
||||||
}
|
}
|
||||||
if ($variant) {
|
if ($variant) {
|
||||||
$classes .= ' ad-banner__slide--' . preg_replace('/[^a-z0-9-]+/', '', strtolower($variant));
|
$classes .= ' ad-banner__slide--' . $slug((string) $variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
$styleParts = [];
|
// Hintergrundbild als Bild-Prop für die img-Komponente. alt="" — der Hintergrund
|
||||||
if ($bg) {
|
// ist Dekoration, die Aussage der Slide steht als Text daneben.
|
||||||
$styleParts[] = '--banner-bg: url(' . asset($bg) . ')';
|
$bgImage = null;
|
||||||
|
if (is_array($bg) && !empty($bg['base'])) {
|
||||||
|
$bgImage = ['base' => $bg['base'], 'widths' => $bg['widths'] ?? [1600], 'alt' => ''];
|
||||||
|
} elseif (is_string($bg) && $bg !== '') {
|
||||||
|
$datei = PUBLIC_PATH . '/assets/' . ltrim($bg, '/');
|
||||||
|
[$w, $h] = is_file($datei) ? (getimagesize($datei) ?: [0, 0]) : [0, 0];
|
||||||
|
$bgImage = ['src' => $bg, 'width' => $w, 'height' => $h, 'alt' => ''];
|
||||||
}
|
}
|
||||||
if ($accent) {
|
|
||||||
$styleParts[] = '--banner-accent: ' . $accent;
|
|
||||||
}
|
|
||||||
$style = $styleParts !== [] ? ' style="' . e(implode('; ', $styleParts)) . '"' : '';
|
|
||||||
?>
|
?>
|
||||||
<?php
|
<?php
|
||||||
// Rendert einen CTA-Link; externe Links öffnen im neuen Tab (rel=sponsored),
|
// Rendert einen CTA-Link; externe Links öffnen im neuen Tab (rel=sponsored),
|
||||||
@@ -69,7 +86,17 @@ $total = count($banners);
|
|||||||
. '</a>';
|
. '</a>';
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
<li class="<?= e($classes) ?>"<?= $style ?> data-carousel-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
|
<li class="<?= e($classes) ?>" data-carousel-slide role="group" aria-roledescription="Folie" aria-label="<?= ($i + 1) ?> von <?= $total ?>"<?= $i === 0 ? '' : ' hidden' ?>>
|
||||||
|
<?php if ($bgImage !== null): ?>
|
||||||
|
<?php /* Erstes Kind und absolut hinter dem Inhalt. Die erste Slide lädt eager
|
||||||
|
(sie ist above the fold und trägt das LCP), die folgenden lazy. */ ?>
|
||||||
|
<?php component('img', [
|
||||||
|
'image' => $bgImage,
|
||||||
|
'class' => 'ad-banner__bg',
|
||||||
|
'sizes' => '(max-width: 1200px) 100vw, 1200px',
|
||||||
|
'eager' => $i === 0,
|
||||||
|
]); ?>
|
||||||
|
<?php endif; ?>
|
||||||
<?php if (!empty($banner['logo'])): ?>
|
<?php if (!empty($banner['logo'])): ?>
|
||||||
<div class="ad-banner__logo<?= !empty($banner['logo_light']) ? ' ad-banner__logo--light' : '' ?>">
|
<div class="ad-banner__logo<?= !empty($banner['logo_light']) ? ' ad-banner__logo--light' : '' ?>">
|
||||||
<img src="<?= e(asset($banner['logo'])) ?>" alt="<?= e($banner['company'] ?? $banner['title']) ?>"<?= img_intrinsic_attrs($banner['logo']) ?> loading="lazy" decoding="async">
|
<img src="<?= e(asset($banner['logo'])) ?>" alt="<?= e($banner['company'] ?? $banner['title']) ?>"<?= img_intrinsic_attrs($banner['logo']) ?> loading="lazy" decoding="async">
|
||||||
|
|||||||
@@ -296,6 +296,49 @@ if ($cronKey === '' || $cronKey === 'CHANGE_ME') {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- Inline-Styles (die CSP verwirft sie stillschweigend) ---
|
||||||
|
// Diese Prüfung existiert wegen eines echten Ausfalls am 31.07.2026: der
|
||||||
|
// Banner-Slider setzte sein Hintergrundbild per style="--banner-bg: url(…)". Auf
|
||||||
|
// echtem Apache greift die CSP aus public/.htaccess (default-src 'self', KEIN
|
||||||
|
// style-src), der Browser verwarf das Attribut, die Slides blieben schwarz. Lokal
|
||||||
|
// war das unsichtbar, weil `php -S` keine .htaccess liest — der Fehler konnte also
|
||||||
|
// erst in Produktion auffallen. Genau davor schützt diese Wache.
|
||||||
|
// Hinweis: per JavaScript gesetzte Styles (element.style.…) sind NICHT betroffen,
|
||||||
|
// die CSP prüft nur vom HTML-Parser gelesene style-Attribute und <style>-Blöcke.
|
||||||
|
/* Zeilenweise und ohne Tag-Kontext geprüft: ein Muster, das beim öffnenden Tag
|
||||||
|
ansetzt und mit [^>]* weitersucht, bricht sofort ab — PHP-Templates enthalten
|
||||||
|
über das schließende PHP-Tag ständig eine spitze Klammer. Ein style-Attribut in
|
||||||
|
einer PHP-Datei ist praktisch immer echtes Markup, diese Prüfung genügt also.
|
||||||
|
|
||||||
|
Dieser Kommentar ist bewusst ein Blockkommentar: die Zeichenfolge des schließenden
|
||||||
|
PHP-Tags beendet den PHP-Modus AUCH in einem //-Kommentar. Genau daran ist diese
|
||||||
|
Prüfung beim Bauen einmal gescheitert (preflight gab ab dort seinen eigenen
|
||||||
|
Quelltext aus, und php -l meldete nichts, weil das gültiges PHP mit HTML ist). */
|
||||||
|
$inlineStyles = [];
|
||||||
|
$phpDateien = new RecursiveIteratorIterator(new RecursiveDirectoryIterator(ROOT_PATH . '/app'));
|
||||||
|
foreach ($phpDateien as $datei) {
|
||||||
|
if (!$datei->isFile() || $datei->getExtension() !== 'php') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foreach (file($datei->getPathname()) ?: [] as $nr => $zeile) {
|
||||||
|
$roh = ltrim($zeile);
|
||||||
|
// Kommentarzeilen überspringen — dieser Abschnitt selbst nennt style=" ja auch.
|
||||||
|
if ($roh === '' || str_starts_with($roh, '*') || str_starts_with($roh, '//') || str_starts_with($roh, '/*')) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (preg_match('/\sstyle\s*=\s*"/i', $zeile) === 1) {
|
||||||
|
$kurz = str_replace(ROOT_PATH . '/', '', $datei->getPathname());
|
||||||
|
$inlineStyles[] = $kurz . ':' . ($nr + 1);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if ($inlineStyles !== []) {
|
||||||
|
$errors[] = 'Inline-Style im Markup — die CSP (kein style-src) verwirft ihn, die Regel greift NUR in Produktion: '
|
||||||
|
. implode(', ', $inlineStyles)
|
||||||
|
. '. Bilder als <img> ausgeben, Farben als Token in tokens.css plus Klasse in components.css.';
|
||||||
|
}
|
||||||
|
|
||||||
// --- Ausgabe ---
|
// --- Ausgabe ---
|
||||||
foreach ($warnings as $w) {
|
foreach ($warnings as $w) {
|
||||||
echo "WARNUNG {$w}\n";
|
echo "WARNUNG {$w}\n";
|
||||||
|
|||||||
@@ -9,8 +9,19 @@
|
|||||||
"title": "Hier könnte Ihre Werbung stehen",
|
"title": "Hier könnte Ihre Werbung stehen",
|
||||||
"text": "Werden Sie Werbepartner des TSV 08 Kulmbach und zeigen Sie Ihr Unternehmen genau hier: sichtbar für tausende sportbegeisterte Menschen aus Kulmbach und dem Katzbachtal.",
|
"text": "Werden Sie Werbepartner des TSV 08 Kulmbach und zeigen Sie Ihr Unternehmen genau hier: sichtbar für tausende sportbegeisterte Menschen aus Kulmbach und dem Katzbachtal.",
|
||||||
"logo": "img/logo.svg",
|
"logo": "img/logo.svg",
|
||||||
"background": "img/pages/fussball-hero-1600.jpg",
|
"background": {
|
||||||
"cta": { "label": "Werbepartner werden", "url": "/partner-werden", "internal": true }
|
"base": "img/pages/fussball-hero",
|
||||||
|
"widths": [
|
||||||
|
640,
|
||||||
|
1000,
|
||||||
|
1600
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"label": "Werbepartner werden",
|
||||||
|
"url": "/partner-werden",
|
||||||
|
"internal": true
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"company": "Breadcrumb Solutions GmbH",
|
"company": "Breadcrumb Solutions GmbH",
|
||||||
@@ -18,9 +29,19 @@
|
|||||||
"text": "Beratung, Software & Implementierung aus einer Hand. Wir entwickeln Intranets, Kundenportale, Apps und Individualsoftware, inklusive Einführung, sodass sie im Alltag tatsächlich ankommt.",
|
"text": "Beratung, Software & Implementierung aus einer Hand. Wir entwickeln Intranets, Kundenportale, Apps und Individualsoftware, inklusive Einführung, sodass sie im Alltag tatsächlich ankommt.",
|
||||||
"logo": "img/banners/breadcrumb-solutions.svg",
|
"logo": "img/banners/breadcrumb-solutions.svg",
|
||||||
"logo_light": true,
|
"logo_light": true,
|
||||||
"accent": "#6cbbbc",
|
"accent": "breadcrumb",
|
||||||
"background": "img/banners/breadcrumb-hero-1600.jpg",
|
"background": {
|
||||||
"cta": { "label": "Neue Website entdecken", "url": "https://www.breadcrumb-solutions.de/" }
|
"base": "img/banners/breadcrumb-hero",
|
||||||
|
"widths": [
|
||||||
|
640,
|
||||||
|
1000,
|
||||||
|
1600
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"cta": {
|
||||||
|
"label": "Neue Website entdecken",
|
||||||
|
"url": "https://www.breadcrumb-solutions.de/"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2110,21 +2110,49 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* Optionaler Hintergrund pro Banner (data: "background").
|
/* Optionaler Hintergrund pro Banner (data: "background").
|
||||||
Dunkler Verlauf über dem Bild hält Text & CTA links lesbar. */
|
Das Bild ist ein <img class="ad-banner__bg">, KEIN CSS-Hintergrund: die CSP
|
||||||
|
(default-src 'self', kein style-src) verwirft style-Attribute, eine Inline-Variable
|
||||||
|
--banner-bg kam auf echtem Apache nie an (31.07.2026, Slides blieben schwarz).
|
||||||
|
Der dunkle Verlauf, der Text und CTA links lesbar hält, liegt deshalb auf ::before. */
|
||||||
.ad-banner__slide--bg {
|
.ad-banner__slide--bg {
|
||||||
background-image:
|
position: relative;
|
||||||
linear-gradient(to right, rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.45) 60%, rgb(0 0 0 / 0.3)),
|
/* Eigener Stapelkontext: das Bild darf hinter den Inhalt, aber nicht hinter
|
||||||
var(--banner-bg);
|
die Slide-Fläche selbst rutschen. */
|
||||||
background-size: cover;
|
isolation: isolate;
|
||||||
background-position: center;
|
|
||||||
/* Bild an der Innenkante kappen, nicht unter den 1px-Rahmen malen
|
|
||||||
(sonst schimmert es an der transparenten Rahmenkante durch). */
|
|
||||||
background-clip: padding-box;
|
|
||||||
border-color: transparent;
|
border-color: transparent;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.ad-banner__bg,
|
||||||
|
.ad-banner__slide--bg::before {
|
||||||
|
position: absolute;
|
||||||
|
/* inset bezieht sich auf die Padding-Box — das Bild endet an der Innenkante und
|
||||||
|
malt nicht unter den 1px-Rahmen (dort schimmerte es sonst durch). */
|
||||||
|
inset: 0;
|
||||||
|
border-radius: inherit;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ad-banner__bg {
|
||||||
|
z-index: -2;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.ad-banner__slide--bg::before {
|
||||||
|
content: '';
|
||||||
|
z-index: -1;
|
||||||
|
background-image: linear-gradient(to right, rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.45) 60%, rgb(0 0 0 / 0.3));
|
||||||
|
}
|
||||||
|
|
||||||
/* Pro-Slide-Akzentfarbe (data: "accent") — z. B. Sponsor-CI statt Vereins-Rot.
|
/* Pro-Slide-Akzentfarbe (data: "accent") — z. B. Sponsor-CI statt Vereins-Rot.
|
||||||
--banner-accent kommt als Inline-Variable von der Komponente. */
|
Der Name kommt aus der JSON, die Farbe aus tokens.css: ein Hex-Wert aus der JSON
|
||||||
|
käme nur per Inline-Style in die Seite, und den verwirft die CSP (siehe oben). */
|
||||||
|
.ad-banner__slide--accent-breadcrumb {
|
||||||
|
--banner-accent: var(--clr-banner-accent-breadcrumb);
|
||||||
|
}
|
||||||
|
|
||||||
.ad-banner__slide--accent {
|
.ad-banner__slide--accent {
|
||||||
--clr-accent: var(--banner-accent);
|
--clr-accent: var(--banner-accent);
|
||||||
--clr-accent-dark: color-mix(in srgb, var(--banner-accent) 82%, #000);
|
--clr-accent-dark: color-mix(in srgb, var(--banner-accent) 82%, #000);
|
||||||
@@ -2153,10 +2181,8 @@
|
|||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
/* Auf Mobil läuft der Verlauf nach unten, damit der gestapelte Text lesbar bleibt. */
|
/* Auf Mobil läuft der Verlauf nach unten, damit der gestapelte Text lesbar bleibt. */
|
||||||
.ad-banner__slide--bg {
|
.ad-banner__slide--bg::before {
|
||||||
background-image:
|
background-image: linear-gradient(to bottom, rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.5));
|
||||||
linear-gradient(to bottom, rgb(0 0 0 / 0.82), rgb(0 0 0 / 0.5)),
|
|
||||||
var(--banner-bg);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -11,6 +11,14 @@
|
|||||||
--clr-accent-hsl: 356.7 94.8% 45.5%; /* dasselbe Rot als H S L */
|
--clr-accent-hsl: 356.7 94.8% 45.5%; /* dasselbe Rot als H S L */
|
||||||
--clr-accent-h: 356.7; /* nur der Farbton — die Dateiablage (/dateien)
|
--clr-accent-h: 356.7; /* nur der Farbton — die Dateiablage (/dateien)
|
||||||
leitet daraus ihre Flächen ab, siehe dateien.css */
|
leitet daraus ihre Flächen ab, siehe dateien.css */
|
||||||
|
/* Akzentfarben einzelner Werbe-Banner (Sponsor-CI statt Vereins-Rot).
|
||||||
|
Sie stehen hier und nicht in data/banners.json, weil ein Hex-Wert aus der JSON
|
||||||
|
nur per Inline-Style in die Seite käme — und den verwirft die CSP
|
||||||
|
(default-src 'self', kein style-src). Die JSON nennt nur den Namen
|
||||||
|
("accent": "breadcrumb"), components.css bildet ihn auf dieses Token ab.
|
||||||
|
Neuer Sponsor mit eigener CI = eine Zeile hier plus eine Klasse dort. */
|
||||||
|
--clr-banner-accent-breadcrumb: #6cbbbc;
|
||||||
|
|
||||||
--clr-bg: #222222; /* Grund-Hintergrund (mit Textur) */
|
--clr-bg: #222222; /* Grund-Hintergrund (mit Textur) */
|
||||||
--clr-bg-deep: #161616; /* abgesetzte Sektionen */
|
--clr-bg-deep: #161616; /* abgesetzte Sektionen */
|
||||||
--clr-bg-footer: #000000;
|
--clr-bg-footer: #000000;
|
||||||
|
|||||||
BIN
public/assets/img/banners/breadcrumb-hero-1000.jpg
Normal file
BIN
public/assets/img/banners/breadcrumb-hero-1000.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 49 KiB |
BIN
public/assets/img/banners/breadcrumb-hero-640.jpg
Normal file
BIN
public/assets/img/banners/breadcrumb-hero-640.jpg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 25 KiB |
Reference in New Issue
Block a user