feat: Scorecard Portrait redesign — Matrix mit Achsen-Labels, flex-grow gegen Slack
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) <noreply@anthropic.com>
This commit is contained in:
parent
099fbd0fb0
commit
d470e03caf
@ -3515,7 +3515,7 @@ async def scorecard_template(
|
|||||||
}
|
}
|
||||||
width, height = dimensions.get(format, dimensions["og"])
|
width, height = dimensions.get(format, dimensions["og"])
|
||||||
|
|
||||||
return templates.TemplateResponse("v2/screens/scorecard.html", {
|
response = templates.TemplateResponse("v2/screens/scorecard.html", {
|
||||||
"request": request,
|
"request": request,
|
||||||
"assessment": assessment,
|
"assessment": assessment,
|
||||||
"bundesland": bundesland,
|
"bundesland": bundesland,
|
||||||
@ -3526,6 +3526,10 @@ async def scorecard_template(
|
|||||||
"width": width,
|
"width": width,
|
||||||
"height": height,
|
"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(
|
async def _render_scorecard_pdf(
|
||||||
|
|||||||
@ -154,29 +154,17 @@
|
|||||||
|
|
||||||
/* ── portrait Layout — ausbalancierte vertikale Komposition.
|
/* ── portrait Layout — ausbalancierte vertikale Komposition.
|
||||||
|
|
||||||
Sektions-Hierarchie (top → bottom):
|
Statt justify-content: space-between (verteilt Slack-Raum unkontrol-
|
||||||
1. Header-Bar (kicker + meta) ~50 px
|
liert) absorbiert .portrait-matrix-block per flex-grow:1 die ueber-
|
||||||
2. Title (kompakt, 3 Zeilen max) ~150 px
|
schuessige Hoehe. Matrix bleibt visuell zentriert, andere Sektionen
|
||||||
3. Fraktionen-Pills ~40 px
|
in natuerlicher Groesse oben/unten. */
|
||||||
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. */
|
|
||||||
.portrait-body {
|
.portrait-body {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
justify-content: space-between;
|
gap: 14px;
|
||||||
gap: 18px;
|
margin-top: 16px;
|
||||||
margin-top: 18px;
|
padding-bottom: 32px;
|
||||||
padding-bottom: 28px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.portrait-title {
|
.portrait-title {
|
||||||
@ -253,65 +241,83 @@
|
|||||||
line-height: 1.08;
|
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 {
|
.portrait-matrix-block {
|
||||||
|
flex-grow: 1;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
align-items: center;
|
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;
|
display: grid;
|
||||||
grid-template-columns: repeat(5, 1fr);
|
grid-template-columns: 130px repeat(5, 88px);
|
||||||
grid-template-rows: repeat(5, 1fr);
|
grid-template-rows: 36px repeat(5, 88px);
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
width: 480px;
|
align-items: stretch;
|
||||||
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. */
|
|
||||||
}
|
}
|
||||||
.portrait-matrix-legend {
|
.portrait-matrix-grid .col-label {
|
||||||
flex: 1;
|
|
||||||
font-family: 'Source Code Pro', monospace;
|
font-family: 'Source Code Pro', monospace;
|
||||||
font-size: 12pt;
|
font-size: 9.5pt;
|
||||||
color: #444;
|
|
||||||
line-height: 1.55;
|
|
||||||
}
|
|
||||||
.portrait-matrix-legend .l-title {
|
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
text-transform: uppercase;
|
|
||||||
color: #009DA5;
|
color: #009DA5;
|
||||||
margin-bottom: 14px;
|
text-transform: uppercase;
|
||||||
letter-spacing: 0.08em;
|
letter-spacing: 0.04em;
|
||||||
font-size: 12pt;
|
display: flex;
|
||||||
|
align-items: end;
|
||||||
|
justify-content: center;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 1.1;
|
||||||
}
|
}
|
||||||
.portrait-matrix-legend ul {
|
.portrait-matrix-grid .row-label {
|
||||||
list-style: none;
|
font-family: 'Source Code Pro', monospace;
|
||||||
padding: 0;
|
font-size: 10pt;
|
||||||
margin: 0;
|
font-weight: 700;
|
||||||
}
|
color: #009DA5;
|
||||||
.portrait-matrix-legend li {
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 10px;
|
padding-right: 8px;
|
||||||
margin-bottom: 7px;
|
line-height: 1.15;
|
||||||
}
|
}
|
||||||
.portrait-matrix-legend .swatch {
|
.portrait-matrix-grid .corner {
|
||||||
width: 22px; height: 22px; border-radius: 2px; flex-shrink: 0;
|
/* leere Ecke oben links */
|
||||||
border: 1px solid rgba(0,0,0,0.08);
|
}
|
||||||
|
.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 {
|
.portrait-summary {
|
||||||
font-size: 13pt;
|
font-size: 13pt;
|
||||||
line-height: 1.55;
|
line-height: 1.5;
|
||||||
color: #333;
|
color: #333;
|
||||||
display: -webkit-box;
|
display: -webkit-box;
|
||||||
-webkit-line-clamp: 4;
|
-webkit-line-clamp: 5;
|
||||||
-webkit-box-orient: vertical;
|
-webkit-box-orient: vertical;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
border-top: 1px solid rgba(0,0,0,0.08);
|
border-top: 1px solid rgba(0,0,0,0.08);
|
||||||
padding-top: 14px;
|
padding-top: 14px;
|
||||||
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
.portrait-summary-label {
|
.portrait-summary-label {
|
||||||
display: block;
|
display: block;
|
||||||
@ -369,8 +375,25 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="portrait-matrix-block">
|
<div class="portrait-matrix-block">
|
||||||
<div class="portrait-matrix matrix">
|
<div class="portrait-matrix-grid matrix">
|
||||||
{% for r in ['A','B','C','D','E'] %}
|
{# Top row: leere Ecke + 5 Werte-Spalten-Labels #}
|
||||||
|
<div class="corner"></div>
|
||||||
|
<div class="col-label">Würde</div>
|
||||||
|
<div class="col-label">Solidarität</div>
|
||||||
|
<div class="col-label">Nachhaltigkeit</div>
|
||||||
|
<div class="col-label">Gerechtigkeit</div>
|
||||||
|
<div class="col-label">Transparenz</div>
|
||||||
|
|
||||||
|
{# Folgende 5 Zeilen: Beruehrungsgruppen-Label + 5 Cells #}
|
||||||
|
{% set rows = [
|
||||||
|
('A', 'Lieferant:innen'),
|
||||||
|
('B', 'Finanzen'),
|
||||||
|
('C', 'Verwaltung'),
|
||||||
|
('D', 'Bürger:innen'),
|
||||||
|
('E', 'Gesellschaft & Natur'),
|
||||||
|
] %}
|
||||||
|
{% for r, r_label in rows %}
|
||||||
|
<div class="row-label">{{ r }} · {{ r_label }}</div>
|
||||||
{% for c in ['1','2','3','4','5'] %}
|
{% for c in ['1','2','3','4','5'] %}
|
||||||
{% set cell = matrix_lookup.get(r ~ c, {}) %}
|
{% set cell = matrix_lookup.get(r ~ c, {}) %}
|
||||||
{% set rt = cell.get('rating', 0) %}
|
{% set rt = cell.get('rating', 0) %}
|
||||||
@ -378,15 +401,12 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
<div class="portrait-matrix-legend">
|
<div class="portrait-legend-row">
|
||||||
<div class="l-title">GWÖ-Matrix 5×5</div>
|
<span class="legend-item"><span class="swatch" style="background:#889E33;"></span>++ stark fördernd</span>
|
||||||
<ul>
|
<span class="legend-item"><span class="swatch" style="background:#cddaa1;"></span>+ fördernd</span>
|
||||||
<li><span class="swatch" style="background:#889E33;"></span>++ stark fördernd</li>
|
<span class="legend-item"><span class="swatch" style="background:#d8d8d2;"></span>○ neutral</span>
|
||||||
<li><span class="swatch" style="background:#cddaa1;"></span>+ fördernd</li>
|
<span class="legend-item"><span class="swatch" style="background:#efc9c3;"></span>− widersprechend</span>
|
||||||
<li><span class="swatch" style="background:#d8d8d2;"></span>○ neutral</li>
|
<span class="legend-item"><span class="swatch" style="background:#9A2A2A;"></span>−− stark widerspr.</span>
|
||||||
<li><span class="swatch" style="background:#efc9c3;"></span>− widersprechend</li>
|
|
||||||
<li><span class="swatch" style="background:#9A2A2A;"></span>−− stark widersprechend</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user