Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions _config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ liquid:
error_mode: strict

# Build settings
exclude:
- docs
- Gemfile
- Gemfile.lock
- vendor
- README.md

permalink: /:title/
markdown: kramdown
feed:
Expand Down
124 changes: 86 additions & 38 deletions _layouts/default.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#f4f1e9">

<title>{% if page.title %}{{ page.title }} | {{ site.title }}{% else %}{{ site.title }}{% endif %}</title>

Expand All @@ -16,46 +17,93 @@
{% feed_meta %}
</head>
<body>
<div class="site-shell">

<aside class="sidebar">
<a class="sidebar-brand" href="{{ '/' | relative_url }}">
<div class="sidebar-eyebrow">Personal site</div>
<div class="sidebar-title">Nitin Kumar<br>Singh</div>
</a>

<nav class="sidebar-nav">
<a class="sidebar-nav-item {% if page.url == '/' %}active{% endif %}" href="{{ '/' | relative_url }}"><span>01</span>Home</a>
<a class="sidebar-nav-item {% if page.url contains '/blogs' %}active{% endif %}" href="{{ '/blogs/' | relative_url }}"><span>02</span>Blogs</a>
<a class="sidebar-nav-item {% if page.url contains '/papershelf' %}active{% endif %}" href="{{ '/papershelf/' | relative_url }}"><span>03</span>Papershelf</a>
<a class="sidebar-nav-item {% if page.url contains '/bookshelf' %}active{% endif %}" href="{{ '/bookshelf/' | relative_url }}"><span>04</span>Bookshelf</a>
</nav>

<div class="sidebar-elsewhere">
<div class="sidebar-elsewhere-label">Elsewhere</div>
<div class="sidebar-elsewhere-links">
<a href="https://twitter.com/nitinkumarsin10" target="_blank" rel="noopener">Twitter</a>
<a href="https://github.com/nitinitleen1" target="_blank" rel="noopener">GitHub</a>
<a href="https://www.linkedin.com/in/nitinnks/" target="_blank" rel="noopener">LinkedIn</a>
<a href="{{ '/atom.xml' | relative_url }}" target="_blank" rel="noopener">RSS</a>
{% assign is_home = false %}
{% if page.url == '/' %}{% assign is_home = true %}{% endif %}

<header class="topbar">
<a class="topbar-brand" href="{{ '/' | relative_url }}">
<span class="mark">Personal site</span>
Nitin Kumar Singh
</a>

<nav class="topbar-nav" aria-label="Primary">
{% if is_home %}
<a class="nav-link" href="#home" data-section="home">Home</a>
<a class="nav-link" href="#writing" data-section="writing">Writing</a>
<a class="nav-link" href="#shelf" data-section="shelf">Shelf</a>
{% else %}
<a class="nav-link" href="{{ '/' | relative_url }}">Home</a>
<a class="nav-link" href="{{ '/#writing' | relative_url }}">Writing</a>
<a class="nav-link" href="{{ '/#shelf' | relative_url }}">Shelf</a>
{% endif %}

<span class="nav-sep" aria-hidden="true"></span>

<a class="nav-link" href="{{ '/blogs/' | relative_url }}"{% if page.url contains '/blogs' %} aria-current="page"{% endif %}>Blogs</a>
<button type="button" class="nav-link" data-about-open>About</button>
</nav>
</header>

<main class="site-main" id="top">
{{ content }}
</main>

<footer class="site-footer">
<div class="wrap">
<div class="colophon">&copy; {{ site.time | date: "%Y" }} {{ site.title }}</div>
<div class="footer-links">
<a href="{{ '/bookshelf/' | relative_url }}">Bookshelf</a>
<a href="{{ '/papershelf/' | relative_url }}">Papershelf</a>
<a href="https://github.com/nitinitleen1" target="_blank" rel="noopener">GitHub</a>
<a href="https://twitter.com/{{ site.twitter.username }}" target="_blank" rel="noopener">Twitter</a>
<a href="https://www.linkedin.com/in/nitinnks/" target="_blank" rel="noopener">LinkedIn</a>
<a href="{{ '/atom.xml' | relative_url }}">RSS</a>
</div>
</div>
</footer>

