Open demo

CSS code

HTML
<div class="drk-progress" aria-hidden="true"></div>

<div><span class="lbl">Hero photo that drifts closer as it scrolls away</span>
<section class="drk drk-hero" aria-labelledby="drk-title">
  <img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=75" alt="The Milky Way over a snowy mountain ridge at night" width="1600" height="1067" loading="lazy">
  <p>Nocturne Optics · Lumen 4</p>
  <h2 id="drk-title">A camera that sees in the dark</h2>
  <p>Built for the sky after midnight: a cooled sensor, a mount that follows the stars and a battery that lasts the whole night.</p>
</section></div>

<div><span class="lbl">Sentences light up as you read</span>
<p class="drk drk-read"><span>Most cameras give up when the sun goes down.</span> <span>Lumen 4 starts there.</span> <span>Its sensor is cooled twenty degrees below the air around it, so long exposures stay clean.</span> <span>Point it at the sky, press once, and go back to bed.</span></p></div>

<div><span class="lbl">Counters that count up</span>
<dl class="drk drk-stats">
  <div><dt>megapixel cooled sensor</dt><dd><span class="drk-num" style="--drk-to:38"><span>38</span></span></dd></div>
  <div><dt>stops of dynamic range</dt><dd><span class="drk-num" style="--drk-to:14"><span>14</span></span></dd></div>
  <div><dt>minutes of star tracking per charge</dt><dd><span class="drk-num" style="--drk-to:412"><span>412</span></span></dd></div>
</dl></div>

<div><span class="lbl">Cards that come into focus</span>
<ul class="drk drk-cards" aria-label="Lumen 4 features">
  <li><i aria-hidden="true">✦</i><h3>Star tracking</h3><p>The mount turns with the earth, so a ten-minute exposure keeps stars as points, not streaks.</p></li>
  <li><i aria-hidden="true">◐</i><h3>Dew heater</h3><p>A ring warms the front glass by two degrees, enough to keep it clear until sunrise.</p></li>
  <li><i aria-hidden="true">⌁</i><h3>Phone control</h3><p>Frame, focus and schedule a night of shots from bed, over Wi-Fi or Bluetooth.</p></li>
</ul></div>
CSS
/* Dark scroll animations, CSS only. Everything is drawn in its finished state first: bright text, real numbers,
   sharp cards. Where scroll-driven animations are supported and motion is allowed, view() and scroll() timelines
   add a page progress glow, a hero photo that drifts closer as it leaves, sentences that light up as they cross the
   middle of the screen, counters that count up and cards that come into focus. */
