feat(reports): PDF build infrastructure — premium WeasyPrint template
- report.css: full-bleed navy cover, Padelnomics logo watermark at 3.5% opacity (position:fixed, repeats every page), gold/teal accents, Georgia headings, running headers via CSS named strings, metric boxes, insight-box - report.html: Jinja2 template with cover stats, TOC, body, disclaimer - build_report_pdf.py: builds EN+DE PDFs from data/content/reports/*.md (WeasyPrint, mistune, PyYAML; reads logo as file:// URI for watermark) - Makefile: report-pdf target Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
549
web/src/padelnomics/templates/reports/report.css
Normal file
549
web/src/padelnomics/templates/reports/report.css
Normal file
@@ -0,0 +1,549 @@
|
||||
/* ============================================================
|
||||
Padelnomics Market Intelligence Report — Premium PDF Stylesheet
|
||||
Rendered by WeasyPrint (A4, CSS3, no JavaScript)
|
||||
============================================================ */
|
||||
|
||||
/* ---------- Design tokens ---------- */
|
||||
:root {
|
||||
--navy: #0F2651;
|
||||
--navy-deep: #091a3a;
|
||||
--navy-mid: #1e3a6e;
|
||||
--gold: #C9922C;
|
||||
--gold-lt: #EDD48A;
|
||||
--teal: #0D9488;
|
||||
--teal-lt: #CCFBF1;
|
||||
--text: #1C2333;
|
||||
--muted: #5E6E8A;
|
||||
--border: #DDE3EE;
|
||||
--bg-light: #F6F8FC;
|
||||
--white: #FFFFFF;
|
||||
--green: #166534;
|
||||
--green-bg: #DCFCE7;
|
||||
--red: #991B1B;
|
||||
--red-bg: #FEE2E2;
|
||||
}
|
||||
|
||||
/* ---------- Page setup ---------- */
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 22mm 20mm 22mm 20mm;
|
||||
|
||||
@top-left {
|
||||
content: string(doc-brand);
|
||||
font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif;
|
||||
font-size: 7pt;
|
||||
color: var(--muted);
|
||||
letter-spacing: 0.06em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
@top-right {
|
||||
content: string(doc-title);
|
||||
font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif;
|
||||
font-size: 7pt;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
}
|
||||
@bottom-left {
|
||||
content: "© 2026 Padelnomics — padelnomics.io";
|
||||
font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif;
|
||||
font-size: 6.5pt;
|
||||
color: var(--border);
|
||||
letter-spacing: 0.03em;
|
||||
}
|
||||
@bottom-center {
|
||||
content: counter(page);
|
||||
font-family: 'Gill Sans', 'Trebuchet MS', Calibri, sans-serif;
|
||||
font-size: 8pt;
|
||||
color: var(--muted);
|
||||
}
|
||||
}
|
||||
|
||||
/* Cover page: full-bleed, no running headers/footers */
|
||||
@page cover {
|
||||
size: A4;
|
||||
margin: 0;
|
||||
@top-left { content: none; }
|
||||
@top-right { content: none; }
|
||||
@bottom-left { content: none; }
|
||||
@bottom-center { content: none; }
|
||||
}
|
||||
|
||||
/* TOC page: suppress page number */
|
||||
@page toc {
|
||||
@bottom-center { content: none; }
|
||||
}
|
||||
|
||||
/* ---------- Named strings for running headers ---------- */
|
||||
.doc-brand-anchor { string-set: doc-brand content(); }
|
||||
.doc-title-anchor { string-set: doc-title content(); }
|
||||
|
||||
/* ---------- Watermark (WeasyPrint repeats position:fixed on every content page) ---------- */
|
||||
.watermark {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%) rotate(-30deg);
|
||||
opacity: 0.035;
|
||||
z-index: -1;
|
||||
pointer-events: none;
|
||||
}
|
||||
.watermark img {
|
||||
width: 400pt;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
/* ---------- Base typography ---------- */
|
||||
body {
|
||||
font-family: 'Gill Sans', 'Gill Sans MT', Calibri, 'Trebuchet MS', sans-serif;
|
||||
font-size: 9.5pt;
|
||||
line-height: 1.58;
|
||||
color: var(--text);
|
||||
background: var(--white);
|
||||
}
|
||||
|
||||
p { margin: 0 0 7pt; }
|
||||
|
||||
/* ---------- Cover page — full-bleed navy ---------- */
|
||||
.cover {
|
||||
page: cover;
|
||||
page-break-after: always;
|
||||
width: 210mm;
|
||||
height: 297mm;
|
||||
background: var(--navy);
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
padding: 22mm 20mm 18mm 22mm;
|
||||
}
|
||||
|
||||
/* Decorative geometric accent — bottom-right corner stripe */
|
||||
.cover::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
width: 90mm;
|
||||
height: 90mm;
|
||||
background: linear-gradient(135deg, transparent 50%, rgba(201,146,44,0.12) 50%);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.cover__header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.cover__logo img {
|
||||
height: 22pt;
|
||||
filter: brightness(0) invert(1);
|
||||
opacity: 0.92;
|
||||
}
|
||||
|
||||
.cover__edition {
|
||||
font-size: 7pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gold);
|
||||
background: rgba(201,146,44,0.12);
|
||||
padding: 3pt 9pt;
|
||||
border-radius: 3pt;
|
||||
border: 1pt solid rgba(201,146,44,0.3);
|
||||
}
|
||||
|
||||
.cover__body {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 20mm 0;
|
||||
}
|
||||
|
||||
.cover__type-label {
|
||||
font-size: 8pt;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.18em;
|
||||
text-transform: uppercase;
|
||||
color: var(--gold);
|
||||
margin-bottom: 12pt;
|
||||
}
|
||||
|
||||
.cover__rule {
|
||||
width: 48pt;
|
||||
height: 2.5pt;
|
||||
background: var(--gold);
|
||||
margin-bottom: 16pt;
|
||||
}
|
||||
|
||||
.cover__title {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 32pt;
|
||||
font-weight: 700;
|
||||
color: var(--white);
|
||||
line-height: 1.18;
|
||||
letter-spacing: -0.02em;
|
||||
margin: 0 0 12pt;
|
||||
}
|
||||
|
||||
.cover__subtitle {
|
||||
font-size: 11pt;
|
||||
color: rgba(255,255,255,0.65);
|
||||
margin-bottom: 20pt;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* 4-stat row on cover */
|
||||
.cover__stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 0;
|
||||
border-top: 1pt solid rgba(255,255,255,0.15);
|
||||
border-left: 1pt solid rgba(255,255,255,0.15);
|
||||
border-radius: 4pt;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.cover__stat {
|
||||
padding: 10pt 12pt;
|
||||
border-right: 1pt solid rgba(255,255,255,0.15);
|
||||
border-bottom: 1pt solid rgba(255,255,255,0.15);
|
||||
background: rgba(255,255,255,0.04);
|
||||
}
|
||||
|
||||
.cover__stat-value {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 16pt;
|
||||
font-weight: 700;
|
||||
color: var(--gold);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.cover__stat-label {
|
||||
font-size: 6.5pt;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: rgba(255,255,255,0.5);
|
||||
margin-top: 3pt;
|
||||
}
|
||||
|
||||
.cover__footer {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-end;
|
||||
padding-top: 14pt;
|
||||
border-top: 1pt solid rgba(255,255,255,0.12);
|
||||
}
|
||||
|
||||
.cover__footer-left {
|
||||
font-size: 7.5pt;
|
||||
color: rgba(255,255,255,0.35);
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.cover__footer-right {
|
||||
font-size: 7.5pt;
|
||||
color: rgba(255,255,255,0.35);
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.cover__footer a {
|
||||
color: var(--gold);
|
||||
text-decoration: none;
|
||||
opacity: 0.7;
|
||||
}
|
||||
|
||||
/* ---------- Table of Contents ---------- */
|
||||
.toc-page {
|
||||
page: toc;
|
||||
page-break-after: always;
|
||||
}
|
||||
|
||||
.toc-heading {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 18pt;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
margin: 0 0 18pt;
|
||||
padding-bottom: 8pt;
|
||||
border-bottom: 2pt solid var(--navy);
|
||||
}
|
||||
|
||||
.toc-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.toc-item {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
padding: 5pt 0;
|
||||
border-bottom: 0.5pt dotted var(--border);
|
||||
font-size: 9pt;
|
||||
}
|
||||
|
||||
.toc-item--section {
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
padding-top: 10pt;
|
||||
border-bottom: none;
|
||||
font-size: 9.5pt;
|
||||
padding-bottom: 2pt;
|
||||
}
|
||||
|
||||
.toc-num {
|
||||
font-size: 8pt;
|
||||
color: var(--gold);
|
||||
font-weight: 700;
|
||||
width: 22pt;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.toc-text { flex: 1; color: var(--text); }
|
||||
|
||||
.toc-dots {
|
||||
flex: 1;
|
||||
border-bottom: 1pt dotted var(--border);
|
||||
margin: 0 6pt 2pt;
|
||||
min-width: 20pt;
|
||||
}
|
||||
|
||||
/* ---------- Section headings ---------- */
|
||||
h1 {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 20pt;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
margin: 0 0 6pt;
|
||||
letter-spacing: -0.02em;
|
||||
}
|
||||
|
||||
h2 {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 14pt;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
margin: 0 0 10pt;
|
||||
padding: 8pt 0 8pt 12pt;
|
||||
border-left: 3.5pt solid var(--gold);
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}
|
||||
|
||||
h3 {
|
||||
font-size: 8.5pt;
|
||||
font-weight: 700;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.09em;
|
||||
margin: 14pt 0 5pt;
|
||||
page-break-after: avoid;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-top: 22pt;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.section--break {
|
||||
page-break-before: always;
|
||||
break-before: page;
|
||||
margin-top: 0;
|
||||
padding-top: 6pt;
|
||||
}
|
||||
|
||||
/* ---------- Key metrics grid ---------- */
|
||||
.metrics-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
gap: 7pt;
|
||||
margin: 8pt 0 12pt;
|
||||
}
|
||||
|
||||
.metric-box {
|
||||
border: 1pt solid var(--border);
|
||||
border-top: 3pt solid var(--navy);
|
||||
border-radius: 0 0 4pt 4pt;
|
||||
padding: 8pt 9pt 7pt;
|
||||
text-align: center;
|
||||
background: var(--white);
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.metric-box--gold { border-top-color: var(--gold); }
|
||||
.metric-box--teal { border-top-color: var(--teal); }
|
||||
|
||||
.metric-label {
|
||||
font-size: 6.5pt;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.07em;
|
||||
color: var(--muted);
|
||||
margin-bottom: 4pt;
|
||||
}
|
||||
|
||||
.metric-value {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 16pt;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.metric-value--gold { color: var(--gold); }
|
||||
.metric-value--teal { color: var(--teal); }
|
||||
|
||||
.metric-sub {
|
||||
font-size: 7pt;
|
||||
color: var(--muted);
|
||||
margin-top: 2pt;
|
||||
}
|
||||
|
||||
/* ---------- Tables ---------- */
|
||||
table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 6pt 0 12pt;
|
||||
font-size: 8.5pt;
|
||||
page-break-inside: auto;
|
||||
}
|
||||
|
||||
thead th {
|
||||
background: var(--navy);
|
||||
color: var(--white);
|
||||
font-size: 7pt;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
padding: 5.5pt 8pt;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
tbody tr:nth-child(even) { background: var(--bg-light); }
|
||||
tbody tr:nth-child(odd) { background: var(--white); }
|
||||
|
||||
tbody td {
|
||||
padding: 5pt 8pt;
|
||||
border-bottom: 0.5pt solid var(--border);
|
||||
color: var(--text);
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
tbody tr:last-child td { border-bottom: none; }
|
||||
|
||||
.total-row td {
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
background: #E8EDF5 !important;
|
||||
border-top: 1.5pt solid var(--navy);
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.highlight-row td {
|
||||
background: #FEF9EE !important;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
border-top: 1pt solid var(--gold);
|
||||
border-bottom: 1pt solid var(--gold);
|
||||
}
|
||||
|
||||
.positive-cell { color: var(--green); font-weight: 600; }
|
||||
.negative-cell { color: var(--red); font-weight: 600; }
|
||||
|
||||
td.r, th.r { text-align: right; }
|
||||
td.c, th.c { text-align: center; }
|
||||
|
||||
/* ---------- Callout / pull quote ---------- */
|
||||
blockquote {
|
||||
border-left: 4pt solid var(--teal);
|
||||
background: var(--teal-lt);
|
||||
padding: 9pt 12pt;
|
||||
margin: 8pt 0 12pt;
|
||||
font-style: italic;
|
||||
font-size: 9.5pt;
|
||||
color: var(--text);
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
blockquote p { margin: 0; }
|
||||
|
||||
/* ---------- Insight box (key takeaway) ---------- */
|
||||
.insight-box {
|
||||
border: 1pt solid var(--gold);
|
||||
border-left: 4pt solid var(--gold);
|
||||
background: #FEF9EE;
|
||||
padding: 9pt 12pt;
|
||||
margin: 8pt 0 12pt;
|
||||
font-size: 9pt;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.insight-box strong {
|
||||
display: block;
|
||||
color: var(--navy);
|
||||
font-size: 8pt;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
margin-bottom: 3pt;
|
||||
}
|
||||
|
||||
/* ---------- Market callout cards ---------- */
|
||||
.market-callout {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 8pt;
|
||||
margin: 8pt 0 12pt;
|
||||
}
|
||||
|
||||
.market-stat {
|
||||
background: var(--bg-light);
|
||||
border: 1pt solid var(--border);
|
||||
border-radius: 4pt;
|
||||
padding: 8pt 10pt;
|
||||
text-align: center;
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.market-stat-value {
|
||||
font-family: Georgia, 'Times New Roman', serif;
|
||||
font-size: 14pt;
|
||||
font-weight: 700;
|
||||
color: var(--navy);
|
||||
}
|
||||
|
||||
.market-stat-label {
|
||||
font-size: 7pt;
|
||||
color: var(--muted);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
margin-top: 2pt;
|
||||
}
|
||||
|
||||
/* ---------- Data note / footnote ---------- */
|
||||
.data-note {
|
||||
font-size: 7.5pt;
|
||||
color: var(--muted);
|
||||
font-style: italic;
|
||||
margin: -8pt 0 10pt;
|
||||
padding-left: 6pt;
|
||||
border-left: 2pt solid var(--border);
|
||||
}
|
||||
|
||||
/* ---------- Two-column layout helper ---------- */
|
||||
.two-col {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 12pt;
|
||||
}
|
||||
|
||||
/* ---------- Disclaimer / legal ---------- */
|
||||
.disclaimer {
|
||||
font-size: 7pt;
|
||||
color: var(--muted);
|
||||
line-height: 1.55;
|
||||
padding-top: 10pt;
|
||||
border-top: 0.5pt solid var(--border);
|
||||
margin-top: 20pt;
|
||||
}
|
||||
Reference in New Issue
Block a user