This commit is contained in:
parent
aa78b43c34
commit
4d9bf43f75
1 changed files with 243 additions and 35 deletions
|
|
@ -1,16 +1,45 @@
|
|||
import { mkdir, writeFile } from "node:fs/promises";
|
||||
|
||||
const blogs = [
|
||||
["research", "research"],
|
||||
["vibes", "vibes"],
|
||||
["puzzles", "puzzles"],
|
||||
["reflections", "reflections"],
|
||||
["poetry", "poetry"],
|
||||
["reviews", "reviews"],
|
||||
["art", "art"],
|
||||
{
|
||||
slug: "research",
|
||||
label: "Research",
|
||||
summary: "Notes from papers, algorithms, computational social choice, and mini-surveys of themes I am learning about.",
|
||||
},
|
||||
{
|
||||
slug: "vibes",
|
||||
label: "Vibes",
|
||||
summary: "Loose experiments, AI conversations, and work-in-progress thoughts that do not need a formal frame.",
|
||||
},
|
||||
{
|
||||
slug: "puzzles",
|
||||
label: "Puzzles",
|
||||
summary: "Problems, teaching prompts, contest-style curiosities, and small mathematical rabbit holes.",
|
||||
},
|
||||
{
|
||||
slug: "reflections",
|
||||
label: "Reflections",
|
||||
summary: "Personal notes on teaching, academia, writing, attention, and the rhythms of intellectual work.",
|
||||
},
|
||||
{
|
||||
slug: "poetry",
|
||||
label: "Poetry",
|
||||
summary: "Poems, tiny forms, and playful writing around mathematics, code, and ordinary life.",
|
||||
},
|
||||
{
|
||||
slug: "reviews",
|
||||
label: "Reviews",
|
||||
summary: "Notes on tools, workflows, books, hardware, and objects that shape everyday work.",
|
||||
},
|
||||
{
|
||||
slug: "art",
|
||||
label: "Art",
|
||||
summary: "Algorithmic sketches, generative art, visual experiments, and mathematical images.",
|
||||
},
|
||||
];
|
||||
|
||||
const rssHostSuffix = process.env.RSS_HOST_SUFFIX ?? "neeldhara.email";
|
||||
const publicHostSuffix = process.env.PUBLIC_HOST_SUFFIX ?? "neeldhara.blog";
|
||||
const outDir = new URL("../dist/", import.meta.url);
|
||||
|
||||
function decodeXml(value) {
|
||||
|
|
@ -60,20 +89,41 @@ async function getPosts(slug) {
|
|||
}
|
||||
|
||||
function renderPost(post) {
|
||||
const date = post.date ? `${escapeHtml(post.date)} ` : "";
|
||||
return `<li>${date}<a href="${escapeHtml(post.link)}">${escapeHtml(post.title)}</a></li>`;
|
||||
const date = post.date ? new Date(`${post.date}T00:00:00Z`) : null;
|
||||
const dateLabel = date
|
||||
? date.toLocaleDateString("en", { month: "short", day: "numeric", timeZone: "UTC" })
|
||||
: "";
|
||||
|
||||
return `<li class="post"><span class="date">${escapeHtml(dateLabel)}</span><a href="${escapeHtml(post.link)}">${escapeHtml(post.title)}</a></li>`;
|
||||
}
|
||||
|
||||
function renderBlog([slug, label], posts) {
|
||||
return `<li>${escapeHtml(label)}
|
||||
function renderRssIcon(blog) {
|
||||
const href = `https://${blog.slug}.${publicHostSuffix}/rss.xml`;
|
||||
|
||||
return `<a class="rss-link" href="${escapeHtml(href)}" aria-label="${escapeHtml(blog.label)} RSS">
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true">
|
||||
<circle cx="6.25" cy="17.75" r="1.8"></circle>
|
||||
<path d="M4.5 5.5c7.7 0 14 6.3 14 14"></path>
|
||||
<path d="M4.5 10.2a9.3 9.3 0 0 1 9.3 9.3"></path>
|
||||
</svg>
|
||||
</a>`;
|
||||
}
|
||||
|
||||
function renderBlog(blog, posts) {
|
||||
return `<details class="blog" open>
|
||||
<summary>
|
||||
<span>${escapeHtml(blog.label)}</span>
|
||||
${renderRssIcon(blog)}
|
||||
</summary>
|
||||
<p>${escapeHtml(blog.summary)}</p>
|
||||
<ul>
|
||||
${posts.map(renderPost).join("\n ")}
|
||||
</ul>
|
||||
</li>`;
|
||||
</details>`;
|
||||
}
|
||||
|
||||
const entries = await Promise.all(
|
||||
blogs.map(async (blog) => [blog, await getPosts(blog[0])]),
|
||||
blogs.map(async (blog) => [blog, await getPosts(blog.slug)]),
|
||||
);
|
||||
|
||||
const html = `<!doctype html>
|
||||
|
|
@ -84,42 +134,200 @@ const html = `<!doctype html>
|
|||
<title>neeldhara.blog</title>
|
||||
<style>
|
||||
html { color-scheme: light; }
|
||||
* { box-sizing: border-box; }
|
||||
body {
|
||||
margin: 0;
|
||||
background: #fff;
|
||||
background: #f0f0f0;
|
||||
color: #111;
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, "Liberation Mono", monospace;
|
||||
font-size: 16px;
|
||||
line-height: 1.55;
|
||||
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
font-size: 15px;
|
||||
line-height: 1.45;
|
||||
}
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration-color: #c6c6c6;
|
||||
text-underline-offset: 3px;
|
||||
}
|
||||
a:hover { text-decoration-color: #111; }
|
||||
main {
|
||||
width: min(640px, calc(100% - 40px));
|
||||
margin: 86px auto;
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.75fr) minmax(320px, 1fr);
|
||||
width: min(1220px, calc(100% - 72px));
|
||||
min-height: min(860px, calc(100vh - 144px));
|
||||
margin: 72px auto;
|
||||
background: #d9d9d9;
|
||||
}
|
||||
h1 {
|
||||
margin: 0 0 18px;
|
||||
font-size: 28px;
|
||||
.left,
|
||||
.right {
|
||||
min-height: inherit;
|
||||
padding: 34px 32px;
|
||||
}
|
||||
.left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 56px;
|
||||
}
|
||||
.right {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
background: #fff;
|
||||
}
|
||||
.mark {
|
||||
display: block;
|
||||
width: 13px;
|
||||
height: 13px;
|
||||
border: 2px solid #111;
|
||||
border-radius: 50%;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
p {
|
||||
margin: 0;
|
||||
}
|
||||
h1,
|
||||
h2 {
|
||||
font-size: 15px;
|
||||
font-weight: 700;
|
||||
line-height: 1.2;
|
||||
}
|
||||
ul {
|
||||
margin-top: 0;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 1.45rem;
|
||||
.role {
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
.intro {
|
||||
max-width: 460px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.blog-list {
|
||||
display: grid;
|
||||
gap: 28px;
|
||||
}
|
||||
.blog {
|
||||
max-width: 650px;
|
||||
}
|
||||
summary {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
width: 100%;
|
||||
cursor: pointer;
|
||||
font-weight: 700;
|
||||
list-style: none;
|
||||
}
|
||||
summary::-webkit-details-marker {
|
||||
display: none;
|
||||
}
|
||||
summary::before {
|
||||
content: "+";
|
||||
width: 18px;
|
||||
flex: 0 0 18px;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
}
|
||||
details[open] summary::before {
|
||||
content: "-";
|
||||
}
|
||||
summary > span {
|
||||
flex: 0 1 auto;
|
||||
}
|
||||
.rss-link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
color: #111;
|
||||
}
|
||||
.rss-link svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.9;
|
||||
stroke-linecap: round;
|
||||
}
|
||||
.rss-link circle {
|
||||
fill: currentColor;
|
||||
stroke: none;
|
||||
}
|
||||
.blog p {
|
||||
max-width: 540px;
|
||||
margin-top: 9px;
|
||||
color: #555;
|
||||
}
|
||||
ul {
|
||||
display: grid;
|
||||
gap: 7px;
|
||||
margin: 14px 0 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
}
|
||||
.post {
|
||||
display: grid;
|
||||
grid-template-columns: 64px minmax(0, 1fr);
|
||||
gap: 0;
|
||||
}
|
||||
.date {
|
||||
color: #666;
|
||||
}
|
||||
.post a {
|
||||
font-weight: 700;
|
||||
}
|
||||
.bio {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
max-width: 390px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.bio a {
|
||||
width: fit-content;
|
||||
}
|
||||
@media (max-width: 860px) {
|
||||
main {
|
||||
grid-template-columns: 1fr;
|
||||
width: min(100%, calc(100% - 28px));
|
||||
margin: 14px auto;
|
||||
}
|
||||
.left,
|
||||
.right {
|
||||
min-height: auto;
|
||||
padding: 28px 26px;
|
||||
}
|
||||
.left {
|
||||
gap: 40px;
|
||||
}
|
||||
.right {
|
||||
gap: 72px;
|
||||
}
|
||||
li { margin: 0.18rem 0; }
|
||||
a { color: #00e; }
|
||||
a:visited { color: #551a8b; }
|
||||
@media (max-width: 640px) {
|
||||
main { margin: 42px auto; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main>
|
||||
<h1>neeldhara.blog</h1>
|
||||
<ul>
|
||||
<section class="left" aria-label="Blog index">
|
||||
<span class="mark" aria-hidden="true"></span>
|
||||
<div>
|
||||
<h1>Neeldhara Misra</h1>
|
||||
<p class="role">seven small blogs</p>
|
||||
</div>
|
||||
<p class="intro">A quiet index of notes on research, puzzles, writing, tools, art, and other recurring fascinations.</p>
|
||||
<div class="blog-list">
|
||||
${entries.map(([blog, posts]) => renderBlog(blog, posts)).join("\n ")}
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<aside class="right" aria-label="About">
|
||||
<div>
|
||||
<h2>Neeldhara Misra</h2>
|
||||
<p class="role">Computer Science and Engineering, IIT Gandhinagar</p>
|
||||
</div>
|
||||
<div class="bio">
|
||||
<p>I am a computer science faculty member at IIT Gandhinagar. My academic work is broadly around algorithms, structural graph theory, parameterized complexity, computational social choice, combinatorial games, and fair allocation.</p>
|
||||
<p>These blogs separate different kinds of notes: paper-reading and mini-surveys, puzzle-like teaching prompts, reflective essays, poems, reviews of tools and workflows, experiments with AI, and visual or generative art.</p>
|
||||
<p>The homepage is deliberately small: each section shows the two newest posts and links to that blog's RSS feed.</p>
|
||||
<a href="https://www.neeldhara.com/">neeldhara.com</a>
|
||||
</div>
|
||||
</aside>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue