Operations Summary
Those are this report's own sheets, printed by the engine from the template and the payload below — not screenshots kept beside them.
The source
This is the whole thing — the template, the pages that print from it, and the stylesheet
they are dressed in, as HTML with {{ }} bindings. Nothing is generated from a
designer format; what you see here is what the engine renders.
template.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="assets/css/report.css">
</head>
<body>
<script id="report-data" type="application/json">{{json}}</script>
<template id="report-header">
<header class="masthead">
<div class="title">
<h1>{{report.title}}</h1>
<p>{{report.owner}}</p>
</div>
<div class="period">
<span class="label">Reporting period</span>
<span class="value">{{report.period}}</span>
</div>
</header>
</template>
<template id="report-footer">
<div class="pagefoot">
<span class="mark">{{report.title}}</span>
<span>{{report.period}} — generated {{date report.generatedOn "dd MMM yyyy" "en-GB"}}</span>
<span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
</div>
</template>
</body>
</html>
page1.html
<div class="sheet">
<section class="kpis">
{{#each kpis}}
<article class="kpi {{#if good}}kpi--good{{else}}kpi--watch{{/if}}">
<span class="kpi-label">{{label}}</span>
<span class="kpi-value">{{value}}</span>
<span class="kpi-caption">
{{#if (eq trend "up")}}▲{{/if}}{{#if (eq trend "down")}}▼{{/if}}
{{caption}}
</span>
</article>
{{/each}}
</section>
<h2 class="section-title">Service performance by site</h2>
<table class="grid">
<thead>
<tr>
<th>Site</th>
<th>Region</th>
<th class="num">Tickets</th>
<th class="num">Resolved</th>
<th class="num">Avg. response</th>
<th class="num">Uptime</th>
<th class="num">SLA breaches</th>
<th class="state">Status</th>
</tr>
</thead>
<tbody>
{{#each sites}}
<tr>
<td class="name">{{name}}</td>
<td class="muted">{{region}}</td>
<td class="num">{{number tickets "N0" "en-US"}}</td>
<td class="num">{{number resolved "N0" "en-US"}}</td>
<td class="num">{{number avgResponseHours "N1" "en-US"}} h</td>
<td class="num">{{number uptime "N2" "en-US"}}%</td>
<td class="num {{#if (gt breaches 0)}}is-bad{{/if}}">{{number breaches "N0" "en-US"}}</td>
<td class="state">
<span class="pill
{{#if (eq status "Healthy")}}pill--good{{/if}}
{{#if (eq status "Watch")}}pill--watch{{/if}}
{{#if (eq status "At risk")}}pill--bad{{/if}}">{{status}}</span>
</td>
</tr>
{{/each}}
</tbody>
</table>
{{#if commentary}}
<section class="commentary">
<h2 class="section-title">Commentary</h2>
<ul>
{{#each commentary}}
<li>{{this}}</li>
{{/each}}
</ul>
</section>
{{/if}}
</div>
assets/css/report.css
body
{
margin: 0;
font: 10pt "Segoe UI", system-ui, sans-serif;
color: #1f2430;
}
.sheet
{
padding: 24mm 14mm 16mm;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}
/* ---------- masthead ---------- */
.masthead
{
height: 17mm;
box-sizing: border-box;
padding: 7mm 14mm 2.5mm;
background: #fff;
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 10mm;
border-bottom: 0.7mm solid #1f2a44;
}
.masthead h1
{
margin: 0;
font-size: 14pt;
font-weight: 700;
letter-spacing: -0.01em;
color: #1f2a44;
}
.masthead .title p
{
margin: 0.8mm 0 0;
font-size: 8.5pt;
color: #6b7280;
}
.period
{
text-align: right;
line-height: 1.25;
}
.period .label
{
display: block;
font-size: 7.5pt;
letter-spacing: 0.16em;
text-transform: uppercase;
color: #6b7280;
}
.period .value
{
display: block;
font-size: 11.5pt;
font-weight: 700;
color: #1f2a44;
}
/* ---------- the measures ---------- */
.kpis
{
display: grid;
grid-template-columns: repeat(auto-fit, minmax(52mm, 1fr));
gap: 5mm;
margin-bottom: 5mm;
}
.kpi
{
padding: 4mm 5mm;
border: 0.3mm solid #d7dbe3;
border-radius: 2mm;
border-top: 1.2mm solid #94a3b8;
background: #f9fafb;
break-inside: avoid;
}
.kpi--good { border-top-color: #0f8a52; }
.kpi--watch { border-top-color: #c2620f; }
.kpi-label
{
display: block;
font-size: 8pt;
letter-spacing: 0.13em;
text-transform: uppercase;
color: #6b7280;
}
.kpi-value
{
display: block;
margin: 1.5mm 0 1mm;
font-size: 20pt;
font-weight: 700;
line-height: 1;
letter-spacing: -0.02em;
color: #1f2a44;
font-variant-numeric: tabular-nums;
}
.kpi-caption
{
display: block;
font-size: 8.5pt;
color: #6b7280;
}
.kpi--good .kpi-caption { color: #0b6b40; }
.kpi--watch .kpi-caption { color: #8a4409; }
/* ---------- headings ---------- */
.section-title
{
margin: 0 0 3mm;
font-size: 8.5pt;
letter-spacing: 0.14em;
text-transform: uppercase;
color: #4b5563;
}
/* ---------- the table ---------- */
.grid
{
width: 100%;
border-collapse: collapse;
font-size: 9pt;
}
.grid thead
{
display: table-header-group;
}
/* A row is the unit here: one split across two sheets reads as two half-rows. */
.grid tr
{
break-inside: avoid;
}
.grid th
{
text-align: left;
font-size: 7.5pt;
letter-spacing: 0.11em;
text-transform: uppercase;
color: #4b5563;
padding: 0 2.5mm 1.6mm;
border-bottom: 0.5mm solid #1f2a44;
white-space: nowrap;
}
.grid td
{
padding: 1.7mm 2.5mm;
border-bottom: 0.2mm solid #e4e7ee;
}
/* Zebra survives print only because the report asks for backgrounds; see printBackground. */
.grid tbody tr:nth-child(even) td
{
background: #f7f8fa;
}
.grid .name
{
font-weight: 600;
}
.grid .muted
{
color: #6b7280;
}
.grid .num
{
text-align: right;
white-space: nowrap;
font-variant-numeric: tabular-nums;
}
.grid .is-bad
{
color: #b3261e;
font-weight: 700;
}
.grid .state
{
text-align: right;
width: 26mm;
}
/* ---------- status pills ---------- */
.pill
{
display: inline-block;
padding: 0.8mm 2.6mm;
border-radius: 4mm;
font-size: 8pt;
font-weight: 600;
white-space: nowrap;
background: #eef1f5;
color: #4b5563;
}
.pill--good { background: #eefaf3; color: #0b6b40; }
.pill--watch { background: #fff5ed; color: #8a4409; }
.pill--bad { background: #fdecea; color: #a01c14; }
/* ---------- commentary ---------- */
.commentary
{
margin-top: 5mm;
break-inside: avoid;
}
.commentary ul
{
margin: 0;
padding-left: 5mm;
color: #4b5563;
line-height: 1.5;
font-size: 9.5pt;
}
.commentary li + li
{
margin-top: 1.2mm;
}
/* ---------- the running footer ---------- */
.pagefoot
{
width: 100%;
padding: 0 14mm;
box-sizing: border-box;
font: 7.5pt "Segoe UI", system-ui, sans-serif;
color: #6b7280;
display: flex;
justify-content: space-between;
gap: 6mm;
}
.pagefoot .mark
{
font-weight: 600;
color: #1f2a44;
}
shared-assets/css/house.css
/*
The project's house style: what every report in it looks like before it looks like itself.
It is in shared-assets rather than in a report because it is the project's, not one report's —
the palette, the type, the sheet, the running header, and the handful of parts (card, chip, pill,
meter, table) that every report here is assembled from. A report's own stylesheet composes these
into its layout; it does not redecide what a card is.
A template references it as assets/css/house.css, exactly as it references its own files. The
shared folder is searched behind the report's own, so the path means the same thing in every
report — and a report that needs its own version keeps a file at that path and wins.
*/
:root
{
/* Named for the job, not the hue, so a rebrand is this block and nothing else. */
--ink: #0f172a;
--ink-soft: #475569;
--ink-faint: #6b7a90;
--ink-invert: #ffffff;
--page: #ffffff;
--panel: #ffffff;
--panel-2: #f8fafc;
--rule: #e2e8f0;
--rule-soft: #eef2f7;
--brand: #0b1f3a;
--brand-2: #12325c;
--accent: #1d4ed8;
--accent-soft: #eef3ff;
--cyan: #0ea5e9;
--good: #047857;
--good-soft: #e7f6f0;
--warn: #b45309;
--warn-soft: #fdf3e3;
--bad: #b91c1c;
--bad-soft: #fdeceb;
--idle: #64748b;
--idle-soft: #f1f5f9;
--radius: 7px;
--radius-sm: 4px;
}
body
{
margin: 0;
color: var(--ink);
background: var(--page);
/*
No Arabic face here, and no Arabic font in shared-assets at all. Naming one for every report in
the project meant seven reports asked for a typeface none of them loads, so what they actually
printed in was the second name in the list. The one Arabic report carries the family and the
files itself.
*/
font-family: "Segoe UI", Tahoma, sans-serif;
font-size: 11px;
line-height: 1.55;
/* Every figure in this project is read down a column, so digits are the same width. */
font-variant-numeric: tabular-nums;
-webkit-font-smoothing: antialiased;
}
/*
The sheet.
The engine prints with no page margin, so the edges are the report's own to leave. Cloned decoration
keeps the padding on every sheet a long page runs onto rather than only the first, and the top
padding is what the fixed running header sits in — it reserves no room of its own.
*/
.sheet
{
box-sizing: border-box;
width: 794px;
min-height: 1123px;
padding: 96px 40px 44px;
-webkit-box-decoration-break: clone;
box-decoration-break: clone;
}
/* ---------- the running header, on every sheet ---------- */
/*
The engine fixes #report-page-header to the top of the sheet; everything below is this project's
band inside it. A band rather than a line of text: it is the first thing on every page and the
only thing carrying the mark, and at 76px it is still under a fifteenth of the sheet.
*/
#report-page-header
{
height: 76px;
background: linear-gradient(105deg, var(--brand) 0%, var(--brand-2) 62%, #17416f 100%);
color: var(--ink-invert);
}
.masthead
{
display: flex;
align-items: center;
gap: 14px;
height: 73px;
padding: 0 40px;
box-sizing: border-box;
}
/* The accent edge, under the band. Three pixels is enough to read as deliberate. */
#report-page-header::after
{
content: "";
display: block;
height: 3px;
background: linear-gradient(to left, var(--accent), var(--cyan) 55%, rgba(14, 165, 233, 0));
}
.masthead .logo
{
width: 30px;
height: 30px;
flex: 0 0 auto;
}
.masthead .titles
{
display: flex;
flex-direction: column;
gap: 1px;
min-width: 0;
}
.masthead .org
{
font-size: 12.5px;
font-weight: 700;
letter-spacing: -.005em;
}
.masthead .subject
{
font-size: 10.5px;
color: #a9c2e0;
}
.masthead .meta
{
margin-inline-start: auto;
display: flex;
flex-direction: column;
align-items: flex-end;
gap: 2px;
text-align: end;
white-space: nowrap;
}
.masthead .period
{
font-size: 11px;
font-weight: 600;
}
/* A quiet outline rather than a filled badge: it is a caveat, not a headline. */
.masthead .classification
{
padding: 1px 7px;
border: 1px solid rgba(255, 255, 255, .34);
border-radius: 999px;
font-size: 9px;
color: #cddcee;
}
/* ---------- the page's own heading, under the band ---------- */
.pagehead
{
display: flex;
align-items: flex-end;
gap: 12px;
padding-bottom: 9px;
border-bottom: 1px solid var(--rule);
}
.pagehead h1
{
margin: 0;
font-size: 19px;
font-weight: 700;
letter-spacing: -.015em;
}
.pagehead .lede
{
margin: 3px 0 0;
color: var(--ink-soft);
font-size: 10.5px;
}
.pagehead .stamp
{
margin-inline-start: auto;
color: var(--ink-faint);
font-size: 10px;
white-space: nowrap;
}
/* ---------- parts ---------- */
.card
{
box-sizing: border-box;
padding: 12px 14px 10px;
background: var(--panel);
border: 1px solid var(--rule);
border-radius: var(--radius);
}
.card-head
{
display: flex;
align-items: baseline;
gap: 8px;
margin-bottom: 8px;
}
.card h2
{
margin: 0;
font-size: 12.5px;
font-weight: 700;
}
.card .note
{
margin-inline-start: auto;
color: var(--ink-faint);
font-size: 9.5px;
white-space: nowrap;
}
/* A chart draws itself left to right whatever the page does, and its own labels sit inside it. */
.chart
{
width: 100%;
direction: ltr;
}
.chip
{
display: inline-flex;
align-items: center;
gap: 4px;
padding: 1px 7px;
border-radius: 999px;
font-size: 9.5px;
font-weight: 700;
line-height: 1.7;
white-space: nowrap;
}
.chip--good { background: var(--good-soft); color: var(--good); }
.chip--bad { background: var(--bad-soft); color: var(--bad); }
.chip--warn { background: var(--warn-soft); color: var(--warn); }
.chip--idle { background: var(--idle-soft); color: var(--idle); }
/*
A status, as a word with a dot rather than as a filled block: eight of them down a table column
would otherwise be eight blocks of colour competing with the figures beside them.
*/
.pill
{
display: inline-flex;
align-items: center;
gap: 5px;
font-size: 10px;
font-weight: 600;
white-space: nowrap;
}
.pill::before
{
content: "";
width: 7px;
height: 7px;
border-radius: 50%;
background: currentColor;
flex: 0 0 auto;
}
.pill--good { color: var(--good); }
.pill--warn { color: var(--warn); }
.pill--bad { color: var(--bad); }
.pill--idle { color: var(--idle); }
.pill--done { color: var(--accent); }
/*
A proportion, drawn rather than written. Vector, so it stays sharp where a canvas would not — the
small measures are the ones a reader leans in on.
*/
.meter
{
height: 5px;
border-radius: 3px;
background: var(--rule-soft);
overflow: hidden;
}
.meter > span
{
display: block;
height: 100%;
border-radius: 3px;
background: var(--accent);
}
.meter--good > span { background: var(--good); }
.meter--warn > span { background: var(--warn); }
.meter--bad > span { background: var(--bad); }
/* ---------- tables ---------- */
/*
Fixed layout and a header group, which is the pair that keeps a long table on the sheet: without
the first, columns size to their content and the far one runs off the paper; without the second,
a table that paginates leaves the next sheet as unlabelled columns of numbers.
*/
table.grid
{
width: 100%;
table-layout: fixed;
border-collapse: collapse;
font-size: 10.5px;
}
table.grid thead { display: table-header-group; }
table.grid tr { break-inside: avoid; }
/*
Border-box, because a fixed layout takes the width it is given as the whole column: with padding
added on top instead, six columns quietly cost another 96px and the one column left on auto pays
for all of it.
*/
table.grid th,
table.grid td { box-sizing: border-box; }
table.grid th
{
padding: 6px 8px;
background: var(--panel-2);
border-bottom: 1px solid var(--rule);
color: var(--ink-soft);
font-size: 9.5px;
font-weight: 700;
text-align: start;
white-space: nowrap;
}
table.grid td
{
padding: 6px 8px;
border-bottom: 1px solid var(--rule-soft);
vertical-align: middle;
overflow-wrap: break-word;
}
table.grid tbody tr:last-child td { border-bottom: 0; }
.num
{
text-align: end;
font-weight: 600;
}
/* ---------- the foot of the page ---------- */
.footnote
{
margin-top: 12px;
padding-top: 8px;
border-top: 1px solid var(--rule);
color: var(--ink-faint);
font-size: 9.5px;
}
/*
The running footer, which the browser draws rather than the page.
It is a document of its own at print time: this stylesheet is read and sent along with it, so type
and layout arrive, but nothing it would have to fetch does — no webfont, no image, no background.
So it is set in whatever face the renderer has, and kept to figures and a code, which read the same
in any of them.
*/
.pagefoot
{
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
padding: 0 40px;
box-sizing: border-box;
color: #8a97a8;
font-family: "Segoe UI", Arial, sans-serif;
font-size: 8.5px;
letter-spacing: .04em;
}
What it carries
Stylesheets, scripts, images and fonts travel inside the report, so nothing is resolved
from the outside while it renders. Anything under shared-assets/ belongs to
the project rather than to this report — held once for every report in it, and reached
from the template at the same assets/… path as the report's own.