Paragon
Examples Pdf LTR

Sales Invoice

Those are this report's own sheets, printed by the engine from the template and the payload below — not screenshots kept beside them.

The first sheet of Sales Invoice, as the engine prints it.

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="issuer">
          <h1>{{seller.name}}</h1>
          <p>{{seller.tagline}}</p>
        </div>
        <div class="docref">
          <span class="doctype">Invoice</span>
          <span class="docno">{{invoice.number}}</span>
        </div>
      </header>
    </template>

    <template id="report-footer">
      <div class="pagefoot">
        <span class="mark">{{seller.name}}</span>
        <span>{{invoice.number}} &mdash; account {{customer.account}}</span>
        <span>Page <span class="pageNumber"></span> of <span class="totalPages"></span></span>
      </div>
    </template>
  </body>
</html>
page1.html
<div class="sheet">
  <section class="parties">
    <div class="party">
      <h2>Billed to</h2>
      <p class="name">{{customer.name}}</p>
      {{#if customer.contact}}<p>Attn. {{customer.contact}}</p>{{/if}}
      {{#each customer.address}}<p>{{this}}</p>{{/each}}
      {{#if customer.vatNumber}}<p class="muted">VAT {{customer.vatNumber}}</p>{{/if}}
    </div>

    <div class="party">
      <h2>Remit to</h2>
      <p class="name">{{seller.name}}</p>
      {{#each seller.address}}<p>{{this}}</p>{{/each}}
      <p class="muted">VAT {{seller.vatNumber}}</p>
    </div>

    <dl class="facts">
      <div><dt>Issued</dt><dd>{{date invoice.issuedOn "dd MMM yyyy" "en-GB"}}</dd></div>
      <div><dt>Due</dt><dd>{{date invoice.dueOn "dd MMM yyyy" "en-GB"}}</dd></div>
      <div><dt>Terms</dt><dd>{{invoice.terms}}</dd></div>
      <div><dt>Account</dt><dd>{{customer.account}}</dd></div>
    </dl>
  </section>

  <p class="status {{#if invoice.paid}}is-paid{{else}}is-due{{/if}}">
    {{#if invoice.paid}}
      Paid in full &mdash; no action required
    {{else}}
      Payment due {{date invoice.dueOn "dd MMM yyyy" "en-GB"}}
    {{/if}}
  </p>

  <table class="ledger">
    <thead>
      <tr>
        <th class="ref">Ref</th>
        <th>Description</th>
        <th class="num qty">Hours</th>
        <th class="num money">Rate</th>
        <th class="num money">Amount</th>
      </tr>
    </thead>
    <tbody>
      {{#each invoice.lines}}
      <tr>
        <td class="ref">{{ref}}</td>
        <td>{{description}}</td>
        <td class="num qty">{{number hours "N1" "en-US"}}</td>
        <td class="num money">{{money rate "en-US"}}</td>
        <td class="num money">{{money amount "en-US"}}</td>
      </tr>
      {{/each}}
    </tbody>
  </table>

  <section class="totals">
    <dl>
      <div><dt>Subtotal</dt><dd>{{money invoice.subtotal "en-US"}}</dd></div>
      <div><dt>{{invoice.taxLabel}}</dt><dd>{{money invoice.tax "en-US"}}</dd></div>
      <div class="grand"><dt>Total due</dt><dd>{{money invoice.total "en-US"}}</dd></div>
    </dl>
  </section>

  {{#if invoice.notes}}
  <section class="notes">
    <h2>Notes</h2>
    <p>{{invoice.notes}}</p>
  </section>
  {{/if}}

</div>
assets/css/report.css
body
{
  margin: 0;
  font: 10.5pt "Segoe UI", system-ui, sans-serif;
  color: #1f2430;
}

.sheet
{
  padding: 36mm 15mm 24mm;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}

/* ---------- masthead ---------- */

.masthead
{
  height: 22mm;
  box-sizing: border-box;
  padding: 11mm 15mm 3mm;
  background: #fff;

  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 8mm;
  border-bottom: 0.8mm solid #1f2a44;
}

.masthead h1
{
  margin: 0;
  font-size: 15pt;
  font-weight: 700;
  letter-spacing: -0.01em;
  color: #1f2a44;
}

.masthead .issuer p
{
  margin: 1mm 0 0;
  font-size: 8.5pt;
  color: #6b7280;
}

.docref
{
  text-align: right;
  line-height: 1.25;
}

.doctype
{
  display: block;
  font-size: 8pt;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: #6b7280;
}

.docno
{
  display: block;
  font-size: 12pt;
  font-weight: 700;
  color: #1f2a44;
}

/* ---------- the parties, and the facts about the document ---------- */

.parties
{
  display: grid;
  grid-template-columns: 1fr 1fr 62mm;
  gap: 8mm;
  margin-bottom: 7mm;
}

.party h2,
.notes h2
{
  margin: 0 0 2mm;
  font-size: 8pt;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: #6b7280;
}

.party p
{
  margin: 0 0 0.8mm;
  line-height: 1.35;
}

.party .name
{
  font-weight: 700;
  font-size: 11pt;
}

.muted
{
  color: #6b7280;
  font-size: 9pt;
}

.facts
{
  margin: 0;
  align-self: start;
  border: 0.3mm solid #d7dbe3;
  border-radius: 1.6mm;
  padding: 3mm 4mm;
  background: #f7f8fa;
}

.facts div
{
  display: flex;
  justify-content: space-between;
  gap: 4mm;
  padding: 1mm 0;
}

.facts div + div
{
  border-top: 0.2mm solid #e4e7ee;
}

.facts dt
{
  color: #6b7280;
  font-size: 9pt;
}

.facts dd
{
  margin: 0;
  font-weight: 600;
  font-size: 9.5pt;
}

/* ---------- the one line that says where the invoice stands ---------- */

.status
{
  margin: 0 0 6mm;
  padding: 2.4mm 4mm;
  border-radius: 1.4mm;
  font-weight: 600;
  font-size: 10pt;
  border-left: 1.2mm solid;
}

.status.is-due
{
  background: #fff5ed;
  border-color: #c2620f;
  color: #8a4409;
}

.status.is-paid
{
  background: #eefaf3;
  border-color: #0f8a52;
  color: #0b6b40;
}

/* ---------- the lines ---------- */

.ledger
{
  width: 100%;
  border-collapse: collapse;
  font-size: 9.5pt;
}

.ledger thead
{
  display: table-header-group;
}

/* A row is the unit here: one split across two sheets reads as two half-rows. */
.ledger tr
{
  break-inside: avoid;
}

.ledger th
{
  text-align: left;
  font-size: 8pt;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: #4b5563;
  padding: 0 2.5mm 1.8mm;
  border-bottom: 0.5mm solid #1f2a44;
}

.ledger td
{
  padding: 2mm 2.5mm;
  border-bottom: 0.2mm solid #e4e7ee;
  vertical-align: top;
}

.ledger .num
{
  text-align: right;
  white-space: nowrap;
}

.ledger .ref
{
  color: #6b7280;
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* Figures line up column-wise only when the digits are all one width. */
.ledger .qty,
.ledger .money
{
  font-variant-numeric: tabular-nums;
}

.ledger .money
{
  width: 26mm;
}

.ledger .qty
{
  width: 18mm;
}

/* ---------- what it comes to ---------- */

.totals
{
  display: flex;
  justify-content: flex-end;
  margin-top: 5mm;
  break-inside: avoid;
}

.totals dl
{
  margin: 0;
  width: 72mm;
}

.totals div
{
  display: flex;
  justify-content: space-between;
  gap: 6mm;
  padding: 1.6mm 0;
}

.totals dt
{
  color: #4b5563;
}

.totals dd
{
  margin: 0;
  font-variant-numeric: tabular-nums;
  font-weight: 600;
}

.totals .grand
{
  margin-top: 1.5mm;
  padding-top: 2.5mm;
  border-top: 0.6mm solid #1f2a44;
  font-size: 12pt;
}

.totals .grand dt
{
  color: #1f2a44;
  font-weight: 700;
}

.totals .grand dd
{
  font-weight: 700;
}

/* ---------- notes ---------- */

.notes
{
  margin-top: 8mm;
  padding-top: 4mm;
  border-top: 0.2mm solid #e4e7ee;
  break-inside: avoid;
}

.notes p
{
  margin: 0;
  color: #4b5563;
  line-height: 1.45;
  font-size: 9.5pt;
}

/* ---------- the running footer ---------- */

.pagefoot
{
  width: 100%;
  padding: 0 15mm;
  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.