@property --drk-n{syntax:"<integer>";inherits:false;initial-value:0}
.drk{--drk-bg:#070b14;--drk-surface:#0f1626;--drk-line:#1e2a40;--drk-text:#e8edf7;--drk-muted:#9aa7bf;--drk-dim:#7d8aa3;
  --drk-a1:#5eead4;--drk-a2:#a78bfa;color:var(--drk-text)}
.drk :where(h2,h3,p,ul,dl,dd,figure){margin:0}
/* page progress glow */
.drk-progress{position:fixed;inset:0 0 auto;z-index:30;height:3px;background:linear-gradient(90deg,var(--drk-a1),var(--drk-a2));
  box-shadow:0 0 14px 1px rgba(94,234,212,.7);transform-origin:0 50%;transform:scaleX(0)}
/* hero */
.drk-hero{position:relative;overflow:hidden;min-height:clamp(420px,70vh,600px);display:grid;align-content:end;padding:clamp(24px,5vw,56px);border-radius:24px;isolation:isolate}
.drk-hero img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2}
.drk-hero::before{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,rgba(7,11,20,0) 20%,rgba(7,11,20,.92) 85%)}
.drk-hero p:first-of-type{font-size:.78rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--drk-a1)}
.drk-hero h2{margin-top:10px;max-width:15ch;font-size:clamp(2.2rem,1.4rem + 3.4vw,4rem);line-height:1;letter-spacing:-.035em}
.drk-hero p+h2+p{margin-top:14px;max-width:46ch;color:#c4cde0}
/* sentence highlight */
.drk-read{max-width:30ch;font-size:clamp(1.5rem,1.1rem + 1.8vw,2.4rem);line-height:1.3;font-weight:600;letter-spacing:-.015em}
.drk-read span{color:var(--drk-text)}
/* counters */
.drk-stats{display:grid;grid-template-columns:repeat(auto-fit,minmax(200px,1fr));gap:16px}
.drk-stats div{padding:24px;border:1px solid var(--drk-line);border-radius:20px;background:var(--drk-surface)}
.drk-stats dd{font-size:clamp(2.6rem,2rem + 2vw,3.6rem);font-weight:800;line-height:1;letter-spacing:-.04em;
  background:linear-gradient(90deg,var(--drk-a1),var(--drk-a2));-webkit-background-clip:text;background-clip:text;color:var(--drk-a1);-webkit-text-fill-color:transparent}
.drk-stats dt{margin-top:10px;color:var(--drk-muted)}
.drk-stats div{display:flex;flex-direction:column-reverse;justify-content:flex-end}
/* focus cards */
.drk-cards{list-style:none;padding:0;display:grid;grid-template-columns:repeat(auto-fit,minmax(230px,1fr));gap:16px}
.drk-cards li{padding:24px;border:1px solid var(--drk-line);border-radius:20px;background:radial-gradient(120% 80% at 0 0,rgba(167,139,250,.14),transparent 60%),var(--drk-surface)}
.drk-cards i{display:grid;place-items:center;width:42px;height:42px;margin-bottom:16px;border-radius:12px;background:rgba(94,234,212,.12);color:var(--drk-a1);font-style:normal;font-size:1.2rem}
.drk-cards h3{font-size:1.08rem}
.drk-cards p{margin-top:6px;font-size:.94rem;color:var(--drk-muted)}

@keyframes drk-grow{to{transform:scaleX(1)}}
@keyframes drk-settle{from{transform:scale(1)}to{transform:scale(1.18)}}
@keyframes drk-light{from{color:var(--drk-dim)}to{color:var(--drk-text)}}
@keyframes drk-count{from{--drk-n:0}to{--drk-n:var(--drk-to)}}
@keyframes drk-focus{from{filter:blur(10px) opacity(0);transform:translateY(30px) scale(.96)}}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .drk-progress{animation:drk-grow linear both;animation-timeline:scroll(root block)}
    .drk-hero img{animation:drk-settle linear both;animation-timeline:view();animation-range:exit 0% exit 100%}
    .drk-read span{animation:drk-light linear both;animation-timeline:view();animation-range:cover 20% cover 45%}
    /* the counter: the real number stays in the markup for screen readers, a counter() draws the moving one */
    .drk-num{position:relative;counter-reset:drk-n var(--drk-n);animation:drk-count linear both;animation-timeline:view();animation-range:entry 20% cover 45%}
    .drk-num>span{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
    .drk-num::after{content:counter(drk-n)}
    .drk-cards li{animation:drk-focus linear both;animation-timeline:view();animation-range:entry 0% cover 30%}
  }
}
@media (prefers-reduced-motion:reduce){.drk-progress{display:none}}

Bootstrap 5 code

Requires: Bootstrap 5.3.8 CSS

HTML
<div class="drk-progress fixed-top" aria-hidden="true"></div>

<div><span class="lbl">Hero photo that drifts closer as it scrolls away</span>
<section class="drk drk-hero rounded-4" aria-labelledby="drk-title">
  <img src="https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=75" alt="The Milky Way over a snowy mountain ridge at night" width="1600" height="1067" loading="lazy">
  <p>Nocturne Optics · Lumen 4</p>
  <h2 id="drk-title">A camera that sees in the dark</h2>
  <p>Built for the sky after midnight: a cooled sensor, a mount that follows the stars and a battery that lasts the whole night.</p>
</section></div>

<div><span class="lbl">Sentences light up as you read</span>
<p class="drk drk-read"><span>Most cameras give up when the sun goes down.</span> <span>Lumen 4 starts there.</span> <span>Its sensor is cooled twenty degrees below the air around it, so long exposures stay clean.</span> <span>Point it at the sky, press once, and go back to bed.</span></p></div>

