20 CSS Blog Layouts07 / 20
Long-Form Article Reading Progress
The reading-progress bar without the scroll listener: animation-timeline: scroll() ties a scale-x animation directly to the document's scroll position, so it runs off the main thread and costs nothing. Wrapped around it, a proper long-form page — full-bleed hero, breakout figures, a sticky chapter rail and a 68ch measure.
Published
The code
<div class="bl-07">
<div class="bl-07__progressTrack" aria-hidden="true"><div class="bl-07__progress"></div></div>
<article class="bl-07__article">
<header class="bl-07__hero">
<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1600&q=70&auto=format&fit=crop" alt="Fog moving through a valley of pine forest at dawn" width="1600" height="900" loading="eager" decoding="async">
<div class="bl-07__heroText">
<p class="bl-07__kicker">Field Notes · 24 min read</p>
<h1>The forest that measures itself</h1>
<p class="bl-07__dek">Four thousand sensors, one contested dataset, and the researchers who spent a decade arguing about what counts as evidence.</p>
<p class="bl-07__byline">By Ines Halvorsen · <time datetime="2026-08-04">4 August 2026</time></p>
</div>
</header>
<nav class="bl-07__chapters" aria-label="Chapters">
<p>Chapters</p>
<ol>
<li><a href="#bl-07-c1" aria-current="location">The ridge</a></li>
<li><a href="#bl-07-c2">A decade of noise</a></li>
<li><a href="#bl-07-c3">What the trees said</a></li>
</ol>
</nav>
<div class="bl-07__body">
<p class="bl-07__lede">The first sensor went up in 2016, screwed into a Douglas fir at shoulder height by a graduate student who has since left science entirely. It is still transmitting.</p>
<h2 id="bl-07-c1">The ridge</h2>
<p>Nobody on the original team expected the array to outlive the grant. It was meant to run for eighteen months — long enough to publish, short enough to be cheap — and then be quietly decommissioned. Instead the sensors kept reporting, the batteries kept being replaced by a rotating cast of volunteers, and the dataset grew into something none of the original questions were designed to answer.</p>
<p>That is the part worth sitting with. A measurement programme that survives its own hypothesis becomes a different kind of object: not an experiment, but an archive.</p>
<figure class="bl-07__figure bl-07__figure--bleed">
<img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1600&q=70&auto=format&fit=crop" alt="Sunlight through tall trees in a dense forest" width="1600" height="900" loading="lazy" decoding="async">
<figcaption>The northern transect, where sensor density is highest. <span>Photo: Unsplash</span></figcaption>
</figure>
<h2 id="bl-07-c2">A decade of noise</h2>
<p>By 2019 the array was producing 40 million readings a year and almost no papers. The problem was not volume; it was that every interesting signal sat inside a band of variance that three different groups modelled three different ways.</p>
<blockquote class="bl-07__quote">
<p>“We spent four years learning that our error bars were the finding.”</p>
<cite>Dr Petra Lindqvist, project lead 2018–2024</cite>
</blockquote>
<p>What broke the deadlock was not better statistics but better bookkeeping. When the team finally published its calibration history — every battery swap, every replaced thermistor, every week a sensor sat under snow — the disagreements collapsed into a single, boring, defensible curve.</p>
<figure class="bl-07__figure">
<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=1200&q=70&auto=format&fit=crop" alt="Mountain lake reflecting a forested slope" width="1200" height="700" loading="lazy" decoding="async">
<figcaption>Downslope of the array, the control site has no instrumentation at all.</figcaption>
</figure>
<h2 id="bl-07-c3">What the trees said</h2>
<p>The published result is modest: the forest is warming slightly faster at the canopy than at the ground, and the gap widens in drought years. It fits inside a sentence. It took ten years, four institutions and an unbudgeted volunteer rota to earn.</p>
<p>Halvorsen's last visit was in June. The original sensor — the one screwed in at shoulder height in 2016 — now sits at chest height on a noticeably thicker trunk, still reporting every fifteen minutes, still uncalibrated against anything except its own long memory.</p>
<p class="bl-07__end">Ines Halvorsen writes about measurement. This piece was reported over three visits between 2024 and 2026.</p>
</div>
</article>
</div><div class="bl-07">
<div class="bl-07__progressTrack" aria-hidden="true"><div class="bl-07__progress"></div></div>
<article class="bl-07__article">
<header class="bl-07__hero">
<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1600&q=70&auto=format&fit=crop" alt="Fog moving through a valley of pine forest at dawn" width="1600" height="900" loading="eager" decoding="async">
<div class="bl-07__heroText">
<p class="bl-07__kicker">Field Notes · 24 min read</p>
<h1>The forest that measures itself</h1>
<p class="bl-07__dek">Four thousand sensors, one contested dataset, and the researchers who spent a decade arguing about what counts as evidence.</p>
<p class="bl-07__byline">By Ines Halvorsen · <time datetime="2026-08-04">4 August 2026</time></p>
</div>
</header>
<nav class="bl-07__chapters" aria-label="Chapters">
<p>Chapters</p>
<ol>
<li><a href="#bl-07-c1" aria-current="location">The ridge</a></li>
<li><a href="#bl-07-c2">A decade of noise</a></li>
<li><a href="#bl-07-c3">What the trees said</a></li>
</ol>
</nav>
<div class="bl-07__body">
<p class="bl-07__lede">The first sensor went up in 2016, screwed into a Douglas fir at shoulder height by a graduate student who has since left science entirely. It is still transmitting.</p>
<h2 id="bl-07-c1">The ridge</h2>
<p>Nobody on the original team expected the array to outlive the grant. It was meant to run for eighteen months — long enough to publish, short enough to be cheap — and then be quietly decommissioned. Instead the sensors kept reporting, the batteries kept being replaced by a rotating cast of volunteers, and the dataset grew into something none of the original questions were designed to answer.</p>
<p>That is the part worth sitting with. A measurement programme that survives its own hypothesis becomes a different kind of object: not an experiment, but an archive.</p>
<figure class="bl-07__figure bl-07__figure--bleed">
<img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1600&q=70&auto=format&fit=crop" alt="Sunlight through tall trees in a dense forest" width="1600" height="900" loading="lazy" decoding="async">
<figcaption>The northern transect, where sensor density is highest. <span>Photo: Unsplash</span></figcaption>
</figure>
<h2 id="bl-07-c2">A decade of noise</h2>
<p>By 2019 the array was producing 40 million readings a year and almost no papers. The problem was not volume; it was that every interesting signal sat inside a band of variance that three different groups modelled three different ways.</p>
<blockquote class="bl-07__quote">
<p>“We spent four years learning that our error bars were the finding.”</p>
<cite>Dr Petra Lindqvist, project lead 2018–2024</cite>
</blockquote>
<p>What broke the deadlock was not better statistics but better bookkeeping. When the team finally published its calibration history — every battery swap, every replaced thermistor, every week a sensor sat under snow — the disagreements collapsed into a single, boring, defensible curve.</p>
<figure class="bl-07__figure">
<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=1200&q=70&auto=format&fit=crop" alt="Mountain lake reflecting a forested slope" width="1200" height="700" loading="lazy" decoding="async">
<figcaption>Downslope of the array, the control site has no instrumentation at all.</figcaption>
</figure>
<h2 id="bl-07-c3">What the trees said</h2>
<p>The published result is modest: the forest is warming slightly faster at the canopy than at the ground, and the gap widens in drought years. It fits inside a sentence. It took ten years, four institutions and an unbudgeted volunteer rota to earn.</p>
<p>Halvorsen's last visit was in June. The original sensor — the one screwed in at shoulder height in 2016 — now sits at chest height on a noticeably thicker trunk, still reporting every fifteen minutes, still uncalibrated against anything except its own long memory.</p>
<p class="bl-07__end">Ines Halvorsen writes about measurement. This piece was reported over three visits between 2024 and 2026.</p>
</div>
</article>
</div>@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,600;1,6..72,400&family=Manrope:wght@400;500;600;700&display=swap');
.bl-07 {
--bg: #fbfaf8;
--text: #16181a;
--muted: #6c7075;
--line: #e2e0da;
--acc: #0f766e;
--hero-h: 68svh;
--measure: 68ch;
--serif: "Newsreader",ui-serif,Georgia,serif;
--sans: "Manrope",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
}
@supports (color: oklch(50% 0 0)) {
.bl-07 {
--bg: oklch(98.5% .004 95);
--text: oklch(19% .008 240);
--muted: oklch(52% .01 240);
--line: oklch(90% .008 95);
--acc: oklch(48% .1 180);
}
}
@media (prefers-color-scheme: dark) {
.bl-07:not([data-theme="light"]) {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
}
.bl-07[data-theme="dark"] {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
.bl-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--text);
font-family: var(--serif);
-webkit-font-smoothing: antialiased;
}
.bl-07,
.bl-07 *,
.bl-07 *::before,
.bl-07 *::after {
box-sizing: border-box;
}
.bl-07 a {
color: inherit;
}
.bl-07 :focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
border-radius: 2px;
}
.bl-07 ::selection {
background: color-mix(in oklab,var(--acc) 25%,transparent);
}
.bl-07__progressTrack {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
z-index: 20;
height: 3px;
background: color-mix(in oklab,var(--text) 10%,transparent);
}
.bl-07__progress {
height: 100%;
width: 100%;
background: var(--acc);
transform-origin: left;
scale: 0 1;
opacity: .35;
}
@supports (animation-timeline: scroll()) {
.bl-07__progress {
opacity: 1;
animation: bl-07-progress linear both;
animation-timeline: scroll(root block);
}
}
@keyframes bl-07-progress {
from {
scale: 0 1;
}
to {
scale: 1 1;
}
}
@supports not (animation-timeline: scroll()) {
.bl-07__progress {
scale: 1 1;
opacity: .28;
}
}
.bl-07__article {
display: grid;
grid-template-columns: [full-start] minmax(1rem,1fr) [content-start] min(var(--measure),100% - 2rem) [content-end] minmax(1rem,1fr) [full-end];
}
.bl-07__article > * {
grid-column: content;
}
.bl-07__body {
grid-column: full-start / full-end;
}
.bl-07__hero {
grid-column: full;
position: relative;
min-height: var(--hero-h);
display: grid;
align-items: end;
isolation: isolate;
}
.bl-07__hero img {
position: absolute;
inset: 0;
z-index: -2;
width: 100%;
height: 100%;
object-fit: cover;
background: var(--line);
}
.bl-07__hero::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(to top,rgba(6,8,10,.92) 8%,rgba(6,8,10,.55) 45%,rgba(6,8,10,.15) 80%);
}
.bl-07__heroText {
display: grid;
gap: .9rem;
color: #fff;
padding: clamp(2rem,6vw,5rem) clamp(1.25rem,6vw,6rem);
max-width: 52rem;
}
.bl-07__kicker {
margin: 0;
font-family: var(--sans);
font-size: .6875rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: #a7f3d0;
}
.bl-07__hero h1 {
margin: 0;
font-weight: 300;
font-size: clamp(2.25rem,6.5vw,5rem);
line-height: .98;
letter-spacing: -.03em;
text-wrap: balance;
}
.bl-07__dek {
margin: 0;
font-size: clamp(1.0625rem,2vw,1.375rem);
line-height: 1.55;
color: rgba(255,255,255,.82);
max-width: 50ch;
text-wrap: pretty;
}
.bl-07__byline {
margin: 0;
font-family: var(--sans);
font-size: .8125rem;
letter-spacing: .03em;
color: rgba(255,255,255,.7);
}
.bl-07__chapters {
position: sticky;
top: 0;
z-index: 10;
grid-column: full;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem 1.5rem;
padding: .75rem clamp(1.25rem,6vw,6rem);
border-bottom: 1px solid var(--line);
background: color-mix(in oklab,var(--bg) 88%,transparent);
font-family: var(--sans);
}
@supports (backdrop-filter: blur(6px)) {
.bl-07__chapters {
backdrop-filter: blur(12px);
}
}
.bl-07__chapters p {
margin: 0;
font-size: .625rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--muted);
}
.bl-07__chapters ol {
display: flex;
flex-wrap: wrap;
gap: .25rem 1.25rem;
margin: 0;
padding: 0;
list-style: none;
counter-reset: bl-07-ch;
}
.bl-07__chapters li {
counter-increment: bl-07-ch;
}
.bl-07__chapters a {
display: inline-flex;
align-items: center;
gap: .4rem;
min-height: 2.5rem;
font-size: .8125rem;
font-weight: 500;
color: var(--muted);
text-decoration: none;
}
.bl-07__chapters a::before {
content: "0" counter(bl-07-ch);
font-size: .625rem;
color: var(--acc);
font-variant-numeric: tabular-nums;
}
.bl-07__chapters a:hover {
color: var(--text);
}
.bl-07__chapters a[aria-current] {
color: var(--text);
text-decoration: underline;
text-underline-offset: .3em;
text-decoration-color: var(--acc);
}
.bl-07__body {
padding-block: clamp(2.5rem,6vw,4.5rem) clamp(4rem,10vw,8rem);
display: grid;
gap: 1.5rem;
grid-template-columns: subgrid;
}
.bl-07__body > * {
grid-column: content;
}
.bl-07__lede {
margin: 0;
font-size: clamp(1.25rem,2.4vw,1.625rem);
line-height: 1.5;
letter-spacing: -.015em;
color: var(--text);
text-wrap: pretty;
}
.bl-07__body h2 {
margin: clamp(1.5rem,3vw,2.5rem) 0 0;
font-family: var(--sans);
font-weight: 700;
font-size: .75rem;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--acc);
scroll-margin-block-start: 5rem;
}
.bl-07__body p {
margin: 0;
font-size: 1.1875rem;
line-height: 1.75;
text-wrap: pretty;
}
.bl-07__figure {
margin: clamp(1rem,2vw,2rem) 0;
display: grid;
gap: .65rem;
}
.bl-07__figure img {
display: block;
width: 100%;
height: auto;
border-radius: .375rem;
background: var(--line);
}
.bl-07__figure figcaption {
font-family: var(--sans);
font-size: .8125rem;
line-height: 1.5;
color: var(--muted);
}
.bl-07__figure figcaption span {
opacity: .65;
}
.bl-07__figure--bleed {
grid-column: full;
margin-inline: 0;
}
.bl-07__figure--bleed img {
border-radius: 0;
aspect-ratio: 16/9;
object-fit: cover;
}
.bl-07__figure--bleed figcaption {
padding-inline: clamp(1.25rem,6vw,6rem);
}
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.bl-07__figure {
animation: bl-07-rise linear both;
animation-timeline: view();
animation-range: entry 0% entry 55%;
}
@keyframes bl-07-rise {
from {
opacity: 0;
translate: 0 2rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
}
}
.bl-07__quote {
margin: clamp(1.5rem,3vw,2.5rem) 0;
padding-inline-start: 1.5rem;
border-inline-start: 2px solid var(--acc);
display: grid;
gap: .6rem;
}
.bl-07__quote p {
margin: 0;
font-size: clamp(1.375rem,3vw,1.875rem);
line-height: 1.3;
font-style: italic;
letter-spacing: -.02em;
text-wrap: balance;
}
.bl-07__quote cite {
font-family: var(--sans);
font-style: normal;
font-size: .8125rem;
color: var(--muted);
}
.bl-07__end {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--line);
font-family: var(--sans);
font-size: .875rem !important;
line-height: 1.6 !important;
color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
.bl-07__figure {
animation: none !important;
opacity: 1 !important;
translate: none !important;
}
.bl-07 * {
transition: none !important;
}
}
@media (forced-colors: active) {
.bl-07__progress {
background: Highlight;
}
}@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,600;1,6..72,400&family=Manrope:wght@400;500;600;700&display=swap');
.bl-07 {
--bg: #fbfaf8;
--text: #16181a;
--muted: #6c7075;
--line: #e2e0da;
--acc: #0f766e;
--hero-h: 68svh;
--measure: 68ch;
--serif: "Newsreader",ui-serif,Georgia,serif;
--sans: "Manrope",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
}
@supports (color: oklch(50% 0 0)) {
.bl-07 {
--bg: oklch(98.5% .004 95);
--text: oklch(19% .008 240);
--muted: oklch(52% .01 240);
--line: oklch(90% .008 95);
--acc: oklch(48% .1 180);
}
}
@media (prefers-color-scheme: dark) {
.bl-07:not([data-theme="light"]) {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
}
.bl-07[data-theme="dark"] {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
.bl-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--text);
font-family: var(--serif);
-webkit-font-smoothing: antialiased;
}
.bl-07,
.bl-07 *,
.bl-07 *::before,
.bl-07 *::after {
box-sizing: border-box;
}
.bl-07 a {
color: inherit;
}
.bl-07 :focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
border-radius: 2px;
}
.bl-07 ::selection {
background: color-mix(in oklab,var(--acc) 25%,transparent);
}
.bl-07__progressTrack {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
z-index: 20;
height: 3px;
background: color-mix(in oklab,var(--text) 10%,transparent);
}
.bl-07__progress {
height: 100%;
width: 100%;
background: var(--acc);
transform-origin: left;
scale: 0 1;
opacity: .35;
}
@supports (animation-timeline: scroll()) {
.bl-07__progress {
opacity: 1;
animation: bl-07-progress linear both;
animation-timeline: scroll(root block);
}
}
@keyframes bl-07-progress {
from {
scale: 0 1;
}
to {
scale: 1 1;
}
}
@supports not (animation-timeline: scroll()) {
.bl-07__progress {
scale: 1 1;
opacity: .28;
}
}
.bl-07__article {
display: grid;
grid-template-columns: [full-start] minmax(1rem,1fr) [content-start] min(var(--measure),100% - 2rem) [content-end] minmax(1rem,1fr) [full-end];
}
.bl-07__article > * {
grid-column: content;
}
.bl-07__body {
grid-column: full-start / full-end;
}
.bl-07__hero {
grid-column: full;
position: relative;
min-height: var(--hero-h);
display: grid;
align-items: end;
isolation: isolate;
}
.bl-07__hero img {
position: absolute;
inset: 0;
z-index: -2;
width: 100%;
height: 100%;
object-fit: cover;
background: var(--line);
}
.bl-07__hero::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(to top,rgba(6,8,10,.92) 8%,rgba(6,8,10,.55) 45%,rgba(6,8,10,.15) 80%);
}
.bl-07__heroText {
display: grid;
gap: .9rem;
color: #fff;
padding: clamp(2rem,6vw,5rem) clamp(1.25rem,6vw,6rem);
max-width: 52rem;
}
.bl-07__kicker {
margin: 0;
font-family: var(--sans);
font-size: .6875rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: #a7f3d0;
}
.bl-07__hero h1 {
margin: 0;
font-weight: 300;
font-size: clamp(2.25rem,6.5vw,5rem);
line-height: .98;
letter-spacing: -.03em;
text-wrap: balance;
}
.bl-07__dek {
margin: 0;
font-size: clamp(1.0625rem,2vw,1.375rem);
line-height: 1.55;
color: rgba(255,255,255,.82);
max-width: 50ch;
text-wrap: pretty;
}
.bl-07__byline {
margin: 0;
font-family: var(--sans);
font-size: .8125rem;
letter-spacing: .03em;
color: rgba(255,255,255,.7);
}
.bl-07__chapters {
position: sticky;
top: 0;
z-index: 10;
grid-column: full;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem 1.5rem;
padding: .75rem clamp(1.25rem,6vw,6rem);
border-bottom: 1px solid var(--line);
background: color-mix(in oklab,var(--bg) 88%,transparent);
font-family: var(--sans);
}
@supports (backdrop-filter: blur(6px)) {
.bl-07__chapters {
backdrop-filter: blur(12px);
}
}
.bl-07__chapters p {
margin: 0;
font-size: .625rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--muted);
}
.bl-07__chapters ol {
display: flex;
flex-wrap: wrap;
gap: .25rem 1.25rem;
margin: 0;
padding: 0;
list-style: none;
counter-reset: bl-07-ch;
}
.bl-07__chapters li {
counter-increment: bl-07-ch;
}
.bl-07__chapters a {
display: inline-flex;
align-items: center;
gap: .4rem;
min-height: 2.5rem;
font-size: .8125rem;
font-weight: 500;
color: var(--muted);
text-decoration: none;
}
.bl-07__chapters a::before {
content: "0" counter(bl-07-ch);
font-size: .625rem;
color: var(--acc);
font-variant-numeric: tabular-nums;
}
.bl-07__chapters a:hover {
color: var(--text);
}
.bl-07__chapters a[aria-current] {
color: var(--text);
text-decoration: underline;
text-underline-offset: .3em;
text-decoration-color: var(--acc);
}
.bl-07__body {
padding-block: clamp(2.5rem,6vw,4.5rem) clamp(4rem,10vw,8rem);
display: grid;
gap: 1.5rem;
grid-template-columns: subgrid;
}
.bl-07__body > * {
grid-column: content;
}
.bl-07__lede {
margin: 0;
font-size: clamp(1.25rem,2.4vw,1.625rem);
line-height: 1.5;
letter-spacing: -.015em;
color: var(--text);
text-wrap: pretty;
}
.bl-07__body h2 {
margin: clamp(1.5rem,3vw,2.5rem) 0 0;
font-family: var(--sans);
font-weight: 700;
font-size: .75rem;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--acc);
scroll-margin-block-start: 5rem;
}
.bl-07__body p {
margin: 0;
font-size: 1.1875rem;
line-height: 1.75;
text-wrap: pretty;
}
.bl-07__figure {
margin: clamp(1rem,2vw,2rem) 0;
display: grid;
gap: .65rem;
}
.bl-07__figure img {
display: block;
width: 100%;
height: auto;
border-radius: .375rem;
background: var(--line);
}
.bl-07__figure figcaption {
font-family: var(--sans);
font-size: .8125rem;
line-height: 1.5;
color: var(--muted);
}
.bl-07__figure figcaption span {
opacity: .65;
}
.bl-07__figure--bleed {
grid-column: full;
margin-inline: 0;
}
.bl-07__figure--bleed img {
border-radius: 0;
aspect-ratio: 16/9;
object-fit: cover;
}
.bl-07__figure--bleed figcaption {
padding-inline: clamp(1.25rem,6vw,6rem);
}
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.bl-07__figure {
animation: bl-07-rise linear both;
animation-timeline: view();
animation-range: entry 0% entry 55%;
}
@keyframes bl-07-rise {
from {
opacity: 0;
translate: 0 2rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
}
}
.bl-07__quote {
margin: clamp(1.5rem,3vw,2.5rem) 0;
padding-inline-start: 1.5rem;
border-inline-start: 2px solid var(--acc);
display: grid;
gap: .6rem;
}
.bl-07__quote p {
margin: 0;
font-size: clamp(1.375rem,3vw,1.875rem);
line-height: 1.3;
font-style: italic;
letter-spacing: -.02em;
text-wrap: balance;
}
.bl-07__quote cite {
font-family: var(--sans);
font-style: normal;
font-size: .8125rem;
color: var(--muted);
}
.bl-07__end {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--line);
font-family: var(--sans);
font-size: .875rem !important;
line-height: 1.6 !important;
color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
.bl-07__figure {
animation: none !important;
opacity: 1 !important;
translate: none !important;
}
.bl-07 * {
transition: none !important;
}
}
@media (forced-colors: active) {
.bl-07__progress {
background: Highlight;
}
}
/* grid items default to min-width:auto; 0 lets wide media and long words
stay inside their track instead of forcing the column open */
.bl-07__article > *{min-width:0;}
.bl-07__body > *{min-width:0;}
/* grid items default to min-width:auto; 0 lets wide media and long words
stay inside their track instead of forcing the column open */
.bl-07__article > *{min-width:0;}
.bl-07__body > *{min-width:0;}
Here's a working CSS Blog Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Long-Form Article Reading Progress
Source: https://codefronts.com/layouts/css-blog-layouts/long-form-article-reading-progress/
The reading-progress bar without the scroll listener: animation-timeline: scroll() ties a scale-x animation directly to the document's scroll position, so it runs off the main thread and costs nothing. Wrapped around it, a proper long-form page — full-bleed hero, breakout figures, a sticky chapter rail and a 68ch measure.
## HTML
```html
<div class="bl-07">
<div class="bl-07__progressTrack" aria-hidden="true"><div class="bl-07__progress"></div></div>
<article class="bl-07__article">
<header class="bl-07__hero">
<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1600&q=70&auto=format&fit=crop" alt="Fog moving through a valley of pine forest at dawn" width="1600" height="900" loading="eager" decoding="async">
<div class="bl-07__heroText">
<p class="bl-07__kicker">Field Notes · 24 min read</p>
<h1>The forest that measures itself</h1>
<p class="bl-07__dek">Four thousand sensors, one contested dataset, and the researchers who spent a decade arguing about what counts as evidence.</p>
<p class="bl-07__byline">By Ines Halvorsen · <time datetime="2026-08-04">4 August 2026</time></p>
</div>
</header>
<nav class="bl-07__chapters" aria-label="Chapters">
<p>Chapters</p>
<ol>
<li><a href="#bl-07-c1" aria-current="location">The ridge</a></li>
<li><a href="#bl-07-c2">A decade of noise</a></li>
<li><a href="#bl-07-c3">What the trees said</a></li>
</ol>
</nav>
<div class="bl-07__body">
<p class="bl-07__lede">The first sensor went up in 2016, screwed into a Douglas fir at shoulder height by a graduate student who has since left science entirely. It is still transmitting.</p>
<h2 id="bl-07-c1">The ridge</h2>
<p>Nobody on the original team expected the array to outlive the grant. It was meant to run for eighteen months — long enough to publish, short enough to be cheap — and then be quietly decommissioned. Instead the sensors kept reporting, the batteries kept being replaced by a rotating cast of volunteers, and the dataset grew into something none of the original questions were designed to answer.</p>
<p>That is the part worth sitting with. A measurement programme that survives its own hypothesis becomes a different kind of object: not an experiment, but an archive.</p>
<figure class="bl-07__figure bl-07__figure--bleed">
<img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1600&q=70&auto=format&fit=crop" alt="Sunlight through tall trees in a dense forest" width="1600" height="900" loading="lazy" decoding="async">
<figcaption>The northern transect, where sensor density is highest. <span>Photo: Unsplash</span></figcaption>
</figure>
<h2 id="bl-07-c2">A decade of noise</h2>
<p>By 2019 the array was producing 40 million readings a year and almost no papers. The problem was not volume; it was that every interesting signal sat inside a band of variance that three different groups modelled three different ways.</p>
<blockquote class="bl-07__quote">
<p>“We spent four years learning that our error bars were the finding.”</p>
<cite>Dr Petra Lindqvist, project lead 2018–2024</cite>
</blockquote>
<p>What broke the deadlock was not better statistics but better bookkeeping. When the team finally published its calibration history — every battery swap, every replaced thermistor, every week a sensor sat under snow — the disagreements collapsed into a single, boring, defensible curve.</p>
<figure class="bl-07__figure">
<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=1200&q=70&auto=format&fit=crop" alt="Mountain lake reflecting a forested slope" width="1200" height="700" loading="lazy" decoding="async">
<figcaption>Downslope of the array, the control site has no instrumentation at all.</figcaption>
</figure>
<h2 id="bl-07-c3">What the trees said</h2>
<p>The published result is modest: the forest is warming slightly faster at the canopy than at the ground, and the gap widens in drought years. It fits inside a sentence. It took ten years, four institutions and an unbudgeted volunteer rota to earn.</p>
<p>Halvorsen's last visit was in June. The original sensor — the one screwed in at shoulder height in 2016 — now sits at chest height on a noticeably thicker trunk, still reporting every fifteen minutes, still uncalibrated against anything except its own long memory.</p>
<p class="bl-07__end">Ines Halvorsen writes about measurement. This piece was reported over three visits between 2024 and 2026.</p>
</div>
</article>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,600;1,6..72,400&family=Manrope:wght@400;500;600;700&display=swap');
.bl-07 {
--bg: #fbfaf8;
--text: #16181a;
--muted: #6c7075;
--line: #e2e0da;
--acc: #0f766e;
--hero-h: 68svh;
--measure: 68ch;
--serif: "Newsreader",ui-serif,Georgia,serif;
--sans: "Manrope",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
}
@supports (color: oklch(50% 0 0)) {
.bl-07 {
--bg: oklch(98.5% .004 95);
--text: oklch(19% .008 240);
--muted: oklch(52% .01 240);
--line: oklch(90% .008 95);
--acc: oklch(48% .1 180);
}
}
@media (prefers-color-scheme: dark) {
.bl-07:not([data-theme="light"]) {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
}
.bl-07[data-theme="dark"] {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
.bl-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--text);
font-family: var(--serif);
-webkit-font-smoothing: antialiased;
}
.bl-07,
.bl-07 *,
.bl-07 *::before,
.bl-07 *::after {
box-sizing: border-box;
}
.bl-07 a {
color: inherit;
}
.bl-07 :focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
border-radius: 2px;
}
.bl-07 ::selection {
background: color-mix(in oklab,var(--acc) 25%,transparent);
}
.bl-07__progressTrack {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
z-index: 20;
height: 3px;
background: color-mix(in oklab,var(--text) 10%,transparent);
}
.bl-07__progress {
height: 100%;
width: 100%;
background: var(--acc);
transform-origin: left;
scale: 0 1;
opacity: .35;
}
@supports (animation-timeline: scroll()) {
.bl-07__progress {
opacity: 1;
animation: bl-07-progress linear both;
animation-timeline: scroll(root block);
}
}
@keyframes bl-07-progress {
from {
scale: 0 1;
}
to {
scale: 1 1;
}
}
@supports not (animation-timeline: scroll()) {
.bl-07__progress {
scale: 1 1;
opacity: .28;
}
}
.bl-07__article {
display: grid;
grid-template-columns: [full-start] minmax(1rem,1fr) [content-start] min(var(--measure),100% - 2rem) [content-end] minmax(1rem,1fr) [full-end];
}
.bl-07__article > * {
grid-column: content;
}
.bl-07__body {
grid-column: full-start / full-end;
}
.bl-07__hero {
grid-column: full;
position: relative;
min-height: var(--hero-h);
display: grid;
align-items: end;
isolation: isolate;
}
.bl-07__hero img {
position: absolute;
inset: 0;
z-index: -2;
width: 100%;
height: 100%;
object-fit: cover;
background: var(--line);
}
.bl-07__hero::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(to top,rgba(6,8,10,.92) 8%,rgba(6,8,10,.55) 45%,rgba(6,8,10,.15) 80%);
}
.bl-07__heroText {
display: grid;
gap: .9rem;
color: #fff;
padding: clamp(2rem,6vw,5rem) clamp(1.25rem,6vw,6rem);
max-width: 52rem;
}
.bl-07__kicker {
margin: 0;
font-family: var(--sans);
font-size: .6875rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: #a7f3d0;
}
.bl-07__hero h1 {
margin: 0;
font-weight: 300;
font-size: clamp(2.25rem,6.5vw,5rem);
line-height: .98;
letter-spacing: -.03em;
text-wrap: balance;
}
.bl-07__dek {
margin: 0;
font-size: clamp(1.0625rem,2vw,1.375rem);
line-height: 1.55;
color: rgba(255,255,255,.82);
max-width: 50ch;
text-wrap: pretty;
}
.bl-07__byline {
margin: 0;
font-family: var(--sans);
font-size: .8125rem;
letter-spacing: .03em;
color: rgba(255,255,255,.7);
}
.bl-07__chapters {
position: sticky;
top: 0;
z-index: 10;
grid-column: full;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem 1.5rem;
padding: .75rem clamp(1.25rem,6vw,6rem);
border-bottom: 1px solid var(--line);
background: color-mix(in oklab,var(--bg) 88%,transparent);
font-family: var(--sans);
}
@supports (backdrop-filter: blur(6px)) {
.bl-07__chapters {
backdrop-filter: blur(12px);
}
}
.bl-07__chapters p {
margin: 0;
font-size: .625rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--muted);
}
.bl-07__chapters ol {
display: flex;
flex-wrap: wrap;
gap: .25rem 1.25rem;
margin: 0;
padding: 0;
list-style: none;
counter-reset: bl-07-ch;
}
.bl-07__chapters li {
counter-increment: bl-07-ch;
}
.bl-07__chapters a {
display: inline-flex;
align-items: center;
gap: .4rem;
min-height: 2.5rem;
font-size: .8125rem;
font-weight: 500;
color: var(--muted);
text-decoration: none;
}
.bl-07__chapters a::before {
content: "0" counter(bl-07-ch);
font-size: .625rem;
color: var(--acc);
font-variant-numeric: tabular-nums;
}
.bl-07__chapters a:hover {
color: var(--text);
}
.bl-07__chapters a[aria-current] {
color: var(--text);
text-decoration: underline;
text-underline-offset: .3em;
text-decoration-color: var(--acc);
}
.bl-07__body {
padding-block: clamp(2.5rem,6vw,4.5rem) clamp(4rem,10vw,8rem);
display: grid;
gap: 1.5rem;
grid-template-columns: subgrid;
}
.bl-07__body > * {
grid-column: content;
}
.bl-07__lede {
margin: 0;
font-size: clamp(1.25rem,2.4vw,1.625rem);
line-height: 1.5;
letter-spacing: -.015em;
color: var(--text);
text-wrap: pretty;
}
.bl-07__body h2 {
margin: clamp(1.5rem,3vw,2.5rem) 0 0;
font-family: var(--sans);
font-weight: 700;
font-size: .75rem;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--acc);
scroll-margin-block-start: 5rem;
}
.bl-07__body p {
margin: 0;
font-size: 1.1875rem;
line-height: 1.75;
text-wrap: pretty;
}
.bl-07__figure {
margin: clamp(1rem,2vw,2rem) 0;
display: grid;
gap: .65rem;
}
.bl-07__figure img {
display: block;
width: 100%;
height: auto;
border-radius: .375rem;
background: var(--line);
}
.bl-07__figure figcaption {
font-family: var(--sans);
font-size: .8125rem;
line-height: 1.5;
color: var(--muted);
}
.bl-07__figure figcaption span {
opacity: .65;
}
.bl-07__figure--bleed {
grid-column: full;
margin-inline: 0;
}
.bl-07__figure--bleed img {
border-radius: 0;
aspect-ratio: 16/9;
object-fit: cover;
}
.bl-07__figure--bleed figcaption {
padding-inline: clamp(1.25rem,6vw,6rem);
}
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.bl-07__figure {
animation: bl-07-rise linear both;
animation-timeline: view();
animation-range: entry 0% entry 55%;
}
@keyframes bl-07-rise {
from {
opacity: 0;
translate: 0 2rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
}
}
.bl-07__quote {
margin: clamp(1.5rem,3vw,2.5rem) 0;
padding-inline-start: 1.5rem;
border-inline-start: 2px solid var(--acc);
display: grid;
gap: .6rem;
}
.bl-07__quote p {
margin: 0;
font-size: clamp(1.375rem,3vw,1.875rem);
line-height: 1.3;
font-style: italic;
letter-spacing: -.02em;
text-wrap: balance;
}
.bl-07__quote cite {
font-family: var(--sans);
font-style: normal;
font-size: .8125rem;
color: var(--muted);
}
.bl-07__end {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--line);
font-family: var(--sans);
font-size: .875rem !important;
line-height: 1.6 !important;
color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
.bl-07__figure {
animation: none !important;
opacity: 1 !important;
translate: none !important;
}
.bl-07 * {
transition: none !important;
}
}
@media (forced-colors: active) {
.bl-07__progress {
background: Highlight;
}
}
```
## JavaScript
```js
/* grid items default to min-width:auto; 0 lets wide media and long words
stay inside their track instead of forcing the column open */
.bl-07__article > *{min-width:0;}
.bl-07__body > *{min-width:0;}
```Here's a working CSS Blog Layout from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Long-Form Article Reading Progress
Source: https://codefronts.com/layouts/css-blog-layouts/long-form-article-reading-progress/
The reading-progress bar without the scroll listener: animation-timeline: scroll() ties a scale-x animation directly to the document's scroll position, so it runs off the main thread and costs nothing. Wrapped around it, a proper long-form page — full-bleed hero, breakout figures, a sticky chapter rail and a 68ch measure.
## HTML
```html
<div class="bl-07">
<div class="bl-07__progressTrack" aria-hidden="true"><div class="bl-07__progress"></div></div>
<article class="bl-07__article">
<header class="bl-07__hero">
<img src="https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?w=1600&q=70&auto=format&fit=crop" alt="Fog moving through a valley of pine forest at dawn" width="1600" height="900" loading="eager" decoding="async">
<div class="bl-07__heroText">
<p class="bl-07__kicker">Field Notes · 24 min read</p>
<h1>The forest that measures itself</h1>
<p class="bl-07__dek">Four thousand sensors, one contested dataset, and the researchers who spent a decade arguing about what counts as evidence.</p>
<p class="bl-07__byline">By Ines Halvorsen · <time datetime="2026-08-04">4 August 2026</time></p>
</div>
</header>
<nav class="bl-07__chapters" aria-label="Chapters">
<p>Chapters</p>
<ol>
<li><a href="#bl-07-c1" aria-current="location">The ridge</a></li>
<li><a href="#bl-07-c2">A decade of noise</a></li>
<li><a href="#bl-07-c3">What the trees said</a></li>
</ol>
</nav>
<div class="bl-07__body">
<p class="bl-07__lede">The first sensor went up in 2016, screwed into a Douglas fir at shoulder height by a graduate student who has since left science entirely. It is still transmitting.</p>
<h2 id="bl-07-c1">The ridge</h2>
<p>Nobody on the original team expected the array to outlive the grant. It was meant to run for eighteen months — long enough to publish, short enough to be cheap — and then be quietly decommissioned. Instead the sensors kept reporting, the batteries kept being replaced by a rotating cast of volunteers, and the dataset grew into something none of the original questions were designed to answer.</p>
<p>That is the part worth sitting with. A measurement programme that survives its own hypothesis becomes a different kind of object: not an experiment, but an archive.</p>
<figure class="bl-07__figure bl-07__figure--bleed">
<img src="https://images.unsplash.com/photo-1441974231531-c6227db76b6e?w=1600&q=70&auto=format&fit=crop" alt="Sunlight through tall trees in a dense forest" width="1600" height="900" loading="lazy" decoding="async">
<figcaption>The northern transect, where sensor density is highest. <span>Photo: Unsplash</span></figcaption>
</figure>
<h2 id="bl-07-c2">A decade of noise</h2>
<p>By 2019 the array was producing 40 million readings a year and almost no papers. The problem was not volume; it was that every interesting signal sat inside a band of variance that three different groups modelled three different ways.</p>
<blockquote class="bl-07__quote">
<p>“We spent four years learning that our error bars were the finding.”</p>
<cite>Dr Petra Lindqvist, project lead 2018–2024</cite>
</blockquote>
<p>What broke the deadlock was not better statistics but better bookkeeping. When the team finally published its calibration history — every battery swap, every replaced thermistor, every week a sensor sat under snow — the disagreements collapsed into a single, boring, defensible curve.</p>
<figure class="bl-07__figure">
<img src="https://images.unsplash.com/photo-1506744038136-46273834b3fb?w=1200&q=70&auto=format&fit=crop" alt="Mountain lake reflecting a forested slope" width="1200" height="700" loading="lazy" decoding="async">
<figcaption>Downslope of the array, the control site has no instrumentation at all.</figcaption>
</figure>
<h2 id="bl-07-c3">What the trees said</h2>
<p>The published result is modest: the forest is warming slightly faster at the canopy than at the ground, and the gap widens in drought years. It fits inside a sentence. It took ten years, four institutions and an unbudgeted volunteer rota to earn.</p>
<p>Halvorsen's last visit was in June. The original sensor — the one screwed in at shoulder height in 2016 — now sits at chest height on a noticeably thicker trunk, still reporting every fifteen minutes, still uncalibrated against anything except its own long memory.</p>
<p class="bl-07__end">Ines Halvorsen writes about measurement. This piece was reported over three visits between 2024 and 2026.</p>
</div>
</article>
</div>
```
## CSS
```css
@import url('https://fonts.googleapis.com/css2?family=Newsreader:ital,opsz,wght@0,6..72,300;0,6..72,400;0,6..72,600;1,6..72,400&family=Manrope:wght@400;500;600;700&display=swap');
.bl-07 {
--bg: #fbfaf8;
--text: #16181a;
--muted: #6c7075;
--line: #e2e0da;
--acc: #0f766e;
--hero-h: 68svh;
--measure: 68ch;
--serif: "Newsreader",ui-serif,Georgia,serif;
--sans: "Manrope",ui-sans-serif,system-ui,-apple-system,"Segoe UI",sans-serif;
}
@supports (color: oklch(50% 0 0)) {
.bl-07 {
--bg: oklch(98.5% .004 95);
--text: oklch(19% .008 240);
--muted: oklch(52% .01 240);
--line: oklch(90% .008 95);
--acc: oklch(48% .1 180);
}
}
@media (prefers-color-scheme: dark) {
.bl-07:not([data-theme="light"]) {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
}
.bl-07[data-theme="dark"] {
--bg: #0d0f10;
--text: #f1f2f2;
--muted: #9aa0a4;
--line: #25292b;
--acc: #5eead4;
}
.bl-07 {
width: 100%;
min-height: 100vh;
min-height: 100svh;
display: block;
background: var(--bg);
color: var(--text);
font-family: var(--serif);
-webkit-font-smoothing: antialiased;
}
.bl-07,
.bl-07 *,
.bl-07 *::before,
.bl-07 *::after {
box-sizing: border-box;
}
.bl-07 a {
color: inherit;
}
.bl-07 :focus-visible {
outline: 2px solid var(--acc);
outline-offset: 3px;
border-radius: 2px;
}
.bl-07 ::selection {
background: color-mix(in oklab,var(--acc) 25%,transparent);
}
.bl-07__progressTrack {
position: fixed;
inset-block-start: 0;
inset-inline: 0;
z-index: 20;
height: 3px;
background: color-mix(in oklab,var(--text) 10%,transparent);
}
.bl-07__progress {
height: 100%;
width: 100%;
background: var(--acc);
transform-origin: left;
scale: 0 1;
opacity: .35;
}
@supports (animation-timeline: scroll()) {
.bl-07__progress {
opacity: 1;
animation: bl-07-progress linear both;
animation-timeline: scroll(root block);
}
}
@keyframes bl-07-progress {
from {
scale: 0 1;
}
to {
scale: 1 1;
}
}
@supports not (animation-timeline: scroll()) {
.bl-07__progress {
scale: 1 1;
opacity: .28;
}
}
.bl-07__article {
display: grid;
grid-template-columns: [full-start] minmax(1rem,1fr) [content-start] min(var(--measure),100% - 2rem) [content-end] minmax(1rem,1fr) [full-end];
}
.bl-07__article > * {
grid-column: content;
}
.bl-07__body {
grid-column: full-start / full-end;
}
.bl-07__hero {
grid-column: full;
position: relative;
min-height: var(--hero-h);
display: grid;
align-items: end;
isolation: isolate;
}
.bl-07__hero img {
position: absolute;
inset: 0;
z-index: -2;
width: 100%;
height: 100%;
object-fit: cover;
background: var(--line);
}
.bl-07__hero::after {
content: "";
position: absolute;
inset: 0;
z-index: -1;
background: linear-gradient(to top,rgba(6,8,10,.92) 8%,rgba(6,8,10,.55) 45%,rgba(6,8,10,.15) 80%);
}
.bl-07__heroText {
display: grid;
gap: .9rem;
color: #fff;
padding: clamp(2rem,6vw,5rem) clamp(1.25rem,6vw,6rem);
max-width: 52rem;
}
.bl-07__kicker {
margin: 0;
font-family: var(--sans);
font-size: .6875rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: #a7f3d0;
}
.bl-07__hero h1 {
margin: 0;
font-weight: 300;
font-size: clamp(2.25rem,6.5vw,5rem);
line-height: .98;
letter-spacing: -.03em;
text-wrap: balance;
}
.bl-07__dek {
margin: 0;
font-size: clamp(1.0625rem,2vw,1.375rem);
line-height: 1.55;
color: rgba(255,255,255,.82);
max-width: 50ch;
text-wrap: pretty;
}
.bl-07__byline {
margin: 0;
font-family: var(--sans);
font-size: .8125rem;
letter-spacing: .03em;
color: rgba(255,255,255,.7);
}
.bl-07__chapters {
position: sticky;
top: 0;
z-index: 10;
grid-column: full;
display: flex;
flex-wrap: wrap;
align-items: center;
gap: .5rem 1.5rem;
padding: .75rem clamp(1.25rem,6vw,6rem);
border-bottom: 1px solid var(--line);
background: color-mix(in oklab,var(--bg) 88%,transparent);
font-family: var(--sans);
}
@supports (backdrop-filter: blur(6px)) {
.bl-07__chapters {
backdrop-filter: blur(12px);
}
}
.bl-07__chapters p {
margin: 0;
font-size: .625rem;
font-weight: 700;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--muted);
}
.bl-07__chapters ol {
display: flex;
flex-wrap: wrap;
gap: .25rem 1.25rem;
margin: 0;
padding: 0;
list-style: none;
counter-reset: bl-07-ch;
}
.bl-07__chapters li {
counter-increment: bl-07-ch;
}
.bl-07__chapters a {
display: inline-flex;
align-items: center;
gap: .4rem;
min-height: 2.5rem;
font-size: .8125rem;
font-weight: 500;
color: var(--muted);
text-decoration: none;
}
.bl-07__chapters a::before {
content: "0" counter(bl-07-ch);
font-size: .625rem;
color: var(--acc);
font-variant-numeric: tabular-nums;
}
.bl-07__chapters a:hover {
color: var(--text);
}
.bl-07__chapters a[aria-current] {
color: var(--text);
text-decoration: underline;
text-underline-offset: .3em;
text-decoration-color: var(--acc);
}
.bl-07__body {
padding-block: clamp(2.5rem,6vw,4.5rem) clamp(4rem,10vw,8rem);
display: grid;
gap: 1.5rem;
grid-template-columns: subgrid;
}
.bl-07__body > * {
grid-column: content;
}
.bl-07__lede {
margin: 0;
font-size: clamp(1.25rem,2.4vw,1.625rem);
line-height: 1.5;
letter-spacing: -.015em;
color: var(--text);
text-wrap: pretty;
}
.bl-07__body h2 {
margin: clamp(1.5rem,3vw,2.5rem) 0 0;
font-family: var(--sans);
font-weight: 700;
font-size: .75rem;
letter-spacing: .18em;
text-transform: uppercase;
color: var(--acc);
scroll-margin-block-start: 5rem;
}
.bl-07__body p {
margin: 0;
font-size: 1.1875rem;
line-height: 1.75;
text-wrap: pretty;
}
.bl-07__figure {
margin: clamp(1rem,2vw,2rem) 0;
display: grid;
gap: .65rem;
}
.bl-07__figure img {
display: block;
width: 100%;
height: auto;
border-radius: .375rem;
background: var(--line);
}
.bl-07__figure figcaption {
font-family: var(--sans);
font-size: .8125rem;
line-height: 1.5;
color: var(--muted);
}
.bl-07__figure figcaption span {
opacity: .65;
}
.bl-07__figure--bleed {
grid-column: full;
margin-inline: 0;
}
.bl-07__figure--bleed img {
border-radius: 0;
aspect-ratio: 16/9;
object-fit: cover;
}
.bl-07__figure--bleed figcaption {
padding-inline: clamp(1.25rem,6vw,6rem);
}
@supports (animation-timeline: view()) {
@media (prefers-reduced-motion: no-preference) {
.bl-07__figure {
animation: bl-07-rise linear both;
animation-timeline: view();
animation-range: entry 0% entry 55%;
}
@keyframes bl-07-rise {
from {
opacity: 0;
translate: 0 2rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
}
}
.bl-07__quote {
margin: clamp(1.5rem,3vw,2.5rem) 0;
padding-inline-start: 1.5rem;
border-inline-start: 2px solid var(--acc);
display: grid;
gap: .6rem;
}
.bl-07__quote p {
margin: 0;
font-size: clamp(1.375rem,3vw,1.875rem);
line-height: 1.3;
font-style: italic;
letter-spacing: -.02em;
text-wrap: balance;
}
.bl-07__quote cite {
font-family: var(--sans);
font-style: normal;
font-size: .8125rem;
color: var(--muted);
}
.bl-07__end {
margin-top: 1.5rem;
padding-top: 1.5rem;
border-top: 1px solid var(--line);
font-family: var(--sans);
font-size: .875rem !important;
line-height: 1.6 !important;
color: var(--muted);
}
@media (prefers-reduced-motion: reduce) {
.bl-07__figure {
animation: none !important;
opacity: 1 !important;
translate: none !important;
}
.bl-07 * {
transition: none !important;
}
}
@media (forced-colors: active) {
.bl-07__progress {
background: Highlight;
}
}
```
## JavaScript
```js
/* grid items default to min-width:auto; 0 lets wide media and long words
stay inside their track instead of forcing the column open */
.bl-07__article > *{min-width:0;}
.bl-07__body > *{min-width:0;}
```How this works
Scroll-driven animations are declarative. You describe the animation, then name the timeline:
@keyframes bl-07-progress{ from{ scale:0 1; } to{ scale:1 1; } }
.bl-07__progress{
transform-origin:left;
animation:bl-07-progress linear both;
animation-timeline:scroll(root block);
}No requestAnimationFrame, no passive:true listener, no layout reads — the compositor drives it, so it stays smooth while the page is busy. The whole block sits behind @supports (animation-timeline: scroll()); where it is missing, the bar simply renders at full width in a muted colour and nothing looks broken.
The same primitive does a second job here: figures fade and rise using animation-timeline: view(), which is per-element and needs no observer:
.bl-07__figure{ animation:bl-07-rise linear both; animation-timeline:view(); animation-range:entry 0% entry 60%; }Layout is a single grid with a named full-bleed track, the technique that lets prose stay at 68ch while images break out to the viewport edge without negative margins.
Make it yours
- Change what the bar measures by swapping the timeline source:
scroll(root block)for the page, or a namedview-timelineon the<article>to track only the article body. - Make it a circular indicator by animating
--pin aconic-gradientinstead ofscale(registered with@propertyso it interpolates). - The full-bleed grid uses
[full-start] minmax(1rem,1fr) [content-start] min(68ch,100%) [content-end] minmax(1rem,1fr) [full-end]— widen68chfor a looser measure. - The chapter rail is a plain nav; add
:targetstyling for the current section if you render anchors server-side. - Drop
--hero-hfrom 68svh to 48svh for a news feel rather than an essay feel.
Gotchas — read before shipping
scroll(root)only works when the document itself scrolls. If your app scrolls an inner element, name that element's timeline withscroll-timeline-nameor the animation never advances.- Scroll-driven animations must be inside
@supports. Without the gate, non-supporting browsers keep the keyframes'fromstate and you ship an invisible or zero-width bar. - Use
svhnotvhfor the hero on mobile, or the browser chrome eats a slice of your headline. - A progress bar is decorative: it needs
aria-hidden="true", notrole="progressbar"— announcing scroll position on every pixel is noise. animation-range: entrypercentages are relative to the element's own view progress, so very tall elements need a shorter range or they finish animating off-screen.- Under
prefers-reduced-motion, keep the bar (it is information, not motion) but drop the figure reveals.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 117+ | 26+ | not yet | 117+ |
Floor set by oklch(), color-mix(), backdrop-filter, animation-timeline, subgrid, text-wrap as this demo is written. The core technique itself goes back further — Chrome 115+.
animation-timeline: scroll()/view() shipped in Chrome 115 and is behind the @supports gate here; Safari and Firefox landed it more recently. Everything else — the full-bleed grid, sticky rail, clamp() type — works in every current browser, and the fallback bar is a static rule.