Compare commits

..
6 Commits
Author SHA1 Message Date
kantarciandClaude Opus 5 245f189d57 Release 1.21.3
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
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 21:56:12 +02:00
kantarciandClaude Opus 5 99b3aba39a Lift what sticks to the bottom above the browser's own bar
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>
2026-09-16 21:56:07 +02:00
kantarciandClaude Opus 5 c91f996524 Release 1.21.2
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
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 21:30:23 +02:00
kantarciandClaude Opus 5 0ed4c83e41 Fix the mobile menu writing white on white and the hero losing its gutter
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>
2026-09-16 21:30:19 +02:00
kantarciandClaude Opus 5 9e0365f841 Release 1.21.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
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 15:56:25 +02:00
kantarciandClaude Opus 5 ff2b57db58 Make the start page of a new installation a construction page
Umut: "Kannst du bitte fuer die Erstseite bzw. nach der Installation eine
schoene Erstseite erstellen. Under Construction oder so ohne Details mit melde
dich an usw. Am besten auch noch Logo von KantSites."

starterPage() shipped two sections of instructions for the owner: "Melde dich
an, oeffne sie im Editor ..." and "Die naechsten Schritte" with design, menus
and settings. That stood on the only page a fresh installation serves
publicly, so the visitor read it, not the owner - who gets the same steps on
the last installer screen and in the admin area anyway.

It is now the page such a site actually needs: the KantSites mark, the label
"In Arbeit" in the accent colour, the site name as the single H1, "Hier
entsteht eine neue Webpraesenz.", a short rule and the line "Erstellt mit
KantSites". The section is a flex container with min-height 70vh and a
gradient from @paper to @mint, so the little that is there sits in the middle
of the screen; the colours are palette references and follow a design change.
Everything is an ordinary block with ordinary style values and can be changed
or removed in the editor. The page description is a real sentence now instead
of "NOCH AUSZUFUELLEN", so a freshly installed site does not carry a
placeholder into search results.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-16 15:56:21 +02:00
6 changed files with 121 additions and 29 deletions

No files matched your search

+1 -1
View File
@@ -1 +1 @@
1.21.0 1.21.3
+12
View File
@@ -1,5 +1,17 @@
# Projektstatus # Projektstatus
## Startseite nach der Installation: eine Baustellenseite statt einer Anleitung (16.09.2026)
**Anlass:** Umut: „Kannst du bitte für die Erstseite bzw. nach der Installation eine schöne Erstseite erstellen – Under Construction oder so, ohne Details mit ‚melde dich an‘ usw. Am besten auch noch Logo von KantSites: ‚Hier entsteht eine neue Webpräsenz mit KantSites‘.“
**Was los war.** `DemoContent::starterPage()` lieferte zwei Abschnitte mit Arbeitsanweisungen an die Betreiberin oder den Betreiber: „Melde dich an, öffne sie im Editor …“ und „Die nächsten Schritte“ mit Design, Menüs und Einstellungen. Das stand auf der einzigen Seite, die bis zum ersten Bearbeiten **öffentlich** erreichbar ist – gelesen hat es also der Besucher, nicht der Betreiber. Für den stehen dieselben Schritte längst auf dem letzten Installerbildschirm (`templates/install/done.html.twig`) und in der Verwaltung.
**Neu.** Eine Baustellenseite, die genau das sagt und sonst nichts: das KantSites-Zeichen (`public/assets/kantsites-logo.svg`, dieselbe Form wie das Favicon), die Kleinzeile „In Arbeit“ in der Akzentfarbe, als einzige H1 der Name der Website, darunter „Hier entsteht eine neue Webpräsenz.“, ein kurzer Trennstrich und die Zeile „Erstellt mit KantSites“. Der Abschnitt ist ein Flex-Container mit `min-height: 70vh` und einem Verlauf von `@paper` nach `@mint`, stellt den Inhalt also mittig auf den Bildschirm; die Farben sind Palettenverweise und folgen jedem Designwechsel. Alles sind gewöhnliche Bausteine mit gewöhnlichen Stilwerten – im Editor also vollständig änder- und löschbar. Die Seitenbeschreibung ist jetzt ein echter Satz statt „NOCH AUSZUFÜLLEN“, damit eine frisch installierte Website in Suchergebnissen nicht mit einem Platzhalter steht.
**Unverändert:** Kopf- und Fußbereich, die beiden Rechtsseiten als Gerüst mit markierten Pflichtangaben, Menüs.
**Geprüft:** `tests/InstallFlowTest.php` (Installation über die sechs Bildschirme, danach liefert `/` die neue Seite), `php vendor/bin/phpunit` 579 von 579. Im Browser angesehen: eine frisch migrierte Datenbank mit dem Inhalt einer Neuinstallation, Desktop und 375 px.
## Fassung 1.21.0: Bildecken gehören dem Designbereich (16.09.2026) ## Fassung 1.21.0: Bildecken gehören dem Designbereich (16.09.2026)
**Anlass:** Umut zum Hero-Bild von DEEG: „Das sollte abgerundet sein, aber wurde von irgendwo gemacht, und die Ecken sehen komisch aus. Es gibt auch im Editor gar nicht Bildecken abrunden, oder?“ **Anlass:** Umut zum Hero-Bild von DEEG: „Das sollte abgerundet sein, aber wurde von irgendwo gemacht, und die Ecken sehen komisch aus. Es gibt auch im Editor gar nicht Bildecken abrunden, oder?“
+1
View File
@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" width="128" height="128"><rect width="128" height="128" rx="32" fill="#182d2b"/><path d="M36 28h16v42l26-28h22L68 76l34 28H78L52 82v22H36z" fill="#e5f0b6"/></svg>

