Make the consent notice styleable and keep the back-to-top button reachable in 1.14.1
CMS quality / php (8.2) (push) Canceled after 0s
CMS quality / php (8.4) (push) Canceled after 0s
CMS quality / browser (push) Canceled after 0s
CMS quality / mysql (push) Canceled after 0s

The back-to-top button was hidden as long as the consent question was open,
on every device - and on a phone the question tends to stay open. Instead
of `display:none` it now anchors itself 12px above the bar wherever the bar
covers its corner: below 800px for the centred card, at every width for the
full-width bar and the corner card. Browsers without anchor positioning keep
hiding it there. The bar renders before the button so it can be an anchor,
and the reserved bottom padding grows to 320px below 620px, where the bar
with its default text is up to 299px tall.

The notice itself gets a face. Settings → Einwilligung now carries layout
(card, bar, corner), shape (soft, round, square - one setting for the box and
its buttons), a heading and four colours, each as #hex or @palette key.
`Theme::cssColor()` resolves both, replacing the copy inside the back-to-top
code; `Consent::styles()` writes the values as `--kant-consent-*` into the
hashed style block. The base layout changes with it: an accent stripe and
shield icon, equal-width buttons, a disclosure with a chevron, underlined
legal links, a two-column bar on wide screens. Declining stays an outlined
button of the same size next to accepting. The archive and the consent page
carry the same variables - the archive had no palette at all until now.

In the editor, rows with an own value are labelled in orange rather than
bold with a dot; the device chips sit 6px apart with the focus ring inside
the gap.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
kantarciandClaude Fable 5.1 committed 2026-09-15 06:57:38 +02:00
1 parent 860bed9199
commit 0bdcc7f90e
18 files changed
+300 -56

No files matched your search

