Compare commits

...

20 Commits

Author SHA1 Message Date
Akshay Kolli
aa7fe8ecc7 minor edits
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 26s
2026-02-27 23:05:29 -05:00
Akshay Kolli
c3f5c875e2 updated resume
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-27 23:03:29 -05:00
Akshay Kolli
6da3a35832 blog: blackwell updated images
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
2026-02-27 23:02:22 -05:00
Akshay Kolli
f647a4ad90 blog: blackwell --final edit
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-27 22:55:24 -05:00
Akshay Kolli
c4fa3976f9 blog draft blackwell -- 2
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-27 22:50:06 -05:00
Akshay Kolli
19d0ad59a0 blog draft blackwell
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-27 22:37:24 -05:00
Akshay Kolli
21859a250f testing images two
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-27 22:18:30 -05:00
Akshay Kolli
8c1d03ac3f image upload test
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 26s
2026-02-27 22:16:22 -05:00
Akshay Kolli
9f8ff8befe update 2 blackwell post
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-27 22:12:17 -05:00
Akshay Kolli
2a8df25c16 Mini change
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
2026-02-27 21:24:12 -05:00
Akshay Kolli
041a22e0d2 Test post
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 29s
2026-02-27 20:42:27 -05:00
Shivam Patel
9e367010d9 Add admin dashboard integration docs
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:30:12 -05:00
Shivam Patel
9cf5f9620c Fix SQLite init: lazy loading, writability check, and host permissions
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 30s
DB initialization was eager at module import time, crashing the entire
analytics route when /server_storage wasn't writable. Now deferred to
first logVisit() call with graceful fallback. Also fix deploy workflow
to chown via a bind-mounted container so permissions apply on the host.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:17:44 -05:00
Shivam Patel
4a01928fe7 Add SQLite visitor IP logging to /server_storage
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 1m9s
Log visitor IPs and paths to a local SQLite database via the existing
analytics API route, persisted at /server_storage/visitors.db in Docker
(falls back to ./visitors.db in dev).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 00:04:45 -05:00
Akshay Kolli
73eb9264df Updated .gitea workflow to use a shared volume
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
2026-02-08 23:51:25 -05:00
Shivam Patel
c0ffc01b88 shivam + claude changes
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
2026-02-08 23:18:21 -05:00
Akshay
3174638dd3 moved file
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 26s
2026-02-08 22:45:20 -05:00
Akshay Kolli
2c3b519af3 Updated links
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 25s
2026-02-08 14:26:06 -05:00
Akshay Kolli
df80a1985e Updated favicon
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 26s
2026-02-08 14:22:56 -05:00
Akshay Kolli
9b8a25f5c8 Minor changes
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 27s
2026-02-08 14:05:15 -05:00
48 changed files with 1056 additions and 209 deletions

View File

@@ -0,0 +1,107 @@
---
name: personal-site-feature-dev
description: "Use this agent when the user wants to build, design, or implement new features for their personal website. This includes adding new pages, components, interactive elements, styling updates, content sections, animations, integrations, or any functional enhancement to the site.\\n\\nExamples:\\n\\n<example>\\nContext: The user wants to add a new section to their personal website.\\nuser: \"I want to add a blog section to my personal website\"\\nassistant: \"Let me launch the personal-site-feature-dev agent to help design and implement a blog section for your site.\"\\n<commentary>\\nSince the user wants to build a new feature for their personal website, use the Task tool to launch the personal-site-feature-dev agent to plan and implement the blog section.\\n</commentary>\\n</example>\\n\\n<example>\\nContext: The user wants to improve an existing part of their website.\\nuser: \"The contact form on my site looks outdated and doesn't validate inputs properly\"\\nassistant: \"I'll use the personal-site-feature-dev agent to redesign and improve your contact form with proper validation.\"\\n<commentary>\\nSince the user wants to enhance an existing feature on their personal website, use the Task tool to launch the personal-site-feature-dev agent to handle the redesign and validation improvements.\\n</commentary>\\n</example>\\n\\n<example>\\nContext: The user mentions wanting to add interactivity or a visual element.\\nuser: \"Can you add a dark mode toggle to my site?\"\\nassistant: \"Let me launch the personal-site-feature-dev agent to implement a dark mode toggle with proper theme persistence.\"\\n<commentary>\\nSince the user wants a new interactive feature for their personal website, use the Task tool to launch the personal-site-feature-dev agent to implement the dark mode toggle.\\n</commentary>\\n</example>"
model: opus
color: purple
memory: local
---
You are an expert full-stack web developer specializing in personal websites and portfolios. You have deep expertise in modern frontend frameworks, responsive design, accessibility, performance optimization, and creating polished, professional personal sites that stand out. You combine strong engineering skills with an eye for design and UX.
## Core Responsibilities
1. **Understand Before Building**: Before writing any code, thoroughly examine the existing codebase to understand:
- The framework and tech stack in use (React, Next.js, Astro, plain HTML/CSS, etc.)
- Existing design patterns, component structures, and styling approaches
- The project's file organization and naming conventions
- Any configuration files (package.json, tsconfig, etc.) that inform how to write code
- Only look at `.env.example` files for environment variable references — never read active `.env` files
2. **Think Critically About Features**: When the user proposes a feature:
- Clarify what they want to achieve and why
- Consider tradeoffs: complexity vs. value, performance implications, maintenance burden, accessibility impact
- Suggest alternatives if a simpler approach would achieve the same goal
- Don't just agree — push back constructively if something would hurt the site's quality
- Present options with clear pros and cons when multiple approaches exist
3. **Plan Before Implementing**: For each feature:
- Outline the components, files, and changes needed
- Identify dependencies or packages required
- Consider how the feature fits with existing design language
- Flag any potential issues (browser compatibility, performance, SEO impact)
4. **Write Production-Quality Code**:
- Follow the existing code style and conventions in the project exactly
- Write semantic, accessible HTML (proper heading hierarchy, ARIA labels, keyboard navigation)
- Ensure responsive design works across mobile, tablet, and desktop
- Add appropriate TypeScript types if the project uses TypeScript
- Keep components modular and reusable where sensible
- Include meaningful comments only where the code isn't self-explanatory
## Feature Development Workflow
1. **Discovery**: Read relevant existing files to understand current state
2. **Proposal**: Present the implementation plan with tradeoffs
3. **Implementation**: Write the code, matching existing patterns
4. **Verification**: Review your own code for bugs, accessibility issues, and edge cases
5. **Polish**: Suggest refinements for animations, transitions, or micro-interactions that elevate the feature
## Quality Standards
- **Accessibility**: All features must be WCAG 2.1 AA compliant at minimum
- **Performance**: Lazy load images, minimize bundle impact, avoid layout shifts
- **SEO**: Use proper meta tags, semantic HTML, and structured data where relevant
- **Responsive**: Mobile-first approach, test mental model across breakpoints
- **Cross-browser**: Note any features that may need polyfills or fallbacks
## Design Sensibility
- Maintain visual consistency with the existing site design
- Use the site's existing color palette, typography, and spacing system
- Suggest subtle animations and transitions that enhance UX without being distracting
- Prioritize clean, readable layouts with good whitespace
- Consider dark mode compatibility if the site supports it
## What NOT To Do
- Don't add unnecessary dependencies when native solutions work
- Don't over-engineer simple features
- Don't break existing functionality when adding new features
- Don't ignore the existing design system or create inconsistent styling
- Don't skip error handling or loading states
- Never read or access active `.env` files
## Communication Style
- Be direct and specific about what you're building and why
- When presenting tradeoffs, use concrete terms ("This adds ~50KB to bundle" not "This might be slightly larger")
- If you're unsure about a design decision, present 2-3 options with clear recommendations
- Proactively mention things the user might not have considered (SEO, accessibility, mobile experience)
**Update your agent memory** as you discover codebase patterns, design tokens, component conventions, framework-specific idioms, and architectural decisions in this personal website project. This builds up knowledge across conversations so you can maintain consistency. Write concise notes about what you found and where.
Examples of what to record:
- Framework and key libraries used (e.g., "Next.js 14 with App Router, Tailwind CSS, Framer Motion")
- Component patterns (e.g., "Components use named exports, co-located styles in .module.css files")
- Design tokens (e.g., "Primary color: #3B82F6, font stack: Inter for body, Fira Code for code blocks")
- File structure conventions (e.g., "Pages in /app, shared components in /components/ui")
- Any custom utilities, hooks, or helpers that should be reused
# Persistent Agent Memory
You have a persistent Persistent Agent Memory directory at `/Users/shivampatel/akshay/Webserver/.claude/agent-memory-local/personal-site-feature-dev/`. Its contents persist across conversations.
As you work, consult your memory files to build on previous experience. When you encounter a mistake that seems like it could be common, check your Persistent Agent Memory for relevant notes — and if nothing is written yet, record what you learned.
Guidelines:
- `MEMORY.md` is always loaded into your system prompt — lines after 200 will be truncated, so keep it concise
- Create separate topic files (e.g., `debugging.md`, `patterns.md`) for detailed notes and link to them from MEMORY.md
- Record insights about problem constraints, strategies that worked or failed, and lessons learned
- Update or remove memories that turn out to be wrong or outdated
- Organize memory semantically by topic, not chronologically
- Use the Write and Edit tools to update your memory files
- Since this memory is local-scope (not checked into version control), tailor your memories to this project and machine
## MEMORY.md
Your MEMORY.md is currently empty. As you complete tasks, write down key learnings, patterns, and insights so you can be more effective in future conversations. Anything saved in MEMORY.md will be included in your system prompt next time.

