Initial commit

This commit is contained in:
Akshay Kolli
2026-02-07 20:17:46 -05:00
parent 78111a5b61
commit 649d9c4555
86 changed files with 9530 additions and 100 deletions

View File

@@ -0,0 +1,33 @@
export function Citation({ id, index }: { id: string; index: number }) {
return (
<sup id={`cite-ref-${id}`} className="ml-0.5">
<a
href={`#cite-note-${id}`}
className="text-zinc-500 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors font-mono text-[10px] no-underline"
>
[{index}]
</a>
</sup>
);
}
export function Bibliography({ items, children }: { items?: { id: string; content: React.ReactNode }[]; children?: React.ReactNode }) {
if (!items && !children) return null;
return (
<div className="mt-12 pt-8 border-t border-zinc-200 dark:border-zinc-800">
<h3 className="text-sm font-bold uppercase tracking-wider text-zinc-900 dark:text-zinc-100 mb-4">References</h3>
<ol className="list-decimal pl-4 space-y-2 text-sm text-zinc-600 dark:text-zinc-400">
{Array.isArray(items) && items.map((item, i) => (
<li key={item.id} id={`cite-note-${item.id}`}>
{item.content}
<a href={`#cite-ref-${item.id}`} className="ml-2 hover:text-zinc-900 dark:hover:text-zinc-100">
</a>
</li>
))}
{!Array.isArray(items) && children}
</ol>
</div>
);
}

View File

@@ -0,0 +1,48 @@
'use client';
import { useState } from 'react';
type Heading = {
id: string;
text: string;
level: number;
};
export function MobileTableOfContents({ headings }: { headings: Heading[] }) {
const [isOpen, setIsOpen] = useState(false);
if (headings.length === 0) return null;
return (
<div className="block xl:hidden mb-8 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden">
<button
onClick={() => setIsOpen(!isOpen)}
className="w-full flex items-center justify-between p-4 bg-zinc-50 dark:bg-zinc-900 text-sm font-medium text-zinc-900 dark:text-zinc-100"
>
<span>Table of Contents</span>
<span className={`transform transition-transform ${isOpen ? 'rotate-180' : ''}`}>
</span>
</button>
{isOpen && (
<ul className="p-4 bg-white dark:bg-black border-t border-zinc-200 dark:border-zinc-800 space-y-3">
{headings.map((heading) => (
<li
key={heading.id}
style={{ paddingLeft: `${(heading.level - 2) * 12}px` }}
>
<a
href={`#${heading.id}`}
onClick={() => setIsOpen(false)}
className="block text-sm text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 truncate"
>
{heading.text}
</a>
</li>
))}
</ul>
)}
</div>
);
}

View File

@@ -0,0 +1,28 @@
'use client';
import { useEffect, useState } from 'react';
export function ReadingProgressBar() {
const [progress, setProgress] = useState(0);
useEffect(() => {
const updateProgress = () => {
const scrollTop = window.scrollY;
const docHeight = document.documentElement.scrollHeight - window.innerHeight;
const readPercent = scrollTop / docHeight;
setProgress(readPercent * 100);
};
window.addEventListener('scroll', updateProgress);
return () => window.removeEventListener('scroll', updateProgress);
}, []);
return (
<div className="fixed top-0 left-0 w-full h-1 z-[100] bg-transparent">
<div
className="h-full bg-zinc-900 dark:bg-zinc-100 transition-all duration-100 ease-out"
style={{ width: `${progress}%` }}
/>
</div>
);
}

View File

@@ -0,0 +1,12 @@
export function SideNote({ children, title }: { children: React.ReactNode; title?: string }) {
return (
<aside className="my-6 p-4 bg-zinc-50 dark:bg-zinc-900/50 border-l-2 border-zinc-300 dark:border-zinc-700 text-sm text-zinc-600 dark:text-zinc-400 font-light italic rounded-r-lg lg:absolute lg:right-0 lg:w-64 lg:mr-[-20rem] lg:my-0 lg:p-0 lg:bg-transparent lg:dark:bg-transparent lg:border-0 lg:not-italic lg:rounded-none">
{/* Mobile/Tablet view: distinct block */}
{/* Desktop view: Absolute positioning to the right margin */}
<span className="lg:block lg:text-xs lg:leading-relaxed">
{title && <strong className="block mb-1 text-zinc-900 dark:text-zinc-200 not-italic">{title}</strong>}
{children}
</span>
</aside>
);
}

View File

@@ -0,0 +1,79 @@
'use client';
import { useEffect, useState } from 'react';
type Heading = {
id: string;
text: string;
level: number;
};
export function TableOfContents({ headings }: { headings: Heading[] }) {
const [activeId, setActiveId] = useState<string>('');
useEffect(() => {
const handleScroll = () => {
const headingElements = headings.map((heading) => ({
id: heading.id,
element: document.getElementById(heading.id),
}));
// Find the first heading that is currently visible or just above the fold
// We look for headings that are above the 150px mark
let currentActiveId = '';
for (const { id, element } of headingElements) {
if (!element) continue;
const rect = element.getBoundingClientRect();
// If the heading is within the top portion of the screen
// OR if we haven't found a better one yet, this one 'might' be it
// We basically want the *last* heading that has a 'top' value <= some threshold
if (rect.top <= 150) {
currentActiveId = id;
}
}
// If we are at the very bottom, it's likely the last item
if ((window.innerHeight + window.scrollY) >= document.body.offsetHeight - 50) {
if (headings.length > 0) currentActiveId = headings[headings.length - 1].id;
}
if (currentActiveId) {
setActiveId(currentActiveId);
}
};
window.addEventListener('scroll', handleScroll, { passive: true });
// Trigger once on mount
handleScroll();
return () => window.removeEventListener('scroll', handleScroll);
}, [headings]);
if (headings.length === 0) return null;
return (
<nav className="text-sm animate-fade-in text-left">
<h4 className="font-bold text-zinc-900 dark:text-zinc-100 mb-4 uppercase tracking-wider text-xs">On this page</h4>
<ul className="space-y-3">
{Array.isArray(headings) && headings.map((heading) => (
<li
key={heading.id}
style={{ paddingRight: `${(heading.level - 2) * 12}px` }}
>
<a
href={`#${heading.id}`}
className={`block transition-all duration-200 border-l-2 pl-4 ${activeId === heading.id
? 'border-zinc-900 dark:border-zinc-100 text-zinc-900 dark:text-zinc-50 font-bold'
: 'border-transparent text-zinc-500 dark:text-zinc-500 hover:text-zinc-700 dark:hover:text-zinc-300'
}`}
>
{heading.text}
</a>
</li>
))}
</ul>
</nav>
);
}