Paragon
Examples Word RTL

Project KPI Dashboard (Arabic)

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 Project KPI Dashboard (Arabic), 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">

<!--
    Two files at the same kind of path, from two different folders. The typeface is this report's
    own — it is the only Arabic report in the project — and is found in the folder beside this
    template. The house style is the project's: the report's own folder is searched first and does
    not hold it, so it falls through to shared-assets, where it is held once for every report.
-->
<link rel="stylesheet" href="assets/css/fonts.css">
<link rel="stylesheet" href="assets/css/house.css">

<!-- This report's own, over the top of the house style. -->
<link rel="stylesheet" href="assets/css/report.css">

<!-- Shared as well: one copy of the chart library for every report in the project that draws. -->
<script src="assets/js/vendor/amcharts5-5.14.4.js"></script>
<script src="assets/js/charts.js"></script>
</head>
<body>

<script id="report-data" type="application/json">{{json}}</script>

<!-- Placed in the page and fixed to the top, so it is on both sheets and keeps the report's
     stylesheet, its direction and its logo. -->
<template id="report-header">
  <div class="masthead">
    <img class="logo" src="assets/images/logo.svg" alt="">

    <span class="titles">
      <span class="org">{{report.org}}</span>
      <span class="subject">{{report.subject}}</span>
    </span>

    <span class="meta">
      <span class="period">{{report.period}}</span>
      <span class="classification">{{report.classification}}</span>
    </span>
  </div>
</template>

<!-- Drawn by the browser, which is the only thing that can count sheets. It loads nothing, so no
     webfont reaches it — hence figures and a code, which read the same in any face. -->
<template id="report-footer">
  <div class="pagefoot" dir="ltr">
    <span><span class="pageNumber"></span> / <span class="totalPages"></span></span>
    <span>{{report.code}}</span>
  </div>
