Give the header scrolled colours and equal card heights in 1.11.1
Cards, quotes and call-to-action bands side by side in a row are equal in height again: the stacking margin meant for a column no longer applies inside a row. The header can carry a second set of colours for the scrolled page, the Flatsome transparent header: a "scrolled" state next to "hover", limited to colours, shadow and opacity, marked by a small script only where it is used, plus a setting that lets the header float over the first section. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
1 parent
b634f486e5
commit
3785d3dfef
18 files changed
+181
-14
No files matched your search
@@ -29,6 +29,10 @@ final class SiteSettings
|
||||
// Der Kopfbereich bleibt beim Scrollen oben stehen. Eine Eigenschaft der Website und
|
||||
// nicht des Dokuments: kleben kann nur das Element, das den Bereich umschliesst.
|
||||
'stickyHeader' => false,
|
||||
// Der Kopfbereich liegt über dem Inhalt statt darüber zu stehen - der Flatsome-Aufbau mit
|
||||
// transparentem Kopf über dem Hero. Welche Farben er beim Scrollen bekommt, bestimmt die
|
||||
// Gestaltung des Kopfbereichs selbst („beim Scrollen" unter Hintergrund).
|
||||
'headerOverlay' => false,
|
||||
// Einblendungen und Hover-Bewegungen. „always" spielt sie wie Flatsome bei jedem
|
||||
// Besucher ab; „respect" hält sich an die Browservorgabe „Bewegung reduzieren", die
|
||||
// manche Systeme melden, ohne dass jemand sie bewusst eingestellt hat.
|
||||
@@ -135,6 +139,7 @@ final class SiteSettings
|
||||
}
|
||||
$values['llmsTxt'] = (bool) ($input['llmsTxt'] ?? false);
|
||||
$values['stickyHeader'] = (bool) ($input['stickyHeader'] ?? false);
|
||||
$values['headerOverlay'] = (bool) ($input['headerOverlay'] ?? false);
|
||||
$favicon = trim((string) ($input['faviconMediaId'] ?? ''));
|
||||
if ('' !== $favicon) {
|
||||
if (1 !== preg_match('/^[a-f0-9]{32}$/D', $favicon) || null === $this->media->find($favicon)) {
|
||||
|
||||
@@ -49,6 +49,14 @@ final class StyleCompiler
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if ('scrolled' === $key) {
|
||||
$this->require(is_array($value), 'Scrolled styles must be an object.');
|
||||
foreach ($value as $scrolledKey => $scrolledValue) {
|
||||
$this->require(in_array($scrolledKey, StyleSchema::scrollable(), true), sprintf('The property "%s" has no scrolled value.', (string) $scrolledKey));
|
||||
$this->value($properties[$scrolledKey], $scrolledValue, (string) $scrolledKey);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
$this->require(is_string($key) && isset($properties[$key]), sprintf('Unknown style property "%s".', (string) $key));
|
||||
$this->value($properties[$key], $value, $key);
|
||||
}
|
||||
@@ -181,6 +189,12 @@ final class StyleCompiler
|
||||
if ('' !== $hover) {
|
||||
$rules .= sprintf('#%s:hover,#%s:focus-within{%s}', $id, $id, $hover);
|
||||
}
|
||||
// The scrolled state: scrolled.js puts `is-scrolled` on <html> once the page has moved.
|
||||
// The change eases in, so a header does not snap from glass to solid.
|
||||
$scrolled = is_array($values['scrolled'] ?? null) ? $this->declarations($values['scrolled'], 'self', $device) : '';
|
||||
if ('' !== $scrolled) {
|
||||
$rules .= sprintf('#%s{transition:background-color .3s,color .3s,border-color .3s,box-shadow .3s,opacity .3s}html.is-scrolled #%s{%s}', $id, $id, $scrolled);
|
||||
}
|
||||
if ('' === $rules) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -219,6 +219,16 @@ final class StyleSchema
|
||||
return array_keys(array_filter(self::properties(), static fn (array $p): bool => $p['hover'] ?? false));
|
||||
}
|
||||
|
||||
/**
|
||||
* Properties that may also carry a value for the scrolled page - the Flatsome transparent
|
||||
* header that turns solid once the reader has scrolled. Colours, shadow and opacity only:
|
||||
* a header that changes size while scrolling would push the page around under the finger.
|
||||
*/
|
||||
public static function scrollable(): array
|
||||
{
|
||||
return ['backgroundColor', 'color', 'borderColor', 'shadowColor', 'opacity'];
|
||||
}
|
||||
|
||||
public static function sides(): array
|
||||
{
|
||||
return ['top' => 'Oben', 'right' => 'Rechts', 'bottom' => 'Unten', 'left' => 'Links'];
|
||||
|
||||
@@ -127,6 +127,7 @@ final class EditorController extends AbstractController
|
||||
'sides' => StyleSchema::sides(),
|
||||
'corners' => StyleSchema::corners(),
|
||||
'hoverable' => StyleSchema::hoverable(),
|
||||
'scrollable' => StyleSchema::scrollable(),
|
||||
'colors' => $theme['colors'],
|
||||
'fonts' => $theme['fonts'],
|
||||
'presets' => $theme['presets'],
|
||||
|
||||
@@ -80,6 +80,9 @@ final class PageController extends AbstractController
|
||||
$documents = [$page['document'], ...array_values($areas), ...array_values($sections)];
|
||||
$needsSlider = (bool) array_filter($documents, fn (array $doc): bool => $this->hasType($doc, 'slider'));
|
||||
$needsMotion = (bool) array_filter($documents, fn (array $doc): bool => $this->hasEntrance($doc));
|
||||
// Only the header changes with the scroll position; a scrolled value anywhere in it asks
|
||||
// for the small script that marks the page as scrolled.
|
||||
$needsScrolled = isset($areas['header']) && $this->hasScrolled($areas['header']);
|
||||
$styles = $this->theme->css($theme).$this->scrollTopStyles($this->settings->load(), $theme).$this->styles->compileDocument($page['document']).$this->code->compileDocument($page['document']);
|
||||
foreach ([...array_values($areas), ...array_values($sections)] as $extra) {
|
||||
$styles .= $this->styles->compileDocument($extra).$this->code->compileDocument($extra);
|
||||
@@ -107,6 +110,7 @@ final class PageController extends AbstractController
|
||||
// dafür ein Skript braucht - nach demselben Muster wie die Tabs.
|
||||
'needsSlider' => $needsSlider,
|
||||
'needsMotion' => $needsMotion,
|
||||
'needsScrolled' => $needsScrolled,
|
||||
// Ein Bild, das sich vergroessern laesst, ist ohne Skript ein Verweis auf die
|
||||
// grosse Fassung. Das Skript kommt nur dazu, wo es etwas zu tun hat.
|
||||
'needsZoom' => $this->hasZoom($page['document']) || (bool) array_filter(array_values($areas), fn (array $area): bool => $this->hasZoom($area)),
|
||||
@@ -136,7 +140,7 @@ final class PageController extends AbstractController
|
||||
// Die Pruefung braucht ein Skript von Cloudflare und einen Rahmen dafuer. Beides wird nur
|
||||
// dort erlaubt, wo tatsaechlich ein Formular steht - nicht auf jeder Seite der Website.
|
||||
$mitTurnstile = $this->turnstile->isActive() && $this->hasType($page['document'], 'form');
|
||||
$wantsScript = ($mitTurnstile || $this->hasType($page['document'], 'tabs') || $needsSlider || $needsMotion || $this->hasZoom($page['document']) || [] !== $this->code->scriptsOf($page['document']));
|
||||
$wantsScript = ($mitTurnstile || $this->hasType($page['document'], 'tabs') || $needsSlider || $needsMotion || $needsScrolled || $this->hasZoom($page['document']) || [] !== $this->code->scriptsOf($page['document']));
|
||||
$frames = $this->frameHosts($request, $page['document'], ...array_values($areas), ...array_values($sections));
|
||||
if ($mitTurnstile) {
|
||||
$frames[] = 'challenges.cloudflare.com';
|
||||
@@ -413,6 +417,27 @@ final class PageController extends AbstractController
|
||||
return $gefunden;
|
||||
}
|
||||
|
||||
/** Whether any block carries values for the scrolled page (see StyleSchema::scrollable). */
|
||||
private function hasScrolled(array $document): bool
|
||||
{
|
||||
$walk = function (array $blocks) use (&$walk): bool {
|
||||
foreach ($blocks as $block) {
|
||||
foreach ($block['style'] ?? [] as $values) {
|
||||
if ([] !== ($values['scrolled'] ?? [])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ($walk($block['children'] ?? [])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
return $walk($document['blocks'] ?? []);
|
||||
}
|
||||
|
||||
private function hasEntrance(array $document): bool
|
||||
{
|
||||
$walk = function (array $blocks) use (&$walk): bool {
|
||||
@@ -464,10 +489,13 @@ final class PageController extends AbstractController
|
||||
}
|
||||
$base = null === $term ? '/beitraege' : '/'.PostRepository::ARCHIVE_PREFIX[$kind].'/'.$term['slug'];
|
||||
|
||||
$frames = $this->frameHosts($request, ...array_values($this->areas()));
|
||||
$areas = $this->areas();
|
||||
$frames = $this->frameHosts($request, ...array_values($areas));
|
||||
$needsScrolled = isset($areas['header']) && $this->hasScrolled($areas['header']);
|
||||
|
||||
return $this->shell($this->render('archive.html.twig', [
|
||||
'areas' => $this->areas(),
|
||||
'areas' => $areas,
|
||||
'needsScrolled' => $needsScrolled,
|
||||
'navigation' => $this->navigation(''),
|
||||
'term' => $term,
|
||||
'heading' => null === $term ? 'Beiträge' : $term['name'],
|
||||
@@ -475,7 +503,7 @@ final class PageController extends AbstractController
|
||||
'base' => $base,
|
||||
'canonical' => rtrim($this->siteOrigin, '/').$base.($result['page'] > 1 ? '?seite='.$result['page'] : ''),
|
||||
'navigation' => $this->navigation(''),
|
||||
]), frameHosts: $frames);
|
||||
]), $needsScrolled, frameHosts: $frames);
|
||||
}
|
||||
|
||||
#[Route('/feed.xml', name: 'feed', methods: ['GET'])]
|
||||
|
||||
Reference in new issue
Block a user