From d470e03caf1044b32edea3e105a7d1f09812c0a3 Mon Sep 17 00:00:00 2001 From: Dotty Dotter Date: Thu, 7 May 2026 13:32:44 +0200 Subject: [PATCH] =?UTF-8?q?feat:=20Scorecard=20Portrait=20redesign=20?= =?UTF-8?q?=E2=80=94=20Matrix=20mit=20Achsen-Labels,=20flex-grow=20gegen?= =?UTF-8?q?=20Slack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit User-Feedback nach Browser-Inspektion: 'Bei mir sieht das immer noch nicht so aus' — und tatsächlich, das vorherige Layout hatte zwei sichtbare Probleme: 1. justify-content: space-between auf portrait-body verteilte den Slack-Raum nicht symmetrisch, sondern haeufte ihn unten zwischen Matrix-Block und Begruendung an. Folge: ~270 px Luecke zwischen diesen Sektionen. 2. Die Matrix war 'stilisiert' nur in Form (5×5 Farb-Grid) — aber ohne Achsen-Beschriftungen muessten Buerger:innen wissen was A1, B2 etc. bedeuten. Kommt nicht an. Redesign: - Layout-Strategie: portrait-matrix-block bekommt flex-grow:1 und absorbiert allen verbleibenden vertikalen Platz; Matrix bleibt zentriert. Andere Sektionen sitzen in natuerlicher Hoehe oben/ unten. Kein space-between. - Matrix stilisiert mit Achsen: · Spalten-Header: Wuerde / Solidaritaet / Nachhaltigkeit / Gerechtigkeit / Transparenz (Brand-Color, Mono-Caps) · Zeilen-Header: A·Lieferant:innen, B·Finanzen, C·Verwaltung, D·Buerger:innen, E·Gesellschaft & Natur · Cells in 88×88 Quadraten, gap 4 px · Legende horizontal unter der Matrix statt seitlich - Begruendung: line-clamp 5, sitzt am Boden, mit Trennlinie und Sublabel. - Cache-Control: no-store auf /v2/scorecard, damit Browser nach Layout-Aenderungen nicht die alte HTML-Variante zeigt. Co-Authored-By: Claude Opus 4.7 (1M context) --- app/main.py | 6 +- app/templates/v2/screens/scorecard.html | 150 ++++++++++++++---------- 2 files changed, 90 insertions(+), 66 deletions(-) diff --git a/app/main.py b/app/main.py index bcaee73..606a0c9 100644 --- a/app/main.py +++ b/app/main.py @@ -3515,7 +3515,7 @@ async def scorecard_template( } width, height = dimensions.get(format, dimensions["og"]) - return templates.TemplateResponse("v2/screens/scorecard.html", { + response = templates.TemplateResponse("v2/screens/scorecard.html", { "request": request, "assessment": assessment, "bundesland": bundesland, @@ -3526,6 +3526,10 @@ async def scorecard_template( "width": width, "height": height, }) + # No-cache fuer die Live-Preview, sonst zeigt der Browser nach Layout- + # Aenderungen die alte HTML-Variante an. + response.headers["Cache-Control"] = "no-store, no-cache, must-revalidate" + return response async def _render_scorecard_pdf( diff --git a/app/templates/v2/screens/scorecard.html b/app/templates/v2/screens/scorecard.html index 836f98d..cc0bbcc 100644 --- a/app/templates/v2/screens/scorecard.html +++ b/app/templates/v2/screens/scorecard.html @@ -154,29 +154,17 @@ /* ── portrait Layout — ausbalancierte vertikale Komposition. - Sektions-Hierarchie (top → bottom): - 1. Header-Bar (kicker + meta) ~50 px - 2. Title (kompakt, 3 Zeilen max) ~150 px - 3. Fraktionen-Pills ~40 px - 4. Score-Hero (full-width Farbband) ~280 px - 5. Matrix-Block (Grid + Legende) ~480 px - 6. Begründung (4 Zeilen) ~140 px - 7. Footer (absolut, ~50 px vom unteren Rand) - Summe ~1190 + Padding 58 + Gaps ~30 = 1280 / 1350 → ~70 px Slack, - gleichmaessig per justify-content space-between auf die fuenf - inneren Sektionen verteilt. - - Visuelle Hierarchie: Score-Hero ist optisch prominent (groesster - Farbblock + groesste Zahl), Matrix als datenreiches Zentrum, Title - deskriptiv aber zurueckhaltend, Begruendung als Ausklang. */ + Statt justify-content: space-between (verteilt Slack-Raum unkontrol- + liert) absorbiert .portrait-matrix-block per flex-grow:1 die ueber- + schuessige Hoehe. Matrix bleibt visuell zentriert, andere Sektionen + in natuerlicher Groesse oben/unten. */ .portrait-body { flex: 1; display: flex; flex-direction: column; - justify-content: space-between; - gap: 18px; - margin-top: 18px; - padding-bottom: 28px; + gap: 14px; + margin-top: 16px; + padding-bottom: 32px; } .portrait-title { @@ -253,65 +241,83 @@ line-height: 1.08; } - /* ── Matrix-Block: Grid links, Legende rechts, vertikal zentriert ── */ + /* ── Matrix-Block: Grid mit beschrifteten Achsen, vertikal zentriert. + flex-grow:1 absorbiert allen verbleibenden Slack der Karte. */ .portrait-matrix-block { + flex-grow: 1; display: flex; + flex-direction: column; align-items: center; - gap: 28px; + justify-content: center; + gap: 12px; } - .portrait-matrix { + .portrait-matrix-grid { + /* 5 Werte als Spalten + 1 Spalte fuer die Zeilen-Labels */ display: grid; - grid-template-columns: repeat(5, 1fr); - grid-template-rows: repeat(5, 1fr); + grid-template-columns: 130px repeat(5, 88px); + grid-template-rows: 36px repeat(5, 88px); gap: 4px; - width: 480px; - height: 480px; - flex-shrink: 0; - /* Kein weisser Frame — der Karten-Hintergrund (Gradient) wird - durch die gap-Zwischenraeume sichtbar, die Cells stehen klar - als Farbflaechen heraus. */ + align-items: stretch; } - .portrait-matrix-legend { - flex: 1; + .portrait-matrix-grid .col-label { font-family: 'Source Code Pro', monospace; - font-size: 12pt; - color: #444; - line-height: 1.55; - } - .portrait-matrix-legend .l-title { + font-size: 9.5pt; font-weight: 700; - text-transform: uppercase; color: #009DA5; - margin-bottom: 14px; - letter-spacing: 0.08em; - font-size: 12pt; + text-transform: uppercase; + letter-spacing: 0.04em; + display: flex; + align-items: end; + justify-content: center; + padding-bottom: 4px; + text-align: center; + line-height: 1.1; } - .portrait-matrix-legend ul { - list-style: none; - padding: 0; - margin: 0; - } - .portrait-matrix-legend li { + .portrait-matrix-grid .row-label { + font-family: 'Source Code Pro', monospace; + font-size: 10pt; + font-weight: 700; + color: #009DA5; + text-transform: uppercase; + letter-spacing: 0.04em; display: flex; align-items: center; - gap: 10px; - margin-bottom: 7px; + padding-right: 8px; + line-height: 1.15; } - .portrait-matrix-legend .swatch { - width: 22px; height: 22px; border-radius: 2px; flex-shrink: 0; - border: 1px solid rgba(0,0,0,0.08); + .portrait-matrix-grid .corner { + /* leere Ecke oben links */ + } + .portrait-legend-row { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px 18px; + font-family: 'Source Code Pro', monospace; + font-size: 10.5pt; + color: #444; + margin-top: 4px; + } + .portrait-legend-row .legend-item { + display: inline-flex; + align-items: center; + gap: 6px; + } + .portrait-legend-row .swatch { + width: 18px; height: 18px; border-radius: 2px; flex-shrink: 0; } .portrait-summary { font-size: 13pt; - line-height: 1.55; + line-height: 1.5; color: #333; display: -webkit-box; - -webkit-line-clamp: 4; + -webkit-line-clamp: 5; -webkit-box-orient: vertical; overflow: hidden; border-top: 1px solid rgba(0,0,0,0.08); padding-top: 14px; + flex-shrink: 0; } .portrait-summary-label { display: block; @@ -369,8 +375,25 @@
-
- {% for r in ['A','B','C','D','E'] %} +
+ {# Top row: leere Ecke + 5 Werte-Spalten-Labels #} +
+
Würde
+
Solidari­tät
+
Nach­haltig­keit
+
Gerech­tigkeit
+
Trans­parenz
+ + {# Folgende 5 Zeilen: Beruehrungsgruppen-Label + 5 Cells #} + {% set rows = [ + ('A', 'Lieferant:­innen'), + ('B', 'Finanzen'), + ('C', 'Verwal­tung'), + ('D', 'Bürger:­innen'), + ('E', 'Gesell­schaft & Natur'), + ] %} + {% for r, r_label in rows %} +
{{ r }} · {{ r_label }}
{% for c in ['1','2','3','4','5'] %} {% set cell = matrix_lookup.get(r ~ c, {}) %} {% set rt = cell.get('rating', 0) %} @@ -378,15 +401,12 @@ {% endfor %} {% endfor %}
-
-
GWÖ-Matrix 5×5
-
    -
  • ++ stark fördernd
  • -
  • + fördernd
  • -
  • ○ neutral
  • -
  • − widersprechend
  • -
  • −− stark widersprechend
  • -
+
+ ++ stark fördernd + + fördernd + ○ neutral + − widersprechend + −− stark widerspr.