# On-page contents with reading rail

> A side contents list whose rail bends with each heading's indent and lights up the sections you are reading as you scroll.

- Category: [Navigation](https://library.aimran.es/components/navigation)
- Built with: HTML + CSS
- Live editor: https://library.aimran.es/blocks/uv-nav-spy-rail
- Library: AIMRAN Library (https://library.aimran.es), free, no sign-up, by Aimran Oukham

## Where to use it

A table of contents for documentation pages: the rail on its left bends inward for sub-headings and lights up the stretch that matches the sections currently on screen. As you scroll the article the highlight grows, shrinks and slides, and clicking an entry scrolls smoothly to it. The path is drawn by a short script from the real link positions, so it adapts to any list.

The main color sets the highlight and active links. Add or remove sections and levels in the data, and keep the headings short so each entry fits on one or two lines.

## Code

### HTML

```html
<div class="uvq12-sr"><div class="sc" tabindex="0" aria-label="Documentation"><section id="uvq12-sr-s0"><h4>Introduction</h4><p>This guide explains how to install the package and get it ready in five minutes. No prior experience needed.</p></section><section id="uvq12-sr-s1"><h4>Installation</h4><p>You need Node 20 or later and a package manager. Pick the one your project already uses.</p></section><section id="uvq12-sr-s2"><h5>With npm</h5><p><code>npm install lumen</code></p></section><section id="uvq12-sr-s3"><h5>With pnpm</h5><p><code>pnpm add lumen</code></p></section><section id="uvq12-sr-s4"><h4>Usage</h4><p>Import the component and pass it your data. It works the same on the server and in the browser.</p></section><section id="uvq12-sr-s5"><h5>Options</h5><p>Every option has a sensible default. Change only the ones you need.</p></section><section id="uvq12-sr-s6"><h4>Questions</h4><p>If something breaks, check your Node version first. Then clear the cache and try again.</p></section></div><nav class="tc" aria-label="On this page"><p>On this page</p><div class="rl"><svg aria-hidden="true"><path class="b"/><path class="h"/></svg><ul><li class="l0"><a href="#uvq12-sr-s0">Introduction</a></li><li class="l0"><a href="#uvq12-sr-s1">Installation</a></li><li class="l1"><a href="#uvq12-sr-s2">With npm</a></li><li class="l1"><a href="#uvq12-sr-s3">With pnpm</a></li><li class="l0"><a href="#uvq12-sr-s4">Usage</a></li><li class="l1"><a href="#uvq12-sr-s5">Options</a></li><li class="l0"><a href="#uvq12-sr-s6">Questions</a></li></ul></div></nav></div>
```

### CSS

```css
.uvq12-sr {
  --c1: #7c3aed;
  --c2: #ede9fe;
  --s: 16px;
  /* tamaño */
  --k: 1;
  /* velocidad */
  --fg: #1d1d1f;
  /* texto */
  --bg: #f5f5f7;
  /* fondo */
}

.uvq12-sr {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  display: flex;
  gap: 1em;
  width: 23em;
  height: 13em;
}

.uvq12-sr .sc {
  position: relative;
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: .2em 1em 4em 0;
  scrollbar-width: thin;
  outline: 0;
  border-right: 1px solid color-mix(in srgb,var(--fg) 12%,transparent);
}

.uvq12-sr .sc:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 2px;
  border-radius: .3em;
}

.uvq12-sr h4,.uvq12-sr h5 {
  margin: 0 0 .3em;
  font-size: 1em;
  font-weight: 750;
  letter-spacing: -.01em;
}

.uvq12-sr h5 {
  font-size: .88em;
  font-weight: 700;
}

.uvq12-sr section {
  padding-bottom: .9em;
}

.uvq12-sr section p {
  margin: 0;
  font-size: .8em;
  line-height: 1.55;
  color: color-mix(in srgb,var(--fg) 75%,transparent);
}

.uvq12-sr code {
  display: block;
  padding: .45em .6em;
  border-radius: .4em;
  font-family: ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  font-size: .95em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq12-sr .tc {
  flex: none;
  width: 7.6em;
}

.uvq12-sr .tc>p {
  margin: .1em 0 .6em;
  font-size: .62em;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq12-sr .rl {
  position: relative;
}

.uvq12-sr svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 14px;
  height: 100%;
  overflow: visible;
}

.uvq12-sr path {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.uvq12-sr .b {
  stroke: color-mix(in srgb,var(--fg) 12%,transparent);
}

.uvq12-sr .h {
  stroke: var(--c1);
  transition: stroke-dasharray calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1),stroke-dashoffset calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1);
}

.uvq12-sr ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.uvq12-sr li a {
  display: block;
  padding: .28em 0 .28em 1.1em;
  font-size: .76em;
  line-height: 1.25;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  text-decoration: none;
  border-radius: .3em;
  transition: color calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),transform calc(0.2s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq12-sr li.l1 a {
  padding-left: 1.9em;
}

.uvq12-sr li a:hover {
  color: var(--fg);
}

.uvq12-sr li a:active {
  transform: translateX(.15em);
}

.uvq12-sr li a.on {
  color: var(--c1);
  font-weight: 650;
}

.uvq12-sr li a:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 1px;
}

@media (prefers-reduced-motion:reduce) {
  .uvq12-sr,.uvq12-sr *,.uvq12-sr *::before,.uvq12-sr *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
```

### JavaScript

```js
document.querySelectorAll(".uvq12-sr").forEach(el => {
  const sc=el.querySelector(".sc"),secs=[...sc.querySelectorAll("section")],ls=[...el.querySelectorAll(".tc a")],ul=el.querySelector(".tc ul"),pb=el.querySelector("path.b"),ph=el.querySelector("path.h");let seg=[],tot=0;
const build=()=>{let d="",len=0,px=null,py=null;seg=ls.map(a=>{const li=a.parentElement,x=li.classList.contains("l1")?10:2,y0=li.offsetTop+5,y1=li.offsetTop+li.offsetHeight-5;if(px===null)d+="M"+x+" "+y0;else{d+="L"+x+" "+y0;len+=Math.hypot(x-px,y0-py)}const s=len;d+="L"+x+" "+y1;len+=y1-y0;px=x;py=y1;return[s,len]});tot=len;pb.setAttribute("d",d);ph.setAttribute("d",d);spy()};
const spy=()=>{const h=sc.clientHeight,t=sc.scrollTop;let on=secs.map(s=>s.offsetTop-t<h-12&&s.offsetTop+s.offsetHeight-t>12);if(!on.includes(true))on=secs.map((_,i)=>i===0);const a=on.indexOf(true),b=on.lastIndexOf(true);ls.forEach((l,i)=>l.classList.toggle("on",on[i]));if(!seg.length)return;const s=seg[a][0],e=seg[b][1];ph.style.strokeDasharray=(e-s)+" "+(tot+10);ph.style.strokeDashoffset=String(-s)};
sc.addEventListener("scroll",spy,{passive:true});
ls.forEach((a,i)=>a.addEventListener("click",e=>{e.preventDefault();sc.scrollTo({top:secs[i].offsetTop-4,behavior:matchMedia("(prefers-reduced-motion: reduce)").matches?"auto":"smooth"})}));
const ro=new ResizeObserver(build);ro.observe(ul);build();
const tm=setTimeout(()=>{if(sc.scrollTop===0)sc.scrollTo({top:sc.scrollHeight*.3,behavior:"smooth"})},1000);return()=>{ro.disconnect();clearTimeout(tm)}
});
```

### React

```tsx
// @ts-nocheck: setup() es JavaScript del DOM sin tipos, el mismo de la pestaña JS.
import { useEffect, useRef, type CSSProperties } from "react";

const css = `
.uvq12-sr {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  display: flex;
  gap: 1em;
  width: 23em;
  height: 13em;
}

.uvq12-sr .sc {
  position: relative;
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: .2em 1em 4em 0;
  scrollbar-width: thin;
  outline: 0;
  border-right: 1px solid color-mix(in srgb,var(--fg) 12%,transparent);
}

.uvq12-sr .sc:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 2px;
  border-radius: .3em;
}

.uvq12-sr h4,.uvq12-sr h5 {
  margin: 0 0 .3em;
  font-size: 1em;
  font-weight: 750;
  letter-spacing: -.01em;
}

.uvq12-sr h5 {
  font-size: .88em;
  font-weight: 700;
}

.uvq12-sr section {
  padding-bottom: .9em;
}

.uvq12-sr section p {
  margin: 0;
  font-size: .8em;
  line-height: 1.55;
  color: color-mix(in srgb,var(--fg) 75%,transparent);
}

.uvq12-sr code {
  display: block;
  padding: .45em .6em;
  border-radius: .4em;
  font-family: ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  font-size: .95em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq12-sr .tc {
  flex: none;
  width: 7.6em;
}

.uvq12-sr .tc>p {
  margin: .1em 0 .6em;
  font-size: .62em;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq12-sr .rl {
  position: relative;
}

.uvq12-sr svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 14px;
  height: 100%;
  overflow: visible;
}

.uvq12-sr path {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.uvq12-sr .b {
  stroke: color-mix(in srgb,var(--fg) 12%,transparent);
}

.uvq12-sr .h {
  stroke: var(--c1);
  transition: stroke-dasharray calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1),stroke-dashoffset calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1);
}

.uvq12-sr ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.uvq12-sr li a {
  display: block;
  padding: .28em 0 .28em 1.1em;
  font-size: .76em;
  line-height: 1.25;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  text-decoration: none;
  border-radius: .3em;
  transition: color calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),transform calc(0.2s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq12-sr li.l1 a {
  padding-left: 1.9em;
}

.uvq12-sr li a:hover {
  color: var(--fg);
}

.uvq12-sr li a:active {
  transform: translateX(.15em);
}

.uvq12-sr li a.on {
  color: var(--c1);
  font-weight: 650;
}

.uvq12-sr li a:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 1px;
}

@media (prefers-reduced-motion:reduce) {
  .uvq12-sr,.uvq12-sr *,.uvq12-sr *::before,.uvq12-sr *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
`;

// Interacción en JavaScript del DOM: la misma que en la pestaña JS.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setup(el: any) {
  const sc=el.querySelector(".sc"),secs=[...sc.querySelectorAll("section")],ls=[...el.querySelectorAll(".tc a")],ul=el.querySelector(".tc ul"),pb=el.querySelector("path.b"),ph=el.querySelector("path.h");let seg=[],tot=0;
const build=()=>{let d="",len=0,px=null,py=null;seg=ls.map(a=>{const li=a.parentElement,x=li.classList.contains("l1")?10:2,y0=li.offsetTop+5,y1=li.offsetTop+li.offsetHeight-5;if(px===null)d+="M"+x+" "+y0;else{d+="L"+x+" "+y0;len+=Math.hypot(x-px,y0-py)}const s=len;d+="L"+x+" "+y1;len+=y1-y0;px=x;py=y1;return[s,len]});tot=len;pb.setAttribute("d",d);ph.setAttribute("d",d);spy()};
const spy=()=>{const h=sc.clientHeight,t=sc.scrollTop;let on=secs.map(s=>s.offsetTop-t<h-12&&s.offsetTop+s.offsetHeight-t>12);if(!on.includes(true))on=secs.map((_,i)=>i===0);const a=on.indexOf(true),b=on.lastIndexOf(true);ls.forEach((l,i)=>l.classList.toggle("on",on[i]));if(!seg.length)return;const s=seg[a][0],e=seg[b][1];ph.style.strokeDasharray=(e-s)+" "+(tot+10);ph.style.strokeDashoffset=String(-s)};
sc.addEventListener("scroll",spy,{passive:true});
ls.forEach((a,i)=>a.addEventListener("click",e=>{e.preventDefault();sc.scrollTo({top:secs[i].offsetTop-4,behavior:matchMedia("(prefers-reduced-motion: reduce)").matches?"auto":"smooth"})}));
const ro=new ResizeObserver(build);ro.observe(ul);build();
const tm=setTimeout(()=>{if(sc.scrollTop===0)sc.scrollTo({top:sc.scrollHeight*.3,behavior:"smooth"})},1000);return()=>{ro.disconnect();clearTimeout(tm)}
}

export default function NavSpyRail() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current?.firstElementChild;
    return el ? setup(el) : undefined;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div
        ref={ref} style={{
          "--c1": "#7c3aed",
          "--c2": "#ede9fe",
          "--s": "16px",
          "--k": "1",
          "--fg": "#1d1d1f",
          "--bg": "#f5f5f7",
        } as CSSProperties}
      >
        <div className="uvq12-sr"><div className="sc" tabIndex={0} aria-label="Documentation"><section id="uvq12-sr-s0"><h4>Introduction</h4><p>This guide explains how to install the package and get it ready in five minutes. No prior experience needed.</p></section><section id="uvq12-sr-s1"><h4>Installation</h4><p>You need Node 20 or later and a package manager. Pick the one your project already uses.</p></section><section id="uvq12-sr-s2"><h5>With npm</h5><p><code>npm install lumen</code></p></section><section id="uvq12-sr-s3"><h5>With pnpm</h5><p><code>pnpm add lumen</code></p></section><section id="uvq12-sr-s4"><h4>Usage</h4><p>Import the component and pass it your data. It works the same on the server and in the browser.</p></section><section id="uvq12-sr-s5"><h5>Options</h5><p>Every option has a sensible default. Change only the ones you need.</p></section><section id="uvq12-sr-s6"><h4>Questions</h4><p>If something breaks, check your Node version first. Then clear the cache and try again.</p></section></div><nav className="tc" aria-label="On this page"><p>On this page</p><div className="rl"><svg aria-hidden="true"><path className="b" /><path className="h" /></svg><ul><li className="l0"><a href="#uvq12-sr-s0">Introduction</a></li><li className="l0"><a href="#uvq12-sr-s1">Installation</a></li><li className="l1"><a href="#uvq12-sr-s2">With npm</a></li><li className="l1"><a href="#uvq12-sr-s3">With pnpm</a></li><li className="l0"><a href="#uvq12-sr-s4">Usage</a></li><li className="l1"><a href="#uvq12-sr-s5">Options</a></li><li className="l0"><a href="#uvq12-sr-s6">Questions</a></li></ul></div></nav></div>
      </div>
    </>
  );
}
```

## Prompt for AI assistants

Paste this into ChatGPT, Claude, Cursor, v0 or Lovable to adapt the block to your stack (Tailwind, Vue, Svelte…):

````text
I want to add the “On-page contents with reading rail” block (Navigation) from AIMRAN Library to my project.

## What it is
A side contents list whose rail bends with each heading's indent and lights up the sections you are reading as you scroll.

## Chosen settings
- Main color: #7c3aed
- Secondary color: #ede9fe
- Size: 16px (from 12 to 24px)
- Speed: 1x (from 0.5 to 2x)
- Text: "En esta página"

## What I need
1. Add it to my project with the same look and animation as the reference code: same sizes, colours, radii, shadows, timings and easing.
2. It is built with plain HTML and CSS (the reference wraps it in a React component with its CSS). If my project uses something else (Vue, Svelte, Astro, plain HTML…), translate it without losing any behaviour. If I use Tailwind, turn the styles into classes; if not, keep the CSS in its own file.
3. Expose the settings above as props (or CSS variables) with those values as defaults.
4. Make it work well on phones: nothing that depends only on hover, touch targets of at least 44 px and no overflow on 320 px screens.
5. Make it look right in light and dark mode.
6. Keep it accessible: semantic HTML, roles and aria-* where needed, visible focus and keyboard support.
7. Respect prefers-reduced-motion: with reduced motion, no big animations but the same end result.

## Reference code
```tsx
// @ts-nocheck: setup() es JavaScript del DOM sin tipos, el mismo de la pestaña JS.
import { useEffect, useRef, type CSSProperties } from "react";

const css = `
.uvq12-sr {
  font-family: -apple-system,BlinkMacSystemFont,"SF Pro Text","Inter",system-ui,sans-serif;
  font-size: var(--s);
  color: var(--fg);
  -webkit-tap-highlight-color: transparent;
  display: flex;
  gap: 1em;
  width: 23em;
  height: 13em;
}

.uvq12-sr .sc {
  position: relative;
  flex: 1;
  min-width: 0;
  overflow-y: auto;
  padding: .2em 1em 4em 0;
  scrollbar-width: thin;
  outline: 0;
  border-right: 1px solid color-mix(in srgb,var(--fg) 12%,transparent);
}

.uvq12-sr .sc:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 2px;
  border-radius: .3em;
}

.uvq12-sr h4,.uvq12-sr h5 {
  margin: 0 0 .3em;
  font-size: 1em;
  font-weight: 750;
  letter-spacing: -.01em;
}

.uvq12-sr h5 {
  font-size: .88em;
  font-weight: 700;
}

.uvq12-sr section {
  padding-bottom: .9em;
}

.uvq12-sr section p {
  margin: 0;
  font-size: .8em;
  line-height: 1.55;
  color: color-mix(in srgb,var(--fg) 75%,transparent);
}

.uvq12-sr code {
  display: block;
  padding: .45em .6em;
  border-radius: .4em;
  font-family: ui-monospace,"SF Mono",Menlo,Consolas,monospace;
  font-size: .95em;
  background: color-mix(in srgb,var(--fg) 6%,transparent);
}

.uvq12-sr .tc {
  flex: none;
  width: 7.6em;
}

.uvq12-sr .tc>p {
  margin: .1em 0 .6em;
  font-size: .62em;
  font-weight: 800;
  letter-spacing: .14em;
  text-transform: uppercase;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
}

.uvq12-sr .rl {
  position: relative;
}

.uvq12-sr svg {
  position: absolute;
  left: 0;
  top: 0;
  width: 14px;
  height: 100%;
  overflow: visible;
}

.uvq12-sr path {
  fill: none;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.uvq12-sr .b {
  stroke: color-mix(in srgb,var(--fg) 12%,transparent);
}

.uvq12-sr .h {
  stroke: var(--c1);
  transition: stroke-dasharray calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1),stroke-dashoffset calc(0.45s / var(--k)) cubic-bezier(.22,1,.36,1);
}

.uvq12-sr ul {
  margin: 0;
  padding: 0;
  list-style: none;
}

.uvq12-sr li a {
  display: block;
  padding: .28em 0 .28em 1.1em;
  font-size: .76em;
  line-height: 1.25;
  color: color-mix(in srgb,var(--fg) 55%,transparent);
  text-decoration: none;
  border-radius: .3em;
  transition: color calc(0.2s / var(--k)) cubic-bezier(.25,.1,.25,1),transform calc(0.2s / var(--k)) cubic-bezier(.34,1.56,.64,1);
}

.uvq12-sr li.l1 a {
  padding-left: 1.9em;
}

.uvq12-sr li a:hover {
  color: var(--fg);
}

.uvq12-sr li a:active {
  transform: translateX(.15em);
}

.uvq12-sr li a.on {
  color: var(--c1);
  font-weight: 650;
}

.uvq12-sr li a:focus-visible {
  outline: 2px solid var(--c1);
  outline-offset: 1px;
}

@media (prefers-reduced-motion:reduce) {
  .uvq12-sr,.uvq12-sr *,.uvq12-sr *::before,.uvq12-sr *::after {
    animation-duration: .01ms!important;
    animation-iteration-count: 1!important;
    transition-duration: .01ms!important;
  }
}
`;

// Interacción en JavaScript del DOM: la misma que en la pestaña JS.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function setup(el: any) {
  const sc=el.querySelector(".sc"),secs=[...sc.querySelectorAll("section")],ls=[...el.querySelectorAll(".tc a")],ul=el.querySelector(".tc ul"),pb=el.querySelector("path.b"),ph=el.querySelector("path.h");let seg=[],tot=0;
const build=()=>{let d="",len=0,px=null,py=null;seg=ls.map(a=>{const li=a.parentElement,x=li.classList.contains("l1")?10:2,y0=li.offsetTop+5,y1=li.offsetTop+li.offsetHeight-5;if(px===null)d+="M"+x+" "+y0;else{d+="L"+x+" "+y0;len+=Math.hypot(x-px,y0-py)}const s=len;d+="L"+x+" "+y1;len+=y1-y0;px=x;py=y1;return[s,len]});tot=len;pb.setAttribute("d",d);ph.setAttribute("d",d);spy()};
const spy=()=>{const h=sc.clientHeight,t=sc.scrollTop;let on=secs.map(s=>s.offsetTop-t<h-12&&s.offsetTop+s.offsetHeight-t>12);if(!on.includes(true))on=secs.map((_,i)=>i===0);const a=on.indexOf(true),b=on.lastIndexOf(true);ls.forEach((l,i)=>l.classList.toggle("on",on[i]));if(!seg.length)return;const s=seg[a][0],e=seg[b][1];ph.style.strokeDasharray=(e-s)+" "+(tot+10);ph.style.strokeDashoffset=String(-s)};
sc.addEventListener("scroll",spy,{passive:true});
ls.forEach((a,i)=>a.addEventListener("click",e=>{e.preventDefault();sc.scrollTo({top:secs[i].offsetTop-4,behavior:matchMedia("(prefers-reduced-motion: reduce)").matches?"auto":"smooth"})}));
const ro=new ResizeObserver(build);ro.observe(ul);build();
const tm=setTimeout(()=>{if(sc.scrollTop===0)sc.scrollTo({top:sc.scrollHeight*.3,behavior:"smooth"})},1000);return()=>{ro.disconnect();clearTimeout(tm)}
}

export default function NavSpyRail() {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current?.firstElementChild;
    return el ? setup(el) : undefined;
  }, []);

  return (
    <>
      <style>{css}</style>
      <div
        ref={ref} style={{
          "--c1": "#7c3aed",
          "--c2": "#ede9fe",
          "--s": "16px",
          "--k": "1",
          "--fg": "#1d1d1f",
          "--bg": "#f5f5f7",
        } as CSSProperties}
      >
        <div className="uvq12-sr"><div className="sc" tabIndex={0} aria-label="Documentation"><section id="uvq12-sr-s0"><h4>Introduction</h4><p>This guide explains how to install the package and get it ready in five minutes. No prior experience needed.</p></section><section id="uvq12-sr-s1"><h4>Installation</h4><p>You need Node 20 or later and a package manager. Pick the one your project already uses.</p></section><section id="uvq12-sr-s2"><h5>With npm</h5><p><code>npm install lumen</code></p></section><section id="uvq12-sr-s3"><h5>With pnpm</h5><p><code>pnpm add lumen</code></p></section><section id="uvq12-sr-s4"><h4>Usage</h4><p>Import the component and pass it your data. It works the same on the server and in the browser.</p></section><section id="uvq12-sr-s5"><h5>Options</h5><p>Every option has a sensible default. Change only the ones you need.</p></section><section id="uvq12-sr-s6"><h4>Questions</h4><p>If something breaks, check your Node version first. Then clear the cache and try again.</p></section></div><nav className="tc" aria-label="On this page"><p>On this page</p><div className="rl"><svg aria-hidden="true"><path className="b" /><path className="h" /></svg><ul><li className="l0"><a href="#uvq12-sr-s0">Introduction</a></li><li className="l0"><a href="#uvq12-sr-s1">Installation</a></li><li className="l1"><a href="#uvq12-sr-s2">With npm</a></li><li className="l1"><a href="#uvq12-sr-s3">With pnpm</a></li><li className="l0"><a href="#uvq12-sr-s4">Usage</a></li><li className="l1"><a href="#uvq12-sr-s5">Options</a></li><li className="l0"><a href="#uvq12-sr-s6">Questions</a></li></ul></div></nav></div>
      </div>
    </>
  );
}
```

Give me the complete final code, ready to paste, and tell me in one line where to put it in my project.
````