<div class="about-overlay" id="about-modal" role="dialog" aria-modal="true" aria-labelledby="about-title" hidden>
<div class="about-panel">
<button type="button" class="about-close" data-about-close aria-label="Close about">&times;</button>

<div class="about-head">
<img class="about-avatar" src="{{ site.author.picture | relative_url }}" alt="" width="62" height="62">
<div>
<div class="about-eyebrow">About</div>
<h2 class="about-name" id="about-title">{{ site.title }}</h2>
</div>
</div>
</aside>

<main class="site-main">
{{ content }}
</main>

{% if site.google_analytics %}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{{ site.google_analytics }}', 'auto');
ga('send', 'pageview');
</script>
{% endif %}

<div class="about-body">
<p>{{ site.description }}. I spend most of my time on distributed systems and
data infrastructure &mdash; pipelines that have to stay up, queries that have to
stay fast, and the failure modes nobody warns you about.</p>
<p>This site is where I write that down: notes from things I have built, papers
worth re-reading, and books that shaped how I think. Nothing here is a tutorial
so much as a record of working it out.</p>
</div>

<div class="about-links">
<a href="https://github.com/nitinitleen1" target="_blank" rel="noopener">GitHub</a>
<a href="https://twitter.com/{{ site.twitter.username }}" target="_blank" rel="noopener">Twitter</a>
<a href="https://www.linkedin.com/in/nitinnks/" target="_blank" rel="noopener">LinkedIn</a>
<a href="{{ '/atom.xml' | relative_url }}">RSS</a>
</div>
</div>
</div>

<script src="{{ '/static/js/site.js' | relative_url }}" defer></script>
{% if is_home %}<script src="{{ '/static/js/chimes.js' | relative_url }}" defer></script>{% endif %}

{% if site.google_analytics %}
<script>
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
})(window,document,'script','//www.google-analytics.com/analytics.js','ga');
ga('create', '{{ site.google_analytics }}', 'auto');
ga('send', 'pageview');
</script>
{% endif %}
</body>
</html>
108 changes: 91 additions & 17 deletions _layouts/home.html
Original file line number Diff line number Diff line change
@@ -1,27 +1,101 @@
---
layout: default
---
<section class="page-section">
<div class="eyebrow">Learning &amp; adventures in CS and life</div>
<h1 class="page-title">Hey, I'm Nitin.</h1>
<p class="page-lede">{{ site.description }} — distributed systems, algorithms, the occasional detour into whatever has my attention this month. <span style="font-style: italic;">This is where I think out loud.</span></p>
<section class="hero" id="home">
<canvas id="chimes-canvas" aria-hidden="true"></canvas>

<div class="divider"></div>
<button type="button" class="chimes-audio" id="chimes-audio" aria-pressed="false" aria-label="Unmute chimes">
<svg class="icon-off" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M11 5 6 9H3v6h3l5 4z"></path>
<path d="m17 9 4 6M21 9l-4 6"></path>
</svg>
<svg class="icon-on" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<path d="M11 5 6 9H3v6h3l5 4z"></path>
<path d="M15.5 8.5a5 5 0 0 1 0 7"></path>
<path d="M18.5 5.5a9 9 0 0 1 0 13"></path>
</svg>
</button>

<div class="section-head">
<h2>Recent writing</h2>
<a href="{{ '/blogs/' | relative_url }}">All blogs →</a>
<div class="hero-inner">
<div class="hero-eyebrow">Learning &amp; adventures in CS and life</div>
<h1 class="hero-title">Hey, I'm<br>Nitin <em>Singh</em>.</h1>
<p class="hero-lede">{{ site.description }} &mdash; distributed systems, algorithms, and
the occasional detour into whatever has my attention this month. This is where I think
out loud.</p>
<div class="hero-actions">
<a class="button is-primary" href="#writing">Read the writing</a>
<button type="button" class="button" data-about-open>About me</button>
</div>
<div class="hero-hint" aria-hidden="true">Sweep the strands &middot; tap the speaker for sound</div>
</div>
</section>