After

Width:  |  Height:  |  Size: 225 B

+39 -8
View File
@@ -6,6 +6,21 @@ h1,h2,h3,h4,h5,h6{hyphens:manual}
p{hyphens:auto} p{hyphens:auto}
.visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.block-image{margin:0;max-width:100%}.block-image img{max-width:100%;height:auto} .visually-hidden{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip-path:inset(50%);white-space:nowrap;border:0}.block-image{margin:0;max-width:100%}.block-image img{max-width:100%;height:auto}
:root{color-scheme:var(--k-scheme,light);--paper:#f7f8f4;--ink:#182d2b;--muted:#50625d;--line:#d7dfd7;--mint:#e5f0b6;--accent:#315b49;--error:var(--k-error,#8b2f22);--error-bg:var(--k-error-bg,#fff0eb);--error-line:var(--k-error-line,#e2b2a7);font-family:Arial,Helvetica,sans-serif;color:var(--ink);background:var(--paper);font-synthesis:none} :root{color-scheme:var(--k-scheme,light);--paper:#f7f8f4;--ink:#182d2b;--muted:#50625d;--line:#d7dfd7;--mint:#e5f0b6;--accent:#315b49;--error:var(--k-error,#8b2f22);--error-bg:var(--k-error-bg,#fff0eb);--error-line:var(--k-error-line,#e2b2a7);font-family:Arial,Helvetica,sans-serif;color:var(--ink);background:var(--paper);font-synthesis:none}
/* Die Hoehe der Browserleiste, die ueber dem Inhalt liegt.
Safari auf dem iPhone schiebt seine Adressleiste beim Herunterscrollen zu einem schmalen
Balken zusammen und legt den ueber die Seite. Ein fixiertes Element rechnet sein `bottom`
aber gegen die volle Ansicht, nicht gegen den freien Teil - was unten klebt, steht damit in
dem Streifen, den der Browser sich selbst genommen hat. Sichtbar ist es, aber jeder Tipp
landet auf der Leiste. Genau das war mit dem Nach-oben-Knopf: am Schreibtisch tadellos, auf
dem Telefon tot, auf jeder Seite jeder Installation.
Die Differenz zwischen grosser Ansicht (`lvh`, Leiste zusammengeschoben) und kleiner Ansicht
(`svh`, Leiste ausgefahren) ist genau diese Hoehe. Wo es keine solche Leiste gibt, ist sie
null, und nichts verschiebt sich.
Die Abfrage davor ist Pflicht, nicht Vorsicht: kennt ein Browser die Einheiten nicht, waere
die Rechnung erst beim Errechnen ungueltig, und `bottom` fiele auf `auto` zurueck - das Ding
stuende dann irgendwo. So bleibt es schlicht bei null. */
:root{--kant-browser-chrome:0px}
@supports (height:100lvh) and (height:100svh){:root{--kant-browser-chrome:calc(100lvh - 100svh)}}
*{box-sizing:border-box}body{margin:0}a{color:inherit;text-underline-offset:5px}a:hover{text-decoration-thickness:2px}:focus-visible{outline:3px solid var(--accent);outline-offset:6px}main:focus{outline:none}.shell{width:min(var(--k-container,1180px),calc(100% - 64px));margin-inline:auto}.site-header{border-bottom:1px solid var(--line)}.site-header.is-sticky{position:sticky;top:0;z-index:40;background:var(--paper);box-shadow:0 1px 0 var(--line),0 10px 30px color-mix(in srgb,var(--ink) 8%,transparent)} *{box-sizing:border-box}body{margin:0}a{color:inherit;text-underline-offset:5px}a:hover{text-decoration-thickness:2px}:focus-visible{outline:3px solid var(--accent);outline-offset:6px}main:focus{outline:none}.shell{width:min(var(--k-container,1180px),calc(100% - 64px));margin-inline:auto}.site-header{border-bottom:1px solid var(--line)}.site-header.is-sticky{position:sticky;top:0;z-index:40;background:var(--paper);box-shadow:0 1px 0 var(--line),0 10px 30px color-mix(in srgb,var(--ink) 8%,transparent)}
/* Über dem Inhalt: der Kopf nimmt keinen Platz ein, die erste Sektion beginnt unter ihm. Farbe /* Über dem Inhalt: der Kopf nimmt keinen Platz ein, die erste Sektion beginnt unter ihm. Farbe
und Schatten kommen aus der Gestaltung des Kopfbereichs, auch die für den gescrollten Zustand. */ und Schatten kommen aus der Gestaltung des Kopfbereichs, auch die für den gescrollten Zustand. */
@@ -182,7 +197,9 @@ p{hyphens:auto}
Aussehen aus den Einstellungen: `--kant-consent-*` im Kopf der Seite (Consent::styles), die 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 Anordnung als Klasse. Ohne Einstellung gilt Papier, Tinte und Akzent des Themes. Ein Wert wie
`--k-consent` aus der Palette bleibt als Zwischenstufe erhalten. */ `--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} /* Das `+ var(--kant-browser-chrome)` hebt die Leiste ueber Safaris zusammengeschobene
Adressleiste; sonst liegen „Annehmen“ und „Ablehnen“ unter ihr und sind nicht zu treffen. */
.consent-bar{position:fixed;inset-inline:16px;bottom:calc(16px + var(--kant-browser-chrome));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. */ /* 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)))} .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} .kant .consent-bar h2{display:flex;align-items:center;gap:10px;font-size:17px;line-height:1.3;margin:0 0 8px}
@@ -214,7 +231,10 @@ p{hyphens:auto}
.consent-links a:hover{color:var(--kant-consent-accent,var(--k-consent,var(--accent)))} .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 /* 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. */ 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))} /* Klebt die Leiste an der Kante, wird sie nicht angehoben - das gaebe einen Spalt, durch den
die Seite scheint. Stattdessen waechst ihr unterer Innenabstand um die Hoehe der
Browserleiste, sodass die Knoepfe darueber bleiben. */
.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));padding-bottom:calc(20px + var(--kant-browser-chrome))}
@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%}} @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} .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 - /* While the question is open the bar is fixed at the bottom, so the page has to end above it -
@@ -226,7 +246,7 @@ body.consent-open{padding-bottom:320px}
@media (min-width:900px){body.consent-open{padding-bottom:200px}} @media (min-width:900px){body.consent-open{padding-bottom:200px}}
.consent-page main{max-width:680px;margin-inline:auto;padding:40px 20px} .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;max-height:none;margin:24px 0 0;display:block} .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}} @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 calc(16px + var(--kant-browser-chrome))}.consent-layout-leiste{padding-inline:18px}}
/* An embedded third party, and what stands in its place until it may load. */ /* 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))} .block-embed iframe{display:block;width:100%;border:0;border-radius:10px;background:color-mix(in srgb,var(--ink) 8%,var(--paper))}
@@ -438,9 +458,14 @@ body.consent-open{padding-bottom:320px}
.kant .width-full>.section-inner{width:100%;max-width:none;margin-inline:0} .kant .width-full>.section-inner{width:100%;max-width:none;margin-inline:0}
@media (max-width:767px){.kant .width-wide>.section-inner{width:calc(100% - 36px)}} @media (max-width:767px){.kant .width-wide>.section-inner{width:calc(100% - 36px)}}
/* A section that is a flex container centres its content box; the inner container must then be /* Ein Abschnitt, der ein Flex-Kasten ist, reiht seinen Inhaltskasten als Flex-Element ein.
allowed to grow, or a full height section would show a strip in the middle. */ Wachsen darf der dabei nicht: die Laufrichtung ist voreingestellt die Zeile, und dort frisst
.block-section>.section-inner{flex:1 1 auto} das Wachsen die Randluft des Containers. Auf dem Telefon stand der Text eines Hero-Abschnitts
damit hart an der Kante, waehrend Kopf und alle uebrigen Abschnitte ihre 18 px hatten - am
Schreibtisch fiel es nicht auf, weil dort die Hoechstbreite des Designs vorher bremst.
Die Hoehe braucht das Wachsen nicht: quer zur Laufrichtung dehnt sich ein Flex-Element
ohnehin ueber die volle Hoehe, und steht "Ausrichtung: Mitte", ist die Mitte gewollt. */
.block-section>.section-inner{flex:0 1 auto}
/* Menu kinds. Horizontal is the default; vertical stacks; burger is a details element, so it /* Menu kinds. Horizontal is the default; vertical stacks; burger is a details element, so it
opens without a script - a menu that needs one would not open on a page that runs none. */ opens without a script - a menu that needs one would not open on a page that runs none. */
@@ -472,9 +497,13 @@ body.consent-open{padding-bottom:320px}
weder faerben noch abschalten. Linie und Schatten kommen dann aus Rahmen und Wirkung. */ weder faerben noch abschalten. Linie und Schatten kommen dann aus Rahmen und Wirkung. */
.site-header.is-sticky.has-own-surface{background:transparent;box-shadow:none} .site-header.is-sticky.has-own-surface{background:transparent;box-shadow:none}
.site-header .menu-burger[open]>summary{border-radius:8px} .site-header .menu-burger[open]>summary{border-radius:8px}
/* Die Flaeche malt ihren eigenen Untergrund, also setzt sie auch ihre eigene Schriftfarbe.
Ohne das erbte sie die Farbe des Kopfbereichs: bei einem Kopf, der ueber einem dunklen Bild
schwebt und deshalb weiss schreibt, stand das aufgeklappte Menue weiss auf weiss. */
.site-header .menu-burger-body{position:absolute;left:0;right:0;top:100%;z-index:30; .site-header .menu-burger-body{position:absolute;left:0;right:0;top:100%;z-index:30;
padding:15px max(16px,calc((100% - var(--k-container,1180px)) / 2 + 16px)) 25px; padding:15px max(16px,calc((100% - var(--k-container,1180px)) / 2 + 16px)) 25px;
background:var(--paper);box-shadow:0 15px 25px color-mix(in srgb,var(--ink) 12%,transparent)} background:var(--paper);color:var(--ink);
box-shadow:0 15px 25px color-mix(in srgb,var(--ink) 12%,transparent)}
.site-header .menu-burger-body .menu-list{gap:0} .site-header .menu-burger-body .menu-list{gap:0}
.site-header .menu-burger-body .menu-list a{padding:12px 10px;font-size:16px} .site-header .menu-burger-body .menu-list a{padding:12px 10px;font-size:16px}
/* Visibility never overwrites the block's grid/flex/display settings. */ /* Visibility never overwrites the block's grid/flex/display settings. */
@@ -489,10 +518,12 @@ body.consent-open{padding-bottom:320px}
Safari), steht er von Anfang an da: sichtbar und nutzbar ist besser als unsichtbar. Safari), steht er von Anfang an da: sichtbar und nutzbar ist besser als unsichtbar.
Aussehen, Größe, Form, Seite und Schwelle kommen aus den Einstellungen. */ Aussehen, Größe, Form, Seite und Schwelle kommen aus den Einstellungen. */
.scroll-top{position:fixed;bottom:22px;right:22px;z-index:40;display:grid;place-items:center;width:var(--kant-top-size,48px);height:var(--kant-top-size,48px);border-radius:var(--kant-top-radius,999px);background:var(--kant-top-bg,var(--accent));color:var(--kant-top-ink,#fff);text-decoration:none;box-shadow:0 8px 24px color-mix(in srgb,var(--ink) 28%,transparent);font-size:calc(var(--kant-top-size,48px) / 2.4);line-height:1} .scroll-top{position:fixed;bottom:22px;right:22px;z-index:40;display:grid;place-items:center;width:var(--kant-top-size,48px);height:var(--kant-top-size,48px);border-radius:var(--kant-top-radius,999px);background:var(--kant-top-bg,var(--accent));color:var(--kant-top-ink,#fff);text-decoration:none;box-shadow:0 8px 24px color-mix(in srgb,var(--ink) 28%,transparent);font-size:calc(var(--kant-top-size,48px) / 2.4);line-height:1}
/* Ueber die Browserleiste hinaus, sonst ist der Knopf auf dem Telefon nicht zu treffen. */
.scroll-top{bottom:calc(22px + var(--kant-browser-chrome))}
.scroll-top-links{right:auto;left:22px} .scroll-top-links{right:auto;left:22px}
.scroll-top:hover{filter:brightness(1.08)} .scroll-top:hover{filter:brightness(1.08)}
.scroll-top:focus-visible{outline:3px solid var(--accent);outline-offset:3px} .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}} @media (max-width:767px){.scroll-top{bottom:calc(16px + var(--kant-browser-chrome));right:16px}.scroll-top-links{left:16px;right:auto}}
/* Solange die Einwilligungsfrage offen ist, bleibt der Knopf da. Frueher verschwand er ganz - /* 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. 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 Bis 799 px deckt die Leiste die Ecke ab, in der er steht: dort setzt er sich per
+67 -19
View File
@@ -122,34 +122,75 @@ final class DemoContent
]; ];
} }
/**
* The header and footer a fresh installation starts with. They are ordinary block documents:
* everything in them can be changed, moved or removed in the editor. No main heading, because
* every page brings its own.
*
* @return array<string, array{name: string, document: array}>
*/
/** /**
* The start page of a fresh installation - not demo content. * The start page of a fresh installation - not demo content.
* *
* Short on purpose. Somebody who has just installed should find a page they can edit, not a * A website that is already reachable but not yet written needs a page that says exactly that:
* brochure about the CMS they have to delete first. Exactly one H1, as every page requires. * the name of the site, the notice that something is being built here, and nothing else. No
* instructions for the owner - those stand on the last screen of the installer and in the
* admin area, not on a page every visitor sees. Exactly one H1, as every page requires.
*/ */
public static function starterPage(string $siteName): array public static function starterPage(string $siteName): array
{ {
return [ return [
'schemaVersion' => 16, 'schemaVersion' => 24,
'title' => $siteName, 'title' => $siteName,
'description' => 'NOCH AUSZUFÜLLEN: ein Satz darüber, was es hier gibt. Er erscheint in Suchergebnissen.', 'description' => 'Hier entsteht eine neue Webpräsenz. Diese Seite wird gerade eingerichtet.',
'blocks' => [ 'blocks' => [
self::section('start-hero', [ [
self::heading('start-heading', $siteName, 1), 'id' => 'start-hero',
self::text('start-intro', 'Diese Seite ist der Anfang. Melde dich an, öffne sie im Editor und ersetze diesen Text durch deinen eigenen.'), 'type' => 'section',
], 'mint'), 'layout' => ['tone' => 'mint', 'width' => 'narrow'],
self::section('start-body', [ 'style' => [
self::heading('start-next-heading', 'Die nächsten Schritte', 2), // Eine Seite ohne Inhalt: der Abschnitt füllt den Bildschirm und stellt das
self::text('start-next', "Impressum und Datenschutzerklärung sind bereits angelegt und müssen noch ausgefüllt werden – die Pflichtangaben sind darin markiert.\n\nUnter „Design“ legst du Farben, Schriften und die Breite fest, unter „Menüs“ die Navigation, unter „Einstellungen“ den Namen und die Angaben zum Unternehmen."), // Wenige, das dasteht, in die Mitte, statt es oben in eine Ecke zu setzen.
], 'paper', 'narrow'), 'desktop' => [
'display' => 'flex', 'alignItems' => 'center', 'justifyContent' => 'center',
'minHeight' => '70vh', 'textAlign' => 'center',
'padding' => ['top' => '96px', 'bottom' => '96px'],
'gradientFrom' => '@paper', 'gradientTo' => '@mint', 'gradientAngle' => 165,
],
'mobile' => ['minHeight' => 'auto', 'padding' => ['top' => '64px', 'bottom' => '64px']],
],
'children' => [
[
'id' => 'start-logo',
'type' => 'image',
'props' => ['src' => '/assets/kantsites-logo.svg', 'alt' => 'KantSites', 'decorative' => false, 'width' => 128, 'height' => 128],
'style' => ['desktop' => ['maxWidth' => '86px', 'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '30px']]],
],
[
'id' => 'start-label',
'type' => 'text',
'props' => ['text' => 'In Arbeit'],
'style' => ['desktop' => [
'fontSize' => '13px', 'fontWeight' => '700', 'letterSpacing' => '2px',
'textTransform' => 'uppercase', 'color' => '@accent', 'margin' => ['bottom' => '16px'],
]],
],
self::heading('start-heading', $siteName, 1),
[
'id' => 'start-intro',
'type' => 'text',
'props' => ['text' => 'Hier entsteht eine neue Webpräsenz.'],
'style' => ['desktop' => [
'fontSize' => '21px', 'maxWidth' => '38ch',
'margin' => ['left' => 'auto', 'right' => 'auto', 'bottom' => '0px'],
]],
],
[
'id' => 'start-rule',
'type' => 'divider',
'style' => ['desktop' => ['width' => '72px', 'margin' => ['top' => '40px', 'right' => 'auto', 'bottom' => '22px', 'left' => 'auto']]],
],
[
'id' => 'start-note',
'type' => 'text',
'props' => ['text' => 'Erstellt mit KantSites'],
'style' => ['desktop' => ['fontSize' => '14px', 'letterSpacing' => '0.4px', 'margin' => ['bottom' => '0px']]],
],
],
],
], ],
]; ];
} }
@@ -164,6 +205,13 @@ final class DemoContent
return $areas; return $areas;
} }
/**
* The header and footer a fresh installation starts with. They are ordinary block documents:
* everything in them can be changed, moved or removed in the editor. No main heading, because
* every page brings its own.
*
* @return array<string, array{name: string, document: array}>
*/
public static function areas(): array public static function areas(): array
{ {
$row = static fn (string $id, array $children): array => [ $row = static fn (string $id, array $children): array => [
+1 -1
View File
@@ -98,7 +98,7 @@ final class InstallFlowTest extends TestCase
$home = $this->request('/'); $home = $this->request('/');
self::assertSame(200, $home->getStatusCode()); self::assertSame(200, $home->getStatusCode());
self::assertStringContainsString('Beispiel GmbH', $home->getContent()); self::assertStringContainsString('Beispiel GmbH', $home->getContent());
self::assertStringContainsString('Diese Seite ist der Anfang.', $home->getContent()); self::assertStringContainsString('Hier entsteht eine neue Webpräsenz.', $home->getContent());
// The public page is still a public page: no script, and the header carries the name. // The public page is still a public page: no script, and the header carries the name.
self::assertStringContainsString("script-src 'none'", (string) $home->headers->get('Content-Security-Policy')); self::assertStringContainsString("script-src 'none'", (string) $home->headers->get('Content-Security-Policy'));
self::assertSame(200, $this->request('/impressum')->getStatusCode()); self::assertSame(200, $this->request('/impressum')->getStatusCode());