Marketing and public web
Landing
A finished page under Kiln. Copy it and it is yours outright: unlike a brickwork component or shell, it never upgrades under semver.
Static reference: This preview uses fictional logos and sample metrics. Navigation and action links use the source example’s sample destinations; they do not form a connected demo.
Invoicing
Invoicing that chases itself
Northwind raises your invoices, sends the reminders, and tells you who is about to pay late. You do the work. It does the asking.
Trusted by finance teams at
Everything the chasing needs
The parts of getting paid that nobody wants to do by hand.
Automatic reminders
Send a clear follow-up before an invoice becomes awkward.
Due-date planning
See which invoices need attention before the week closes.
Payment matching
Keep paid invoices visible beside the work that remains.
Built for the boring half of getting paid
See what changes in the first quarter
Most teams cut days-to-pay without hiring anyone. Start free, import when you are ready.
What changes in the first quarter
What finance teams say after a quarter
We cut our average days-to-pay from 38 to 21 without hiring anyone. The reminders just go out.
I imported two years of invoices on a Tuesday afternoon and the first reminders went out that evening. There was no implementation project.
The thing I was worried about was sounding aggressive to customers I like. It stops the moment they pay, so nobody has been chased for an invoice they already settled.
Stop writing chase emails
Free for 30 days. No card, no setup call, no data import to sit through.
Scroll inside the frame to inspect the full page.
Paste into a static page. Link @bwui/css (and @bwui/js for behaviours).
<!DOCTYPE html>
<html lang="en-GB" data-bw-brand="northline">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Northline: start your depot on a fourteen-day trial</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Source+Sans+3:wght@400;500;600;700&family=Source+Serif+4:opsz,wght@8..60,500;8..60,650;8..60,700&display=swap"
rel="stylesheet"
/>
<link rel="stylesheet" href="/static/product_templates/assets/brickwork.css" />
<script defer src="/static/product_templates/assets/behaviours.js"></script>
<style>
/* Northline brand pack (fictional): cool paper, teal accent, display serif.
Only the semantic variables are set here; every dark and light tone on the
page is derived from --bw-color-accent, so a brand swap recolours it all. */
:root,
[data-bw-brand="northline"] {
--bw-color-surface: oklch(0.985 0.004 240);
--bw-color-fg: oklch(0.26 0.02 250);
--bw-color-border: oklch(0.90 0.01 240);
--bw-color-accent: oklch(0.42 0.05 230);
--bw-color-danger: oklch(0.55 0.18 25);
--bw-color-success: oklch(0.56 0.13 155);
--bw-color-warning: oklch(0.70 0.14 75);
--bw-color-fg-on-accent: oklch(0.99 0 0);
--bw-font-family-sans: "Source Sans 3", system-ui, sans-serif;
--bw-font-family-display: "Source Serif 4", Georgia, serif;
}
body {
/* Page-local tones, all derived from the brand variables. */
--lp-ink: color-mix(in oklab, var(--bw-color-accent) 42%, black);
--lp-deep: color-mix(in oklab, var(--bw-color-accent) 82%, black);
--lp-mist: color-mix(in oklab, var(--bw-color-accent) 86%, white);
--lp-glow: color-mix(in oklab, var(--bw-color-accent) 38%, white);
--lp-paper: color-mix(in oklab, var(--bw-color-surface) 92%, var(--bw-color-accent));
--lp-paper-72: color-mix(in oklab, var(--lp-paper) 74%, transparent);
--lp-paper-16: color-mix(in oklab, var(--lp-paper) 16%, transparent);
--lp-paper-08: color-mix(in oklab, var(--lp-paper) 8%, transparent);
margin: 0;
font-family: var(--bw-font-family-sans);
background: var(--bw-color-surface);
color: var(--bw-color-fg);
}
/* ---- Hero: header, copy and board share one full-bleed field ---- */
.lp-hero {
position: relative;
display: grid;
grid-template-rows: auto 1fr;
min-height: min(100svh, 56rem);
color: var(--lp-paper);
background:
linear-gradient(115deg, color-mix(in oklab, var(--lp-ink) 55%, transparent) 0%, transparent 60%),
linear-gradient(165deg, var(--lp-ink) 0%, var(--lp-deep) 55%, var(--lp-mist) 130%);
overflow: hidden;
/* Re-point the button colour variables so bw-btn reads on the dark field. */
--bw-color-accent: var(--lp-paper);
--bw-color-accent-hover: color-mix(in oklab, var(--lp-paper) 88%, var(--lp-glow));
--bw-color-fg-on-accent: var(--lp-ink);
}
.lp-hero::before {
content: "";
position: absolute;
inset: auto -10% -45% 30%;
height: 150%;
background:
radial-gradient(ellipse at 40% 40%, color-mix(in oklab, var(--lp-paper) 14%, transparent) 0%, transparent 55%),
radial-gradient(ellipse at 70% 60%, color-mix(in oklab, var(--lp-glow) 38%, transparent) 0%, transparent 50%);
pointer-events: none;
}
.lp-header {
position: relative;
z-index: 2;
display: flex;
align-items: center;
gap: var(--bw-space-5);
padding: var(--bw-space-4) clamp(1.25rem, 4vw, 3rem);
}
.lp-header__brand {
display: inline-flex;
align-items: center;
gap: var(--bw-space-2);
margin-right: auto;
font-weight: 700;
font-size: 1.125rem;
letter-spacing: -0.03em;
color: var(--lp-paper);
text-decoration: none;
}
.lp-header__mark {
width: 1.125rem;
height: 1.125rem;
border-radius: 5px;
background: linear-gradient(135deg, var(--lp-glow), var(--lp-mist));
}
.lp-header__nav {
display: flex;
align-items: center;
gap: var(--bw-space-5);
}
.lp-header__nav a {
color: var(--lp-paper-72);
text-decoration: none;
font-size: var(--bw-font-size-sm);
font-weight: 500;
}
.lp-header__nav a:hover {
color: var(--lp-paper);
}
.lp-hero__body {
position: relative;
z-index: 1;
display: grid;
gap: var(--bw-space-10);
align-items: center;
padding: clamp(2rem, 5vw, 4rem) clamp(1.25rem, 4vw, 3rem) clamp(3rem, 7vw, 5.5rem);
}
@media (min-width: 64rem) {
.lp-hero__body {
grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.15fr);
}
}
.lp-eyebrow {
margin: 0 0 var(--bw-space-3);
font-size: var(--bw-font-size-xs);
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--lp-paper-72);
}
.lp-hero__heading {
margin: 0 0 var(--bw-space-4);
font-family: var(--bw-font-family-display);
font-weight: 650;
font-size: clamp(2.5rem, 5.6vw, 4.25rem);
letter-spacing: -0.04em;
line-height: 1.03;
max-width: 13ch;
color: var(--lp-paper);
}
.lp-hero__lede {
margin: 0 0 var(--bw-space-6);
max-width: 38ch;
font-size: clamp(1.0625rem, 1.5vw, 1.1875rem);
line-height: 1.45;
color: var(--lp-paper-72);
}
.lp-hero__actions {
display: flex;
flex-wrap: wrap;
gap: var(--bw-space-3);
margin-bottom: var(--bw-space-4);
}
.lp-hero .bw-btn--ghost {
color: var(--lp-paper);
border-color: color-mix(in oklab, var(--lp-paper) 38%, transparent);
}
.lp-hero .bw-btn--ghost:hover {
color: var(--lp-paper);
background: var(--lp-paper-08);
}
.lp-hero__meta {
margin: 0;
font-size: var(--bw-font-size-sm);
color: color-mix(in oklab, var(--lp-paper) 60%, transparent);
}
/* ---- Dominant visual: the depot board ---- */
.lp-board {
border-radius: 1rem;
overflow: hidden;
border: 1px solid var(--lp-paper-16);
background: color-mix(in oklab, var(--lp-ink) 78%, black);
box-shadow:
0 2rem 4rem color-mix(in oklab, black 38%, transparent),
0 0 0 1px var(--lp-paper-08);
}
@media (min-width: 64rem) {
.lp-board {
margin-right: calc(clamp(1.25rem, 4vw, 3rem) * -1);
border-right: 0;
border-radius: 1rem 0 0 1rem;
}
}
.lp-board__bar {
display: flex;
align-items: center;
gap: 0.4rem;
padding: 0.75rem 1rem;
border-bottom: 1px solid var(--lp-paper-08);
background: var(--lp-paper-08);
}
.lp-board__dot {
width: 0.55rem;
height: 0.55rem;
border-radius: 999px;
background: color-mix(in oklab, var(--lp-paper) 28%, transparent);
}
.lp-board__title {
margin: 0 0 0 0.5rem;
font-size: 0.75rem;
font-weight: 500;
color: var(--lp-paper-72);
}
.lp-board__live {
margin-left: auto;
display: inline-flex;
align-items: center;
gap: 0.4rem;
font-size: 0.75rem;
font-weight: 600;
color: var(--lp-paper-72);
}
.lp-board__live::before {
content: "";
width: 0.5rem;
height: 0.5rem;
border-radius: 999px;
background: var(--bw-color-success);
box-shadow: 0 0 0 3px color-mix(in oklab, var(--bw-color-success) 28%, transparent);
}
.lp-board__summary {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
border-bottom: 1px solid var(--lp-paper-08);
}
.lp-board__summary > div {
padding: var(--bw-space-4) var(--bw-space-5);
border-right: 1px solid var(--lp-paper-08);
}
.lp-board__summary > div:last-child {
border-right: 0;
}
.lp-board__figure {
margin: 0;
font-family: var(--bw-font-family-display);
font-size: clamp(1.5rem, 2.6vw, 2rem);
font-weight: 650;
letter-spacing: -0.03em;
line-height: 1;
color: var(--lp-paper);
}
.lp-board__caption {
margin: var(--bw-space-2) 0 0;
font-size: 0.75rem;
color: color-mix(in oklab, var(--lp-paper) 58%, transparent);
}
.lp-board__rows {
list-style: none;
margin: 0;
padding: var(--bw-space-3);
display: grid;
gap: var(--bw-space-2);
}
.lp-row {
display: grid;
grid-template-columns: auto minmax(0, 1fr) auto;
align-items: center;
gap: var(--bw-space-4);
padding: var(--bw-space-3) var(--bw-space-4);
border-radius: 0.75rem;
border: 1px solid var(--lp-paper-08);
background: color-mix(in oklab, var(--lp-deep) 55%, transparent);
}
.lp-row--focus {
background: color-mix(in oklab, var(--lp-mist) 34%, var(--lp-deep));
border-color: color-mix(in oklab, var(--lp-glow) 40%, transparent);
}
.lp-row__id {
font-size: 0.8125rem;
font-weight: 700;
letter-spacing: 0.02em;
color: var(--lp-glow);
min-width: 3.25rem;
}
.lp-row__route {
margin: 0;
font-size: 0.9375rem;
font-weight: 600;
color: var(--lp-paper);
}
.lp-row__detail {
margin: 0.15rem 0 0;
font-size: 0.75rem;
color: color-mix(in oklab, var(--lp-paper) 58%, transparent);
}
.lp-pill {
display: inline-flex;
align-items: center;
min-height: 1.625rem;
padding: 0 0.7rem;
border-radius: 999px;
font-size: 0.75rem;
font-weight: 600;
white-space: nowrap;
color: var(--lp-paper);
background: color-mix(in oklab, var(--pill, var(--bw-color-success)) 30%, transparent);
border: 1px solid color-mix(in oklab, var(--pill, var(--bw-color-success)) 55%, transparent);
}
.lp-pill--late {
--pill: var(--bw-color-warning);
}
.lp-pill--held {
--pill: var(--bw-color-danger);
}
.lp-board__track {
display: block;
width: 100%;
height: auto;
padding: 0 var(--bw-space-3) var(--bw-space-3);
box-sizing: border-box;
}
.lp-track-line {
fill: none;
stroke: var(--lp-glow);
stroke-width: 2.5;
stroke-linecap: round;
stroke-linejoin: round;
}
.lp-track-base {
fill: none;
stroke: var(--lp-paper-16);
stroke-width: 2.5;
stroke-dasharray: 2 7;
stroke-linecap: round;
}
.lp-track-stop {
fill: var(--lp-ink);
stroke: var(--lp-glow);
stroke-width: 2.5;
}
.lp-track-now {
fill: var(--lp-paper);
}
.lp-track-halo {
fill: color-mix(in oklab, var(--lp-glow) 30%, transparent);
}
.lp-track-label {
font-family: var(--bw-font-family-sans);
font-size: 11px;
font-weight: 600;
fill: color-mix(in oklab, var(--lp-paper) 62%, transparent);
}
/* ---- Proof and depth band ---- */
.lp-proof {
display: grid;
gap: var(--bw-space-10);
padding: clamp(3.5rem, 8vw, 6rem) clamp(1.25rem, 4vw, 3rem);
background: color-mix(in oklab, var(--bw-color-surface) 93%, var(--bw-color-accent));
border-bottom: 1px solid var(--bw-color-border);
}
@media (min-width: 64rem) {
.lp-proof {
grid-template-columns: minmax(0, 1fr) minmax(0, 1.05fr);
align-items: center;
column-gap: clamp(3rem, 7vw, 6rem);
}
}
.lp-proof__eyebrow {
margin: 0 0 var(--bw-space-2);
font-size: var(--bw-font-size-xs);
font-weight: 600;
letter-spacing: 0.08em;
text-transform: uppercase;
color: var(--bw-color-accent);
}
.lp-proof__heading {
margin: 0 0 var(--bw-space-4);
max-width: 17ch;
font-family: var(--bw-font-family-display);
font-size: clamp(1.875rem, 3.4vw, 2.75rem);
font-weight: 650;
letter-spacing: -0.03em;
line-height: 1.08;
}
.lp-proof__body {
margin: 0 0 var(--bw-space-8);
max-width: 44ch;
line-height: 1.5;
color: var(--bw-color-fg-muted);
}
.lp-stats {
display: grid;
grid-template-columns: repeat(3, minmax(0, 1fr));
gap: var(--bw-space-5);
margin: 0 0 var(--bw-space-8);
padding: var(--bw-space-5) 0;
border-block: 1px solid var(--bw-color-border);
}
.lp-stat dt {
order: 2;
margin: var(--bw-space-2) 0 0;
font-size: var(--bw-font-size-sm);
color: var(--bw-color-fg-muted);
}
.lp-stat dd {
order: 1;
margin: 0;
font-family: var(--bw-font-family-display);
font-size: clamp(1.75rem, 3.4vw, 2.5rem);
font-weight: 650;
letter-spacing: -0.04em;
line-height: 1;
color: var(--bw-color-accent);
}
.lp-stat {
display: flex;
flex-direction: column;
}
.lp-quote {
margin: 0;
}
.lp-quote blockquote {
margin: 0 0 var(--bw-space-3);
padding-left: var(--bw-space-4);
border-left: 3px solid var(--bw-color-accent);
font-family: var(--bw-font-family-display);
font-size: clamp(1.25rem, 2vw, 1.5rem);
letter-spacing: -0.02em;
line-height: 1.3;
}
.lp-quote figcaption {
padding-left: calc(var(--bw-space-4) + 3px);
font-size: var(--bw-font-size-sm);
color: var(--bw-color-fg-muted);
}
.lp-quote strong {
color: var(--bw-color-fg);
}
.lp-handoff {
border-radius: 1rem;
border: 1px solid var(--bw-color-border);
background: var(--bw-color-surface);
padding: clamp(1.25rem, 3vw, 2rem);
box-shadow: 0 1.25rem 3rem color-mix(in oklab, var(--lp-ink) 10%, transparent);
}
.lp-handoff__head {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: var(--bw-space-4);
margin: 0 0 var(--bw-space-5);
}
.lp-handoff__head h3 {
margin: 0;
font-size: 1rem;
letter-spacing: -0.02em;
}
.lp-handoff__head span {
font-size: var(--bw-font-size-sm);
color: var(--bw-color-fg-muted);
}
.lp-steps {
list-style: none;
margin: 0;
padding: 0;
display: grid;
gap: 0;
}
.lp-step {
position: relative;
display: grid;
grid-template-columns: 2.25rem minmax(0, 1fr) auto;
gap: var(--bw-space-4);
align-items: start;
padding-bottom: var(--bw-space-5);
}
.lp-step:last-child {
padding-bottom: 0;
}
.lp-step::before {
content: "";
position: absolute;
left: calc(1.125rem - 1px);
top: 2.25rem;
bottom: 0;
width: 2px;
background: var(--bw-color-border);
}
.lp-step:last-child::before {
display: none;
}
.lp-step__node {
display: grid;
place-items: center;
width: 2.25rem;
height: 2.25rem;
border-radius: 999px;
font-size: 0.8125rem;
font-weight: 700;
color: var(--bw-color-fg-on-accent);
background: var(--bw-color-accent);
}
.lp-step--done .lp-step__node {
background: color-mix(in oklab, var(--bw-color-accent) 14%, var(--bw-color-surface));
color: var(--bw-color-accent);
border: 1px solid color-mix(in oklab, var(--bw-color-accent) 35%, var(--bw-color-surface));
}
.lp-step__title {
margin: 0;
font-weight: 600;
line-height: 1.3;
}
.lp-step__note {
margin: 0.2rem 0 0;
font-size: var(--bw-font-size-sm);
color: var(--bw-color-fg-muted);
line-height: 1.4;
}
.lp-step__time {
font-size: var(--bw-font-size-sm);
font-weight: 600;
color: var(--bw-color-fg-muted);
font-variant-numeric: tabular-nums;
}
/* ---- Close ---- */
.lp-close {
padding: clamp(3.5rem, 8vw, 6rem) clamp(1.25rem, 4vw, 3rem);
color: var(--lp-paper);
background:
radial-gradient(ellipse at 85% 0%, color-mix(in oklab, var(--lp-glow) 22%, transparent) 0%, transparent 55%),
var(--lp-ink);
--bw-color-accent: var(--lp-paper);
--bw-color-accent-hover: color-mix(in oklab, var(--lp-paper) 88%, var(--lp-glow));
--bw-color-fg-on-accent: var(--lp-ink);
}
.lp-close__inner {
display: flex;
flex-wrap: wrap;
gap: var(--bw-space-6);
align-items: end;
justify-content: space-between;
max-width: 72rem;
margin-inline: auto;
}
.lp-close h2 {
margin: 0 0 var(--bw-space-3);
max-width: 16ch;
font-family: var(--bw-font-family-display);
font-size: clamp(2rem, 3.8vw, 3rem);
font-weight: 650;
letter-spacing: -0.035em;
line-height: 1.06;
}
.lp-close p {
margin: 0;
max-width: 40ch;
line-height: 1.45;
color: var(--lp-paper-72);
}
.lp-close__actions {
display: flex;
flex-wrap: wrap;
gap: var(--bw-space-3);
}
.lp-close .bw-btn--ghost {
color: var(--lp-paper);
border-color: color-mix(in oklab, var(--lp-paper) 38%, transparent);
}
.lp-close .bw-btn--ghost:hover {
color: var(--lp-paper);
background: var(--lp-paper-08);
}
.lp-footer {
display: flex;
flex-wrap: wrap;
gap: var(--bw-space-4);
justify-content: space-between;
padding: var(--bw-space-5) clamp(1.25rem, 4vw, 3rem);
color: color-mix(in oklab, var(--lp-paper) 58%, transparent);
background: var(--lp-ink);
border-top: 1px solid var(--lp-paper-08);
font-size: var(--bw-font-size-sm);
}
.lp-footer p {
margin: 0;
}
.lp-footer a {
color: inherit;
}
.lp-footer a:hover {
color: var(--lp-paper);
}
@media (max-width: 48rem) {
.lp-header__nav a:not(.bw-btn) {
display: none;
}
.lp-hero {
min-height: 0;
}
.lp-board__summary > div {
padding: var(--bw-space-3);
}
.lp-row {
grid-template-columns: minmax(0, 1fr) auto;
}
.lp-row__id {
display: none;
}
.lp-stats {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body data-theme="light" data-bw-brand="northline">
<a class="bw-skip-link" href="#main">Skip to content</a>
<div class="lp-hero">
<header class="lp-header">
<a class="lp-header__brand" href="/templates/homepage/full/">
<span class="lp-header__mark" aria-hidden="true"></span>
Northline
</a>
<nav class="lp-header__nav" aria-label="Primary">
<a href="#depth">Customers</a>
<a href="/templates/pricing/full/">Pricing</a>
<a class="bw-btn bw-btn--ghost" href="/templates/contact/full/">Talk to us</a>
</nav>
</header>
<main id="main" style="display: contents">
<section class="lp-hero__body" aria-labelledby="lp-hero-heading">
<div class="lp-hero__copy">
<p class="lp-eyebrow">Depot and fleet operations</p>
<h1 id="lp-hero-heading" class="lp-hero__heading">Run the depot, not the phone tree.</h1>
<p class="lp-hero__lede">
Northline puts every route, driver and bay on one live board, so late loads are handled before a customer calls.
</p>
<div class="lp-hero__actions">
<a class="bw-btn bw-btn--primary bw-btn--lg" href="/templates/contact/full/">Start free trial</a>
<a class="bw-btn bw-btn--ghost bw-btn--lg" href="/templates/pricing/full/">See pricing</a>
</div>
<p class="lp-hero__meta">Fourteen days, no card. Your first depot live by Friday.</p>
</div>
<div class="lp-hero__media">
<div class="lp-board" role="img" aria-label="Northline depot board for the Mersey hub showing three routes, one running late and one held at a bay">
<div class="lp-board__bar" aria-hidden="true">
<span class="lp-board__dot"></span>
<span class="lp-board__dot"></span>
<span class="lp-board__dot"></span>
<p class="lp-board__title">Depot board · Mersey hub</p>
<span class="lp-board__live">Live</span>
</div>
<div class="lp-board__summary" aria-hidden="true">
<div>
<p class="lp-board__figure">42</p>
<p class="lp-board__caption">Routes today</p>
</div>
<div>
<p class="lp-board__figure">94%</p>
<p class="lp-board__caption">On time</p>
</div>
<div>
<p class="lp-board__figure">2</p>
<p class="lp-board__caption">Need action</p>
</div>
</div>
<ul class="lp-board__rows" aria-hidden="true">
<li class="lp-row">
<span class="lp-row__id">R-218</span>
<div>
<p class="lp-row__route">Liverpool to Preston</p>
<p class="lp-row__detail">Driver A. Okafor · Next stop 11:40</p>
</div>
<span class="lp-pill">On time</span>
</li>
<li class="lp-row lp-row--focus">
<span class="lp-row__id">R-224</span>
<div>
<p class="lp-row__route">Warrington to Leeds</p>
<p class="lp-row__detail">Handoff to evening shift at 14:10</p>
</div>
<span class="lp-pill lp-pill--late">12 min late</span>
</li>
<li class="lp-row">
<span class="lp-row__id">R-231</span>
<div>
<p class="lp-row__route">Chester to Manchester</p>
<p class="lp-row__detail">Waiting for bay 4 to clear</p>
</div>
<span class="lp-pill lp-pill--held">Held at bay</span>
</li>
</ul>
<svg class="lp-board__track" viewBox="0 0 520 70" aria-hidden="true" focusable="false">
<path class="lp-track-base" d="M20 44 C 110 44, 130 20, 220 24 S 360 52, 500 30" />
<path class="lp-track-line" d="M20 44 C 110 44, 130 20, 220 24 S 300 40, 330 40" />
<circle class="lp-track-stop" cx="20" cy="44" r="5" />
<circle class="lp-track-stop" cx="220" cy="24" r="5" />
<circle class="lp-track-halo" cx="330" cy="40" r="13" />
<circle class="lp-track-now" cx="330" cy="40" r="5.5" />
<circle class="lp-track-stop" cx="500" cy="30" r="5" />
<text class="lp-track-label" x="8" y="64">Depot</text>
<text class="lp-track-label" x="200" y="12">Handoff</text>
<text class="lp-track-label" x="300" y="66">Van 224</text>
<text class="lp-track-label" x="458" y="54">Customer</text>
</svg>
</div>
</div>
</section>
</main>
</div>
<section id="depth" class="lp-proof" aria-labelledby="lp-proof-heading">
<div>
<p class="lp-proof__eyebrow">One shared status</p>
<h2 id="lp-proof-heading" class="lp-proof__heading">Depot, driver and customer see the same story.</h2>
<p class="lp-proof__body">
Handoffs stop depending on who remembered to ring whom. Every change lands on the board once, and everyone downstream sees it.
</p>
<dl class="lp-stats">
<div class="lp-stat">
<dt>Fewer late arrivals</dt>
<dd>18%</dd>
</div>
<div class="lp-stat">
<dt>Average handoff</dt>
<dd>6 min</dd>
</div>
<div class="lp-stat">
<dt>For every shift</dt>
<dd>1 board</dd>
</div>
</dl>
<figure class="lp-quote">
<blockquote>We stopped chasing updates by phone. The board was already right.</blockquote>
<figcaption><strong>Priya Raman</strong> · Head of Operations, Harbour Freight</figcaption>
</figure>
</div>
<div class="lp-handoff">
<div class="lp-handoff__head">
<h3>Route R-224 handoff</h3>
<span>Warrington to Leeds</span>
</div>
<ol class="lp-steps">
<li class="lp-step lp-step--done">
<span class="lp-step__node" aria-hidden="true">1</span>
<div>
<p class="lp-step__title">Loaded and sealed</p>
<p class="lp-step__note">Bay 2 confirmed the manifest.</p>
</div>
<span class="lp-step__time">06:05</span>
</li>
<li class="lp-step lp-step--done">
<span class="lp-step__node" aria-hidden="true">2</span>
<div>
<p class="lp-step__title">Delay flagged on the M62</p>
<p class="lp-step__note">ETA moved to 14:22. The customer was told automatically.</p>
</div>
<span class="lp-step__time">12:48</span>
</li>
<li class="lp-step">
<span class="lp-step__node" aria-hidden="true">3</span>
<div>
<p class="lp-step__title">Evening driver takes over</p>
<p class="lp-step__note">Notes, photos and signatures move with the job.</p>
</div>
<span class="lp-step__time">14:10</span>
</li>
</ol>
</div>
</section>
<section class="lp-close" aria-labelledby="lp-close-heading">
<div class="lp-close__inner">
<div>
<h2 id="lp-close-heading">Ready when your next depot goes live.</h2>
<p>Start the trial today and invite the whole ops team. Nothing to install.</p>
</div>
<div class="lp-close__actions">
<a class="bw-btn bw-btn--primary bw-btn--lg" href="/templates/contact/full/">Start free trial</a>
<a class="bw-btn bw-btn--ghost bw-btn--lg" href="/templates/homepage/full/">Explore the product</a>
</div>
</div>
</section>
<footer class="lp-footer">
<p>Northline. Depot and fleet operations.</p>
<p>
<a href="/templates/homepage/full/">Product</a> · <a href="/templates/pricing/full/">Pricing</a> ·
<a href="/templates/contact/full/">Contact</a>
</p>
</footer>
</body>
</html>
Named export from @bwui/react. Do not invent other page components.
import "@bwui/css";
import { Landing } from "@bwui/react";
export default function Page() {
return (
<Landing
brand="Your brand"
title="Ship your next interface this afternoon"
description="One primary offer. One CTA. Recolour with tokens."
/>
);
}
Copy into your project. This example is not on the template loader path.
{% extends "brickwork_marketing/shell/marketing.html" %}
{% comment %}
A product landing page.
COPY THIS FILE into your project and edit it. It is not on the template loader
path, so you cannot extend it (ADR-056).
THIS IS THE PAGE THAT MOTIVATED ADR-056. Before 2.0.0 brickwork shipped a
landing page you extended and fed a flat context bag: heading, lede,
cta_heading, logo_cloud_heading, features_heading, and so on. That inverted
ownership of your own landing page (a pin bump could reorder your sections) and
forced prefix soup, because one page cannot use "heading" for six sections.
Here instead every section carries its own content, scoped to its own include.
There is no page-level bag, no prefix soup, and reordering, dropping, or
duplicating a section is editing this file, which is yours.
What your view must supply: nothing at all for the copy. List-shaped sections
cannot be built inline, so they come from context:
features [{icon, heading, body, url?}]
logos the logo cloud's items
feature_rows [{heading, body, media?}] for the alternating depth band
footer_groups [{heading, links:[{label, href}]}] for the footer include
Optional for a media-led hero (scorecard S5):
hero_media pre-rendered safe HTML for media_placement="beside"
(prefer a .bw-product-chrome / .bw-product-chrome-stack frame
with an illustrative SVG stage, not a flat placeholder panel)
Everything else below is typed straight into the template.
States: the auth-aware header actions branch on request.user.is_authenticated
(a signed-in vs signed-out header state); every composed component's own
states (hero, logo cloud, feature grid, feature rows, cta split, stat band,
testimonial, cta bleed) are unchanged, see each component's own header.
Accessibility: inherits shell/marketing.html's skip link and header/footer
landmarks; the composed hero renders the page's own <h1>. Covered by the
archetype harness's full gate sweep (render, axe WCAG 2.2 AA, no
horizontal overflow, light/dark distinctness, skip-link first-tab-stop
with JS disabled) at every W0.1 breakpoint, both themes, and directly by
axe.spec.mjs against landing-*.html, this page's own hand-maintained
fixture (also the fixture hero-placement-*.html and cta-width-*.html
compose their OWN standalone _hero.html/_cta.html renders against, not
this page, for the media_placement and width axes this page's own hero
and CTA do not exercise).
Responsive: no breakpoint switch of its own; inherits shell/marketing.html's
fluid header wrap and content-column cap. Each composed section carries
whatever responsive behaviour its own header documents (feature grid: 1/2/3
columns at sm/lg; feature rows and cta split: stack then two columns from
md; footer groups: 1 then 2 columns via .bw-marketing-footer__groups; the
testimonial grid: auto-fit columns; the rest: fluid, no named breakpoint).
{% endcomment %}
{% load brickwork_components %}
{% block page_title %}Northwind - invoicing that chases itself{% endblock %}
{% block brand_wordmark %}Northwind{% endblock %}
{% comment %}
Package-owned mobile disclosure (icvoss/django-brickwork#263 / #509): include
the toggle as a sibling of the nav via marketing_nav_region. Do not fill the
outer marketing_header and reproduce brand/nav/actions verbatim.
{% endcomment %}
{% block marketing_nav_region %}
{% include "brickwork_marketing/components/_mobile_nav_toggle.html" %}
<nav class="bw-site-header__nav" aria-label="Primary">
{% block marketing_nav %}
<a href="/features/">Features</a>
<a href="/pricing/">Pricing</a>
<a href="/customers/">Customers</a>
<a href="/docs/">Docs</a>
{% endblock %}
</nav>
{% endblock %}
{% comment %}
Auth-aware header actions. brickwork never reads auth state itself, so this is
your branch, not a package feature. Note Django's logout is POST-only.
{% endcomment %}
{% block marketing_actions %}
{% if request.user.is_authenticated %}
<a href="/app/">Go to app</a>
{% else %}
<a href="/accounts/login/">Sign in</a>
{% bw_button "Start free trial" variant="primary" href="/accounts/signup/" %}
{% endif %}
{% endblock %}
{% block content %}
{% comment %}
The hero. CTAs take flat label/url pairs, so a plain template can drive them
with no view involvement. (They also accept {label, url} dicts if the data
already exists in your context; the dict wins when both are given.)
hero_media is optional pre-rendered safe HTML for the media column;
media_placement="beside" is the screenshot/diagram shape. Prefer product
chrome (see fixture) over a flat coloured rectangle.
{% endcomment %}
{% include "brickwork_marketing/components/_hero.html" with eyebrow="Invoicing" heading="Invoicing that chases itself" lede="Northwind raises your invoices, sends the reminders, and tells you who is about to pay late. You do the work. It does the asking." primary_cta_label="Start free trial" primary_cta_href="/accounts/signup/" secondary_cta_label="Book a demo" secondary_cta_href="/demo/" media=hero_media media_placement="beside" %}
{% comment %}
Logo cloud. List-shaped, so logos comes from context.
{% endcomment %}
{% include "brickwork_marketing/components/_logo_cloud.html" with heading="Trusted by finance teams at" logos=logos greyscale=True %}
{% comment %}
Feature grid. Also list-shaped: build features in your view as
[{icon, heading, body, url}]. Give an item a url and the whole card becomes
a link; leave it off and the card stays a plain non-interactive block, never
a clickable-looking card with nowhere to go.
{% endcomment %}
{% include "brickwork_marketing/components/_feature_grid.html" with heading="Everything the chasing needs" lede="The parts of getting paid that nobody wants to do by hand." items=features columns=3 %}
{% comment %}
Alternating feature rows: the two or three capabilities that sell the
product, each with its own media. List-shaped media HTML comes from context
(same product-chrome idea as the hero).
{% endcomment %}
{% include "brickwork_marketing/components/_feature_rows.html" with heading="Built for the boring half of getting paid" items=feature_rows %}
{% comment %}
Mid-page CTA split: a nudge, not the page ending. Sibling of the closing
bleed below.
{% endcomment %}
{% include "brickwork_marketing/components/_cta_split.html" with heading="See what changes in the first quarter" body="Most teams cut days-to-pay without hiring anyone. Start free, import when you are ready." primary_cta_label="Start free trial" primary_cta_href="/accounts/signup/" secondary_cta_label="Talk to us" secondary_cta_href="/contact/" %}
{% comment %}
Stats. The band takes a list too, but a three-number band is often clearer
written straight into your view as [{value, label}].
{% endcomment %}
{% include "brickwork_marketing/components/_stat_band.html" with heading="What changes in the first quarter" stats=stats %}
{% comment %}
Quote grid: three testimonials that answer different doubts. Markup for the
grid wrapper lives here (nothing in the kit arranges several quotes); each
card is still the shipped _testimonial.html include.
{% endcomment %}
<section class="bw-testimonial-grid-section">
<h2 class="bw-testimonial-grid-section__heading">What finance teams say after a quarter</h2>
<div class="bw-testimonial-grid">
{% include "brickwork_marketing/components/_testimonial.html" with quote="We cut our average days-to-pay from 38 to 21 without hiring anyone. The reminders just go out." author="Priya Raman" role="Finance Director, Halden Group" %}
{% include "brickwork_marketing/components/_testimonial.html" with quote="I imported two years of invoices on a Tuesday afternoon and the first reminders went out that evening. There was no implementation project." author="Daniel Okoro" role="Head of Operations, Cransley Freight" %}
{% include "brickwork_marketing/components/_testimonial.html" with quote="The thing I was worried about was sounding aggressive to customers I like. It stops the moment they pay, so nobody has been chased for an invoice they already settled." author="Marta Lindqvist" role="Owner, Lindqvist Joinery" %}
</div>
</section>
{% comment %}
Closing CTA as inverse full-bleed: once per page, at the end.
{% endcomment %}
{% include "brickwork_marketing/components/_cta_bleed.html" with heading="Stop writing chase emails" body="Free for 30 days. No card, no setup call, no data import to sit through." primary_cta_label="Start free trial" primary_cta_href="/accounts/signup/" secondary_cta_label="Book a demo" secondary_cta_href="/demo/" %}
{% endblock %}
{% block marketing_footer %}
{% comment %}
Footer link groups via the package include. Pass footer_groups from the
view as [{heading, links:[{label, href}]}]; the include emits the shared
.bw-marketing-footer__groups vocabulary (#500 / #542).
{% endcomment %}
{% include "brickwork_marketing/components/_marketing_footer_groups.html" with groups=footer_groups %}
{% endblock %}
{% block footer_legal %}
<p>Copyright 2026 Northwind Software Ltd. <a href="/privacy/">Privacy</a> <a href="/terms/">Terms</a></p>
{% endblock %}
Details
| Kind | Page example |
|---|---|
| Used in | Marketing and public web |
Composed from
| Template | Description |
|---|---|
brickwork/components/_button.html |
A button or link styled as a button, in several variants. |
brickwork_marketing/components/_cta_bleed.html |
A full-bleed call-to-action band that runs edge to edge. |
brickwork_marketing/components/_cta_split.html |
A split call-to-action: copy on one side, actions on the other. |
brickwork_marketing/components/_feature_grid.html |
A grid of features, each with an icon, heading, and body. |
brickwork_marketing/components/_feature_rows.html |
Alternating feature rows with media and copy. |
brickwork_marketing/components/_hero.html |
A page-opening band with a heading, lede, and call to action. |
brickwork_marketing/components/_logo_cloud.html |
A row of partner or customer logos. |
brickwork_marketing/components/_marketing_footer_groups.html |
Named columns of footer links for the marketing shell footer region. |
brickwork_marketing/components/_mobile_nav_toggle.html |
A package-owned disclosure that collapses marketing nav and actions below the large breakpoint. |
brickwork_marketing/components/_stat_band.html |
A row of key figures, styled for a marketing page. |
brickwork_marketing/components/_testimonial.html |
A quote with attribution and an optional logo. |
brickwork_marketing/shell/marketing.html |
The public marketing shell: header, nav, content, and footer. |