<div><span class="lbl">Counters that count up</span>
<div class="drk drk-stats row row-cols-1 row-cols-md-3 g-3" role="list" aria-label="Lumen 4 in numbers">
  <div class="col" role="listitem"><div class="card h-100"><dl class="card-body mb-0"><dt>megapixel cooled sensor</dt><dd class="mb-0"><span class="drk-num" style="--drk-to:38"><span>38</span></span></dd></dl></div></div>
  <div class="col" role="listitem"><div class="card h-100"><dl class="card-body mb-0"><dt>stops of dynamic range</dt><dd class="mb-0"><span class="drk-num" style="--drk-to:14"><span>14</span></span></dd></dl></div></div>
  <div class="col" role="listitem"><div class="card h-100"><dl class="card-body mb-0"><dt>minutes of star tracking per charge</dt><dd class="mb-0"><span class="drk-num" style="--drk-to:412"><span>412</span></span></dd></dl></div></div>
</div></div>

<div><span class="lbl">Cards that come into focus</span>
<ul class="drk drk-cards row row-cols-1 row-cols-md-3 g-3 list-unstyled" aria-label="Lumen 4 features">
  <li class="col"><div class="card h-100"><div class="card-body"><i aria-hidden="true">✦</i><h3>Star tracking</h3><p>The mount turns with the earth, so a ten-minute exposure keeps stars as points, not streaks.</p></div></div></li>
  <li class="col"><div class="card h-100"><div class="card-body"><i aria-hidden="true">◐</i><h3>Dew heater</h3><p>A ring warms the front glass by two degrees, enough to keep it clear until sunrise.</p></div></div></li>
  <li class="col"><div class="card h-100"><div class="card-body"><i aria-hidden="true">⌁</i><h3>Phone control</h3><p>Frame, focus and schedule a night of shots from bed, over Wi-Fi or Bluetooth.</p></div></div></li>
</ul></div>
CSS
/* Dark scroll animations in Bootstrap 5 with data-bs-theme="dark": .fixed-top, .rounded-4, the grid and .card
   for the parts, and the shared CSS below for the motion. CSS only. Everything is drawn in its finished state first: bright text, real numbers,
   sharp cards. Where scroll-driven animations are supported and motion is allowed, view() and scroll() timelines
   add a page progress glow, a hero photo that drifts closer as it leaves, sentences that light up as they cross the
   middle of the screen, counters that count up and cards that come into focus. */
