gwoe-antragspruefer/app/templates/v2/screens/scorecard_werkstatt.html

397 lines
12 KiB
HTML
Raw Normal View History

{% extends "v2/base.html" %}
{% block title %}Scorecard-Werkstatt — GWÖ-Antragsprüfer{% endblock %}
{% set v2_active_nav = "" %}
{% block head_extra %}
<style>
.ws-shell {
display: grid;
grid-template-columns: 1fr 480px;
gap: 18px;
height: calc(100vh - 100px);
min-height: 700px;
}
@media (max-width: 1100px) {
.ws-shell { grid-template-columns: 1fr; height: auto; }
}
.ws-preview {
background: var(--ecg-bg-subtle);
border: 1px solid var(--ecg-border);
border-radius: 4px;
padding: 14px;
overflow: auto;
display: flex;
flex-direction: column;
gap: 10px;
}
.ws-preview iframe {
border: 1px solid var(--ecg-border);
background: #fff;
/* iframe-Skalierung: 1080×1350 → ein bisschen verkleinert anzeigen,
Skalierung pro Format konfigurierbar via JS */
transform-origin: top left;
background: #fff;
}
.ws-frame-wrap {
overflow: hidden;
background: #fff;
border: 1px solid var(--ecg-border);
border-radius: 4px;
}
.ws-controls {
display: flex;
flex-direction: column;
gap: 14px;
overflow-y: auto;
padding-right: 6px;
}
.ws-section {
border: 1px solid var(--ecg-border);
border-radius: 4px;
padding: 12px 14px;
background: var(--ecg-card-bg);
}
.ws-section h3 {
font-family: var(--font-mono);
font-size: 10px;
text-transform: uppercase;
letter-spacing: 0.07em;
color: var(--ecg-blue);
margin: 0 0 8px;
font-weight: 700;
}
.ws-row {
display: flex;
gap: 8px;
align-items: center;
flex-wrap: wrap;
margin-bottom: 6px;
}
.ws-row label {
font-family: var(--font-mono);
font-size: 11px;
color: var(--ecg-dark);
opacity: 0.75;
min-width: 90px;
}
.ws-row select,
.ws-row input[type="text"] {
flex: 1;
padding: 4px 8px;
border: 1px solid var(--ecg-border);
border-radius: 3px;
font-family: var(--font-mono);
font-size: 11px;
background: var(--paper);
color: var(--ecg-dark);
}
.ws-format-pills {
display: flex;
gap: 4px;
flex-wrap: wrap;
}
.ws-format-pills button {
padding: 4px 10px;
border: 1px solid var(--ecg-border);
border-radius: 3px;
background: var(--paper);
color: var(--ecg-dark);
font-family: var(--font-mono);
font-size: 11px;
cursor: pointer;
}
.ws-format-pills button.active {
background: var(--ecg-blue);
color: #fff;
border-color: var(--ecg-blue);
}
textarea.ws-css-editor {
width: 100%;
min-height: 320px;
flex: 1;
padding: 10px 12px;
border: 1px solid var(--ecg-border);
border-radius: 3px;
font-family: var(--font-mono);
font-size: 11px;
background: #1f1f1f;
color: #d8e4d8;
line-height: 1.5;
resize: vertical;
tab-size: 2;
box-sizing: border-box;
}
.ws-css-actions {
display: flex;
gap: 6px;
margin-top: 6px;
flex-wrap: wrap;
}
.ws-css-actions button {
padding: 6px 14px;
border: 1px solid var(--ecg-border);
border-radius: 3px;
font-family: var(--font-mono);
font-size: 11px;
cursor: pointer;
background: var(--paper);
color: var(--ecg-dark);
}
.ws-css-actions button.primary {
background: var(--ecg-blue);
border-color: var(--ecg-blue);
color: #fff;
font-weight: 700;
}
.ws-meta {
font-family: var(--font-mono);
font-size: 10px;
color: var(--ecg-dark);
opacity: 0.65;
line-height: 1.5;
}
.ws-embed-row code {
display: block;
padding: 8px 10px;
background: var(--ecg-bg-subtle);
border: 1px solid var(--ecg-border);
border-radius: 3px;
font-family: var(--font-mono);
font-size: 10px;
word-break: break-all;
user-select: all;
}
.ws-toolbar {
display: flex;
gap: 6px;
margin-bottom: 4px;
flex-wrap: wrap;
align-items: center;
}
.ws-toolbar button {
padding: 3px 9px;
border: 1px solid var(--ecg-border);
border-radius: 3px;
background: var(--paper);
color: var(--ecg-dark);
font-family: var(--font-mono);
font-size: 10px;
cursor: pointer;
}
.ws-toolbar span.zoom-info {
font-family: var(--font-mono);
font-size: 10px;
color: var(--ecg-dark);
opacity: 0.6;
}
</style>
{% endblock %}
{% block main %}
<div style="padding:0 0 1rem;">
<h1 style="font-family:var(--font-display);font-size:22px;color:var(--ecg-teal);margin:0 0 4px;">Scorecard-Werkstatt</h1>
<p style="font-size:12px;font-family:var(--font-mono);color:var(--ecg-dark);opacity:0.7;">
Live-Editor für Scorecard-Layout. Drucksache + Format wählen, CSS im rechten Editor anpassen,
Apply drücken — Vorschau aktualisiert ohne neue PNG-Render. Auch als Embed-Quelle für
eingebettete Übersichten in anderen Kontexten nutzbar.
</p>
</div>
<div class="ws-shell">
{# ── Vorschau links ──────────────────────────────────────────────── #}
<div class="ws-preview">
<div class="ws-toolbar">
<button onclick="wsZoomFit()">Fit</button>
<button onclick="wsZoom(0.4)">40 %</button>
<button onclick="wsZoom(0.5)">50 %</button>
<button onclick="wsZoom(0.65)">65 %</button>
<button onclick="wsZoom(0.8)">80 %</button>
<button onclick="wsZoom(1.0)">100 %</button>
<span class="zoom-info" id="ws-zoom-info"></span>
</div>
<div class="ws-frame-wrap" id="ws-frame-wrap">
<iframe id="ws-frame" src="about:blank" title="Scorecard-Vorschau"></iframe>
</div>
<div class="ws-meta" id="ws-meta">Drucksache + Format wählen.</div>
</div>
{# ── Controls + CSS-Editor rechts ────────────────────────────────── #}
<div class="ws-controls">
<div class="ws-section">
<h3>Inhalt</h3>
<div class="ws-row">
<label>Drucksache</label>
<select id="ws-drs" onchange="wsRender()">
{% for d in drucksachen %}
<option value="{{ d.drucksache }}|{{ d.bundesland }}">
{{ d.bundesland }} · {{ d.drucksache }} — {{ d.title }}
</option>
{% endfor %}
</select>
</div>
<div class="ws-row">
<label>Format</label>
<div class="ws-format-pills">
<button data-format="portrait" class="active" onclick="wsSetFormat('portrait')">Portrait 4:5 (1080×1350)</button>
<button data-format="square" onclick="wsSetFormat('square')">Square 1:1 (1080×1080)</button>
<button data-format="og" onclick="wsSetFormat('og')">OG 16:8 (1200×630)</button>
</div>
</div>
</div>
<div class="ws-section" style="display:flex;flex-direction:column;flex:1;min-height:280px;">
<h3>CSS-Override (live)</h3>
<textarea class="ws-css-editor" id="ws-css" placeholder="/* CSS-Selektoren in den iframe-Kontext.
Beispiel:
.portrait-title { font-size: 28pt; }
.portrait-score-num { font-size: 150pt; }
.cell.r-0 { background: #c8c8c2; }
*/"></textarea>
<div class="ws-css-actions">
<button class="primary" onclick="wsApplyCss()">Anwenden ⏎</button>
<button onclick="wsResetCss()">Zurücksetzen</button>
<button onclick="wsCopyCss()">CSS kopieren</button>
</div>
<div class="ws-meta" style="margin-top:6px;">
Tipp: Strg/⌘+Enter wendet das CSS sofort an. Reset entfernt nur den Override —
die Server-CSS bleibt.
</div>
</div>
<div class="ws-section ws-embed-row">
<h3>Direkt-Link / Embed</h3>
<code id="ws-embed-url"></code>
<div class="ws-meta" style="margin-top:6px;">
Iframe-Snippet:
</div>
<code id="ws-embed-iframe" style="margin-top:4px;"></code>
</div>
</div>
</div>
{% endblock %}
{% block body_scripts %}
<script>
(function () {
var drsSel = document.getElementById('ws-drs');
var iframe = document.getElementById('ws-frame');
var frameWrap = document.getElementById('ws-frame-wrap');
var cssEditor = document.getElementById('ws-css');
var meta = document.getElementById('ws-meta');
var embedUrl = document.getElementById('ws-embed-url');
var embedIframe = document.getElementById('ws-embed-iframe');
var zoomInfo = document.getElementById('ws-zoom-info');
var currentFormat = 'portrait';
var formatDims = {
portrait: [1080, 1350],
square: [1080, 1080],
og: [1200, 630],
};
function getCurrent() {
var v = drsSel.value || '';
var parts = v.split('|');
return { drs: parts[0] || '', bl: parts[1] || 'NRW' };
}
window.wsSetFormat = function (f) {
currentFormat = f;
document.querySelectorAll('.ws-format-pills button').forEach(function (b) {
b.classList.toggle('active', b.dataset.format === f);
});
wsRender();
};
window.wsRender = function () {
var c = getCurrent();
if (!c.drs) return;
var src = '/v2/scorecard'
+ '?drucksache=' + encodeURIComponent(c.drs)
+ '&bundesland=' + encodeURIComponent(c.bl)
+ '&format=' + currentFormat
+ '&_=' + Date.now();
iframe.src = src;
var dims = formatDims[currentFormat];
iframe.width = dims[0];
iframe.height = dims[1];
iframe.style.width = dims[0] + 'px';
iframe.style.height = dims[1] + 'px';
meta.textContent = c.bl + ' · ' + c.drs + ' · ' + currentFormat
+ ' (' + dims[0] + '×' + dims[1] + ')';
var publicUrl = window.location.origin + '/v2/scorecard'
+ '?drucksache=' + encodeURIComponent(c.drs)
+ '&bundesland=' + encodeURIComponent(c.bl)
+ '&format=' + currentFormat;
embedUrl.textContent = publicUrl;
embedIframe.textContent = '<iframe src="' + publicUrl + '" '
+ 'width="' + dims[0] + '" height="' + dims[1] + '" '
+ 'frameborder="0"></iframe>';
setTimeout(wsZoomFit, 100);
iframe.addEventListener('load', wsApplyCss, { once: true });
};
window.wsApplyCss = function () {
var doc = iframe.contentDocument;
if (!doc) return;
var styleId = 'ws-override-style';
var existing = doc.getElementById(styleId);
if (existing) existing.remove();
var style = doc.createElement('style');
style.id = styleId;
style.textContent = cssEditor.value;
doc.head.appendChild(style);
};
window.wsResetCss = function () {
cssEditor.value = '';
wsApplyCss();
};
window.wsCopyCss = function () {
if (navigator.clipboard && cssEditor.value) {
navigator.clipboard.writeText(cssEditor.value);
}
};
window.wsZoom = function (factor) {
iframe.style.transform = 'scale(' + factor + ')';
var dims = formatDims[currentFormat];
frameWrap.style.width = (dims[0] * factor) + 'px';
frameWrap.style.height = (dims[1] * factor) + 'px';
zoomInfo.textContent = Math.round(factor * 100) + '%';
};
window.wsZoomFit = function () {
var avail = document.querySelector('.ws-preview').clientWidth - 30;
var dims = formatDims[currentFormat];
var factor = Math.min(1.0, avail / dims[0]);
factor = Math.round(factor * 100) / 100;
wsZoom(factor);
};
cssEditor.addEventListener('keydown', function (e) {
if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
e.preventDefault();
wsApplyCss();
}
if (e.key === 'Tab') {
e.preventDefault();
var s = this.selectionStart;
this.value = this.value.substring(0, s) + ' ' + this.value.substring(this.selectionEnd);
this.selectionStart = this.selectionEnd = s + 2;
}
});
wsRender();
})();
</script>
{% endblock %}