</template>

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

  <header class="pagehead">
    <div>
      <h1>{{report.title}}</h1>
      <p class="lede">{{report.lede}}</p>
    </div>

    <span class="stamp">صدر في {{date report.issuedOn "d MMMM yyyy" "ar-AE"}}</span>
  </header>

  <section class="kpis">
    {{#each kpis}}
    <article class="kpi kpi--{{tone}}">
      <span class="kpi-label">{{label}}</span>

      <span class="kpi-figure">
        <span class="value">{{number value format "ar-AE"}}</span>
        <span class="unit">{{unit}}</span>
      </span>

      <span class="kpi-delta">
        <span class="chip chip--{{tone}}">
          {{#if (eq direction "up")}}&#9650;{{else}}&#9660;{{/if}} {{delta}}
        </span>
        <span class="caption">{{caption}}</span>
      </span>

      <div class="meter meter--{{tone}}"><span style="width: {{meter}}%"></span></div>

      <span class="kpi-target">{{meterNote}}</span>
    </article>
    {{/each}}
  </section>

  <div class="panels">
    <section class="card">
      <div class="card-head">
        <h2>{{charts.status.title}}</h2>
        <span class="note">{{charts.status.note}}</span>
      </div>

      <div class="chart chart--status" id="chart-status"></div>
    </section>

    <section class="card">
      <div class="card-head">
        <h2>{{charts.quarters.title}}</h2>
        <span class="note">{{charts.quarters.note}}</span>
      </div>

      <div class="chart chart--quarters" id="chart-quarters"></div>
    </section>
  </div>

  <section class="card">
    <div class="card-head">
      <h2>{{charts.progress.title}}</h2>
      <span class="note">{{charts.progress.note}}</span>
    </div>

    <div class="chart chart--progress" id="chart-progress"></div>
  </section>

  <section class="card">
    <div class="card-head">
      <h2>{{highlights.title}}</h2>
      <span class="note">{{highlights.note}}</span>
    </div>

    <div class="highlights">
      {{#each highlights.rows}}
      <article class="highlight highlight--{{tone}}">
        <span class="what">{{title}}</span>
        <span class="why">{{note}}</span>
      </article>
      {{/each}}
    </div>
  </section>

  <p class="footnote">{{report.footnote}}</p>

</div>
page2.html
<div class="sheet">

  <header class="pagehead">
    <div>
      <h1>تفاصيل المحفظة</h1>
      <p class="lede">أكبر المشاريع بالميزانية، والتصعيدات والمعالم التي تتابعها اللجنة هذا الربع.</p>
    </div>

    <span class="stamp">{{report.period}}</span>
  </header>

  <section class="card">
    <div class="card-head">
      <h2>{{charts.sectors.title}}</h2>
      <span class="note">{{charts.sectors.note}}</span>
    </div>

    <div class="chart chart--sectors" id="chart-sectors"></div>
  </section>

  <section class="card">
    <div class="card-head">
      <h2>{{portfolio.title}}</h2>
      <span class="note">{{portfolio.note}}</span>
    </div>

    <table class="grid portfolio">
      <thead>
        <tr>
          <th class="col-project">المشروع</th>
          <th class="col-sector">القطاع</th>
          <th class="col-money">الميزانية</th>
          <th class="col-money">المنصرف</th>
          <th class="col-progress">الإنجاز</th>
          <th class="col-status">الحالة</th>
        </tr>
      </thead>

      <tbody>
        {{#each portfolio.rows}}
        <tr>
          <td class="project">
            <span class="title">{{name}}</span>
            <!-- Isolated: a Latin code inside an Arabic line leaves the algorithm to guess where its
                 dash belongs, and bdi is what stops it guessing. -->
            <bdi class="code">{{code}}</bdi>
          </td>
          <td class="sector">{{sector}}</td>
          <td class="num">{{number budget "N0" "ar-AE"}}</td>
          <td class="num">{{number spent "N0" "ar-AE"}}</td>
          <td>
            <div class="progress">
              <div class="meter meter--{{tone}}"><span style="width: {{completion}}%"></span></div>
              <span class="pct">{{completion}}%</span>
            </div>
          </td>
          <td><span class="pill pill--{{tone}}">{{status}}</span></td>
        </tr>
        {{/each}}
      </tbody>
    </table>
  </section>

  <div class="panels">
    <section class="card">
      <div class="card-head">
        <h2>{{risks.title}}</h2>
        <span class="note">{{risks.note}}</span>
      </div>

      <ul class="risks">
        {{#each risks.rows}}
        <li>
          <span class="what">{{title}}</span>
          <span class="who">{{owner}} &middot; المعالجة قبل {{due}}</span>
          <span class="chip chip--{{tone}}">{{severity}}</span>
        </li>
        {{/each}}
      </ul>
    </section>

    <section class="card card--fill">
      <div class="card-head">
        <h2>{{milestones.title}}</h2>
        <span class="note">{{milestones.note}}</span>
      </div>

      <ol class="milestones">
        {{#each milestones.rows}}
        <li class="ms--{{tone}}">
          <span class="when">{{when}}</span>
          <span class="what">{{title}}</span>
          <span class="pill pill--{{tone}}">{{state}}</span>
        </li>
        {{/each}}
      </ol>
    </section>
  </div>

  <p class="footnote">{{report.footnote}}</p>

</div>
assets/css/fonts.css
/*
  IBM Plex Sans Arabic, split by script and by weight so a sheet of Arabic pulls the Arabic file and
  nothing else. The unicode-range on each face is what decides which.

  This report's own, not the project's. It is the only report here whose contents are Arabic, and a
  typeface in shared-assets is one every report in the project carries — six files and a family
  name in the house style, for seven reports that print in Latin. Put it back in shared-assets the
  day a second Arabic report exists.

  It has to be carried. A render blocks every host but the report's own, and Chromium has no fallback
  to reach for in that state — an Arabic page with no font in the folder prints as boxes.
*/
@font-face
{
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 400;
  src: url('../fonts/ibm-plex-sans-arabic-400-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face
{
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 400;
  src: url('../fonts/ibm-plex-sans-arabic-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face
{
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 600;
  src: url('../fonts/ibm-plex-sans-arabic-600-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face
{
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 600;
  src: url('../fonts/ibm-plex-sans-arabic-600-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}

@font-face
{
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 700;
  src: url('../fonts/ibm-plex-sans-arabic-700-arabic.woff2') format('woff2');
  unicode-range: U+0600-06FF, U+0750-077F, U+0870-088E, U+0890-0891, U+0897-08E1, U+08E3-08FF, U+200C-200E, U+2010-2011, U+204F, U+2E41, U+FB50-FDFF, U+FE70-FE74, U+FE76-FEFC, U+102E0-102FB, U+10E60-10E7E, U+10EC2-10EC4, U+10EFC-10EFF, U+1EE00-1EE03, U+1EE05-1EE1F, U+1EE21-1EE22, U+1EE24, U+1EE27, U+1EE29-1EE32, U+1EE34-1EE37, U+1EE39, U+1EE3B, U+1EE42, U+1EE47, U+1EE49, U+1EE4B, U+1EE4D-1EE4F, U+1EE51-1EE52, U+1EE54, U+1EE57, U+1EE59, U+1EE5B, U+1EE5D, U+1EE5F, U+1EE61-1EE62, U+1EE64, U+1EE67-1EE6A, U+1EE6C-1EE72, U+1EE74-1EE77, U+1EE79-1EE7C, U+1EE7E, U+1EE80-1EE89, U+1EE8B-1EE9B, U+1EEA1-1EEA3, U+1EEA5-1EEA9, U+1EEAB-1EEBB, U+1EEF0-1EEF1;
}

@font-face
{
  font-family: 'IBM Plex Sans Arabic';
  font-style: normal;
  font-weight: 700;
  src: url('../fonts/ibm-plex-sans-arabic-700-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
assets/css/report.css
/*
  This report's own layout, over the project's house style.

  What is here is the composition — how many tiles across, how tall a chart is, what the table's
  columns are worth. The palette, the type, the sheet and the parts those are assembled from come
  from assets/css/house.css, which is the project's and lives in shared-assets.

  Everything is laid out with logical properties. The report is right-to-left, and the engine sets
  that on <html> from the report's own setting — so start is the right edge here, and nothing below
  has to know that.
*/

/*
  The two softer inks, darker here than the house style sets them. The greys sit well enough on a
  sheet, but Word draws the text at its own scale and thins the strokes further — the labels and
  the notes come out looking blurred. Darker ink holds them together. Only this report is moved;
  house.css is the project's and is left as it is.
*/
:root
{
  --ink-soft: #000000;
  --ink-faint: #000000;
}

/*
  The one report here whose contents are Arabic, so the one that sets the Arabic face. The faces
  are in this report's own assets/css/fonts.css; nothing about Arabic is in shared-assets, because
  a report that prints in Latin should neither name the family nor carry the files.
*/
body
{
  font-family: "IBM Plex Sans Arabic", "Segoe UI", Tahoma, sans-serif;
}

/* ---------- the figures across the top ---------- */

.kpis
{
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: 11px;
  margin-top: 14px;
}

/*
  A tile is a column: label, figure, movement, then the measure against target. Fixed order rather
  than a free-form card, because four of them are read across and the eye should not have to find
  each part again in the next one.
*/
.kpi
{
  position: relative;
  display: flex;
  flex-direction: column;
  gap: 4px;
  padding: 11px 12px 10px;
  background: var(--panel);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow: hidden;
}

/* The tone as an edge rather than a wash: it marks the tile without tinting the figure inside it. */
.kpi::before
{
  content: "";
  position: absolute;
  inset-block-start: 0;
  inset-inline: 0;
  height: 3px;
  background: var(--accent);
}

.kpi--good::before { background: var(--good); }
.kpi--warn::before { background: var(--warn); }
.kpi--bad::before  { background: var(--bad); }
.kpi--idle::before { background: var(--idle); }

.kpi-label
{
  color: var(--ink-soft);
  font-size: 10px;
  font-weight: 600;
}

.kpi-figure
{
  display: flex;
  align-items: baseline;
  gap: 5px;
  margin-top: -1px;
}

.kpi-figure .value
{
  font-size: 23px;
  font-weight: 700;
  line-height: 1.15;
  letter-spacing: -.02em;
}

.kpi-figure .unit
{
  color: var(--ink-faint);
  font-size: 9.5px;
}

.kpi-delta
{
  display: flex;
  align-items: center;
  gap: 6px;
  min-width: 0;
}

.kpi-delta .caption
{
  color: var(--ink-faint);
  font-size: 9px;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.kpi .meter { margin-top: 3px; }

.kpi-target
{
  color: var(--ink-faint);
  font-size: 9px;
}

/* ---------- the charts ---------- */

/* Two panels of equal width, and the same grid does the pair of panels on the second sheet. */
.panels
{
  display: grid;
  grid-template-columns: 1fr 1fr;
  gap: 12px;
  margin-top: 12px;
}

.panels + .card,
.card + .card,
.card + .panels { margin-top: 12px; }

.chart--status    { height: 208px; }
.chart--quarters  { height: 208px; }
.chart--progress  { height: 232px; }
.chart--sectors   { height: 150px; }

/* ---------- what the sheet is saying ---------- */

/*
  Three readings of the figures above, in the words somebody would use at the meeting. Last on the
  sheet rather than first: they are the conclusion, and a reader who disagrees has the charts above
  to argue with.
*/
.highlights
{
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 12px;
}

.highlight
{
  display: flex;
  flex-direction: column;
  gap: 2px;
  padding-inline-start: 10px;
  border-inline-start: 3px solid var(--idle);
}

.highlight--good { border-inline-start-color: var(--good); }
.highlight--warn { border-inline-start-color: var(--warn); }
.highlight--bad  { border-inline-start-color: var(--bad); }

.highlight .what
{
  font-size: 11px;
  font-weight: 700;
}

.highlight .why
{
  color: var(--ink-soft);
  font-size: 9.5px;
  line-height: 1.6;
}

/* ---------- the table of projects ---------- */

/*
  Fixed widths, because table-layout is fixed and the browser is therefore not sizing these from
  their content. The project column takes what the rest do not: it is the one holding a name that
  can run long, and the only one that may wrap.
*/
.portfolio .col-project  { width: auto; }
.portfolio .col-sector   { width: 108px; }
.portfolio .col-money    { width: 74px;  text-align: end; }
.portfolio .col-progress { width: 104px; }
.portfolio .col-status   { width: 88px; }

.portfolio .project
{
  display: flex;
  align-items: baseline;
  gap: 7px;
}

.portfolio .project .title { font-weight: 600; }

.portfolio .project .code
{
  color: var(--ink-faint);
  font-size: 9px;
  letter-spacing: .02em;
  white-space: nowrap;
}

.portfolio .sector
{
  color: var(--ink-soft);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* The bar carries the reading and the figure confirms it, so the figure is small and out of the way. */
.progress
{
  display: flex;
  align-items: center;
  gap: 7px;
}

.progress .meter { flex: 1 1 auto; }

.progress .pct
{
  flex: 0 0 auto;
  width: 28px;
  text-align: end;
  color: var(--ink-soft);
  font-size: 9.5px;
  font-weight: 600;
}

/* ---------- escalations ---------- */

.risks
{
  margin: 0;
  padding: 0;
  list-style: none;
}

.risks li
{
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 2px 8px;
  padding: 6px 0;
  border-bottom: 1px solid var(--rule-soft);
}

.risks li:last-child { border-bottom: 0; }

.risks .what
{
  font-size: 10.5px;
  font-weight: 600;
}

.risks .who
{
  grid-column: 1;
  color: var(--ink-faint);
  font-size: 9px;
}

/* Spans both rows of its own item, so the chip sits against the pair rather than beside the top line. */
.risks .chip
{
  grid-column: 2;
  grid-row: 1 / span 2;
}

/* ---------- the year's milestones ---------- */

/*
  A rail with a dot per item. The rail is a border on the list and the dots sit on top of it, so
  neither is a picture to load and both stay sharp at whatever the composer rasterises to.
*/
/*
  The pair of panels are the same height, because a grid row makes them so, and this is the shorter
  of the two. Rather than leave the difference as a gap under the last milestone, the list takes the
  height and spreads its items down it.
*/
.card--fill
{
  display: flex;
  flex-direction: column;
}

.milestones
{
  flex: 1 1 auto;
  display: flex;
  flex-direction: column;
  justify-content: space-between;

  margin: 2px 0 0;
  padding: 0;
  padding-inline-start: 15px;
  border-inline-start: 1px solid var(--rule);
  list-style: none;
}

.milestones li
{
  position: relative;
  display: grid;
  grid-template-columns: 1fr auto;
  align-items: center;
  gap: 1px 8px;
  padding: 5px 0;
}

.milestones li::before
{
  content: "";
  position: absolute;
  inset-inline-start: -20px;
  top: 11px;
  width: 7px;
  height: 7px;
  border: 1.5px solid var(--idle);
  border-radius: 50%;
  background: var(--page);
}

.milestones .ms--done::before { border-color: var(--accent); background: var(--accent); }
.milestones .ms--good::before { border-color: var(--good);   background: var(--good); }
.milestones .ms--bad::before  { border-color: var(--bad);    background: var(--bad); }

.milestones .when
{
  grid-column: 1;
  grid-row: 2;
  color: var(--ink-faint);
  font-size: 9px;
}

.milestones .what
{
  grid-column: 1;
  grid-row: 1;
  font-size: 10.5px;
  font-weight: 600;
}

.milestones .pill
{
  grid-column: 2;
  grid-row: 1 / span 2;
}
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.