@property --drk-n{syntax:"<integer>";inherits:false;initial-value:0}
.drk{--drk-bg:#070b14;--drk-surface:#0f1626;--drk-line:#1e2a40;--drk-text:#e8edf7;--drk-muted:#9aa7bf;--drk-dim:#7d8aa3;
  --drk-a1:#5eead4;--drk-a2:#a78bfa;color:var(--drk-text)}
.drk :where(h2,h3,p,ul,dl,dd,figure){margin:0}
/* page progress glow */
.drk-progress{height:3px;background:linear-gradient(90deg,var(--drk-a1),var(--drk-a2));
  box-shadow:0 0 14px 1px rgba(94,234,212,.7);transform-origin:0 50%;transform:scaleX(0)}
/* hero */
.drk-hero{position:relative;overflow:hidden;min-height:clamp(420px,70vh,600px);display:grid;align-content:end;padding:clamp(24px,5vw,56px);border-radius:24px;isolation:isolate}
.drk-hero img{position:absolute;inset:0;width:100%;height:100%;object-fit:cover;z-index:-2}
.drk-hero::before{content:"";position:absolute;inset:0;z-index:-1;background:linear-gradient(180deg,rgba(7,11,20,0) 20%,rgba(7,11,20,.92) 85%)}
.drk-hero p:first-of-type{font-size:.78rem;font-weight:700;letter-spacing:.16em;text-transform:uppercase;color:var(--drk-a1)}
.drk-hero h2{margin-top:10px;font-weight:800;max-width:15ch;font-size:clamp(2.2rem,1.4rem + 3.4vw,4rem);line-height:1;letter-spacing:-.035em}
.drk-hero p+h2+p{margin-top:14px;max-width:46ch;color:#c4cde0}
/* sentence highlight */
.drk-read{max-width:30ch;font-size:clamp(1.5rem,1.1rem + 1.8vw,2.4rem);line-height:1.3;font-weight:600;letter-spacing:-.015em}
.drk-read span{color:var(--drk-text)}
/* counters */
.drk-stats .card{--bs-card-bg:var(--drk-surface);--bs-card-border-color:var(--drk-line);--bs-card-border-radius:20px;--bs-card-spacer-x:24px;--bs-card-spacer-y:24px}
.drk-stats dd{font-size:clamp(2.6rem,2rem + 2vw,3.6rem);font-weight:800;line-height:1;letter-spacing:-.04em;
  background:linear-gradient(90deg,var(--drk-a1),var(--drk-a2));-webkit-background-clip:text;background-clip:text;color:var(--drk-a1);-webkit-text-fill-color:transparent}
.drk-stats dt{margin-top:10px;font-weight:400;color:var(--drk-muted)}
.drk-stats .card-body{display:flex;flex-direction:column-reverse;justify-content:flex-end}
/* focus cards */
.drk-cards .card{--bs-card-border-color:var(--drk-line);--bs-card-border-radius:20px;--bs-card-spacer-x:24px;--bs-card-spacer-y:24px;background:radial-gradient(120% 80% at 0 0,rgba(167,139,250,.14),transparent 60%),var(--drk-surface)}
.drk-cards i{display:grid;place-items:center;width:42px;height:42px;margin-bottom:16px;border-radius:12px;background:rgba(94,234,212,.12);color:var(--drk-a1);font-style:normal;font-size:1.2rem}
.drk-cards h3{font-size:1.08rem;font-weight:700}
.drk-cards p{margin-top:6px;font-size:.94rem;color:var(--drk-muted)}

@keyframes drk-grow{to{transform:scaleX(1)}}
@keyframes drk-settle{from{transform:scale(1)}to{transform:scale(1.18)}}
@keyframes drk-light{from{color:var(--drk-dim)}to{color:var(--drk-text)}}
@keyframes drk-count{from{--drk-n:0}to{--drk-n:var(--drk-to)}}
@keyframes drk-focus{from{filter:blur(10px) opacity(0);transform:translateY(30px) scale(.96)}}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    .drk-progress{animation:drk-grow linear both;animation-timeline:scroll(root block)}
    .drk-hero img{animation:drk-settle linear both;animation-timeline:view();animation-range:exit 0% exit 100%}
    .drk-read span{animation:drk-light linear both;animation-timeline:view();animation-range:cover 20% cover 45%}
    /* the counter: the real number stays in the markup for screen readers, a counter() draws the moving one */
    .drk-num{position:relative;counter-reset:drk-n var(--drk-n);animation:drk-count linear both;animation-timeline:view();animation-range:entry 20% cover 45%}
    .drk-num>span{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
    .drk-num::after{content:counter(drk-n)}
    .drk-cards .card{animation:drk-focus linear both;animation-timeline:view();animation-range:entry 0% cover 30%}
  }
}
@media (prefers-reduced-motion:reduce){.drk-progress{display:none}}

Tailwind 4 code

Requires: Tailwind CSS 4

HTML
<div class="fixed inset-x-0 top-0 z-30 h-[3px] origin-left [transform:scaleX(0)] bg-linear-to-r from-[#5eead4] to-[#a78bfa] shadow-[0_0_14px_1px_rgba(94,234,212,.7)] motion-reduce:hidden" data-drk="progress" aria-hidden="true"></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa7bf] uppercase">Hero photo that drifts closer as it scrolls away</span>
<section class="relative isolate grid min-h-[clamp(420px,70vh,600px)] content-end overflow-hidden rounded-3xl p-[clamp(24px,5vw,56px)] before:absolute before:inset-0 before:-z-10 before:bg-linear-to-b before:from-[rgba(7,11,20,0)] before:from-20% before:to-[rgba(7,11,20,.92)] before:to-85% before:content-['']" aria-labelledby="drk-title">
  <img class="absolute inset-0 -z-20 size-full object-cover" data-drk="zoom" src="https://images.unsplash.com/photo-1519681393784-d120267933ba?auto=format&fit=crop&w=1600&q=75" alt="The Milky Way over a snowy mountain ridge at night" width="1600" height="1067" loading="lazy">
  <p class="text-[.78rem] font-bold tracking-[.16em] text-[#5eead4] uppercase">Nocturne Optics · Lumen 4</p>
  <h2 class="mt-2.5 max-w-[15ch] text-[clamp(2.2rem,1.4rem+3.4vw,4rem)] leading-none font-extrabold tracking-[-.035em]" id="drk-title">A camera that sees in the dark</h2>
  <p class="mt-3.5 max-w-[46ch] text-[#c4cde0]">Built for the sky after midnight: a cooled sensor, a mount that follows the stars and a battery that lasts the whole night.</p>
