shivam + claude changes
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
All checks were successful
Deploy Website / build-and-deploy (push) Successful in 28s
This commit is contained in:
107
.claude/agents/personal-site-feature-dev.md
Normal file
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.
|
||||||
@@ -1,30 +1,37 @@
|
|||||||
import { NextResponse } from 'next/server';
|
import { NextResponse } from 'next/server';
|
||||||
|
|
||||||
|
const ANALYTICS_KEY = process.env.ANALYTICS_KEY || 'default-analytics-key';
|
||||||
|
|
||||||
export async function POST(req: Request) {
|
export async function POST(req: Request) {
|
||||||
try {
|
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();
|
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 headers = req.headers;
|
||||||
|
const forwarded = headers.get('x-forwarded-for');
|
||||||
|
const ip = forwarded ? forwarded.split(',')[0].trim() : 'unknown';
|
||||||
|
|
||||||
// Get IP here since we are the public facing entity
|
|
||||||
const ip = headers.get('x-forwarded-for') || 'unknown';
|
|
||||||
|
|
||||||
// Relay to Admin Dashboard (Internal Docker Network)
|
|
||||||
// admin_dash must be reachable. If in same docker network, use container name.
|
|
||||||
// If separate, use host.docker.internal or configured URL.
|
|
||||||
const adminUrl = process.env.ADMIN_DASH_URL || 'http://admin_dash:3000/api/track';
|
const adminUrl = process.env.ADMIN_DASH_URL || 'http://admin_dash:3000/api/track';
|
||||||
|
|
||||||
// We fire and forget - don't wait for response to keep this fast
|
|
||||||
fetch(adminUrl, {
|
fetch(adminUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ ...body, ip }), // Pass IP along
|
body: JSON.stringify({ path: body.path, timestamp: body.timestamp, ip }),
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
'X-Forwarded-For': ip // Preserve IP
|
'X-Forwarded-For': ip,
|
||||||
},
|
},
|
||||||
}).catch(e => console.error('Relay failed', e));
|
}).catch(e => console.error('Relay failed', e));
|
||||||
|
|
||||||
return NextResponse.json({ success: true });
|
return NextResponse.json({ success: true });
|
||||||
} catch (error) {
|
} catch {
|
||||||
return NextResponse.json({ success: false });
|
return NextResponse.json({ success: false, error: 'Bad request' }, { status: 400 });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
app/blog/[slug]/loading.tsx
Normal file
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
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
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import { Inter } from "next/font/google";
|
|||||||
import "./globals.css";
|
import "./globals.css";
|
||||||
import { Navbar } from "../components/layout/Navbar";
|
import { Navbar } from "../components/layout/Navbar";
|
||||||
import { Footer } from "../components/layout/Footer";
|
import { Footer } from "../components/layout/Footer";
|
||||||
|
import { Analytics } from "../components/Analytics";
|
||||||
|
|
||||||
const inter = Inter({
|
const inter = Inter({
|
||||||
variable: "--font-inter",
|
variable: "--font-inter",
|
||||||
@@ -11,12 +12,24 @@ const inter = Inter({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Akshay Kolli",
|
metadataBase: new URL("https://akkolli.net"),
|
||||||
description: "My personal website",
|
title: {
|
||||||
|
default: "Akshay Kolli",
|
||||||
|
template: "%s | Akshay Kolli",
|
||||||
|
},
|
||||||
|
description: "Personal website of Akshay Kolli — CS PhD Student at UMass Lowell researching World Models, Reinforcement Learning, and Multi-Agent Systems.",
|
||||||
|
openGraph: {
|
||||||
|
title: "Akshay Kolli",
|
||||||
|
description: "CS PhD Student at UMass Lowell researching World Models, Reinforcement Learning, and Multi-Agent Systems.",
|
||||||
|
siteName: "Akshay Kolli",
|
||||||
|
type: "website",
|
||||||
|
},
|
||||||
|
twitter: {
|
||||||
|
card: "summary",
|
||||||
|
creator: "@thekolliakshay",
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
import { Analytics } from "../components/Analytics";
|
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
}: Readonly<{
|
}: Readonly<{
|
||||||
@@ -27,8 +40,11 @@ export default function RootLayout({
|
|||||||
<body
|
<body
|
||||||
className={`${inter.variable} antialiased flex flex-col min-h-screen`}
|
className={`${inter.variable} antialiased flex flex-col min-h-screen`}
|
||||||
>
|
>
|
||||||
|
<a href="#main-content" className="sr-only focus:not-sr-only focus:absolute focus:top-4 focus:left-4 focus:z-[100] focus:bg-white focus:dark:bg-zinc-900 focus:px-4 focus:py-2 focus:text-sm focus:font-medium focus:rounded focus:shadow">
|
||||||
|
Skip to content
|
||||||
|
</a>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main className="flex-grow">
|
<main id="main-content" className="flex-grow">
|
||||||
{children}
|
{children}
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|||||||
18
app/not-found.tsx
Normal file
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>
|
||||||
|
);
|
||||||
|
}
|
||||||
14
app/page.tsx
14
app/page.tsx
@@ -1,6 +1,10 @@
|
|||||||
import Image from "next/image";
|
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">
|
||||||
|
|
||||||
@@ -47,7 +51,7 @@ export default function Home() {
|
|||||||
<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">
|
<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>
|
||||||
@@ -57,7 +61,7 @@ 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 deep learning happenings <a href="/blog/testing-layout" className="text-zinc-600 dark:text-zinc-400 underline decoration-zinc-300 dark:decoration-zinc-700 hover:text-zinc-900 dark:hover:text-zinc-100">Read latest →</a>
|
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>
|
||||||
@@ -65,13 +69,7 @@ export default function Home() {
|
|||||||
{/* 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">
|
<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.jpeg" alt="Akshay Kolli" fill className="object-cover" priority />
|
||||||
|
|
||||||
{/* Placeholder */}
|
|
||||||
{/* <div className="absolute inset-0 flex items-center justify-center text-zinc-300 dark:text-zinc-700 border-2 border-dashed border-zinc-200 dark:border-zinc-800 m-2 rounded-xl">
|
|
||||||
<span className="font-mono text-sm">profile.jpg</span>
|
|
||||||
</div> */}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,14 @@
|
|||||||
|
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">
|
||||||
|
|||||||
11
app/robots.ts
Normal file
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
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,
|
||||||
|
];
|
||||||
|
}
|
||||||
@@ -14,7 +14,10 @@ export function Analytics() {
|
|||||||
await fetch('/api/analytics', {
|
await fetch('/api/analytics', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: JSON.stringify({ path: pathname, timestamp: Date.now() }),
|
body: JSON.stringify({ path: pathname, timestamp: Date.now() }),
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'X-Analytics-Key': process.env.NEXT_PUBLIC_ANALYTICS_KEY || 'default-analytics-key',
|
||||||
|
},
|
||||||
keepalive: true,
|
keepalive: true,
|
||||||
});
|
});
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -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/akkolli" 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://x.com/thekolliakshay" 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) => (
|
||||||
|
|||||||
15
lib/mdx.ts
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)$).*)'],
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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',
|
||||||
|
|||||||
@@ -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 |
@@ -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 |
@@ -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 |
Reference in New Issue
Block a user