diff --git a/app/main.py b/app/main.py index e632882..fcfb7b8 100644 --- a/app/main.py +++ b/app/main.py @@ -3537,8 +3537,10 @@ async def scorecard_template( "og": (1200, 630), "square": (1080, 1080), "portrait": (1080, 1350), + "story": (1080, 1920), + "wide": (1920, 1080), } - width, height = dimensions.get(format, dimensions["og"]) + width, height = dimensions.get(format, dimensions["portrait"]) # ─── Cloud-Design (portrait) braucht aggregierte Hilfsdaten ────── # Chips: Top-3 positiv bewerteter Felder mit Code + Wert-Kurzname + Symbol @@ -3636,12 +3638,13 @@ async def scorecard_template( score_color_band = "good" if score >= 7 else "mid" if score >= 4 else "low" - # Template-Wahl: portrait nutzt das Cloud-Design (eigene Datei), - # square/og bleiben beim alten Layout. + # Template-Wahl: portrait/square/story/wide nutzen das Cloud-Design + # (eine Template-Datei, format-Variable schaltet Card-Klasse). + # Nur og bleibt beim alten Layout (fuer OG-Meta-Tag-Crawler). template_name = ( - "v2/screens/scorecard_portrait.html" - if format == "portrait" - else "v2/screens/scorecard.html" + "v2/screens/scorecard.html" + if format == "og" + else "v2/screens/scorecard_portrait.html" ) response = templates.TemplateResponse(template_name, { @@ -3659,6 +3662,7 @@ async def scorecard_template( "beschluss": beschluss, "antrag_typ": (row.get("typ") or "Antrag"), "wahlperiode": wahlperiode, + "format": format, "width": width, "height": height, }) @@ -3678,9 +3682,15 @@ async def _render_scorecard_pdf( from weasyprint import HTML, CSS from .models import Assessment - if format not in ("og", "square", "portrait"): - raise HTTPException(status_code=400, detail="format muss 'og', 'square' oder 'portrait' sein") - dimensions = {"og": (1200, 630), "square": (1080, 1080), "portrait": (1080, 1350)} + if format not in ("og", "square", "portrait", "story", "wide"): + raise HTTPException(status_code=400, detail="format muss 'og', 'square', 'portrait', 'story' oder 'wide' sein") + dimensions = { + "og": (1200, 630), + "square": (1080, 1080), + "portrait": (1080, 1350), + "story": (1080, 1920), + "wide": (1920, 1080), + } width, height = dimensions[format] drucksache = validate_drucksache(drucksache) @@ -3703,10 +3713,10 @@ async def _render_scorecard_pdf( elif score >= 5: score_color = "#bf6c10" else: score_color = "#9a2a2a" - # Portrait nutzt das Cloud-Design-Template; square/og das alte. - # Fuer Portrait brauchen wir die gleichen Aggregat-Daten wie der - # HTML-Render (Chips, Fraktions-Bars, Beschluss). - if format == "portrait": + # Cloud-Design-Template fuer portrait/square/story/wide; og bleibt + # beim alten Layout. Cloud-Design braucht die gleichen Aggregat- + # Daten wie der HTML-Render (Chips, Fraktions-Bars, Beschluss). + if format in ("portrait", "square", "story", "wide"): werte_kurz = { "1": "Würde", "2": "Solidarität", "3": "Nachhaltigkeit", "4": "Soz. Gerechtigkeit", "5": "Transparenz", @@ -3785,6 +3795,7 @@ async def _render_scorecard_pdf( antrag_typ=(row.get("typ") or "Antrag"), wahlperiode=wahlperiode, score_color_band=score_color_band, + format=format, # damit das Template den Card-Klassen-Switch macht width=width, height=height, ) else: diff --git a/app/templates/v2/screens/scorecard_portrait.html b/app/templates/v2/screens/scorecard_portrait.html index 19df8d0..587a49b 100644 --- a/app/templates/v2/screens/scorecard_portrait.html +++ b/app/templates/v2/screens/scorecard_portrait.html @@ -1,14 +1,19 @@ -{# scorecard_portrait.html — 1080×1350 Hochkant-Scorecard (Instagram 4:5) +{# scorecard_portrait.html — Cloud-Design Multi-Format-Scorecard. - Designvorgabe: Claude Design, ZIP "GWÖ Antrag Score Card.zip" vom 2026-05-07. - Übernommen 1:1 in HTML/CSS, Jinja-Variablen ersetzen die Beispiel-Inhalte. + Vier Formate mit eigenen Klassen + Größen, alle aus der Spec + "GWÖ Antrag Score Card-2.zip" (Claude Design, 2026-05-07): - Komprimierung gegenüber der Profi-Detail-Ansicht (siehe Spec-Sheet): - - 5×5-Matrix → Top-3 Schwerpunkt-Chips - - Zitate, Verbesserungen, Kommentare, News alle raus - - Stärken/Schwächen-Fließtext → 1-Satz-Zusammenfassung - - Fraktions-Bewertung als Balken-Grid (5 Spalten) statt langer Liste - - Beschluss als invertierte schwarze Bar am Ende + format=portrait (Default) → card-portrait → 1080×1350 · 4:5 · IG-Feed + format=square → card-square → 1080×1080 · 1:1 · IG/LinkedIn-Feed + format=story → card-story → 1080×1920 · 9:16 · Story/Reels + format=wide → card-wide → 1920×1080 · 16:9 · OG/Slide + + Wide hat 2-spaltigen Body (Story-Spalte links, Daten-Spalte rechts); + die anderen drei haben 1-spaltigen Body (linear gestapelt). + + WeasyPrint rendert nur was der format-Param sagt. Browser-Vorschau + skaliert per JS auf 90 % der Viewport-Hoehe (mit Width-Fallback) und + zeigt eine Statusleiste mit Format-Switcher und Download-Button. #} @@ -24,30 +29,268 @@ --muted:#6b6660; --accent:#0a5d3f; --accent-warn:#b04a2f; - --grid:#d9d2c5; - --score-bg: {% if score_color_band == 'good' %}#0a5d3f{% elif score_color_band == 'mid' %}#6b6660{% else %}#b04a2f{% endif %}; + --score-bg:#0a5d3f; } *{box-sizing:border-box;margin:0;padding:0} html,body{background:var(--paper);color:var(--ink);font-family:'Inter',sans-serif;} - /* Print/PDF-Defaults — WeasyPrint nutzt media=print und sieht NUR - diese Werte. Body == Card-Groesse, ohne Wrapper. */ - body{ - width:{{ width }}px;height:{{ height }}px;overflow:hidden; - } - .card-viewport { width: 100%; height: 100%; } + body{ width:{{ width }}px;height:{{ height }}px;overflow:hidden; } + /* ===== Generische Card-Bauelemente — Größen kommen je Format-Klasse ===== */ .card{ - width:1080px;height:1350px; background:var(--paper);color:var(--ink); position:relative;overflow:hidden; - display:grid;grid-template-rows: 88px 1fr 96px; + } + .head{ + border-bottom:2px solid var(--rule); + display:flex;align-items:center;justify-content:space-between; + font-family:'JetBrains Mono',monospace; + font-weight:500;text-transform:uppercase; + } + .head .brand{display:flex;align-items:center;gap:12px} + .head .dot{background:var(--accent);border-radius:50%} + .head .meta{display:flex;color:var(--muted)} + .ids{font-family:'JetBrains Mono',monospace;color:var(--muted);letter-spacing:.06em} + .ids strong{color:var(--ink);font-weight:600} + .party-pill{ + display:inline-flex;align-items:center;gap:10px; + border:2px solid var(--rule);border-radius:999px; + font-weight:700;letter-spacing:.04em; + } + .party-pill::before{content:"";border-radius:50%;background:#d44} + h1.title{font-weight:800;letter-spacing:-.025em;text-wrap:balance} + .lede{color:var(--ink);text-wrap:pretty} + .scorewrap{ + display:grid;align-items:stretch; + border-top:2px solid var(--rule);border-bottom:2px solid var(--rule); + } + .score{ + background:var(--score-bg);color:var(--paper); + display:flex;flex-direction:column;justify-content:center;align-items:center; + border-radius:8px; + } + .score .num{font-weight:800;line-height:1;letter-spacing:-.04em;font-variant-numeric:tabular-nums} + .score .num small{opacity:.7;font-weight:600} + .score .label{font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:.1em;opacity:.85} + .verdict{display:flex;flex-direction:column;justify-content:center} + .verdict .kicker{font-family:'JetBrains Mono',monospace;text-transform:uppercase;letter-spacing:.12em;color:var(--muted)} + .verdict .vtext{font-weight:700;line-height:1.1;letter-spacing:-.01em} + .verdict .fields{display:flex;flex-wrap:wrap} + .field-chip{ + font-family:'JetBrains Mono',monospace;font-weight:600; + border:2px solid var(--rule);border-radius:4px;background:transparent; + } + .fractions{display:grid;grid-template-columns:repeat({{ fraktionen_count }},1fr);border:2px solid var(--rule)} + .frac{ + border-right:2px solid var(--rule); + display:flex;flex-direction:column;align-items:center;text-align:center; + background:var(--paper); + } + .frac:last-child{border-right:none} + .frac .name{font-family:'JetBrains Mono',monospace;font-weight:700;letter-spacing:.05em} + .frac .bar{width:100%;background:#e6dfd1;border-radius:2px;overflow:hidden} + .frac .bar > span{display:block;height:100%;background:var(--accent)} + .frac.weak .bar > span{background:var(--accent-warn)} + .frac .val{font-family:'JetBrains Mono',monospace;font-weight:600} + .frac .vote{font-weight:700;letter-spacing:.08em;text-transform:uppercase} + .vote.ja{color:var(--accent)} + .vote.nein{color:var(--accent-warn)} + .vote.enth{color:var(--muted)} + .vote.unbekannt{color:var(--muted);opacity:.6} + .decision{ + display:flex;align-items:center;justify-content:space-between; + background:var(--ink);color:var(--paper);border-radius:6px; + } + .decision .left{font-family:'JetBrains Mono',monospace;letter-spacing:.1em;text-transform:uppercase;opacity:.7} + .decision .right{font-weight:700} + .decision .right .x{color:#ff6a4a;margin-right:10px} + .decision .right .ok{color:#7fd9a8;margin-right:10px} + .foot{ + border-top:2px solid var(--rule); + display:flex;align-items:center;justify-content:space-between; + font-family:'JetBrains Mono',monospace;letter-spacing:.04em;color:var(--muted); + } + .foot .url{color:var(--ink);font-weight:600} + .foot .qr{ + background:repeating-conic-gradient(var(--ink) 0 25%, var(--paper) 0 50%) 50%/16px 16px; + border:2px solid var(--ink); } - /* Browser-Preview — skaliert die Card auf Viewport-Breite, scrollbar - wenn die Hoehe nicht reicht. WeasyPrint ignoriert @media screen. - Die eigentliche Skalierung wird per JS gesetzt (CSS calc/scale - mit Unit-Mismatch geht nicht), CSS-Defaults sorgen nur fuer das - Drumherum. */ + /* ===== A · 4:5 Portrait — 1080×1350 (DEFAULT, IG-Feed) ===== */ + .card-portrait{ + width:1080px;height:1350px; + display:grid;grid-template-rows: 88px 1fr 96px; + } + .card-portrait .head{padding:0 56px;font-size:18px;letter-spacing:.04em} + .card-portrait .head .dot{width:14px;height:14px} + .card-portrait .head .meta{gap:24px} + .card-portrait .body{padding:40px 56px 32px;display:flex;flex-direction:column;gap:28px;min-height:0} + .card-portrait .topline{display:flex;justify-content:space-between;align-items:flex-start;gap:32px} + .card-portrait .ids{font-size:20px} + .card-portrait .party-pill{padding:8px 18px;font-size:20px} + .card-portrait .party-pill::before{width:12px;height:12px} + .card-portrait h1.title{font-size:78px;line-height:.95} + .card-portrait .lede{font-size:26px;line-height:1.35;max-width:880px} + .card-portrait .scorewrap{grid-template-columns:320px 1fr;gap:32px;padding:24px 0} + .card-portrait .score{padding:18px} + .card-portrait .score .num{font-size:120px} + .card-portrait .score .num small{font-size:42px} + .card-portrait .score .label{font-size:15px;margin-top:6px} + .card-portrait .verdict{gap:14px} + .card-portrait .verdict .kicker{font-size:15px} + .card-portrait .verdict .vtext{font-size:34px} + .card-portrait .verdict .fields{gap:8px;margin-top:4px} + .card-portrait .field-chip{font-size:16px;padding:4px 10px} + .card-portrait .frac{padding:16px 14px;gap:8px} + .card-portrait .frac .name{font-size:17px} + .card-portrait .frac .bar{height:8px} + .card-portrait .frac .val{font-size:18px} + .card-portrait .frac .vote{font-size:14px} + .card-portrait .decision{padding:18px 22px} + .card-portrait .decision .left{font-size:16px} + .card-portrait .decision .right{font-size:24px} + .card-portrait .foot{padding:0 56px;font-size:16px} + .card-portrait .foot .qr{width:64px;height:64px} + + /* ===== B · 1:1 Square — 1080×1080 (IG/LinkedIn-Feed) ===== */ + .card-square{ + width:1080px;height:1080px; + display:grid;grid-template-rows:80px 1fr 80px; + } + .card-square .head{padding:0 48px;font-size:16px;letter-spacing:.04em} + .card-square .head .dot{width:13px;height:13px} + .card-square .head .meta{gap:20px} + .card-square .body{padding:32px 48px 24px;display:flex;flex-direction:column;gap:20px;min-height:0} + .card-square .topline{display:flex;justify-content:space-between;align-items:center;gap:24px} + .card-square .ids{font-size:17px} + .card-square .party-pill{padding:6px 16px;font-size:18px} + .card-square .party-pill::before{width:11px;height:11px} + .card-square h1.title{font-size:64px;line-height:.95} + .card-square .lede{font-size:21px;line-height:1.35;max-width:880px} + .card-square .scorewrap{grid-template-columns:280px 1fr;gap:24px;padding:18px 0} + .card-square .score{padding:14px} + .card-square .score .num{font-size:96px} + .card-square .score .num small{font-size:36px} + .card-square .score .label{font-size:13px;margin-top:4px} + .card-square .verdict{gap:10px} + .card-square .verdict .kicker{font-size:13px} + .card-square .verdict .vtext{font-size:28px} + .card-square .verdict .fields{gap:6px;margin-top:2px} + .card-square .field-chip{font-size:14px;padding:3px 9px} + .card-square .frac{padding:12px 10px;gap:6px} + .card-square .frac .name{font-size:15px} + .card-square .frac .bar{height:7px} + .card-square .frac .val{font-size:16px} + .card-square .frac .vote{font-size:12px} + .card-square .decision{padding:14px 18px} + .card-square .decision .left{font-size:14px} + .card-square .decision .right{font-size:21px} + .card-square .foot{padding:0 48px;font-size:14px} + .card-square .foot .qr{width:54px;height:54px} + + /* ===== C · 9:16 Story — 1080×1920 (Story/Reels) ===== */ + .card-story{ + width:1080px;height:1920px; + display:grid;grid-template-rows:104px 1fr 112px; + } + .card-story .head{padding:0 64px;font-size:20px} + .card-story .head .dot{width:16px;height:16px} + .card-story .head .meta{gap:28px} + .card-story .body{padding:80px 64px 56px;display:flex;flex-direction:column;gap:44px;min-height:0} + .card-story .topline{display:flex;justify-content:space-between;align-items:flex-start;gap:32px} + .card-story .ids{font-size:24px} + .card-story .party-pill{padding:10px 22px;font-size:24px} + .card-story .party-pill::before{width:14px;height:14px} + .card-story h1.title{font-size:108px;line-height:.92} + .card-story .lede{font-size:34px;line-height:1.32;max-width:920px} + /* Story: Score-Block VERTIKAL gestapelt */ + .card-story .scorewrap{grid-template-columns:1fr;gap:0;padding:32px 0} + .card-story .score{padding:36px 24px;border-radius:10px;margin-bottom:28px} + .card-story .score .num{font-size:200px} + .card-story .score .num small{font-size:64px} + .card-story .score .label{font-size:20px;margin-top:8px} + .card-story .verdict{gap:18px;text-align:center;align-items:center} + .card-story .verdict .kicker{font-size:20px} + .card-story .verdict .vtext{font-size:48px;text-align:center} + .card-story .verdict .fields{gap:10px;justify-content:center} + .card-story .field-chip{font-size:20px;padding:6px 14px} + .card-story .frac{padding:24px 14px;gap:12px} + .card-story .frac .name{font-size:22px} + .card-story .frac .bar{height:10px} + .card-story .frac .val{font-size:24px} + .card-story .frac .vote{font-size:18px} + .card-story .decision{padding:24px 28px} + .card-story .decision .left{font-size:20px} + .card-story .decision .right{font-size:30px} + .card-story .foot{padding:0 64px;font-size:18px} + .card-story .foot .qr{width:76px;height:76px} + + /* ===== D · 16:9 Wide — 1920×1080 (OG/Slide/Twitter) ===== */ + .card-wide{ + width:1920px;height:1080px; + display:grid; + grid-template-columns:760px 1fr; + grid-template-rows:80px 1fr 80px; + grid-template-areas: + "head head" + "left right" + "foot foot"; + } + .card-wide .head{ + grid-area:head; + padding:0 56px;font-size:18px;letter-spacing:.04em; + } + .card-wide .head .dot{width:14px;height:14px} + .card-wide .head .meta{gap:24px} + .card-wide .left{ + grid-area:left; + padding:48px 40px 40px 56px; + display:flex;flex-direction:column;gap:24px; + border-right:2px solid var(--rule); + } + .card-wide .topline{display:flex;justify-content:space-between;align-items:center;gap:24px} + .card-wide .ids{font-size:18px} + .card-wide .party-pill{padding:7px 16px;font-size:18px} + .card-wide .party-pill::before{width:11px;height:11px} + .card-wide h1.title{font-size:72px;line-height:.94} + .card-wide .lede{font-size:22px;line-height:1.35;max-width:660px} + .card-wide .scorewrap{ + grid-template-columns:240px 1fr;gap:24px;padding:20px 0; + margin-top:auto; + } + .card-wide .score{padding:14px} + .card-wide .score .num{font-size:96px} + .card-wide .score .num small{font-size:36px} + .card-wide .score .label{font-size:13px;margin-top:4px} + .card-wide .verdict{gap:10px} + .card-wide .verdict .kicker{font-size:13px} + .card-wide .verdict .vtext{font-size:28px} + .card-wide .verdict .fields{gap:6px;margin-top:2px} + .card-wide .field-chip{font-size:14px;padding:3px 9px} + .card-wide .right{ + grid-area:right; + padding:48px 56px 40px 48px; + display:flex;flex-direction:column;gap:28px;justify-content:space-between; + } + .card-wide .right .right-head{ + font-family:'JetBrains Mono',monospace; + font-size:14px;letter-spacing:.12em;text-transform:uppercase; + color:var(--muted); + } + .card-wide .frac{padding:18px 14px;gap:10px} + .card-wide .frac .name{font-size:18px} + .card-wide .frac .bar{height:9px} + .card-wide .frac .val{font-size:20px} + .card-wide .frac .vote{font-size:14px} + .card-wide .decision{padding:18px 24px} + .card-wide .decision .left{font-size:15px} + .card-wide .decision .right{font-size:24px;font-weight:700} + .card-wide .foot{ + grid-area:foot; + padding:0 56px;font-size:14px; + } + .card-wide .foot .qr{width:56px;height:56px} + + /* ===== Browser-Preview — Skalierung + Toolbar ===== */ @media screen { html, body { background: #2a2724 !important; @@ -56,7 +299,7 @@ min-height: 100vh; overflow: auto !important; margin: 0; - padding: 20px 20px 80px; /* bottom-padding fuer die fixed Toolbar */ + padding: 20px 20px 80px; } body { display: flex; @@ -64,190 +307,133 @@ align-items: flex-start; } .card-viewport { - width: 1080px; - height: 1350px; + width: {{ width }}px; + height: {{ height }}px; overflow: hidden; position: relative; box-shadow: 0 30px 80px rgba(0,0,0,0.5); background: var(--paper); } - .card { - transform-origin: top left; - position: absolute; - top: 0; left: 0; - } + .card { transform-origin: top left; position: absolute; top: 0; left: 0; } - /* Statusleiste am unteren Viewport-Rand mit Download-Button. - Nur im Browser sichtbar; PDF bekommt sie nicht. */ .screen-toolbar { position: fixed; bottom: 0; left: 0; right: 0; - background: var(--ink); - color: var(--paper); - padding: 12px 24px; - display: flex; - justify-content: space-between; - align-items: center; - gap: 16px; - z-index: 1000; - font-family: 'JetBrains Mono', monospace; - font-size: 13px; - flex-wrap: wrap; + background: var(--ink); color: var(--paper); + padding: 10px 20px; + display: flex; justify-content: space-between; align-items: center; + gap: 16px; z-index: 1000; flex-wrap: wrap; + font-family: 'JetBrains Mono', monospace; font-size: 12px; box-shadow: 0 -8px 24px rgba(0,0,0,0.3); } - .screen-toolbar .label { - opacity: 0.75; - letter-spacing: 0.04em; - } - .screen-toolbar .label strong { - color: var(--paper); - font-weight: 600; - opacity: 1; - } - .screen-toolbar .actions { - display: flex; - gap: 8px; - flex-wrap: wrap; - } - .screen-download { - background: var(--accent); - color: var(--paper); - padding: 8px 18px; - border-radius: 4px; - font-family: 'JetBrains Mono', monospace; - font-size: 13px; - font-weight: 700; - text-decoration: none; - border: none; - cursor: pointer; - letter-spacing: 0.04em; + .screen-toolbar .label { opacity: 0.75; letter-spacing: 0.04em; } + .screen-toolbar .label strong { color: var(--paper); font-weight: 600; opacity: 1; } + .screen-toolbar .group { display: flex; gap: 6px; flex-wrap: wrap; align-items: center; } + .format-pill { + padding: 6px 12px; + border-radius: 3px; + font-family: 'JetBrains Mono', monospace; font-size: 11px; + font-weight: 700; letter-spacing: 0.04em; + text-decoration: none; cursor: pointer; + background: transparent; color: var(--paper); + border: 2px solid rgba(245, 241, 234, 0.3); text-transform: uppercase; } - .screen-download:hover { - filter: brightness(1.1); + .format-pill:hover { background: rgba(245, 241, 234, 0.1); border-color: var(--paper); } + .format-pill.active { background: var(--paper); color: var(--ink); border-color: var(--paper); } + .screen-download { + background: var(--accent); color: var(--paper); + padding: 8px 18px; border-radius: 4px; + font-family: 'JetBrains Mono', monospace; font-size: 13px; + font-weight: 700; text-decoration: none; border: none; cursor: pointer; + letter-spacing: 0.04em; text-transform: uppercase; } + .screen-download:hover { filter: brightness(1.1); } .screen-download.secondary { - background: transparent; - color: var(--paper); - border: 1.5px solid rgba(245, 241, 234, 0.3); + background: transparent; color: var(--paper); + border: 2px solid rgba(245, 241, 234, 0.3); } .screen-download.secondary:hover { - background: rgba(245, 241, 234, 0.1); - border-color: var(--paper); + background: rgba(245, 241, 234, 0.1); border-color: var(--paper); } } @media print { .screen-toolbar { display: none !important; } } - - /* Kopfleiste */ - .head{ - border-bottom:2px solid var(--rule); - padding:0 56px; - display:flex;align-items:center;justify-content:space-between; - font-family:'JetBrains Mono',monospace; - font-size:18px;font-weight:500;letter-spacing:.04em; - text-transform:uppercase; - } - .head .brand{display:flex;align-items:center;gap:14px} - .head .dot{width:14px;height:14px;background:var(--accent);border-radius:50%} - .head .meta{display:flex;gap:24px;color:var(--muted)} - - /* Body */ - .body{padding:40px 56px 32px;display:flex;flex-direction:column;gap:28px;min-height:0} - - .topline{display:flex;justify-content:space-between;align-items:flex-start;gap:32px} - .ids{font-family:'JetBrains Mono',monospace;font-size:20px;color:var(--muted);letter-spacing:.06em} - .ids strong{color:var(--ink);font-weight:600} - .party-pill{ - display:inline-flex;align-items:center;gap:10px; - padding:8px 18px;border:2px solid var(--rule);border-radius:999px; - font-weight:700;font-size:20px;letter-spacing:.04em; - } - .party-pill::before{content:"";width:12px;height:12px;border-radius:50%;background:#d44} - - h1.title{ - font-family:'Inter',sans-serif; - font-weight:800;font-size:78px;line-height:.95; - letter-spacing:-.025em;text-wrap:balance; - } - - .lede{ - font-size:26px;line-height:1.35;color:var(--ink); - max-width:880px;text-wrap:pretty; - } - - .scorewrap{ - display:grid;grid-template-columns: 320px 1fr;gap:32px;align-items:stretch; - border-top:2px solid var(--rule);border-bottom:2px solid var(--rule); - padding:24px 0; - } - .score{ - background:var(--score-bg);color:var(--paper); - display:flex;flex-direction:column;justify-content:center;align-items:center; - padding:18px;border-radius:8px; - } - .score .num{font-size:120px;font-weight:800;line-height:1;letter-spacing:-.04em;font-variant-numeric:tabular-nums} - .score .num small{font-size:42px;opacity:.7;font-weight:600} - .score .label{font-family:'JetBrains Mono',monospace;font-size:15px;text-transform:uppercase;letter-spacing:.1em;margin-top:6px;opacity:.85} - - .verdict{display:flex;flex-direction:column;justify-content:center;gap:14px} - .verdict .kicker{font-family:'JetBrains Mono',monospace;font-size:15px;text-transform:uppercase;letter-spacing:.12em;color:var(--muted)} - .verdict .vtext{font-size:34px;font-weight:700;line-height:1.1;letter-spacing:-.01em} - .verdict .fields{display:flex;gap:8px;flex-wrap:wrap;margin-top:4px} - .field-chip{ - font-family:'JetBrains Mono',monospace;font-size:16px;font-weight:600; - border:1.5px solid var(--rule);padding:4px 10px;border-radius:4px;background:transparent; - } - - /* Fraktionen Grid */ - .fractions{display:grid;grid-template-columns:repeat({{ fraktionen_count }},1fr);gap:0;border:1.5px solid var(--rule)} - .frac{ - padding:16px 14px;border-right:1.5px solid var(--rule); - display:flex;flex-direction:column;gap:8px;align-items:center;text-align:center; - background:var(--paper); - } - .frac:last-child{border-right:none} - .frac .name{font-family:'JetBrains Mono',monospace;font-size:17px;font-weight:700;letter-spacing:.05em} - .frac .bar{height:8px;width:100%;background:#e6dfd1;border-radius:2px;overflow:hidden} - .frac .bar > span{display:block;height:100%;background:var(--accent)} - .frac.weak .bar > span{background:var(--accent-warn)} - .frac .val{font-family:'JetBrains Mono',monospace;font-size:18px;font-weight:600} - .frac .vote{font-size:14px;font-weight:700;letter-spacing:.08em;text-transform:uppercase} - .vote.ja{color:var(--accent)} - .vote.nein{color:var(--accent-warn)} - .vote.enth{color:var(--muted)} - .vote.unbekannt{color:var(--muted);opacity:.6} - - /* Beschluss */ - .decision{ - display:flex;align-items:center;justify-content:space-between; - padding:18px 22px;background:var(--ink);color:var(--paper); - border-radius:6px; - } - .decision .left{font-family:'JetBrains Mono',monospace;font-size:16px;letter-spacing:.1em;text-transform:uppercase;opacity:.7} - .decision .right{font-size:24px;font-weight:700} - .decision .right .x{color:#ff6a4a;margin-right:10px} - .decision .right .ok{color:#7fd9a8;margin-right:10px} - - /* Footer */ - .foot{ - border-top:2px solid var(--rule); - padding:0 56px; - display:flex;align-items:center;justify-content:space-between; - font-family:'JetBrains Mono',monospace;font-size:16px;letter-spacing:.04em; - color:var(--muted); - } - .foot .url{color:var(--ink);font-weight:600} - .foot .qr{width:64px;height:64px;background: - repeating-conic-gradient(var(--ink) 0 25%, var(--paper) 0 50%) 50%/16px 16px; - border:2px solid var(--ink); - }
-
+{# ===== Wide-Variante hat eigene Body-Struktur (2-spaltig) ===== #} +{% if format == 'wide' %} +
+
+
GWÖ-Antragsprüfer · Matrix 2.0
+
+ {{ bundesland }}{% if wahlperiode %} · {{ wahlperiode.split('-')[-1] if '-' in wahlperiode else 'WP' ~ wahlperiode }}{% endif %} + Drs. {{ assessment.drucksache }} + {% if assessment.datum %}{{ assessment.datum }}{% endif %} +
+
+
+
+
{{ antrag_typ or 'Antrag' }}{% if assessment.datum %} · eingebracht {{ assessment.datum }}{% endif %}
+ {% if assessment.fraktionen %}
{{ assessment.fraktionen[0] }}
{% endif %} +
+

{{ assessment.title|truncate(80, end="…") }}

+ {% if assessment.antrag_zusammenfassung %} +

{{ assessment.antrag_zusammenfassung|truncate(180, end="…") }}

+ {% endif %} +
+
+
{{ "%.1f"|format(assessment.gwoe_score) }}/10
+
GWÖ-Score
+
+
+
Empfehlung
+
{{ assessment.empfehlung.value }}
+ {% if matrix_chips %} +
+ {% for chip in matrix_chips %}{{ chip.code }} {{ chip.symbol }}{% endfor %} +
+ {% endif %} +
+
+
+
+
Programm-Treue · Fraktionen · Abstimmung
+ {% if fraktionen_bars %} +
+ {% for f in fraktionen_bars %} +
+
{{ f.name }}
+
+
{{ f.score_text }}
+
{{ f.vote_label }}
+
+ {% endfor %} +
+ {% endif %} + {% if beschluss %} +
+
Beschluss Plenum
+
+ {{ '✓' if beschluss.is_positive else '✗' }} + {{ beschluss.text }} +
+
+ {% endif %} +
+
+
gwoe.toppyr.de · /antrag/{{ assessment.drucksache }}
+
CC BY 4.0 · Matrix 2.0
+ +
+
+{% else %} +{# ===== Portrait / Square / Story — gemeinsame 1-spaltige Body-Struktur ===== #} +{% set card_cls = 'card-' + (format if format in ['portrait','square','story'] else 'portrait') %} +
GWÖ-Antragsprüfer
@@ -255,14 +441,12 @@ {{ bundesland }}{% if wahlperiode %} · {{ wahlperiode.split('-')[-1] if '-' in wahlperiode else 'WP' ~ wahlperiode }}{% endif %}
-
-
Drs. {{ assessment.drucksache }} {% if antrag_typ %} · {{ antrag_typ }}{% endif %} - {% if assessment.datum %} · eingebracht {{ assessment.datum }}{% endif %} + {% if assessment.datum %} · {{ assessment.datum }}{% endif %}
{% if assessment.fraktionen %}
{{ assessment.fraktionen[0] }}
@@ -315,58 +499,66 @@
{% endif %} - -
+{% endif %}
{# .card-viewport #} -{# Statusleiste mit Download-Button (nur im Browser, nicht im PDF). #} +{# ===== Statusleiste — Format-Switcher + Download-Buttons ===== #} {% set _drs_safe = assessment.drucksache | replace('/', '-') %} +{% set _query_base = '?drucksache=' + (assessment.drucksache | urlencode) + '&bundesland=' + (bundesland | urlencode) %}
Scorecard · {{ bundesland }} · {{ assessment.drucksache }} - · 1080×1350 (Instagram 4:5) + · {{ width }}×{{ height }}
-
+
+ {% set _formats = [ + ('portrait', '4:5 Feed'), + ('square', '1:1 Square'), + ('story', '9:16 Story'), + ('wide', '16:9 Wide'), + ] %} + {% for fkey, flabel in _formats %} + {{ flabel }} + {% endfor %} +
+