</section></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa7bf] uppercase">Sentences light up as you read</span>
<p class="max-w-[30ch] text-[clamp(1.5rem,1.1rem+1.8vw,2.4rem)] leading-[1.3] font-semibold tracking-[-.015em]" data-drk="light"><span>Most cameras give up when the sun goes down.</span> <span>Lumen 4 starts there.</span> <span>Its sensor is cooled twenty degrees below the air around it, so long exposures stay clean.</span> <span>Point it at the sky, press once, and go back to bed.</span></p></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa7bf] uppercase">Counters that count up</span>
<dl class="grid grid-cols-[repeat(auto-fit,minmax(200px,1fr))] gap-4">
  <div class="flex flex-col-reverse justify-end rounded-[20px] border border-[#1e2a40] bg-[#0f1626] p-6"><dt class="mt-2.5 text-[#9aa7bf]">megapixel cooled sensor</dt><dd class="bg-linear-to-r from-[#5eead4] to-[#a78bfa] bg-clip-text text-[clamp(2.6rem,2rem+2vw,3.6rem)] leading-none font-extrabold tracking-[-.04em] text-[#5eead4] [-webkit-text-fill-color:transparent]"><span data-drk="count" style="--drk-to:38"><span>38</span></span></dd></div>
  <div class="flex flex-col-reverse justify-end rounded-[20px] border border-[#1e2a40] bg-[#0f1626] p-6"><dt class="mt-2.5 text-[#9aa7bf]">stops of dynamic range</dt><dd class="bg-linear-to-r from-[#5eead4] to-[#a78bfa] bg-clip-text text-[clamp(2.6rem,2rem+2vw,3.6rem)] leading-none font-extrabold tracking-[-.04em] text-[#5eead4] [-webkit-text-fill-color:transparent]"><span data-drk="count" style="--drk-to:14"><span>14</span></span></dd></div>
  <div class="flex flex-col-reverse justify-end rounded-[20px] border border-[#1e2a40] bg-[#0f1626] p-6"><dt class="mt-2.5 text-[#9aa7bf]">minutes of star tracking per charge</dt><dd class="bg-linear-to-r from-[#5eead4] to-[#a78bfa] bg-clip-text text-[clamp(2.6rem,2rem+2vw,3.6rem)] leading-none font-extrabold tracking-[-.04em] text-[#5eead4] [-webkit-text-fill-color:transparent]"><span data-drk="count" style="--drk-to:412"><span>412</span></span></dd></div>
</dl></div>

<div><span class="mb-4 block text-[.72rem] tracking-[.12em] text-[#9aa7bf] uppercase">Cards that come into focus</span>
<ul class="grid grid-cols-[repeat(auto-fit,minmax(230px,1fr))] gap-4" data-drk="focus" aria-label="Lumen 4 features">
  <li class="rounded-[20px] border border-[#1e2a40] [background:radial-gradient(120%_80%_at_0_0,rgba(167,139,250,.14),transparent_60%),#0f1626] p-6"><i class="mb-4 grid size-[42px] place-items-center rounded-xl bg-[rgba(94,234,212,.12)] text-[1.2rem] text-[#5eead4] not-italic" aria-hidden="true">✦</i><h3 class="text-[1.08rem] font-bold">Star tracking</h3><p class="mt-1.5 text-[.94rem] text-[#9aa7bf]">The mount turns with the earth, so a ten-minute exposure keeps stars as points, not streaks.</p></li>
  <li class="rounded-[20px] border border-[#1e2a40] [background:radial-gradient(120%_80%_at_0_0,rgba(167,139,250,.14),transparent_60%),#0f1626] p-6"><i class="mb-4 grid size-[42px] place-items-center rounded-xl bg-[rgba(94,234,212,.12)] text-[1.2rem] text-[#5eead4] not-italic" aria-hidden="true">◐</i><h3 class="text-[1.08rem] font-bold">Dew heater</h3><p class="mt-1.5 text-[.94rem] text-[#9aa7bf]">A ring warms the front glass by two degrees, enough to keep it clear until sunrise.</p></li>
  <li class="rounded-[20px] border border-[#1e2a40] [background:radial-gradient(120%_80%_at_0_0,rgba(167,139,250,.14),transparent_60%),#0f1626] p-6"><i class="mb-4 grid size-[42px] place-items-center rounded-xl bg-[rgba(94,234,212,.12)] text-[1.2rem] text-[#5eead4] not-italic" aria-hidden="true">⌁</i><h3 class="text-[1.08rem] font-bold">Phone control</h3><p class="mt-1.5 text-[.94rem] text-[#9aa7bf]">Frame, focus and schedule a night of shots from bed, over Wi-Fi or Bluetooth.</p></li>
