The open burger menu now closes when someone clicks elsewhere, without a
script: an invisible layer that belongs to the menu button covers the
page while the menu is open. Facebook, Instagram and WhatsApp join the
icon set, so menu buttons can carry them. The stroke attributes of every
icon move into the stylesheet, which saves about 4 KB per page.
Hüfner gets a right-aligned menu with the quote button, Facebook and
Instagram as menu buttons.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A site can have further languages (Settings → Languages). A page is built once; each language
adds only its texts, keyed by the stable block IDs, plus its own address under /en/… (ADR 111,
document schema v27, migration 20261009000100 for page_translation).
- Editor: DE | EN switch, a list of all texts with original, state (missing, translated,
original changed) and filter; structure stays with the main language. Undo, drafts,
publishing and revisions cover all languages at once.
- Website: /en and translated slugs, lang, canonical, hreflang with x-default, og:locale,
sitemap; links to translated pages follow the language; a language switch block.
- Header, footer and linked sections translate the same way; menu entries carry labels per
language; fixed template texts come from a small dictionary.
- Forms: labels, help, options, button and success message per language; messages and the
step script speak the language, the submission and the email stay in the main language.
- Posts, archives, category names, consent text and the 404 page follow the language.
- Single-language sites see none of it and never touch the new table.
The forms administration becomes an overview with one row per form and its own edit page;
fields are one line each with details collapsed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The menu editor gets "Als Button darstellen" per entry: shape, appearance and
size from the button design, fill and ink with hover and palette, icon colour,
icon from the dialog, motion, icon-only and where the button sits on the phone
(in the opened menu, beside the burger, or there as an icon only). Colours go
into the page stylesheet under a stable per-button id because the CSP forbids
inline styles. ADR 110.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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>
Schema v25: a button takes an optional icon (any name from the shared set or
"none") and an icon position (start or end). Without a choice it keeps the
diagonal arrow behind the text, still with the button-arrow class, so stored
pages and their own CSS look exactly as before.
Thirteen everyday icons join the set (arrow down, chevron, calendar, message,
send, download, document, play, search, heart, info, plus, person). Button,
icon and icon card choose from a tile picker with a drawing per icon, grouped,
keyboard navigable, instead of a select with English keys. icons.js holds the
paths for the editor; a test keeps them equal to the Twig drawings and the
validator list. ADR 108.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- 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>
PDFs go through the same upload field as images, are checked by their content and kept
unchanged in their own table. Every link field can pick one, and a page that links a PDF
opens it in a dialog with the browser's own viewer; touch devices and browsers without a
viewer get a new tab instead.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Built while setting up the redesign of ebsolar.de as the sixth local
installation (scripts/seed-ebsolar.php, reset-ebsolar.sh, ports 8096/8097).
The rebuild surfaced four things in the core:
- The icon set knows three arrows now: arrow-right, arrow-left,
arrow-up-right. The button block and the post navigation render them as
inline SVG inside the same aria-hidden span as before, so a template that
hides the arrow with "& span{display:none}" keeps working. Until now the
arrow was a text glyph that looked different in every font. Umut: "bei
alle Projekte bzw bei allen möchte ich dieses emoji icon weg haben".
- Own CSS with the same nested selector twice kept only the last rule.
"& .post-card{border:0}" followed by "& .post-card{transform:none}" lost
the border - silently, which brought the core's card frame back on the
project grid. The parser now joins the declarations in source order, as
CSS does; the same for "&{…}" next to the element's own properties.
- The eager image over the fold was not preloaded. The background image of
the first section has had a preload link for a while; a plain <img> was
found only after the stylesheet was parsed, and that round trip went
straight into LCP. PageController::heroImage() now writes the same src,
srcset and sizes the block template writes, and page.html.twig emits the
link - unless a background image is already preloaded.
- Measured on the way: an entrance effect on the LCP element costs about
900 ms of render delay. The EB Solar hero image therefore has none.
Tests: 581 green, the icon test accepts quoted keys, two cases for the CSS
cascade, the preload asserted in the big-picture test.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Umut, after 1.21.3: "fix hat nichts gebracht" - and the fix could not have,
for two reasons. kant-it.de still runs 1.20.1, so nothing since then was ever
live there. And the diagnosis behind 1.21.3 rested on a test that proved
nothing: Safari's folded address bar shows only the domain, never the
fragment, so "nothing in the address bar" said nothing about whether the tap
had arrived.
Reproduced this time in the real engine: Playwright's WebKit with the iPhone 14
profile. The tap arrives, #seitenanfang lands in the URL, and scrollY stays at
1500. Setting location.hash directly does the same. In the desktop profile as
well, so this is Safari on the Mac too. Chromium jumps to 0.
Narrowed down with fourteen variants of the target: WebKit does not scroll to a
fragment target whose box is inline and has no area. The same empty span with
display:block jumps; an empty div with no height jumps; #top, #main and body
jump. It is the inline box, not the size. The section anchors are not affected
- position:absolute already blockifies them and they jump in WebKit.
So the target gets display:block, one rule in site.css. The template stays as
it is.
And the lift from 1.21.3 comes out again: --kant-browser-chrome and the six
places that added it to bottom or padding. It moved the button and the consent
bar up by the folded toolbar's height on every iPhone, for a cause that turned
out not to be the cause. On the desktop it was zero anyway.
WebKit iPhone 14 after the change: tap 0, click 0, hash 0. Chromium unchanged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Umut: "Beim Smarphone/iphone geht der button scroll to top nicht. hier gehts."
The button was there and did nothing, on every page of every installation, and
tapping it did not even put #seitenanfang in the address bar - so the tap never
reached the page.
Safari on the iPhone folds its address bar into a narrow strip while reading
and lays that over the content. A fixed element measures its "bottom" against
the full viewport, not against the part that is left: the button stood in the
strip the browser had taken for itself. Drawn, visible, and every tap landed on
the bar. Measured at 390x844 the button occupies y 780 to 828 - the bottom 64
pixels, which is where that strip sits.
The difference between the large viewport (bar folded) and the small one (bar
out) is exactly that height, so --kant-browser-chrome carries it and the pieces
that stick to the bottom add it. Where there is no such bar it is zero and
nothing moves. The @supports around it is not caution but necessity: a browser
that does not know lvh/svh would find the calculation invalid at computed-value
time, and bottom would fall back to auto - the button would then stand
anywhere.
The consent bar has the same geometry, so "Annehmen" and "Ablehnen" were just
as hard to hit. It is lifted the same way, except where it is glued to the edge
- raising it there would leave a gap with the page showing through, so its
bottom padding grows instead and the buttons move up with it.
Verified with a test page that sets the height to 50px, the only way to see the
effect without an iPhone: the button moves up by exactly 50px, the glued bar
keeps its edge and its padding goes from 16px to 66px. On the real page in
Chromium bottom stays 16px and the jump still goes 1500 to 0.
Not verified on a real iPhone - that needs the device.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut on Elektro Bernhard: "Menü im Mobil Dropdown textfarben sind weiß also
weiß auf weiß und Hero banner texte sind ohne abstand rechts links im mobil".
Both are core faults, not content - every site with a floating header and a
flex section had them.
The opened burger panel paints its own white surface but took its text colour
from the header. A header that hovers over a dark photo writes white, so the
menu was white on white until the page was scrolled and the header turned
light. The design area has settings for the burger button - text, surface,
border, corners, size - but none for the panel, so there was no way to fix
this from the editor. The panel now sets its own colour alongside its own
background.
The gutter was lost to "flex:1 1 auto" on .section-inner, added in 67b8575 so
a full height section could grow. Growing works along the main axis, and that
defaults to the row: the inner box grew sideways and pushed the container
gutter out. On the desktop the theme's maximum width stopped it before anyone
noticed; on a phone there is none, and the text sat hard against the edge
while the header and every other section kept their 18px. It is "flex:0 1
auto" now. The height never needed it - across the main axis a flex item
stretches anyway, and where "align-items: center" is set, the middle is what
was asked for.
Measured at 375px: hero content was x 0 / 375 wide, now x 18 / 339, the same
as the header. At 1440px nothing changes (x 93 / 1240). Checked against the
full screen hero template as well - edge to edge, 100dvh, content centred -
which renders as before.
579 PHP tests / 4798 assertions on 8.4 and 8.2, 35 JavaScript model tests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut asked for a block that takes custom HTML, and for an iframe on a new
DEEG offers page. Only one of those was missing.
New core block "Eigenes HTML" (document schema v24): one property, the
markup, up to 20.000 characters, written to the page unchanged. Changing it
is reserved for administrators and compared against what is stored - on
saving, on saving an area, and on restoring an older revision - exactly like
an own script. <script> and <iframe> are refused with the way to the right
place instead of being swallowed by the content security policy: scripts
belong in the script field, frames in the "Externer Inhalt" block, because
only that one asks for consent and puts the host into frame-src (ADR 106).
DEEG: /angebote existed as a draft with placeholder text. It is now built
like the legal pages - small caps line, large light heading, lead on the
tinted band - with the IS24 result list below it and a line pointing to the
portal and to the contact form. Navigation and the offers band on the home
page point there. The consent question is switched on, so the frame loads
only after the visitor agrees, and both the banner and the placeholder carry
the site's own shapes and colours.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Forms gained steps, conditions, image choices, number fields with units and a
summary step; the DEEG price finder uses all of it. Plugins are manifest plus
Twig: blocks, settings, containers, routes, presets and theme packs, switched
per installation. The review backlog is done: archive pages render through the
normal page pipeline, forms and post lists work inside areas and sections,
repeated sections get their own ids, breakpoints and colours come from the
palette, backup and update work on MySQL and lock each other out.
The task worker no longer shortens a process that has no time limit:
set_time_limit() sets the limit instead of extending it, which turned an
unlimited CLI run into eighty seconds and killed the full test suite far away
from the scheduler.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A column may now live directly in a section, not only in a row - the way
to centre or narrow a button or a short text without a one-column row.
The align design token applies to buttons; because a button is
inline-flex, auto margins never centred it, so site.css turns an aligned
button into a fit-content block per device. The Drews seed uses the
token for its centred buttons.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Ihr Malerservice Drews (Gernsbach) as its own installation: seed script with
theme, header, footer, eight pages, 31 posts in two categories, contact form,
consent-gated map and videos, redirects; the wording lives in a generated
content file. Reset script, launch entry on port 8093, Cormorant Garamond
served locally with its OFL text.
Two CMS fixes found while building: wide and full sections lost against the
theme's .kant .shell max-width, and the scroll-top link sat outside every
landmark (axe "region"). Lighthouse on six pages: accessibility, best
practices, SEO and agentic browsing 100, performance 98-100; axe zero
violations on nine pages at 1440 and 390 px.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Forms are an editor now: one card per field with add and remove, the
technical name derived from the label, options only for a choice, and a
new "Aussehen" section with a button label and four colours (field border
and background, button and button text) that travel to the page as one
rule per form inside the hashed style block. A live preview draws the
form with the site's default button and names the text contrast.
Statistics open with six figures (views against the same span before,
average per day, busiest day, pages, referred views, missing addresses),
a chart with grid lines and a title per bar, a weekday chart and ranked
lists with share bars. The design screen shows the palette as colour
cards with seven ready-made palettes, picks the base colours from the
palette, previews every font in itself and the typography as a sample.
The sidebar ends in an account card with role and a logout icon.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A section in the header set to #ffffff00 for the scrolled page still looked
white: the sticky header element paints its own white surface and a shadow
line underneath, so a transparent section showed that surface instead of the
page, and the line could be neither recoloured nor removed.
When a top-level header section defines a background of its own - normal,
hover or scrolled - the header element now gets `has-own-surface` and paints
nothing. A header without its own design keeps the white surface, so it does
not turn see-through over the content. A text colour alone is not a surface,
and a boxed column is not the whole header.
The check is a Twig function rather than a controller variable because four
paths render the header: page, archive, editor preview and area preview.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
"Einzeln wählen" sits on the left and the legal links on the right, on one
baseline; opened, the disclosure takes the full width and the links move
below it, right-aligned. `.consent-bar p` outranked `.consent-links` and
took the right margin away, so the links selector now carries the element.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
The icon set had no drawing for people, music or a house - the three pictures
the Tagespflege Moldovan template asks for. They join as `people`, `music` and
`home`; a test keeps the list and the drawings together, because a missing path
renders an empty `<path d="">`: invisible, without an error.
An icon block's own colour never reached its drawing: the embedded SVG carries
the same `block-icon` class as its wrapper and set the accent colour again.
`.block-icon .block-icon{color:inherit}` fixes it at the root.
The menu button becomes a thing you can shape. `burgerLabel` chooses symbol and
text, text only or symbol only (which keeps the name for screen readers), and
the new style group `menu` targets the button itself, the way `img` targets an
image and `controls` the slider. Inside the header the opened menu now lies
under the header across the full width instead of pushing the header apart -
on a phone that looked like a missing navigation.
The back-to-top button no longer depends on `animation-timeline: scroll()`. A
browser that parses the property but does not run the timeline left it in the
first frame of the animation: permanently invisible. Two classes from
scrolled.js decide now, and without a script the button simply stays visible.
Also: the Tagespflege Moldovan rebuild as its own installation, built only by
`scripts/seed-tagespflege-moldovan.php`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Social profiles are maintained once under settings (one HTTPS address per
network, SocialProfiles::NETWORKS) and shown by the new `social` block
(document schema v23) wherever it stands: size, shape, layout, labels,
new tab and an optional subset of networks on the block; colour, hover,
spacing and alignment through the free block styles. The addresses also
feed sameAs in the structured data. Icons are own outline drawings in
the style of the existing icon set. ADR 093.
Elektro Bernhard is rebuilt with a dark hero over the team photo, a
transparent header that turns white on scroll, Lato, cards with shadows
and hover lift, a review slider with six real Google reviews, a dark
solar band, page headers on every subpage and staggered entrance
effects. Social profiles sit in the top bar and the footer. The site is
a second local reference installation next to the Kant-IT pilot
(var/elektro-bernhard.db, launch config on port 8088); the seed script
rebuilds it from scratch.
Checked: PHP suite 492 tests (only the documented old failure remains),
33 JS tests, Twig lint, PHP style, full editor browser check with axe.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Round white arrows left and right, dots bottom right with the active one as
an accent bar, a small pause button and no counter - the design of the hero
slider on kant-it.de. Slider and controls share a positioned shell; arrows
hide on phones. New style property for the active dot colour.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Dots, arrows, counter and button background are style properties of the
slider ("Slider-Steuerung"), written as custom properties onto the slider and
onto the control bar the script places after it, so the script-free markers
and the scripted controls share the same colours.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Safari cancels a smooth scroll on a snapping container and snaps back to
the slide it came from, so arrows and autoplay stuttered on the iPhone. The
slider switches snapping off for the ride and restores it on arrival. The
control dots are drawn as dots with the active one filled.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
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>
The editor follows the Flatsome UX Builder: dark top bar with the device
switch, one sidebar with tabs for elements, structure and page, an element
view with breadcrumbs, property tabs and actions, element cards, a context
menu in the preview and keyboard shortcuts that also work inside the preview.
Entrance and hover effects play for every visitor unless the website chooses
to respect the browser's reduced-motion preference (setting "motion", class
"motion-respect" on <html>). Waiting blocks stay hidden until they scroll into
view and are shown at once when the reader jumps past them.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Der Knopf "nach oben" ist ein Anker, kein Skript: er springt an den
Seitenanfang, eingeblendet ueber eine Scroll-Zeitachse. Abschaltbar und
vollstaendig einstellbar - Seite, Form, Groesse, Farben, Schwelle,
Beschriftung.
Bilder lassen sich vergroessern, je Bild waehlbar (nichts / vergroessern
/ Adresse), Galerien mit einem Schalter. Grundlage ist ein gewoehnlicher
Verweis auf die grosse Fassung: ohne JavaScript oeffnet er das Bild, mit
JavaScript einen dialog-Betrachter mit Blaettern, Escape und Pfeiltasten.
Das Skript laedt nur, wo es etwas zu tun hat.
Die Rechtszeile wird ein Baustein und steht damit im Fussbereich statt
als heller Streifen darunter. Die eingebaute Leiste erscheint nur noch,
wenn die Seite den Baustein nicht enthaelt.
Dabei gefunden: zwei Stylesheet-Regeln griffen seit drei
Veroeffentlichungen nicht mehr. Beim Entfernen alter Regeln waren
Selektorreste stehen geblieben, und CSS nimmt dann die naechste Regel
mit - betroffen waren der Einwilligungsdialog und die Rechtszeile. Ein
Test prueft jetzt jede CSS-Datei auf abgebrochene Selektoren.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
In der wechselseitigen Beitragsliste stand "Weiterlesen" als reine
Beschriftung da: sichtbar wie ein Knopf, aber nicht anklickbar. Jetzt
ein Link, mit dem Beitragstitel im Linktext fuer Vorlesesoftware.
Jedes Menue holte beim Aufloesen die vollstaendige Seitenliste selbst.
Mit zwei Menuen fiel das nicht auf, mit vier - Kopf, zwei Listen im
Fuss, Rechtszeile - waren es vier gleiche Abfragen je Seitenaufruf.
Einmal je Anfrage gemerkt: 21 -> 18 Abfragen auf der Startseite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Eine Website ohne Beitraege redet nirgends von Beitraegen: Archiv und
Feed antworten mit 404, der Feedverweis faellt aus dem Kopf, und der
Hinweiskasten der Beitragsliste bleibt im Editor statt oeffentlich zu
erscheinen. Das gilt auch je Kategorie, sobald deren letzter Beitrag
weg ist. Dabei gefunden: die Liste fragte je Beitrag nach Bild und
Begriffen - jetzt je eine Abfrage fuer die ganze Liste (22 -> 16
Abfragen auf der Startseite des Nachbaus).
Unterstrichene Links gibt es nur noch im Fliesstext, dort mit einem
Schalter im Designbereich; Karten, Menue, Kontaktzeilen und der
Ansehen-Streifen haben keinen mehr. Die aktuelle Seite im Menue wird
ueber das Schriftgewicht kenntlich - die Akzentfarbe fiel im dunklen
Fussmenue durch die Kontrastpruefung.
Neu: das Symbol der Website laesst sich festlegen. Aus einem Bild der
Mediathek entstehen beim Speichern quadratische PNG-Dateien in 32 und
180 px; schlaegt das fehl, wird die Einstellung nicht gespeichert.
Die Browserpruefung laeuft jetzt gegen jede Installation statt nur
gegen die Demo: geprueft wird, was in der Sitemap steht, und das
Ladebudget trennt Bilder vom Beiwerk.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Karten (Raster mit Bild oben), Liste (Bild links, Text rechts), Kompakt
(nur Titel und Datum) und Wechselseitig (Bild mal links, mal rechts).
Dieselben Daten, dasselbe Markup - nur eine Klasse wechselt.
Die erste Fassung von "Wechselseitig" war ein umgedrehter Flexkasten und
sah auch so aus. Jetzt sitzt das Bild in einer Karte mit Ecken, Schatten
und festem Verhaeltnis, der Text hat eine Nummer aus einem CSS-Zaehler,
eine begrenzte Zeilenlaenge und einen sichtbaren Verweis, und zwischen den
Reihen liegt eine feine Linie.
Zwei Details: die Karte ist das Listenelement, also zaehlt :nth-child auf
ihr - die erste Fassung wechselte deshalb gar nicht. Und die Umkehrung
laeuft ueber order statt row-reverse, damit auf dem Telefon sauber wieder
Bild ueber Text steht.
Geprueft: 399 Tests unter PHP 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss auf allen vier Darstellungen.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut wollte mehrere Designs zur Auswahl. Bei Seiten waere das falsch -
dort weiss vorher niemand, was daraus wird. Bei Beitraegen ist die Form
fast immer dieselbe, und wer jedes Mal von vorn anfaengt, baut jedes Mal
etwas leicht anderes.
Bildkopf (Bild ueber die volle Breite, Titel darauf), Klassisch (Titel und
Vorspann schmal, Bild darunter) und Magazin (Titel neben den Eckdaten).
Gewaehlt wird beim Anlegen; danach ist jeder Block aenderbar wie jeder
andere. Kein Aufbau verlaesst sich auf ein Bild, das es noch nicht gibt.
Dabei ist eine Regel gefallen: der erste Textblock eines Abschnitts wurde
automatisch zur Kleinbeschriftung in Akzentfarbe. Das gestaltet nach
Position statt nach Absicht - im Fussbereich wurde daraus roter
Versalientext, in der Magazin-Vorlage aus dem Einstiegsabsatz eine
Beschriftung. Wer eine Beschriftung will, stellt sie ein.
Geprueft: 398 Tests unter PHP 8.2 und 8.4, Editortest, Browserpruefung,
axe ohne Verstoss auf allen drei Aufbauten.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut: "Beitragsseite gefaellt mir nicht, sollte wie bei kant-it.de sein
oder besser. Falls noetig Plugin bauen."
Die Originalseite hat Galerie, Vor/Zurueck zwischen Projekten und einen
Abschlussaufruf. Galerie und Aufruf gab es laengst - sie waren nur nicht
benutzt. Die Navigation gab es nicht.
Kein Plugin: ein Plugin ist hier ein Bausteintyp, dessen Eigenschaften der
Kern aufbewahrt, aber nicht rendert - es koennte also nichts ausgeben,
solange es keine Laufzeit dafuer gibt. Und die Nachbarn eines Beitrags
stehen in derselben Tabelle wie die Beitragsliste, die der Kern schon
fuellt. Also ein Baustein: postnav.
Gespeichert sind zwei Beschriftungen, mehr nicht - welcher Beitrag vorher
und nachher kommt, entscheidet der Renderer. Gesucht wird innerhalb
derselben Kategorie, und am Rand wird nicht umgebrochen.
Ausserdem: die uebernommenen Projekttexte begannen mit dem wiederholten
Titel (20 Beitraege), und schmale Abschnitte standen links statt mittig -
max-width ohne margin:auto ist eine halbe Einstellung.
Geprueft: 396 Tests unter PHP 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss auf der Beitragsseite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Zwei Wuensche, zwei Bausteine.
Die Autorotation ist Sekunden, kein Haekchen: 0 heisst aus, ab 3 laeuft
sie. Sie braucht ein Skript - das zweite auf einer oeffentlichen Seite
nach den Tabs - und es kommt nur auf Seiten mit einem solchen Slider,
sonst bleibt script-src 'none'. Es laeuft nicht bei "weniger Bewegung",
hoert beim ersten Tippen, Klicken, Wischen oder Scrollen endgueltig auf
und pausiert bei Zeiger, Fokus, Hintergrundtab und ausserhalb des Bildes.
Ohne Skript bleibt der Slider vollstaendig bedienbar.
Die Beitragsliste kann blaettern statt abzuschneiden. Eine Auswahl auf der
Startseite zeigt die neuesten und hoert auf; eine Uebersichtsseite
blaettert und sagt, wie viele es sind. Das war die eigene Regel aus
Arbeitspaket M, die fuer diesen Baustein noch nicht galt.
Beim Bauen zweimal dieselbe Falle wie vorgestern: eine Variable war in der
Schliessung nicht gebunden. Einmal das Sprungziel des Sliders (-78 statt
666), einmal die Seitenzahl - die zweite fiel sofort auf, weil die Seite
mit 500 antwortete.
Geprueft: 395 Tests unter PHP 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut: "schau selbst drueber, wuerdest du so online gehen?" Nein, so nicht.
Verborgener Text stand sichtbar auf der Seite: .visually-hidden gibt es in
admin.css, aber nicht in site.css. "Bewertung: 5 von 5" neben den Sternen
und "(Pflichtfeld)" hinter jedem Formularfeld waren zu lesen - seit dem
ersten Formular. axe meldet so etwas nicht, es prueft das Ansagen, nicht
das faelschliche Sichtbarsein.
Jede Spalte war ein Kasten: Rahmen, Ecken, 30px Innenabstand von
vornherein. Eine Spalte ist ein Layoutmittel; die Karte ist jetzt eine
Einstellung. Die Demoseite wirkt dadurch ruhiger.
Der Knopf im Aufruf-Band war Tinte auf Tinte, also auf dunklem Grund fast
unsichtbar - ausgerechnet beim Handlungsaufruf. Aufruf-Band und Formular
nehmen die Akzentfarbe.
Dazu: gestapelte Karten klebten aneinander (18px), und die linke Spalte im
Kontaktbereich war halb leer.
Geprueft: 393 Tests unter PHP 8.2 und 8.4, Editortest, Browserpruefung,
axe ohne Verstoss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut: "es gibt keinen Mausover-Effekt, das fehlt". Am Original
nachgemessen und uebernommen: die Karte hebt sich um 8px mit weichem
Schatten, das Bild zieht auf 1,06 auf, darueber blendet ein Hinweis ein.
Die Bewegung steht vollstaendig hinter der Bewegungsschranke - wer
weniger Bewegung eingestellt hat, bekommt keine, sieht den Hinweis aber
trotzdem. Alles haengt zusaetzlich an :focus-within, damit der Effekt
nicht nur Mausbenutzern gehoert. Der Hinweis ist fuer Vorlesesoftware
ausgeblendet, weil der Titel darunter zum selben Ziel fuehrt.
Ausserdem: der Leuchteffekt trug noch das Gruen der Vorlage statt der
Akzentfarbe der Website.
Geprueft: 391 Tests unter PHP 8.2 und 8.4, Editortest, Browserpruefung,
axe ohne Verstoss.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Beim vollstaendigen Nachbau von kant-it.de aufgefallen: die Groesse der
Kennzahl liess sich am Block einstellen und aenderte nichts, weil
.block-stat strong 44px fest setzte. Dasselbe bei der Kundenstimme mit
18px. Beide erben jetzt vom Block - dieselbe Familie wie die Theme-Skala,
die zuvor die Kartenueberschriften ueberschrieben hat.
Der Nachbau umfasst jetzt alle 29 Projektbeitraege mit den echten Bildern
und Texten sowie die vollstaendige Datenschutzerklaerung.
Dabei ein Befund fuer den echten Umzug: 11 der 29 Projektbilder haette die
Mediathek auf einem 128-MB-Hosting abgewiesen, zwei auch mit mehr Speicher
- sie ueberschreiten die Pixelgrenze. Ein Umzug muss solche Bilder vorher
verkleinern.
Geprueft: 390 Tests unter PHP 8.2 und 8.4, Editortest, Browserpruefung,
axe ohne Verstoss auf fuenf Ansichten des Nachbaus.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut hat den Nachbau Detail fuer Detail mit kant-it.de verglichen. Vier
Stellen, an denen das CMS etwas nicht konnte oder etwas Falsches tat.
Der Slider zeigte eine Rollleiste statt Punkte. Punkte zeichnet jetzt der
Browser selbst - scroll-marker-group und ::scroll-marker, ohne Skript, mit
erkennbarem aktivem Punkt. Wo ein Browser das noch nicht kennt, bleibt das
wischbare Schiebefeld. Pfeile gibt es bewusst nicht: Chrome legt
::scroll-button an den Seitenrand statt in den Slider. Und er rotiert
nicht von selbst, weil eine oeffentliche Seite kein JavaScript laedt.
Eine Beitragskarte war keine Karte, sondern eine lose Spalte. Jetzt weiss,
18px Ecken, Bild buendig oben im Verhaeltnis 16:10, Text darunter.
Die Theme-Schriftskala ueberschrieb die Groessen der Bausteine: jede
Kartenueberschrift war so gross wie eine Abschnittsueberschrift.
Eine Ueberschrift darf ab v17 ein Wort hervorheben. Dabei waere beinahe
eine Luecke entstanden: die Dokumentfassung war in der Schliessung des
Filters nicht gebunden, der Filter lief nie, die Ausgabe war schon
unescaped. Ein Test mit <script> in einer Ueberschrift hat es gezeigt.
Geprueft: 390 PHP-Tests unter 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung, axe ohne Verstoss auf dem Nachbau.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Alles aus dem Nachbau von kant-it.de, den Umut durchgesehen hat.
Eine Beitragsliste kann Datum, Autor, Auszug und Kategorie ausblenden und
sieht damit aus wie ein Projektraster statt wie ein Blogarchiv. Beinahe
lautlos danebengegangen: |default(true) macht in Twig aus einem
gespeicherten false ein true - die Einstellung haette sich speichern
lassen und nichts getan. Der Test prueft beide Richtungen.
Ein Bild darf verlinken, damit ein Logo auf die Startseite zeigt; die
Adresse geht durch dieselbe Pruefung wie jeder andere Link. Und der
Kopfbereich kann beim Scrollen oben bleiben - als Einstellung der
Website, weil nur das umschliessende Element kleben kann.
Der groesste Fund steckt im Editor: die Liste bekannter Schemafassungen
endete bei 12, waehrend Bausteine v13 bis v15 verlangen. In einem
aktuellen Dokument liess sich damit kein Baustein mehr einfuegen, und
Symbol, Fortschritt, Slider und externer Inhalt in gar keinem. Jetzt eine
Zahl an einer Stelle, mit einem Test ueber jeden Typ und jede Fassung.
Geprueft: 389 PHP-Tests unter 8.2 und 8.4, 18 JS-Tests, Editortest,
Browserpruefung.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Gefunden beim Nachbau von kant-it.de: eine Website mit eigener Palette
behielt das Gruen der Vorlage. Symbole, Knoepfe, Rahmen, ruhiger Text,
Fokusring und der Einwilligungsdialog kamen aus festen Token in site.css,
die das Theme nie gesetzt hat - die Farben liessen sich einstellen und
taten nichts.
Das Theme setzt --ink, --paper, --accent, --muted, --line und --mint jetzt
selbst. Als Akzent dient die Linkfarbe, nicht ein Palettenname: das Token
faerbt auch kleine Beschriftungen, und eine helle Akzentfarbe waere dort
nicht lesbar. --muted und --line werden aus Textton und Hintergrund
gemischt; eine Palettenfarbe gleichen Namens gewinnt.
Der Einwilligungsdialog nimmt die Primaerfarbe und laesst sich eigens
faerben ueber eine Palettenfarbe mit dem Schluessel "consent".
Ausserdem aus demselben Nachbau: Ueberschriften werden nicht mehr getrennt
("di-gitale Exzellenz"), und Fliesstext auf dunkel gefaerbten Abschnitten
mischt seine Farbe aus der geerbten statt aus einem Token fuer helle
Hintergruende.
Geprueft: 381 Tests unter PHP 8.2 und 8.4, axe ohne Verstoss auf vier
Seiten des Nachbaus in Desktop- und Handybreite.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drei Fragen, drei unterschiedliche Antworten.
Die globale Seitenbreite war einstellbar und wirkte nur in eine Richtung:
site.css schrieb width:min(1180px,...), also schluckte die 1180 jeden
groesseren Wert aus dem Designbereich. Schmaler ging, breiter nicht, ohne
jede Meldung. Die Zahl kommt jetzt aus dem Theme. Das ist der dritte Fehler
dieser Art an einem Tag - nach der Rahmenstaerke und der Bildrundung -, und
alle drei hatten dieselbe Ursache: CSS, das nie gegen eine Erwartung geprueft
wurde.
Menueplaetze und Menueart waren vermischt. Wo ein Menue steht, bestimmt der
Block und war immer frei. Welche Eintraege es zeigt, bestimmt der Platz -
davon gab es zwei, jetzt vier (Haupt, zweite, Fusszeile, Rechtliches). Wie es
aussieht, war gar nicht einstellbar: jetzt waagerecht, untereinander oder
aufklappbar als Burger. Der Burger ist ein details-Element, denn ein Menue,
das ein Skript zum Oeffnen braucht, geht auf einer Seite ohne Skript nicht auf.
"Seiten ohne Menue" war nicht moeglich - der Kopfbereich war ein Dokument fuer
alle Seiten. Jetzt waehlt jede Seite: Standard, ein anderer Bereich, oder
keiner. Dafuer musste die Regel "es gibt genau einen Kopf- und Fussbereich"
fallen; sie war richtig, solange niemand waehlen konnte, und wurde falsch,
sobald man waehlen kann. Ein bestehender Test, der diese Regel zusicherte, ist
deshalb umgeschrieben und prueft jetzt die neue.
Gespeichert wird die Wahl, nicht das Ergebnis: leer heisst Standard, none
heisst keiner, sonst die Adresse. Ein Verweis auf einen geloeschten Bereich
faellt auf den Standard zurueck, statt die Seite stillschweigend ohne
Navigation auszuliefern.
Neu im Ausbauplan: ein einzelnes install.php, das das Releasearchiv
herunterlaedt und entpackt, damit niemand tausende Dateien per FTP hochladen
muss. Mit den Regeln, die darueber entscheiden, ob das eine Hilfe oder eine
Einladung ist: Adresse und SHA-256 stehen in der Datei und nicht im Formular,
HTTPS mit Zertifikatspruefung, Schutz gegen ZIP-Slip, Selbstloeschung danach.
336 PHP-Tests / 1354 Assertions, 17 JS-Modelltests.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umuts Frage: "Haben wir bilder usw als volles bild startseite? 100% hoehe
usw?" Die Teile gab es, die Kombination nicht - und zwei Sachen fehlten
wirklich.
Abschnittsbreiten: neu "randlos" (ohne Begrenzung, ohne Rand, von Kante zu
Kante) und "breit" (1600 px) neben normal und schmal. Vorher war jeder Inhalt
auf 1180 px begrenzt; ein Bild von Kante zu Kante war nicht moeglich.
Hoeheneinheiten: neu dvh, svh, lvh. 100vh ist auf dem Handy hoeher als der
Bildschirm - es meint die Ansicht ohne Adressleiste, und der untere Rand
verschwindet darunter. 100dvh ist das, was jemand meint, der "volle Hoehe"
sagt.
Und eine Vorlage "Vollbild-Hero", die die ganze Kombination auf einen Klick
setzt: randlos, 100dvh (mobil 80dvh), Inhalt senkrecht zentriert,
Hintergrundbild formatfuellend, dunkle Ueberlagerung damit der Text lesbar
bleibt, Ueberschrift, Text, Button. Die Teile waren alle da, aber diese
Kombination zu finden war eine Recherche.
328 PHP-Tests / 1329 Assertions, 17 JS-Modelltests. Geprueft: vier Breiten
angenommen und eine erfundene abgewiesen, sieben Hoehenangaben mit dvh/svh/lvh
und zwei ungueltige, ein randloser Abschnitt mit formatfuellendem Bild, und
die Hero-Vorlage Eigenschaft fuer Eigenschaft.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Umut fragte nach Zeilen, nach Rahmen und Rundungen - und ob der Designer so
aufgestellt ist wie Flatsome. Der Vergleich steht in
docs/DESIGNER-VERGLEICH.md, mit der Einschraenkung, dass Flatsome hier nicht
installierbar war: das ist Wissen, keine Messung.
Zwei echte Fehler kamen dabei heraus, beide vorher unbemerkt.
Eine Rahmenstaerke je Seite wurde zu border-width-top. Das ist kein CSS, der
Browser verwirft die Zeile stillschweigend - Rahmenstaerken je Seite hatten
nie eine Wirkung, und es gab keine Meldung, die darauf hingewiesen haette. Die
Schreibweise steht jetzt je Eigenschaft im Schema, weil padding-top dem Namen
folgt und border-top-width nicht.
Und eine Rundung auf einem Bildblock landete auf dem figure, waehrend das Bild
darin eckig blieb. Eigenschaften koennen jetzt auf das Bild im Block zielen
(target 'img' im Schema, eine zweite Regel im Compiler) - deshalb tut
object-fit ueberhaupt etwas, denn auf einem figure tut es nichts.
Von 50 auf 93 Eigenschaften: Bild (Ausschnitt, Bildmitte, Seitenverhaeltnis,
Hoehe, Ecken, Rahmen), Positionierung mit z-index, getrennte Zeilen- und
Spaltenabstaende, Flex je Element, Innen- und Textschatten, acht Bildfilter
als eine filter-Deklaration in fester Reihenfolge, Mischmodus, Zusatzrahmen,
Mauszeiger. Neun Einblendungen mit Verzoegerung, sieben Hover-Effekte, alle
innerhalb von prefers-reduced-motion.
Drei neue Bausteine (Dokumentschema v15): Symbol als eigenes Element,
Fortschritt mit role=meter und dem Wert im Markup statt nur in der
Balkenbreite, und ein Slider als Scrollbereich mit Einrasten - ohne
JavaScript, ohne Autoplay, mit Finger, Trackpad, Mausrad und Tastatur.
Zeilen waren nicht unauffindbar, sondern unbrauchbar leer: eine Zeile ohne
Spalten sieht aus wie nichts. Jetzt legt "Zeile, 2/3/4 Spalten" beides auf
einen Klick an, und im Werkzeugkasten steht, wie der Aufbau gemeint ist.
315 PHP-Tests / 1309 Assertions, 16 JS-Modelltests, axe auf dreizehn
Bildschirmen ohne Verstoesse. 16 Datensaetze rechnen jede Eigenschaftsform
gegen die erwartete Deklaration nach - die Pruefung, die die beiden Fehler
oben gefunden haette, wenn es sie vorher gegeben haette.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Ein Hinweis, der nur fragt und sonst nichts ändert, ist Theater. Deshalb zwei
Hälften, und die zweite ist die wichtigere.
Die Frage: serverseitig gerendert, per Formular beantwortet, ohne JavaScript -
die öffentlichen Seiten laufen mit script-src 'none', und ein Banner mit
Skript wäre das eine, was diese Regel bricht. "Alle akzeptieren" und "Nur
notwendige" sind dieselbe Art Knopf, gleich groß, nebeneinander. Die
Entscheidung steht in einem eigenen Cookie: Fassung der Frage, gewährte
Gruppen, Zeitpunkt. Keine Kennung, kein Serverabgleich, ein halbes Jahr. Kein
CSRF-Token, absichtlich: die Anfrage ändert nichts auf dem Server, und ein
Token bräuchte für jeden Besucher eine Sitzung - genau das, was ein
Einwilligungshinweis vermeiden soll. Geprüft wird stattdessen das
Rücksprungziel.
Das Zurückhalten: der neue Block "Externer Inhalt" (Dokumentschema v14) lädt
ohne Einwilligung nichts - kein iframe, keine Adresse im Markup, frame-src
'none'. Der Platzhalter nennt den Anbieter; sein Knopf gewährt genau diese
Gruppe und nimmt die vorher gewährten mit. Erst danach kommt der Host in die
Sicherheitsrichtlinie, und nur dieser. Im Editor bleibt es immer ein
Platzhalter.
Datenschutz und Impressum sind keine Einstellung mehr: fehlen sie, warnt die
Verwaltung und legt sie auf Knopfdruck als Gerüst an, mit NOCH AUSZUFÜLLEN an
jeder Pflichtangabe. Den Text schreibt das CMS nicht - das ist eine rechtliche
Aussage über eine Firma. Verlinkt werden beide aus dem Template, nicht aus dem
Fußbereich: ein Pflichtlink, den man löschen kann, ist keiner.
Dazu der Fehler, den Umut im Screenshot gesehen hat: die Wortmarke stand als
Buchstabenkolonne in einem 60 Pixel breiten Kasten. .site-header war noch ein
Flexcontainer aus der Zeit des handgeschriebenen Markups und quetschte die
Sektion zusammen, und design.css band jede Designklasse an #main - wo Kopf-
und Fußbereich seit ADR 026 nicht mehr liegen. Beides behoben.
Neu im Ausbauplan: Statistik (serverseitige, wiedererkennungsfreie Zählung,
die deshalb keine Einwilligung braucht), Inhalte und Ladeverhalten prüfen
(Blättern überall, minimal laden, Ladebudget), Installer (Schritt für Schritt
im Browser, Migration inklusive).
245 PHP-Tests / 976 Assertions unter PHP 8.2.12 und 8.4.22. Der Browsertest
fand zwei Fehler von mir: grauer Text auf dunklem Fußbereich mit 2,24:1, und
das fixierte Banner verdeckte den Einwilligungsknopf am Seitenende.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Migration 20260912001000 legt die Tabelle partial an: Blockdokumente,
die keine Seiten sind. Kopf- und Fußbereich je einmal,
wiederverwendbare Sektionen beliebig viele - im selben Editor
gestaltbar wie eine Seite, weil der Editor seine Schnittstelle aus
einem Datenattribut nimmt statt sie fest zu kennen.
Im Template steht keine Marke und keine Navigation mehr. Ein neuer
Block menu zeigt einen Menüplatz; die Einträge bleiben in der
Menüverwaltung.
Eine Seite braucht genau eine Hauptüberschrift, ein geteilter Bereich
keine - sonst hätte jede Seite, die ihn trägt, zwei. Geteilte
Bereiche haben keinen Entwurf und keine Veröffentlichung: sie sind
überall sichtbar, gespeichert heißt live.
Verknüpft statt kopiert: partialref speichert nur die Adresse, die
Blöcke bleiben im Bereich, und eine Änderung erreicht jede Seite, die
sie einbindet. Zyklen abgewiesen, Verschachtelung auf drei Ebenen
begrenzt, eine entfernte Sektion hinterlässt keine Lücke und der
Verweis bleibt erhalten. Die Abschnittsvorlagen kopieren weiterhin.
Ein echter Fehler kam dabei heraus, der jede dunkle Sektion mit
Spalten betraf: .block-column>.block-text:first-child setzt eine
Farbe für helle Hintergründe und gewinnt auf Spezifität gegen
.tone-ink - gemessen 1,87:1.
Geprüft: 165 PHP-Tests / 660 Assertions unter PHP 8.2.12 und 8.4.22,
15 JavaScript-Modelltests, Editortest mit axe ohne Verstöße,
öffentliche Browserprüfung, Migration nach Backup angewendet.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>