upload
This commit is contained in:
@@ -0,0 +1,373 @@
|
||||
<!doctype html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Monatsreport {{ data.report.period.label }} - {{ data.customer.name }}</title>
|
||||
<style>
|
||||
:root {
|
||||
--primary: {{ data.design.primary_color }};
|
||||
--secondary: {{ data.design.secondary_color }};
|
||||
--accent: {{ data.design.accent_color }};
|
||||
--text: {{ data.design.text_color }};
|
||||
--muted: {{ data.design.muted_color }};
|
||||
--surface: {{ data.design.surface_color }};
|
||||
--line: #dfe5e8;
|
||||
--positive: #207a5a;
|
||||
--neutral: #667680;
|
||||
--negative: #b14949;
|
||||
--watch: #9a6815;
|
||||
}
|
||||
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 13mm 14mm 18mm 14mm;
|
||||
}
|
||||
|
||||
* { box-sizing: border-box; }
|
||||
html { -webkit-print-color-adjust: exact; print-color-adjust: exact; }
|
||||
body {
|
||||
margin: 0;
|
||||
color: var(--text);
|
||||
font-family: {{ data.design.font_family }}, Arial, sans-serif;
|
||||
font-size: 9.3pt;
|
||||
line-height: 1.48;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
h1, h2, h3, p { margin-top: 0; }
|
||||
h1 { font-size: 27pt; line-height: 1.08; letter-spacing: -0.6px; margin-bottom: 2mm; }
|
||||
h2 { font-size: 14.5pt; line-height: 1.2; margin-bottom: 4mm; }
|
||||
h3 { font-size: 10.8pt; line-height: 1.25; margin-bottom: 1.5mm; }
|
||||
p { margin-bottom: 3mm; }
|
||||
|
||||
.cover {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
min-height: 67mm;
|
||||
padding: 9mm;
|
||||
border-radius: 5mm;
|
||||
color: #fff;
|
||||
background: linear-gradient(128deg, var(--primary), var(--secondary));
|
||||
margin-bottom: 7mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.cover::after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 92mm;
|
||||
height: 92mm;
|
||||
right: -27mm;
|
||||
top: -42mm;
|
||||
border: 12mm solid rgba(255,255,255,.08);
|
||||
border-radius: 50%;
|
||||
}
|
||||
.cover-top { display: flex; align-items: center; justify-content: space-between; gap: 8mm; position: relative; z-index: 1; }
|
||||
.brand { display: flex; align-items: center; gap: 4mm; }
|
||||
.logo {
|
||||
width: 15mm; height: 15mm; border-radius: 4mm;
|
||||
background: rgba(255,255,255,.96); color: var(--primary);
|
||||
display: flex; align-items: center; justify-content: center; font-size: 15pt; font-weight: 800;
|
||||
box-shadow: 0 2mm 5mm rgba(0,0,0,.12);
|
||||
overflow: hidden;
|
||||
}
|
||||
.logo img { width: 100%; height: 100%; object-fit: contain; padding: 1.6mm; }
|
||||
.brand-name { font-size: 11pt; font-weight: 700; }
|
||||
.brand-sub { font-size: 8pt; opacity: .82; }
|
||||
.period-chip {
|
||||
padding: 2.2mm 4mm; border-radius: 99mm;
|
||||
border: .25mm solid rgba(255,255,255,.38);
|
||||
background: rgba(255,255,255,.12); font-size: 8.3pt; font-weight: 700;
|
||||
}
|
||||
.cover-main { position: relative; z-index: 1; margin-top: 13mm; max-width: 142mm; }
|
||||
.eyebrow { text-transform: uppercase; letter-spacing: 1.7px; font-size: 7.2pt; font-weight: 800; opacity: .78; margin-bottom: 2.2mm; }
|
||||
.cover-meta { display: flex; flex-wrap: wrap; gap: 3mm 8mm; font-size: 8.4pt; opacity: .88; }
|
||||
|
||||
.section { margin-bottom: 7mm; }
|
||||
.section-heading { display: flex; align-items: flex-end; justify-content: space-between; gap: 5mm; margin-bottom: 3.5mm; }
|
||||
.section-heading h2 { margin: 0; }
|
||||
.section-kicker { font-size: 7.3pt; font-weight: 800; color: var(--muted); text-transform: uppercase; letter-spacing: 1.2px; margin-bottom: 1mm; }
|
||||
|
||||
.summary {
|
||||
display: grid; grid-template-columns: 10mm 1fr; gap: 4mm;
|
||||
padding: 5mm; border-radius: 3mm; background: var(--surface);
|
||||
border-left: 1.5mm solid var(--accent); break-inside: avoid;
|
||||
}
|
||||
.summary-icon {
|
||||
width: 9mm; height: 9mm; border-radius: 50%;
|
||||
display: flex; align-items: center; justify-content: center; align-self: start; color: #fff; font-weight: 900; margin-top: .5mm;
|
||||
}
|
||||
.summary-icon.positive { background: var(--positive); }
|
||||
.summary-icon.neutral { background: var(--neutral); }
|
||||
.summary-icon.watch { background: var(--watch); }
|
||||
.summary h2 { font-size: 14pt; margin-bottom: 2mm; }
|
||||
.summary p { margin-bottom: 0; }
|
||||
|
||||
.kpi-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 3mm; }
|
||||
.kpi-card {
|
||||
min-height: 31mm; padding: 4mm; border: .25mm solid var(--line);
|
||||
border-radius: 3mm; break-inside: avoid; background: #fff;
|
||||
}
|
||||
.kpi-label { font-size: 7.3pt; text-transform: uppercase; letter-spacing: .8px; color: var(--muted); font-weight: 800; }
|
||||
.kpi-value { font-size: 16pt; font-weight: 800; color: var(--primary); line-height: 1.15; margin: 3mm 0 1.5mm; white-space: nowrap; }
|
||||
.kpi-delta { font-size: 7.6pt; color: var(--muted); }
|
||||
.delta-positive { color: var(--positive); font-weight: 700; }
|
||||
.delta-negative { color: var(--negative); font-weight: 700; }
|
||||
|
||||
.detail-strip {
|
||||
display: grid; grid-template-columns: repeat(4, 1fr); gap: 0;
|
||||
border: .25mm solid var(--line); border-radius: 3mm; overflow: hidden; margin-top: 3mm;
|
||||
}
|
||||
.detail-item { padding: 3mm 4mm; border-right: .25mm solid var(--line); }
|
||||
.detail-item:last-child { border-right: 0; }
|
||||
.detail-label { font-size: 7.2pt; color: var(--muted); }
|
||||
.detail-value { font-weight: 800; margin-top: .8mm; }
|
||||
|
||||
.trend-panel {
|
||||
border: .25mm solid var(--line); border-radius: 3mm; padding: 5mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.trend-legend { display: flex; gap: 5mm; font-size: 7.4pt; color: var(--muted); }
|
||||
.legend-dot { width: 2.2mm; height: 2.2mm; border-radius: 50%; display: inline-block; margin-right: 1.2mm; }
|
||||
.legend-revenue { background: var(--primary); }
|
||||
.legend-occ { background: var(--accent); }
|
||||
.trend-grid { display: block; margin-top: 3mm; }
|
||||
.trend-row { display: grid; grid-template-columns: 20mm 1fr 25mm 25mm; align-items: center; gap: 2mm; margin-top: 2mm; }
|
||||
.trend-label { font-weight: 700; font-size: 8pt; }
|
||||
.bar-track { height: 4mm; border-radius: 99mm; background: #edf1f3; overflow: hidden; position: relative; }
|
||||
.bar { height: 100%; border-radius: inherit; }
|
||||
.bar-revenue { background: var(--primary); }
|
||||
.bar-occ { background: var(--accent); height: 1.5mm; position: absolute; left: 0; bottom: 0; }
|
||||
.trend-number { text-align: right; font-size: 7.7pt; font-variant-numeric: tabular-nums; }
|
||||
|
||||
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 5mm; align-items: start; }
|
||||
.development-list { display: grid; gap: 3mm; }
|
||||
.development {
|
||||
border: .25mm solid var(--line); border-radius: 3mm; padding: 4mm;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.development-top { display: flex; align-items: center; gap: 2mm; margin-bottom: 1.5mm; }
|
||||
.impact-dot { width: 2.5mm; height: 2.5mm; border-radius: 50%; flex: 0 0 auto; }
|
||||
.impact-dot.positive { background: var(--positive); }
|
||||
.impact-dot.neutral { background: var(--neutral); }
|
||||
.impact-dot.negative { background: var(--negative); }
|
||||
.development p { margin: 0; color: #35434a; }
|
||||
|
||||
.recommendations { display: grid; gap: 3.5mm; }
|
||||
.recommendation {
|
||||
display: grid; grid-template-columns: 11mm 1fr; gap: 4mm;
|
||||
padding: 4.5mm; border-radius: 3mm; background: var(--surface);
|
||||
border: .25mm solid #d7e1e2;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.priority {
|
||||
width: 10mm; height: 10mm; border-radius: 3mm; background: var(--primary); color: #fff;
|
||||
display: flex; align-items: center; justify-content: center; align-self: start; font-weight: 900; font-size: 12pt;
|
||||
}
|
||||
.category { color: var(--primary); font-size: 7.2pt; font-weight: 900; text-transform: uppercase; letter-spacing: .8px; }
|
||||
.recommendation h3 { font-size: 11.3pt; margin: .8mm 0 2mm; }
|
||||
.recommendation p { margin-bottom: 2mm; }
|
||||
.recommendation p:last-child { margin-bottom: 0; }
|
||||
.action-line { padding-top: 2mm; border-top: .25mm solid rgba(0,0,0,.08); }
|
||||
|
||||
.outlook {
|
||||
padding: 5mm; color: #fff; border-radius: 3mm;
|
||||
background: linear-gradient(115deg, var(--primary), var(--secondary));
|
||||
break-inside: avoid;
|
||||
}
|
||||
.outlook h2 { margin-bottom: 2mm; }
|
||||
.outlook p { margin: 0; opacity: .94; }
|
||||
|
||||
.notes {
|
||||
margin-top: 4mm; padding: 3.5mm 4mm; border-radius: 2mm;
|
||||
background: #f7f8f9; color: var(--muted); font-size: 7.5pt;
|
||||
break-inside: avoid;
|
||||
}
|
||||
.notes ul { margin: 1.5mm 0 0; padding-left: 4mm; }
|
||||
.notes li { margin-bottom: .8mm; }
|
||||
.legal { margin-top: 4mm; color: var(--muted); font-size: 6.9pt; }
|
||||
|
||||
.page-break { break-before: page; }
|
||||
.avoid { break-inside: avoid; }
|
||||
|
||||
@media print {
|
||||
.section:last-child { margin-bottom: 0; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<section class="cover">
|
||||
<div class="cover-top">
|
||||
<div class="brand">
|
||||
<div class="logo">
|
||||
{% if data.customer.logo_data_uri %}
|
||||
<img src="{{ data.customer.logo_data_uri }}" alt="Logo">
|
||||
{% else %}
|
||||
{{ data.customer.logo_text }}
|
||||
{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="brand-name">{{ data.customer.name }}</div>
|
||||
<div class="brand-sub">{{ data.customer.property_name }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="period-chip">{{ data.report.period.label }}</div>
|
||||
</div>
|
||||
<div class="cover-main">
|
||||
<div class="eyebrow">Performance & Handlungsempfehlungen</div>
|
||||
<h1>Monatsreport</h1>
|
||||
<div class="cover-meta">
|
||||
<span>{{ data.customer.location }}</span>
|
||||
<span>Erstellt am {{ data.report.generated_at|date_de }}</span>
|
||||
<span>Version {{ data.report.report_version }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="summary">
|
||||
<div class="summary-icon {{ data.content.summary.rating }}">
|
||||
{% if data.content.summary.rating == 'positive' %}↑{% elif data.content.summary.rating == 'watch' %}!{% else %}•{% endif %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="section-kicker">Kurzfazit</div>
|
||||
<h2>{{ data.content.summary.headline }}</h2>
|
||||
<p>{{ data.content.summary.text }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<div class="section-kicker">Kennzahlen</div>
|
||||
<h2>Der Monat auf einen Blick</h2>
|
||||
</div>
|
||||
<div class="section-kicker">Vergleich: {{ data.report.period.comparison_label }}</div>
|
||||
</div>
|
||||
<div class="kpi-grid">
|
||||
<article class="kpi-card">
|
||||
<div class="kpi-label">Umsatz</div>
|
||||
<div class="kpi-value">{{ data.metrics.revenue|currency_de(data.metrics.currency) }}</div>
|
||||
<div class="kpi-delta {% if data.metrics.comparison.revenue_delta_pct >= 0 %}delta-positive{% else %}delta-negative{% endif %}">
|
||||
{{ data.metrics.comparison.revenue_delta_pct|delta_de(1, '%') }}
|
||||
</div>
|
||||
</article>
|
||||
<article class="kpi-card">
|
||||
<div class="kpi-label">Auslastung</div>
|
||||
<div class="kpi-value">{{ data.metrics.occupancy_rate|percent_de }}</div>
|
||||
<div class="kpi-delta {% if data.metrics.comparison.occupancy_delta_pp >= 0 %}delta-positive{% else %}delta-negative{% endif %}">
|
||||
{{ data.metrics.comparison.occupancy_delta_pp|delta_de(1, 'PP') }}
|
||||
</div>
|
||||
</article>
|
||||
<article class="kpi-card">
|
||||
<div class="kpi-label">Ø Tagespreis</div>
|
||||
<div class="kpi-value">{{ data.metrics.adr|currency_de(data.metrics.currency) }}</div>
|
||||
<div class="kpi-delta {% if data.metrics.comparison.adr_delta_pct >= 0 %}delta-positive{% else %}delta-negative{% endif %}">
|
||||
{{ data.metrics.comparison.adr_delta_pct|delta_de(1, '%') }}
|
||||
</div>
|
||||
</article>
|
||||
<article class="kpi-card">
|
||||
<div class="kpi-label">Buchungen</div>
|
||||
<div class="kpi-value">{{ data.metrics.bookings|number_de(0) }}</div>
|
||||
<div class="kpi-delta {% if data.metrics.comparison.bookings_delta >= 0 %}delta-positive{% else %}delta-negative{% endif %}">
|
||||
{{ data.metrics.comparison.bookings_delta|delta_de(0, '') }} Buchungen
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<div class="detail-strip">
|
||||
<div class="detail-item"><div class="detail-label">RevPAR</div><div class="detail-value">{{ data.metrics.revpar|currency_de(data.metrics.currency) }}</div></div>
|
||||
<div class="detail-item"><div class="detail-label">Verkaufte Nächte</div><div class="detail-value">{{ data.metrics.nights_sold|number_de(0) }}</div></div>
|
||||
<div class="detail-item"><div class="detail-label">Ø Aufenthaltsdauer</div><div class="detail-value">{{ data.metrics.average_stay|number_de(1) }} Nächte</div></div>
|
||||
<div class="detail-item"><div class="detail-label">Stornoquote</div><div class="detail-value">{{ data.metrics.cancellation_rate|percent_de }}</div></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<div class="section-kicker">Verlauf</div>
|
||||
<h2>Entwicklung der letzten Monate</h2>
|
||||
</div>
|
||||
<div class="trend-legend">
|
||||
<span><i class="legend-dot legend-revenue"></i> Umsatz</span>
|
||||
<span><i class="legend-dot legend-occ"></i> Auslastung</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="trend-panel">
|
||||
<div class="trend-grid">
|
||||
{% for item in data.trends %}
|
||||
<div class="trend-row">
|
||||
<div class="trend-label">{{ item.label }}</div>
|
||||
<div class="bar-track">
|
||||
<div class="bar bar-revenue" style="width: {{ item.revenue_bar }}%"></div>
|
||||
<div class="bar bar-occ" style="width: {{ item.occupancy_bar }}%"></div>
|
||||
</div>
|
||||
<div class="trend-number">{{ item.revenue|currency_de(data.metrics.currency) }}</div>
|
||||
<div class="trend-number">{{ item.occupancy_rate|percent_de }}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<div class="section-kicker">Analyse</div>
|
||||
<h2>Wichtige Entwicklungen</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="two-col">
|
||||
{% for item in data.content.developments %}
|
||||
<article class="development">
|
||||
<div class="development-top"><span class="impact-dot {{ item.impact }}"></span><h3>{{ item.title }}</h3></div>
|
||||
<p>{{ item.text }}</p>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<div class="section-kicker">Naechste Schritte</div>
|
||||
<h2>Priorisierte Empfehlungen</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="recommendations">
|
||||
{% for item in data.content.recommendations|sort(attribute='priority') %}
|
||||
<article class="recommendation">
|
||||
<div class="priority">{{ item.priority }}</div>
|
||||
<div>
|
||||
<div class="category">{{ item.category }}</div>
|
||||
<h3>{{ item.title }}</h3>
|
||||
<p><strong>Warum:</strong> {{ item.reason }}</p>
|
||||
<p class="action-line"><strong>Massnahme:</strong> {{ item.action }}</p>
|
||||
<p><strong>Erwarteter Effekt:</strong> {{ item.expected_effect }}</p>
|
||||
</div>
|
||||
</article>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="section">
|
||||
<div class="outlook">
|
||||
<div class="section-kicker" style="color: rgba(255,255,255,.7)">Ausblick</div>
|
||||
<h2>{{ data.content.outlook.headline }}</h2>
|
||||
<p>{{ data.content.outlook.text }}</p>
|
||||
</div>
|
||||
{% if data.content.data_quality_notes %}
|
||||
<div class="notes">
|
||||
<strong>Hinweise zur Datengrundlage</strong>
|
||||
<ul>
|
||||
{% for note in data.content.data_quality_notes %}<li>{{ note }}</li>{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="legal">{{ data.legal_note }}</div>
|
||||
</section>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user