The icon set grows from 33 to 98 drawings in seven categories (arrows, contact, commerce, files and media, people and security, house and trade, general), including truck, cart, euro, camera, tools and solar. icons.js is the single source; npm run build:icons writes the Twig drawings and the validator list, and a test keeps them equal. Choosing an icon no longer fills the panel with tiles: the panel shows the current icon with "Change", which opens a modal with category tabs and named tiles, keyboard navigable, closing with Escape. Button, icon and icon card share it. A new style group "Symbol" targets the icon inside a button per device and on hover: colour, background, size, padding, radius, rotation, plus the gap to the text. Schema v26 adds hover motion (slide, reveal) and an icon-only button whose label stays as the accessible name and keeps the line height. ADR 109. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
281 lines
26 KiB
PHP
281 lines
26 KiB
PHP
<?php
|
||
|
||
namespace App\Content;
|
||
|
||
/**
|
||
* The single source of truth for free block styling.
|
||
*
|
||
* Every property is declared once here with its type, its CSS counterpart and its limits. The
|
||
* validator, the CSS generator and the editor panel all read this definition, so the three can
|
||
* never drift apart: what the editor offers is exactly what the server accepts and renders.
|
||
*
|
||
* Values are stored as short strings ("18px", "#1a2b3c", "flex"), never as CSS text. Each type has
|
||
* an anchored pattern or a fixed list, so no declaration can escape its property.
|
||
*/
|
||
final class StyleSchema
|
||
{
|
||
public const DEVICES = ['desktop' => 'Desktop', 'tablet' => 'Tablet', 'mobile' => 'Mobil'];
|
||
/** A colour is a hex value or "@name", a reference into the site palette. */
|
||
public const COLOR = '/^(#[0-9a-fA-F]{6}([0-9a-fA-F]{2})?|@[a-z][a-z0-9-]{0,31})$/D';
|
||
/** A length is a number with a unit, plus the two keywords that behave like one. */
|
||
/**
|
||
* A length is a number with a unit, plus the two keywords that behave like one.
|
||
*
|
||
* dvh/svh/lvh are in the list because 100vh is wrong on a phone: it means the viewport
|
||
* without the address bar, so a "full height" section is taller than the screen and the
|
||
* bottom edge sits under the browser chrome. 100dvh is what people mean.
|
||
*/
|
||
public const LENGTH = '/^(0|-?\d{1,5}(\.\d{1,3})?(px|rem|em|%|vw|vh|ch|dvh|svh|lvh|dvw))$/D';
|
||
public const LENGTH_AUTO = '/^(auto|none|0|-?\d{1,5}(\.\d{1,3})?(px|rem|em|%|vw|vh|ch|dvh|svh|lvh|dvw))$/D';
|
||
/** Column shares of a row, up to twelve numbers separated by blanks: "2 1" or "66.7 33.3". */
|
||
public const FRACTIONS = '/^\d{1,3}(\.\d)?( \d{1,3}(\.\d)?){0,11}$/D';
|
||
|
||
/**
|
||
* group – where the property appears in the editor
|
||
* type – length | length-auto | color | number | choice | text
|
||
* css – the CSS property it becomes; sides/corners expand to four
|
||
* label – German label for the editor
|
||
* target – 'img' applies the declaration to the images inside the block instead of the block
|
||
* itself. object-fit on a figure does nothing; it belongs on the image.
|
||
* sideCss – how one side is spelled. "padding-top" follows the property name, but a border
|
||
* width is "border-top-width" and not "border-width-top" - written out, because
|
||
* guessing it wrong produces a declaration the browser silently drops.
|
||
*
|
||
* @return array<string, array{group: string, type: string, css: string|array, label: string, values?: array<string, string>, min?: float, max?: float, step?: float, sides?: bool, hover?: bool}>
|
||
*/
|
||
public static function properties(): array
|
||
{
|
||
return [
|
||
// ---- Typografie -------------------------------------------------------------
|
||
'fontFamily' => ['group' => 'text', 'type' => 'font', 'css' => 'font-family', 'label' => 'Schriftart'],
|
||
'fontSize' => ['group' => 'text', 'type' => 'length', 'css' => 'font-size', 'label' => 'Schriftgröße'],
|
||
'fontWeight' => ['group' => 'text', 'type' => 'choice', 'css' => 'font-weight', 'label' => 'Schriftstärke', 'values' => [
|
||
'100' => '100 · Thin', '200' => '200 · Extra Light', '300' => '300 · Light', '400' => '400 · Regular',
|
||
'500' => '500 · Medium', '600' => '600 · Semibold', '700' => '700 · Bold', '800' => '800 · Extra Bold', '900' => '900 · Black',
|
||
]],
|
||
'fontStyle' => ['group' => 'text', 'type' => 'choice', 'css' => 'font-style', 'label' => 'Schriftlage', 'values' => ['normal' => 'Normal', 'italic' => 'Kursiv']],
|
||
'lineHeight' => ['group' => 'text', 'type' => 'number', 'css' => 'line-height', 'label' => 'Zeilenhöhe', 'min' => 0.7, 'max' => 4, 'step' => 0.05],
|
||
'letterSpacing' => ['group' => 'text', 'type' => 'length', 'css' => 'letter-spacing', 'label' => 'Buchstabenabstand'],
|
||
'wordSpacing' => ['group' => 'text', 'type' => 'length', 'css' => 'word-spacing', 'label' => 'Wortabstand'],
|
||
'textAlign' => ['group' => 'text', 'type' => 'choice', 'css' => 'text-align', 'label' => 'Ausrichtung', 'values' => [
|
||
'left' => 'Links', 'center' => 'Mittig', 'right' => 'Rechts', 'justify' => 'Blocksatz',
|
||
]],
|
||
'textTransform' => ['group' => 'text', 'type' => 'choice', 'css' => 'text-transform', 'label' => 'Schreibweise', 'values' => [
|
||
'none' => 'Wie eingegeben', 'uppercase' => 'GROSSBUCHSTABEN', 'lowercase' => 'kleinbuchstaben', 'capitalize' => 'Erster Buchstabe Groß',
|
||
]],
|
||
'textDecoration' => ['group' => 'text', 'type' => 'choice', 'css' => 'text-decoration-line', 'label' => 'Textlinie', 'values' => [
|
||
'none' => 'Keine', 'underline' => 'Unterstrichen', 'line-through' => 'Durchgestrichen',
|
||
]],
|
||
'color' => ['group' => 'text', 'type' => 'color', 'css' => 'color', 'label' => 'Textfarbe', 'hover' => true],
|
||
|
||
// ---- Hintergrund ------------------------------------------------------------
|
||
'backgroundColor' => ['group' => 'background', 'type' => 'color', 'css' => 'background-color', 'label' => 'Hintergrundfarbe', 'hover' => true],
|
||
'gradientFrom' => ['group' => 'background', 'type' => 'color', 'css' => null, 'label' => 'Verlauf von'],
|
||
'gradientTo' => ['group' => 'background', 'type' => 'color', 'css' => null, 'label' => 'Verlauf bis'],
|
||
'gradientAngle' => ['group' => 'background', 'type' => 'number', 'css' => null, 'label' => 'Verlaufswinkel', 'min' => 0, 'max' => 360, 'step' => 1],
|
||
'backgroundImage' => ['group' => 'background', 'type' => 'media', 'css' => null, 'label' => 'Hintergrundbild'],
|
||
'backgroundSize' => ['group' => 'background', 'type' => 'choice', 'css' => 'background-size', 'label' => 'Bildgröße', 'values' => [
|
||
'cover' => 'Füllend', 'contain' => 'Vollständig sichtbar', 'auto' => 'Originalgröße',
|
||
]],
|
||
'backgroundPosition' => ['group' => 'background', 'type' => 'choice', 'css' => 'background-position', 'label' => 'Bildausschnitt', 'values' => [
|
||
'center' => 'Mittig', 'top' => 'Oben', 'bottom' => 'Unten', 'left' => 'Links', 'right' => 'Rechts',
|
||
'top left' => 'Oben links', 'top right' => 'Oben rechts', 'bottom left' => 'Unten links', 'bottom right' => 'Unten rechts',
|
||
]],
|
||
'backgroundRepeat' => ['group' => 'background', 'type' => 'choice', 'css' => 'background-repeat', 'label' => 'Wiederholung', 'values' => [
|
||
'no-repeat' => 'Nicht wiederholen', 'repeat' => 'Kacheln', 'repeat-x' => 'Waagerecht kacheln', 'repeat-y' => 'Senkrecht kacheln',
|
||
]],
|
||
'backgroundAttachment' => ['group' => 'background', 'type' => 'choice', 'css' => 'background-attachment', 'label' => 'Scrollverhalten', 'values' => [
|
||
'scroll' => 'Mitscrollen', 'fixed' => 'Feststehend (Parallax)',
|
||
]],
|
||
'overlayColor' => ['group' => 'background', 'type' => 'color', 'css' => null, 'label' => 'Überlagerung'],
|
||
'overlayOpacity' => ['group' => 'background', 'type' => 'number', 'css' => null, 'label' => 'Deckkraft der Überlagerung', 'min' => 0, 'max' => 1, 'step' => 0.05],
|
||
|
||
// ---- Abstand ----------------------------------------------------------------
|
||
'padding' => ['group' => 'spacing', 'type' => 'length', 'css' => 'padding', 'label' => 'Innenabstand', 'sides' => true, 'sideCss' => 'padding-%s'],
|
||
'margin' => ['group' => 'spacing', 'type' => 'length-auto', 'css' => 'margin', 'label' => 'Außenabstand', 'sides' => true, 'sideCss' => 'margin-%s'],
|
||
|
||
// ---- Größe ------------------------------------------------------------------
|
||
'width' => ['group' => 'size', 'type' => 'length-auto', 'css' => 'width', 'label' => 'Breite'],
|
||
'maxWidth' => ['group' => 'size', 'type' => 'length-auto', 'css' => 'max-width', 'label' => 'Maximale Breite'],
|
||
'minWidth' => ['group' => 'size', 'type' => 'length-auto', 'css' => 'min-width', 'label' => 'Mindestbreite'],
|
||
'height' => ['group' => 'size', 'type' => 'length-auto', 'css' => 'height', 'label' => 'Höhe'],
|
||
'minHeight' => ['group' => 'size', 'type' => 'length-auto', 'css' => 'min-height', 'label' => 'Mindesthöhe'],
|
||
'maxHeight' => ['group' => 'size', 'type' => 'length-auto', 'css' => 'max-height', 'label' => 'Maximale Höhe'],
|
||
'overflow' => ['group' => 'size', 'type' => 'choice', 'css' => 'overflow', 'label' => 'Überlauf', 'values' => [
|
||
'visible' => 'Sichtbar', 'hidden' => 'Abschneiden', 'auto' => 'Scrollen bei Bedarf',
|
||
]],
|
||
|
||
// ---- Rahmen -----------------------------------------------------------------
|
||
'borderWidth' => ['group' => 'border', 'type' => 'length', 'css' => 'border-width', 'label' => 'Rahmenstärke', 'sides' => true, 'sideCss' => 'border-%s-width'],
|
||
'borderStyle' => ['group' => 'border', 'type' => 'choice', 'css' => 'border-style', 'label' => 'Rahmenart', 'values' => [
|
||
'solid' => 'Durchgezogen', 'dashed' => 'Gestrichelt', 'dotted' => 'Gepunktet', 'double' => 'Doppelt', 'none' => 'Keiner',
|
||
]],
|
||
'borderColor' => ['group' => 'border', 'type' => 'color', 'css' => 'border-color', 'label' => 'Rahmenfarbe', 'hover' => true],
|
||
'borderRadius' => ['group' => 'border', 'type' => 'length', 'css' => 'border-radius', 'label' => 'Ecken', 'corners' => true],
|
||
'outlineWidth' => ['group' => 'border', 'type' => 'length', 'css' => 'outline-width', 'label' => 'Zusatzrahmen Stärke'],
|
||
'outlineColor' => ['group' => 'border', 'type' => 'color', 'css' => 'outline-color', 'label' => 'Zusatzrahmen Farbe', 'hover' => true],
|
||
'outlineOffset' => ['group' => 'border', 'type' => 'length', 'css' => 'outline-offset', 'label' => 'Zusatzrahmen Abstand'],
|
||
'outlineStyle' => ['group' => 'border', 'type' => 'choice', 'css' => 'outline-style', 'label' => 'Zusatzrahmen Art', 'values' => [
|
||
'solid' => 'Durchgezogen', 'dashed' => 'Gestrichelt', 'dotted' => 'Gepunktet', 'double' => 'Doppelt', 'none' => 'Keiner',
|
||
]],
|
||
|
||
// ---- Bild: wirkt auf die Bilder im Block, nicht auf den Block ----------------
|
||
'objectFit' => ['group' => 'image', 'type' => 'choice', 'css' => 'object-fit', 'label' => 'Bildausschnitt', 'target' => 'img', 'values' => [
|
||
'cover' => 'Füllen und beschneiden', 'contain' => 'Vollständig zeigen', 'fill' => 'Verzerren', 'none' => 'Originalgröße', 'scale-down' => 'Nur verkleinern',
|
||
]],
|
||
'objectPosition' => ['group' => 'image', 'type' => 'choice', 'css' => 'object-position', 'label' => 'Bildmitte', 'target' => 'img', 'values' => [
|
||
'center' => 'Mitte', 'top' => 'Oben', 'bottom' => 'Unten', 'left' => 'Links', 'right' => 'Rechts',
|
||
'top left' => 'Oben links', 'top right' => 'Oben rechts', 'bottom left' => 'Unten links', 'bottom right' => 'Unten rechts',
|
||
]],
|
||
'imageRatio' => ['group' => 'image', 'type' => 'choice', 'css' => 'aspect-ratio', 'label' => 'Seitenverhältnis', 'target' => 'img', 'values' => [
|
||
'auto' => 'Wie das Bild', '1/1' => 'Quadrat 1:1', '4/3' => 'Klassisch 4:3', '3/2' => 'Foto 3:2', '16/9' => 'Breitbild 16:9',
|
||
'21/9' => 'Panorama 21:9', '3/4' => 'Hochkant 3:4', '2/3' => 'Hochkant 2:3', '9/16' => 'Hochkant 9:16',
|
||
]],
|
||
'imageHeight' => ['group' => 'image', 'type' => 'length-auto', 'css' => 'height', 'label' => 'Bildhöhe', 'target' => 'img'],
|
||
'imageRadius' => ['group' => 'image', 'type' => 'length', 'css' => 'border-radius', 'label' => 'Bildecken', 'target' => 'img', 'corners' => true],
|
||
'imageBorderWidth' => ['group' => 'image', 'type' => 'length', 'css' => 'border-width', 'label' => 'Bildrahmen Stärke', 'target' => 'img', 'sides' => true, 'sideCss' => 'border-%s-width'],
|
||
'imageBorderColor' => ['group' => 'image', 'type' => 'color', 'css' => 'border-color', 'label' => 'Bildrahmen Farbe', 'target' => 'img'],
|
||
'imageBorderStyle' => ['group' => 'image', 'type' => 'choice', 'css' => 'border-style', 'label' => 'Bildrahmen Art', 'target' => 'img', 'values' => [
|
||
'solid' => 'Durchgezogen', 'dashed' => 'Gestrichelt', 'dotted' => 'Gepunktet', 'double' => 'Doppelt', 'none' => 'Keiner',
|
||
]],
|
||
|
||
// ---- Layout -----------------------------------------------------------------
|
||
'display' => ['group' => 'layout', 'type' => 'choice', 'css' => 'display', 'label' => 'Anzeige', 'values' => [
|
||
'block' => 'Block', 'flex' => 'Flex', 'inline-flex' => 'Inline-Flex', 'grid' => 'Raster', 'inline-block' => 'Inline-Block', 'none' => 'Ausgeblendet',
|
||
]],
|
||
'flexDirection' => ['group' => 'layout', 'type' => 'choice', 'css' => 'flex-direction', 'label' => 'Flexrichtung', 'values' => [
|
||
'row' => 'Nebeneinander', 'row-reverse' => 'Nebeneinander, umgekehrt', 'column' => 'Untereinander', 'column-reverse' => 'Untereinander, umgekehrt',
|
||
]],
|
||
'flexWrap' => ['group' => 'layout', 'type' => 'choice', 'css' => 'flex-wrap', 'label' => 'Umbruch', 'values' => ['wrap' => 'Umbrechen', 'nowrap' => 'Nicht umbrechen']],
|
||
'justifyContent' => ['group' => 'layout', 'type' => 'choice', 'css' => 'justify-content', 'label' => 'Verteilung', 'values' => [
|
||
'flex-start' => 'Anfang', 'center' => 'Mitte', 'flex-end' => 'Ende', 'space-between' => 'Außen verteilt', 'space-around' => 'Gleichmäßig', 'space-evenly' => 'Exakt gleich',
|
||
]],
|
||
'alignItems' => ['group' => 'layout', 'type' => 'choice', 'css' => 'align-items', 'label' => 'Ausrichtung quer', 'values' => [
|
||
'stretch' => 'Dehnen', 'flex-start' => 'Anfang', 'center' => 'Mitte', 'flex-end' => 'Ende', 'baseline' => 'Grundlinie',
|
||
]],
|
||
'gridColumns' => ['group' => 'layout', 'type' => 'number', 'css' => null, 'label' => 'Spalten', 'min' => 1, 'max' => 12, 'step' => 1],
|
||
// Die Anteile der Spalten einer Zeile: "2 1" ist zwei Drittel zu einem Drittel. Gesetzt wird
|
||
// das vor allem durch Ziehen der Spaltengrenze in der Vorschau des Editors.
|
||
'columnWidths' => ['group' => 'layout', 'type' => 'fractions', 'css' => null, 'label' => 'Spaltenbreiten'],
|
||
'gap' => ['group' => 'layout', 'type' => 'length', 'css' => 'gap', 'label' => 'Abstand zwischen Elementen'],
|
||
'order' => ['group' => 'layout', 'type' => 'number', 'css' => 'order', 'label' => 'Reihenfolge', 'min' => -20, 'max' => 20, 'step' => 1],
|
||
'rowGap' => ['group' => 'layout', 'type' => 'length', 'css' => 'row-gap', 'label' => 'Abstand zwischen Zeilen'],
|
||
'columnGap' => ['group' => 'layout', 'type' => 'length', 'css' => 'column-gap', 'label' => 'Abstand zwischen Spalten'],
|
||
'alignSelf' => ['group' => 'layout', 'type' => 'choice', 'css' => 'align-self', 'label' => 'Eigene Ausrichtung quer', 'values' => [
|
||
'auto' => 'Wie die Gruppe', 'flex-start' => 'Anfang', 'center' => 'Mitte', 'flex-end' => 'Ende', 'stretch' => 'Strecken', 'baseline' => 'Grundlinie',
|
||
]],
|
||
'flexGrow' => ['group' => 'layout', 'type' => 'number', 'css' => 'flex-grow', 'label' => 'Wachsen', 'min' => 0, 'max' => 12, 'step' => 1],
|
||
'flexShrink' => ['group' => 'layout', 'type' => 'number', 'css' => 'flex-shrink', 'label' => 'Schrumpfen', 'min' => 0, 'max' => 12, 'step' => 1],
|
||
'flexBasis' => ['group' => 'layout', 'type' => 'length-auto', 'css' => 'flex-basis', 'label' => 'Grundbreite'],
|
||
'position' => ['group' => 'layout', 'type' => 'choice', 'css' => 'position', 'label' => 'Positionierung', 'values' => [
|
||
'static' => 'Im Fluss', 'relative' => 'Relativ verschoben', 'absolute' => 'Freigestellt', 'sticky' => 'Klebt beim Scrollen',
|
||
]],
|
||
'top' => ['group' => 'layout', 'type' => 'length-auto', 'css' => 'top', 'label' => 'Abstand von oben'],
|
||
'right' => ['group' => 'layout', 'type' => 'length-auto', 'css' => 'right', 'label' => 'Abstand von rechts'],
|
||
'bottom' => ['group' => 'layout', 'type' => 'length-auto', 'css' => 'bottom', 'label' => 'Abstand von unten'],
|
||
'left' => ['group' => 'layout', 'type' => 'length-auto', 'css' => 'left', 'label' => 'Abstand von links'],
|
||
'zIndex' => ['group' => 'layout', 'type' => 'number', 'css' => 'z-index', 'label' => 'Ebene (vorn/hinten)', 'min' => -10, 'max' => 100, 'step' => 1],
|
||
|
||
// ---- Wirkung ----------------------------------------------------------------
|
||
'opacity' => ['group' => 'effect', 'type' => 'number', 'css' => 'opacity', 'label' => 'Deckkraft', 'min' => 0, 'max' => 1, 'step' => 0.05, 'hover' => true],
|
||
'shadowX' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Schatten waagerecht'],
|
||
'shadowY' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Schatten senkrecht'],
|
||
'shadowBlur' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Schatten weich'],
|
||
'shadowSpread' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Schatten größer'],
|
||
'shadowColor' => ['group' => 'effect', 'type' => 'color', 'css' => null, 'label' => 'Schattenfarbe', 'hover' => true],
|
||
'translateX' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Verschieben waagerecht', 'hover' => true],
|
||
'translateY' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Verschieben senkrecht', 'hover' => true],
|
||
'scale' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Skalierung', 'min' => 0.1, 'max' => 3, 'step' => 0.01, 'hover' => true],
|
||
'rotate' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Drehung', 'min' => -360, 'max' => 360, 'step' => 1, 'hover' => true],
|
||
'shadowInset' => ['group' => 'effect', 'type' => 'choice', 'css' => null, 'label' => 'Schatten innen', 'values' => ['aussen' => 'Außen', 'innen' => 'Innen']],
|
||
'textShadowX' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Textschatten waagerecht'],
|
||
'textShadowY' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Textschatten senkrecht'],
|
||
'textShadowBlur' => ['group' => 'effect', 'type' => 'length', 'css' => null, 'label' => 'Textschatten weich'],
|
||
'textShadowColor' => ['group' => 'effect', 'type' => 'color', 'css' => null, 'label' => 'Textschattenfarbe'],
|
||
'blur' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Weichzeichnen (px)', 'min' => 0, 'max' => 40, 'step' => 0.5, 'hover' => true],
|
||
'brightness' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Helligkeit', 'min' => 0, 'max' => 3, 'step' => 0.05, 'hover' => true],
|
||
'contrast' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Kontrast', 'min' => 0, 'max' => 3, 'step' => 0.05, 'hover' => true],
|
||
'saturate' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Farbsättigung', 'min' => 0, 'max' => 3, 'step' => 0.05, 'hover' => true],
|
||
'grayscale' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Graustufen', 'min' => 0, 'max' => 1, 'step' => 0.05, 'hover' => true],
|
||
'sepia' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Sepia', 'min' => 0, 'max' => 1, 'step' => 0.05, 'hover' => true],
|
||
'hueRotate' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Farbdrehung (Grad)', 'min' => -180, 'max' => 180, 'step' => 1, 'hover' => true],
|
||
'invert' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Umkehren', 'min' => 0, 'max' => 1, 'step' => 0.05],
|
||
'mixBlendMode' => ['group' => 'effect', 'type' => 'choice', 'css' => 'mix-blend-mode', 'label' => 'Mischmodus', 'values' => [
|
||
'normal' => 'Normal', 'multiply' => 'Multiplizieren', 'screen' => 'Negativ multiplizieren', 'overlay' => 'Überlagern',
|
||
'darken' => 'Abdunkeln', 'lighten' => 'Aufhellen', 'color-dodge' => 'Farbig abwedeln', 'difference' => 'Differenz',
|
||
'exclusion' => 'Ausschluss', 'hue' => 'Farbton', 'saturation' => 'Sättigung', 'color' => 'Farbe', 'luminosity' => 'Leuchtkraft',
|
||
]],
|
||
'cursor' => ['group' => 'effect', 'type' => 'choice', 'css' => 'cursor', 'label' => 'Mauszeiger', 'values' => [
|
||
'auto' => 'Automatisch', 'pointer' => 'Hand', 'default' => 'Pfeil', 'not-allowed' => 'Gesperrt', 'grab' => 'Greifen', 'zoom-in' => 'Lupe',
|
||
]],
|
||
'transition' => ['group' => 'effect', 'type' => 'number', 'css' => null, 'label' => 'Übergangsdauer (ms)', 'min' => 0, 'max' => 2000, 'step' => 25],
|
||
// The slider controls are built by the script next to the slider, so their colours
|
||
// travel as custom properties onto the slider and its controls (target "controls").
|
||
// Der Menü-Knopf ist ein eigener Knopf im Menüblock, kein eigener Baustein: seine
|
||
// Farben liegen deshalb auf dem Block und wirken auf das summary-Element darin.
|
||
'burgerColor' => ['group' => 'menu', 'type' => 'color', 'css' => 'color', 'label' => 'Menü-Knopf: Schrift und Striche', 'target' => 'burger'],
|
||
'burgerBackgroundColor' => ['group' => 'menu', 'type' => 'color', 'css' => 'background-color', 'label' => 'Menü-Knopf: Fläche', 'target' => 'burger'],
|
||
'burgerBorderColor' => ['group' => 'menu', 'type' => 'color', 'css' => 'border-color', 'label' => 'Menü-Knopf: Rahmen', 'target' => 'burger'],
|
||
'burgerBorderWidth' => ['group' => 'menu', 'type' => 'length', 'css' => 'border-width', 'label' => 'Menü-Knopf: Rahmenstärke', 'target' => 'burger'],
|
||
'burgerBorderRadius' => ['group' => 'menu', 'type' => 'length', 'css' => 'border-radius', 'label' => 'Menü-Knopf: Ecken', 'target' => 'burger'],
|
||
'burgerFontSize' => ['group' => 'menu', 'type' => 'length', 'css' => 'font-size', 'label' => 'Menü-Knopf: Schriftgröße', 'target' => 'burger'],
|
||
// Das Symbol im Button (ADR 109). Die Werte liegen am Button und wirken auf seine
|
||
// Symbolhülle, je Gerät und mit eigenem Wert beim Überfahren. Ohne Angabe folgt das
|
||
// Symbol der Schriftfarbe und hat 1,15 em Größe wie bisher.
|
||
'iconColor' => ['group' => 'icon', 'type' => 'color', 'css' => 'color', 'label' => 'Symbolfarbe', 'target' => 'icon', 'hover' => true],
|
||
'iconBackgroundColor' => ['group' => 'icon', 'type' => 'color', 'css' => 'background-color', 'label' => 'Fläche hinter dem Symbol', 'target' => 'icon', 'hover' => true],
|
||
'iconSize' => ['group' => 'icon', 'type' => 'length', 'css' => '--icon-size', 'label' => 'Symbolgröße', 'target' => 'icon'],
|
||
'iconPadding' => ['group' => 'icon', 'type' => 'length', 'css' => 'padding', 'label' => 'Innenabstand der Fläche', 'target' => 'icon'],
|
||
'iconRadius' => ['group' => 'icon', 'type' => 'length', 'css' => 'border-radius', 'label' => 'Ecken der Fläche', 'target' => 'icon'],
|
||
'iconRotate' => ['group' => 'icon', 'type' => 'choice', 'css' => 'rotate', 'label' => 'Drehung', 'target' => 'icon', 'hover' => true, 'values' => [
|
||
'0deg' => 'Keine', '45deg' => '45° im Uhrzeigersinn', '90deg' => '90° im Uhrzeigersinn', '180deg' => '180°', '-45deg' => '45° gegen den Uhrzeigersinn', '-90deg' => '90° gegen den Uhrzeigersinn',
|
||
]],
|
||
// Am Button selbst: der Abstand gilt auch, wenn das Symbol erst beim Überfahren erscheint.
|
||
'iconGap' => ['group' => 'icon', 'type' => 'length', 'css' => '--icon-gap', 'label' => 'Abstand zum Text'],
|
||
'sliderDotColor' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-dot', 'label' => 'Punkte', 'target' => 'controls'],
|
||
'sliderDotActiveColor' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-dot-active', 'label' => 'Aktiver Punkt', 'target' => 'controls'],
|
||
'sliderControlColor' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-ink', 'label' => 'Pfeile und Zähler', 'target' => 'controls'],
|
||
'sliderControlBackground' => ['group' => 'slider', 'type' => 'color', 'css' => '--slider-bg', 'label' => 'Hintergrund der Knöpfe', 'target' => 'controls'],
|
||
];
|
||
}
|
||
|
||
/** Properties that are written onto the images inside a block instead of the block itself. */
|
||
public static function imageTargets(): array
|
||
{
|
||
return array_keys(array_filter(self::properties(), static fn (array $p): bool => 'img' === ($p['target'] ?? null)));
|
||
}
|
||
|
||
/** Properties that may also be given a hover value. */
|
||
public static function hoverable(): array
|
||
{
|
||
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'];
|
||
}
|
||
|
||
/** Corners round a box; they are not sides, and their CSS property is spelled differently. */
|
||
public static function corners(): array
|
||
{
|
||
return ['topLeft' => 'Oben links', 'topRight' => 'Oben rechts', 'bottomRight' => 'Unten rechts', 'bottomLeft' => 'Unten links'];
|
||
}
|
||
|
||
/** "topLeft" becomes the CSS fragment "top-left". */
|
||
public static function cornerCss(string $corner): string
|
||
{
|
||
return strtolower(preg_replace('/([A-Z])/', '-$1', $corner) ?? $corner);
|
||
}
|
||
}
|