<section class="band" id="writing">
<div class="wrap">
<div class="band-head" data-reveal>
<h2>Recent writing</h2>
<a class="band-more" href="{{ '/blogs/' | relative_url }}">All {{ site.posts | size }} posts &rarr;</a>
</div>

<div class="post-list" data-reveal data-reveal-stagger>
{% for post in site.posts limit: 4 %}
<a class="post-row" href="{{ post.url | relative_url }}">
<div class="post-row-date">{{ post.date | date: "%b %d, %Y" }}</div>
<div>
<div class="post-row-title">{{ post.title }}</div>
<div class="post-row-blurb">{{ post.description }}</div>
</div>
</a>
{% endfor %}
</div>
</div>
</section>

<section class="band is-sunk" id="shelf">
<div class="wrap">
<div class="band-head" data-reveal>
<h2>On the shelf</h2>
<a class="band-more" href="{{ '/bookshelf/' | relative_url }}">Everything I'm reading &rarr;</a>
</div>

<div class="shelf-grid" data-reveal data-reveal-stagger>
<div class="shelf-card">
<div class="shelf-card-head">
<h3>Books</h3>
<a href="{{ '/bookshelf/' | relative_url }}">All &rarr;</a>
</div>
<p class="shelf-card-note">A running log of what I'm reading &mdash; technical and otherwise.</p>
<div class="shelf-list">
{% for book in site.data.books limit: 4 %}
<div class="shelf-row">
<div>
<div class="shelf-row-title">{{ book.title }}</div>
<div class="shelf-row-sub">{{ book.author }}</div>
</div>
<div class="shelf-row-status" style="color: {{ book.status_color }};">{{ book.status }}</div>
</div>
{% endfor %}
</div>
</div>

<div class="post-list">
{% for post in site.posts limit: 4 %}
<a class="post-row" href="{{ post.url | relative_url }}">
<div class="post-row-date">{{ post.date | date: "%b %d, %Y" }}</div>
<div>
<div class="post-row-title">{{ post.title }}</div>
<div class="post-row-blurb">{{ post.description }}</div>
<div class="shelf-card">
<div class="shelf-card-head">
<h3>Papers</h3>
<a href="{{ '/papershelf/' | relative_url }}">All &rarr;</a>
</div>
<p class="shelf-card-note">Research that shaped how I think about systems and computation.</p>
<div class="shelf-list">
{% for paper in site.data.papers limit: 4 %}
<div class="shelf-row">
<div>
<div class="shelf-row-title">{{ paper.title }}</div>
<div class="shelf-row-sub">{{ paper.authors }}</div>
</div>
<div class="shelf-row-meta">{{ paper.year }}</div>
</div>
{% endfor %}
</div>
</div>
</a>
{% endfor %}
</div>
</div>
</section>
120 changes: 120 additions & 0 deletions docs/superpowers/specs/2026-08-12-chimes-redesign-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
# Chimes-inspired redesign — design

**Date:** 2026-08-12
**Repo:** nitinitleen1.github.io
**Reference:** https://marinabudarina.github.io/chimes/

## Goal

Rebuild the visual language of the personal site around a tactile, interactive hero —
verlet-simulated hanging strands the visitor can part with cursor or touch — while keeping
the existing Jekyll content model, warm-paper palette, and archive pages intact.

This is a presentation-layer rewrite. No content migration, no plugin changes, no build-step
additions, no framework.

## Decisions (agreed with user)

| Question | Decision |
|---|---|
| Scope | Full redesign, chimes-inspired |
| Palette | Keep warm paper, add depth |
| Home | Hero + Writing + Shelves, archives kept as separate pages |
| Hero canvas | Hanging strings / chimes (verlet physics) |
| About | Overlay modal |
| Audio | Chime on strand strike, muted by default |
| Mobile / reduced motion | Keep physics, touch-driven (hard-disabled under `prefers-reduced-motion`) |

## Architecture

