{% set type = block.type %} {% set props = block.props|default({}) %} {% set layout = block.layout|default({}) %} {% set designClasses = '' %} {% for device, settings in layout.design|default({}) %} {% for key, value in settings %}{% set designClasses = designClasses ~ ' d-' ~ device ~ '-' ~ key ~ '-' ~ value %}{% endfor %} {% endfor %} {% for key, value in layout.effects|default({}) %}{% set designClasses = designClasses ~ ' fx-' ~ key ~ '-' ~ value %}{% endfor %} {% if type in ['section', 'row', 'column'] %} {% set tag = type == 'section' ? 'section' : 'div' %} <{{ tag }} id="{{ block.id }}" class="block-{{ type }} tone-{{ layout.tone|default('paper') }} width-{{ layout.width|default('normal') }}{{ designClasses }}"> {% if type == 'section' %}
{% endif %} {% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %} {% if type == 'section' %}
{% endif %} {% elseif type == 'heading' %} {% if richHeadings|default(false) %}{{ props.text|raw }}{% else %}{{ props.text }}{% endif %} {% elseif type == 'text' %} {% if richText|default(false) %} {# Already filtered by RichText when it was stored, so printing it unescaped is safe. A div, not a p: the allowlist includes lists and paragraphs, which may not nest in one. #}
{{ props.text|raw }}
{% else %}

{{ props.text }}

{% endif %} {% elseif type == 'button' %} {{ props.text }} {% elseif type == 'image' %}
{% set eager = eagerImage|default(null) == block.id %}{% if props.href|default('') %}{% endif %}{{ props.alt }}{% if props.href|default('') %}{% endif %}{% if props.caption|default('') %}
{{ props.caption }}
{% endif %}
{% elseif type == 'iconcard' %}
{% include 'blocks/icon.html.twig' with {name: props.icon} only %}

{% if props.href|default('') %}{{ props.title }}{% else %}{{ props.title }}{% endif %}

{% if props.text|default('') %}

{{ props.text }}

{% endif %}
{% elseif type == 'stat' %}

{{ props.value }}{{ props.label }}

{% elseif type == 'quote' %}
{% if props.rating|default(0) > 0 %}

Bewertung: {{ props.rating }} von 5

{% endif %}

{{ props.text }}

{{ props.author }}{% if props.role|default('') %} {{ props.role }}{% endif %}
{% elseif type == 'logos' %} {% elseif type == 'cta' %}

{{ props.title }}

{% if props.text|default('') %}

{{ props.text }}

{% endif %}
{{ props.buttonText }}
{% elseif type == 'contact' %}
{% if props.phone|default('') %}{% include 'blocks/icon.html.twig' with {name: 'phone'} only %}{{ props.phone }}{% endif %} {% if props.email|default('') %}{% include 'blocks/icon.html.twig' with {name: 'mail'} only %}{{ props.email }}{% endif %} {% if props.address|default('') %}{% include 'blocks/icon.html.twig' with {name: 'pin'} only %}{{ props.address }}{% endif %}
{% elseif type == 'divider' %}
{% elseif type == 'spacer' %} {% elseif type == 'menu' %} {% set links = navigation[props.slot]|default([]) %} {% if links is not empty %} {# The layout is a class, and the burger variant is a details element: a menu that needs a script to open would be a menu that does not open on a page without one. #} {% endif %} {% elseif type == 'partialref' %} {# A linked section: its blocks come from the partial, resolved by the controller. One change there reaches every page that links it, which is the difference to a copied section. #} {% set section = sections[props.partial]|default(null) %} {% if section %}
{% for child in section.blocks %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
{% elseif editing|default(false) %}

Die verknüpfte Sektion „{{ props.partial }}“ gibt es nicht mehr. Der Verweis bleibt gespeichert.

{% else %} {% endif %} {% elseif type == 'breadcrumb' %} {% if breadcrumbs|default([])|length > 1 %} {% endif %} {% elseif type == 'form' %}
{% include 'blocks/form.html.twig' with (forms[block.id]|default({form: null, sent: false, errors: {}, values: {}, blockId: block.id, page: {path: ''}, issued: 0, issuedSignature: ''})) only %}
{% elseif type == 'tabs' %} {# Without the script this is an anchor list followed by every panel: complete and readable. tabs.js turns it into a tab widget; it only changes presentation, never content. #}
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
{% elseif type == 'tabitem' %}

{{ props.title }}

{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
{% elseif type == 'accordion' %}
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
{% elseif type == 'accordionitem' %}
{{ props.title }}
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
{% elseif type == 'icon' %} {# A single icon. Decorative ones are hidden from assistive technology; one that means something carries its label as text for screen readers. #} {% set iconBody %}{% include 'blocks/icon.html.twig' with {name: props.icon} only %}{% if not props.decorative %}{{ props.label }}{% endif %}{% endset %} {% if props.href|default('') %} {% else %} {% endif %} {% elseif type == 'progress' %} {# A progress bar as a real meter: the number is in the markup, not only in the width of a coloured div, so it is readable without seeing it. #}

{{ props.label }}{% if props.showValue %} {{ props.value }} %{% endif %}

{% elseif type == 'slider' %} {# A slider without a script: a scroll container with scroll snapping. It works with a finger, a trackpad, a mouse wheel and the keyboard, and it degrades to a scrollable strip on a browser that ignores snapping. No autoplay - a moving carousel is something people ask to stop, not something a page should start on its own. #}
= 3 %} data-autoplay="{{ props.autoplay }}"{% endif %} tabindex="0" role="group" aria-label="Zum Blättern seitwärts scrollen"> {% for child in block.children|default([]) %}
{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}
{% endfor %}
{% elseif type == 'embed' %} {# Content from a third party. Until the group is granted, nothing of it is requested - no iframe, no image, no preconnect. That is the whole point of the consent question: the placeholder is not a nicety, it is what keeps the promise. In the editor it always stays a placeholder: the administration's policy forbids foreign frames, and a preview must not load anything from outside either. #} {% if not editing|default(false) and consentGate.granted(app.request, props.group) %}
{% else %}

{{ props.title }}

{{ props.hint ?: 'Dieser Inhalt wird von einem anderen Anbieter geladen. Dabei erfährt er deine IP-Adresse und welche Seite du ansiehst.' }}

Anbieter: {{ props.provider ?: props.url|split('/')[2] }}

{% if editing|default(false) %}

Wird erst auf der veröffentlichten Seite geladen, und dort erst nach Einwilligung.

{% else %}
{# The groups already granted stay granted; this button adds this one. #} {% for granted in consentGate.state(app.request).groups %}{% if granted.granted %}{% endif %}{% endfor %}
{% endif %}
{% endif %} {% elseif type == 'postlist' %}
{% set feed = postLists[block.id]|default([]) %} {% include 'blocks/post-list.html.twig' with {posts: feed.posts|default(feed), pages: feed.pages|default(1), page: feed.page|default(1), total: feed.total|default(null), layout: block.props.layout|default('karten'), showMeta: block.props.showMeta ?? true, showExcerpt: block.props.showExcerpt ?? true, showTerms: block.props.showTerms ?? true} only %}
{% elseif type == 'postnav' %}
{% include 'blocks/post-nav.html.twig' with {previous: neighbours.previous|default(null), next: neighbours.next|default(null), previousLabel: props.previousLabel|default('Vorheriger Beitrag'), nextLabel: props.nextLabel|default('Nächster Beitrag')} only %}
{% elseif type == 'gallery' %}
{% for child in block.children|default([]) %}{% include 'blocks/block.html.twig' with {block: child, editing: editing|default(false), postLists: postLists|default({}), forms: forms|default({}), breadcrumbs: breadcrumbs|default([]), navigation: navigation|default({}), sections: sections|default({}), richText: richText|default(false), richHeadings: richHeadings|default(false), neighbours: neighbours|default({}), eagerImage: eagerImage|default(null)} only %}{% endfor %}
{% else %} {# A block whose plugin is not available. Its stored content stays in the document; the core renders nothing from its unvalidated props. Visitors see no broken fragment, while the editor preview shows a placeholder so the block can still be found and moved. #} {% if editing|default(false) %}

Baustein „{{ type }}“ ist in dieser Installation nicht verfügbar. Der Inhalt bleibt gespeichert.

{% endif %} {% endif %}