View File

@@ -8,7 +8,7 @@ on:
jobs: jobs:
build-and-deploy: build-and-deploy:
runs-on: ubuntu-latest # Ensure your runner has this label, or change to 'docker' or similar runs-on: ubuntu-latest
steps: steps:
- name: Check out repository code - name: Check out repository code
uses: actions/checkout@v3 uses: actions/checkout@v3
@@ -22,10 +22,18 @@ jobs:
docker stop website-container || true docker stop website-container || true
docker rm website-container || true docker rm website-container || true
- name: Prepare Host Storage
run: |
# The bind mount creates /server_storage on the host if missing.
# Run a throwaway container to fix ownership so UID 1001 (nextjs) can write.
docker run --rm -v /server_storage:/data alpine sh -c \
"chown -R 1001:1001 /data && chmod -R 755 /data"
- name: Run New Container - name: Run New Container
run: | run: |
docker run -d \ docker run -d \
--name website-container \ --name website-container \
--restart unless-stopped \ --restart unless-stopped \
-p 8080:3000 \ -p 8080:3000 \
my-website:latest -v /server_storage:/server_storage \
my-website:latest

106
ADMIN_DASH_INTEGRATION.md Normal file
View File

@@ -0,0 +1,106 @@
# Admin Dashboard Integration
How to integrate with the website container from an external admin dashboard.
## 1. Uptime Check
**GET** `http://website-container:3000/api/health`
Returns:
```json
{ "status": "ok", "timestamp": "2026-02-09T05:19:53.468Z" }
```
- 200 = up, anything else (timeout, connection refused, non-200) = down
- Both containers must be on the same Docker network for hostname resolution:
```bash
docker network create app-net
docker network connect app-net website-container
docker network connect app-net admin-dash-container
```
Alternatively, use `http://host.docker.internal:8080/api/health` to go through the host port mapping (no shared network needed, but adds overhead).
## 2. Visitors Database
The website writes visitor data to a SQLite DB at `/server_storage/visitors.db`. Mount the same host volume in the admin dashboard container (read-only):
```bash
docker run -d \
--name admin-dash-container \
-v /server_storage:/server_storage:ro \
admin-dash:latest
```
### Schema
```sql
CREATE TABLE visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_address TEXT NOT NULL, -- e.g. "2600:6c65:6740:..." or "18.199.106.183"
path TEXT NOT NULL, -- e.g. "/", "/blog", "/resume"
visited_at TEXT NOT NULL -- UTC datetime, e.g. "2026-02-09 05:19:53"
);
```
### Reading from the DB
The DB uses WAL mode, so reads won't block the website's writes. Open in **read-only** mode to avoid conflicts:
```typescript
// Node.js with better-sqlite3
import Database from 'better-sqlite3';
const db = new Database('/server_storage/visitors.db', { readonly: true });
const visits = db.prepare('SELECT * FROM visits ORDER BY visited_at DESC LIMIT 100').all();
```
```python
# Python with sqlite3
import sqlite3
conn = sqlite3.connect('file:///server_storage/visitors.db?mode=ro', uri=True)
visits = conn.execute('SELECT * FROM visits ORDER BY visited_at DESC LIMIT 100').fetchall()
```
### Useful Queries
```sql
-- Unique visitors today
SELECT COUNT(DISTINCT ip_address) FROM visits
WHERE visited_at >= date('now');
-- Page view counts
SELECT path, COUNT(*) as views FROM visits
GROUP BY path ORDER BY views DESC;
-- Visits per hour (last 24h)
SELECT strftime('%Y-%m-%d %H:00', visited_at) as hour, COUNT(*) as views
FROM visits WHERE visited_at >= datetime('now', '-1 day')
GROUP BY hour ORDER BY hour;
```
## Docker Compose Example
```yaml
services:
website:
image: my-website:latest
ports:
- "8080:3000"
volumes:
- /server_storage:/server_storage
networks:
- app-net
admin-dash:
image: admin-dash:latest
ports:
- "3333:3000"
volumes:
- /server_storage:/server_storage:ro
networks:
- app-net
networks:
app-net:
```

View File

@@ -3,7 +3,7 @@ FROM node:20-alpine AS base
# Install dependencies only when needed # Install dependencies only when needed
FROM base AS deps FROM base AS deps
# Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed. # Check https://github.com/nodejs/docker-node/tree/b4117f9333da4138b03a546ec926ef50a31506c3#nodealpine to understand why libc6-compat might be needed.
RUN apk add --no-cache libc6-compat RUN apk add --no-cache libc6-compat python3 make g++
WORKDIR /app WORKDIR /app
# Install dependencies based on the preferred package manager # Install dependencies based on the preferred package manager
@@ -35,6 +35,9 @@ RUN adduser --system --uid 1001 nextjs
COPY --from=builder /app/public ./public COPY --from=builder /app/public ./public
# Create server_storage directory for SQLite DB
RUN mkdir -p /server_storage && chown nextjs:nodejs /server_storage
# Set the correct permission for prerender cache # Set the correct permission for prerender cache
RUN mkdir .next RUN mkdir .next
RUN chown nextjs:nodejs .next RUN chown nextjs:nodejs .next