+4 -3
View File
@@ -190,7 +190,7 @@ html:has(>.editor-body){overflow:hidden;height:100%}
.design-row-head label{margin:0;font-size:11px;line-height:1.5;flex:1;min-width:0}
/* Belegte Zeilen tragen dieselbe Farbe wie die belegten Geraetechips - eine Farbe, eine
Bedeutung: hier steht ein eigener Wert und nicht der geerbte. */
.design-own .design-row-head label{color:#6f3c0a;font-weight:700}
.design-own .design-row-head label{color:var(--used);font-weight:700}
.design-own .design-row-head label::before{content:'● ';color:var(--used)}
.design-reset-one{font-size:14px;padding:2px 6px;min-height:24px;line-height:1}
.design-inherited{font-size:10px;color:var(--muted);margin:5px 0 0;line-height:1.45}
@@ -222,13 +222,14 @@ html:has(>.editor-body){overflow:hidden;height:100%}
"Gerade bearbeitet" ist ein Rahmen, und zwar ein Rahmen INNEN: ein `outline` liegt ausserhalb
des Kaestchens, laeuft bei 2 px Abstand unter den Nachbarn und wird von der seitlich
beschnittenen Spalte gekappt - es sah aus wie ein abgeschnittener roter Kasten. */
.device-chips{display:flex;gap:4px;margin-left:auto}
.device-chips{display:flex;flex:none;gap:6px;margin-left:auto}
.device-chip{width:22px;height:22px;display:grid;place-items:center;padding:0;min-height:0;border:1px solid var(--line);border-radius:5px;background:#fff;color:var(--muted);font:700 10px/1 system-ui,sans-serif;cursor:pointer}
.device-chip:hover{border-color:var(--ink)}
.device-chip-own{background:var(--used);border-color:var(--used);color:#fff}
.device-chip[aria-pressed=true]{border-width:2px;border-color:var(--ink)}
.device-chip-own[aria-pressed=true]{border-color:var(--ink);box-shadow:inset 0 0 0 1px #fff}
.device-chip:focus-visible{outline-width:2px;outline-offset:1px}
/* Der Fokusring bleibt innerhalb der Luecke zwischen zwei Chips - nichts liegt uebereinander. */
.device-chip:focus-visible{outline-width:2px;outline-offset:0}
.design-side-head{margin-bottom:5px}
.design-side-head .design-group-label{margin:0}
/* A number field that can be dragged sideways. The cursor is the only announcement it needs. */
+51 -18
View File
@@ -126,31 +126,51 @@ p{hyphens:auto}
/* The consent question. Fixed to the bottom, but it never covers the whole page: the content
stays readable while the decision is open, because a wall would make "accept" the only way
forward. Accepting and refusing are the same button in the same row. */
.consent-bar{position:fixed;inset-inline:12px;bottom:12px;z-index:50;max-width:620px;margin-inline:auto;background:var(--paper);color:var(--ink);border:1px solid var(--line);border-radius:14px;padding:20px 22px;box-shadow:0 18px 50px color-mix(in srgb,var(--ink) 15%,transparent);font-size:14px;line-height:1.6}
.kant .consent-bar h2{font-size:16px;margin:0 0 8px}
.consent-bar p{margin:0 0 14px}
forward. Accepting and refusing are the same button in the same row.
Aussehen aus den Einstellungen: `--kant-consent-*` im Kopf der Seite (Consent::styles), die
Anordnung als Klasse. Ohne Einstellung gilt Papier, Tinte und Akzent des Themes. Ein Wert wie
`--k-consent` aus der Palette bleibt als Zwischenstufe erhalten. */
.consent-bar{position:fixed;inset-inline:16px;bottom:16px;z-index:50;max-width:640px;max-height:calc(100dvh - 24px);overflow:auto;margin-inline:auto;background:var(--kant-consent-bg,var(--paper));color:var(--kant-consent-ink,var(--ink));border:1px solid color-mix(in srgb,currentColor 12%,transparent);border-radius:var(--kant-consent-radius,18px);padding:22px 24px 20px;box-shadow:0 24px 60px color-mix(in srgb,var(--ink) 22%,transparent);font-size:14px;line-height:1.6;overscroll-behavior:contain}
/* Ein schmaler Streifen in der Akzentfarbe oben: die Fläche gehört sichtbar zur Website. */
.consent-bar::before{content:'';position:absolute;inset:0 0 auto 0;height:4px;background:var(--kant-consent-accent,var(--k-consent,var(--accent)))}
.kant .consent-bar h2{display:flex;align-items:center;gap:10px;font-size:17px;line-height:1.3;margin:0 0 8px}
.consent-icon{flex:none;color:var(--kant-consent-accent,var(--k-consent,var(--accent)))}
.consent-bar p{margin:0 0 16px;color:color-mix(in srgb,currentColor 82%,transparent)}
.consent-actions{display:flex;flex-wrap:wrap;gap:10px}
.consent-button{display:inline-flex;align-items:center;justify-content:center;min-height:44px;padding:11px 18px;border-radius:8px;border:1px solid var(--k-consent,var(--accent));background:var(--k-consent,var(--accent));color:#fff;font:inherit;font-weight:600;cursor:pointer}
.consent-actions .consent-button:last-child{background:var(--paper);color:var(--ink);border-color:var(--line)}
.consent-button{display:inline-flex;align-items:center;justify-content:center;flex:1 1 150px;min-height:46px;padding:11px 18px;border-radius:var(--kant-consent-button-radius,10px);border:1.5px solid var(--kant-consent-accent,var(--k-consent,var(--accent)));background:var(--kant-consent-accent,var(--k-consent,var(--accent)));color:var(--kant-consent-accent-ink,#fff);font:inherit;font-weight:600;cursor:pointer;transition:filter .12s,background-color .12s}
.consent-decline,.consent-choice{background:transparent;color:inherit;border-color:color-mix(in srgb,currentColor 28%,transparent)}
.consent-button:hover{filter:brightness(.94)}
.consent-button:focus-visible,.consent-bar a:focus-visible,.consent-bar summary:focus-visible,.consent-bar input:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.consent-decline:hover,.consent-choice:hover{filter:none;background:color-mix(in srgb,currentColor 7%,transparent)}
.consent-button:focus-visible,.consent-bar a:focus-visible,.consent-bar summary:focus-visible,.consent-bar input:focus-visible{outline:3px solid var(--kant-consent-accent,var(--k-consent,var(--accent)));outline-offset:2px}
.consent-details{margin-top:14px}
.consent-details>summary{cursor:pointer;font-size:13px;font-weight:600;padding:4px 0}
.consent-details>summary{cursor:pointer;font-size:13px;font-weight:600;padding:6px 0;list-style:none;display:inline-flex;align-items:center;gap:6px}
.consent-details>summary::-webkit-details-marker{display:none}
.consent-details>summary::after{content:'';width:7px;height:7px;border-right:1.6px solid currentColor;border-bottom:1.6px solid currentColor;transform:translateY(-2px) rotate(45deg);transition:transform .15s}
.consent-details[open]>summary::after{transform:translateY(2px) rotate(-135deg)}
.consent-details>summary:hover{text-decoration:underline}
.consent-groups{list-style:none;margin:10px 0 0;padding:0;display:grid;gap:10px}
.consent-groups label{display:flex;gap:10px;align-items:flex-start;font-size:13px;line-height:1.5}
.consent-groups input{margin-top:3px;width:18px;height:18px;flex-shrink:0}
.consent-choice{margin-top:14px;background:var(--paper);color:var(--ink);border-color:var(--line)}
.consent-links{margin:14px 0 0;font-size:12px}
.consent-links a{color:var(--accent)}
.consent-groups input{margin-top:3px;width:18px;height:18px;flex-shrink:0;accent-color:var(--kant-consent-accent,var(--k-consent,var(--accent)))}
.consent-choice{margin-top:14px;flex:none;width:auto}
.consent-links{margin:14px 0 0;font-size:12px;color:color-mix(in srgb,currentColor 72%,transparent)}
.consent-links a{color:inherit;text-decoration:underline;text-underline-offset:2px}
.consent-links a:hover{color:var(--kant-consent-accent,var(--k-consent,var(--accent)))}
/* Leiste: über die ganze Breite am unteren Rand, auf breiten Bildschirmen Text links und Knöpfe
rechts. Ecke: eine kleine Karte rechts unten, die den Inhalt kaum verdeckt. */
.consent-layout-leiste{inset-inline:0;bottom:0;max-width:none;border-radius:var(--kant-consent-radius,18px) var(--kant-consent-radius,18px) 0 0;border-inline:0;border-bottom:0;padding-inline:max(24px,calc((100% - 1180px) / 2))}
@media (min-width:900px){.consent-layout-leiste{display:grid;grid-template-columns:minmax(0,1fr) auto;gap:8px 40px;align-items:center}.consent-layout-leiste .consent-body p{margin-bottom:0}.consent-layout-leiste .consent-form{width:360px;max-width:100%}}
.consent-layout-ecke{inset-inline:auto 16px;max-width:400px;margin:0}
/* While the question is open the bar is fixed at the bottom, so the page has to end above it -
otherwise it covers the last element, and an embed's own consent button sat right underneath.
Found by the browser test, which could not click it. */
body.consent-open{padding-bottom:260px}
Found by the browser test, which could not click it. Auf schmalen Bildschirmen ist die Leiste
mit Standardtext bis 290 px hoch; 320 px lassen ihr Luft, ohne einen Bildschirm zu verschenken. */
body.consent-open{padding-bottom:320px}
@media (min-width:621px){body.consent-open{padding-bottom:270px}}
@media (min-width:900px){body.consent-open{padding-bottom:200px}}
.consent-page main{max-width:680px;margin-inline:auto;padding:40px 20px}
.consent-page .consent-bar{position:static;inset:auto;box-shadow:none;max-width:none;margin-top:24px}
@media (max-width:620px){.consent-bar{inset-inline:0;bottom:0;border-radius:14px 14px 0 0;padding:16px}.consent-actions .consent-button{flex:1 1 140px}}
.consent-page .consent-bar{position:static;inset:auto;box-shadow:none;max-width:none;max-height:none;margin:24px 0 0;display:block}
@media (max-width:620px){.consent-bar,.consent-layout-ecke{inset-inline:0;bottom:0;max-width:none;border-radius:var(--kant-consent-radius,18px) var(--kant-consent-radius,18px) 0 0;border-inline:0;border-bottom:0;padding:18px 18px 16px}.consent-layout-leiste{padding-inline:18px}}
/* An embedded third party, and what stands in its place until it may load. */
.block-embed iframe{display:block;width:100%;border:0;border-radius:10px;background:color-mix(in srgb,var(--ink) 8%,var(--paper))}
@@ -406,8 +426,21 @@ body.consent-open{padding-bottom:260px}
.scroll-top:hover{filter:brightness(1.08)}
.scroll-top:focus-visible{outline:3px solid var(--accent);outline-offset:3px}
@media (max-width:767px){.scroll-top{bottom:16px;right:16px}.scroll-top-links{left:16px;right:auto}}
/* Solange der Einwilligungsdialog unten steht, würde der Knopf darauf liegen. */
body.consent-open .scroll-top{display:none}
/* Solange die Einwilligungsfrage offen ist, bleibt der Knopf da. Frueher verschwand er ganz -
und auf dem Telefon bleibt die Frage oft einfach stehen, der Knopf kam dort also nie.
Bis 799 px deckt die Leiste die Ecke ab, in der er steht: dort setzt er sich per
Ankerpositionierung auf ihre Oberkante, wie hoch ihr Text auch umbricht. Ab 800 px steht die
Leiste zentriert mit Rand, die Ecke bleibt frei. Ein Browser ohne Ankerpositionierung blendet
ihn in der schmalen Stufe weiter aus - lieber kein Knopf als einer unter der Leiste. Der Anker
muss im Dokument vor dem Knopf stehen; page.html.twig gibt die Leiste deshalb zuerst aus. */
.consent-bar{anchor-name:--kant-consent}
@media (max-width:799px){
@supports (anchor-name:--kant-consent){body.consent-open .scroll-top{position-anchor:--kant-consent;bottom:calc(anchor(top) + 12px)}}
@supports not (anchor-name:--kant-consent){body.consent-open .scroll-top{display:none}}
}
/* Leiste und Ecke decken die Ecke auf jeder Breite ab - dort gilt dasselbe ohne Stufe. */
@supports (anchor-name:--kant-consent){body.consent-open:has(.consent-layout-leiste,.consent-layout-ecke) .scroll-top{position-anchor:--kant-consent;bottom:calc(anchor(top) + 12px)}}
@supports not (anchor-name:--kant-consent){body.consent-open:has(.consent-layout-leiste,.consent-layout-ecke) .scroll-top{display:none}}
/* Sichtbar, sobald der Leser ein Stueck gescrollt hat - gesteuert von scrolled.js ueber
`kant-top-armed` und `kant-top-shown`. Ohne Skript bleibt der Knopf einfach sichtbar. */
html.kant-top-armed .scroll-top{opacity:0;visibility:hidden;transform:translateY(10px);transition:opacity .25s,visibility .25s,transform .25s}