Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URLs added here will be added as <link>s in order, and before the CSS in the editor. You can use the CSS from another Pen by using its URL and the proper URL extension.

+ add another resource

JavaScript

Babel includes JSX processing.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Packages

Add Packages

Search for and use JavaScript packages from npm here. By selecting a package, an import statement will be added to the top of the JavaScript editor for this package.

Behavior

Auto Save

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <article>
	<h1>Endnotes/Footnotes/Sidenotes</h1>

	<p>I like being able to add extra information, tangents, or conjecture to my writing, but I don't always wish to clutter or disrupt the main flow of text. Footnotes are great for this, but the typical implementation is less than ideal as it has users jumping all over the page every time they wish to read one.</p>
	<p>Thus, I have this implementation. Footnotes are marked up as usual, but some JavaScript-based progressive enhancement improves the experience. On horizontally challenged viewports, clicking a footnote will open it in a popover. Otherwise it'll also appear as a sidenote. This overall experience is very much influenced by <a href="https://gwern.net/sidenote">Gwern's implementation and research</a>.</p>

	<p>There is a note after this. <sup><a id="footnote-ref-1" href="#footnote-1" data-footnote-ref aria-describedby="footnote-label">1</a></sup></p>
	<p>Here is a lot of them, to see if they break&#8230; <sup><a id="footnote-ref-2" href="#footnote-2" data-footnote-ref aria-describedby="footnote-label">2</a></sup> <sup><a id="footnote-ref-3" href="#footnote-3" data-footnote-ref aria-describedby="footnote-label">3</a></sup> <sup><a id="footnote-ref-4" href="#footnote-4" data-footnote-ref aria-describedby="footnote-label">4</a></sup> <sup><a id="footnote-ref-5" href="#footnote-5" data-footnote-ref aria-describedby="footnote-label">5</a></sup> <sup><a id="footnote-ref-6" href="#footnote-6" data-footnote-ref aria-describedby="footnote-label">6</a></sup> <sup><a id="footnote-ref-7" href="#footnote-7" data-footnote-ref aria-describedby="footnote-label">7</a></sup> <sup><a id="footnote-ref-8" href="#footnote-8" data-footnote-ref aria-describedby="footnote-label">8</a></sup></p>

	<p>Lorem ipsum odor amet, consectetuer adipiscing elit. Dui fames viverra ultrices nulla augue, suscipit mollis ligula. Leo egestas interdum dapibus egestas est neque ligula dictum. Lectus dictum parturient eget proin mauris ornare scelerisque dui pretium. Mollis neque ex mus non lobortis mauris ad. Facilisi et viverra ullamcorper nullam ex. Sodales sociosqu eu rutrum erat ipsum, volutpat porttitor mattis. Senectus lectus dis diam fringilla, class dapibus mollis.</p>

	<p>Nostra maximus volutpat, etiam placerat varius eros. Rutrum tempor blandit placerat pretium netus sit nullam sollicitudin. Quam pulvinar orci finibus vel sapien sapien tempus. Orci at maecenas parturient facilisi senectus enim rhoncus. Porttitor neque nam mus dui taciti turpis platea. Lacinia nibh ornare odio vestibulum ex curabitur. Fames fusce vitae sed aenean quisque. Platea ut quam volutpat pulvinar sem. Molestie neque euismod netus arcu purus natoque?</p>

	<p>Quis augue tempus quisque ultricies; in lectus metus. Rhoncus vitae nunc habitant habitant; egestas porta scelerisque. Vivamus cursus interdum in sem turpis tincidunt laoreet varius. Tristique velit est ipsum ac in. Sagittis ornare imperdiet suspendisse porta platea tristique per. Accumsan integer sociosqu semper leo natoque dictum laoreet. Mi mus vestibulum donec etiam leo feugiat parturient.</p>

	<p>Bibendum scelerisque donec consequat maximus dapibus vitae, felis nisl luctus. Curabitur ex curae ligula mattis facilisis ut dolor pretium placerat. Efficitur leo imperdiet mauris mattis convallis, massa pulvinar duis? Aenean imperdiet hac condimentum rhoncus aliquet felis tempus curae. Justo egestas natoque at donec ante enim; eget curabitur. Diam cursus aliquet sapien convallis pretium taciti. Fusce sed potenti efficitur phasellus natoque per. Phasellus pretium semper aliquam ante enim sapien a viverra conubia.</p>

	<p>Efficitur tellus facilisis nascetur, bibendum nunc sit. Finibus adipiscing vestibulum proin dis nascetur dui enim faucibus. Ut volutpat integer, suscipit netus orci fringilla commodo feugiat. Sociosqu gravida posuere egestas praesent fames egestas. Torquent lobortis maecenas, neque id bibendum faucibus quisque. Dis phasellus tristique dignissim himenaeos accumsan tortor venenatis. Porta tellus aliquet euismod sollicitudin posuere torquent. Iaculis enim semper penatibus et facilisis auctor eget accumsan dui.</p>

	<section class="footnotes" data-footnotes>
		<h2 id="footnote-label" class="sr-only">Footnotes</h2>
		<ol>
			<li id="footnote-1">
				<p>This is a footnote, but more accurately it&#8217;s an endnote, and it&#8217;ll appear as a sidenote on larger screens. <a href="#footnote-ref-1" data-footnote-backref aria-label="Back to reference 1">↩</a></p>
			</li>
			<li id="footnote-2">
				<p>Magni dolorum ipsa voluptas tempore est ut in. Dicta corrupti ipsa aspernatur. Soluta sunt quae impedit. Eos nobis accusantium ut amet. <a href="#footnote-ref-2" data-footnote-backref aria-label="Back to reference 2">↩</a></p>
			</li>
			<li id="footnote-3">
				<p>Id ex ipsum rerum tempore. Sunt ab deserunt impedit architecto reprehenderit ex. Corporis dolores qui accusamus dolorem. Laudantium optio voluptatem eum id qui. Est ut laborum unde. <a href="#footnote-ref-3" data-footnote-backref aria-label="Back to reference 3">↩</a></p>
			</li>
			<li id="footnote-4">
				<p>Dolor expedita repudiandae voluptas. Veritatis beatae quis impedit id veniam. Nisi illum animi voluptatem magnam labore sunt omnis. <a href="#footnote-ref-4" data-footnote-backref aria-label="Back to reference 4">↩</a></p>
			</li>
			<li id="footnote-5">
				<p>Est illo fuga autem fugiat qui quaerat inventore. Suscipit in aut vero libero labore ut. Facere enim nihil itaque. Enim aut dignissimos non velit autem odio quae. Libero et corrupti eos. <a href="#footnote-ref-5" data-footnote-backref aria-label="Back to reference 5">↩</a></p>
			</li>
			<li id="footnote-6">
				<p>Similique laboriosam quas ipsam molestias quia. Earum maxime quo veniam modi. Exercitationem et odit cum. Aspernatur et ad dolor voluptatem aperiam nihil et ut. <a href="#footnote-ref-6" data-footnote-backref aria-label="Back to reference 6">↩</a></p>
			</li>
			<li id="footnote-7">
				<p>Cum eligendi quaerat blanditiis. Nostrum distinctio vel veritatis ut quod vel ea ipsa. Doloribus quae quia quibusdam veritatis provident reiciendis reiciendis quisquam. <a href="#footnote-ref-7" data-footnote-backref aria-label="Back to reference 7">↩</a></p>
			</li>
			<li id="footnote-8">
				<p>Eaque perferendis eos illo. Cum quos qui aut commodi. Et cumque quaerat molestiae. Quo laboriosam itaque odit dolorem perferendis eius. <a href="#footnote-ref-8" data-footnote-backref aria-label="Back to reference 8">↩</a></p>
			</li>
		</ol>
	</section>