View File

@@ -0,0 +1,44 @@
import { NextResponse } from 'next/server';
import { logVisit } from '@/lib/db';
const ANALYTICS_KEY = process.env.ANALYTICS_KEY || 'default-analytics-key';
export async function POST(req: Request) {
try {
const analyticsKey = req.headers.get('X-Analytics-Key');
if (analyticsKey !== ANALYTICS_KEY) {
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 });
}
const body = await req.json();
if (typeof body.path !== 'string' || typeof body.timestamp !== 'number') {
return NextResponse.json({ success: false, error: 'Invalid input' }, { status: 400 });
}
const headers = req.headers;
const forwarded = headers.get('x-forwarded-for');
const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
try {
logVisit(ip, body.path);
} catch (e) {
console.error('Failed to log visit to SQLite', e);
}
const adminUrl = process.env.ADMIN_DASH_URL || 'http://admin_dash:3000/api/track';
fetch(adminUrl, {
method: 'POST',
body: JSON.stringify({ path: body.path, timestamp: body.timestamp, ip }),
headers: {
'Content-Type': 'application/json',
'X-Forwarded-For': ip,
},
}).catch(e => console.error('Relay failed', e));
return NextResponse.json({ success: true });
} catch {
return NextResponse.json({ success: false, error: 'Bad request' }, { status: 400 });
}
}

View File

@@ -0,0 +1,16 @@
export default function BlogPostLoading() {
return (
<div className="max-w-3xl mx-auto px-6 py-24 animate-pulse">
<div className="space-y-4 mb-12">
<div className="h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-1/4" />
<div className="h-12 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" />
<div className="h-6 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" />
</div>
<div className="space-y-4">
{Array.from({ length: 8 }).map((_, i) => (
<div key={i} className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded" style={{ width: `${70 + Math.random() * 30}%` }} />
))}
</div>
</div>
);
}

View File

