Paragon
Examples Pdf LTR

Certificate of Completion

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 Certificate of Completion, 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"></template>
<template id="report-footer"></template>

</body>
</html>
page1.html
<div class="frame"></div>

<div class="sheet">

  <header class="crest">
    <span class="mark">{{academy.name}}</span>
    <span class="rule"></span>
    <span class="programme">{{course.programme}}</span>
  </header>

  <p class="kind">Certificate of Completion</p>

  <p class="preamble">This is to certify that</p>

  <h1 class="recipient">{{recipient.name}}</h1>

  <p class="citation">
    has completed the {{course.hours}}-hour programme
    <strong>{{course.title}}</strong>, meeting every assessment requirement
    and achieving a final standing of <strong>{{result.grade}}</strong>.
  </p>

  <dl class="facts">
    {{#each facts}}
    <div>
      <dt>{{label}}</dt>
      <dd>{{value}}</dd>
    </div>
    {{/each}}
  </dl>

  <footer class="attest">
    <div class="signature">
      <span class="ink">{{#each signatories}}{{#if @first}}{{signature}}{{/if}}{{/each}}</span>
      <span class="line"></span>
      {{#each signatories}}{{#if @first}}
      <strong>{{name}}</strong>
      <span class="role">{{role}}</span>
      {{/if}}{{/each}}
    </div>

    <img class="seal" src="assets/images/seal.svg" alt="">

    <div class="signature">
      <span class="ink">{{#each signatories}}{{#unless @first}}{{signature}}{{/unless}}{{/each}}</span>
      <span class="line"></span>
      {{#each signatories}}{{#unless @first}}
      <strong>{{name}}</strong>
      <span class="role">{{role}}</span>
      {{/unless}}{{/each}}
    </div>
  </footer>

  <p class="verify">
    Credential {{credential.id}} &middot; issued {{date credential.issued "d MMMM yyyy"}} &middot;
    verify at {{credential.verifyAt}}
  </p>

</div>
assets/css/report.css
@page { margin: 0; }

body
{
  margin: 0;
  color: #1f3d5c;
  font: 11pt "Segoe UI", system-ui, sans-serif;
}

/* The engraving, fixed to the page rather than carried by the content, so a certificate that grew
   a line does not print half a border. */
.frame
{
  position: fixed;
  inset: 0;
  z-index: -1;
  background: url("../images/frame.svg") center / 100% 100% no-repeat;
}

.sheet
{
  box-sizing: border-box;
  height: 100vh;
  padding: 22mm 32mm 16mm;
  display: flex;
  flex-direction: column;
  align-items: center;
  text-align: center;
}

/* ---------- the house ---------- */

.crest
{
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 2.5mm;
}

.crest .mark
{
  font: 600 13pt "Segoe UI", system-ui, sans-serif;
  letter-spacing: .42em;
  text-indent: .42em;
  text-transform: uppercase;
}

.crest .rule
{
  width: 22mm;
  height: 0.7mm;
  background: linear-gradient(90deg, transparent, #c9a227, transparent);
}

.crest .programme
{
  color: #6b7b8d;
  font-size: 9.5pt;
  letter-spacing: .16em;
  text-transform: uppercase;
}

/* ---------- the award ---------- */

.kind
{
  margin: 9mm 0 0;
  font: 400 15pt Georgia, "Times New Roman", serif;
  font-style: italic;
  color: #b8891f;
  letter-spacing: .04em;
}

.preamble
{
  margin: 5mm 0 0;
  color: #6b7b8d;
  font-size: 10.5pt;
  letter-spacing: .1em;
}

.recipient
{
  margin: 3mm 0 0;
  font: 400 34pt Georgia, "Times New Roman", serif;
  letter-spacing: .01em;
  line-height: 1.1;
}

.citation
{
  margin: 5mm 0 0;
  max-width: 165mm;
  font-size: 11.5pt;
  line-height: 1.7;
  color: #33475e;
}

.citation strong
{
  font-weight: 600;
  color: #1f3d5c;
}

/* ---------- the particulars ---------- */

.facts
{
  display: flex;
  gap: 14mm;
  margin: 9mm 0 0;
}

.facts div
{
  display: flex;
  flex-direction: column;
  gap: 1.2mm;
}

.facts dt
{
  color: #8a97a6;
  font-size: 8pt;
  letter-spacing: .14em;
  text-transform: uppercase;
}

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

/* ---------- signatures and seal ---------- */

.attest
{
  display: flex;
  align-items: flex-end;
  justify-content: center;
  gap: 22mm;
  margin-top: auto;
  width: 100%;
}

.signature
{
  display: flex;
  flex-direction: column;
  align-items: center;
  width: 62mm;
}

/* The hand, set in a script face so the name above the rule is not the same lettering as the name
   below it. Where no script face is installed the fallback still reads as writing rather than as
   another line of the document. */
.signature .ink
{
  font: 400 22pt "Segoe Script", "Brush Script MT", "Lucida Handwriting", cursive;
  color: #2c4a6b;
  line-height: 1;
  padding-bottom: 1.5mm;
}

.signature .line
{
  width: 100%;
  height: 0.35mm;
  background: #b9c3ce;
}

.signature strong
{
  margin-top: 2mm;
  font-size: 10.5pt;
}

.signature .role
{
  color: #6b7b8d;
  font-size: 8.5pt;
  letter-spacing: .08em;
  text-transform: uppercase;
}

.seal
{
  width: 34mm;
  height: 34mm;
  margin-bottom: 2mm;
}

/* ---------- the small print ---------- */

.verify
{
  margin: 7mm 0 0;
  color: #8a97a6;
  font-size: 8.5pt;
  letter-spacing: .05em;
}
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.