</ul></div>
CSS
/* The look is utility classes. The motion is the shared CSS below, keyed on data-drk attributes: it only runs
   where scroll-driven animations are supported and motion is allowed, and everything starts in its finished state. */
@property --drk-n{syntax:"<integer>";inherits:false;initial-value:0}
@keyframes drk-grow{to{transform:scaleX(1)}}
@keyframes drk-settle{from{transform:scale(1)}to{transform:scale(1.18)}}
@keyframes drk-light{from{color:#7d8aa3}to{color:#e8edf7}}
@keyframes drk-count{from{--drk-n:0}to{--drk-n:var(--drk-to)}}
@keyframes drk-focus{from{filter:blur(10px) opacity(0);transform:translateY(30px) scale(.96)}}
@supports (animation-timeline:view()){
  @media (prefers-reduced-motion:no-preference){
    [data-drk="progress"]{animation:drk-grow linear both;animation-timeline:scroll(root block)}
    [data-drk="zoom"]{animation:drk-settle linear both;animation-timeline:view();animation-range:exit 0% exit 100%}
    [data-drk="light"]>span{animation:drk-light linear both;animation-timeline:view();animation-range:cover 20% cover 45%}
    [data-drk="count"]{position:relative;counter-reset:drk-n var(--drk-n);animation:drk-count linear both;animation-timeline:view();animation-range:entry 20% cover 45%}
    [data-drk="count"]>span{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%);white-space:nowrap}
    [data-drk="count"]::after{content:counter(drk-n)}
    [data-drk="focus"]>li{animation:drk-focus linear both;animation-timeline:view();animation-range:entry 0% cover 30%}
  }
}

About this set

A dark product page for a night sky camera with five scroll effects and no JavaScript. A thin glowing gradient line along the top of the window fills as the page scrolls. A full-bleed photo of the Milky Way drifts closer as the hero leaves the screen. A large paragraph lights up one sentence at a time as each crosses the middle of the window, moving from a dimmed grey to near white. Three stats count up from zero to 38, 14 and 412: a registered integer property is animated by a view() timeline and shown with counter(), while the real number stays in the markup as visually hidden text for screen readers. Three feature cards come into focus from a soft blur as they enter. Everything is drawn in its finished state first, bright text, real numbers and sharp cards, and the motion is layered on top inside @supports and prefers-reduced-motion: no-preference. The dimmed start colour of the sentences still passes 4.5:1 on the dark background, so the text is readable at every point of the effect.

What’s included

  • Glowing page progress line with a scroll() timeline
  • Sentence-by-sentence highlight with view() ranges
  • Counters animated through an @property integer and counter()
  • Hero photo zoom and blur-in cards
  • No JavaScript; Bootstrap version runs in data-bs-theme="dark"

Accessibility

Both the dimmed and the bright text colours reach at least 4.5:1 on the background, so the highlight never makes text unreadable. Animated counters keep the real number as visually hidden text, and the drawn counter is generated content. The photo has alt text, icons are aria-hidden and the stats are a description list. Under prefers-reduced-motion nothing animates and the progress line is not drawn.

Customise it

Change --drk-a1 and --drk-a2 for the accent gradient, --drk-dim for the unread sentence colour and --drk-to on each counter for its final value. In Tailwind the same values are the from- and to- classes and the --drk-to inline style.