@@ -1,4 +1,4 @@
import { getPostBySlug } from '@/lib/mdx'; import { getPostBySlug, getPostSlugs } from '@/lib/mdx';
import { MDXRemote } from 'next-mdx-remote/rsc'; import { MDXRemote } from 'next-mdx-remote/rsc';
import Link from 'next/link'; import Link from 'next/link';
import { notFound } from 'next/navigation'; import { notFound } from 'next/navigation';
@@ -11,7 +11,7 @@ import { Citation, Bibliography } from '@/components/mdx/Citation';
import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents'; import { MobileTableOfContents } from '@/components/mdx/MobileTableOfContents';
// Utility to ensure consistent IDs // Utility to ensure consistent IDs
const slugify = (text: any): string => { const slugify = (text: React.ReactNode): string => {
if (!text) return ''; if (!text) return '';
const str = typeof text === 'string' ? text : String(text); const str = typeof text === 'string' ? text : String(text);
return str return str
@@ -21,16 +21,16 @@ const slugify = (text: any): string => {
}; };
const components = { const components = {
h1: (props: any) => <h1 {...props} id={slugify(props.children)} className="text-3xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />, h1: (props: React.ComponentPropsWithoutRef<'h1'>) => <h1 {...props} id={slugify(props.children)} className="text-3xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
h2: (props: any) => <h2 {...props} id={slugify(props.children)} className="text-2xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />, h2: (props: React.ComponentPropsWithoutRef<'h2'>) => <h2 {...props} id={slugify(props.children)} className="text-2xl font-bold mt-8 mb-4 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
h3: (props: any) => <h3 {...props} id={slugify(props.children)} className="text-xl font-bold mt-6 mb-3 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />, h3: (props: React.ComponentPropsWithoutRef<'h3'>) => <h3 {...props} id={slugify(props.children)} className="text-xl font-bold mt-6 mb-3 text-zinc-900 dark:text-zinc-50 scroll-mt-24" />,
p: (props: any) => <p {...props} className="mb-4 leading-relaxed text-zinc-700 dark:text-zinc-300" />, p: (props: React.ComponentPropsWithoutRef<'p'>) => <p {...props} className="mb-4 leading-relaxed text-zinc-700 dark:text-zinc-300" />,
ul: (props: any) => <ul {...props} className="list-disc pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />, ul: (props: React.ComponentPropsWithoutRef<'ul'>) => <ul {...props} className="list-disc pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
ol: (props: any) => <ol {...props} className="list-decimal pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />, ol: (props: React.ComponentPropsWithoutRef<'ol'>) => <ol {...props} className="list-decimal pl-5 mb-4 space-y-2 text-zinc-700 dark:text-zinc-300" />,
blockquote: (props: any) => <blockquote {...props} className="border-l-4 border-zinc-200 dark:border-zinc-700 pl-4 italic my-6 text-zinc-600 dark:text-zinc-400" />, blockquote: (props: React.ComponentPropsWithoutRef<'blockquote'>) => <blockquote {...props} className="border-l-4 border-zinc-200 dark:border-zinc-700 pl-4 italic my-6 text-zinc-600 dark:text-zinc-400" />,
code: (props: any) => <code {...props} className="bg-zinc-100 dark:bg-zinc-800 text-pink-600 dark:text-pink-400 px-1 py-0.5 rounded text-sm font-mono" />, code: (props: React.ComponentPropsWithoutRef<'code'>) => <code {...props} className="bg-zinc-100 dark:bg-zinc-800 text-pink-600 dark:text-pink-400 px-1 py-0.5 rounded text-sm font-mono" />,
pre: (props: any) => <pre {...props} className="bg-zinc-900 dark:bg-zinc-900 text-zinc-100 p-4 rounded-lg overflow-x-auto my-6 text-sm font-mono" />, pre: (props: React.ComponentPropsWithoutRef<'pre'>) => <pre {...props} className="bg-zinc-900 dark:bg-zinc-900 text-zinc-100 p-4 rounded-lg overflow-x-auto my-6 text-sm font-mono" />,
a: (props: any) => <a {...props} className="text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all font-medium" />, a: (props: React.ComponentPropsWithoutRef<'a'>) => <a {...props} className="text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all font-medium" />,
SideNote, SideNote,
Citation, Citation,
Bibliography, Bibliography,
@@ -40,6 +40,12 @@ type Props = {
params: Promise<{ slug: string }>; params: Promise<{ slug: string }>;
}; };
export function generateStaticParams() {
return getPostSlugs()
.filter((slug) => slug.endsWith('.mdx'))
.map((slug) => ({ slug: slug.replace(/\.mdx$/, '') }));
}
export async function generateMetadata({ params }: Props) { export async function generateMetadata({ params }: Props) {
const { slug } = await params; const { slug } = await params;
try { try {
@@ -47,6 +53,17 @@ export async function generateMetadata({ params }: Props) {
return { return {
title: metadata.title, title: metadata.title,
description: metadata.description, description: metadata.description,
openGraph: {
title: metadata.title,
description: metadata.description,
type: 'article' as const,
publishedTime: metadata.date,
},
twitter: {
card: 'summary' as const,
title: metadata.title,
description: metadata.description,
},
}; };
} catch { } catch {
return { return {

20
app/blog/loading.tsx Normal file
View File

@@ -0,0 +1,20 @@
export default function BlogLoading() {
return (
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-pulse">
<div className="space-y-4">
<div className="h-10 bg-zinc-200 dark:bg-zinc-800 rounded w-1/3" />
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-2/3" />
</div>
<div className="grid gap-10">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="space-y-3">
<div className="h-4 bg-zinc-200 dark:bg-zinc-800 rounded w-1/4" />
<div className="h-7 bg-zinc-200 dark:bg-zinc-800 rounded w-3/4" />
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-full" />
<div className="h-5 bg-zinc-200 dark:bg-zinc-800 rounded w-5/6" />
</div>
))}
</div>
</div>
);
}

View File

@@ -1,7 +1,17 @@
import type { Metadata } from 'next';
import Link from 'next/link'; import Link from 'next/link';
import { getAllPosts } from '@/lib/mdx'; import { getAllPosts } from '@/lib/mdx';
import { format } from 'date-fns'; import { format } from 'date-fns';
export const metadata: Metadata = {
title: 'Blog',
description: 'Thoughts on software, deep learning, and research by Akshay Kolli.',
openGraph: {
title: 'Blog',
description: 'Thoughts on software, deep learning, and research by Akshay Kolli.',
},
};
export default function BlogIndex() { export default function BlogIndex() {
const posts = getAllPosts(); const posts = getAllPosts();

25
app/error.tsx Normal file
View File

@@ -0,0 +1,25 @@
'use client';
export default function Error({
reset,
}: {
error: Error & { digest?: string };
reset: () => void;
}) {
return (
<div className="min-h-[60vh] flex flex-col items-center justify-center px-6 text-center animate-fade-in">
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 mb-4">
Something went wrong
</h1>
<p className="text-lg text-zinc-500 dark:text-zinc-400 mb-8">
An unexpected error occurred.
</p>
<button
onClick={reset}
className="text-sm font-medium px-4 py-2 rounded bg-zinc-900 dark:bg-zinc-100 text-zinc-50 dark:text-zinc-900 hover:opacity-80 transition-opacity"
>
Try again
</button>
</div>
);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1,6 +1,13 @@
@import "tailwindcss"; @import "tailwindcss";
@plugin "@tailwindcss/typography"; @plugin "@tailwindcss/typography";
/* Getting rid of backticks in code blocks in blogs */
.prose code::before,
.prose code::after {
content: "" !important;
}
@theme { @theme {
--font-sans: var(--font-inter); --font-sans: var(--font-inter);
--color-zinc-50: #fafafa; --color-zinc-50: #fafafa;

View File

@@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { Navbar } from "../components/layout/Navbar"; import { Navbar } from "../components/layout/Navbar";
import { Footer } from "../components/layout/Footer"; import { Footer } from "../components/layout/Footer";
import { Analytics } from "../components/Analytics";
const inter = Inter({ const inter = Inter({
variable: "--font-inter", variable: "--font-inter",
@@ -11,12 +12,24 @@ const inter = Inter({
}); });
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Akshay Kolli", metadataBase: new URL("https://akkolli.net"),
description: "My personal website", title: {
default: "Akshay Kolli",
template: "%s | Akshay Kolli",
},
description: "Personal website of Akshay Kolli — CS PhD Student at UMass Lowell researching World Models, Reinforcement Learning, and Multi-Agent Systems.",
openGraph: {
title: "Akshay Kolli",
description: "CS PhD Student at UMass Lowell researching World Models, Reinforcement Learning, and Multi-Agent Systems.",
siteName: "Akshay Kolli",
type: "website",
},
twitter: {
card: "summary",
creator: "@thekolliakshay",
},
}; };
import { Analytics } from "../components/Analytics";
export default function RootLayout({ export default function RootLayout({
children, children,
}: Readonly<{ }: Readonly<{
@@ -27,8 +40,11 @@ export default function RootLayout({
<body <body
className={`${inter.variable} antialiased flex flex-col min-h-screen`} className={`${inter.variable} antialiased flex flex-col min-h-screen`}
> >
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:bg-white focus:dark:bg-zinc-900 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:rounded focus:shadow">
Skip to content
</a>
<Navbar /> <Navbar />
<main className="flex-grow"> <main id="main-content" className="flex-grow">
{children} {children}
</main> </main>
<Footer /> <Footer />

18
app/not-found.tsx Normal file
View File

@@ -0,0 +1,18 @@
import Link from 'next/link';
export default function NotFound() {
return (
<div className="min-h-[60vh] flex flex-col items-center justify-center px-6 text-center animate-fade-in">
<h1 className="text-6xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50 mb-4">404</h1>
<p className="text-lg text-zinc-500 dark:text-zinc-400 mb-8">
This page doesn&apos;t exist.
</p>
<Link
href="/"
className="text-sm font-medium text-zinc-900 dark:text-zinc-100 underline decoration-zinc-300 dark:decoration-zinc-700 underline-offset-4 hover:decoration-zinc-900 dark:hover:decoration-zinc-100 transition-all"
>
&larr; Back to home
</Link>
</div>
);
}

View File

@@ -1,4 +1,10 @@
import Image from "next/image";
import { getAllPosts } from "@/lib/mdx";
export default function Home() { export default function Home() {
const posts = getAllPosts();
const latestPost = posts[0];
const latestPostHref = latestPost ? `/blog/${latestPost.slug}` : '/blog';
return ( 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"> <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 +16,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"> <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 */} {/* Left Column: Text & Info */}
<section className="space-y-8 order-2 md:order-1"> <section className="space-y-8 order-2 md:order-1">
@@ -36,16 +42,16 @@ export default function Home() {
<div className="pt-4 border-t border-zinc-200 dark:border-zinc-800 animate-slide-up" style={{ animationDelay: '0.4s' }}> <div className="pt-4 border-t border-zinc-200 dark:border-zinc-800 animate-slide-up" style={{ animationDelay: '0.4s' }}>
<h3 className="text-xs font-bold uppercase tracking-widest text-zinc-900 dark:text-zinc-100 mb-4">Connect</h3> <h3 className="text-xs font-bold uppercase tracking-widest text-zinc-900 dark:text-zinc-100 mb-4">Connect</h3>
<div className="flex gap-6 font-medium text-sm"> <div className="flex gap-6 font-medium text-sm">
<a href="mailto:your.email@example.com" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors"> <a href="mailto:akshaykolli@hotmail.com" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
Email Email
</a> </a>
<a href="https://github.com/profLepton" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors"> <a href="https://github.com/akkolli" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
GitHub GitHub
</a> </a>
<a href="https://twitter.com" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors"> <a href="https://x.com/thekolliakshay" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
Twitter Twitter
</a> </a>
<a href="https://linkedin.com" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors"> <a href="https://www.linkedin.com/in/akshay-kolli-/" target="_blank" rel="noopener noreferrer" className="text-zinc-600 dark:text-zinc-400 hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">
LinkedIn LinkedIn
</a> </a>
</div> </div>
@@ -55,21 +61,15 @@ export default function Home() {
<div className="pt-2 animate-slide-up" style={{ animationDelay: '0.5s' }}> <div className="pt-2 animate-slide-up" style={{ animationDelay: '0.5s' }}>
<p className="text-sm text-zinc-400 dark:text-zinc-500 font-mono"> <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> <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 &rarr;</a> Writing about deep learning happenings <a href={latestPostHref} 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 &rarr;</a>
</p> </p>
</div> </div>
</section> </section>
{/* Right Column: Photo */} {/* 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="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.jpeg" alt="Akshay Kolli" fill className="object-cover" priority />
{/* <Image src="/profile.jpg" 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">
<span className="font-mono text-sm">profile.jpg</span>
</div>
</div> </div>
</div> </div>

View File

@@ -1,11 +1,20 @@
import type { Metadata } from 'next';
export const metadata: Metadata = {
title: 'Resume',
description: 'Experience, education, and technical skills of Akshay Kolli.',
openGraph: {
title: 'Resume',
description: 'Experience, education, and technical skills of Akshay Kolli.',
},
};
export default function ResumePage() { export default function ResumePage() {
return ( return (
<div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-fade-in"> <div className="max-w-3xl mx-auto px-6 py-24 space-y-12 animate-fade-in">
<header className="space-y-4"> <header className="space-y-4">
<h1 className="text-4xl font-bold tracking-tight text-zinc-900 dark:text-zinc-50">Resume</h1> <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> </header>
{/* Timeline Section */} {/* Timeline Section */}
@@ -96,13 +105,13 @@ export default function ResumePage() {
<div> <div>
<h3 className="font-semibold text-zinc-900 dark:text-zinc-100 mb-3 block">Languages</h3> <h3 className="font-semibold text-zinc-900 dark:text-zinc-100 mb-3 block">Languages</h3>
<div className="flex flex-wrap gap-2 text-sm text-zinc-600 dark:text-zinc-400 font-mono"> <div className="flex flex-wrap gap-2 text-sm text-zinc-600 dark:text-zinc-400 font-mono">
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Rust</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Kotlin</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Python</span> <span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Python</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Go</span> <span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Rust</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">C++</span> <span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">C++</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Go</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">SQL</span> <span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">SQL</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">TypeScript</span> <span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">TypeScript</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">Kotlin</span>
<span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">R</span> <span className="px-2 py-1 bg-zinc-100 dark:bg-zinc-900 rounded">R</span>
</div> </div>
</div> </div>

11
app/robots.ts Normal file
View File

@@ -0,0 +1,11 @@
import type { MetadataRoute } from 'next';
export default function robots(): MetadataRoute.Robots {
return {
rules: {
userAgent: '*',
allow: '/',
},
sitemap: 'https://akkolli.net/sitemap.xml',
};
}

18
app/sitemap.ts Normal file
View File

@@ -0,0 +1,18 @@
import type { MetadataRoute } from 'next';
import { getAllPosts } from '@/lib/mdx';
export default function sitemap(): MetadataRoute.Sitemap {
const posts = getAllPosts();
const blogEntries: MetadataRoute.Sitemap = posts.map((post) => ({
url: `https://akkolli.net/blog/${post.slug}`,
lastModified: new Date(post.date),
}));
return [
{ url: 'https://akkolli.net', lastModified: new Date() },
{ url: 'https://akkolli.net/blog', lastModified: new Date() },
{ url: 'https://akkolli.net/resume', lastModified: new Date() },
...blogEntries,
];
}

View File

@@ -10,15 +10,14 @@ export function Analytics() {
// Send beacon on mount and path change // Send beacon on mount and path change
const sendBeacon = async () => { const sendBeacon = async () => {
try { try {
// Point to the Admin Dashboard API // Send beacon to THIS website's API, which will relay it to the admin dash
// In prod, this URL needs to be the absolute URL of admin_dash await fetch('/api/analytics', {
// 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
method: 'POST', method: 'POST',
body: JSON.stringify({ path: pathname, timestamp: Date.now() }), body: JSON.stringify({ path: pathname, timestamp: Date.now() }),
headers: { 'Content-Type': 'application/json' }, headers: {
'Content-Type': 'application/json',
'X-Analytics-Key': process.env.NEXT_PUBLIC_ANALYTICS_KEY || 'default-analytics-key',
},
keepalive: true, keepalive: true,
}); });
} catch (e) { } catch (e) {

View File

@@ -2,11 +2,10 @@ export function Footer() {
return ( 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"> <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"> <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"> <div className="flex gap-4">
<a href="#" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">Twitter</a> <a href="https://x.com/thekolliakshay" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">Twitter</a>
<a href="#" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">GitHub</a> <a href="https://github.com/akkolli" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors">GitHub</a>
</div> </div>
</div> </div>
</footer> </footer>

View File

@@ -9,7 +9,7 @@ export function Navbar() {
const isActive = (path: string) => pathname?.startsWith(path); const isActive = (path: string) => pathname?.startsWith(path);
return ( return (
<nav className="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-zinc-50/80 dark:bg-zinc-950/80 border-b border-zinc-200/50 dark:border-zinc-800/50"> <nav aria-label="Main navigation" className="fixed top-0 left-0 w-full z-50 backdrop-blur-md bg-zinc-50/80 dark:bg-zinc-950/80 border-b border-zinc-200/50 dark:border-zinc-800/50">
<div className="max-w-4xl mx-auto px-6 h-16 flex items-center justify-between"> <div className="max-w-4xl mx-auto px-6 h-16 flex items-center justify-between">
<Link href="/" className="font-bold text-lg tracking-tight hover:opacity-70 transition-opacity"> <Link href="/" className="font-bold text-lg tracking-tight hover:opacity-70 transition-opacity">
AK AK
@@ -29,13 +29,13 @@ export function Navbar() {
Resume Resume
</Link> </Link>
<div className="w-px h-4 bg-zinc-200 dark:bg-zinc-800 hidden sm:block"></div> <div className="w-px h-4 bg-zinc-200 dark:bg-zinc-800 hidden sm:block"></div>
<a href="https://code.akkolli.net/lepton" target="_blank" rel="noopener noreferrer" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block"> <a href="https://code.akkolli.net/lepton" target="_blank" rel="noopener noreferrer" aria-label="Code repositories" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block">
Code Code
</a> </a>
<a href="https://github.com/profLepton" target="_blank" rel="noopener noreferrer" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block"> <a href="https://github.com/akkolli" target="_blank" rel="noopener noreferrer" aria-label="GitHub profile" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block">
GitHub GitHub
</a> </a>
<a href="https://twitter.com" target="_blank" rel="noopener noreferrer" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block"> <a href="https://x.com/thekolliakshay" target="_blank" rel="noopener noreferrer" aria-label="Twitter profile" className="hover:text-zinc-900 dark:hover:text-zinc-100 transition-colors hidden sm:block">
Twitter Twitter
</a> </a>
</div> </div>

View File

@@ -17,6 +17,8 @@ export function MobileTableOfContents({ headings }: { headings: Heading[] }) {
<div className="block xl:hidden mb-8 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden"> <div className="block xl:hidden mb-8 border border-zinc-200 dark:border-zinc-800 rounded-lg overflow-hidden">
<button <button
onClick={() => setIsOpen(!isOpen)} onClick={() => setIsOpen(!isOpen)}
aria-expanded={isOpen}
aria-controls="mobile-toc"
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" 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>Table of Contents</span>
@@ -26,7 +28,7 @@ export function MobileTableOfContents({ headings }: { headings: Heading[] }) {
</button> </button>
{isOpen && ( {isOpen && (
<ul className="p-4 bg-white dark:bg-black border-t border-zinc-200 dark:border-zinc-800 space-y-3"> <ul id="mobile-toc" className="p-4 bg-white dark:bg-black border-t border-zinc-200 dark:border-zinc-800 space-y-3">
{headings.map((heading) => ( {headings.map((heading) => (
<li <li
key={heading.id} key={heading.id}

View File

@@ -1,28 +0,0 @@
'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

@@ -54,7 +54,7 @@ export function TableOfContents({ headings }: { headings: Heading[] }) {
if (headings.length === 0) return null; if (headings.length === 0) return null;
return ( return (
<nav className="text-sm animate-fade-in text-left"> <nav aria-label="Table of contents" 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> <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"> <ul className="space-y-3">
{Array.isArray(headings) && headings.map((heading) => ( {Array.isArray(headings) && headings.map((heading) => (

View File

@@ -0,0 +1,51 @@
---
title: 'Blackwell: Datacenter vs GeForce GPUs'
date: '2026-02-27'
description: 'Jensen scammed us.'
tags: ['Nvidia', 'GPU', 'GPU Kernel']
---
I'm a proud owner for an RTX 5090 FE. I occasionally play games on it, but it's mostly used for ML workloads.
I jumped on the 50-series especially for the fp4 support on their 5th generation blackwell tensor cores, cause I'm actively working on some pretty exciting low precision computing.
Imagine my surprise when I was perusing the GPU mode discord and find people calling the GeForce blackwell cards "Fake blackwell"?!!
Looking online, I found next to no resources on the difference. I foolishly assumed that my GeForce card
<SideNote>The GeForce Cards are `sm_120` with compute capability 12 and the Datacenter cards are `sm_100` with compute capability 10
You'd expect a higher compute capability to mean something.</SideNote> would contain all the features from the datacenter cards, as
it seemed to be a later arch. No, Nvidia just made it confusing, and managed to obscure the technical details extremely well. Going through the [CUDA documentation](https://docs.nvidia.com/CUDA/parallel-thread-execution/),
you'll see that the new tensor core gen 5 instructions are only compatible with `sm_100[a-f]` (Datacenter Blackwell) and `sm_101` (Jetson Thor). What does this mean? That involved a lot more digging.
### What's in the new tensor cores?
The Blackwell Tensor Cores now support lower precision, namely FP6 and FP4, which the previous Hopper generation didn't. This enables extremely fast low precision matrix multiplications.
The PTX ISA also introduces `tcgen05` instructions, which make use of `TMEM` or tensor memory, which only the datacenter cards support. This additional memory sits next to the Tensor cores, and can
be used independent of the registers used in CUDA cores. The GeForce cards get 128KB of shared memory per SM, while the datacenter card and the Jetson thor get 228KB SMEM + 256KB TMEM. This is absolutely insane for
any kind of work load. Why did I have to dig so hard to find this information? The 5090 is an enthusiast tier card, which I feel deserves a clear description of what you're buying.
### Benchmarking NVFP4 performance
I needed to confirm this myself. <SideNote>NVFP4 is Nvidia's new low precision format. </SideNote> I downloaded the cutlass repo and ran the nvfp4 matrix multiply example. Here's what I got
![A screenshot of a cutlass nvfp4 matmul benchmark](/images/1_blackwell_dc_vs_gf/5090_65536_cropped.png)
Over a PETA FLOP of nvfp4 compute! ggs. This is already insane, and I'm very happy with it. I didn't get `wgmma` from hopper, nor the `tcgen05` instructions and the `TMEM`, but I did get a petaflop of nvfp4 compute.
Nsight Compute tells us exactly what we would expect
![Nisght Compute shows registers spilling, due to extremely high register pressure.](/images/1_blackwell_dc_vs_gf/geforce_ncu.png)
Tensor cores are so fast that the memory is bottlenecking them. All of the shared memory is filling up. Huh, I guess nvidia realised this and created `tcgen05` but we don't get to see any of that.
![Look at all that memory. Nvtop from my dreams.](/images/1_blackwell_dc_vs_gf/nvtop_b200.png)
To see how the GPU folk with datacenters live, I booted up a vast ai instance and ran the same matmul, but with cutlass kernels for `sm_100a`.
![jeez louise these things are fast](/images/1_blackwell_dc_vs_gf/b200_65536_cropped.png)
We're getting over 2 petaflops, and I'm sure these things can go even faster with better code. Not having `tcgen05` really holds back the geforce cards.
This is amazing, I wish I'd be able to get a taste of this locally.
Why Jensen, why.

View File

@@ -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>
);
}
```
![Example Image](/images/testing-layout/example.svg)
## 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>

52
lib/db.ts Normal file
View File

@@ -0,0 +1,52 @@
import Database from 'better-sqlite3';
import type BetterSqlite3 from 'better-sqlite3';
import path from 'path';
import fs from 'fs';
let db: BetterSqlite3.Database | null = null;
let insertStmt: BetterSqlite3.Statement | null = null;
function getDb() {
if (db) return db;
try {
const SERVER_STORAGE = '/server_storage';
let dbDir: string;
try {
fs.accessSync(SERVER_STORAGE, fs.constants.W_OK);
dbDir = SERVER_STORAGE;
} catch {
dbDir = process.cwd();
}
const dbPath = path.join(dbDir, 'visitors.db');
db = new Database(dbPath);
db.pragma('journal_mode = WAL');
db.exec(`
CREATE TABLE IF NOT EXISTS visits (
id INTEGER PRIMARY KEY AUTOINCREMENT,
ip_address TEXT NOT NULL,
path TEXT NOT NULL,
visited_at TEXT NOT NULL DEFAULT (datetime('now'))
);
`);
insertStmt = db.prepare(
'INSERT INTO visits (ip_address, path) VALUES (?, ?)'
);
return db;
} catch (e) {
console.error('Failed to initialize SQLite database:', e);
db = null;
insertStmt = null;
return null;
}
}
export function logVisit(ip: string, visitPath: string) {
const database = getDb();
if (!database || !insertStmt) return;
insertStmt.run(ip, visitPath);
}

View File

@@ -10,7 +10,6 @@ export type PostMetadata = {
description: string; description: string;
slug: string; slug: string;
tags?: string[]; tags?: string[];
[key: string]: any;
}; };
export type Post = { export type Post = {
@@ -27,8 +26,20 @@ export function getPostSlugs() {
export function getPostBySlug(slug: string): Post { export function getPostBySlug(slug: string): Post {
const realSlug = slug.replace(/\.mdx$/, ''); const realSlug = slug.replace(/\.mdx$/, '');
if (/[\/\\]|\.\./.test(realSlug)) {
throw new Error(`Invalid slug: ${realSlug}`);
}
const fullPath = path.join(postsDirectory, `${realSlug}.mdx`); const fullPath = path.join(postsDirectory, `${realSlug}.mdx`);
const fileContents = fs.readFileSync(fullPath, 'utf8');
let fileContents: string;
try {
fileContents = fs.readFileSync(fullPath, 'utf8');
} catch {
throw new Error(`Post not found: ${realSlug}`);
}
const { data, content } = matter(fileContents); const { data, content } = matter(fileContents);
return { return {

View File

@@ -3,16 +3,15 @@ import type { NextRequest } from 'next/server';
const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute const RATE_LIMIT_WINDOW = 60 * 1000; // 1 minute
const MAX_REQUESTS = 100; // 100 requests per window const MAX_REQUESTS = 100; // 100 requests per window
const CLEANUP_THRESHOLD = 500;
const ipMap = new Map<string, { count: number; expires: number }>(); const ipMap = new Map<string, { count: number; expires: number }>();
export function middleware(request: NextRequest) { export function middleware(request: NextRequest) {
// Simple in-memory rate limiting implementation const forwarded = request.headers.get('x-forwarded-for');
// Note: specific to a single instance container. For distributed, use Redis. const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
const ip = request.headers.get('x-forwarded-for') || 'unknown';
const now = Date.now(); const now = Date.now();
// Logging Stub: In future, this will push to a DB service
console.log(`[${new Date().toISOString()}] Request to ${request.nextUrl.pathname} from ${ip}`); console.log(`[${new Date().toISOString()}] Request to ${request.nextUrl.pathname} from ${ip}`);
const record = ipMap.get(ip); const record = ipMap.get(ip);
@@ -26,8 +25,8 @@ export function middleware(request: NextRequest) {
} }
} }
// Cleanup old entries occasionally (naive approach for this scale) // Cleanup old entries probabilistically (1-in-10 requests)
if (ipMap.size > 1000) { if (ipMap.size > CLEANUP_THRESHOLD && Math.random() < 0.1) {
for (const [key, val] of ipMap.entries()) { for (const [key, val] of ipMap.entries()) {
if (now > val.expires) { if (now > val.expires) {
ipMap.delete(key); ipMap.delete(key);
@@ -39,5 +38,5 @@ export function middleware(request: NextRequest) {
} }
export const config = { export const config = {
matcher: '/:path*', matcher: ['/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|ico)$).*)'],
}; };

View File

@@ -2,7 +2,7 @@ import type { NextConfig } from "next";
const nextConfig: NextConfig = { const nextConfig: NextConfig = {
output: "standalone", output: "standalone",
/* config options here */ serverExternalPackages: ['better-sqlite3'],
headers: async () => { headers: async () => {
return [ return [
{ {
@@ -14,7 +14,7 @@ const nextConfig: NextConfig = {
}, },
{ {
key: 'Content-Security-Policy', key: 'Content-Security-Policy',
value: "default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:;", value: "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:; connect-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self';",
}, },
{ {
key: 'X-Content-Type-Options', key: 'X-Content-Type-Options',

441
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "0.1.0", "version": "0.1.0",
"dependencies": { "dependencies": {
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"better-sqlite3": "^12.6.2",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"next": "16.1.6", "next": "16.1.6",
@@ -21,6 +22,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",
@@ -1496,6 +1498,16 @@
"tslib": "^2.4.0" "tslib": "^2.4.0"
} }
}, },
"node_modules/@types/better-sqlite3": {
"version": "7.6.13",
"resolved": "https://registry.npmjs.org/@types/better-sqlite3/-/better-sqlite3-7.6.13.tgz",
"integrity": "sha512-NMv9ASNARoKksWtsq/SHakpYAYnhBrQgGD8zkLYk/jaK8jUGn08CfEdTRgYhMypUQAfzSP8W6gNLe0q19/t4VA==",
"dev": true,
"license": "MIT",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/debug": { "node_modules/@types/debug": {
"version": "4.1.12", "version": "4.1.12",
"resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.12.tgz",
@@ -2393,6 +2405,26 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "dev": true
}, },
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/baseline-browser-mapping": { "node_modules/baseline-browser-mapping": {
"version": "2.9.19", "version": "2.9.19",
"resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.19.tgz",
@@ -2401,6 +2433,40 @@
"baseline-browser-mapping": "dist/cli.js" "baseline-browser-mapping": "dist/cli.js"
} }
}, },
"node_modules/better-sqlite3": {
"version": "12.6.2",
"resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-12.6.2.tgz",
"integrity": "sha512-8VYKM3MjCa9WcaSAI3hzwhmyHVlH8tiGFwf0RlTsZPWJ1I5MkzjiudCo4KC4DxOaL/53A5B1sI/IbldNFDbsKA==",
"hasInstallScript": true,
"license": "MIT",
"dependencies": {
"bindings": "^1.5.0",
"prebuild-install": "^7.1.1"
},
"engines": {
"node": "20.x || 22.x || 23.x || 24.x || 25.x"
}
},
"node_modules/bindings": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
"integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==",
"license": "MIT",
"dependencies": {
"file-uri-to-path": "1.0.0"
}
},
"node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"license": "MIT",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.12", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
@@ -2456,6 +2522,30 @@
"node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
} }
}, },
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/call-bind": { "node_modules/call-bind": {
"version": "1.0.8", "version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -2592,6 +2682,12 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/chownr": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
"license": "ISC"
},
"node_modules/client-only": { "node_modules/client-only": {
"version": "0.0.1", "version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
@@ -2769,6 +2865,30 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/decompress-response": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz",
"integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==",
"license": "MIT",
"dependencies": {
"mimic-response": "^3.1.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/deep-extend": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz",
"integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/deep-is": { "node_modules/deep-is": {
"version": "0.1.4", "version": "0.1.4",
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -2821,7 +2941,6 @@
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
"integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==",
"devOptional": true,
"engines": { "engines": {
"node": ">=8" "node": ">=8"
} }
@@ -2876,6 +2995,15 @@
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true "dev": true
}, },
"node_modules/end-of-stream": {
"version": "1.4.5",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz",
"integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==",
"license": "MIT",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/enhanced-resolve": { "node_modules/enhanced-resolve": {
"version": "5.19.0", "version": "5.19.0",
"resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.19.0.tgz",
@@ -3608,6 +3736,15 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/expand-template": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz",
"integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==",
"license": "(MIT OR WTFPL)",
"engines": {
"node": ">=6"
}
},
"node_modules/extend": { "node_modules/extend": {
"version": "3.0.2", "version": "3.0.2",
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
@@ -3691,6 +3828,12 @@
"node": ">=16.0.0" "node": ">=16.0.0"
} }
}, },
"node_modules/file-uri-to-path": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz",
"integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==",
"license": "MIT"
},
"node_modules/fill-range": { "node_modules/fill-range": {
"version": "7.1.1", "version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3753,6 +3896,12 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/fs-constants": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
"license": "MIT"
},
"node_modules/function-bind": { "node_modules/function-bind": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
@@ -3875,6 +4024,12 @@
"url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1"
} }
}, },
"node_modules/github-from-package": {
"version": "0.0.0",
"resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz",
"integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==",
"license": "MIT"
},
"node_modules/github-slugger": { "node_modules/github-slugger": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz", "resolved": "https://registry.npmjs.org/github-slugger/-/github-slugger-2.0.0.tgz",
@@ -4175,6 +4330,26 @@
"hermes-estree": "0.25.1" "hermes-estree": "0.25.1"
} }
}, },
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "BSD-3-Clause"
},
"node_modules/ignore": { "node_modules/ignore": {
"version": "5.3.2", "version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -4209,6 +4384,18 @@
"node": ">=0.8.19" "node": ">=0.8.19"
} }
}, },
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"license": "ISC"
},
"node_modules/ini": {
"version": "1.3.8",
"resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
"integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
"license": "ISC"
},
"node_modules/inline-style-parser": { "node_modules/inline-style-parser": {
"version": "0.2.7", "version": "0.2.7",
"resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz", "resolved": "https://registry.npmjs.org/inline-style-parser/-/inline-style-parser-0.2.7.tgz",
@@ -6159,6 +6346,18 @@
"node": ">=8.6" "node": ">=8.6"
} }
}, },
"node_modules/mimic-response": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz",
"integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==",
"license": "MIT",
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": { "node_modules/minimatch": {
"version": "3.1.2", "version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -6175,11 +6374,16 @@
"version": "1.2.8", "version": "1.2.8",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz",
"integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==",
"dev": true,
"funding": { "funding": {
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/mkdirp-classic": {
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
"integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==",
"license": "MIT"
},
"node_modules/ms": { "node_modules/ms": {
"version": "2.1.3", "version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -6202,6 +6406,12 @@
"node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
} }
}, },
"node_modules/napi-build-utils": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz",
"integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==",
"license": "MIT"
},
"node_modules/napi-postinstall": { "node_modules/napi-postinstall": {
"version": "0.3.4", "version": "0.3.4",
"resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz", "resolved": "https://registry.npmjs.org/napi-postinstall/-/napi-postinstall-0.3.4.tgz",
@@ -6322,6 +6532,30 @@
"node": "^10 || ^12 || >=14" "node": "^10 || ^12 || >=14"
} }
}, },
"node_modules/node-abi": {
"version": "3.87.0",
"resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz",
"integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==",
"license": "MIT",
"dependencies": {
"semver": "^7.3.5"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-abi/node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
"integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==",
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/node-releases": { "node_modules/node-releases": {
"version": "2.0.27", "version": "2.0.27",
"resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz",
@@ -6443,6 +6677,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"license": "ISC",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/optionator": { "node_modules/optionator": {
"version": "0.9.4", "version": "0.9.4",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
@@ -6632,6 +6875,32 @@
"node": ">=4" "node": ">=4"
} }
}, },
"node_modules/prebuild-install": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz",
"integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==",
"license": "MIT",
"dependencies": {
"detect-libc": "^2.0.0",
"expand-template": "^2.0.3",
"github-from-package": "0.0.0",
"minimist": "^1.2.3",
"mkdirp-classic": "^0.5.3",
"napi-build-utils": "^2.0.0",
"node-abi": "^3.3.0",
"pump": "^3.0.0",
"rc": "^1.2.7",
"simple-get": "^4.0.0",
"tar-fs": "^2.0.0",
"tunnel-agent": "^0.6.0"
},
"bin": {
"prebuild-install": "bin.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/prelude-ls": { "node_modules/prelude-ls": {
"version": "1.2.1", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
@@ -6661,6 +6930,16 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/pump": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz",
"integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==",
"license": "MIT",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/punycode": { "node_modules/punycode": {
"version": "2.3.1", "version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -6690,6 +6969,30 @@
} }
] ]
}, },
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
"integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==",
"license": "(BSD-2-Clause OR MIT OR Apache-2.0)",
"dependencies": {
"deep-extend": "^0.6.0",
"ini": "~1.3.0",
"minimist": "^1.2.0",
"strip-json-comments": "~2.0.1"
},
"bin": {
"rc": "cli.js"
}
},
"node_modules/rc/node_modules/strip-json-comments": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
"integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/react": { "node_modules/react": {
"version": "19.2.3", "version": "19.2.3",
"resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz", "resolved": "https://registry.npmjs.org/react/-/react-19.2.3.tgz",
@@ -6715,6 +7018,20 @@
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
"dev": true "dev": true
}, },
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"license": "MIT",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/recma-build-jsx": { "node_modules/recma-build-jsx": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz", "resolved": "https://registry.npmjs.org/recma-build-jsx/-/recma-build-jsx-1.0.0.tgz",
@@ -7032,6 +7349,26 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/safe-push-apply": { "node_modules/safe-push-apply": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -7286,6 +7623,51 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT"
},
"node_modules/simple-get": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz",
"integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"license": "MIT",
"dependencies": {
"decompress-response": "^6.0.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/source-map": { "node_modules/source-map": {
"version": "0.7.6", "version": "0.7.6",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz",
@@ -7335,6 +7717,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"license": "MIT",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string.prototype.includes": { "node_modules/string.prototype.includes": {
"version": "2.0.1", "version": "2.0.1",
"resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz", "resolved": "https://registry.npmjs.org/string.prototype.includes/-/string.prototype.includes-2.0.1.tgz",
@@ -7564,6 +7955,34 @@
"url": "https://opencollective.com/webpack" "url": "https://opencollective.com/webpack"
} }
}, },
"node_modules/tar-fs": {
"version": "2.1.4",
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz",
"integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==",
"license": "MIT",
"dependencies": {
"chownr": "^1.1.1",
"mkdirp-classic": "^0.5.2",
"pump": "^3.0.0",
"tar-stream": "^2.1.4"
}
},
"node_modules/tar-stream": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
"license": "MIT",
"dependencies": {
"bl": "^4.0.3",
"end-of-stream": "^1.4.1",
"fs-constants": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1"
},
"engines": {
"node": ">=6"
}
},
"node_modules/tinyglobby": { "node_modules/tinyglobby": {
"version": "0.2.15", "version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -7680,6 +8099,18 @@
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz",
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="
}, },
"node_modules/tunnel-agent": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
"integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==",
"license": "Apache-2.0",
"dependencies": {
"safe-buffer": "^5.0.1"
},
"engines": {
"node": "*"
}
},
"node_modules/type-check": { "node_modules/type-check": {
"version": "0.4.0", "version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -8189,6 +8620,12 @@
"node": ">=0.10.0" "node": ">=0.10.0"
} }
}, },
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"license": "ISC"
},
"node_modules/yallist": { "node_modules/yallist": {
"version": "3.1.1", "version": "3.1.1",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",

View File

@@ -10,6 +10,7 @@
}, },
"dependencies": { "dependencies": {
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
"better-sqlite3": "^12.6.2",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"gray-matter": "^4.0.3", "gray-matter": "^4.0.3",
"next": "16.1.6", "next": "16.1.6",
@@ -22,6 +23,7 @@
}, },
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4", "@tailwindcss/postcss": "^4",
"@types/better-sqlite3": "^7.6.13",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "^19",
"@types/react-dom": "^19", "@types/react-dom": "^19",

View File

@@ -1 +0,0 @@
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>

Before

Width:  |  Height:  |  Size: 391 B

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>

Before

Width:  |  Height:  |  Size: 1.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 307 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

BIN
public/images/star.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>

Before

Width:  |  Height:  |  Size: 1.3 KiB

BIN
public/profile.jpeg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 180 KiB

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>

Before

Width:  |  Height:  |  Size: 128 B

View File

@@ -1 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>

Before

Width:  |  Height:  |  Size: 385 B