</article>
              
            
!

CSS

              
                :root {
	color-scheme: light dark;

	--black: oklch(18% 0.003 17.5);
	--dark_grey: oklch(24% 0.006 17.6);
	--grey: oklch(36% 0.003 17.3);
	--bright_grey: oklch(47% 0.005 17.3);
	--white: oklch(76% 0.03 55);
	--bright_white: oklch(94.75% 0.04 73);
	--blue: oklch(56.5% 0.13 253);
	--cyan: oklch(71.2% 0.112 198.7);
}

body {
	margin: 3rem;
	font-family: "EB Garamond", serif;
	color: light-dark(var(--black), var(--bright_white));
	background: light-dark(var(--bright_white), var(--black));

	@media (max-width: 700px) {
		margin: 0 0.8rem;
	}
}

article {
	max-width: 80ch;

	* + h2 {
		margin-top: 2rem;
	}

	> * + * {
		margin-top: 1rem;
	}
}

h1,
h2 {
	text-transform: uppercase;
	text-wrap: balance;
	font-weight: 700;
}

h1 {
	font-size: 2.5rem;
	line-height: 2.8rem;
}

h2 {
	font-size: 1.8rem;
	line-height: 2rem;
}

a,
.footnote-button {
	color: inherit;
	text-decoration: underline 0.1rem light-dark(var(--cyan), var(--blue));
	-webkit-text-decoration-line: underline;
	-webkit-text-decoration-color: light-dark(var(--cyan), var(--blue));
	text-decoration-skip-ink: none;
	text-underline-offset: 0.2rem;

	&:hover,
	&:active {
		text-decoration-thickness: 0.7rem;
		text-underline-offset: -0.37rem;
	}
}

.sidenote {
	display: none;

	@media (min-width: 960px) {
		display: block;
		position: absolute;
		left: 45rem;
		right: 3rem;
		max-width: 60ch;
		font-size: small;
		line-height: 1.5;
		color: light-dark(var(--grey), var(--white));
		padding-bottom: 1rem;
		transition: background-color 0.25s;
		border-top: 1px solid light-dark(var(--white), var(--bright_grey));

		p {
			padding-top: 0.25rem;
		}

		> * + * {
			margin-top: 0.5rem;
		}
	}
}