| File | Change |
|---|---|
| `_layouts/default.html` | Sidebar → sticky top nav. Adds canvas mount, About modal markup, JS includes. |
| `_layouts/home.html` | Rewritten: anchored sections `#home`, `#writing`, `#shelf`. |
| `_layouts/post.html` | Restyled; TOC script kept, structure unchanged. |
| `blogs.html`, `bookshelf.html`, `papershelf.html`, `courses.html`, `talks.html` | Restyled via CSS only. |
| `static/css/site.css` | Rewritten. Existing tokens extended, not replaced. |
| `static/js/chimes.js` | **New.** Verlet strands + WebAudio chime. |
| `static/js/site.js` | **New.** Scroll reveals, About modal, nav scrollspy. |

Navigation on inner pages links back to home anchors (`/#writing`). "About" is a button
that opens the modal, available on every page.

## Hero canvas — `chimes.js`

Strands hang from the top edge of the hero, full-bleed.

```
strand = chain of ~18 points, point[0] pinned to top
verlet: v = (p - p.old) * damping
p.old = p
p += v + gravity
constraint: 6 relaxation passes, fixed segment length
pointer: points within radius R pushed by pointer delta,
falloff = 1 - (d/R)
```

- Strand count scales with viewport width (~28 desktop, ~14 phone).
- Pointer and touch both drive the simulation.
- Rendered as thin tapered ink lines at low alpha, with a single bead near each strand's
lower third — reads as a beaded curtain without per-bead rigid bodies.
- `IntersectionObserver` pauses the RAF loop when the hero leaves the viewport.
- `prefers-reduced-motion` → simulation disabled, strands drawn once at rest.

### Audio

WebAudio synthesis, zero audio files.

- Strike detected when the pointer crosses a strand's rest-x with velocity above threshold.
- Triangle oscillator → exponential decay envelope, ~1.2s tail.
- Pitch from a pentatonic scale indexed by strand position, so sweeping left-to-right
plays a run.
- **Muted by default.** Speaker toggle bottom-right; state persisted in `localStorage`.
- `AudioContext` created lazily on first unmute, avoiding browser autoplay warnings.

## Palette — warm paper + depth

Existing tokens stay. Added:

```css
--accent-amber: #c8922f; /* golden courtyard */
--accent-lacquer: #9c3b28; /* sparing — active nav, strand beads */
--paper-raise: #faf7f0; /* lifted surfaces above --bg */
--shadow-1: 0 1px 2px rgba(36,34,27,.06);
--shadow-2: 0 8px 24px rgba(36,34,27,.09);
--shadow-3: 0 24px 60px rgba(36,34,27,.12);
```

Tactility comes from layering: post rows and shelf cards sit on `--paper-raise` with
`--shadow-1`, lifting to `--shadow-2` and rising 2px on hover. Hero headline uses
display-scale Newsreader (`clamp(3.5rem, 9vw, 7rem)`) with tight tracking.

## Motion — `site.js`

- **Scroll reveal:** one `IntersectionObserver` adds `.in` → staggered fade-up, 60ms per
child; elements unobserved after firing.
- **About modal:** full-screen overlay, fade + scale-in, focus trap, Esc to close,
`aria-modal`, body scroll lock.
- **Scrollspy:** active nav item tracks the visible section on home.
- **Hover:** post rows and nav items get spring-eased transforms. No custom cursor.

All motion respects `prefers-reduced-motion`: reveals become instant, transforms drop.

## Out of scope

- Country-selector metaphor, per-section audio, beaded curtains on every section — these
fight a text-heavy technical blog.
- Any framework or build-step addition. Vanilla JS, two files, no dependencies.
- Post reading experience stays conservative; the drama lives on the home page.

## Acceptance criteria

1. `bundle exec jekyll build` succeeds with no new warnings.
2. Home renders hero with animated strands; dragging the cursor parts them and they settle.
3. Audio is silent until the speaker toggle is clicked; preference survives reload.
4. `#writing` and `#shelf` sections reveal on scroll; nav highlights the visible section.
5. About modal opens from any page, traps focus, closes on Esc and on backdrop click.
6. `/blogs/`, `/bookshelf/`, `/papershelf/`, `/courses/`, `/talks/` and a post page all
render correctly under the new nav and palette.
7. With `prefers-reduced-motion: reduce`, no simulation runs and no fade-up animations play.
8. Layout holds at 375px, 768px, and 1440px widths.
Loading