Compare commits
22 Commits
4ae092ab53
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa7fe8ecc7 | ||
|
|
c3f5c875e2 | ||
|
|
6da3a35832 | ||
|
|
f647a4ad90 | ||
|
|
c4fa3976f9 | ||
|
|
19d0ad59a0 | ||
|
|
21859a250f | ||
|
|
8c1d03ac3f | ||
|
|
9f8ff8befe | ||
|
|
2a8df25c16 | ||
|
|
041a22e0d2 | ||
|
|
9e367010d9 | ||
|
|
9cf5f9620c | ||
|
|
4a01928fe7 | ||
|
|
73eb9264df | ||
|
|
c0ffc01b88 | ||
|
|
3174638dd3 | ||
|
|
2c3b519af3 | ||
|
|
df80a1985e | ||
|
|
9b8a25f5c8 | ||
|
|
b0ad1287fb | ||
|
|
35f7254734 |
107
.claude/agents/personal-site-feature-dev.md
Normal 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.
|
||||||
@@ -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 \
|
||||||
|
-v /server_storage:/server_storage \
|
||||||
my-website:latest
|
my-website:latest
|
||||||
106
ADMIN_DASH_INTEGRATION.md
Normal 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:
|
||||||
|
```
|
||||||
@@ -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
|
||||||
|
|||||||
44
app/api/analytics/route.ts
Normal 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 });
|
||||||
|
}
|
||||||
|
}
|
||||||
16
app/blog/[slug]/loading.tsx
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -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
@@ -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>
|
||||||
|
);
|
||||||
|
}
|
||||||
BIN
app/favicon.ico
|
Before Width: | Height: | Size: 25 KiB After Width: | Height: | Size: 33 KiB |
@@ -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;
|
||||||
|
|||||||
@@ -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,8 +12,22 @@ const inter = Inter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
|
metadataBase: new URL("https://akkolli.net"),
|
||||||
|
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",
|
title: "Akshay Kolli",
|
||||||
description: "My personal website",
|
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",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
@@ -25,11 +40,15 @@ 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 />
|
||||||
|
<Analytics />
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
);
|
);
|
||||||
|
|||||||
18
app/not-found.tsx
Normal 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'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"
|
||||||
|
>
|
||||||
|
← Back to home
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
28
app/page.tsx
@@ -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 →</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 →</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>
|
||||||
|
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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,
|
||||||
|
];
|
||||||
|
}
|
||||||
33
components/Analytics.tsx
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect } from 'react';
|
||||||
|
import { usePathname } from 'next/navigation';
|
||||||
|
|
||||||
|
export function Analytics() {
|
||||||
|
const pathname = usePathname();
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Send beacon on mount and path change
|
||||||
|
const sendBeacon = async () => {
|
||||||
|
try {
|
||||||
|
// 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',
|
||||||
|
'X-Analytics-Key': process.env.NEXT_PUBLIC_ANALYTICS_KEY || 'default-analytics-key',
|
||||||
|
},
|
||||||
|
keepalive: true,
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
// Fail silently
|
||||||
|
console.error('Analytics fail', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
sendBeacon();
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -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) => (
|
||||||
|
|||||||
51
content/posts/blackwell_datacenter_vs_geforce.mdx
Normal 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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
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`.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
@@ -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>
|
|
||||||
52
lib/db.ts
Normal 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);
|
||||||
|
}
|
||||||
15
lib/mdx.ts
@@ -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 {
|
||||||
|
|||||||
@@ -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)$).*)'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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
@@ -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",
|
||||||
|
|||||||
@@ -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",
|
||||||
|
|||||||
@@ -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 |
@@ -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 |
BIN
public/images/1_blackwell_dc_vs_gf/5090_65536.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/5090_65536_cropped.png
Normal file
|
After Width: | Height: | Size: 8.2 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/b200_32768.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/b200_65536.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/b200_65536_cropped.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/b200_unable_to_profile.png
Normal file
|
After Width: | Height: | Size: 41 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/geforce_ncu.png
Normal file
|
After Width: | Height: | Size: 307 KiB |
BIN
public/images/1_blackwell_dc_vs_gf/nvtop_b200.png
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
public/images/star.jpg
Normal file
|
After Width: | Height: | Size: 33 KiB |
@@ -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
|
After Width: | Height: | Size: 180 KiB |
@@ -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 |
@@ -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 |