.footnote-button.highlight,
.sidenote.highlight {
	outline: 2px solid var(--blue);
	color: inherit;
}

.sidenote:hover {
	color: inherit;
}

.footnote-popover {
	position: absolute;
	inset: auto auto auto 3rem;
	border: 2px solid light-dark(var(--white), var(--bright_grey));
	background: light-dark(var(--bright_white), var(--black));
	padding: 0.5rem 1rem;
	font-size: small;
	line-height: 1.5;

	@media (max-width: 700px) {
		inset: auto 0.8rem;
	}

	.footnote-content > * + * {
		margin-top: 0.5rem;
	}

	[data-footnote-backref] {
		display: none;
	}
}

.footnotes li {
	font-size: small;
	line-height: 1.5;

	p {
		border-left: 2px solid light-dark(var(--white), var(--bright_grey));
		padding-left: 1rem;
		margin-left: 0.5rem;
	}

	&:target p {
		border-color: var(--blue);
	}

	* + * {
		padding-top: 0.5rem;
	}
}

.footnote-button {
	background: none;
	border: transparent;
	font: inherit;
}

              
            
!

JS

              
                class FootnotesSidenotes {
    constructor() {
        this.sidenotesBreakpoint = 960;
        this.references = document.querySelectorAll("sup a[data-footnote-ref]");
        this.sidenoteContainer = this.createSidenoteContainer();
        this.articleWidth = this.getArticleWidth();
        this.init();
    }

    init() {
        if (!this.references.length) {
            console.warn("No footnote references found");
            return;
        }
        this.setupReferences();
    }

    getArticleWidth() {
        const article = document.querySelector('article');
        return article ? article.offsetWidth : null;
    }

    createSidenoteContainer() {
        const container = document.createElement("div");
        container.className = "sidenote-container";
        document.body.appendChild(container);
        return container;
    }

    toggleHighlight(element, action) {
        element.classList[action]("highlight");
    }

    positionSidenote(sidenote, reference, index) {
        if (window.innerWidth < this.sidenotesBreakpoint) {
            return;
        }
        const rect = reference.getBoundingClientRect();
        let top = rect.top + window.scrollY;
        if (index > 0) {
            const prevSidenote = this.sidenoteContainer.children[index - 1];
            const prevBottom = prevSidenote.offsetTop + prevSidenote.offsetHeight;
            if (top < prevBottom) {
                top = prevBottom + 10;
            }
        }
        sidenote.style.top = `${top}px`;
    }

    createPopover(content, index) {
        const popover = document.createElement("div");
        popover.id = `footnote-popover-${index}`;
        popover.setAttribute("popover", "auto");
        popover.className = "footnote-popover";
        
        if (this.articleWidth) {
            popover.style.maxWidth = `${this.articleWidth}px`;
        }

        const contentWrapper = document.createElement("div");
        contentWrapper.className = "footnote-content";
        contentWrapper.innerHTML = content;
        popover.appendChild(contentWrapper);
        return popover;
    }

    positionPopover(popover, button) {
        const buttonRect = button.getBoundingClientRect();
        const popoverRect = popover.getBoundingClientRect();
        const scrollTop = window.scrollY;
        popover.style.top = `${buttonRect.bottom + 10 + scrollTop}px`;
    }

    setupReference(reference, index) {
        const footnoteId = reference.getAttribute("href").substring(1);
        const footnoteContent = document.getElementById(footnoteId).innerHTML;
        const sidenote = document.createElement("div");
        sidenote.className = "sidenote";
        sidenote.innerHTML = footnoteContent;
        this.sidenoteContainer.appendChild(sidenote);

        const button = document.createElement("button");
        button.className = "footnote-button";
				button.id = `footnote-ref-${index + 1}`;
        button.innerHTML = reference.innerHTML;
        const popover = this.createPopover(footnoteContent, index);
        document.body.appendChild(popover);
        reference.parentNode.replaceChild(button, reference);

        ["mouseenter", "mouseleave"].forEach((event) => {
            const action = event === "mouseenter" ? "add" : "remove";
            button.addEventListener(event, () => this.toggleHighlight(sidenote, action));
            sidenote.addEventListener(event, () => this.toggleHighlight(button, action));
        });

        button.setAttribute("popovertarget", popover.id);
        button.setAttribute("popovertargetaction", "toggle");

        const updatePosition = () => {
            this.articleWidth = this.getArticleWidth(); // Update article width on resize
            if (this.articleWidth) {
                popover.style.maxWidth = `${this.articleWidth}px`;
            }
            this.positionSidenote(sidenote, button, index);
        };

        window.addEventListener("load", updatePosition);
        window.addEventListener("resize", updatePosition);
        popover.addEventListener("toggle", (e) => {
            if (popover.matches(":popover-open")) {
                this.positionPopover(popover, button);
            }
        });
    }

    setupReferences() {
        this.references.forEach((reference, index) =>
            this.setupReference(reference, index)
        );
    }
}

document.addEventListener("DOMContentLoaded", () => {
    new FootnotesSidenotes();
});
              
            
!
999px

Console