This commit is contained in:
30
app/api/analytics/route.ts
Normal file
30
app/api/analytics/route.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import { NextResponse } from 'next/server';
|
||||
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const headers = req.headers;
|
||||
|
||||
// Get IP here since we are the public facing entity
|
||||
const ip = headers.get('x-forwarded-for') || 'unknown';
|
||||
|
||||
// Relay to Admin Dashboard (Internal Docker Network)
|
||||
// admin_dash must be reachable. If in same docker network, use container name.
|
||||
// If separate, use host.docker.internal or configured URL.
|
||||
const adminUrl = process.env.ADMIN_DASH_URL || 'http://admin_dash:3000/api/track';
|
||||
|
||||
// We fire and forget - don't wait for response to keep this fast
|
||||
fetch(adminUrl, {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ ...body, ip }), // Pass IP along
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Forwarded-For': ip // Preserve IP
|
||||
},
|
||||
}).catch(e => console.error('Relay failed', e));
|
||||
|
||||
return NextResponse.json({ success: true });
|
||||
} catch (error) {
|
||||
return NextResponse.json({ success: false });
|
||||
}
|
||||
}
|
||||
14
app/page.tsx
14
app/page.tsx
@@ -1,3 +1,5 @@
|
||||
import Image from "next/image";
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="min-h-screen flex flex-col items-center p-8 sm:p-20 relative overflow-hidden bg-zinc-50 dark:bg-zinc-950 text-zinc-900 dark:text-zinc-50 font-sans">
|
||||
@@ -10,7 +12,7 @@ export default function Home() {
|
||||
|
||||
<main className="z-10 max-w-5xl w-full animate-fade-in mt-20 sm:mt-32 pb-24 px-6">
|
||||
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24 items-center">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-12 md:gap-24 items-start">
|
||||
|
||||
{/* Left Column: Text & Info */}
|
||||
<section className="space-y-8 order-2 md:order-1">
|
||||
@@ -55,21 +57,21 @@ export default function Home() {
|
||||
<div className="pt-2 animate-slide-up" style={{ animationDelay: '0.5s' }}>
|
||||
<p className="text-sm text-zinc-400 dark:text-zinc-500 font-mono">
|
||||
<span className="text-zinc-900 dark:text-zinc-100 mr-2">Currently:</span>
|
||||
Writing about the architecture of personal sites. <a href="/blog/testing-layout" className="text-zinc-600 dark:text-zinc-400 underline decoration-zinc-300 dark:decoration-zinc-700 hover:text-zinc-900 dark:hover:text-zinc-100">Read latest →</a>
|
||||
Writing about deep learning happenings <a href="/blog/testing-layout" className="text-zinc-600 dark:text-zinc-400 underline decoration-zinc-300 dark:decoration-zinc-700 hover:text-zinc-900 dark:hover:text-zinc-100">Read latest →</a>
|
||||
</p>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
{/* Right Column: Photo */}
|
||||
<div className="order-1 md:order-2 flex justify-center md:justify-end animate-slide-up" style={{ animationDelay: '0.2s' }}>
|
||||
<div className="relative w-64 h-64 sm:w-80 sm:h-80 rounded-2xl overflow-hidden bg-zinc-100 dark:bg-zinc-900 shadow-2xl transition-transform duration-500 ease-out grayscale hover:grayscale-0">
|
||||
<div className="relative w-64 h-64 sm:w-80 sm:h-80 rounded-2xl overflow-hidden bg-zinc-100 dark:bg-zinc-900 shadow-2xl transition-transform duration-500 ease-out">
|
||||
{/* UNCOMMENT AFTER ADDING public/profile.jpg */}
|
||||
{/* <Image src="/profile.jpg" alt="Akshay Kolli" fill className="object-cover" priority /> */}
|
||||
<Image src="/profile.jpeg" alt="Akshay Kolli" fill className="object-cover" priority />
|
||||
|
||||
{/* Placeholder */}
|
||||
<div className="absolute inset-0 flex items-center justify-center text-zinc-300 dark:text-zinc-700 border-2 border-dashed border-zinc-200 dark:border-zinc-800 m-2 rounded-xl">
|
||||
{/* <div className="absolute inset-0 flex items-center justify-center text-zinc-300 dark:text-zinc-700 border-2 border-dashed border-zinc-200 dark:border-zinc-800 m-2 rounded-xl">
|
||||
<span className="font-mono text-sm">profile.jpg</span>
|
||||
</div>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -3,9 +3,7 @@ export default function ResumePage() {
|
||||
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-fade-in">
|
||||
<header className="space-y-4">
|
||||
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">Resume</h1>
|
||||
<p className="text-zinc-500 dark:text-zinc-400 font-light">
|
||||
Academic and professional timeline.
|
||||
</p>
|
||||
|
||||
</header>
|
||||
|
||||
{/* Timeline Section */}
|
||||
|
||||
@@ -10,12 +10,8 @@ export function Analytics() {
|
||||
// Send beacon on mount and path change
|
||||
const sendBeacon = async () => {
|
||||
try {
|
||||
// Point to the Admin Dashboard API
|
||||
// In prod, this URL needs to be the absolute URL of admin_dash
|
||||
// For local docker, standard localhost access might work if CORS allows,
|
||||
// or we route through a proxy.
|
||||
// For this personal setup, let's assume they are on same domain or localhost.
|
||||
await fetch('http://localhost:3000/api/track', { // TODO: Make this configurable
|
||||
// Send beacon to THIS website's API, which will relay it to the admin dash
|
||||
await fetch('/api/analytics', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ path: pathname, timestamp: Date.now() }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
||||
@@ -2,7 +2,6 @@ export function Footer() {
|
||||
return (
|
||||
<footer className="w-full py-8 text-center text-xs text-zinc-400 dark:text-zinc-600 font-mono border-t border-zinc-200/50 dark:border-zinc-800/50 mt-auto">
|
||||
<div className="max-w-4xl mx-auto px-6 flex flex-col sm:flex-row justify-between items-center gap-4">
|
||||
<span>© {new Date().getFullYear()} John Doe. All rights reserved.</span>
|
||||
|
||||
<div className="flex gap-4">
|
||||
<a href="#" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">Twitter</a>
|
||||
|
||||
@@ -1,107 +0,0 @@
|
||||
---
|
||||
title: "The Architecture of a Modern Personal Site"
|
||||
date: "2025-02-06"
|
||||
description: "A deep dive into the technical stacks, design philosophies, and implementation details of building a minimalist digital garden in the age of AI."
|
||||
tags: ["Architecture", "Next.js", "Design", "System"]
|
||||
---
|
||||
|
||||
# Introduction
|
||||
|
||||
In an era dominated by algorithmic feeds and ephemeral content, the personal website remains the last bastion of digital sovereignty. It is a space where the signal-to-noise ratio is controlled entirely by the author.
|
||||
|
||||
This post serves as **stress test** for the typography and layout of this blog engine. We need to verify that long-form content is readable, that the table of contents tracks correctly, and that the marginalia (side notes) function as intended.
|
||||
|
||||
## The Tech Stack
|
||||
|
||||
We opted for a "bleeding-edge but stable" approach. The core constraints were:
|
||||
|
||||
1. **Performance**: Sub-100ms navigation.
|
||||
2. **Simplicity**: No heavy client-side hydration unless necessary.
|
||||
3. **Ownership**: Markdown-backed content.
|
||||
|
||||
<SideNote title="Why Markdown?">
|
||||
Markdown ensures that the content is portable. If this site moves to a completely different stack in 5 years, the content remains valid.
|
||||
</SideNote>
|
||||
|
||||
### Next.js & React Server Components
|
||||
|
||||
We utilize React Server Components (RSC) to render the MDX content entirely on the server. This means zero JavaScript is shipped for the blog post content itself.
|
||||
|
||||
```typescript
|
||||
// app/blog/[slug]/page.tsx
|
||||
export default async function BlogPost({ params }: Props) {
|
||||
const { slug } = await params;
|
||||
const post = getPostBySlug(slug);
|
||||
|
||||
return (
|
||||
<article>
|
||||
<MDXRemote source={post.content} />
|
||||
</article>
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||

|
||||
|
||||
## Typography & Rhythm
|
||||
|
||||
Good typography is invisible. It guides the eye without drawing attention to itself. We use **Inter** for its tall x-height and readability on screens.
|
||||
|
||||
The vertical rhythm is maintained using `prose` (Tailwind Typography), ensuring that margins between paragraphs, headings, and lists are consistent.
|
||||
|
||||
### List Styles
|
||||
|
||||
* **Unordered lists** are used for collections of related items.
|
||||
* **Ordered lists** imply a sequence or ranking.
|
||||
* **Nested lists** should handle indentation gracefully.
|
||||
|
||||
1. First item
|
||||
2. Second item
|
||||
* Sub-item A
|
||||
* Sub-item B
|
||||
3. Third item
|
||||
|
||||
## Handling Complexity
|
||||
|
||||
What happens when we introduce complex diagrams or citations?
|
||||
|
||||
The system uses a custom citation component <Citation id="tufte" index="1" /> that links to a bibliography at the end. This is inspired by Tufte's design principles.
|
||||
|
||||
> "Clutter and confusion are failures of design, not attributes of information." — Edward Tufte
|
||||
|
||||
## Deep Dive: The Graph Network
|
||||
|
||||
Let's discuss the graph neural network work mentioned in my resume. When dealing with multi-agent systems, the topology of interaction is often unknown.
|
||||
|
||||
### Inference Challenge
|
||||
|
||||
Inferring the graph $G = (V, E)$ from a set of trajectories $T$ is an NP-hard problem in its general form. However, by restricting the hypothesis space to local interactions, we can approximate the adjacency matrix.
|
||||
|
||||
<SideNote title="Attention Mechanism">
|
||||
The attention mechanism acts as a "soft" adjacency matrix, allowing the model to learn weights between agents dynamically.
|
||||
</SideNote>
|
||||
|
||||
We developed a novel **Fast Graph Attention** layer that allows for:
|
||||
|
||||
1. **Batching**: Processing heavily disjoint graphs in parallel.
|
||||
2. **Sparsity**: Pruning edges with low attention scores during training.
|
||||
|
||||
```python
|
||||
class FastGAT(nn.Module):
|
||||
def forward(self, x, adj):
|
||||
# Efficient sparse matrix multiplication
|
||||
return spmm(adj, self.W(x))
|
||||
```
|
||||
|
||||
## Conclusion
|
||||
|
||||
This layout seems to handle various content types well. The sticky TOC on the left should have tracked your progress through *The Tech Stack*, *Typography*, and *Deep Dive* sections.
|
||||
|
||||
The marginalia should have appeared to the right of the relevant paragraphs without overlapping the main text.
|
||||
|
||||
<Bibliography>
|
||||
<div id="bib-tufte">
|
||||
[1] Edward Tufte, *Envisioning Information*, Graphics Press, 1990.
|
||||
</div>
|
||||
</Bibliography>
|
||||
BIN
public/profile.jpeg
Normal file
BIN
public/profile.jpeg
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 180 KiB |
Reference in New Issue
Block a user