Build Hüfner Gartenbau as a ninth installation, let buttons call and write, colour the button arrow like its text
- Website of Garten- und Landschaftsbau Hüfner GmbH rebuilt from the delivered design with imprint, privacy, 404 and the enquiry form (scripts/reset-huefner.sh, ports 8102/8103), Barlow and Barlow Condensed served locally with their licence. - Buttons, icon cards, calls to action, icons and linked images accept tel: and mailto: links, the same targets menus and rich text already allowed (ADR 107). - The arrow inside a button takes the button's text colour instead of the accent colour. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
0ba82b7d33
commit
5bfe1f3c6e
24 files changed
+1576
-11
No files matched your search
@@ -236,14 +236,14 @@ final class DocumentValidator
|
||||
}
|
||||
}
|
||||
if ('button' === $type) {
|
||||
$this->url($props['href'] ?? null);
|
||||
$this->link($props['href'] ?? null);
|
||||
}
|
||||
if ('iconcard' === $type) {
|
||||
$this->require(in_array($props['icon'] ?? null, self::ICONS, true), 'Unknown icon.');
|
||||
$this->text($props['title'] ?? null, 120);
|
||||
$this->text($props['text'] ?? null, 600, true);
|
||||
if ('' !== ($props['href'] ?? '')) {
|
||||
$this->url($props['href']);
|
||||
$this->link($props['href']);
|
||||
}
|
||||
}
|
||||
if ('stat' === $type) {
|
||||
@@ -261,7 +261,7 @@ final class DocumentValidator
|
||||
$this->text($props['title'] ?? null, 160);
|
||||
$this->text($props['text'] ?? null, 600, true);
|
||||
$this->text($props['buttonText'] ?? null, 80);
|
||||
$this->url($props['href'] ?? null);
|
||||
$this->link($props['href'] ?? null);
|
||||
}
|
||||
if ('contact' === $type) {
|
||||
foreach (['phone', 'email', 'address'] as $field) {
|
||||
@@ -329,7 +329,7 @@ final class DocumentValidator
|
||||
$this->text($props['label'] ?? null, 120, $props['decorative']);
|
||||
$this->require(!$props['decorative'] || '' === $props['label'], 'Ein dekoratives Symbol hat keine Beschriftung.');
|
||||
if ('' !== ($props['href'] ?? '')) {
|
||||
$this->url($props['href']);
|
||||
$this->link($props['href']);
|
||||
}
|
||||
}
|
||||
if ('social' === $type) {
|
||||
@@ -414,7 +414,7 @@ final class DocumentValidator
|
||||
// Kopfbereich ohne Verweis auf die Startseite ist der erste Klick, den jeder sucht.
|
||||
if ('' !== ($props['href'] ?? '')) {
|
||||
$this->require($schemaVersion >= 17, 'Ein verlinktes Bild braucht Schema v17.');
|
||||
$this->url($props['href']);
|
||||
$this->link($props['href']);
|
||||
}
|
||||
// Was ein Klick auf das Bild tut. Ohne Angabe: nichts - wie bisher.
|
||||
if (isset($props['click'])) {
|
||||
@@ -498,6 +498,38 @@ final class DocumentValidator
|
||||
$this->require($local || $external || 1 === preg_match('/^#[a-zA-Z][a-zA-Z0-9_-]*$/D', $url), 'Only local paths, fragments and HTTPS URLs are allowed.');
|
||||
}
|
||||
|
||||
/**
|
||||
* Ein Linkziel: alles, was url() erlaubt, dazu tel: und mailto: - dieselben Ziele, die Menü
|
||||
* (MenuRepository::url) und formatierter Text (RichText::safeHref) schon kennen. Ein Knopf
|
||||
* „Jetzt anrufen“ war sonst nur als Textlink im Knopf-Aussehen zu bauen (Hüfner, 06.10.2026).
|
||||
* Bildquellen und eingebettete Inhalte bleiben bei url(): dort ist tel: kein Ziel.
|
||||
*/
|
||||
private function link(mixed $url): void
|
||||
{
|
||||
if (is_string($url) && strlen($url) <= 2048 && !preg_match('/[\x00-\x20\\\\]/', $url)) {
|
||||
if (1 === preg_match('/^tel:\+?[0-9()-]{3,32}$/D', $url)) {
|
||||
return;
|
||||
}
|
||||
// Adresse vor dem ersten ?, danach höchstens Betreff und Text, prozentkodiert.
|
||||
if (1 === preg_match('/^mailto:([^?]+)(\?[A-Za-z0-9%._~=&+-]{0,500})?$/D', $url, $teile)
|
||||
&& false !== filter_var(rawurldecode($teile[1]), FILTER_VALIDATE_EMAIL)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
$this->require(true === $this->isUrl($url), 'Erlaubt sind lokale Pfade, Sprungmarken, HTTPS-Adressen sowie tel: und mailto:.');
|
||||
}
|
||||
|
||||
private function isUrl(mixed $url): bool
|
||||
{
|
||||
try {
|
||||
$this->url($url);
|
||||
|
||||
return true;
|
||||
} catch (InvalidDocument) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private function require(bool $condition, string $message): void
|
||||
{
|
||||
if (!$condition) {
|
||||
|
||||
Reference in new issue
Block a user