From 8e4333b3acabcd954f369f3129c3aa574ee788d4 Mon Sep 17 00:00:00 2001 From: tleininger Date: Thu, 1 Oct 2026 18:24:34 +0200 Subject: [PATCH 1/4] Keep design blocks in the reading column, rework key figures and tiles, drop focus ring on tabindex=-1 Co-Authored-By: Claude Opus 5.5 --- Elternbeirat.Web/wwwroot/app.css | 109 +++++++++++++++++++++++++++---- 1 file changed, 96 insertions(+), 13 deletions(-) diff --git a/Elternbeirat.Web/wwwroot/app.css b/Elternbeirat.Web/wwwroot/app.css index 540f44b..21ef07d 100644 --- a/Elternbeirat.Web/wwwroot/app.css +++ b/Elternbeirat.Web/wwwroot/app.css @@ -170,6 +170,7 @@ light button text ................. 8.1 / 9.7 (hover 7.2 / 8.3) white on calendar band ............ 5.2 / 6.4 avatar blue / green ............... 7.2 / 8.6 5.9 / 6.7 + key figure / caption on card ...... 5.4 / 7.5 5.0 / 6.9 focus ring on page / card ......... 7.5 / 11.0 8.1 / 9.4 white ring on header / footer ..... 4.9 / 5.5 11.4 / 16.6 green markers on card (graphic) ... 3.8 / 7.5 @@ -346,8 +347,12 @@ h1:focus { links (and with them every card, which is one big link), buttons, the of each disclosure, form fields and anything made focusable with tabindex. Dark brand surfaces (header, hero, footer) switch the colour to - --color-focus-inverse. */ -:is(a, button, summary, input, select, textarea, [tabindex]):focus-visible { + --color-focus-inverse. + tabindex="-1" is left out: such an element is never reached with Tab, only + focused by code -- the h1 by FocusOnNavigate on every page load,
by the + skip link. Chromium counts that as :focus-visible, and since this rule outweighs + h1:focus / main:focus below, the page title would otherwise get a ring. */ +:is(a, button, summary, input, select, textarea, [tabindex]:not([tabindex="-1"])):focus-visible { outline: 3px solid var(--color-focus); outline-offset: 2px; } @@ -902,24 +907,39 @@ main:focus { mask: var(--icon-info) center / contain no-repeat; } -/* ::: kacheln and ::: kennzahlen -- a list laid out as a grid of cards. auto-fill - with a minimum width gives as many columns as fit (one on a phone) without any - breakpoint. */ +/* ::: kacheln and ::: kennzahlen -- a list laid out as a grid of cards. They keep + the 42rem reading column like the text and the other boxes: one calm left and + right edge down the page instead of blocks of three different widths. */ .markdown-body :is(.kacheln, .kennzahlen) > ul { display: grid; gap: var(--space-3); - max-width: none; + box-sizing: border-box; + max-width: 42rem; margin: 0 0 var(--space-4); padding: 0; list-style: none; } +/* Kacheln: auto-fit with a 15rem minimum gives exactly two columns in the 42rem + (three would need 46rem), so four tiles sit as 2 x 2. Below ~31rem it drops to + one column -- two would squeeze a long word like "Chancengerechtigkeit" out of + its card. min() keeps a single card from overflowing a very narrow screen. */ .markdown-body .kacheln > ul { - grid-template-columns: repeat(auto-fill, minmax(14rem, 1fr)); + grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr)); +} + +/* Kennzahlen: one row of equal columns where there is room. auto-fill used to + leave empty tracks on the right on a wide screen and one figure alone in a second + row on a phone. A narrow column switches to stacked rows instead (below). The + block is its own container, so the switch follows the width the figures actually + get, not the screen. */ +.markdown-body .kennzahlen { + container-type: inline-size; } .markdown-body .kennzahlen > ul { - grid-template-columns: repeat(auto-fill, minmax(10rem, 1fr)); + grid-auto-flow: column; + grid-auto-columns: 1fr; } /* The grid gap does the spacing; the list's own item spacing would only push the @@ -943,24 +963,87 @@ main:focus { color: var(--color-heading); } +/* On a phone the tiles keep their card but tighten up, so four of them stacked + do not turn into a long scroll. */ +@media (max-width: 40rem) { + .markdown-body .kacheln > ul { + gap: var(--space-2); + } + + .markdown-body .kacheln li { + padding: var(--space-2) var(--space-3); + } + + .markdown-body .kacheln li > strong:first-child { + margin-bottom: 0; + } +} + /* Kennzahlen: "- **seit 1975** Gründung" -- the bold part becomes a big figure, - the rest a small caption beneath. Brand blue for the figure, not the green: the - green does not reach 3:1 on its own tint, the blue does. */ + the rest a small caption beneath. A white card with a green stroke on top, not + a green wash: a ::: hinweis often follows right below, and two green tints in a + row ran together into one green patch. Brand blue for the figure: 5.4:1 on the + card (5.0 dark), where the green would not reach 3:1. */ .markdown-body .kennzahlen li { - padding: var(--space-4) var(--space-3); + padding: var(--space-4) var(--space-2); + border-top: 4px solid var(--color-accent); border-radius: var(--radius); - background: var(--color-accent-tint); + background: var(--color-bg); + box-shadow: var(--shadow); color: var(--color-text-soft); text-align: center; } +/* The figure scales with the block (cqi, not vw): 2rem in the full 42rem, a little + less just above the switch to rows. nowrap: "seit 1975" split over two lines + reads as two figures. */ .markdown-body .kennzahlen li > strong:first-child { display: block; color: var(--color-brand); - font-size: 2rem; + font-size: clamp(1.5rem, 4.75cqi, 2rem); font-weight: var(--font-weight-heading); letter-spacing: var(--letter-spacing-heading); line-height: 1.15; + white-space: nowrap; +} + +/* Below 30rem three columns get under 10rem each: the figure no longer fits on one + line and a caption like "im Einsatz für die IGMH" stacks up word by word. So the + figures become rows of one card -- figure left, caption beside it, a thin line + between rows. Subgrid gives every row the same figure column, so the captions + line up whatever the figure's length. */ +@container (max-width: 30rem) { + .markdown-body .kennzahlen > ul { + grid-auto-flow: row; + grid-template-columns: max-content 1fr; + gap: 0; + border-top: 4px solid var(--color-accent); + border-radius: var(--radius); + background: var(--color-bg); + box-shadow: var(--shadow); + } + + .markdown-body .kennzahlen li { + display: grid; + grid-column: 1 / -1; + grid-template-columns: subgrid; + column-gap: var(--space-3); + align-items: baseline; + padding: var(--space-2) var(--space-3); + border-top: none; + border-radius: 0; + background: none; + box-shadow: none; + text-align: left; + } + + .markdown-body .kennzahlen li + li { + border-top: 1px solid var(--color-border); + } + + .markdown-body .kennzahlen li > strong:first-child { + font-size: 1.35rem; + } } /* ::: team -- one person card per list item (Markdown.cs/TeamMember.cs build the -- 2.54.0 From 7d09233ca2df44d49db85e07ba453d3dee1228d6 Mon Sep 17 00:00:00 2001 From: tleininger Date: Thu, 1 Oct 2026 18:28:06 +0200 Subject: [PATCH 2/4] Keep mobile menu links as pills with a full-width active stroke --- Elternbeirat.Web/wwwroot/app.css | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Elternbeirat.Web/wwwroot/app.css b/Elternbeirat.Web/wwwroot/app.css index 21ef07d..30e0259 100644 --- a/Elternbeirat.Web/wwwroot/app.css +++ b/Elternbeirat.Web/wwwroot/app.css @@ -521,19 +521,18 @@ main:focus { background: var(--color-accent); } -/* Mobile: the stacked links in the open menu become full-width rows with a - rounded highlight instead of pills, and the green stroke shrinks to a short - mark at the start, so a long row does not get a long line. Same global - placement, same NavLink reason as above. */ +/* Mobile: the open menu stacks the links centred, each as wide as its text -- so + they stay the same pills as on the desktop, with the stroke under the whole word. + Only taller, for a comfortable tap target; the stroke moves up with the extra + padding so it still sits right under the text, not at the pill's edge. Same + global placement, same NavLink reason as above. */ @media (max-width: 40rem) { .main-nav a { padding: 0.6rem var(--space-3); - border-radius: var(--radius-sm); } .main-nav a.active::after { - right: auto; - width: 1.5rem; + bottom: 0.55rem; } } -- 2.54.0 From a847507b46b5ba5e57d8b252d119cf189a3bf92c Mon Sep 17 00:00:00 2001 From: tleininger Date: Thu, 1 Oct 2026 18:48:55 +0200 Subject: [PATCH 3/4] Centre one 42rem reading column on content, post, event and FAQ pages --- .../Features/Events/EventList.razor.css | 17 ++++++++++++- .../Features/Faqs/FaqList.razor.css | 17 +++++++++++++ .../Features/Pages/ContentPage.razor.css | 11 +++++++++ .../Features/Posts/PostDetail.razor.css | 24 ++++++++++++------- .../Features/Posts/PostList.razor.css | 17 +++++++++++++ Elternbeirat.Web/wwwroot/app.css | 6 ++++- 6 files changed, 82 insertions(+), 10 deletions(-) create mode 100644 Elternbeirat.Web/Features/Faqs/FaqList.razor.css create mode 100644 Elternbeirat.Web/Features/Pages/ContentPage.razor.css diff --git a/Elternbeirat.Web/Features/Events/EventList.razor.css b/Elternbeirat.Web/Features/Events/EventList.razor.css index 2f4f073..a437c84 100644 --- a/Elternbeirat.Web/Features/Events/EventList.razor.css +++ b/Elternbeirat.Web/Features/Events/EventList.razor.css @@ -1,14 +1,29 @@ /* The card grid (.event-list) is shared with the home-page embed and lives in the global app.css; the card itself is EventCard. Only the page's own parts are here. */ +/* Same centred 42rem reading column as a content page (ContentPage.razor.css), so + every page of the site shares one column under the header. */ +h1, +h2, +article, +p, +.event-list, +.past-events { + max-width: 42rem; + margin-inline: auto; +} + +/* auto, not 0: this rule outweighs the column rule above and would otherwise pull + the button back to the left edge of the page. */ .events-subscribe { - margin: var(--space-4) 0; + margin: var(--space-4) auto; } /* Past events: one folded-up card. Opened, a compact list grouped by year, deliberately quieter than the upcoming cards above. The open/close triangle is the shared .disclosure rule in app.css, the same as on the FAQ topics. */ .past-events { + box-sizing: border-box; /* padding inside the 42rem, flush with the text */ margin-top: var(--space-5); padding: var(--space-3) var(--space-4); border: 1px solid var(--color-border); diff --git a/Elternbeirat.Web/Features/Faqs/FaqList.razor.css b/Elternbeirat.Web/Features/Faqs/FaqList.razor.css new file mode 100644 index 0000000..55e4009 --- /dev/null +++ b/Elternbeirat.Web/Features/Faqs/FaqList.razor.css @@ -0,0 +1,17 @@ +/* Same centred 42rem reading column as a content page (ContentPage.razor.css), so + every page of the site shares one column under the header. The topic cards and + the questions inside them live in the global app.css. */ +h1, +article, +p, +.faq-topic { + box-sizing: border-box; /* padding inside the 42rem, flush with the text */ + max-width: 42rem; + margin-inline: auto; +} + +/* The intro's last paragraph drops its margin (app.css), so the first card would + sit right under the text; this gives it the same air as between sections. */ +article { + margin-bottom: var(--space-5); +} diff --git a/Elternbeirat.Web/Features/Pages/ContentPage.razor.css b/Elternbeirat.Web/Features/Pages/ContentPage.razor.css new file mode 100644 index 0000000..4297684 --- /dev/null +++ b/Elternbeirat.Web/Features/Pages/ContentPage.razor.css @@ -0,0 +1,11 @@ +/* A content page is one reading column: its text, boxes and card grids all stop at + 42rem (app.css). Left-aligned in the 72rem layout that column left a wide empty + strip on the right of a desktop screen; centred, the empty space splits evenly and + the page sits balanced under the header. Title and body share the column, so they + keep one left edge. The children inside keep margin 0 and simply fill it. */ +h1, +article, +p { + max-width: 42rem; + margin-inline: auto; +} diff --git a/Elternbeirat.Web/Features/Posts/PostDetail.razor.css b/Elternbeirat.Web/Features/Posts/PostDetail.razor.css index cbf12b6..17c0ba8 100644 --- a/Elternbeirat.Web/Features/Posts/PostDetail.razor.css +++ b/Elternbeirat.Web/Features/Posts/PostDetail.razor.css @@ -1,13 +1,21 @@ -/* The article: a reading column of about 68 characters with generous leading, so a - long post reads like a page, not like a form. Everything inside follows this - width -- the global 42rem cap on paragraphs, lists and boxes is lifted here, or - the text would stop short of the column it sits in. */ +/* The article: the site's centred 42rem reading column (as on a content page, + ContentPage.razor.css) with generous leading, so a long post reads like a page, + not like a form. Everything inside follows this width -- the global 42rem cap on + paragraphs, lists and boxes is lifted here, so nothing stops short of the column + (the post's own leading makes the text a touch wider than elsewhere). */ .post { - max-width: 68ch; - margin-top: var(--space-4); + max-width: 42rem; + margin: var(--space-4) auto 0; line-height: 1.75; } +/* The back link is an inline button, which margin: auto cannot centre. A left + margin of half the free space puts it on the column's left edge instead, and + max() keeps it at 0 once the screen is narrower than the column. */ +.btn-quiet { + margin-left: max(0px, (100% - 42rem) / 2); +} + .post ::deep :is(p, ul, ol, blockquote, .aufruf, .hinweis) { max-width: none; } @@ -46,8 +54,8 @@ display: grid; grid-template-columns: 1fr 1fr; gap: var(--space-3); - max-width: 68ch; - margin-top: var(--space-6); + max-width: 42rem; + margin: var(--space-6) auto 0; padding-top: var(--space-4); border-top: 1px solid var(--color-border); } diff --git a/Elternbeirat.Web/Features/Posts/PostList.razor.css b/Elternbeirat.Web/Features/Posts/PostList.razor.css index b7e0b0a..a880788 100644 --- a/Elternbeirat.Web/Features/Posts/PostList.razor.css +++ b/Elternbeirat.Web/Features/Posts/PostList.razor.css @@ -10,3 +10,20 @@ grid-column: 1 / -1; } } + +/* Same centred 42rem reading column as a content page (ContentPage.razor.css), so + every page of the site shares one column under the header. Two cards still fit + side by side in it. */ +h1, +article, +p, +.post-list { + max-width: 42rem; + margin-inline: auto; +} + +/* The intro's last paragraph drops its margin (app.css), so the first card would + sit right under the text; this gives it the same air as between sections. */ +article { + margin-bottom: var(--space-5); +} diff --git a/Elternbeirat.Web/wwwroot/app.css b/Elternbeirat.Web/wwwroot/app.css index 30e0259..7da3ef1 100644 --- a/Elternbeirat.Web/wwwroot/app.css +++ b/Elternbeirat.Web/wwwroot/app.css @@ -1050,13 +1050,17 @@ main:focus { a normal heading; every group is a block of its own. One raster for all of them, so the cards line up across groups: a group of one person gets the same card width as a group of six, not a stretched one. 17rem fits a long double name. + Like every other block it stays in the 42rem reading column, so the cards share + the edges of the text above and below them; that leaves room for two columns + (three would need 52.5rem), one on a phone. The cards of a row share one height (the grid's default stretch), so a row reads as a calm band; the card itself keeps its content at the top (align-content). */ .markdown-body .team > ul { display: grid; grid-template-columns: repeat(auto-fill, minmax(17rem, 1fr)); gap: var(--space-3); - max-width: none; + box-sizing: border-box; + max-width: 42rem; margin: 0 0 var(--space-4); padding: 0; list-style: none; -- 2.54.0 From ee69892058bb6f2d21f4fb98ba425737b5d0cab8 Mon Sep 17 00:00:00 2001 From: tleininger Date: Thu, 1 Oct 2026 19:06:54 +0200 Subject: [PATCH 4/4] fix buttons --- Elternbeirat.Web/Features/Home/Home.razor.css | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Elternbeirat.Web/Features/Home/Home.razor.css b/Elternbeirat.Web/Features/Home/Home.razor.css index d922e29..c411eb2 100644 --- a/Elternbeirat.Web/Features/Home/Home.razor.css +++ b/Elternbeirat.Web/Features/Home/Home.razor.css @@ -93,6 +93,26 @@ outline-color: var(--color-focus-inverse); } +/* On a phone the buttons used to wrap onto a row each: two wide blocks stacked, + far too heavy for the top of the page. Instead they share one row in equal + columns -- the same buttons as on the desktop, just side by side and a little + tighter. Short labels ("Termine", "Kontakt") fit on one line; a longer one + wraps inside its button (centred) rather than pushing the other off the row. */ +@media (max-width: 40rem) { + .hero-actions { + display: grid; + grid-auto-flow: column; + grid-auto-columns: 1fr; + gap: var(--space-2); + } + + .hero .btn { + justify-content: center; + padding: var(--space-2) var(--space-3); + text-align: center; + } +} + /* --- Next event --------------------------------------------------------------- A card on the gradient: the regular card surface, so its text uses the normal tokens and adapts to dark mode by itself. The whole card is one link to the -- 2.54.0